Add: style settings (#1358)
* Add: style settings * Change: use radio instead of select for dark/light mode * Change: x -> Delete * Change: (in|ex)clusion messages * Fix: avoid extra space when there is no rule * Fix: UI in mobile * Change: delete priority * Change: use textarea for include/exclude, remove isCodeUpdated * Fix: separate toggle * Fix: minor * Fix: remove codeIsUpdated in styleman
This commit is contained in:
parent
a59aab73fc
commit
9d1243073b
|
@ -375,6 +375,12 @@
|
||||||
},
|
},
|
||||||
"description": "Title of the page for editing styles"
|
"description": "Title of the page for editing styles"
|
||||||
},
|
},
|
||||||
|
"editorCodeLabel": {
|
||||||
|
"message": "Code"
|
||||||
|
},
|
||||||
|
"editorSettingLabel": {
|
||||||
|
"message": "Settings"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Enable",
|
"message": "Enable",
|
||||||
"description": "Label for the button to enable a style"
|
"description": "Label for the button to enable a style"
|
||||||
|
@ -1634,6 +1640,21 @@
|
||||||
"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).",
|
"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"
|
"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": "Custom included sites"
|
||||||
|
},
|
||||||
|
"styleExcludeLabel": {
|
||||||
|
"message": "Custom excluded sites"
|
||||||
|
},
|
||||||
"syncDropboxDeprecated": {
|
"syncDropboxDeprecated": {
|
||||||
"message": "Dropbox import/export is replaced by a more advanced style sync in the options page."
|
"message": "Dropbox import/export is replaced by a more advanced style sync in the options page."
|
||||||
},
|
},
|
||||||
|
|
|
@ -283,7 +283,7 @@ const styleMan = (() => {
|
||||||
async toggle(id, enabled) {
|
async toggle(id, enabled) {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
const style = Object.assign({}, id2style(id), {enabled});
|
const style = Object.assign({}, id2style(id), {enabled});
|
||||||
await saveStyle(style, {reason: 'toggle', codeIsUpdated: false});
|
await saveStyle(style, {reason: 'toggle'});
|
||||||
return id;
|
return id;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -297,6 +297,13 @@ const styleMan = (() => {
|
||||||
removeExclusion: removeIncludeExclude.bind(null, 'exclusions'),
|
removeExclusion: removeIncludeExclude.bind(null, 'exclusions'),
|
||||||
/** @returns {Promise<?StyleObj>} */
|
/** @returns {Promise<?StyleObj>} */
|
||||||
removeInclusion: removeIncludeExclude.bind(null, 'inclusions'),
|
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'});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -375,7 +382,7 @@ const styleMan = (() => {
|
||||||
throw new Error('The rule already exists');
|
throw new Error('The rule already exists');
|
||||||
}
|
}
|
||||||
style[type] = list.concat([rule]);
|
style[type] = list.concat([rule]);
|
||||||
return saveStyle(style, {reason: 'styleSettings'});
|
return saveStyle(style, {reason: 'config'});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeIncludeExclude(type, id, rule) {
|
async function removeIncludeExclude(type, id, rule) {
|
||||||
|
@ -386,10 +393,10 @@ const styleMan = (() => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
style[type] = list.filter(r => r !== rule);
|
style[type] = list.filter(r => r !== rule);
|
||||||
return saveStyle(style, {reason: 'styleSettings'});
|
return saveStyle(style, {reason: 'config'});
|
||||||
}
|
}
|
||||||
|
|
||||||
function broadcastStyleUpdated(style, reason, method = 'styleUpdated', codeIsUpdated = true) {
|
function broadcastStyleUpdated(style, reason, method = 'styleUpdated') {
|
||||||
const {id} = style;
|
const {id} = style;
|
||||||
const data = id2data(id);
|
const data = id2data(id);
|
||||||
const excluded = new Set();
|
const excluded = new Set();
|
||||||
|
@ -412,7 +419,6 @@ const styleMan = (() => {
|
||||||
return msg.broadcast({
|
return msg.broadcast({
|
||||||
method,
|
method,
|
||||||
reason,
|
reason,
|
||||||
codeIsUpdated,
|
|
||||||
style: {
|
style: {
|
||||||
id,
|
id,
|
||||||
md5Url: style.md5Url,
|
md5Url: style.md5Url,
|
||||||
|
@ -452,7 +458,7 @@ const styleMan = (() => {
|
||||||
return handleSave(style, handlingOptions);
|
return handleSave(style, handlingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSave(style, {reason, codeIsUpdated, broadcast = true}) {
|
function handleSave(style, {reason, broadcast = true}) {
|
||||||
const data = id2data(style.id);
|
const data = id2data(style.id);
|
||||||
const method = data ? 'styleUpdated' : 'styleAdded';
|
const method = data ? 'styleUpdated' : 'styleAdded';
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
@ -460,7 +466,7 @@ const styleMan = (() => {
|
||||||
} else {
|
} else {
|
||||||
data.style = style;
|
data.style = style;
|
||||||
}
|
}
|
||||||
if (broadcast) broadcastStyleUpdated(style, reason, method, codeIsUpdated);
|
if (broadcast) broadcastStyleUpdated(style, reason, method);
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
53
edit.html
53
edit.html
|
@ -17,6 +17,7 @@
|
||||||
<script src="content/apply.js"></script>
|
<script src="content/apply.js"></script>
|
||||||
|
|
||||||
<script src="js/sections-util.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/codemirror-themes.js"></script> <!-- must precede base.js -->
|
||||||
<script src="edit/base.js"></script>
|
<script src="edit/base.js"></script>
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@
|
||||||
<script src="edit/sections-editor-section.js"></script>
|
<script src="edit/sections-editor-section.js"></script>
|
||||||
<script src="edit/sections-editor.js"></script>
|
<script src="edit/sections-editor.js"></script>
|
||||||
<script src="edit/usw-integration.js"></script>
|
<script src="edit/usw-integration.js"></script>
|
||||||
|
<script src="edit/settings.js"></script>
|
||||||
<script src="edit/edit.js"></script>
|
<script src="edit/edit.js"></script>
|
||||||
|
|
||||||
<template data-id="appliesTo">
|
<template data-id="appliesTo">
|
||||||
|
@ -239,6 +241,8 @@
|
||||||
<link href="js/color/color-picker.css" rel="stylesheet">
|
<link href="js/color/color-picker.css" rel="stylesheet">
|
||||||
<link href="edit/codemirror-default.css" rel="stylesheet">
|
<link href="edit/codemirror-default.css" rel="stylesheet">
|
||||||
<link href="edit/edit.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>
|
</head>
|
||||||
|
|
||||||
<body id="stylus-edit">
|
<body id="stylus-edit">
|
||||||
|
@ -433,7 +437,53 @@
|
||||||
target="_blank"></a>
|
target="_blank"></a>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
<div class="form-group style-prefer-scheme radio-group">
|
||||||
|
<!-- FIXME: should we use a different message from install page? -->
|
||||||
|
<span class="form-label" i18n-text="installPreferSchemeLabel"></span>
|
||||||
|
<label class="radio-item">
|
||||||
|
<input type="radio" name="preferScheme" value="none">
|
||||||
|
<span class="radio-label" i18n-text="installPreferSchemeNone"></span>
|
||||||
|
</label>
|
||||||
|
<label class="radio-item">
|
||||||
|
<input type="radio" name="preferScheme" value="dark">
|
||||||
|
<span class="radio-label" i18n-text="installPreferSchemeDark"></span>
|
||||||
|
</label>
|
||||||
|
<label class="radio-item">
|
||||||
|
<input type="radio" name="preferScheme" value="light">
|
||||||
|
<span class="radio-label" i18n-text="installPreferSchemeLight"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="form-group style-include">
|
||||||
|
<span class="form-label" i18n-text="styleIncludeLabel"></span>
|
||||||
|
<textarea></textarea>
|
||||||
|
</label>
|
||||||
|
<label class="form-group style-exclude">
|
||||||
|
<span class="form-label" i18n-text="styleExcludeLabel"></span>
|
||||||
|
<textarea></textarea>
|
||||||
|
</label>
|
||||||
|
<!-- <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 id="help-popup">
|
||||||
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
|
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
|
||||||
<div class="contents"></div>
|
<div class="contents"></div>
|
||||||
|
@ -478,5 +528,6 @@
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
|
<script src="edit/tab.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -14,13 +14,14 @@
|
||||||
tryJSONparse
|
tryJSONparse
|
||||||
tryURL
|
tryURL
|
||||||
*/// toolbox.js
|
*/// toolbox.js
|
||||||
|
/* global EventEmitter */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type Editor
|
* @type Editor
|
||||||
* @namespace Editor
|
* @namespace Editor
|
||||||
*/
|
*/
|
||||||
const editor = {
|
const editor = Object.assign(EventEmitter(), {
|
||||||
style: null,
|
style: null,
|
||||||
dirty: DirtyReporter(),
|
dirty: DirtyReporter(),
|
||||||
isUsercss: false,
|
isUsercss: false,
|
||||||
|
@ -47,7 +48,7 @@ const editor = {
|
||||||
customName || name || t('styleMissingName')
|
customName || name || t('styleMissingName')
|
||||||
} - Stylus`; // the suffix enables external utilities to process our windows e.g. pin on top
|
} - Stylus`; // the suffix enables external utilities to process our windows e.g. pin on top
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
//#region pre-init
|
//#region pre-init
|
||||||
|
|
||||||
|
|
|
@ -105,8 +105,11 @@ label {
|
||||||
#header h1 {
|
#header h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
#sections {
|
.main {
|
||||||
padding-left: 280px;
|
padding-left: 280px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#sections {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -1177,13 +1180,16 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
#lint:not([open]) + #footer {
|
#lint:not([open]) + #footer {
|
||||||
margin: .25em 0 -1em .25em;
|
margin: .25em 0 -1em .25em;
|
||||||
}
|
}
|
||||||
#sections {
|
.main {
|
||||||
height: unset !important;
|
height: unset !important;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
.tab-bar {
|
||||||
|
margin-top: var(--fixed-height);
|
||||||
|
}
|
||||||
#sections > :not(.single-editor) {
|
#sections > :not(.single-editor) {
|
||||||
margin: 0 .5rem;
|
margin: 0 .5rem;
|
||||||
padding: .5rem 0;
|
padding: .5rem 0;
|
||||||
|
|
23
edit/edit.js
23
edit/edit.js
|
@ -1,5 +1,5 @@
|
||||||
/* global $ $create messageBoxProxy waitForSheet */// dom.js
|
/* global $ $create messageBoxProxy waitForSheet */// dom.js
|
||||||
/* global API msg */// msg.js
|
/* global msg API */// msg.js
|
||||||
/* global CodeMirror */
|
/* global CodeMirror */
|
||||||
/* global SectionsEditor */
|
/* global SectionsEditor */
|
||||||
/* global SourceEditor */
|
/* global SourceEditor */
|
||||||
|
@ -11,6 +11,7 @@
|
||||||
/* global linterMan */
|
/* global linterMan */
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
/* global t */// localization.js
|
/* global t */// localization.js
|
||||||
|
/* global StyleSettings */// settings.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
//#region init
|
//#region init
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
baseInit.ready.then(async () => {
|
baseInit.ready.then(async () => {
|
||||||
await waitForSheet();
|
await waitForSheet();
|
||||||
(editor.isUsercss ? SourceEditor : SectionsEditor)();
|
(editor.isUsercss ? SourceEditor : SectionsEditor)();
|
||||||
|
StyleSettings(editor);
|
||||||
await editor.ready;
|
await editor.ready;
|
||||||
editor.ready = true;
|
editor.ready = true;
|
||||||
editor.dirty.onChange(editor.updateDirty);
|
editor.dirty.onChange(editor.updateDirty);
|
||||||
|
@ -59,7 +61,8 @@ const IGNORE_UPDATE_REASONS = [
|
||||||
'editPreview',
|
'editPreview',
|
||||||
'editPreviewEnd',
|
'editPreviewEnd',
|
||||||
'editSave',
|
'editSave',
|
||||||
'config',
|
// https://github.com/openstyles/stylus/issues/807 is closed without fix
|
||||||
|
// 'config,
|
||||||
];
|
];
|
||||||
|
|
||||||
msg.onExtension(request => {
|
msg.onExtension(request => {
|
||||||
|
@ -67,8 +70,14 @@ msg.onExtension(request => {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
if (editor.style.id === style.id && !IGNORE_UPDATE_REASONS.includes(request.reason)) {
|
if (editor.style.id === style.id && !IGNORE_UPDATE_REASONS.includes(request.reason)) {
|
||||||
Promise.resolve(request.codeIsUpdated === false ? style : API.styles.get(style.id))
|
if (request.reason === 'toggle') {
|
||||||
.then(newStyle => editor.replaceStyle(newStyle, request.codeIsUpdated));
|
editor.emit('styleToggled', request.style);
|
||||||
|
} else {
|
||||||
|
API.styles.get(request.style.id)
|
||||||
|
.then(style => {
|
||||||
|
editor.emit('styleChange', style, request.reason);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
|
@ -159,9 +168,9 @@ window.on('beforeunload', e => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleStyle() {
|
toggleStyle(enabled = style.enabled) {
|
||||||
$('#enabled').checked = !style.enabled;
|
$('#enabled').checked = enabled;
|
||||||
editor.updateEnabledness(!style.enabled);
|
editor.updateEnabledness(enabled);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDirty() {
|
updateDirty() {
|
||||||
|
|
|
@ -84,16 +84,13 @@ function SectionsEditor() {
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async replaceStyle(newStyle, codeIsUpdated) {
|
async replaceStyle(newStyle) {
|
||||||
dirty.clear('name');
|
dirty.clear();
|
||||||
// FIXME: avoid recreating all editors?
|
// FIXME: avoid recreating all editors?
|
||||||
if (codeIsUpdated !== false) {
|
await initSections(newStyle.sections, {replace: true});
|
||||||
await initSections(newStyle.sections, {replace: true});
|
|
||||||
}
|
|
||||||
Object.assign(style, newStyle);
|
Object.assign(style, newStyle);
|
||||||
editor.onStyleUpdated();
|
editor.onStyleUpdated();
|
||||||
updateHeader();
|
updateHeader();
|
||||||
dirty.clear();
|
|
||||||
// Go from new style URL to edit style URL
|
// Go from new style URL to edit style URL
|
||||||
if (style.id && !/[&?]id=/.test(location.search)) {
|
if (style.id && !/[&?]id=/.test(location.search)) {
|
||||||
history.replaceState({}, document.title, `${location.pathname}?id=${style.id}`);
|
history.replaceState({}, document.title, `${location.pathname}?id=${style.id}`);
|
||||||
|
@ -111,6 +108,9 @@ function SectionsEditor() {
|
||||||
}
|
}
|
||||||
newStyle = await API.styles.editSave(newStyle);
|
newStyle = await API.styles.editSave(newStyle);
|
||||||
destroyRemovedSections();
|
destroyRemovedSections();
|
||||||
|
if (!style.id) {
|
||||||
|
editor.emit('styleChange', newStyle, 'new');
|
||||||
|
}
|
||||||
sessionStore.justEditedStyleId = newStyle.id;
|
sessionStore.justEditedStyleId = newStyle.id;
|
||||||
editor.replaceStyle(newStyle, false);
|
editor.replaceStyle(newStyle, false);
|
||||||
},
|
},
|
||||||
|
@ -128,6 +128,28 @@ function SectionsEditor() {
|
||||||
|
|
||||||
editor.ready = initSections(style.sections);
|
editor.ready = initSections(style.sections);
|
||||||
|
|
||||||
|
editor.on('styleToggled', newStyle => {
|
||||||
|
if (!dirty.isDirty()) {
|
||||||
|
Object.assign(style, newStyle);
|
||||||
|
} else {
|
||||||
|
editor.toggleStyle(newStyle.enabled);
|
||||||
|
}
|
||||||
|
updateHeader();
|
||||||
|
updateLivePreview();
|
||||||
|
});
|
||||||
|
editor.on('styleChange', (newStyle, reason) => {
|
||||||
|
if (reason === 'new') return; // nothing is new for us
|
||||||
|
if (reason === 'config') {
|
||||||
|
delete newStyle.sections;
|
||||||
|
delete newStyle.name;
|
||||||
|
delete newStyle.enabled;
|
||||||
|
Object.assign(style, newStyle);
|
||||||
|
updateLivePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.replaceStyle(newStyle);
|
||||||
|
});
|
||||||
|
|
||||||
/** @param {EditorSection} section */
|
/** @param {EditorSection} section */
|
||||||
function fitToContent(section) {
|
function fitToContent(section) {
|
||||||
const {cm, cm: {display: {wrapper, sizer}}} = section;
|
const {cm, cm: {display: {wrapper, sizer}}} = section;
|
||||||
|
|
45
edit/settings.css
Normal file
45
edit/settings.css
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
.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,
|
||||||
|
.form-group textarea {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.radio-group .form-label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.radio-item {
|
||||||
|
display: flex;
|
||||||
|
margin: 0.3em 0 .3em;
|
||||||
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
.radio-item input {
|
||||||
|
margin: 0 0.6em 0 0;
|
||||||
|
}
|
||||||
|
[disabled] .radio-label {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
.style-include textarea,
|
||||||
|
.style-exclude textarea {
|
||||||
|
height: 12em;
|
||||||
|
}
|
65
edit/settings.js
Normal file
65
edit/settings.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/* 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)),
|
||||||
|
createRadio('.style-prefer-scheme input', () => style.preferScheme || 'none',
|
||||||
|
e => API.styles.config(style.id, 'preferScheme', e.target.value)),
|
||||||
|
createInput('.style-include textarea', () => (style.inclusions || []).join('\n'),
|
||||||
|
e => API.styles.config(style.id, 'inclusions', textToList(e.target.value))),
|
||||||
|
createInput('.style-exclude textarea', () => (style.exclusions || []).join('\n'),
|
||||||
|
e => API.styles.config(style.id, 'exclusions', textToList(e.target.value))),
|
||||||
|
];
|
||||||
|
|
||||||
|
update(style);
|
||||||
|
|
||||||
|
editor.on('styleChange', update);
|
||||||
|
|
||||||
|
function textToList(text) {
|
||||||
|
const list = text.split(/\s*\r?\n\s*/g);
|
||||||
|
return list.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 createRadio(selector, getter, setter) {
|
||||||
|
const els = document.querySelectorAll(selector);
|
||||||
|
for (const el of els) {
|
||||||
|
el.addEventListener('change', e => {
|
||||||
|
if (el.checked) {
|
||||||
|
setter(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
update() {
|
||||||
|
for (const el of els) {
|
||||||
|
if (el.value === getter()) {
|
||||||
|
el.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInput(selector, getter, setter) {
|
||||||
|
const el = document.querySelector(selector);
|
||||||
|
el.addEventListener('change', setter);
|
||||||
|
return {
|
||||||
|
update() {
|
||||||
|
el.value = getter();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,6 +70,9 @@ function SourceEditor() {
|
||||||
messageBoxProxy.alert(t('usercssAvoidOverwriting'), 'danger', t('genericError'));
|
messageBoxProxy.alert(t('usercssAvoidOverwriting'), 'danger', t('genericError'));
|
||||||
} else {
|
} else {
|
||||||
res = await API.usercss.editSave({customName, enabled, id, sourceCode});
|
res = await API.usercss.editSave({customName, enabled, id, sourceCode});
|
||||||
|
if (!id) {
|
||||||
|
editor.emit('styleChange', res.style, 'new');
|
||||||
|
}
|
||||||
// Awaiting inside `try` so that exceptions go to our `catch`
|
// Awaiting inside `try` so that exceptions go to our `catch`
|
||||||
await replaceStyle(res.style);
|
await replaceStyle(res.style);
|
||||||
}
|
}
|
||||||
|
@ -113,6 +116,26 @@ function SourceEditor() {
|
||||||
if (!$isTextInput(document.activeElement)) {
|
if (!$isTextInput(document.activeElement)) {
|
||||||
cm.focus();
|
cm.focus();
|
||||||
}
|
}
|
||||||
|
editor.on('styleToggled', newStyle => {
|
||||||
|
if (dirty.isDirty()) {
|
||||||
|
editor.toggleStyle(newStyle.enabled);
|
||||||
|
} else {
|
||||||
|
style.enabled = newStyle.enabled;
|
||||||
|
}
|
||||||
|
updateMeta();
|
||||||
|
updateLivePreview();
|
||||||
|
});
|
||||||
|
editor.on('styleChange', (newStyle, reason) => {
|
||||||
|
if (reason === 'new') return;
|
||||||
|
if (reason === 'config') {
|
||||||
|
delete newStyle.sourceCode;
|
||||||
|
delete newStyle.name;
|
||||||
|
Object.assign(style, newStyle);
|
||||||
|
updateLivePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
replaceStyle(newStyle);
|
||||||
|
});
|
||||||
|
|
||||||
async function preprocess(style) {
|
async function preprocess(style) {
|
||||||
const res = await API.usercss.build({
|
const res = await API.usercss.build({
|
||||||
|
@ -208,14 +231,12 @@ function SourceEditor() {
|
||||||
cm.setPreprocessor((style.usercssData || {}).preprocessor);
|
cm.setPreprocessor((style.usercssData || {}).preprocessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceStyle(newStyle, codeIsUpdated) {
|
function replaceStyle(newStyle) {
|
||||||
dirty.clear('name');
|
dirty.clear('name');
|
||||||
const sameCode = newStyle.sourceCode === cm.getValue();
|
const sameCode = newStyle.sourceCode === cm.getValue();
|
||||||
if (sameCode) {
|
if (sameCode) {
|
||||||
savedGeneration = cm.changeGeneration();
|
savedGeneration = cm.changeGeneration();
|
||||||
dirty.clear('sourceGeneration');
|
dirty.clear('sourceGeneration');
|
||||||
}
|
|
||||||
if (codeIsUpdated === false || sameCode) {
|
|
||||||
updateEnvironment();
|
updateEnvironment();
|
||||||
dirty.clear('enabled');
|
dirty.clear('enabled');
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
|
|
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