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="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>
@ -62,7 +61,6 @@
<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">
@ -232,6 +230,47 @@
</table>
</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/addon/dialog/dialog.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="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">
@ -278,7 +315,8 @@
<div>
<button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save" disabled></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 id="mozilla-format-buttons" class="sectioned-only">
<button id="from-mozilla" i18n-text="importLabel"></button>
@ -437,53 +475,7 @@
target="_blank"></a>
</div>
</div>
<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 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>
<section id="sections"></section>
<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>
@ -528,6 +520,5 @@
</symbol>
</svg>
<script src="edit/tab.js"></script>
</body>
</html>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/* global $ $create messageBoxProxy waitForSheet */// dom.js
/* global msg API */// msg.js
/* global API msg */// msg.js
/* global CodeMirror */
/* global SectionsEditor */
/* global SourceEditor */
@ -11,7 +11,6 @@
/* global linterMan */
/* global prefs */
/* global t */// localization.js
/* global StyleSettings */// settings.js
'use strict';
//#region init
@ -19,7 +18,6 @@
baseInit.ready.then(async () => {
await waitForSheet();
(editor.isUsercss ? SourceEditor : SectionsEditor)();
StyleSettings(editor);
await editor.ready;
editor.ready = true;
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
$('#name').required = !editor.isUsercss;
$('#save-button').onclick = editor.save;
$('#cancel-button').onclick = editor.cancel;
const elSec = $('#sections-list');
// 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());
$('#lint-help').onclick = () =>
require(['/edit/linter-dialogs'], () => linterMan.showLintHelp());
$('#style-cfg-btn').onclick = () => require([
'/edit/settings.css',
'/edit/settings', /* global StyleSettings */
], () => StyleSettings());
require([
'/edit/autocomplete',
'/edit/global-search',
@ -75,7 +79,7 @@ msg.onExtension(request => {
} else {
API.styles.get(request.style.id)
.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;
editor.updateEnabledness(enabled);
},

View File

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

View File

@ -1,3 +1,6 @@
.compact-layout #help-popup[data-type="styleSettings"] {
width: 90%;
}
.style-settings {
padding: 0.7rem 1.7rem;
border: 0;
@ -41,6 +44,10 @@
}
.style-settings textarea {
resize: vertical;
min-width: 33vw;
min-height: 2.5em;
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 editor */
/* global helpPopup */// util.js
/* global t */// localization.js
/* exported StyleSettings */
'use strict';
function StyleSettings(editor) {
function StyleSettings() {
let {style} = editor;
const ui = t.template.styleSettings.cloneNode(true);
const inputs = [
createInput('.style-update-url input', () => style.updateUrl || '',
e => API.styles.config(style.id, 'updateUrl', e.target.value)),
@ -16,10 +19,16 @@ function StyleSettings(editor) {
['.style-exclude', 'exclusions'],
].map(createArea),
];
update(style);
editor.on('styleChange', update);
editor.on('styleUpdated', 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) {
const list = text.split(/\s*\r?\n\s*/g);
@ -30,13 +39,12 @@ function StyleSettings(editor) {
if (!newStyle.id) return;
if (reason === 'editSave') return;
style = newStyle;
$('.style-settings').disabled = false;
inputs.forEach(i => i.update());
}
function createArea([parentSel, type]) {
const sel = parentSel + ' textarea';
const el = $(sel);
const el = $(sel, ui);
el.on('input', () => {
const val = el.value;
el.rows = val.match(/^/gm).length + !val.endsWith('\n');
@ -53,7 +61,7 @@ function StyleSettings(editor) {
}
function createRadio(selector, getter, setter) {
const els = $$(selector);
const els = $$(selector, ui);
for (const el of els) {
el.addEventListener('change', e => {
if (el.checked) {
@ -73,7 +81,7 @@ function StyleSettings(editor) {
}
function createInput(selector, getter, setter) {
const el = $(selector);
const el = $(selector, ui);
el.addEventListener('change', setter);
return {
update() {

View File

@ -71,7 +71,7 @@ function SourceEditor() {
} else {
res = await API.usercss.editSave({customName, enabled, id, sourceCode});
if (!id) {
editor.emit('styleChange', res.style, 'new');
editor.emit('styleUpdated', res.style, 'new');
}
// Awaiting inside `try` so that exceptions go to our `catch`
await replaceStyle(res.style);
@ -125,17 +125,6 @@ function SourceEditor() {
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) {
const res = await API.usercss.build({
@ -265,7 +254,7 @@ function SourceEditor() {
}
sessionStore.justEditedStyleId = newStyle.id;
Object.assign(style, newStyle);
editor.onStyleUpdated();
editor.updateClass();
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);
}
}
},
};
}