Merge pull request #1054 from tophf/custom-name
fix local name customization for usercss/legacy
This commit is contained in:
commit
7f15ae324d
|
@ -250,6 +250,13 @@
|
||||||
"message": "Copy to clipboard",
|
"message": "Copy to clipboard",
|
||||||
"description": "Tooltip for elements which can be copied"
|
"description": "Tooltip for elements which can be copied"
|
||||||
},
|
},
|
||||||
|
"customNameHint": {
|
||||||
|
"message": "Enter a custom name here to rename the style in UI without breaking its updates"
|
||||||
|
},
|
||||||
|
"customNameResetHint": {
|
||||||
|
"message": "Stop using customized name, switch to the style's own name",
|
||||||
|
"description": "Tooltip of 'x' button shown in editor when changing the name input of a) styles updated from a URL i.e. not locally created, b) UserCSS styles"
|
||||||
|
},
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Date installed",
|
"message": "Date installed",
|
||||||
"description": "Option text for the user to sort the style by install date"
|
"description": "Option text for the user to sort the style by install date"
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const part in PARTS) {
|
for (const part in PARTS) {
|
||||||
const text = style[part];
|
const text = part === 'name' ? style.customName || style.name : style[part];
|
||||||
if (text && PARTS[part](text, rx, words, icase)) {
|
if (text && PARTS[part](text, rx, words, icase)) {
|
||||||
results.push(id);
|
results.push(id);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -61,6 +61,8 @@ const styleManager = (() => {
|
||||||
username: ''
|
username: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DELETE_IF_NULL = ['id', 'customName'];
|
||||||
|
|
||||||
handleLivePreviewConnections();
|
handleLivePreviewConnections();
|
||||||
|
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
|
@ -387,8 +389,10 @@ const styleManager = (() => {
|
||||||
if (!style.name) {
|
if (!style.name) {
|
||||||
throw new Error('style name is empty');
|
throw new Error('style name is empty');
|
||||||
}
|
}
|
||||||
if (style.id == null) {
|
for (const key of DELETE_IF_NULL) {
|
||||||
delete style.id;
|
if (style[key] == null) {
|
||||||
|
delete style[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!style._id) {
|
if (!style._id) {
|
||||||
style._id = uuidv4();
|
style._id = uuidv4();
|
||||||
|
@ -569,6 +573,16 @@ const styleManager = (() => {
|
||||||
touched = true;
|
touched = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// upgrade the old way of customizing local names
|
||||||
|
const {originalName} = style;
|
||||||
|
if (originalName) {
|
||||||
|
touched = true;
|
||||||
|
if (originalName !== style.name) {
|
||||||
|
style.customName = style.name;
|
||||||
|
style.name = originalName;
|
||||||
|
}
|
||||||
|
delete style.originalName;
|
||||||
|
}
|
||||||
return touched;
|
return touched;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportSuccess(saved) {
|
function reportSuccess(saved) {
|
||||||
log(STATES.UPDATED + ` #${style.id} ${style.name}`);
|
log(STATES.UPDATED + ` #${style.id} ${style.customName || style.name}`);
|
||||||
const info = {updated: true, style: saved};
|
const info = {updated: true, style: saved};
|
||||||
if (port) port.postMessage(info);
|
if (port) port.postMessage(info);
|
||||||
return info;
|
return info;
|
||||||
|
@ -139,7 +139,7 @@
|
||||||
if (typeof error === 'object' && error.message) {
|
if (typeof error === 'object' && error.message) {
|
||||||
error = error.message;
|
error = error.message;
|
||||||
}
|
}
|
||||||
log(STATES.SKIPPED + ` (${error}) #${style.id} ${style.name}`);
|
log(STATES.SKIPPED + ` (${error}) #${style.id} ${style.customName || style.name}`);
|
||||||
const info = {error, STATES, style: getStyleWithNoCode(style)};
|
const info = {error, STATES, style: getStyleWithNoCode(style)};
|
||||||
if (port) port.postMessage(info);
|
if (port) port.postMessage(info);
|
||||||
return info;
|
return info;
|
||||||
|
@ -207,13 +207,6 @@
|
||||||
// keep current state
|
// keep current state
|
||||||
delete json.enabled;
|
delete json.enabled;
|
||||||
|
|
||||||
// keep local name customizations
|
|
||||||
if (style.originalName !== style.name && style.name !== json.name) {
|
|
||||||
delete json.name;
|
|
||||||
} else {
|
|
||||||
json.originalName = json.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newStyle = Object.assign({}, style, json);
|
const newStyle = Object.assign({}, style, json);
|
||||||
if (styleSectionsEqual(json, style, {checkSource: true})) {
|
if (styleSectionsEqual(json, style, {checkSource: true})) {
|
||||||
// update digest even if save === false as there might be just a space added etc.
|
// update digest even if save === false as there might be just a space added etc.
|
||||||
|
|
53
edit.html
53
edit.html
|
@ -18,6 +18,22 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<link id="cm-theme" rel="stylesheet">
|
||||||
|
|
||||||
|
<script src="js/polyfill.js"></script>
|
||||||
|
<script src="js/dom.js"></script>
|
||||||
|
<script src="js/messaging.js"></script>
|
||||||
|
<script src="js/msg.js"></script>
|
||||||
|
<script src="js/prefs.js"></script>
|
||||||
|
<script src="js/localization.js"></script>
|
||||||
|
<script src="js/script-loader.js"></script>
|
||||||
|
<script src="js/storage-util.js"></script>
|
||||||
|
|
||||||
|
<script src="content/style-injector.js"></script>
|
||||||
|
<script src="content/apply.js"></script>
|
||||||
|
|
||||||
|
<script src="edit/edit.js"></script> <!-- run it ASAP to send a request for the style -->
|
||||||
|
|
||||||
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
|
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
|
||||||
<script src="vendor/codemirror/lib/codemirror.js"></script>
|
<script src="vendor/codemirror/lib/codemirror.js"></script>
|
||||||
|
|
||||||
|
@ -63,44 +79,27 @@
|
||||||
|
|
||||||
<script src="vendor-overwrites/codemirror-addon/match-highlighter.js"></script>
|
<script src="vendor-overwrites/codemirror-addon/match-highlighter.js"></script>
|
||||||
|
|
||||||
<script src="js/polyfill.js"></script>
|
<script src="msgbox/msgbox.js" async></script>
|
||||||
<script src="js/dom.js"></script>
|
|
||||||
<script src="js/messaging.js"></script>
|
|
||||||
<script src="js/prefs.js"></script>
|
|
||||||
<script src="js/localization.js"></script>
|
|
||||||
<script src="js/script-loader.js"></script>
|
|
||||||
<script src="js/storage-util.js"></script>
|
|
||||||
<script src="js/msg.js"></script>
|
|
||||||
<script src="js/worker-util.js"></script>
|
|
||||||
|
|
||||||
<script src="content/style-injector.js"></script>
|
|
||||||
<script src="content/apply.js"></script>
|
|
||||||
|
|
||||||
<link href="edit/global-search.css" rel="stylesheet">
|
|
||||||
<script src="edit/global-search.js"></script>
|
|
||||||
|
|
||||||
<link href="edit/codemirror-default.css" rel="stylesheet">
|
<link href="edit/codemirror-default.css" rel="stylesheet">
|
||||||
<script src="edit/codemirror-default.js"></script>
|
<script src="edit/codemirror-default.js"></script>
|
||||||
|
<script src="edit/codemirror-factory.js"></script>
|
||||||
<script src="edit/util.js"></script>
|
<script src="edit/util.js"></script>
|
||||||
<script src="edit/regexp-tester.js"></script>
|
<script src="edit/regexp-tester.js"></script>
|
||||||
<script src="edit/live-preview.js"></script>
|
<script src="edit/live-preview.js"></script>
|
||||||
<script src="edit/applies-to-line-widget.js"></script>
|
<script src="edit/applies-to-line-widget.js"></script>
|
||||||
<script src="edit/reroute-hotkeys.js"></script>
|
<script src="edit/reroute-hotkeys.js"></script>
|
||||||
<script src="edit/codemirror-factory.js"></script>
|
<link href="edit/global-search.css" rel="stylesheet">
|
||||||
|
<script src="edit/global-search.js"></script>
|
||||||
<script src="edit/colorpicker-helper.js"></script>
|
<script src="edit/colorpicker-helper.js"></script>
|
||||||
<script src="edit/beautify.js"></script>
|
<script src="edit/beautify.js"></script>
|
||||||
<script src="edit/show-keymap-help.js"></script>
|
<script src="edit/show-keymap-help.js"></script>
|
||||||
<script src="edit/codemirror-themes.js"></script>
|
<script src="edit/codemirror-themes.js"></script>
|
||||||
|
|
||||||
<script src="edit/source-editor.js"></script>
|
<script src="edit/source-editor.js"></script>
|
||||||
<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/edit.js"></script>
|
<script src="js/worker-util.js"></script>
|
||||||
|
|
||||||
<script src="msgbox/msgbox.js" async></script>
|
|
||||||
|
|
||||||
<script src="edit/linter.js"></script>
|
<script src="edit/linter.js"></script>
|
||||||
<script src="edit/linter-defaults.js"></script>
|
<script src="edit/linter-defaults.js"></script>
|
||||||
<script src="edit/linter-engines.js"></script>
|
<script src="edit/linter-engines.js"></script>
|
||||||
|
@ -109,8 +108,6 @@
|
||||||
<script src="edit/linter-report.js"></script>
|
<script src="edit/linter-report.js"></script>
|
||||||
<script src="edit/linter-config-dialog.js"></script>
|
<script src="edit/linter-config-dialog.js"></script>
|
||||||
|
|
||||||
<link id="cm-theme" rel="stylesheet">
|
|
||||||
|
|
||||||
<template data-id="appliesTo">
|
<template data-id="appliesTo">
|
||||||
<li class="applies-to-item">
|
<li class="applies-to-item">
|
||||||
<div class="select-resizer">
|
<div class="select-resizer">
|
||||||
|
@ -284,7 +281,13 @@
|
||||||
<h1 id="heading"> </h1> <!-- nbsp allocates the actual height which prevents page shift -->
|
<h1 id="heading"> </h1> <!-- nbsp allocates the actual height which prevents page shift -->
|
||||||
<section id="basic-info">
|
<section id="basic-info">
|
||||||
<div id="basic-info-name">
|
<div id="basic-info-name">
|
||||||
<input id="name" class="style-contributor" spellcheck="false" required>
|
<input id="name" class="style-contributor" spellcheck="false">
|
||||||
|
<a id="reset-name" href="#" i18n-title="customNameResetHint" tabindex="0" hidden>
|
||||||
|
<svg class="svg-icon" viewBox="0 0 20 20">
|
||||||
|
<polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5
|
||||||
|
5.5,16.2 10,11.7 14.5,16.2 16.2,14.5 11.7,10 "/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
<a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
|
<a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
|
||||||
</div>
|
</div>
|
||||||
<div id="basic-info-enabled">
|
<div id="basic-info-enabled">
|
||||||
|
|
|
@ -315,7 +315,7 @@ CodeMirror.hint && (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// USO vars in usercss mode editor
|
// USO vars in usercss mode editor
|
||||||
const vars = editor.getStyle().usercssData.vars;
|
const vars = editor.style.usercssData.vars;
|
||||||
const list = vars ?
|
const list = vars ?
|
||||||
Object.keys(vars).filter(name => name.startsWith(leftPart)) : [];
|
Object.keys(vars).filter(name => name.startsWith(leftPart)) : [];
|
||||||
return {
|
return {
|
||||||
|
@ -343,7 +343,7 @@ CodeMirror.hint && (() => {
|
||||||
string[start + 3] === '[' &&
|
string[start + 3] === '[' &&
|
||||||
string[pos - 3] === ']' &&
|
string[pos - 3] === ']' &&
|
||||||
string[pos - 4] === ']') {
|
string[pos - 4] === ']') {
|
||||||
const vars = typeof editor !== 'undefined' && (editor.getStyle().usercssData || {}).vars;
|
const vars = typeof editor !== 'undefined' && (editor.style.usercssData || {}).vars;
|
||||||
const name = vars && string.slice(start + 4, pos - 4);
|
const name = vars && string.slice(start + 4, pos - 4);
|
||||||
if (vars && Object.hasOwnProperty.call(vars, name.endsWith('-rgb') ? name.slice(0, -4) : name)) {
|
if (vars && Object.hasOwnProperty.call(vars, name.endsWith('-rgb') ? name.slice(0, -4) : name)) {
|
||||||
token[0] = USO_VALID_VAR;
|
token[0] = USO_VALID_VAR;
|
||||||
|
|
|
@ -88,6 +88,9 @@ label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
#reset-name {
|
||||||
|
margin: 0 .25em 0 .5em;
|
||||||
|
}
|
||||||
#url {
|
#url {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -610,7 +613,8 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
|
||||||
right: 4px;
|
right: 4px;
|
||||||
top: .5em;
|
top: .5em;
|
||||||
}
|
}
|
||||||
#help-popup input[type="search"] {
|
#help-popup input[type="search"],
|
||||||
|
#help-popup .CodeMirror {
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -813,11 +817,6 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
margin-top: .75rem;
|
margin-top: .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.usercss #name {
|
|
||||||
background-color: #eee;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.single-editor {
|
.single-editor {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
365
edit/edit.js
365
edit/edit.js
|
@ -1,15 +1,11 @@
|
||||||
/* global CodeMirror onDOMready prefs setupLivePrefs $ $$ $create t tHTML
|
/* global CodeMirror onDOMready prefs setupLivePrefs $ $$ $create t tHTML
|
||||||
createSourceEditor sessionStorageHash getOwnTab FIREFOX API tryCatch
|
createSourceEditor sessionStorageHash getOwnTab FIREFOX API tryCatch
|
||||||
closeCurrentTab messageBox debounce workerUtil
|
closeCurrentTab messageBox debounce
|
||||||
initBeautifyButton ignoreChromeError
|
initBeautifyButton ignoreChromeError dirtyReporter linter
|
||||||
moveFocus msg createSectionsEditor rerouteHotkeys CODEMIRROR_THEMES */
|
moveFocus msg createSectionsEditor rerouteHotkeys CODEMIRROR_THEMES */
|
||||||
/* exported showCodeMirrorPopup editorWorker toggleContextMenuDelete */
|
/* exported showCodeMirrorPopup editorWorker toggleContextMenuDelete */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const editorWorker = workerUtil.createWorker({
|
|
||||||
url: '/edit/editor-worker.js'
|
|
||||||
});
|
|
||||||
|
|
||||||
let saveSizeOnClose;
|
let saveSizeOnClose;
|
||||||
|
|
||||||
// direct & reverse mapping of @-moz-document keywords and internal property names
|
// direct & reverse mapping of @-moz-document keywords and internal property names
|
||||||
|
@ -28,48 +24,84 @@ document.addEventListener('visibilitychange', beforeUnload);
|
||||||
window.addEventListener('beforeunload', beforeUnload);
|
window.addEventListener('beforeunload', beforeUnload);
|
||||||
msg.onExtension(onRuntimeMessage);
|
msg.onExtension(onRuntimeMessage);
|
||||||
|
|
||||||
preinit();
|
lazyInit();
|
||||||
|
|
||||||
(() => {
|
(async function init() {
|
||||||
onDOMready().then(() => {
|
const [style] = await Promise.all([
|
||||||
prefs.subscribe(['editor.keyMap'], showHotkeyInTooltip);
|
initStyleData(),
|
||||||
addEventListener('showHotkeyInTooltip', showHotkeyInTooltip);
|
onDOMready(),
|
||||||
showHotkeyInTooltip();
|
prefs.initializing.then(() => new Promise(resolve => {
|
||||||
|
const theme = prefs.get('editor.theme');
|
||||||
|
const el = $('#cm-theme');
|
||||||
|
if (theme === 'default') {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
// preload the theme so CodeMirror can use the correct metrics
|
||||||
|
el.href = `vendor/codemirror/theme/${theme}.css`;
|
||||||
|
el.addEventListener('load', resolve, {once: true});
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
const usercss = isUsercss(style);
|
||||||
|
const dirty = dirtyReporter();
|
||||||
|
let wasDirty = false;
|
||||||
|
let nameTarget;
|
||||||
|
|
||||||
buildThemeElement();
|
prefs.subscribe(['editor.linter'], updateLinter);
|
||||||
buildKeymapElement();
|
prefs.subscribe(['editor.keyMap'], showHotkeyInTooltip);
|
||||||
|
addEventListener('showHotkeyInTooltip', showHotkeyInTooltip);
|
||||||
|
showHotkeyInTooltip();
|
||||||
|
buildThemeElement();
|
||||||
|
buildKeymapElement();
|
||||||
|
setupLivePrefs();
|
||||||
|
initNameArea();
|
||||||
|
initBeautifyButton($('#beautify'), () => editor.getEditors());
|
||||||
|
initResizeListener();
|
||||||
|
detectLayout();
|
||||||
|
updateTitle();
|
||||||
|
|
||||||
setupLivePrefs();
|
$('#heading').textContent = t(style.id ? 'editStyleHeading' : 'addStyleTitle');
|
||||||
|
$('#preview-label').classList.toggle('hidden', !style.id);
|
||||||
|
|
||||||
|
editor = (usercss ? createSourceEditor : createSectionsEditor)({
|
||||||
|
style,
|
||||||
|
dirty,
|
||||||
|
updateName,
|
||||||
|
toggleStyle,
|
||||||
});
|
});
|
||||||
|
dirty.onChange(updateDirty);
|
||||||
|
await editor.ready;
|
||||||
|
|
||||||
initEditor();
|
// enabling after init to prevent flash of validation failure on an empty name
|
||||||
|
$('#name').required = !usercss;
|
||||||
|
$('#save-button').onclick = editor.save;
|
||||||
|
|
||||||
function getCodeMirrorThemes() {
|
function initNameArea() {
|
||||||
if (!chrome.runtime.getPackageDirectoryEntry) {
|
const nameEl = $('#name');
|
||||||
const themes = [
|
const resetEl = $('#reset-name');
|
||||||
chrome.i18n.getMessage('defaultTheme'),
|
const isCustomName = style.updateUrl || usercss;
|
||||||
...CODEMIRROR_THEMES
|
nameTarget = isCustomName ? 'customName' : 'name';
|
||||||
];
|
nameEl.placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
|
||||||
localStorage.codeMirrorThemes = themes.join(' ');
|
nameEl.title = isCustomName ? t('customNameHint') : '';
|
||||||
return Promise.resolve(themes);
|
nameEl.addEventListener('input', () => {
|
||||||
}
|
updateName();
|
||||||
return new Promise(resolve => {
|
resetEl.hidden = false;
|
||||||
chrome.runtime.getPackageDirectoryEntry(rootDir => {
|
|
||||||
rootDir.getDirectory('vendor/codemirror/theme', {create: false}, themeDir => {
|
|
||||||
themeDir.createReader().readEntries(entries => {
|
|
||||||
const themes = [
|
|
||||||
chrome.i18n.getMessage('defaultTheme')
|
|
||||||
].concat(
|
|
||||||
entries.filter(entry => entry.isFile)
|
|
||||||
.sort((a, b) => (a.name < b.name ? -1 : 1))
|
|
||||||
.map(entry => entry.name.replace(/\.css$/, ''))
|
|
||||||
);
|
|
||||||
localStorage.codeMirrorThemes = themes.join(' ');
|
|
||||||
resolve(themes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
resetEl.hidden = !style.customName;
|
||||||
|
resetEl.onclick = () => {
|
||||||
|
const style = editor.style;
|
||||||
|
nameEl.focus();
|
||||||
|
nameEl.select();
|
||||||
|
// trying to make it undoable via Ctrl-Z
|
||||||
|
if (!document.execCommand('insertText', false, style.name)) {
|
||||||
|
nameEl.value = style.name;
|
||||||
|
updateName();
|
||||||
|
}
|
||||||
|
style.customName = null; // to delete it from db
|
||||||
|
resetEl.hidden = true;
|
||||||
|
};
|
||||||
|
const enabledEl = $('#enabled');
|
||||||
|
enabledEl.onchange = () => updateEnabledness(enabledEl.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findKeyForCommand(command, map) {
|
function findKeyForCommand(command, map) {
|
||||||
|
@ -88,27 +120,10 @@ preinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildThemeElement() {
|
function buildThemeElement() {
|
||||||
const themeElement = $('#editor.theme');
|
CODEMIRROR_THEMES.unshift(chrome.i18n.getMessage('defaultTheme'));
|
||||||
const themeList = localStorage.codeMirrorThemes;
|
$('#editor.theme').append(...CODEMIRROR_THEMES.map(s => $create('option', s)));
|
||||||
|
// move the theme after built-in CSS so that its same-specificity selectors win
|
||||||
const optionsFromArray = options => {
|
document.head.appendChild($('#cm-theme'));
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
options.forEach(opt => fragment.appendChild($create('option', opt)));
|
|
||||||
themeElement.appendChild(fragment);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (themeList) {
|
|
||||||
optionsFromArray(themeList.split(/\s+/));
|
|
||||||
} else {
|
|
||||||
// Chrome is starting up and shows our edit.html, but the background page isn't loaded yet
|
|
||||||
const theme = prefs.get('editor.theme');
|
|
||||||
optionsFromArray([theme === 'default' ? t('defaultTheme') : theme]);
|
|
||||||
getCodeMirrorThemes().then(() => {
|
|
||||||
const themes = (localStorage.codeMirrorThemes || '').split(/\s+/);
|
|
||||||
optionsFromArray(themes);
|
|
||||||
themeElement.selectedIndex = Math.max(0, themes.indexOf(theme));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildKeymapElement() {
|
function buildKeymapElement() {
|
||||||
|
@ -159,134 +174,118 @@ preinit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initEditor() {
|
function initResizeListener() {
|
||||||
return Promise.all([
|
const {onBoundsChanged} = chrome.windows || {};
|
||||||
initStyleData(),
|
if (onBoundsChanged) {
|
||||||
onDOMready(),
|
// * movement is reported even if the window wasn't resized
|
||||||
prefs.initializing,
|
// * fired just once when done so debounce is not needed
|
||||||
])
|
onBoundsChanged.addListener(wnd => {
|
||||||
.then(([style]) => {
|
// getting the current window id as it may change if the user attached/detached the tab
|
||||||
const usercss = isUsercss(style);
|
chrome.windows.getCurrent(ownWnd => {
|
||||||
$('#heading').textContent = t(style.id ? 'editStyleHeading' : 'addStyleTitle');
|
if (wnd.id === ownWnd.id) rememberWindowSize();
|
||||||
$('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
|
|
||||||
$('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
|
|
||||||
$('#preview-label').classList.toggle('hidden', !style.id);
|
|
||||||
initBeautifyButton($('#beautify'), () => editor.getEditors());
|
|
||||||
const {onBoundsChanged} = chrome.windows || {};
|
|
||||||
if (onBoundsChanged) {
|
|
||||||
// * movement is reported even if the window wasn't resized
|
|
||||||
// * fired just once when done so debounce is not needed
|
|
||||||
onBoundsChanged.addListener(wnd => {
|
|
||||||
// getting the current window id as it may change if the user attached/detached the tab
|
|
||||||
chrome.windows.getCurrent(ownWnd => {
|
|
||||||
if (wnd.id === ownWnd.id) rememberWindowSize();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
if (!onBoundsChanged) debounce(rememberWindowSize, 100);
|
|
||||||
detectLayout();
|
|
||||||
});
|
});
|
||||||
detectLayout();
|
|
||||||
editor = (usercss ? createSourceEditor : createSectionsEditor)({
|
|
||||||
style,
|
|
||||||
onTitleChanged: updateTitle
|
|
||||||
});
|
|
||||||
editor.dirty.onChange(updateDirty);
|
|
||||||
return Promise.resolve(editor.ready && editor.ready())
|
|
||||||
.then(updateDirty);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function updateTitle() {
|
|
||||||
if (editor) {
|
|
||||||
const styleName = editor.getStyle().name;
|
|
||||||
const isDirty = editor.dirty.isDirty();
|
|
||||||
document.title = (isDirty ? '* ' : '') + styleName;
|
|
||||||
}
|
}
|
||||||
}
|
window.addEventListener('resize', () => {
|
||||||
|
if (!onBoundsChanged) debounce(rememberWindowSize, 100);
|
||||||
function updateDirty() {
|
detectLayout();
|
||||||
const isDirty = editor.dirty.isDirty();
|
|
||||||
document.body.classList.toggle('dirty', isDirty);
|
|
||||||
$('#save-button').disabled = !isDirty;
|
|
||||||
updateTitle();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
function preinit() {
|
|
||||||
// preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs()
|
|
||||||
new MutationObserver((mutations, observer) => {
|
|
||||||
const themeElement = $('#cm-theme');
|
|
||||||
if (themeElement) {
|
|
||||||
themeElement.href = prefs.get('editor.theme') === 'default' ? ''
|
|
||||||
: 'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css';
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
}).observe(document, {subtree: true, childList: true});
|
|
||||||
|
|
||||||
if (chrome.windows) {
|
|
||||||
browser.tabs.query({currentWindow: true}).then(tabs => {
|
|
||||||
const windowId = tabs[0].windowId;
|
|
||||||
if (prefs.get('openEditInWindow')) {
|
|
||||||
if (
|
|
||||||
/true/.test(sessionStorage.saveSizeOnClose) &&
|
|
||||||
'left' in prefs.get('windowPosition', {}) &&
|
|
||||||
!isWindowMaximized()
|
|
||||||
) {
|
|
||||||
// window was reopened via Ctrl-Shift-T etc.
|
|
||||||
chrome.windows.update(windowId, prefs.get('windowPosition'));
|
|
||||||
}
|
|
||||||
if (tabs.length === 1 && window.history.length === 1) {
|
|
||||||
chrome.windows.getAll(windows => {
|
|
||||||
if (windows.length > 1) {
|
|
||||||
sessionStorageHash('saveSizeOnClose').set(windowId, true);
|
|
||||||
saveSizeOnClose = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
saveSizeOnClose = sessionStorageHash('saveSizeOnClose').value[windowId];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getOwnTab().then(tab => {
|
function toggleStyle() {
|
||||||
const ownTabId = tab.id;
|
$('#enabled').checked = !style.enabled;
|
||||||
|
updateEnabledness(!style.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
// use browser history back when 'back to manage' is clicked
|
function updateDirty() {
|
||||||
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
const isDirty = dirty.isDirty();
|
||||||
onDOMready().then(() => {
|
if (wasDirty !== isDirty) {
|
||||||
$('#cancel-button').onclick = event => {
|
wasDirty = isDirty;
|
||||||
event.stopPropagation();
|
document.body.classList.toggle('dirty', isDirty);
|
||||||
event.preventDefault();
|
$('#save-button').disabled = !isDirty;
|
||||||
history.back();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// no windows on android
|
updateTitle();
|
||||||
if (!chrome.windows) {
|
}
|
||||||
|
|
||||||
|
function updateEnabledness(enabled) {
|
||||||
|
dirty.modify('enabled', style.enabled, enabled);
|
||||||
|
style.enabled = enabled;
|
||||||
|
editor.updateLivePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateName() {
|
||||||
|
if (!editor) return;
|
||||||
|
const {value} = $('#name');
|
||||||
|
dirty.modify('name', style[nameTarget] || style.name, value);
|
||||||
|
style[nameTarget] = value;
|
||||||
|
updateTitle({});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTitle() {
|
||||||
|
document.title = `${dirty.isDirty() ? '* ' : ''}${style.customName || style.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLinter(key, value) {
|
||||||
|
$('body').classList.toggle('linter-disabled', value === '');
|
||||||
|
linter.run();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
/* Stuff not needed for the main init so we can let it run at its own tempo */
|
||||||
|
async function lazyInit() {
|
||||||
|
const ownTabId = (await getOwnTab()).id;
|
||||||
|
// use browser history back when 'back to manage' is clicked
|
||||||
|
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
||||||
|
onDOMready().then(() => {
|
||||||
|
$('#cancel-button').onclick = event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
history.back();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// no windows on android
|
||||||
|
if (!chrome.windows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tabs = await browser.tabs.query({currentWindow: true});
|
||||||
|
const windowId = tabs[0].windowId;
|
||||||
|
if (prefs.get('openEditInWindow')) {
|
||||||
|
if (
|
||||||
|
/true/.test(sessionStorage.saveSizeOnClose) &&
|
||||||
|
'left' in prefs.get('windowPosition', {}) &&
|
||||||
|
!isWindowMaximized()
|
||||||
|
) {
|
||||||
|
// window was reopened via Ctrl-Shift-T etc.
|
||||||
|
chrome.windows.update(windowId, prefs.get('windowPosition'));
|
||||||
|
}
|
||||||
|
if (tabs.length === 1 && window.history.length === 1) {
|
||||||
|
chrome.windows.getAll(windows => {
|
||||||
|
if (windows.length > 1) {
|
||||||
|
sessionStorageHash('saveSizeOnClose').set(windowId, true);
|
||||||
|
saveSizeOnClose = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
saveSizeOnClose = sessionStorageHash('saveSizeOnClose').value[windowId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chrome.tabs.onAttached.addListener((tabId, info) => {
|
||||||
|
if (tabId !== ownTabId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// When an edit page gets attached or detached, remember its state
|
if (info.newPosition !== 0) {
|
||||||
// so we can do the same to the next one to open.
|
prefs.set('openEditInWindow', false);
|
||||||
chrome.tabs.onAttached.addListener((tabId, info) => {
|
return;
|
||||||
if (tabId !== ownTabId) {
|
}
|
||||||
return;
|
chrome.windows.get(info.newWindowId, {populate: true}, win => {
|
||||||
|
// If there's only one tab in this window, it's been dragged to new window
|
||||||
|
const openEditInWindow = win.tabs.length === 1;
|
||||||
|
if (openEditInWindow && FIREFOX) {
|
||||||
|
// FF-only because Chrome retardedly resets the size during dragging
|
||||||
|
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
|
||||||
}
|
}
|
||||||
if (info.newPosition !== 0) {
|
prefs.set('openEditInWindow', openEditInWindow);
|
||||||
prefs.set('openEditInWindow', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
chrome.windows.get(info.newWindowId, {populate: true}, win => {
|
|
||||||
// If there's only one tab in this window, it's been dragged to new window
|
|
||||||
const openEditInWindow = win.tabs.length === 1;
|
|
||||||
if (openEditInWindow && FIREFOX) {
|
|
||||||
// FF-only because Chrome retardedly resets the size during dragging
|
|
||||||
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
|
|
||||||
}
|
|
||||||
prefs.set('openEditInWindow', openEditInWindow);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -295,7 +294,7 @@ function onRuntimeMessage(request) {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
if (
|
if (
|
||||||
editor.getStyleId() === request.style.id &&
|
editor.style.id === request.style.id &&
|
||||||
!['editPreview', 'editPreviewEnd', 'editSave', 'config']
|
!['editPreview', 'editPreviewEnd', 'editSave', 'config']
|
||||||
.includes(request.reason)
|
.includes(request.reason)
|
||||||
) {
|
) {
|
||||||
|
@ -309,7 +308,7 @@ function onRuntimeMessage(request) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
if (editor.getStyleId() === request.style.id) {
|
if (editor.style.id === request.style.id) {
|
||||||
document.removeEventListener('visibilitychange', beforeUnload);
|
document.removeEventListener('visibilitychange', beforeUnload);
|
||||||
document.removeEventListener('beforeunload', beforeUnload);
|
document.removeEventListener('beforeunload', beforeUnload);
|
||||||
closeCurrentTab();
|
closeCurrentTab();
|
||||||
|
@ -503,10 +502,6 @@ function rememberWindowSize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs.subscribe(['editor.linter'], (key, value) => {
|
|
||||||
$('body').classList.toggle('linter-disabled', value === '');
|
|
||||||
});
|
|
||||||
|
|
||||||
function fixedHeader() {
|
function fixedHeader() {
|
||||||
const scrollPoint = $('#header').clientHeight - 40;
|
const scrollPoint = $('#header').clientHeight - 40;
|
||||||
const linterEnabled = prefs.get('editor.linter') !== '';
|
const linterEnabled = prefs.get('editor.linter') !== '';
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
/* global prefs */
|
/* global workerUtil */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/* exported editorWorker */
|
||||||
|
const editorWorker = workerUtil.createWorker({
|
||||||
|
url: '/edit/editor-worker.js'
|
||||||
|
});
|
||||||
|
|
||||||
/* exported linter */
|
/* exported linter */
|
||||||
const linter = (() => {
|
const linter = (() => {
|
||||||
const lintingUpdatedListeners = [];
|
const lintingUpdatedListeners = [];
|
||||||
|
@ -59,8 +64,3 @@ const linter = (() => {
|
||||||
.then(results => [].concat(...results.filter(Boolean)));
|
.then(results => [].concat(...results.filter(Boolean)));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// FIXME: this should be put inside edit.js
|
|
||||||
prefs.subscribe(['editor.linter'], () => {
|
|
||||||
linter.run();
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,41 +1,24 @@
|
||||||
/* global dirtyReporter showHelp toggleContextMenuDelete createSection
|
/* global showHelp toggleContextMenuDelete createSection
|
||||||
CodeMirror linter createLivePreview showCodeMirrorPopup
|
CodeMirror linter createLivePreview showCodeMirrorPopup
|
||||||
sectionsToMozFormat messageBox clipString
|
sectionsToMozFormat messageBox clipString
|
||||||
rerouteHotkeys $ $$ $create t FIREFOX API
|
$ $$ $create t FIREFOX API
|
||||||
debounce */
|
debounce */
|
||||||
/* exported createSectionsEditor */
|
/* exported createSectionsEditor */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function createSectionsEditor({style, onTitleChanged}) {
|
function createSectionsEditor(editorBase) {
|
||||||
|
const {style, dirty} = editorBase;
|
||||||
|
|
||||||
let INC_ID = 0; // an increment id that is used by various object to track the order
|
let INC_ID = 0; // an increment id that is used by various object to track the order
|
||||||
const dirty = dirtyReporter();
|
|
||||||
|
|
||||||
const container = $('#sections');
|
const container = $('#sections');
|
||||||
const sections = [];
|
const sections = [];
|
||||||
|
|
||||||
container.classList.add('section-editor');
|
container.classList.add('section-editor');
|
||||||
|
|
||||||
const nameEl = $('#name');
|
|
||||||
nameEl.addEventListener('input', () => {
|
|
||||||
dirty.modify('name', style.name, nameEl.value);
|
|
||||||
style.name = nameEl.value;
|
|
||||||
onTitleChanged();
|
|
||||||
});
|
|
||||||
|
|
||||||
const enabledEl = $('#enabled');
|
|
||||||
enabledEl.addEventListener('change', () => {
|
|
||||||
dirty.modify('enabled', style.enabled, enabledEl.checked);
|
|
||||||
style.enabled = enabledEl.checked;
|
|
||||||
updateLivePreview();
|
|
||||||
});
|
|
||||||
|
|
||||||
updateHeader();
|
updateHeader();
|
||||||
rerouteHotkeys(true);
|
|
||||||
|
|
||||||
$('#to-mozilla').addEventListener('click', showMozillaFormat);
|
$('#to-mozilla').addEventListener('click', showMozillaFormat);
|
||||||
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp);
|
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp);
|
||||||
$('#from-mozilla').addEventListener('click', () => showMozillaFormatImport());
|
$('#from-mozilla').addEventListener('click', () => showMozillaFormatImport());
|
||||||
$('#save-button').addEventListener('click', saveStyle);
|
|
||||||
|
|
||||||
document.addEventListener('wheel', scrollEntirePageOnCtrlShift, {passive: false});
|
document.addEventListener('wheel', scrollEntirePageOnCtrlShift, {passive: false});
|
||||||
CodeMirror.defaults.extraKeys['Shift-Ctrl-Wheel'] = 'scrollWindow';
|
CodeMirror.defaults.extraKeys['Shift-Ctrl-Wheel'] = 'scrollWindow';
|
||||||
|
@ -65,33 +48,30 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
|
|
||||||
let sectionOrder = '';
|
let sectionOrder = '';
|
||||||
let headerOffset; // in compact mode the header is at the top so it reduces the available height
|
let headerOffset; // in compact mode the header is at the top so it reduces the available height
|
||||||
const initializing = initSections(style.sections.slice());
|
const ready = initSections(style.sections.slice());
|
||||||
|
|
||||||
const livePreview = createLivePreview();
|
const livePreview = createLivePreview();
|
||||||
livePreview.show(Boolean(style.id));
|
livePreview.show(Boolean(style.id));
|
||||||
|
|
||||||
return {
|
return Object.assign({}, editorBase, {
|
||||||
ready: () => initializing,
|
ready,
|
||||||
replaceStyle,
|
replaceStyle,
|
||||||
dirty,
|
|
||||||
getStyle: () => style,
|
|
||||||
getEditors,
|
getEditors,
|
||||||
scrollToEditor,
|
scrollToEditor,
|
||||||
getStyleId: () => style.id,
|
|
||||||
getEditorTitle: cm => {
|
getEditorTitle: cm => {
|
||||||
const index = sections.filter(s => !s.isRemoved()).findIndex(s => s.cm === cm);
|
const index = sections.filter(s => !s.isRemoved()).findIndex(s => s.cm === cm);
|
||||||
return `${t('sectionCode')} ${index + 1}`;
|
return `${t('sectionCode')} ${index + 1}`;
|
||||||
},
|
},
|
||||||
save: saveStyle,
|
save,
|
||||||
toggleStyle,
|
|
||||||
nextEditor,
|
nextEditor,
|
||||||
prevEditor,
|
prevEditor,
|
||||||
closestVisible,
|
closestVisible,
|
||||||
getSearchableInputs,
|
getSearchableInputs,
|
||||||
};
|
updateLivePreview,
|
||||||
|
});
|
||||||
|
|
||||||
function fitToContent(section) {
|
function fitToContent(section) {
|
||||||
const {cm, cm: {display: {wrapper, sizer}}} = section;
|
const {el, cm, cm: {display: {wrapper, sizer}}} = section;
|
||||||
if (cm.display.renderedView) {
|
if (cm.display.renderedView) {
|
||||||
resize();
|
resize();
|
||||||
} else {
|
} else {
|
||||||
|
@ -104,7 +84,7 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (headerOffset == null) {
|
if (headerOffset == null) {
|
||||||
headerOffset = wrapper.getBoundingClientRect().top;
|
headerOffset = el.getBoundingClientRect().top;
|
||||||
}
|
}
|
||||||
contentHeight += 9; // border & resize grip
|
contentHeight += 9; // border & resize grip
|
||||||
cm.off('update', resize);
|
cm.off('update', resize);
|
||||||
|
@ -115,16 +95,15 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fitToAvailableSpace() {
|
function fitToAvailableSpace() {
|
||||||
const available =
|
const ch = container.offsetHeight;
|
||||||
Math.floor(container.offsetHeight - sections.reduce((h, s) => h + s.el.offsetHeight, 0)) ||
|
let available = ch - sections[sections.length - 1].el.getBoundingClientRect().bottom + headerOffset;
|
||||||
window.innerHeight - container.offsetHeight;
|
if (available <= 1) available = window.innerHeight - ch - headerOffset;
|
||||||
if (available <= 0) {
|
const delta = Math.floor(available / sections.length);
|
||||||
return;
|
if (delta > 1) {
|
||||||
|
sections.forEach(({cm}) => {
|
||||||
|
cm.setSize(null, cm.display.wrapper.offsetHeight + delta);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const cmHeights = sections.map(s => s.cm.getWrapperElement().offsetHeight);
|
|
||||||
sections.forEach((section, i) => {
|
|
||||||
section.cm.setSize(null, cmHeights[i] + Math.floor(available / sections.length));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function genId() {
|
function genId() {
|
||||||
|
@ -246,14 +225,6 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
return sections.filter(s => !s.isRemoved()).map(s => s.cm);
|
return sections.filter(s => !s.isRemoved()).map(s => s.cm);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleStyle() {
|
|
||||||
const newValue = !style.enabled;
|
|
||||||
dirty.modify('enabled', style.enabled, newValue);
|
|
||||||
style.enabled = newValue;
|
|
||||||
enabledEl.checked = newValue;
|
|
||||||
updateLivePreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextEditor(cm, cycle = true) {
|
function nextEditor(cm, cycle = true) {
|
||||||
if (!cycle && findLast(sections, s => !s.isRemoved()).cm === cm) {
|
if (!cycle && findLast(sections, s => !s.isRemoved()).cm === cm) {
|
||||||
return;
|
return;
|
||||||
|
@ -417,7 +388,7 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
if (!nameEl.reportValidity()) {
|
if (!$('#name').reportValidity()) {
|
||||||
messageBox.alert(t('styleMissingName'));
|
messageBox.alert(t('styleMissingName'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -435,7 +406,7 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveStyle() {
|
function save() {
|
||||||
if (!dirty.isDirty()) {
|
if (!dirty.isDirty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -464,10 +435,10 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHeader() {
|
function updateHeader() {
|
||||||
nameEl.value = style.name || '';
|
$('#name').value = style.customName || style.name || '';
|
||||||
enabledEl.checked = style.enabled !== false;
|
$('#enabled').checked = style.enabled !== false;
|
||||||
$('#url').href = style.url || '';
|
$('#url').href = style.url || '';
|
||||||
onTitleChanged();
|
editorBase.updateName();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLivePreview() {
|
function updateLivePreview() {
|
||||||
|
@ -609,6 +580,7 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceStyle(newStyle, codeIsUpdated) {
|
function replaceStyle(newStyle, codeIsUpdated) {
|
||||||
|
dirty.clear('name');
|
||||||
// FIXME: avoid recreating all editors?
|
// FIXME: avoid recreating all editors?
|
||||||
reinit().then(() => {
|
reinit().then(() => {
|
||||||
Object.assign(style, newStyle);
|
Object.assign(style, newStyle);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global dirtyReporter
|
/* global
|
||||||
createAppliesToLineWidget messageBox
|
createAppliesToLineWidget messageBox
|
||||||
sectionsToMozFormat
|
sectionsToMozFormat
|
||||||
createMetaCompiler linter createLivePreview cmFactory $ $create API prefs t
|
createMetaCompiler linter createLivePreview cmFactory $ $create API prefs t
|
||||||
|
@ -6,17 +6,14 @@
|
||||||
/* exported createSourceEditor */
|
/* exported createSourceEditor */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function createSourceEditor({style, onTitleChanged}) {
|
function createSourceEditor(editorBase) {
|
||||||
$('#name').disabled = true;
|
const {style, dirty} = editorBase;
|
||||||
$('#save-button').disabled = true;
|
|
||||||
$('#mozilla-format-container').remove();
|
$('#mozilla-format-container').remove();
|
||||||
$('#save-button').onclick = save;
|
|
||||||
$('#header').addEventListener('wheel', headerOnScroll);
|
$('#header').addEventListener('wheel', headerOnScroll);
|
||||||
$('#sections').textContent = '';
|
$('#sections').textContent = '';
|
||||||
$('#sections').appendChild($create('.single-editor'));
|
$('#sections').appendChild($create('.single-editor'));
|
||||||
|
|
||||||
const dirty = dirtyReporter();
|
|
||||||
|
|
||||||
// normalize style
|
// normalize style
|
||||||
if (!style.id) setupNewStyle(style);
|
if (!style.id) setupNewStyle(style);
|
||||||
|
|
||||||
|
@ -28,13 +25,6 @@ function createSourceEditor({style, onTitleChanged}) {
|
||||||
const livePreview = createLivePreview(preprocess);
|
const livePreview = createLivePreview(preprocess);
|
||||||
livePreview.show(Boolean(style.id));
|
livePreview.show(Boolean(style.id));
|
||||||
|
|
||||||
$('#enabled').onchange = function () {
|
|
||||||
const value = this.checked;
|
|
||||||
dirty.modify('enabled', style.enabled, value);
|
|
||||||
style.enabled = value;
|
|
||||||
updateLivePreview();
|
|
||||||
};
|
|
||||||
|
|
||||||
cm.on('changes', () => {
|
cm.on('changes', () => {
|
||||||
dirty.modify('sourceGeneration', savedGeneration, cm.changeGeneration());
|
dirty.modify('sourceGeneration', savedGeneration, cm.changeGeneration());
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
|
@ -162,14 +152,15 @@ function createSourceEditor({style, onTitleChanged}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMeta() {
|
function updateMeta() {
|
||||||
$('#name').value = style.name;
|
$('#name').value = style.customName || style.name;
|
||||||
$('#enabled').checked = style.enabled;
|
$('#enabled').checked = style.enabled;
|
||||||
$('#url').href = style.url;
|
$('#url').href = style.url;
|
||||||
onTitleChanged();
|
editorBase.updateName();
|
||||||
return cm.setPreprocessor((style.usercssData || {}).preprocessor);
|
return cm.setPreprocessor((style.usercssData || {}).preprocessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceStyle(newStyle, codeIsUpdated) {
|
function replaceStyle(newStyle, codeIsUpdated) {
|
||||||
|
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();
|
||||||
|
@ -210,14 +201,6 @@ function createSourceEditor({style, onTitleChanged}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleStyle() {
|
|
||||||
const value = !style.enabled;
|
|
||||||
dirty.modify('enabled', style.enabled, value);
|
|
||||||
style.enabled = value;
|
|
||||||
updateMeta();
|
|
||||||
$('#enabled').dispatchEvent(new Event('change', {bubbles: true}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
if (!dirty.isDirty()) return;
|
if (!dirty.isDirty()) return;
|
||||||
const code = cm.getValue();
|
const code = cm.getValue();
|
||||||
|
@ -226,6 +209,7 @@ function createSourceEditor({style, onTitleChanged}) {
|
||||||
id: style.id,
|
id: style.id,
|
||||||
enabled: style.enabled,
|
enabled: style.enabled,
|
||||||
sourceCode: code,
|
sourceCode: code,
|
||||||
|
customName: style.customName,
|
||||||
}))
|
}))
|
||||||
.then(replaceStyle)
|
.then(replaceStyle)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -372,19 +356,17 @@ function createSourceEditor({style, onTitleChanged}) {
|
||||||
(mode.helperType || '');
|
(mode.helperType || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return Object.assign({}, editorBase, {
|
||||||
|
ready: Promise.resolve(),
|
||||||
replaceStyle,
|
replaceStyle,
|
||||||
dirty,
|
|
||||||
getStyle: () => style,
|
|
||||||
getEditors: () => [cm],
|
getEditors: () => [cm],
|
||||||
scrollToEditor: () => {},
|
scrollToEditor: () => {},
|
||||||
getStyleId: () => style.id,
|
|
||||||
getEditorTitle: () => '',
|
getEditorTitle: () => '',
|
||||||
save,
|
save,
|
||||||
toggleStyle,
|
|
||||||
prevEditor: cm => nextPrevMozDocument(cm, -1),
|
prevEditor: cm => nextPrevMozDocument(cm, -1),
|
||||||
nextEditor: cm => nextPrevMozDocument(cm, 1),
|
nextEditor: cm => nextPrevMozDocument(cm, 1),
|
||||||
closestVisible: () => cm,
|
closestVisible: () => cm,
|
||||||
getSearchableInputs: () => []
|
getSearchableInputs: () => [],
|
||||||
};
|
updateLivePreview,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,7 +243,7 @@
|
||||||
(!dup ?
|
(!dup ?
|
||||||
Promise.resolve(true) :
|
Promise.resolve(true) :
|
||||||
messageBox.confirm(t('styleInstallOverwrite', [
|
messageBox.confirm(t('styleInstallOverwrite', [
|
||||||
data.name,
|
data.name + (dup.customName ? ` (${dup.customName})` : ''),
|
||||||
dupData.version,
|
dupData.version,
|
||||||
data.version,
|
data.version,
|
||||||
]))
|
]))
|
||||||
|
|
|
@ -33,7 +33,8 @@ self.msg = self.INJECTED === 1 ? self.msg : (() => {
|
||||||
onExtension,
|
onExtension,
|
||||||
off,
|
off,
|
||||||
RX_NO_RECEIVER,
|
RX_NO_RECEIVER,
|
||||||
RX_PORT_CLOSED
|
RX_PORT_CLOSED,
|
||||||
|
isBg,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getBg() {
|
function getBg() {
|
||||||
|
|
15
js/prefs.js
15
js/prefs.js
|
@ -1,6 +1,8 @@
|
||||||
/* global promisifyChrome */
|
/* global promisifyChrome msg API */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// Needs msg.js loaded first
|
||||||
|
|
||||||
self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
|
self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
|
||||||
const defaults = {
|
const defaults = {
|
||||||
'openEditInWindow': false, // new editor opens in a own browser window
|
'openEditInWindow': false, // new editor opens in a own browser window
|
||||||
|
@ -112,12 +114,11 @@ self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
|
||||||
'storage.sync': ['get', 'set'],
|
'storage.sync': ['get', 'set'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const initializing = browser.storage.sync.get('settings')
|
const initializing = (
|
||||||
.then(result => {
|
msg.isBg
|
||||||
if (result.settings) {
|
? browser.storage.sync.get('settings').then(res => res.settings)
|
||||||
setAll(result.settings, true);
|
: API.getPrefs()
|
||||||
}
|
).then(res => res && setAll(res, true));
|
||||||
});
|
|
||||||
|
|
||||||
chrome.storage.onChanged.addListener((changes, area) => {
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
if (area !== 'sync' || !changes.settings || !changes.settings.newValue) {
|
if (area !== 'sync' || !changes.settings || !changes.settings.newValue) {
|
||||||
|
|
|
@ -48,73 +48,3 @@ const loadScript = (() => {
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
let subscribers, observer;
|
|
||||||
// natively declared <script> elements in html can't have onload= attribute
|
|
||||||
// due to the default extension CSP that forbids inline code (and we don't want to relax it),
|
|
||||||
// so we're using MutationObserver to add onload event listener to the script element to be loaded
|
|
||||||
window.onDOMscriptReady = (srcSuffix, timeout = 1000) => {
|
|
||||||
if (!subscribers) {
|
|
||||||
subscribers = new Map();
|
|
||||||
observer = new MutationObserver(observe);
|
|
||||||
observer.observe(document.head, {childList: true});
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const listeners = subscribers.get(srcSuffix);
|
|
||||||
if (listeners) {
|
|
||||||
listeners.push(resolve);
|
|
||||||
} else {
|
|
||||||
subscribers.set(srcSuffix, [resolve]);
|
|
||||||
}
|
|
||||||
// a resolved Promise won't reject anymore
|
|
||||||
setTimeout(() => {
|
|
||||||
emptyAfterCleanup(srcSuffix);
|
|
||||||
reject(new Error('Timeout'));
|
|
||||||
}, timeout);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
function observe(mutations) {
|
|
||||||
for (const {addedNodes} of mutations) {
|
|
||||||
for (const n of addedNodes) {
|
|
||||||
if (n.src && getSubscribersForSrc(n.src)) {
|
|
||||||
n.addEventListener('load', notifySubscribers, {once: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSubscribersForSrc(src) {
|
|
||||||
for (const [suffix, listeners] of subscribers.entries()) {
|
|
||||||
if (src.endsWith(suffix)) {
|
|
||||||
return {suffix, listeners};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function notifySubscribers(event) {
|
|
||||||
for (let data; (data = getSubscribersForSrc(this.src));) {
|
|
||||||
data.listeners.forEach(fn => fn(event));
|
|
||||||
if (emptyAfterCleanup(data.suffix)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptyAfterCleanup(suffix) {
|
|
||||||
if (!subscribers) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
subscribers.delete(suffix);
|
|
||||||
if (!subscribers.size) {
|
|
||||||
observer.disconnect();
|
|
||||||
observer = null;
|
|
||||||
subscribers = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
|
@ -149,8 +149,8 @@
|
||||||
<script src="js/polyfill.js"></script>
|
<script src="js/polyfill.js"></script>
|
||||||
<script src="js/dom.js"></script>
|
<script src="js/dom.js"></script>
|
||||||
<script src="js/messaging.js"></script>
|
<script src="js/messaging.js"></script>
|
||||||
<script src="js/prefs.js"></script>
|
|
||||||
<script src="js/msg.js"></script>
|
<script src="js/msg.js"></script>
|
||||||
|
<script src="js/prefs.js"></script>
|
||||||
<script src="js/router.js"></script>
|
<script src="js/router.js"></script>
|
||||||
<script src="content/style-injector.js"></script>
|
<script src="content/style-injector.js"></script>
|
||||||
<script src="content/apply.js"></script>
|
<script src="content/apply.js"></script>
|
||||||
|
|
|
@ -23,7 +23,7 @@ function configDialog(style) {
|
||||||
vars.forEach(renderValueState);
|
vars.forEach(renderValueState);
|
||||||
|
|
||||||
return messageBox({
|
return messageBox({
|
||||||
title: `${style.name} v${data.version}`,
|
title: `${style.customName || style.name} v${data.version}`,
|
||||||
className: 'config-dialog' + (isPopup ? ' stylus-popup' : ''),
|
className: 'config-dialog' + (isPopup ? ' stylus-popup' : ''),
|
||||||
contents: [
|
contents: [
|
||||||
$create('.config-heading', data.supportURL &&
|
$create('.config-heading', data.supportURL &&
|
||||||
|
|
|
@ -14,7 +14,7 @@ let initialized = false;
|
||||||
router.watch({search: ['search']}, ([search]) => {
|
router.watch({search: ['search']}, ([search]) => {
|
||||||
$('#search').value = search || '';
|
$('#search').value = search || '';
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
init();
|
initFilters();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
} else {
|
} else {
|
||||||
searchStyles();
|
searchStyles();
|
||||||
|
@ -36,7 +36,7 @@ HTMLSelectElement.prototype.adjustWidth = function () {
|
||||||
parent.replaceChild(this, singleSelect);
|
parent.replaceChild(this, singleSelect);
|
||||||
};
|
};
|
||||||
|
|
||||||
function init() {
|
function initFilters() {
|
||||||
$('#search').oninput = e => {
|
$('#search').oninput = e => {
|
||||||
router.updateSearch('search', e.target.value);
|
router.updateSearch('search', e.target.value);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
/* global messageBox styleSectionsEqual API onDOMready
|
/* global messageBox styleSectionsEqual API onDOMready
|
||||||
tryJSONparse scrollElementIntoView $ $$ API $create t animateElement
|
tryJSONparse scrollElementIntoView $ $$ API $create t animateElement
|
||||||
styleJSONseemsValid */
|
styleJSONseemsValid bulkChangeQueue */
|
||||||
/* exported bulkChangeQueue bulkChangeTime */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const STYLISH_DUMP_FILE_EXT = '.txt';
|
const STYLISH_DUMP_FILE_EXT = '.txt';
|
||||||
const STYLUS_BACKUP_FILE_EXT = '.json';
|
const STYLUS_BACKUP_FILE_EXT = '.json';
|
||||||
|
|
||||||
let bulkChangeQueue = [];
|
|
||||||
let bulkChangeTime = 0;
|
|
||||||
|
|
||||||
onDOMready().then(() => {
|
onDOMready().then(() => {
|
||||||
$('#file-all-styles').onclick = () => exportToFile();
|
$('#file-all-styles').onclick = () => exportToFile();
|
||||||
$('#unfile-all-styles').onclick = () => importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT});
|
$('#unfile-all-styles').onclick = () => importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT});
|
||||||
|
@ -135,7 +131,7 @@ function importFromString(jsonString) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
bulkChangeQueue.length = 0;
|
bulkChangeQueue.length = 0;
|
||||||
bulkChangeTime = performance.now();
|
bulkChangeQueue.time = performance.now();
|
||||||
return API.importManyStyles(items.map(i => i.item))
|
return API.importManyStyles(items.map(i => i.item))
|
||||||
.then(styles => {
|
.then(styles => {
|
||||||
for (let i = 0; i < styles.length; i++) {
|
for (let i = 0; i < styles.length; i++) {
|
||||||
|
|
146
manage/manage.js
146
manage/manage.js
|
@ -4,11 +4,10 @@ global messageBox getStyleWithNoCode
|
||||||
checkUpdate handleUpdateInstalled
|
checkUpdate handleUpdateInstalled
|
||||||
objectDiff
|
objectDiff
|
||||||
configDialog
|
configDialog
|
||||||
sorter msg prefs API onDOMready $ $$ $create template setupLivePrefs
|
sorter msg prefs API $ $$ $create template setupLivePrefs
|
||||||
t tWordBreak formatDate
|
t tWordBreak formatDate
|
||||||
getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce
|
getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce
|
||||||
scrollElementIntoView CHROME VIVALDI router
|
scrollElementIntoView CHROME VIVALDI router
|
||||||
bulkChangeTime:true bulkChangeQueue
|
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -18,6 +17,8 @@ const ENTRY_ID_PREFIX_RAW = 'style-';
|
||||||
const ENTRY_ID_PREFIX = '#' + ENTRY_ID_PREFIX_RAW;
|
const ENTRY_ID_PREFIX = '#' + ENTRY_ID_PREFIX_RAW;
|
||||||
|
|
||||||
const BULK_THROTTLE_MS = 100;
|
const BULK_THROTTLE_MS = 100;
|
||||||
|
const bulkChangeQueue = [];
|
||||||
|
bulkChangeQueue.time = 0;
|
||||||
|
|
||||||
// define pref-mapped ids separately
|
// define pref-mapped ids separately
|
||||||
const newUI = {
|
const newUI = {
|
||||||
|
@ -49,18 +50,45 @@ Promise.all([
|
||||||
API.getAllStyles(true),
|
API.getAllStyles(true),
|
||||||
// FIXME: integrate this into filter.js
|
// FIXME: integrate this into filter.js
|
||||||
router.getSearch('search') && API.searchDB({query: router.getSearch('search')}),
|
router.getSearch('search') && API.searchDB({query: router.getSearch('search')}),
|
||||||
Promise.all([
|
waitForSelector('#installed'), // needed to avoid flicker due to an extra frame and layout shift
|
||||||
onDOMready(),
|
prefs.initializing
|
||||||
prefs.initializing,
|
]).then(([styles, ids, el]) => {
|
||||||
])
|
installed = el;
|
||||||
.then(() => {
|
installed.onclick = handleEvent.entryClicked;
|
||||||
initGlobalEvents();
|
$('#manage-options-button').onclick = () => router.updateHash('#stylus-options');
|
||||||
if (!VIVALDI) {
|
$('#sync-styles').onclick = () => router.updateHash('#stylus-options');
|
||||||
$$('#header select').forEach(el => el.adjustWidth());
|
$$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external));
|
||||||
}
|
// show date installed & last update on hover
|
||||||
}),
|
installed.addEventListener('mouseover', handleEvent.lazyAddEntryTitle);
|
||||||
]).then(args => {
|
installed.addEventListener('mouseout', handleEvent.lazyAddEntryTitle);
|
||||||
showStyles(...args);
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||||
|
// N.B. triggers existing onchange listeners
|
||||||
|
setupLivePrefs();
|
||||||
|
sorter.init();
|
||||||
|
prefs.subscribe(newUI.ids.map(newUI.prefKeyForId), () => switchUI());
|
||||||
|
switchUI({styleOnly: true});
|
||||||
|
// translate CSS manually
|
||||||
|
document.head.appendChild($create('style', `
|
||||||
|
.disabled h2::after {
|
||||||
|
content: "${t('genericDisabledLabel')}";
|
||||||
|
}
|
||||||
|
#update-all-no-updates[data-skipped-edited="true"]::after {
|
||||||
|
content: " ${t('updateAllCheckSucceededSomeEdited')}";
|
||||||
|
}
|
||||||
|
body.all-styles-hidden-by-filters::after {
|
||||||
|
content: "${t('filteredStylesAllHidden')}";
|
||||||
|
}
|
||||||
|
`));
|
||||||
|
if (!VIVALDI) {
|
||||||
|
$$('#header select').forEach(el => el.adjustWidth());
|
||||||
|
}
|
||||||
|
if (CHROME >= 80 && CHROME <= 88) {
|
||||||
|
// Wrong checkboxes are randomly checked after going back in history, https://crbug.com/1138598
|
||||||
|
addEventListener('pagehide', () => {
|
||||||
|
$$('input[type=checkbox]').forEach((el, i) => (el.name = `bug${i}`));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
showStyles(styles, ids);
|
||||||
});
|
});
|
||||||
|
|
||||||
msg.onExtension(onRuntimeMessage);
|
msg.onExtension(onRuntimeMessage);
|
||||||
|
@ -71,7 +99,7 @@ function onRuntimeMessage(msg) {
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
bulkChangeQueue.push(msg);
|
bulkChangeQueue.push(msg);
|
||||||
if (performance.now() - bulkChangeTime < BULK_THROTTLE_MS) {
|
if (performance.now() - bulkChangeQueue.time < BULK_THROTTLE_MS) {
|
||||||
debounce(handleBulkChange, BULK_THROTTLE_MS);
|
debounce(handleBulkChange, BULK_THROTTLE_MS);
|
||||||
} else {
|
} else {
|
||||||
handleBulkChange();
|
handleBulkChange();
|
||||||
|
@ -86,61 +114,16 @@ function onRuntimeMessage(msg) {
|
||||||
setTimeout(sorter.updateStripes, 0, {onlyWhenColumnsChanged: true});
|
setTimeout(sorter.updateStripes, 0, {onlyWhenColumnsChanged: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function initGlobalEvents() {
|
|
||||||
installed = $('#installed');
|
|
||||||
installed.onclick = handleEvent.entryClicked;
|
|
||||||
$('#manage-options-button').onclick = () => router.updateHash('#stylus-options');
|
|
||||||
$('#sync-styles').onclick = () => router.updateHash('#stylus-options');
|
|
||||||
$$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external));
|
|
||||||
// show date installed & last update on hover
|
|
||||||
installed.addEventListener('mouseover', handleEvent.lazyAddEntryTitle);
|
|
||||||
installed.addEventListener('mouseout', handleEvent.lazyAddEntryTitle);
|
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
||||||
|
|
||||||
$$('[data-toggle-on-click]').forEach(el => {
|
|
||||||
// dataset on SVG doesn't work in Chrome 49-??, works in 57+
|
|
||||||
const target = $(el.getAttribute('data-toggle-on-click'));
|
|
||||||
el.onclick = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
target.classList.toggle('hidden');
|
|
||||||
if (target.classList.contains('hidden')) {
|
|
||||||
el.removeAttribute('open');
|
|
||||||
} else {
|
|
||||||
el.setAttribute('open', '');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// N.B. triggers existing onchange listeners
|
|
||||||
setupLivePrefs();
|
|
||||||
sorter.init();
|
|
||||||
|
|
||||||
prefs.subscribe(newUI.ids.map(newUI.prefKeyForId), () => switchUI());
|
|
||||||
|
|
||||||
switchUI({styleOnly: true});
|
|
||||||
|
|
||||||
// translate CSS manually
|
|
||||||
document.head.appendChild($create('style', `
|
|
||||||
.disabled h2::after {
|
|
||||||
content: "${t('genericDisabledLabel')}";
|
|
||||||
}
|
|
||||||
#update-all-no-updates[data-skipped-edited="true"]::after {
|
|
||||||
content: " ${t('updateAllCheckSucceededSomeEdited')}";
|
|
||||||
}
|
|
||||||
body.all-styles-hidden-by-filters::after {
|
|
||||||
content: "${t('filteredStylesAllHidden')}";
|
|
||||||
}
|
|
||||||
`));
|
|
||||||
}
|
|
||||||
|
|
||||||
function showStyles(styles = [], matchUrlIds) {
|
function showStyles(styles = [], matchUrlIds) {
|
||||||
const sorted = sorter.sort({
|
const sorted = sorter.sort({
|
||||||
styles: styles.map(style => ({
|
styles: styles.map(style => {
|
||||||
style,
|
const name = style.customName || style.name || '';
|
||||||
name: (style.name || '').toLocaleLowerCase() + '\n' + style.name,
|
return {
|
||||||
})),
|
style,
|
||||||
|
// sort case-insensitively the whole list then sort dupes like `Foo` and `foo` case-sensitively
|
||||||
|
name: name.toLocaleLowerCase() + '\n' + name,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let firstRun = true;
|
let firstRun = true;
|
||||||
|
@ -187,7 +170,7 @@ function showStyles(styles = [], matchUrlIds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function createStyleElement({style, name}) {
|
function createStyleElement({style, name: nameLC}) {
|
||||||
// query the sub-elements just once, then reuse the references
|
// query the sub-elements just once, then reuse the references
|
||||||
if ((createStyleElement.parts || {}).newUI !== newUI.enabled) {
|
if ((createStyleElement.parts || {}).newUI !== newUI.enabled) {
|
||||||
const entry = template[`style${newUI.enabled ? 'Compact' : ''}`];
|
const entry = template[`style${newUI.enabled ? 'Compact' : ''}`];
|
||||||
|
@ -216,8 +199,9 @@ function createStyleElement({style, name}) {
|
||||||
}
|
}
|
||||||
const parts = createStyleElement.parts;
|
const parts = createStyleElement.parts;
|
||||||
const configurable = style.usercssData && style.usercssData.vars && Object.keys(style.usercssData.vars).length > 0;
|
const configurable = style.usercssData && style.usercssData.vars && Object.keys(style.usercssData.vars).length > 0;
|
||||||
|
const name = style.customName || style.name;
|
||||||
parts.checker.checked = style.enabled;
|
parts.checker.checked = style.enabled;
|
||||||
parts.nameLink.textContent = tWordBreak(style.name);
|
parts.nameLink.textContent = tWordBreak(name);
|
||||||
parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id;
|
parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id;
|
||||||
parts.homepage.href = parts.homepage.title = style.url || '';
|
parts.homepage.href = parts.homepage.title = style.url || '';
|
||||||
if (!newUI.enabled) {
|
if (!newUI.enabled) {
|
||||||
|
@ -234,7 +218,7 @@ function createStyleElement({style, name}) {
|
||||||
const entry = parts.entry.cloneNode(true);
|
const entry = parts.entry.cloneNode(true);
|
||||||
entry.id = ENTRY_ID_PREFIX_RAW + style.id;
|
entry.id = ENTRY_ID_PREFIX_RAW + style.id;
|
||||||
entry.styleId = style.id;
|
entry.styleId = style.id;
|
||||||
entry.styleNameLowerCase = name || style.name.toLocaleLowerCase();
|
entry.styleNameLowerCase = nameLC || name.toLocaleLowerCase() + '\n' + name;
|
||||||
entry.styleMeta = style;
|
entry.styleMeta = style;
|
||||||
entry.className = parts.entryClassBase + ' ' +
|
entry.className = parts.entryClassBase + ' ' +
|
||||||
(style.enabled ? 'enabled' : 'disabled') +
|
(style.enabled ? 'enabled' : 'disabled') +
|
||||||
|
@ -437,7 +421,7 @@ Object.assign(handleEvent, {
|
||||||
animateElement(entry);
|
animateElement(entry);
|
||||||
messageBox({
|
messageBox({
|
||||||
title: t('deleteStyleConfirm'),
|
title: t('deleteStyleConfirm'),
|
||||||
contents: entry.styleMeta.name,
|
contents: entry.styleMeta.customName || entry.styleMeta.name,
|
||||||
className: 'danger center',
|
className: 'danger center',
|
||||||
buttons: [t('confirmDelete'), t('confirmCancel')],
|
buttons: [t('confirmDelete'), t('confirmCancel')],
|
||||||
})
|
})
|
||||||
|
@ -533,7 +517,7 @@ function handleBulkChange() {
|
||||||
const {id} = msg.style;
|
const {id} = msg.style;
|
||||||
if (msg.method === 'styleDeleted') {
|
if (msg.method === 'styleDeleted') {
|
||||||
handleDelete(id);
|
handleDelete(id);
|
||||||
bulkChangeTime = performance.now();
|
bulkChangeQueue.time = performance.now();
|
||||||
} else {
|
} else {
|
||||||
handleUpdateForId(id, msg);
|
handleUpdateForId(id, msg);
|
||||||
}
|
}
|
||||||
|
@ -544,7 +528,7 @@ function handleBulkChange() {
|
||||||
function handleUpdateForId(id, opts) {
|
function handleUpdateForId(id, opts) {
|
||||||
return API.getStyle(id, true).then(style => {
|
return API.getStyle(id, true).then(style => {
|
||||||
handleUpdate(style, opts);
|
handleUpdate(style, opts);
|
||||||
bulkChangeTime = performance.now();
|
bulkChangeQueue.time = performance.now();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -718,6 +702,20 @@ function highlightEditedStyle() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForSelector(selector) {
|
||||||
|
// TODO: if used in other places, move to dom.js
|
||||||
|
// TODO: if used concurrently, rework to use just one observer internally
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const mo = new MutationObserver(() => {
|
||||||
|
const el = $(selector);
|
||||||
|
if (el) {
|
||||||
|
mo.disconnect();
|
||||||
|
resolve(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mo.observe(document, {childList: true, subtree: true});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function embedOptions() {
|
function embedOptions() {
|
||||||
let options = $('#stylus-embedded-options');
|
let options = $('#stylus-embedded-options');
|
||||||
|
|
|
@ -130,7 +130,7 @@ const sorter = (() => {
|
||||||
const sorted = sort({
|
const sorted = sort({
|
||||||
styles: current.map(entry => ({
|
styles: current.map(entry => ({
|
||||||
entry,
|
entry,
|
||||||
name: entry.styleNameLowerCase + '\n' + entry.styleMeta.name,
|
name: entry.styleNameLowerCase,
|
||||||
style: entry.styleMeta,
|
style: entry.styleMeta,
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
|
@ -180,8 +180,8 @@
|
||||||
<script src="js/dom.js"></script>
|
<script src="js/dom.js"></script>
|
||||||
<script src="js/messaging.js"></script>
|
<script src="js/messaging.js"></script>
|
||||||
<script src="js/localization.js"></script>
|
<script src="js/localization.js"></script>
|
||||||
<script src="js/prefs.js"></script>
|
|
||||||
<script src="js/msg.js"></script>
|
<script src="js/msg.js"></script>
|
||||||
|
<script src="js/prefs.js"></script>
|
||||||
<script src="content/style-injector.js"></script>
|
<script src="content/style-injector.js"></script>
|
||||||
<script src="content/apply.js"></script>
|
<script src="content/apply.js"></script>
|
||||||
|
|
||||||
|
|
|
@ -310,7 +310,7 @@ function sortStyles(entries) {
|
||||||
return entries.sort(({styleMeta: a}, {styleMeta: b}) =>
|
return entries.sort(({styleMeta: a}, {styleMeta: b}) =>
|
||||||
Boolean(a.frameUrl) - Boolean(b.frameUrl) ||
|
Boolean(a.frameUrl) - Boolean(b.frameUrl) ||
|
||||||
enabledFirst && Boolean(b.enabled) - Boolean(a.enabled) ||
|
enabledFirst && Boolean(b.enabled) - Boolean(a.enabled) ||
|
||||||
a.name.localeCompare(b.name));
|
(a.customName || a.name).localeCompare(b.customName || b.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
function showStyles(frameResults) {
|
function showStyles(frameResults) {
|
||||||
|
@ -408,7 +408,7 @@ function createStyleElement(style) {
|
||||||
$('.checker', entry).checked = style.enabled;
|
$('.checker', entry).checked = style.enabled;
|
||||||
|
|
||||||
const styleName = $('.style-name', entry);
|
const styleName = $('.style-name', entry);
|
||||||
styleName.lastChild.textContent = style.name;
|
styleName.lastChild.textContent = style.customName || style.name;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
styleName.title = entry.styleMeta.sloppy ?
|
styleName.title = entry.styleMeta.sloppy ?
|
||||||
t('styleNotAppliedRegexpProblemTooltip') :
|
t('styleNotAppliedRegexpProblemTooltip') :
|
||||||
|
|
Loading…
Reference in New Issue
Block a user