show style settings in a dialog

This commit is contained in:
tophf 2021-12-25 21:43:07 +03:00
parent 440395db9f
commit 669fb443a9
9 changed files with 118 additions and 161 deletions

View File

@ -17,7 +17,6 @@
<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>
@ -62,7 +61,6 @@
<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">
@ -232,6 +230,47 @@
</table> </table>
</template> </template>
<template data-id="styleSettings">
<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 spellcheck="false" placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<label class="form-group style-exclude">
<span class="form-label" i18n-text="styleExcludeLabel"></span>
<textarea spellcheck="false" placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<!-- <label class="style-always-important">
<input type="checkbox">
<span class="form-label" i18n-text="styleAlwaysImportantLabel"></span>
</label> -->
</fieldset>
</template>
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet"> <link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
<link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet"> <link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
<link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet"> <link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet">
@ -241,8 +280,6 @@
<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">
@ -278,7 +315,8 @@
<div> <div>
<button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save" disabled></button> <button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save" disabled></button>
<button id="beautify" i18n-text="styleBeautify"></button> <button id="beautify" i18n-text="styleBeautify"></button>
<a href="manage.html" tabindex="-1"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a> <button id="style-cfg-btn" i18n-text="editorSettingLabel"></button>
<button id="cancel-button" i18n-title="styleCancelEditLabel"></button>
</div> </div>
<div id="mozilla-format-buttons" class="sectioned-only"> <div id="mozilla-format-buttons" class="sectioned-only">
<button id="from-mozilla" i18n-text="importLabel"></button> <button id="from-mozilla" i18n-text="importLabel"></button>
@ -437,53 +475,7 @@
target="_blank"></a> target="_blank"></a>
</div> </div>
</div> </div>
<div class="main tab-container"> <section id="sections"></section>
<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 spellcheck="false" placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<label class="form-group style-exclude">
<span class="form-label" i18n-text="styleExcludeLabel"></span>
<textarea spellcheck="false" placeholder="*://site1.com/*&#10;*://site2.com/*"></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>
@ -528,6 +520,5 @@
</symbol> </symbol>
</svg> </svg>
<script src="edit/tab.js"></script>
</body> </body>
</html> </html>

View File

@ -11,19 +11,20 @@
debounce debounce
getOwnTab getOwnTab
sessionStore sessionStore
tryCatch
tryJSONparse tryJSONparse
tryURL tryURL
*/// toolbox.js */// toolbox.js
/* global EventEmitter */
'use strict'; 'use strict';
/** /**
* @type Editor * @type Editor
* @namespace Editor * @namespace Editor
*/ */
const editor = Object.assign(EventEmitter(), { const editor = {
style: null, style: null,
dirty: DirtyReporter(), dirty: DirtyReporter(),
events: {},
isUsercss: false, isUsercss: false,
isWindowed: false, isWindowed: false,
lazyKeymaps: { lazyKeymaps: {
@ -36,7 +37,21 @@ const editor = Object.assign(EventEmitter(), {
previewDelay: 200, // Chrome devtools uses 200 previewDelay: 200, // Chrome devtools uses 200
scrollInfo: null, scrollInfo: null,
onStyleUpdated() { cancel: () => location.assign('/manage.html'),
emit(name, ...args) {
for (const fn of editor.events[name] || []) {
tryCatch(fn, ...args);
}
},
on(name, fn) {
(editor.events[name] || (
editor.events[name] = new Set()
)).add(fn);
},
updateClass() {
document.documentElement.classList.toggle('is-new-style', !editor.style.id); document.documentElement.classList.toggle('is-new-style', !editor.style.id);
}, },
@ -48,6 +63,19 @@ const editor = Object.assign(EventEmitter(), {
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
}, },
};
editor.on('styleUpdated', (newStyle, reason) => {
if (reason === 'config') {
delete newStyle.sourceCode;
delete newStyle.sections;
delete newStyle.name;
delete newStyle.enabled;
Object.assign(editor.style, newStyle);
editor.updateLivePreview();
} else if (reason !== 'new') {
editor.replaceStyle(newStyle);
}
}); });
//#region pre-init //#region pre-init
@ -90,7 +118,7 @@ const baseInit = (() => {
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded // switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss')); editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
editor.style = style; editor.style = style;
editor.onStyleUpdated(); editor.updateClass();
editor.updateTitle(false); editor.updateTitle(false);
document.documentElement.classList.toggle('usercss', editor.isUsercss); document.documentElement.classList.toggle('usercss', editor.isUsercss);
sessionStore.justEditedStyleId = style.id || ''; sessionStore.justEditedStyleId = style.id || '';
@ -292,16 +320,10 @@ baseInit.ready.then(() => {
} }
} }
getOwnTab().then(async tab => { getOwnTab().then(tab => {
ownTabId = tab.id; ownTabId = tab.id;
// use browser history back when 'back to manage' is clicked
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) { if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
await baseInit.domReady; editor.cancel = () => history.back();
$('#cancel-button').onclick = event => {
event.stopPropagation();
event.preventDefault();
history.back();
};
} }
}); });

View File

@ -105,11 +105,8 @@ label {
#header h1 { #header h1 {
margin-top: 0; margin-top: 0;
} }
.main {
padding-left: 280px;
height: 100%;
}
#sections { #sections {
padding-left: 280px;
min-height: 0; min-height: 0;
height: 100%; height: 100%;
} }
@ -284,10 +281,6 @@ input:invalid {
margin: 0 .2rem .5rem 0; margin: 0 .2rem .5rem 0;
} }
#actions #cancel-button {
margin: 0;
}
#options:not([open]) + #lint h2 { #options:not([open]) + #lint h2 {
margin-top: 0; margin-top: 0;
} }
@ -768,11 +761,6 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
max-height: calc(100vh - 8rem); max-height: calc(100vh - 8rem);
overflow-y: auto; overflow-y: auto;
} }
#help-popup .settings {
min-width: 500px;
min-height: 200px;
max-width: 48vw;
}
#help-popup .dismiss { #help-popup .dismiss {
position: absolute; position: absolute;
right: 4px; right: 4px;
@ -1180,16 +1168,13 @@ body.linter-disabled .hidden-unless-compact {
#lint:not([open]) + #footer { #lint:not([open]) + #footer {
margin: .25em 0 -1em .25em; margin: .25em 0 -1em .25em;
} }
.main { #sections {
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;

View File

@ -1,5 +1,5 @@
/* global $ $create messageBoxProxy waitForSheet */// dom.js /* global $ $create messageBoxProxy waitForSheet */// dom.js
/* global msg API */// msg.js /* global API msg */// msg.js
/* global CodeMirror */ /* global CodeMirror */
/* global SectionsEditor */ /* global SectionsEditor */
/* global SourceEditor */ /* global SourceEditor */
@ -11,7 +11,6 @@
/* 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
@ -19,7 +18,6 @@
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);
@ -32,6 +30,7 @@ baseInit.ready.then(async () => {
// enabling after init to prevent flash of validation failure on an empty name // enabling after init to prevent flash of validation failure on an empty name
$('#name').required = !editor.isUsercss; $('#name').required = !editor.isUsercss;
$('#save-button').onclick = editor.save; $('#save-button').onclick = editor.save;
$('#cancel-button').onclick = editor.cancel;
const elSec = $('#sections-list'); const elSec = $('#sections-list');
// editor.toc.expanded pref isn't saved in compact-layout so prefs.subscribe won't work // editor.toc.expanded pref isn't saved in compact-layout so prefs.subscribe won't work
@ -48,6 +47,11 @@ baseInit.ready.then(async () => {
require(['/edit/linter-dialogs'], () => linterMan.showLintConfig()); require(['/edit/linter-dialogs'], () => linterMan.showLintConfig());
$('#lint-help').onclick = () => $('#lint-help').onclick = () =>
require(['/edit/linter-dialogs'], () => linterMan.showLintHelp()); require(['/edit/linter-dialogs'], () => linterMan.showLintHelp());
$('#style-cfg-btn').onclick = () => require([
'/edit/settings.css',
'/edit/settings', /* global StyleSettings */
], () => StyleSettings());
require([ require([
'/edit/autocomplete', '/edit/autocomplete',
'/edit/global-search', '/edit/global-search',
@ -75,7 +79,7 @@ msg.onExtension(request => {
} else { } else {
API.styles.get(request.style.id) API.styles.get(request.style.id)
.then(style => { .then(style => {
editor.emit('styleChange', style, request.reason); editor.emit('styleUpdated', style, request.reason);
}); });
} }
} }
@ -168,7 +172,7 @@ window.on('beforeunload', e => {
} }
}, },
toggleStyle(enabled = style.enabled) { toggleStyle(enabled = !style.enabled) {
$('#enabled').checked = enabled; $('#enabled').checked = enabled;
editor.updateEnabledness(enabled); editor.updateEnabledness(enabled);
}, },

View File

@ -89,7 +89,7 @@ function SectionsEditor() {
// FIXME: avoid recreating all editors? // FIXME: avoid recreating all editors?
await initSections(newStyle.sections, {replace: true}); await initSections(newStyle.sections, {replace: true});
Object.assign(style, newStyle); Object.assign(style, newStyle);
editor.onStyleUpdated(); editor.updateClass();
updateHeader(); updateHeader();
// 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)) {
@ -109,7 +109,7 @@ function SectionsEditor() {
newStyle = await API.styles.editSave(newStyle); newStyle = await API.styles.editSave(newStyle);
destroyRemovedSections(); destroyRemovedSections();
if (!style.id) { if (!style.id) {
editor.emit('styleChange', newStyle, 'new'); editor.emit('styleUpdated', newStyle, 'new');
} }
sessionStore.justEditedStyleId = newStyle.id; sessionStore.justEditedStyleId = newStyle.id;
editor.replaceStyle(newStyle, false); editor.replaceStyle(newStyle, false);
@ -137,18 +137,6 @@ function SectionsEditor() {
updateHeader(); updateHeader();
updateLivePreview(); 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) {

View File

@ -1,3 +1,6 @@
.compact-layout #help-popup[data-type="styleSettings"] {
width: 90%;
}
.style-settings { .style-settings {
padding: 0.7rem 1.7rem; padding: 0.7rem 1.7rem;
border: 0; border: 0;
@ -41,6 +44,10 @@
} }
.style-settings textarea { .style-settings textarea {
resize: vertical; resize: vertical;
min-width: 33vw;
min-height: 2.5em; min-height: 2.5em;
max-height: 50vh; max-height: 50vh;
} }
.style-settings textarea:not(:placeholder-shown) {
min-width: 50vw;
}

View File

@ -1,11 +1,14 @@
/* global $ $$ */// dom.js /* global $ $$ $create moveFocus */// dom.js
/* global API */// msg.js /* global API */// msg.js
/* global editor */
/* global helpPopup */// util.js
/* global t */// localization.js
/* exported StyleSettings */ /* exported StyleSettings */
'use strict'; 'use strict';
function StyleSettings(editor) { function StyleSettings() {
let {style} = editor; let {style} = editor;
const ui = t.template.styleSettings.cloneNode(true);
const inputs = [ const inputs = [
createInput('.style-update-url input', () => style.updateUrl || '', createInput('.style-update-url input', () => style.updateUrl || '',
e => API.styles.config(style.id, 'updateUrl', e.target.value)), e => API.styles.config(style.id, 'updateUrl', e.target.value)),
@ -16,10 +19,16 @@ function StyleSettings(editor) {
['.style-exclude', 'exclusions'], ['.style-exclude', 'exclusions'],
].map(createArea), ].map(createArea),
]; ];
update(style); update(style);
editor.on('styleUpdated', update);
editor.on('styleChange', update); helpPopup.show(t('editorSettingLabel'), $create([
ui,
$create('.buttons', [
$create('button', {onclick: helpPopup.close}, t('confirmClose')),
]),
]));
$('#help-popup').dataset.type = 'styleSettings';
moveFocus(ui, 0);
function textToList(text) { function textToList(text) {
const list = text.split(/\s*\r?\n\s*/g); const list = text.split(/\s*\r?\n\s*/g);
@ -30,13 +39,12 @@ function StyleSettings(editor) {
if (!newStyle.id) return; if (!newStyle.id) return;
if (reason === 'editSave') return; if (reason === 'editSave') return;
style = newStyle; style = newStyle;
$('.style-settings').disabled = false;
inputs.forEach(i => i.update()); inputs.forEach(i => i.update());
} }
function createArea([parentSel, type]) { function createArea([parentSel, type]) {
const sel = parentSel + ' textarea'; const sel = parentSel + ' textarea';
const el = $(sel); const el = $(sel, ui);
el.on('input', () => { el.on('input', () => {
const val = el.value; const val = el.value;
el.rows = val.match(/^/gm).length + !val.endsWith('\n'); el.rows = val.match(/^/gm).length + !val.endsWith('\n');
@ -53,7 +61,7 @@ function StyleSettings(editor) {
} }
function createRadio(selector, getter, setter) { function createRadio(selector, getter, setter) {
const els = $$(selector); const els = $$(selector, ui);
for (const el of els) { for (const el of els) {
el.addEventListener('change', e => { el.addEventListener('change', e => {
if (el.checked) { if (el.checked) {
@ -73,7 +81,7 @@ function StyleSettings(editor) {
} }
function createInput(selector, getter, setter) { function createInput(selector, getter, setter) {
const el = $(selector); const el = $(selector, ui);
el.addEventListener('change', setter); el.addEventListener('change', setter);
return { return {
update() { update() {

View File

@ -71,7 +71,7 @@ function SourceEditor() {
} else { } else {
res = await API.usercss.editSave({customName, enabled, id, sourceCode}); res = await API.usercss.editSave({customName, enabled, id, sourceCode});
if (!id) { if (!id) {
editor.emit('styleChange', res.style, 'new'); editor.emit('styleUpdated', 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);
@ -125,17 +125,6 @@ function SourceEditor() {
updateMeta(); updateMeta();
updateLivePreview(); 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({
@ -265,7 +254,7 @@ function SourceEditor() {
} }
sessionStore.justEditedStyleId = newStyle.id; sessionStore.justEditedStyleId = newStyle.id;
Object.assign(style, newStyle); Object.assign(style, newStyle);
editor.onStyleUpdated(); editor.updateClass();
updateMeta(); updateMeta();
} }
} }

View File

@ -1,37 +0,0 @@
/* 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);
}
}
},
};
}