Add: style settings
This commit is contained in:
parent
6c13db1468
commit
6a324eb151
|
@ -375,6 +375,12 @@
|
|||
},
|
||||
"description": "Title of the page for editing styles"
|
||||
},
|
||||
"editorCodeLabel": {
|
||||
"message": "Code"
|
||||
},
|
||||
"editorSettingLabel": {
|
||||
"message": "Settings"
|
||||
},
|
||||
"enableStyleLabel": {
|
||||
"message": "Enable",
|
||||
"description": "Label for the button to enable a style"
|
||||
|
@ -1634,6 +1640,30 @@
|
|||
"message": "As a security precaution, the browser prohibits extensions from affecting its built-in pages (like chrome://version, the standard new tab page as of Chrome 61, about:addons, and so on) as well as other extensions' pages. Each browser also restricts access to its own extensions gallery (like Chrome Web Store or AMO).",
|
||||
"description": "Sub-note in the toolbar pop-up when on a URL Stylus can't affect"
|
||||
},
|
||||
"styleOriginLabel": {
|
||||
"message": "Style origin"
|
||||
},
|
||||
"styleUpdateUrlLabel": {
|
||||
"message": "Update URL"
|
||||
},
|
||||
"stylePreferSchemeLabel": {
|
||||
"message": "Dark/Light mode"
|
||||
},
|
||||
"styleIncludeLabel": {
|
||||
"message": "User defined inclusion"
|
||||
},
|
||||
"styleIncludeNewLabel": {
|
||||
"message": "Add new inclusion rule"
|
||||
},
|
||||
"styleExcludeLabel": {
|
||||
"message": "User defined exclusion"
|
||||
},
|
||||
"styleExcludeNewLabel": {
|
||||
"message": "Add new exclusion rule"
|
||||
},
|
||||
"stylePriorityLabel": {
|
||||
"message": "Priority"
|
||||
},
|
||||
"syncDropboxDeprecated": {
|
||||
"message": "Dropbox import/export is replaced by a more advanced style sync in the options page."
|
||||
},
|
||||
|
|
|
@ -297,6 +297,13 @@ const styleMan = (() => {
|
|||
removeExclusion: removeIncludeExclude.bind(null, 'exclusions'),
|
||||
/** @returns {Promise<?StyleObj>} */
|
||||
removeInclusion: removeIncludeExclude.bind(null, 'inclusions'),
|
||||
|
||||
async config(id, prop, value) {
|
||||
if (ready.then) await ready;
|
||||
const style = Object.assign({}, id2style(id));
|
||||
style[prop] = value;
|
||||
return saveStyle(style, {reason: 'config', codeIsUpdated: false});
|
||||
},
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
|
52
edit.html
52
edit.html
|
@ -17,6 +17,7 @@
|
|||
<script src="content/apply.js"></script>
|
||||
|
||||
<script src="js/sections-util.js"></script>
|
||||
<script src="js/event-emitter.js"></script>
|
||||
<script src="edit/codemirror-themes.js"></script> <!-- must precede base.js -->
|
||||
<script src="edit/base.js"></script>
|
||||
|
||||
|
@ -61,6 +62,7 @@
|
|||
<script src="edit/sections-editor-section.js"></script>
|
||||
<script src="edit/sections-editor.js"></script>
|
||||
<script src="edit/usw-integration.js"></script>
|
||||
<script src="edit/settings.js"></script>
|
||||
<script src="edit/edit.js"></script>
|
||||
|
||||
<template data-id="appliesTo">
|
||||
|
@ -239,6 +241,8 @@
|
|||
<link href="js/color/color-picker.css" rel="stylesheet">
|
||||
<link href="edit/codemirror-default.css" rel="stylesheet">
|
||||
<link href="edit/edit.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="edit/tab.css">
|
||||
<link rel="stylesheet" href="edit/settings.css">
|
||||
</head>
|
||||
|
||||
<body id="stylus-edit">
|
||||
|
@ -433,7 +437,52 @@
|
|||
target="_blank"></a>
|
||||
</div>
|
||||
</div>
|
||||
<section id="sections"></section>
|
||||
<div class="main tab-container">
|
||||
<div class="tab-bar">
|
||||
<div class="tab-bar-item active" i18n-text="editorCodeLabel"></div>
|
||||
<div class="tab-bar-item" i18n-text="editorSettingLabel"></div>
|
||||
</div>
|
||||
<div class="tab-panel">
|
||||
<section id="sections" class="active"></section>
|
||||
<fieldset class="style-settings" disabled>
|
||||
<!-- <label class="style-origin">
|
||||
<span class="form-label" i18n-text="styleOriginLabel"></span>
|
||||
<input id="styleOrigin" type="text">
|
||||
</label> -->
|
||||
<label class="form-group style-update-url">
|
||||
<span class="form-label" i18n-text="styleUpdateUrlLabel"></span>
|
||||
<input type="text">
|
||||
</label>
|
||||
<label class="form-group style-prefer-scheme">
|
||||
<!-- FIXME: should we use a different message from install page? -->
|
||||
<span class="form-label" i18n-text="installPreferSchemeLabel"></span>
|
||||
<select>
|
||||
<option value="none" i18n-text="installPreferSchemeNone"></option>
|
||||
<option value="dark" i18n-text="installPreferSchemeDark"></option>
|
||||
<option value="light" i18n-text="installPreferSchemeLight"></option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="form-group style-include">
|
||||
<div class="form-label" i18n-text="styleIncludeLabel"></div>
|
||||
<div class="rule-table"></div>
|
||||
<button i18n-text="styleIncludeNewLabel" class="add-rule"></button>
|
||||
</div>
|
||||
<div class="form-group style-exclude">
|
||||
<div class="form-label" i18n-text="styleExcludeLabel"></div>
|
||||
<div class="rule-table"></div>
|
||||
<button i18n-text="styleExcludeNewLabel" class="add-rule"></button>
|
||||
</div>
|
||||
<div class="form-group style-priority">
|
||||
<div class="form-label" i18n-text="stylePriorityLabel"></div>
|
||||
<input type="number">
|
||||
</div>
|
||||
<!-- <label class="style-always-important">
|
||||
<input type="checkbox">
|
||||
<span class="form-label" i18n-text="styleAlwaysImportantLabel"></span>
|
||||
</label> -->
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div id="help-popup">
|
||||
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
|
||||
<div class="contents"></div>
|
||||
|
@ -478,5 +527,6 @@
|
|||
</symbol>
|
||||
|
||||
</svg>
|
||||
<script src="edit/tab.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -14,13 +14,14 @@
|
|||
tryJSONparse
|
||||
tryURL
|
||||
*/// toolbox.js
|
||||
/* global EventEmitter */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @type Editor
|
||||
* @namespace Editor
|
||||
*/
|
||||
const editor = {
|
||||
const editor = Object.assign(EventEmitter(), {
|
||||
style: null,
|
||||
dirty: DirtyReporter(),
|
||||
isUsercss: false,
|
||||
|
@ -47,7 +48,7 @@ const editor = {
|
|||
customName || name || t('styleMissingName')
|
||||
} - Stylus`; // the suffix enables external utilities to process our windows e.g. pin on top
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
//#region pre-init
|
||||
|
||||
|
|
|
@ -105,8 +105,11 @@ label {
|
|||
#header h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
#sections {
|
||||
.main {
|
||||
padding-left: 280px;
|
||||
height: 100%;
|
||||
}
|
||||
#sections {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
10
edit/edit.js
10
edit/edit.js
|
@ -11,6 +11,7 @@
|
|||
/* global linterMan */
|
||||
/* global prefs */
|
||||
/* global t */// localization.js
|
||||
/* global StyleSettings */// settings.js
|
||||
'use strict';
|
||||
|
||||
//#region init
|
||||
|
@ -18,6 +19,7 @@
|
|||
baseInit.ready.then(async () => {
|
||||
await waitForSheet();
|
||||
(editor.isUsercss ? SourceEditor : SectionsEditor)();
|
||||
StyleSettings(editor);
|
||||
await editor.ready;
|
||||
editor.ready = true;
|
||||
editor.dirty.onChange(editor.updateDirty);
|
||||
|
@ -59,7 +61,8 @@ const IGNORE_UPDATE_REASONS = [
|
|||
'editPreview',
|
||||
'editPreviewEnd',
|
||||
'editSave',
|
||||
'config',
|
||||
// https://github.com/openstyles/stylus/issues/807 is closed without fix
|
||||
// 'config,
|
||||
];
|
||||
|
||||
msg.onExtension(request => {
|
||||
|
@ -68,7 +71,10 @@ msg.onExtension(request => {
|
|||
case 'styleUpdated':
|
||||
if (editor.style.id === style.id && !IGNORE_UPDATE_REASONS.includes(request.reason)) {
|
||||
Promise.resolve(request.codeIsUpdated === false ? style : API.styles.get(style.id))
|
||||
.then(newStyle => editor.replaceStyle(newStyle, request.codeIsUpdated));
|
||||
.then(newStyle => {
|
||||
editor.replaceStyle(newStyle, request.codeIsUpdated);
|
||||
editor.emit('styleChange', newStyle, request.reason);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'styleDeleted':
|
||||
|
|
|
@ -111,6 +111,9 @@ function SectionsEditor() {
|
|||
}
|
||||
newStyle = await API.styles.editSave(newStyle);
|
||||
destroyRemovedSections();
|
||||
if (!style.id) {
|
||||
editor.emit('styleChange', newStyle, 'new');
|
||||
}
|
||||
sessionStore.justEditedStyleId = newStyle.id;
|
||||
editor.replaceStyle(newStyle, false);
|
||||
},
|
||||
|
|
31
edit/settings.css
Normal file
31
edit/settings.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
.style-settings {
|
||||
padding: 0.7rem 1.7rem;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.form-group {
|
||||
display: block;
|
||||
margin: .6em 0;
|
||||
padding: 0;
|
||||
}
|
||||
.form-label {
|
||||
display: inline-block;
|
||||
margin: .3em 0;
|
||||
}
|
||||
[disabled] .form-label {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.form-group input[type=text],
|
||||
.form-group input[type=number],
|
||||
.form-group select {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.rule-table {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(min-content, 1fr) min-content;
|
||||
grid-gap: .3em;
|
||||
margin: .3em 0;
|
||||
}
|
||||
|
81
edit/settings.js
Normal file
81
edit/settings.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
/* global API */
|
||||
/* exported StyleSettings */
|
||||
'use strict';
|
||||
|
||||
function StyleSettings(editor) {
|
||||
let {style} = editor;
|
||||
|
||||
const inputs = [
|
||||
createInput('.style-update-url input', () => style.updateUrl || '',
|
||||
e => API.styles.config(style.id, 'updateUrl', e.target.value)),
|
||||
createInput('.style-prefer-scheme select', () => style.preferScheme || 'none',
|
||||
e => API.styles.config(style.id, 'preferScheme', e.target.value)),
|
||||
createInput('.style-priority input', () => style.priority || 0,
|
||||
e => API.styles.config(style.id, 'priority', e.target.valueAsNumber)),
|
||||
createRuleTable(document.querySelector('.style-include'), 'inclusions'),
|
||||
createRuleTable(document.querySelector('.style-exclude'), 'exclusions'),
|
||||
];
|
||||
|
||||
update(style);
|
||||
|
||||
editor.on('styleChange', update);
|
||||
|
||||
function update(newStyle, reason) {
|
||||
if (!newStyle.id) return;
|
||||
if (reason === 'editSave' || reason === 'config') return;
|
||||
style = newStyle;
|
||||
document.querySelector('.style-settings').disabled = false;
|
||||
inputs.forEach(i => i.update());
|
||||
}
|
||||
|
||||
function createInput(selector, getter, setter) {
|
||||
const el = document.querySelector(selector);
|
||||
el.addEventListener('change', setter);
|
||||
return {
|
||||
update() {
|
||||
el.value = getter();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createRuleTable(container, type) {
|
||||
const table = container.querySelector('.rule-table');
|
||||
container.querySelector('.add-rule').addEventListener('click', addRule);
|
||||
return {update};
|
||||
|
||||
function update() {
|
||||
// TODO: don't recreate everything
|
||||
table.innerHTML = '';
|
||||
if (!style[type]) {
|
||||
style[type] = [];
|
||||
}
|
||||
style[type].forEach((rule, i) => {
|
||||
const input = document.createElement('input');
|
||||
input.value = rule;
|
||||
input.addEventListener('change', () => {
|
||||
style[type][i] = input.value;
|
||||
API.styles.config(style.id, type, style[type]);
|
||||
});
|
||||
table.append(input);
|
||||
|
||||
const delButton = document.createElement('button');
|
||||
delButton.textContent = 'x';
|
||||
delButton.addEventListener('click', () => {
|
||||
style[type].splice(i, 1);
|
||||
API.styles.config(style.id, type, style[type]);
|
||||
update();
|
||||
});
|
||||
table.append(delButton);
|
||||
});
|
||||
}
|
||||
|
||||
function addRule() {
|
||||
if (!style[type]) {
|
||||
style[type] = [];
|
||||
}
|
||||
style[type].push('');
|
||||
API.styles.config(style.id, type, style[type]);
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,6 +71,9 @@ function SourceEditor() {
|
|||
} else {
|
||||
res = await API.usercss.editSave({customName, enabled, id, sourceCode});
|
||||
// Awaiting inside `try` so that exceptions go to our `catch`
|
||||
if (!id) {
|
||||
editor.emit('styleChange', res.style, 'new');
|
||||
}
|
||||
await replaceStyle(res.style);
|
||||
}
|
||||
showLog(res);
|
||||
|
|
28
edit/tab.css
Normal file
28
edit/tab.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
.tab-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid silver;
|
||||
padding: 5px 5px 0 15px;
|
||||
}
|
||||
.tab-bar-item {
|
||||
margin: 0 0.3em;
|
||||
padding: 0.3em 0.6em;
|
||||
border: 1px solid silver;
|
||||
border-bottom: none;
|
||||
background: silver;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tab-bar-item.active {
|
||||
background: white;
|
||||
}
|
||||
.tab-panel {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.tab-panel > :not(.active) {
|
||||
display: none;
|
||||
}
|
||||
|
19
edit/tab.js
Normal file
19
edit/tab.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
(() => {
|
||||
for (const container of document.querySelectorAll('.tab-container')) {
|
||||
init(container);
|
||||
}
|
||||
|
||||
function init(container) {
|
||||
const tabButtons = [...container.querySelector('.tab-bar').children];
|
||||
const tabPanels = [...container.querySelector('.tab-panel').children];
|
||||
tabButtons.forEach((button, i) => button.addEventListener('click', () => activate(i)));
|
||||
|
||||
function activate(index) {
|
||||
const toggleActive = (button, i) => button.classList.toggle('active', i === index);
|
||||
tabButtons.forEach(toggleActive);
|
||||
tabPanels.forEach(toggleActive);
|
||||
}
|
||||
}
|
||||
})();
|
37
js/event-emitter.js
Normal file
37
js/event-emitter.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* exported EventEmitter */
|
||||
'use strict';
|
||||
|
||||
function EventEmitter() {
|
||||
const listeners = new Map();
|
||||
return {
|
||||
on(ev, cb, opt) {
|
||||
if (!listeners.has(ev)) {
|
||||
listeners.set(ev, new Map());
|
||||
}
|
||||
listeners.get(ev).set(cb, opt);
|
||||
if (opt && opt.runNow) {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
off(ev, cb) {
|
||||
const cbs = listeners.get(ev);
|
||||
if (cbs) {
|
||||
cbs.delete(cb);
|
||||
}
|
||||
},
|
||||
emit(ev, ...args) {
|
||||
const cbs = listeners.get(ev);
|
||||
if (!cbs) return;
|
||||
for (const [cb, opt] of cbs.entries()) {
|
||||
try {
|
||||
cb(...args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
if (opt && opt.once) {
|
||||
cbs.delete(cb);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user