Merge pull request #110 from Mottie/cleanup

Coding Style Cleanup
This commit is contained in:
tophf 2017-07-19 10:58:17 +03:00 committed by GitHub
commit e48e1ab874
123 changed files with 4234 additions and 4119 deletions

View File

@ -1,3 +1,2 @@
beautify/ vendor/
codemirror/ vendor-overwrites/
csslint/

View File

@ -82,7 +82,7 @@ rules:
dot-location: [2, property] dot-location: [2, property]
dot-notation: [0] dot-notation: [0]
eol-last: [2] eol-last: [2]
eqeqeq: [0] eqeqeq: [1, always]
func-call-spacing: [2, never] func-call-spacing: [2, never]
func-name-matching: [0] func-name-matching: [0]
func-names: [0] func-names: [0]
@ -132,7 +132,7 @@ rules:
no-duplicate-imports: [2] no-duplicate-imports: [2]
no-else-return: [0] no-else-return: [0]
no-empty-character-class: [2] no-empty-character-class: [2]
no-empty-function: [0] no-empty-function: [1]
no-empty-pattern: [2] no-empty-pattern: [2]
no-empty: [2, {allowEmptyCatch: true}] no-empty: [2, {allowEmptyCatch: true}]
no-eq-null: [2] no-eq-null: [2]
@ -166,7 +166,7 @@ rules:
no-mixed-operators: [0] no-mixed-operators: [0]
no-mixed-requires: [2, true] no-mixed-requires: [2, true]
no-mixed-spaces-and-tabs: [2] no-mixed-spaces-and-tabs: [2]
no-multi-spaces: [0] no-multi-spaces: [2, {ignoreEOLComments: true}]
no-multi-str: [2] no-multi-str: [2]
no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}] no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}]
no-native-reassign: [2] no-native-reassign: [2]
@ -249,7 +249,7 @@ rules:
sort-imports: [0] sort-imports: [0]
sort-keys: [0] sort-keys: [0]
space-before-blocks: [2, always] space-before-blocks: [2, always]
space-before-function-paren: [2, never] space-before-function-paren: [2, {anonymous: always, asyncArrow: always, named: never}]
space-in-parens: [2, never] space-in-parens: [2, never]
space-infix-ops: [2] space-infix-ops: [2]
space-unary-ops: [2] space-unary-ops: [2]

33
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,33 @@
# Contributing to Stylus
1. [Getting Involved](#getting-involved)
2. [How to Report Issues](#how-to-report-issues)
3. [Adding Tranlations](#adding-translations)
4. [Core Style Guide](#core-style-guide)
5. [Getting Started](#getting-started)
## Getting Involved
There are a number of ways to get involved with the development of Stylus. Even if you've never contributed to an Open Source project before, we're always looking for help by identifying issues and suggesting improvements.
## How to Report issues
When an issue is opened, a template is provided. Please answer these questions as thoroughly as possible. If we were psychic, we'd be hanging out in casinos playing poker until they kicked us out. We can't read your mind! Please provide step-by-step direction on how to reproduce the issue as well as the browser, operating system and versions of each.
When adding a feature request, please include
## Adding Translations
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus). When `messages.json` file is ready to be merged, please open a new bug report in [stylus/issues](https://github.com/openstyles/stylus/issues).
## Core Style Guide
* Use the provided `.editorconfig` file with your code editor. Don't know what that is? Then check out http://editorconfig.org/.
## Getting Started
* First open an issue to discuss your changes.
* Then download, fork or clone this repository.
<!-- * Use [node.js](http://nodejs.org/) to run `npm install`. -->
* Make any changes within a branch of this repository (not the `master` branch).
* Submit a pull request and include a reference to the initial issue with the discussion.

28
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,28 @@
<!--
Thank you for reporting an issue. Please make sure that your style is up to
date and you checked the recent commits that your issue wasn't recently
addressed. To update:
Make sure to first update DIRECTLY from https://userstyles.org/styles/37035/
(see https://github.com/JasonBarnabe/stylish-chrome/issues/179 to know why) or,
if using the GitHub-Dark script, use the "Force Update Style" button, then
force refresh the web page (Windows: Ctrl+F5; Mac/Apple: Apple+R or Command+R;
Linux: F5).
If the issue persists, please help us identifying the cause by providing these
details:
-->
* **Browser**:
* **Operating System**:
* **Screenshot**:
* **HTML of the section where the issue occurs**:
<!-- You can get the HTML by right click on the element, look for the
highlighted node in the DevTools, right click it and select
Copy -> Outer HTML -->
````html
````

View File

@ -16,13 +16,15 @@ See the [help docs](http://userstyles.org/help/stylish_chrome) or [ask in userst
## Contributing ## Contributing
The source is hosted on [GitHub](https://github.com/schomery/stylus) and pull requests are welcome. The source is hosted on [GitHub](https://github.com/openstyles/stylus) and pull requests are welcome.
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus). When `messages.json` file is ready to be merged, please open a new bug report in [stylus/issues](https://github.com/schomery/stylus/issues). You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus). When `messages.json` file is ready to be merged, please open a new bug report in [stylus/issues](https://github.com/openstyles/stylus/issues).
See our [contributing](./.github/CONTRIBUTING.md) page for more details.
## License ## License
For copyright status of the "codemirror" directory, see codemirror/LICENSE. Everything else is: For copyright status of the "codemirror" directory, see [codemirror/LICENSE](https://github.com/openstyles/stylus/blob/master/src/vendor/codemirror/LICENSE). Everything else is:
Copyright (C) 2005-2014 Jason Barnabe <jason.barnabe@gmail.com> Copyright (C) 2005-2014 Jason Barnabe <jason.barnabe@gmail.com>

View File

@ -26,14 +26,14 @@ chrome.webNavigation.onHistoryStateUpdated.addListener(data =>
chrome.webNavigation.onReferenceFragmentUpdated.addListener(data => chrome.webNavigation.onReferenceFragmentUpdated.addListener(data =>
webNavigationListener('styleReplaceAll', data)); webNavigationListener('styleReplaceAll', data));
chrome.tabs.onAttached.addListener((tabId, data) => { chrome.tabs.onAttached.addListener(tabId => {
// When an edit page gets attached or detached, remember its state // When an edit page gets attached or detached, remember its state
// so we can do the same to the next one to open. // so we can do the same to the next one to open.
chrome.tabs.get(tabId, tab => { chrome.tabs.get(tabId, tab => {
if (tab.url.startsWith(URLS.ownOrigin + 'edit.html')) { if (tab.url.startsWith(URLS.ownOrigin + 'edit.html')) {
chrome.windows.get(tab.windowId, {populate: true}, win => { chrome.windows.get(tab.windowId, {populate: true}, win => {
// If there's only one tab in this window, it's been dragged to new window // If there's only one tab in this window, it's been dragged to new window
prefs.set('openEditInWindow', win.tabs.length == 1); prefs.set('openEditInWindow', win.tabs.length === 1);
}); });
} }
}); });
@ -59,13 +59,13 @@ updateIcon({id: undefined}, {});
const manifest = chrome.runtime.getManifest(); const manifest = chrome.runtime.getManifest();
// Open FAQs page once after installation to guide new users. // Open FAQs page once after installation to guide new users.
// Do not display it in development mode. // Do not display it in development mode.
if (reason == 'install' && manifest.update_url) { if (reason === 'install' && manifest.update_url) {
setTimeout(openURL, 100, { setTimeout(openURL, 100, {
url: 'http://add0n.com/stylus.html' url: 'http://add0n.com/stylus.html'
}); });
} }
// reset L10N cache on update // reset L10N cache on update
if (reason == 'update') { if (reason === 'update') {
localStorage.L10N = JSON.stringify({ localStorage.L10N = JSON.stringify({
browserUIlanguage: chrome.i18n.getUILanguage(), browserUIlanguage: chrome.i18n.getUILanguage(),
}); });
@ -98,7 +98,7 @@ updateIcon({id: undefined}, {});
// browser commands // browser commands
browserCommands = { browserCommands = {
openManage() { openManage() {
openURL({url: '/manage.html'}); openURL({url: 'manage.html'});
}, },
styleDisableAll(info) { styleDisableAll(info) {
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll')); prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
@ -138,7 +138,7 @@ contextMenus = Object.assign({
const item = Object.assign({id}, contextMenus[id]); const item = Object.assign({id}, contextMenus[id]);
const prefValue = prefs.readOnlyValues[id]; const prefValue = prefs.readOnlyValues[id];
item.title = chrome.i18n.getMessage(item.title); item.title = chrome.i18n.getMessage(item.title);
if (!item.type && typeof prefValue == 'boolean') { if (!item.type && typeof prefValue === 'boolean') {
item.type = 'checkbox'; item.type = 'checkbox';
item.checked = prefValue; item.checked = prefValue;
} }
@ -151,7 +151,7 @@ contextMenus = Object.assign({
}; };
createContextMenus(); createContextMenus();
prefs.subscribe((id, checked) => { prefs.subscribe((id, checked) => {
if (id == 'editor.contextDelete') { if (id === 'editor.contextDelete') {
if (checked) { if (checked) {
createContextMenus([id]); createContextMenus([id]);
} else { } else {
@ -160,7 +160,7 @@ contextMenus = Object.assign({
} else { } else {
chrome.contextMenus.update(id, {checked}, ignoreChromeError); chrome.contextMenus.update(id, {checked}, ignoreChromeError);
} }
}, Object.keys(contextMenus).filter(key => typeof prefs.readOnlyValues[key] == 'boolean')); }, Object.keys(contextMenus).filter(key => typeof prefs.readOnlyValues[key] === 'boolean'));
} }
// ************************************************************************* // *************************************************************************
@ -176,7 +176,7 @@ contextMenus = Object.assign({
.replace(/\*/g, '.*?'), flags); .replace(/\*/g, '.*?'), flags);
for (const cs of contentScripts) { for (const cs of contentScripts) {
cs.matches = cs.matches.map(m => ( cs.matches = cs.matches.map(m => (
m == ALL_URLS ? m : wildcardAsRegExp(m) m === ALL_URLS ? m : wildcardAsRegExp(m)
)); ));
} }
@ -191,8 +191,8 @@ contextMenus = Object.assign({
const pingCS = (cs, {id, url}) => { const pingCS = (cs, {id, url}) => {
cs.matches.some(match => { cs.matches.some(match => {
if ((match == ALL_URLS || url.match(match)) if ((match === ALL_URLS || url.match(match))
&& (!url.startsWith('chrome') || url == NTP)) { && (!url.startsWith('chrome') || url === NTP)) {
chrome.tabs.sendMessage(id, PING, pong => { chrome.tabs.sendMessage(id, PING, pong => {
if (!pong) { if (!pong) {
injectCS(cs, id); injectCS(cs, id);
@ -229,7 +229,7 @@ function webNavigationListener(method, {url, tabId, frameId}) {
}); });
} }
// main page frame id is 0 // main page frame id is 0
if (frameId == 0) { if (frameId === 0) {
updateIcon({id: tabId, url}, styles); updateIcon({id: tabId, url}, styles);
} }
}); });
@ -258,7 +258,7 @@ function updateIcon(tab, styles) {
} }
} }
const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll'); const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
const postfix = disableAll ? 'x' : numStyles == 0 ? 'w' : ''; const postfix = disableAll ? 'x' : numStyles === 0 ? 'w' : '';
const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal'); const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal');
const text = prefs.get('show-badge') && numStyles ? String(numStyles) : ''; const text = prefs.get('show-badge') && numStyles ? String(numStyles) : '';
const iconset = ['', 'light/'][prefs.get('iconset')] || ''; const iconset = ['', 'light/'][prefs.get('iconset')] || '';

View File

@ -63,7 +63,7 @@ function dbExec(method, data) {
reject(event); reject(event);
}, },
onupgradeneeded(event) { onupgradeneeded(event) {
if (event.oldVersion == 0) { if (event.oldVersion === 0) {
event.target.result.createObjectStore('styles', { event.target.result.createObjectStore('styles', {
keyPath: 'id', keyPath: 'id',
autoIncrement: true, autoIncrement: true,
@ -111,15 +111,17 @@ function filterStyles({
asHash = null, asHash = null,
strictRegexp = true, // used by the popup to detect bad regexps strictRegexp = true, // used by the popup to detect bad regexps
} = {}) { } = {}) {
enabled = enabled === null || typeof enabled == 'boolean' ? enabled : enabled = enabled === null || typeof enabled === 'boolean' ? enabled :
typeof enabled == 'string' ? enabled == 'true' : null; typeof enabled === 'string' ? enabled === 'true' : null;
id = id === null ? null : Number(id); id = id === null ? null : Number(id);
if (enabled === null if (
&& url === null enabled === null &&
&& id === null url === null &&
&& matchUrl === null id === null &&
&& asHash != true) { matchUrl === null &&
asHash !== true
) {
return cachedStyles.list; return cachedStyles.list;
} }
const blankHash = asHash && { const blankHash = asHash && {
@ -189,10 +191,11 @@ function filterStylesInternal({
const needSections = asHash || matchUrl !== null; const needSections = asHash || matchUrl !== null;
for (let i = 0, style; (style = styles[i]); i++) { let style;
if ((enabled === null || style.enabled == enabled) for (let i = 0; (style = styles[i]); i++) {
&& (url === null || style.url == url) if ((enabled === null || style.enabled === enabled)
&& (id === null || style.id == id)) { && (url === null || style.url === url)
&& (id === null || style.id === id)) {
const sections = needSections && const sections = needSections &&
getApplicableSections({style, matchUrl, strictRegexp, stopOnFirst: !asHash}); getApplicableSections({style, matchUrl, strictRegexp, stopOnFirst: !asHash});
if (asHash) { if (asHash) {
@ -230,17 +233,18 @@ function saveStyle(style) {
if (!style.name) { if (!style.name) {
delete style.name; delete style.name;
} }
let existed, codeIsUpdated; let existed;
if (reason == 'update' || reason == 'update-digest') { let codeIsUpdated;
if (reason === 'update' || reason === 'update-digest') {
return calcStyleDigest(style).then(digest => { return calcStyleDigest(style).then(digest => {
style.originalDigest = digest; style.originalDigest = digest;
return decide(); return decide();
}); });
} }
if (reason == 'import') { if (reason === 'import') {
style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
delete style.styleDigest; // TODO: remove in the future delete style.styleDigest; // TODO: remove in the future
if (typeof style.originalDigest != 'string' || style.originalDigest.length != 40) { if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
delete style.originalDigest; delete style.originalDigest;
} }
} }
@ -253,7 +257,7 @@ function saveStyle(style) {
return dbExec('get', id).then((event, store) => { return dbExec('get', id).then((event, store) => {
const oldStyle = event.target.result; const oldStyle = event.target.result;
existed = Boolean(oldStyle); existed = Boolean(oldStyle);
if (reason == 'update-digest' && oldStyle.originalDigest == style.originalDigest) { if (reason === 'update-digest' && oldStyle.originalDigest === style.originalDigest) {
return style; return style;
} }
codeIsUpdated = !existed || 'sections' in style && !styleSectionsEqual(style, oldStyle); codeIsUpdated = !existed || 'sections' in style && !styleSectionsEqual(style, oldStyle);
@ -287,7 +291,7 @@ function saveStyle(style) {
} }
function done(event) { function done(event) {
if (reason == 'update-digest') { if (reason === 'update-digest') {
return style; return style;
} }
style.id = style.id || event.target.result; style.id = style.id || event.target.result;
@ -365,14 +369,14 @@ function getApplicableSections({style, matchUrl, strictRegexp = true, stopOnFirs
function arraySomeMatches(array, matchUrl, strictRegexp) { function arraySomeMatches(array, matchUrl, strictRegexp) {
for (const regexp of array) { for (const regexp of array) {
for (let pass = 1; pass <= (strictRegexp ? 1 : 2); pass++) { for (let pass = 1; pass <= (strictRegexp ? 1 : 2); pass++) {
const cacheKey = pass == 1 ? regexp : SLOPPY_REGEXP_PREFIX + regexp; const cacheKey = pass === 1 ? regexp : SLOPPY_REGEXP_PREFIX + regexp;
let rx = cachedStyles.regexps.get(cacheKey); let rx = cachedStyles.regexps.get(cacheKey);
if (rx == false) { if (rx === false) {
// invalid regexp // invalid regexp
break; break;
} }
if (!rx) { if (!rx) {
const anchored = pass == 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$'; const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
rx = tryRegExp(anchored); rx = tryRegExp(anchored);
cachedStyles.regexps.set(cacheKey, rx || false); cachedStyles.regexps.set(cacheKey, rx || false);
if (!rx) { if (!rx) {
@ -413,7 +417,7 @@ function styleSectionsEqual({sections: a}, {sections: b}) {
if (!a || !b) { if (!a || !b) {
return undefined; return undefined;
} }
if (a.length != b.length) { if (a.length !== b.length) {
return false; return false;
} }
const checkedInB = []; const checkedInB = [];
@ -430,16 +434,16 @@ function styleSectionsEqual({sections: a}, {sections: b}) {
return false; return false;
} }
} }
return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a == b); return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a === b);
} }
function equalOrEmpty(a, b, telltale, comparator) { function equalOrEmpty(a, b, telltale, comparator) {
const typeA = a && typeof a[telltale] == 'function'; const typeA = a && typeof a[telltale] === 'function';
const typeB = b && typeof b[telltale] == 'function'; const typeB = b && typeof b[telltale] === 'function';
return ( return (
(a === null || a === undefined || (typeA && !a.length)) && (a === null || a === undefined || (typeA && !a.length)) &&
(b === null || b === undefined || (typeB && !b.length)) (b === null || b === undefined || (typeB && !b.length))
) || typeA && typeB && a.length == b.length && comparator(a, b); ) || typeA && typeB && a.length === b.length && comparator(a, b);
} }
function arrayMirrors(array1, array2) { function arrayMirrors(array1, array2) {
@ -523,12 +527,12 @@ function cleanupCachedFilters({force = false} = {}) {
function getDomains(url) { function getDomains(url) {
if (url.indexOf('file:') == 0) { if (url.indexOf('file:') === 0) {
return []; return [];
} }
let d = /.*?:\/*([^/:]+)/.exec(url)[1]; let d = /.*?:\/*([^/:]+)/.exec(url)[1];
const domains = [d]; const domains = [d];
while (d.indexOf('.') != -1) { while (d.indexOf('.') !== -1) {
d = d.substring(d.indexOf('.') + 1); d = d.substring(d.indexOf('.') + 1);
domains.push(d); domains.push(d);
} }

View File

@ -68,17 +68,17 @@ var updater = {
}); });
function maybeFetchMd5(digest) { function maybeFetchMd5(digest) {
if (!ignoreDigest && style.originalDigest && style.originalDigest != digest) { if (!ignoreDigest && style.originalDigest && style.originalDigest !== digest) {
return Promise.reject(updater.EDITED); return Promise.reject(updater.EDITED);
} }
return download(style.md5Url); return download(style.md5Url);
} }
function maybeFetchCode(md5) { function maybeFetchCode(md5) {
if (!md5 || md5.length != 32) { if (!md5 || md5.length !== 32) {
return Promise.reject(updater.ERROR_MD5); return Promise.reject(updater.ERROR_MD5);
} }
if (md5 == style.originalMd5 && style.originalDigest && !ignoreDigest) { if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
return Promise.reject(updater.SAME_MD5); return Promise.reject(updater.SAME_MD5);
} }
return download(style.updateUrl); return download(style.updateUrl);
@ -109,8 +109,8 @@ var updater = {
return json return json
&& json.sections && json.sections
&& json.sections.length && json.sections.length
&& typeof json.sections.every == 'function' && typeof json.sections.every === 'function'
&& typeof json.sections[0].code == 'string'; && typeof json.sections[0].code === 'string';
} }
}, },

View File

@ -28,7 +28,7 @@ function requestStyles(options, callback = applyStyles) {
// dynamic about: and javascript: iframes don't have an URL yet // dynamic about: and javascript: iframes don't have an URL yet
// so we'll try the parent frame which is guaranteed to have a real URL // so we'll try the parent frame which is guaranteed to have a real URL
try { try {
if (window != parent) { if (window !== parent) {
matchUrl = parent.location.href; matchUrl = parent.location.href;
} }
} catch (e) {} } catch (e) {}
@ -49,7 +49,7 @@ function requestStyles(options, callback = applyStyles) {
function applyOnMessage(request, sender, sendResponse) { function applyOnMessage(request, sender, sendResponse) {
if (request.styles == 'DIY') { if (request.styles === 'DIY') {
// Do-It-Yourself tells our built-in pages to fetch the styles directly // Do-It-Yourself tells our built-in pages to fetch the styles directly
// which is faster because IPC messaging JSON-ifies everything internally // which is faster because IPC messaging JSON-ifies everything internally
requestStyles({}, styles => { requestStyles({}, styles => {
@ -114,7 +114,7 @@ function doDisableAll(disable = disableAll) {
disableAll = disable; disableAll = disable;
Array.prototype.forEach.call(document.styleSheets, stylesheet => { Array.prototype.forEach.call(document.styleSheets, stylesheet => {
if (stylesheet.ownerNode.matches(`STYLE.stylus[id^="${ID_PREFIX}"]`) if (stylesheet.ownerNode.matches(`STYLE.stylus[id^="${ID_PREFIX}"]`)
&& stylesheet.disabled != disable) { && stylesheet.disabled !== disable) {
stylesheet.disabled = disable; stylesheet.disabled = disable;
} }
}); });
@ -122,14 +122,14 @@ function doDisableAll(disable = disableAll) {
function doExposeIframes(state = exposeIframes) { function doExposeIframes(state = exposeIframes) {
if (state === exposeIframes || window == parent) { if (state === exposeIframes || window === parent) {
return; return;
} }
exposeIframes = state; exposeIframes = state;
const attr = document.documentElement.getAttribute('stylus-iframe'); const attr = document.documentElement.getAttribute('stylus-iframe');
if (state && attr != '') { if (state && attr !== '') {
document.documentElement.setAttribute('stylus-iframe', ''); document.documentElement.setAttribute('stylus-iframe', '');
} else if (!state && attr == '') { } else if (!state && attr === '') {
document.documentElement.removeAttribute('stylus-iframe'); document.documentElement.removeAttribute('stylus-iframe');
} }
} }
@ -193,7 +193,7 @@ function applyStyles(styles) {
} }
if (document.head if (document.head
&& document.head.firstChild && document.head.firstChild
&& document.head.firstChild.id == 'xml-viewer-style') { && document.head.firstChild.id === 'xml-viewer-style') {
// when site response is application/xml Chrome displays our style elements // when site response is application/xml Chrome displays our style elements
// under document.documentElement as plain text so we need to move them into HEAD // under document.documentElement as plain text so we need to move them into HEAD
// which is already autogenerated at this moment // which is already autogenerated at this moment
@ -293,7 +293,7 @@ function initDocRewriteObserver() {
for (let m = mutations.length; --m >= 0;) { for (let m = mutations.length; --m >= 0;) {
const added = mutations[m].addedNodes; const added = mutations[m].addedNodes;
for (let n = added.length; --n >= 0;) { for (let n = added.length; --n >= 0;) {
if (added[n].localName == 'html') { if (added[n].localName === 'html') {
reinjectStyles(); reinjectStyles();
return; return;
} }
@ -303,7 +303,7 @@ function initDocRewriteObserver() {
docRewriteObserver.observe(document, {childList: true}); docRewriteObserver.observe(document, {childList: true});
// detect dynamic iframes rewritten after creation by the embedder i.e. externally // detect dynamic iframes rewritten after creation by the embedder i.e. externally
setTimeout(() => { setTimeout(() => {
if (document.documentElement != ROOT) { if (document.documentElement !== ROOT) {
reinjectStyles(); reinjectStyles();
} }
}); });

View File

@ -1,360 +1,360 @@
'use strict'; 'use strict';
const CHROMIUM = /Chromium/.test(navigator.userAgent); // non-Windows Chromium const CHROMIUM = /Chromium/.test(navigator.userAgent); // non-Windows Chromium
const FIREFOX = /Firefox/.test(navigator.userAgent); const FIREFOX = /Firefox/.test(navigator.userAgent);
const VIVALDI = /Vivaldi/.test(navigator.userAgent); const VIVALDI = /Vivaldi/.test(navigator.userAgent);
const OPERA = /OPR/.test(navigator.userAgent); const OPERA = /OPR/.test(navigator.userAgent);
document.addEventListener('stylishUpdate', onUpdateClicked); document.addEventListener('stylishUpdate', onUpdateClicked);
document.addEventListener('stylishUpdateChrome', onUpdateClicked); document.addEventListener('stylishUpdateChrome', onUpdateClicked);
document.addEventListener('stylishUpdateOpera', onUpdateClicked); document.addEventListener('stylishUpdateOpera', onUpdateClicked);
document.addEventListener('stylishInstall', onInstallClicked); document.addEventListener('stylishInstall', onInstallClicked);
document.addEventListener('stylishInstallChrome', onInstallClicked); document.addEventListener('stylishInstallChrome', onInstallClicked);
document.addEventListener('stylishInstallOpera', onInstallClicked); document.addEventListener('stylishInstallOpera', onInstallClicked);
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
// orphaned content script check // orphaned content script check
if (msg.method == 'ping') { if (msg.method === 'ping') {
sendResponse(true); sendResponse(true);
} }
}); });
// TODO: remove the following statement when USO is fixed // TODO: remove the following statement when USO is fixed
document.documentElement.appendChild(document.createElement('script')).text = '(' + document.documentElement.appendChild(document.createElement('script')).text = '(' +
function() { function () {
let settings; let settings;
document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) { document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) {
document.removeEventListener('stylusFixBuggyUSOsettings', _); document.removeEventListener('stylusFixBuggyUSOsettings', _);
settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search); settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search);
}); });
const originalResponseJson = Response.prototype.json; const originalResponseJson = Response.prototype.json;
Response.prototype.json = function(...args) { Response.prototype.json = function (...args) {
return originalResponseJson.call(this, ...args).then(json => { return originalResponseJson.call(this, ...args).then(json => {
Response.prototype.json = originalResponseJson; Response.prototype.json = originalResponseJson;
if (!settings || typeof ((json || {}).style_settings || {}).every != 'function') { if (!settings || typeof ((json || {}).style_settings || {}).every !== 'function') {
return json; return json;
} }
const images = new Map(); const images = new Map();
for (const jsonSetting of json.style_settings) { for (const jsonSetting of json.style_settings) {
let value = settings.get('ik-' + jsonSetting.install_key); let value = settings.get('ik-' + jsonSetting.install_key);
if (!value if (!value
|| !jsonSetting.style_setting_options || !jsonSetting.style_setting_options
|| !jsonSetting.style_setting_options[0]) { || !jsonSetting.style_setting_options[0]) {
continue; continue;
} }
if (value.startsWith('ik-')) { if (value.startsWith('ik-')) {
value = value.replace(/^ik-/, ''); value = value.replace(/^ik-/, '');
const defaultItem = jsonSetting.style_setting_options.find(item => item.default); const defaultItem = jsonSetting.style_setting_options.find(item => item.default);
if (!defaultItem || defaultItem.install_key != value) { if (!defaultItem || defaultItem.install_key !== value) {
if (defaultItem) { if (defaultItem) {
defaultItem.default = false; defaultItem.default = false;
} }
jsonSetting.style_setting_options.some(item => { jsonSetting.style_setting_options.some(item => {
if (item.install_key == value) { if (item.install_key === value) {
item.default = true; item.default = true;
return true; return true;
} }
}); });
} }
} else if (jsonSetting.setting_type == 'image') { } else if (jsonSetting.setting_type === 'image') {
jsonSetting.style_setting_options.some(item => { jsonSetting.style_setting_options.some(item => {
if (item.default) { if (item.default) {
item.default = false; item.default = false;
return true; return true;
} }
}); });
images.set(jsonSetting.install_key, value); images.set(jsonSetting.install_key, value);
} else { } else {
const item = jsonSetting.style_setting_options[0]; const item = jsonSetting.style_setting_options[0];
if (item.value !== value && item.install_key == 'placeholder') { if (item.value !== value && item.install_key === 'placeholder') {
item.value = value; item.value = value;
} }
} }
} }
if (images.size) { if (images.size) {
new MutationObserver((_, observer) => { new MutationObserver((_, observer) => {
if (!document.getElementById('style-settings')) { if (!document.getElementById('style-settings')) {
return; return;
} }
observer.disconnect(); observer.disconnect();
for (const [name, url] of images.entries()) { for (const [name, url] of images.entries()) {
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`); const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url')); const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
if (elUrl) { if (elUrl) {
elUrl.value = url; elUrl.value = url;
} }
} }
}).observe(document, {childList: true, subtree: true}); }).observe(document, {childList: true, subtree: true});
} }
return json; return json;
}); });
}; };
} + ')()'; } + ')()';
// TODO: remove the following statement when USO pagination is fixed // TODO: remove the following statement when USO pagination is fixed
if (location.search.includes('category=')) { if (location.search.includes('category=')) {
document.addEventListener('DOMContentLoaded', function _() { document.addEventListener('DOMContentLoaded', function _() {
document.removeEventListener('DOMContentLoaded', _); document.removeEventListener('DOMContentLoaded', _);
new MutationObserver((_, observer) => { new MutationObserver((_, observer) => {
if (!document.getElementById('pagination')) { if (!document.getElementById('pagination')) {
return; return;
} }
observer.disconnect(); observer.disconnect();
const category = '&' + location.search.match(/category=[^&]+/)[0]; const category = '&' + location.search.match(/category=[^&]+/)[0];
const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])'); const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])');
for (let i = 0; i < links.length; i++) { for (let i = 0; i < links.length; i++) {
links[i].href += category; links[i].href += category;
} }
}).observe(document, {childList: true, subtree: true}); }).observe(document, {childList: true, subtree: true});
}); });
} }
new MutationObserver((mutations, observer) => { new MutationObserver((mutations, observer) => {
if (document.body) { if (document.body) {
observer.disconnect(); observer.disconnect();
// TODO: remove the following statement when USO pagination title is fixed // TODO: remove the following statement when USO pagination title is fixed
document.title = document.title.replace(/^\d+&category=/, ''); document.title = document.title.replace(/^\d+&category=/, '');
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
method: 'getStyles', method: 'getStyles',
url: getMeta('stylish-id-url') || location.href url: getMeta('stylish-id-url') || location.href
}, checkUpdatability); }, checkUpdatability);
} }
}).observe(document.documentElement, {childList: true}); }).observe(document.documentElement, {childList: true});
/* since we are using "stylish-code-chrome" meta key on all browsers and /* since we are using "stylish-code-chrome" meta key on all browsers and
US.o does not provide "advanced settings" on this url if browser is not Chrome, US.o does not provide "advanced settings" on this url if browser is not Chrome,
we need to fix this URL using "stylish-update-url" meta key we need to fix this URL using "stylish-update-url" meta key
*/ */
function getStyleURL() { function getStyleURL() {
const url = getMeta('stylish-code-chrome'); const url = getMeta('stylish-code-chrome');
// TODO: remove when USO is fixed // TODO: remove when USO is fixed
const directUrl = getMeta('stylish-update-url'); const directUrl = getMeta('stylish-update-url');
if (directUrl.includes('?') && !url.includes('?')) { if (directUrl.includes('?') && !url.includes('?')) {
/* get custom settings from the update url */ /* get custom settings from the update url */
return Object.assign(new URL(url), { return Object.assign(new URL(url), {
search: (new URL(directUrl)).search search: (new URL(directUrl)).search
}).href; }).href;
} }
return url; return url;
} }
function checkUpdatability([installedStyle]) { function checkUpdatability([installedStyle]) {
// TODO: remove the following statement when USO is fixed // TODO: remove the following statement when USO is fixed
document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', { document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', {
detail: installedStyle && installedStyle.updateUrl, detail: installedStyle && installedStyle.updateUrl,
})); }));
if (!installedStyle) { if (!installedStyle) {
sendEvent('styleCanBeInstalledChrome'); sendEvent('styleCanBeInstalledChrome');
return; return;
} }
const md5Url = getMeta('stylish-md5-url'); const md5Url = getMeta('stylish-md5-url');
if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) { if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
getResource(md5Url).then(md5 => { getResource(md5Url).then(md5 => {
reportUpdatable(md5 != installedStyle.originalMd5); reportUpdatable(md5 !== installedStyle.originalMd5);
}); });
} else { } else {
getResource(getStyleURL()).then(code => { getResource(getStyleURL()).then(code => {
reportUpdatable(code === null || reportUpdatable(code === null ||
!styleSectionsEqual(JSON.parse(code), installedStyle)); !styleSectionsEqual(JSON.parse(code), installedStyle));
}); });
} }
function reportUpdatable(isUpdatable) { function reportUpdatable(isUpdatable) {
sendEvent( sendEvent(
isUpdatable isUpdatable
? 'styleCanBeUpdatedChrome' ? 'styleCanBeUpdatedChrome'
: 'styleAlreadyInstalledChrome', : 'styleAlreadyInstalledChrome',
{ {
updateUrl: installedStyle.updateUrl updateUrl: installedStyle.updateUrl
} }
); );
} }
} }
function sendEvent(type, detail = null) { function sendEvent(type, detail = null) {
if (FIREFOX) { if (FIREFOX) {
type = type.replace('Chrome', ''); type = type.replace('Chrome', '');
} else if (OPERA || VIVALDI) { } else if (OPERA || VIVALDI) {
type = type.replace('Chrome', 'Opera'); type = type.replace('Chrome', 'Opera');
} }
detail = {detail}; detail = {detail};
if (typeof cloneInto != 'undefined') { if (typeof cloneInto !== 'undefined') {
// Firefox requires explicit cloning, however USO can't process our messages anyway // Firefox requires explicit cloning, however USO can't process our messages anyway
// because USO tries to use a global "event" variable deprecated in Firefox // because USO tries to use a global "event" variable deprecated in Firefox
detail = cloneInto(detail, document); // eslint-disable-line no-undef detail = cloneInto(detail, document); // eslint-disable-line no-undef
} }
onDOMready().then(() => { onDOMready().then(() => {
document.dispatchEvent(new CustomEvent(type, detail)); document.dispatchEvent(new CustomEvent(type, detail));
}); });
} }
function onInstallClicked() { function onInstallClicked() {
if (!orphanCheck || !orphanCheck()) { if (!orphanCheck || !orphanCheck()) {
return; return;
} }
getResource(getMeta('stylish-description')) getResource(getMeta('stylish-description'))
.then(name => saveStyleCode('styleInstall', name)) .then(name => saveStyleCode('styleInstall', name))
.then(() => getResource(getMeta('stylish-install-ping-url-chrome'))); .then(() => getResource(getMeta('stylish-install-ping-url-chrome')));
} }
function onUpdateClicked() { function onUpdateClicked() {
if (!orphanCheck || !orphanCheck()) { if (!orphanCheck || !orphanCheck()) {
return; return;
} }
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
method: 'getStyles', method: 'getStyles',
url: getMeta('stylish-id-url') || location.href, url: getMeta('stylish-id-url') || location.href,
}, ([style]) => { }, ([style]) => {
saveStyleCode('styleUpdate', style.name, {id: style.id}); saveStyleCode('styleUpdate', style.name, {id: style.id});
}); });
} }
function saveStyleCode(message, name, addProps) { function saveStyleCode(message, name, addProps) {
return new Promise(resolve => { return new Promise(resolve => {
if (!confirm(chrome.i18n.getMessage(message, [name]))) { if (!confirm(chrome.i18n.getMessage(message, [name]))) {
return; return;
} }
enableUpdateButton(false); enableUpdateButton(false);
getResource(getStyleURL()).then(code => { getResource(getStyleURL()).then(code => {
chrome.runtime.sendMessage( chrome.runtime.sendMessage(
Object.assign(JSON.parse(code), addProps, { Object.assign(JSON.parse(code), addProps, {
method: 'saveStyle', method: 'saveStyle',
reason: 'update', reason: 'update',
}), }),
style => { style => {
if (message == 'styleUpdate' && style.updateUrl.includes('?')) { if (message === 'styleUpdate' && style.updateUrl.includes('?')) {
enableUpdateButton(true); enableUpdateButton(true);
} else { } else {
sendEvent('styleInstalledChrome'); sendEvent('styleInstalledChrome');
} }
} }
); );
resolve(); resolve();
}); });
}); });
function enableUpdateButton(state) { function enableUpdateButton(state) {
const button = document.getElementById('update_style_button'); const button = document.getElementById('update_style_button');
if (button) { if (button) {
button.style.cssText = state ? '' : button.style.cssText = state ? '' :
'pointer-events: none !important; opacity: .25 !important;'; 'pointer-events: none !important; opacity: .25 !important;';
} }
} }
} }
function getMeta(name) { function getMeta(name) {
const e = document.querySelector(`link[rel="${name}"]`); const e = document.querySelector(`link[rel="${name}"]`);
return e ? e.getAttribute('href') : null; return e ? e.getAttribute('href') : null;
} }
function getResource(url) { function getResource(url) {
return new Promise(resolve => { return new Promise(resolve => {
if (url.startsWith('#')) { if (url.startsWith('#')) {
resolve(document.getElementById(url.slice(1)).textContent); resolve(document.getElementById(url.slice(1)).textContent);
} else { } else {
chrome.runtime.sendMessage({method: 'download', url}, resolve); chrome.runtime.sendMessage({method: 'download', url}, resolve);
} }
}); });
} }
function styleSectionsEqual({sections: a}, {sections: b}) { function styleSectionsEqual({sections: a}, {sections: b}) {
if (!a || !b) { if (!a || !b) {
return undefined; return undefined;
} }
if (a.length != b.length) { if (a.length !== b.length) {
return false; return false;
} }
const checkedInB = []; const checkedInB = [];
return a.every(sectionA => b.some(sectionB => { return a.every(sectionA => b.some(sectionB => {
if (!checkedInB.includes(sectionB) && propertiesEqual(sectionA, sectionB)) { if (!checkedInB.includes(sectionB) && propertiesEqual(sectionA, sectionB)) {
checkedInB.push(sectionB); checkedInB.push(sectionB);
return true; return true;
} }
})); }));
function propertiesEqual(secA, secB) { function propertiesEqual(secA, secB) {
for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) { for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) { if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
return false; return false;
} }
} }
return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a == b); return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a === b);
} }
function equalOrEmpty(a, b, telltale, comparator) { function equalOrEmpty(a, b, telltale, comparator) {
const typeA = a && typeof a[telltale] == 'function'; const typeA = a && typeof a[telltale] === 'function';
const typeB = b && typeof b[telltale] == 'function'; const typeB = b && typeof b[telltale] === 'function';
return ( return (
(a === null || a === undefined || (typeA && !a.length)) && (a === null || a === undefined || (typeA && !a.length)) &&
(b === null || b === undefined || (typeB && !b.length)) (b === null || b === undefined || (typeB && !b.length))
) || typeA && typeB && a.length == b.length && comparator(a, b); ) || typeA && typeB && a.length === b.length && comparator(a, b);
} }
function arrayMirrors(array1, array2) { function arrayMirrors(array1, array2) {
for (const el of array1) { for (const el of array1) {
if (array2.indexOf(el) < 0) { if (array2.indexOf(el) < 0) {
return false; return false;
} }
} }
for (const el of array2) { for (const el of array2) {
if (array1.indexOf(el) < 0) { if (array1.indexOf(el) < 0) {
return false; return false;
} }
} }
return true; return true;
} }
} }
function onDOMready() { function onDOMready() {
if (document.readyState != 'loading') { if (document.readyState !== 'loading') {
return Promise.resolve(); return Promise.resolve();
} }
return new Promise(resolve => { return new Promise(resolve => {
document.addEventListener('DOMContentLoaded', function _() { document.addEventListener('DOMContentLoaded', function _() {
document.removeEventListener('DOMContentLoaded', _); document.removeEventListener('DOMContentLoaded', _);
resolve(); resolve();
}); });
}); });
} }
function orphanCheck() { function orphanCheck() {
const port = chrome.runtime.connect(); const port = chrome.runtime.connect();
if (port) { if (port) {
port.disconnect(); port.disconnect();
return true; return true;
} }
// we're orphaned due to an extension update // we're orphaned due to an extension update
// we can detach event listeners // we can detach event listeners
document.removeEventListener('stylishUpdate', onUpdateClicked); document.removeEventListener('stylishUpdate', onUpdateClicked);
document.removeEventListener('stylishUpdateChrome', onUpdateClicked); document.removeEventListener('stylishUpdateChrome', onUpdateClicked);
document.removeEventListener('stylishUpdateOpera', onUpdateClicked); document.removeEventListener('stylishUpdateOpera', onUpdateClicked);
document.removeEventListener('stylishInstall', onInstallClicked); document.removeEventListener('stylishInstall', onInstallClicked);
document.removeEventListener('stylishInstallChrome', onInstallClicked); document.removeEventListener('stylishInstallChrome', onInstallClicked);
document.removeEventListener('stylishInstallOpera', onInstallClicked); document.removeEventListener('stylishInstallOpera', onInstallClicked);
// we can't detach chrome.runtime.onMessage because it's no longer connected internally // we can't detach chrome.runtime.onMessage because it's no longer connected internally
// we can destroy global functions in this context to free up memory // we can destroy global functions in this context to free up memory
[ [
'checkUpdatability', 'checkUpdatability',
'getMeta', 'getMeta',
'getResource', 'getResource',
'onDOMready', 'onDOMready',
'onInstallClicked', 'onInstallClicked',
'onUpdateClicked', 'onUpdateClicked',
'orphanCheck', 'orphanCheck',
'saveStyleCode', 'saveStyleCode',
'sendEvent', 'sendEvent',
'styleSectionsEqual', 'styleSectionsEqual',
].forEach(fn => (window[fn] = null)); ].forEach(fn => (window[fn] = null));
} }

977
edit.html
View File

@ -1,792 +1,213 @@
<html id="stylus"> <html id="stylus">
<head> <head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<script src="dom.js"></script> <script src="js/dom.js"></script>
<script src="messaging.js"></script> <script src="js/messaging.js"></script>
<script src="prefs.js"></script> <script src="js/prefs.js"></script>
<script src="localization.js"></script> <script src="js/localization.js"></script>
<script src="apply.js"></script> <script src="content/apply.js"></script>
<script src="edit.js"></script> <link rel="stylesheet" href="edit/edit.css">
<script src="edit/edit.js"></script>
<script src="codemirror/lib/codemirror.js"></script> <script src="vendor/codemirror/lib/codemirror.js"></script>
<link rel="stylesheet" href="codemirror/lib/codemirror.css"> <link rel="stylesheet" href="vendor/codemirror/lib/codemirror.css">
<script src="codemirror/mode/css/css.js"></script> <script src="vendor/codemirror/mode/css/css.js"></script>
<link rel="stylesheet" href="codemirror/addon/dialog/dialog.css"> <link rel="stylesheet" href="vendor/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" href="codemirror/addon/search/matchesonscrollbar.css"> <link rel="stylesheet" href="vendor/codemirror/addon/search/matchesonscrollbar.css">
<script src="codemirror/addon/scroll/annotatescrollbar.js"></script> <script src="vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="codemirror/addon/search/matchesonscrollbar.js"></script> <script src="vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="codemirror-overwrites/addon/search/match-highlighter.js"></script> <script src="vendor-overwrites/codemirror/addon/search/match-highlighter.js"></script>
<script src="codemirror/addon/dialog/dialog.js"></script> <script src="vendor/codemirror/addon/dialog/dialog.js"></script>
<script src="codemirror/addon/search/searchcursor.js"></script> <script src="vendor/codemirror/addon/search/searchcursor.js"></script>
<script src="codemirror/addon/search/search.js"></script> <script src="vendor/codemirror/addon/search/search.js"></script>
<script src="codemirror/addon/comment/comment.js"></script> <script src="vendor/codemirror/addon/comment/comment.js"></script>
<script src="codemirror/addon/selection/active-line.js"></script> <script src="vendor/codemirror/addon/selection/active-line.js"></script>
<link rel="stylesheet" href="codemirror/addon/fold/foldgutter.css" /> <link rel="stylesheet" href="vendor/codemirror/addon/fold/foldgutter.css" />
<script src="codemirror/addon/fold/foldcode.js"></script> <script src="vendor/codemirror/addon/fold/foldcode.js"></script>
<script src="codemirror/addon/fold/foldgutter.js"></script> <script src="vendor/codemirror/addon/fold/foldgutter.js"></script>
<script src="codemirror/addon/fold/brace-fold.js"></script> <script src="vendor/codemirror/addon/fold/brace-fold.js"></script>
<script src="codemirror/addon/fold/comment-fold.js"></script> <script src="vendor/codemirror/addon/fold/comment-fold.js"></script>
<script src="codemirror/addon/edit/matchbrackets.js"></script> <script src="vendor/codemirror/addon/edit/matchbrackets.js"></script>
<link rel="stylesheet" href="codemirror/addon/lint/lint.css" /> <link rel="stylesheet" href="vendor/codemirror/addon/lint/lint.css" />
<script src="csslint/csslint-worker.js"></script> <script src="vendor/csslint/csslint-worker.js"></script>
<script src="codemirror/addon/lint/lint.js"></script> <script src="vendor/codemirror/addon/lint/lint.js"></script>
<script src="codemirror-overwrites/addon/lint/css-lint.js"></script> <script src="vendor-overwrites/codemirror/addon/lint/css-lint.js"></script>
<link rel="stylesheet" href="codemirror/addon/hint/show-hint.css" /> <link rel="stylesheet" href="vendor/codemirror/addon/hint/show-hint.css" />
<script src="codemirror/addon/hint/show-hint.js"></script> <script src="vendor/codemirror/addon/hint/show-hint.js"></script>
<script src="codemirror/addon/hint/css-hint.js"></script> <script src="vendor/codemirror/addon/hint/css-hint.js"></script>
<script src="codemirror/keymap/sublime.js"></script> <script src="vendor/codemirror/keymap/sublime.js"></script>
<script src="codemirror/keymap/emacs.js"></script> <script src="vendor/codemirror/keymap/emacs.js"></script>
<script src="codemirror/keymap/vim.js"></script> <script src="vendor/codemirror/keymap/vim.js"></script>
<link id="cm-theme" rel="stylesheet">
<style type="text/css"> <template data-id="appliesTo">
<li>
<select name="applies-type" class="applies-type style-contributor">
<option value="url" i18n-text="appliesUrlOption"></option>
<option value="url-prefix" i18n-text="appliesUrlPrefixOption"></option>
<option value="domain" i18n-text="appliesDomainOption"></option>
<option value="regexp" i18n-text="appliesRegexpOption"></option>
</select>
<input name="applies-value" class="applies-value style-contributor">
<button class="remove-applies-to" i18n-text="appliesRemove"></button>
<button class="add-applies-to" i18n-text="appliesAdd"></button>
</li>
</template>
<template data-id="appliesToEverything">
<li class="applies-to-everything" i18n-html="appliesToEverything">
<button class="add-applies-to" i18n-text="appliesSpecify"></button>
</li>
</template>
<template data-id="section">
<div>
<label i18n-text="sectionCode"></label>
<textarea class="code"></textarea>
<br>
<div class="applies-to">
<label i18n-text="appliesLabel">
<svg class="svg-icon info applies-to-help"><use xlink:href="#svg-icon-help"/></svg>
</label>
<ul class="applies-to-list"></ul>
</div>
<button class="remove-section" i18n-text="sectionRemove"></button>
<button class="add-section" i18n-text="sectionAdd"></button>
<button class="beautify-section" i18n-text="styleBeautify"></button>
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
</div>
</template>
<template data-id="find">
<span i18n-text="search">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>
</span>
</template>
<template data-id="replace">
<span i18n-text="replace">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>
</span>
</template>
<template data-id="replaceAll">
<span i18n-text="replaceAll">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>
</span>
</template>
<template data-id="replaceWith">
<span i18n-text="replaceWith">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
</span>
</template>
<template data-id="replaceConfirm">
<span i18n-text="replace">?
<button i18n-text="confirmYes"></button>
<button i18n-text="confirmNo"></button>
<button i18n-text="confirmStop"></button>
</span>
</template>
<template data-id="jumpToLine">
<span i18n-text="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
</template>
<template data-id="regexpTestPartial">
<a target="_blank" href="https://github.com/stylish-userstyles/stylish/wiki/Applying-styles-to-specific-sites#advanced-matching-with-regular-expressions"><svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></a>
</template>
<template data-id="resizeGrip">
<div class="resize-grip" i18n-title="cm_resizeGripHint"></div>
</template>
</head>
body { <body id="stylus-edit">
margin: 0; <div id="header">
font: 12px arial,sans-serif; <h1 id="heading">&nbsp;</h1> <!-- nbsp allocates the actual height which prevents page shift -->
} <section id="basic-info">
/************ header ************/ <div id="basic-info-name">
#header { <input id="name" class="style-contributor" i18n-placeholder="styleMissingName" spellcheck="false">
width: 280px; <a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
height: 100vh; </div>
overflow: auto; <div id="basic-info-enabled">
position: fixed; <input type="checkbox" id="enabled" class="style-contributor">
top: 0; <label for="enabled" id="enabled-label" i18n-text="styleEnabledLabel"></label><!--
padding: 15px; --><svg id="toggle-style-help" class="svg-icon info">
border-right: 1px dashed #AAA; <use xlink:href="#svg-icon-help"/>
-webkit-box-shadow: 0 0 3rem -1.2rem black; </svg>
box-sizing: border-box; </div>
} </section>
#header h1 { <section id="actions">
margin-top: 0; <div>
} <button id="save-button" title="Ctrl-S" i18n-text="styleSaveLabel"></button>
#sections { <button id="beautify" i18n-text="styleBeautify"></button>
padding-left: 280px; <a href="manage.html"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a>
} </div>
#sections h2 { <div>
margin-top: 1rem; <h2 id="mozilla-format-heading" i18n-text="styleMozillaFormatHeading"><svg id="to-mozilla-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2>
margin-left: 1.7rem; <button id="from-mozilla" i18n-text="importLabel"></button>
} <button id="to-mozilla" i18n-text="exportLabel"></button>
.aligned { </div>
display: table-row; </section>
} <section id="options">
.aligned > *:not(svg) { <h2 id="options-heading" i18n-text="optionsHeading"></h2>
display: table-cell; <div class="option">
margin-top: 0.1rem; <input id="editor.lineWrapping" type="checkbox">
min-height: 1.4rem; <label id="lineWrapping-label" for="editor.lineWrapping" i18n-text="cm_lineWrapping"></label>
} </div>
input[type="checkbox"] { <div class="option">
margin-left: 0.1rem; <input id="editor.smartIndent" type="checkbox">
} <label id="smartIndent-label" for="editor.smartIndent" i18n-text="cm_smartIndent"></label>
/* basic info */ </div>
#basic-info { <div class="option">
margin-bottom: 1rem; <input id="editor.indentWithTabs" type="checkbox">
} <label id="indentWithTabs-label" for="editor.indentWithTabs" i18n-text="cm_indentWithTabs"></label>
#name { </div>
width: 100%; <div class="option">
} <input id="editor.autocompleteOnTyping" type="checkbox">
#basic-info-name { <label for="editor.autocompleteOnTyping" i18n-text="cm_autocompleteOnTyping"></label>
display: flex; </div>
align-items: center; <div class="option aligned">
} <label id="tabSize-label" for="editor.tabSize" i18n-text="cm_tabSize"></label>
#url { <input id="editor.tabSize" type="number" min="0">
margin-left: 0.25rem; </div>
} <div class="option aligned">
#url:not([href^="http"]) { <label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label>
display: none; <select id="editor.keyMap"></select>
} <svg id="keyMap-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
#save-button { </div>
opacity: .5; <div class="option aligned">
pointer-events: none; <label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label>
} <select id="editor.theme"></select>
.dirty #save-button { </div>
opacity: 1; <div class="option aligned">
pointer-events: all; <label id="highlight-label" for="editor.matchHighlight" i18n-text="cm_matchHighlight"></label>
} <select id="editor.matchHighlight">
.svg-icon { <option i18n-text="cm_matchHighlightToken" value="token">
cursor: pointer; <option i18n-text="cm_matchHighlightSelection" value="selection">
vertical-align: middle; <option i18n-text="genericDisabledLabel" value="">
transition: fill .5s; </select>
width: 16px; </div>
height: 16px; </section>
} <section id="lint"><h2 i18n-text="issues">: <span id="issue-count"></span><svg id="lint-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2><div></div></section>
.svg-icon:not(.dismiss) { </div>
margin-left: 0.2rem; <section id="sections">
} <h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><svg id="sections-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2>
h2 .svg-icon, label .svg-icon { </section>
margin-top: -1px; <div id="help-popup">
} <div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg></svg>
.svg-icon.info { <div class="contents"></div>
width: 14px; </div>
height: 16px;
}
.svg-icon:hover,
.svg-icon.info {
fill: #666;
}
.svg-icon,
.svg-icon.info:hover {
fill: #000;
}
#enabled {
margin-left: 0;
vertical-align: middle;
}
#enabled-label {
vertical-align: middle;
}
/* actions */
#actions > * {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
/* options */
#options [type="number"] {
max-width: 2.5rem;
text-align: right;
}
#options .option > * {
padding-right: 0.25rem;
}
/************ content ***********/
#sections > div {
margin: 0.7rem;
padding: 1rem;
}
#sections > div:not(:first-of-type) {
border-top: 2px solid black;
}
#sections > div:only-of-type .remove-section {
display: none;
}
#sections > div > button:not(:first-of-type) {
margin-left: 0.2rem;
}
.dirty > label::before {
content: "*";
font-weight: bold;
}
#sections {
counter-reset: codebox;
}
#sections > div > label::after {
counter-increment: codebox;
content: counter(codebox);
margin-left: 0.25rem;
}
/* code */
.CodeMirror-hint:hover {
color: white;
background: #08f;
}
.code {
height: 10rem;
width: 40rem;
}
.CodeMirror {
border: solid #CCC 1px;
}
.CodeMirror-scroll {
height: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 6px; /* resize-grip height */
}
.CodeMirror-lint-mark-warning {
background: none;
}
.CodeMirror-vscrollbar {
margin-bottom: 7px; /* make space for resize-grip */
}
.CodeMirror-hscrollbar {
bottom: 7px; /* make space for resize-grip */
}
.CodeMirror-scrollbar-filler {
bottom: 7px; /* make space for resize-grip */
}
.CodeMirror-dialog {
-webkit-animation: highlight 3s ease-out;
}
.CodeMirror-focused {
outline: -webkit-focus-ring-color auto 5px;
outline-offset: -2px;
}
.CodeMirror-search-field {
width: 10em;
}
.CodeMirror-jump-field {
width: 5em;
}
.CodeMirror-search-hint {
color: #888;
}
body[data-match-highlight="token"] .cm-matchhighlight-approved .cm-matchhighlight,
body[data-match-highlight="token"] .CodeMirror-selection-highlight-scrollbar {
animation: fadein-match-highlighter 1s cubic-bezier(.97,.01,.42,.98);
animation-fill-mode: both;
}
body[data-match-highlight="selection"] .cm-matchhighlight-approved .cm-matchhighlight,
body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar {
background-color: rgba(1, 151, 193, 0.1);
}
@-webkit-keyframes highlight {
from {
background-color: #ff9;
}
to {
background-color: none;
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadein-match-highlighter {
from { background-color: transparent; }
to { background-color: rgba(1, 151, 193, 0.1); }
}
.resize-grip {
position: absolute;
display: block;
height: 6px;
content: "";
left: 0;
right: 0;
bottom: 0;
z-index: 9;
cursor: n-resize;
background-color: inherit;
border-top-width: 1px;
border-top-style: solid;
border-top-color: inherit;
}
.resize-grip:after {
content: "";
bottom: 2px;
left: 0;
right: 0;
margin: 0 8px;
display: block;
position: absolute;
border-top-width: 2px;
border-top-style: dotted;
border-top-color: inherit;
}
/* applies-to */
.applies-to {
display: flex;
}
.applies-to label {
flex: auto;
margin-top: 0.2rem;
}
.applies-to ul {
flex: auto;
flex-grow: 99;
margin: 0;
padding: 0;
}
.applies-to li {
display: flex;
list-style-type: none;
align-items: center;
margin-bottom: 0.35rem;
}
.applies-to li > *:not(button) {
flex: auto;
min-height: 1.4rem;
margin-left: 0.35rem;
}
.applies-to li .add-applies-to {
visibility: hidden;
text-align: left;
}
.applies-to li:last-child .add-applies-to {
visibility: visible
}
.applies-to li .add-applies-to:first-child {
margin-left: 1rem;
}
.applies-to li .applies-value {
flex-grow: 99;
padding-left: 0.2rem;
}
.applies-to img {
vertical-align: bottom;
}
.test-regexp {
display: none;
}
.has-regexp .test-regexp {
display: inline-block;
}
.regexp-report summary, .regexp-report div {
cursor: pointer;
outline: none;
}
.regexp-report mark {
background-color: rgba(255, 255, 0, .5);
}
.regexp-report details {
margin-left: 1rem;
}
.regexp-report details:not(:last-child) {
margin-bottom: 1rem;
}
.regexp-report summary {
font-weight: bold;
margin-left: -1rem;
margin-bottom: .5rem;
outline: none;
cursor: default;
}
.regexp-report details[data-type="full"] {
color: darkgreen;
}
.regexp-report details[data-type="partial"] {
color: darkgray;
}
.regexp-report details[data-type="invalid"] {
color: maroon;
}
.regexp-report details details {
margin-left: 2rem;
margin-top: .5rem;
}
.regexp-report .svg-icon {
position: absolute;
margin-top: -1px;
}
.regexp-report details div:hover {
text-decoration: underline;
text-decoration-skip: ink;
}
.regexp-report details div img {
width: 16px;
max-height: 16px;
position: absolute;
margin-left: -20px;
margin-top: -1px;
animation: fadein 1s cubic-bezier(.03, .67, .08, .94);
animation-fill-mode: both;
}
/************ help popup ************/
#help-popup {
top: 3rem;
right: 3rem;
max-width: 50vw;
position: fixed;
display: none;
background-color: white;
box-shadow: 3px 3px 30px rgba(0, 0, 0, 0.5);
padding: 0.5rem;
z-index: 99;
}
#help-popup.big {
max-width: 100%;
left: 3rem;
}
#help-popup.big .CodeMirror {
min-height: 2rem;
height: 70vh;
}
#help-popup .title {
font-weight: bold;
background-color: rgba(0,0,0,0.05);
margin: -0.5rem -0.5rem 0.5rem;
padding: .5rem 32px .5rem .5rem;
}
#help-popup .contents {
max-height: calc(100vh - 8rem);
overflow-y: auto;
}
#help-popup .dismiss {
position: absolute;
right: 4px;
top: .5em;
}
.keymap-list { <svg xmlns="http://www.w3.org/2000/svg" style="display: none">
font-size: 85%; <symbol id="svg-icon-external-link" height="16" width="16" viewBox="0 0 8 8">
line-height: 1.0; <path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
border-spacing: 0; </symbol>
word-break: break-all; <symbol id="svg-icon-help" height="16" width="14" viewBox="0 0 14 16" i18n-alt="helpAlt">
} <path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
.keymap-list input { </symbol>
width: 100%; <symbol id="svg-icon-close" height="16" width="12" viewBox="0 0 12 16">
} <path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path>
.keymap-list tr:nth-child(odd) { </symbol>
background-color: rgba(0, 0, 0, 0.07); </svg>
}
.keymap-list td:first-child {
white-space: nowrap;
font-family: monospace;
padding-right: 0.5rem;
}
#help-popup button[name^="import"] { </body>
line-height: 1.5rem;
padding: 0 0.5rem;
margin: 0.5rem 0 0 0.5rem;
pointer-events: none;
opacity: 0.5;
float: right;
}
#help-popup.ready button[name^="import"] {
pointer-events: all;
opacity: 1.0;
}
/************ lint ************/
#lint {
display: none;
}
#lint > div {
overflow-y: auto;
}
#lint table {
font-size: 100%;
border-spacing: 0;
margin-bottom: 1rem;
line-height: 1.0;
}
#lint table:last-child {
margin-bottom: 0;
}
#lint caption {
text-align: left;
font-weight: bold;
}
#lint tbody {
font-size: 85%;
cursor: pointer;
}
#lint tr:hover {
background-color: rgba(0, 0, 0, 0.1);
}
#lint td[role="severity"] {
font-size: 0;
width: 16px;
padding-right: 0.25rem;
}
#lint td[role="line"], #lint td[role="sep"] {
text-align: right;
padding-right: 0;
}
#lint td[role="col"] {
text-align: left;
padding-right: 0.25rem;
}
#lint td[role="message"] {
text-align: left;
}
/************ CSS beautifier ************/
.beautify-options {
white-space: nowrap;
font-family: monospace;
}
.beautify-options div {
float: left;
}
.beautify-options div[newline="true"] + div {
clear: left;
}
.beautify-options div[newline="true"] + div span[indent] {
padding-left: 2rem;
}
.beautify-options:after {
clear: both;
display: block;
content: " ";
height: 1rem;
}
.beautify-options span {
font-weight: bold;
}
.beautify-options select {
border: none;
background-color: rgba(0, 0, 0, 0.05);
}
/************ reponsive layouts ************/
@media(max-width:737px) {
#header {
width: auto;
height: auto;
position: inherit;
border-right: none;
border-bottom: 1px dashed #AAA;
}
#header section:not(:last-child) {
margin-bottom: 0.4rem;
}
#header input[type="checkbox"] {
vertical-align: middle;
}
h2 {
display: none;
}
#basic-info {
display: flex;
align-items: baseline;
}
#basic-info > * {
flex: auto;
}
#basic-info > *:first-child {
flex-grow: 99;
display: flex;
}
#basic-info > *:not(:last-child) {
margin-right: 0.8rem;
}
#basic-info #name {
width: auto;
flex-grow: 99;
}
#actions {
margin-top: 1rem;
}
#actions > * {
display: inline-block;
}
#options {
-webkit-column-count: 2;
}
#options .aligned > *:not(svg) {
margin: 1px 0 0 0; /* workaround the flowing-padding column bug in webkit */
padding-right: 0.4rem;
vertical-align: baseline;
min-height: 1.4rem;
}
.option {
-webkit-column-break-inside: avoid;
}
.option label {
line-height: 1.25rem;
margin: 0;
}
#options [type="number"] {
text-align: left; /* workaround the column flow bug in webkit */
padding-left: 0.2rem;
}
#options #tabSize-label {
position: relative;
top: 0.2rem;
}
#lint h2 {
display: block;
cursor: default;
margin-bottom: 0;
}
#lint > div {
max-height: 0;
}
#lint.collapsed > div {
display: none;
}
#lint:hover > div {
margin-top: 1em;
max-height: 30vh;
}
#sections {
padding-left: 0;
}
#sections > div {
padding: 0;
}
#sections > *:not(h2) {
padding-left: 0.4rem;
}
.applies-type {
width: 30%;
}
}
@media(max-width:500px) {
#options {
-webkit-column-count: 1;
}
#options #tabSize-label {
position: static;
}
}
</style>
<link id="cm-theme" rel="stylesheet">
<template data-id="appliesTo">
<li>
<select name="applies-type" class="applies-type style-contributor">
<option value="url" i18n-text="appliesUrlOption"></option>
<option value="url-prefix" i18n-text="appliesUrlPrefixOption"></option>
<option value="domain" i18n-text="appliesDomainOption"></option>
<option value="regexp" i18n-text="appliesRegexpOption"></option>
</select>
<input name="applies-value" class="applies-value style-contributor">
<button class="remove-applies-to" i18n-text="appliesRemove"></button>
<button class="add-applies-to" i18n-text="appliesAdd"></button>
</li>
</template>
<template data-id="appliesToEverything">
<li class="applies-to-everything" i18n-html="appliesToEverything">
<button class="add-applies-to" i18n-text="appliesSpecify"></button>
</li>
</template>
<template data-id="section">
<div>
<label i18n-text="sectionCode"></label>
<textarea class="code"></textarea>
<br>
<div class="applies-to">
<label i18n-text="appliesLabel">
<svg class="svg-icon info applies-to-help"><use xlink:href="#svg-icon-help"/></svg>
</label>
<ul class="applies-to-list"></ul>
</div>
<button class="remove-section" i18n-text="sectionRemove"></button>
<button class="add-section" i18n-text="sectionAdd"></button>
<button class="beautify-section" i18n-text="styleBeautify"></button>
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
</div>
</template>
<template data-id="find">
<span i18n-text="search">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>
</span>
</template>
<template data-id="replace">
<span i18n-text="replace">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>
</span>
</template>
<template data-id="replaceAll">
<span i18n-text="replaceAll">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>
</span>
</template>
<template data-id="replaceWith">
<span i18n-text="replaceWith">: <input type="text" class="CodeMirror-search-field" spellcheck="false">
</span>
</template>
<template data-id="replaceConfirm">
<span i18n-text="replace">?
<button i18n-text="confirmYes"></button>
<button i18n-text="confirmNo"></button>
<button i18n-text="confirmStop"></button>
</span>
</template>
<template data-id="jumpToLine">
<span i18n-text="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
</template>
<template data-id="regexpTestPartial">
<a target="_blank" href="https://github.com/stylish-userstyles/stylish/wiki/Applying-styles-to-specific-sites#advanced-matching-with-regular-expressions"><svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></a>
</template>
<template data-id="resizeGrip">
<div class="resize-grip" i18n-title="cm_resizeGripHint"></div>
</template>
</head>
<body id="stylus-edit">
<div id="header">
<h1 id="heading">&nbsp;</h1> <!-- nbsp allocates the actual height which prevents page shift -->
<section id="basic-info">
<div id="basic-info-name">
<input id="name" class="style-contributor" i18n-placeholder="styleMissingName" spellcheck="false">
<a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
</div>
<div id="basic-info-enabled">
<input type="checkbox" id="enabled" class="style-contributor">
<label for="enabled" id="enabled-label" i18n-text="styleEnabledLabel"></label><!--
--><svg id="toggle-style-help" class="svg-icon info">
<use xlink:href="#svg-icon-help"/>
</svg>
</div>
</section>
<section id="actions">
<div>
<button id="save-button" title="Ctrl-S" i18n-text="styleSaveLabel"></button>
<button id="beautify" i18n-text="styleBeautify"></button>
<a href="manage.html"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a>
</div>
<div>
<h2 id="mozilla-format-heading" i18n-text="styleMozillaFormatHeading"><svg id="to-mozilla-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2>
<button id="from-mozilla" i18n-text="importLabel"></button>
<button id="to-mozilla" i18n-text="exportLabel"></button>
</div>
</section>
<section id="options">
<h2 id="options-heading" i18n-text="optionsHeading"></h2>
<div class="option">
<input id="editor.lineWrapping" type="checkbox">
<label id="lineWrapping-label" for="editor.lineWrapping" i18n-text="cm_lineWrapping"></label>
</div>
<div class="option">
<input id="editor.smartIndent" type="checkbox">
<label id="smartIndent-label" for="editor.smartIndent" i18n-text="cm_smartIndent"></label>
</div>
<div class="option">
<input id="editor.indentWithTabs" type="checkbox">
<label id="indentWithTabs-label" for="editor.indentWithTabs" i18n-text="cm_indentWithTabs"></label>
</div>
<div class="option">
<input id="editor.autocompleteOnTyping" type="checkbox">
<label for="editor.autocompleteOnTyping" i18n-text="cm_autocompleteOnTyping"></label>
</div>
<div class="option aligned">
<label id="tabSize-label" for="editor.tabSize" i18n-text="cm_tabSize"></label>
<input id="editor.tabSize" type="number" min="0">
</div>
<div class="option aligned">
<label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label>
<select id="editor.keyMap"></select>
<svg id="keyMap-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</div>
<div class="option aligned">
<label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label>
<select id="editor.theme"></select>
</div>
<div class="option aligned">
<label id="highlight-label" for="editor.matchHighlight" i18n-text="cm_matchHighlight"></label>
<select id="editor.matchHighlight">
<option i18n-text="cm_matchHighlightToken" value="token">
<option i18n-text="cm_matchHighlightSelection" value="selection">
<option i18n-text="genericDisabledLabel" value="">
</select>
</div>
</section>
<section id="lint"><h2 i18n-text="issues">: <span id="issue-count"></span><svg id="lint-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2><div></div></section>
</div>
<section id="sections">
<h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><svg id="sections-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2>
</section>
<div id="help-popup">
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg></svg>
<div class="contents"></div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="svg-icon-external-link" height="16" width="16" viewBox="0 0 8 8">
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
</symbol>
<symbol id="svg-icon-help" height="16" width="14" viewBox="0 0 14 16" i18n-alt="helpAlt">
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
</symbol>
<symbol id="svg-icon-close" height="16" width="12" viewBox="0 0 12 16">
<path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path>
</symbol>
</svg>
</body>
</html> </html>

2037
edit.js

File diff suppressed because it is too large Load Diff

576
edit/edit.css Normal file
View File

@ -0,0 +1,576 @@
body {
margin: 0;
font: 12px arial,sans-serif;
}
/************ header ************/
#header {
width: 280px;
height: 100vh;
overflow: auto;
position: fixed;
top: 0;
padding: 15px;
border-right: 1px dashed #AAA;
-webkit-box-shadow: 0 0 3rem -1.2rem black;
box-sizing: border-box;
}
#header h1 {
margin-top: 0;
}
#sections {
padding-left: 280px;
}
#sections h2 {
margin-top: 1rem;
margin-left: 1.7rem;
}
.aligned {
display: table-row;
}
.aligned > *:not(svg) {
display: table-cell;
margin-top: 0.1rem;
min-height: 1.4rem;
}
input[type="checkbox"] {
margin-left: 0.1rem;
}
/* basic info */
#basic-info {
margin-bottom: 1rem;
}
#name {
width: 100%;
}
#basic-info-name {
display: flex;
align-items: center;
}
#url {
margin-left: 0.25rem;
}
#url:not([href^="http"]) {
display: none;
}
#save-button {
opacity: .5;
pointer-events: none;
}
.dirty #save-button {
opacity: 1;
pointer-events: all;
}
.svg-icon {
cursor: pointer;
vertical-align: middle;
transition: fill .5s;
width: 16px;
height: 16px;
}
.svg-icon:not(.dismiss) {
margin-left: 0.2rem;
}
h2 .svg-icon, label .svg-icon {
margin-top: -1px;
}
.svg-icon.info {
width: 14px;
height: 16px;
}
.svg-icon:hover,
.svg-icon.info {
fill: #666;
}
.svg-icon,
.svg-icon.info:hover {
fill: #000;
}
#enabled {
margin-left: 0;
vertical-align: middle;
}
#enabled-label {
vertical-align: middle;
}
/* actions */
#actions > * {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
/* options */
#options [type="number"] {
max-width: 2.5rem;
text-align: right;
}
#options .option > * {
padding-right: 0.25rem;
}
/************ content ***********/
#sections > div {
margin: 0.7rem;
padding: 1rem;
}
#sections > div:not(:first-of-type) {
border-top: 2px solid black;
}
#sections > div:only-of-type .remove-section {
display: none;
}
#sections > div > button:not(:first-of-type) {
margin-left: 0.2rem;
}
.dirty > label::before {
content: "*";
font-weight: bold;
}
#sections {
counter-reset: codebox;
}
#sections > div > label::after {
counter-increment: codebox;
content: counter(codebox);
margin-left: 0.25rem;
}
/* code */
.CodeMirror-hint:hover {
color: white;
background: #08f;
}
.code {
height: 10rem;
width: 40rem;
}
.CodeMirror {
border: solid #CCC 1px;
}
.CodeMirror-scroll {
height: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 6px; /* resize-grip height */
}
.CodeMirror-lint-mark-warning {
background: none;
}
.CodeMirror-vscrollbar {
margin-bottom: 7px; /* make space for resize-grip */
}
.CodeMirror-hscrollbar {
bottom: 7px; /* make space for resize-grip */
}
.CodeMirror-scrollbar-filler {
bottom: 7px; /* make space for resize-grip */
}
.CodeMirror-dialog {
-webkit-animation: highlight 3s ease-out;
}
.CodeMirror-focused {
outline: -webkit-focus-ring-color auto 5px;
outline-offset: -2px;
}
.CodeMirror-search-field {
width: 10em;
}
.CodeMirror-jump-field {
width: 5em;
}
.CodeMirror-search-hint {
color: #888;
}
body[data-match-highlight="token"] .cm-matchhighlight-approved .cm-matchhighlight,
body[data-match-highlight="token"] .CodeMirror-selection-highlight-scrollbar {
animation: fadein-match-highlighter 1s cubic-bezier(.97,.01,.42,.98);
animation-fill-mode: both;
}
body[data-match-highlight="selection"] .cm-matchhighlight-approved .cm-matchhighlight,
body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar {
background-color: rgba(1, 151, 193, 0.1);
}
@-webkit-keyframes highlight {
from {
background-color: #ff9;
}
to {
background-color: none;
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadein-match-highlighter {
from { background-color: transparent; }
to { background-color: rgba(1, 151, 193, 0.1); }
}
.resize-grip {
position: absolute;
display: block;
height: 6px;
content: "";
left: 0;
right: 0;
bottom: 0;
z-index: 9;
cursor: n-resize;
background-color: inherit;
border-top-width: 1px;
border-top-style: solid;
border-top-color: inherit;
}
.resize-grip:after {
content: "";
bottom: 2px;
left: 0;
right: 0;
margin: 0 8px;
display: block;
position: absolute;
border-top-width: 2px;
border-top-style: dotted;
border-top-color: inherit;
}
/* applies-to */
.applies-to {
display: flex;
}
.applies-to label {
flex: auto;
margin-top: 0.2rem;
}
.applies-to ul {
flex: auto;
flex-grow: 99;
margin: 0;
padding: 0;
}
.applies-to li {
display: flex;
list-style-type: none;
align-items: center;
margin-bottom: 0.35rem;
}
.applies-to li > *:not(button) {
flex: auto;
min-height: 1.4rem;
margin-left: 0.35rem;
}
.applies-to li .add-applies-to {
visibility: hidden;
text-align: left;
}
.applies-to li:last-child .add-applies-to {
visibility: visible
}
.applies-to li .add-applies-to:first-child {
margin-left: 1rem;
}
.applies-to li .applies-value {
flex-grow: 99;
padding-left: 0.2rem;
}
.applies-to img {
vertical-align: bottom;
}
.test-regexp {
display: none;
}
.has-regexp .test-regexp {
display: inline-block;
}
.regexp-report summary, .regexp-report div {
cursor: pointer;
outline: none;
}
.regexp-report mark {
background-color: rgba(255, 255, 0, .5);
}
.regexp-report details {
margin-left: 1rem;
}
.regexp-report details:not(:last-child) {
margin-bottom: 1rem;
}
.regexp-report summary {
font-weight: bold;
margin-left: -1rem;
margin-bottom: .5rem;
outline: none;
cursor: default;
}
.regexp-report details[data-type="full"] {
color: darkgreen;
}
.regexp-report details[data-type="partial"] {
color: darkgray;
}
.regexp-report details[data-type="invalid"] {
color: maroon;
}
.regexp-report details details {
margin-left: 2rem;
margin-top: .5rem;
}
.regexp-report .svg-icon {
position: absolute;
margin-top: -1px;
}
.regexp-report details div:hover {
text-decoration: underline;
text-decoration-skip: ink;
}
.regexp-report details div img {
width: 16px;
max-height: 16px;
position: absolute;
margin-left: -20px;
margin-top: -1px;
animation: fadein 1s cubic-bezier(.03, .67, .08, .94);
animation-fill-mode: both;
}
/************ help popup ************/
#help-popup {
top: 3rem;
right: 3rem;
max-width: 50vw;
position: fixed;
display: none;
background-color: white;
box-shadow: 3px 3px 30px rgba(0, 0, 0, 0.5);
padding: 0.5rem;
z-index: 99;
}
#help-popup.big {
max-width: 100%;
left: 3rem;
}
#help-popup.big .CodeMirror {
min-height: 2rem;
height: 70vh;
}
#help-popup .title {
font-weight: bold;
background-color: rgba(0,0,0,0.05);
margin: -0.5rem -0.5rem 0.5rem;
padding: .5rem 32px .5rem .5rem;
}
#help-popup .contents {
max-height: calc(100vh - 8rem);
overflow-y: auto;
}
#help-popup .dismiss {
position: absolute;
right: 4px;
top: .5em;
}
.keymap-list {
font-size: 85%;
line-height: 1.0;
border-spacing: 0;
word-break: break-all;
}
.keymap-list input {
width: 100%;
}
.keymap-list tr:nth-child(odd) {
background-color: rgba(0, 0, 0, 0.07);
}
.keymap-list td:first-child {
white-space: nowrap;
font-family: monospace;
padding-right: 0.5rem;
}
#help-popup button[name^="import"] {
line-height: 1.5rem;
padding: 0 0.5rem;
margin: 0.5rem 0 0 0.5rem;
pointer-events: none;
opacity: 0.5;
float: right;
}
#help-popup.ready button[name^="import"] {
pointer-events: all;
opacity: 1.0;
}
/************ lint ************/
#lint {
display: none;
}
#lint > div {
overflow-y: auto;
}
#lint table {
font-size: 100%;
border-spacing: 0;
margin-bottom: 1rem;
line-height: 1.0;
}
#lint table:last-child {
margin-bottom: 0;
}
#lint caption {
text-align: left;
font-weight: bold;
}
#lint tbody {
font-size: 85%;
cursor: pointer;
}
#lint tr:hover {
background-color: rgba(0, 0, 0, 0.1);
}
#lint td[role="severity"] {
font-size: 0;
width: 16px;
padding-right: 0.25rem;
}
#lint td[role="line"], #lint td[role="sep"] {
text-align: right;
padding-right: 0;
}
#lint td[role="col"] {
text-align: left;
padding-right: 0.25rem;
}
#lint td[role="message"] {
text-align: left;
}
/************ CSS beautifier ************/
.beautify-options {
white-space: nowrap;
font-family: monospace;
}
.beautify-options div {
float: left;
}
.beautify-options div[newline="true"] + div {
clear: left;
}
.beautify-options div[newline="true"] + div span[indent] {
padding-left: 2rem;
}
.beautify-options:after {
clear: both;
display: block;
content: " ";
height: 1rem;
}
.beautify-options span {
font-weight: bold;
}
.beautify-options select {
border: none;
background-color: rgba(0, 0, 0, 0.05);
}
/************ reponsive layouts ************/
@media(max-width:737px) {
#header {
width: auto;
height: auto;
position: inherit;
border-right: none;
border-bottom: 1px dashed #AAA;
}
#header section:not(:last-child) {
margin-bottom: 0.4rem;
}
#header input[type="checkbox"] {
vertical-align: middle;
}
h2 {
display: none;
}
#basic-info {
display: flex;
align-items: baseline;
}
#basic-info > * {
flex: auto;
}
#basic-info > *:first-child {
flex-grow: 99;
display: flex;
}
#basic-info > *:not(:last-child) {
margin-right: 0.8rem;
}
#basic-info #name {
width: auto;
flex-grow: 99;
}
#actions {
margin-top: 1rem;
}
#actions > * {
display: inline-block;
}
#options {
-webkit-column-count: 2;
}
#options .aligned > *:not(svg) {
margin: 1px 0 0 0; /* workaround the flowing-padding column bug in webkit */
padding-right: 0.4rem;
vertical-align: baseline;
min-height: 1.4rem;
}
.option {
-webkit-column-break-inside: avoid;
}
.option label {
line-height: 1.25rem;
margin: 0;
}
#options [type="number"] {
text-align: left; /* workaround the column flow bug in webkit */
padding-left: 0.2rem;
}
#options #tabSize-label {
position: relative;
top: 0.2rem;
}
#lint h2 {
display: block;
cursor: default;
margin-bottom: 0;
}
#lint > div {
max-height: 0;
}
#lint.collapsed > div {
display: none;
}
#lint:hover > div {
margin-top: 1em;
max-height: 30vh;
}
#sections {
padding-left: 0;
}
#sections > div {
padding: 0;
}
#sections > *:not(h2) {
padding-left: 0.4rem;
}
.applies-type {
width: 30%;
}
}
@media(max-width:500px) {
#options {
-webkit-column-count: 1;
}
#options #tabSize-label {
position: static;
}
}

2072
edit/edit.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ for (const type of [NodeList, NamedNodeMap, HTMLCollection, HTMLAllCollection])
} }
// add favicon in Firefox // add favicon in Firefox
// eslint-disable-next-line no-unused-expressions
navigator.userAgent.includes('Firefox') && setTimeout(() => { navigator.userAgent.includes('Firefox') && setTimeout(() => {
const iconset = ['', 'light/'][prefs.get('iconset')] || ''; const iconset = ['', 'light/'][prefs.get('iconset')] || '';
for (const size of [38, 32, 19, 16]) { for (const size of [38, 32, 19, 16]) {
@ -26,7 +27,7 @@ navigator.userAgent.includes('Firefox') && setTimeout(() => {
function onDOMready() { function onDOMready() {
if (document.readyState != 'loading') { if (document.readyState !== 'loading') {
return Promise.resolve(); return Promise.resolve();
} }
return new Promise(resolve => { return new Promise(resolve => {
@ -78,9 +79,9 @@ function enforceInputRange(element) {
const max = Number(element.max); const max = Number(element.max);
const doNotify = () => element.dispatchEvent(new Event('change', {bubbles: true})); const doNotify = () => element.dispatchEvent(new Event('change', {bubbles: true}));
const onChange = ({type}) => { const onChange = ({type}) => {
if (type == 'input' && element.checkValidity()) { if (type === 'input' && element.checkValidity()) {
doNotify(); doNotify();
} else if (type == 'change' && !element.checkValidity()) { } else if (type === 'change' && !element.checkValidity()) {
element.value = Math.max(min, Math.min(max, Number(element.value))); element.value = Math.max(min, Math.min(max, Number(element.value)));
doNotify(); doNotify();
} }
@ -112,7 +113,7 @@ function $element(opt) {
? opt.tag.split('#') ? opt.tag.split('#')
: [null, opt.tag]; : [null, opt.tag];
const element = ns const element = ns
? document.createElementNS(ns == 'SVG' || ns == 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag) ? document.createElementNS(ns === 'SVG' || ns === 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag)
: document.createElement(tag || 'div'); : document.createElement(tag || 'div');
(opt.appendChild instanceof Array ? opt.appendChild : [opt.appendChild]) (opt.appendChild instanceof Array ? opt.appendChild : [opt.appendChild])
.forEach(child => child && element.appendChild(child)); .forEach(child => child && element.appendChild(child));

View File

@ -7,7 +7,7 @@ tDocLoader();
function t(key, params) { function t(key, params) {
const cache = !params && t.cache[key]; const cache = !params && t.cache[key];
const s = cache || chrome.i18n.getMessage(key, params); const s = cache || chrome.i18n.getMessage(key, params);
if (s == '') { if (s === '') {
throw `Missing string "${key}"`; throw `Missing string "${key}"`;
} }
if (!params && !cache) { if (!params && !cache) {
@ -20,7 +20,7 @@ function t(key, params) {
function tE(id, key, attr, esc) { function tE(id, key, attr, esc) {
if (attr) { if (attr) {
document.getElementById(id).setAttribute(attr, t(key)); document.getElementById(id).setAttribute(attr, t(key));
} else if (typeof esc == 'undefined' || esc) { } else if (typeof esc === 'undefined' || esc) {
document.getElementById(id).appendChild(document.createTextNode(t(key))); document.getElementById(id).appendChild(document.createTextNode(t(key)));
} else { } else {
document.getElementById(id).innerHTML = t(key); document.getElementById(id).innerHTML = t(key);
@ -43,10 +43,10 @@ function tNodeList(nodes) {
for (let n = nodes.length; --n >= 0;) { for (let n = nodes.length; --n >= 0;) {
const node = nodes[n]; const node = nodes[n];
// skip non-ELEMENT_NODE // skip non-ELEMENT_NODE
if (node.nodeType != 1) { if (node.nodeType !== 1) {
continue; continue;
} }
if (node.localName == 'template') { if (node.localName === 'template') {
const elements = node.content.querySelectorAll('*'); const elements = node.content.querySelectorAll('*');
tNodeList(elements); tNodeList(elements);
template[node.dataset.id] = elements[0]; template[node.dataset.id] = elements[0];
@ -94,7 +94,7 @@ function tDocLoader() {
// reset L10N cache on UI language change // reset L10N cache on UI language change
const UIlang = chrome.i18n.getUILanguage(); const UIlang = chrome.i18n.getUILanguage();
if (t.cache.browserUIlanguage != UIlang) { if (t.cache.browserUIlanguage !== UIlang) {
t.cache = {browserUIlanguage: UIlang}; t.cache = {browserUIlanguage: UIlang};
localStorage.L10N = JSON.stringify(t.cache); localStorage.L10N = JSON.stringify(t.cache);
} }
@ -114,7 +114,7 @@ function tDocLoader() {
const onLoad = () => { const onLoad = () => {
tDocLoader.stop(); tDocLoader.stop();
process(observer.takeRecords()); process(observer.takeRecords());
if (cacheLength != Object.keys(t.cache).length) { if (cacheLength !== Object.keys(t.cache).length) {
localStorage.L10N = JSON.stringify(t.cache); localStorage.L10N = JSON.stringify(t.cache);
} }
}; };

View File

@ -1,371 +1,371 @@
/* global BG: true, onRuntimeMessage, applyOnMessage, handleUpdate, handleDelete */ /* global BG: true, onRuntimeMessage, applyOnMessage, handleUpdate, handleDelete */
'use strict'; 'use strict';
// keep message channel open for sendResponse in chrome.runtime.onMessage listener // keep message channel open for sendResponse in chrome.runtime.onMessage listener
const KEEP_CHANNEL_OPEN = true; const KEEP_CHANNEL_OPEN = true;
const FIREFOX = /Firefox/.test(navigator.userAgent); const FIREFOX = /Firefox/.test(navigator.userAgent);
const OPERA = /OPR/.test(navigator.userAgent); const OPERA = /OPR/.test(navigator.userAgent);
const URLS = { const URLS = {
ownOrigin: chrome.runtime.getURL(''), ownOrigin: chrome.runtime.getURL(''),
optionsUI: [ optionsUI: [
chrome.runtime.getURL('options/index.html'), chrome.runtime.getURL('options.html'),
'chrome://extensions/?options=' + chrome.runtime.id, 'chrome://extensions/?options=' + chrome.runtime.id,
], ],
configureCommands: configureCommands:
OPERA ? 'opera://settings/configureCommands' OPERA ? 'opera://settings/configureCommands'
: 'chrome://extensions/configureCommands', : 'chrome://extensions/configureCommands',
// CWS cannot be scripted in chromium, see ChromeExtensionsClient::IsScriptableURL // CWS cannot be scripted in chromium, see ChromeExtensionsClient::IsScriptableURL
// https://cs.chromium.org/chromium/src/chrome/common/extensions/chrome_extensions_client.cc // https://cs.chromium.org/chromium/src/chrome/common/extensions/chrome_extensions_client.cc
chromeWebStore: FIREFOX ? 'https://addons.mozilla.org/' : ( chromeWebStore: FIREFOX ? 'https://addons.mozilla.org/' : (
OPERA ? 'https://addons.opera.com/' : 'https://chrome.google.com/webstore/' OPERA ? 'https://addons.opera.com/' : 'https://chrome.google.com/webstore/'
), ),
supported: new RegExp( supported: new RegExp(
'^(file|ftps?|http)://|' + '^(file|ftps?|http)://|' +
`^https://${FIREFOX ? '(?!addons\\.mozilla\\.org)' : ( `^https://${FIREFOX ? '(?!addons\\.mozilla\\.org)' : (
OPERA ? '(?!addons\\.opera\\.com)' : '(?!chrome\\.google\\.com/webstore)' OPERA ? '(?!addons\\.opera\\.com)' : '(?!chrome\\.google\\.com/webstore)'
)}|` + )}|` +
'^' + chrome.runtime.getURL('')), '^' + chrome.runtime.getURL('')),
}; };
let BG = chrome.extension.getBackgroundPage(); let BG = chrome.extension.getBackgroundPage();
if (!BG || BG != window) { if (!BG || BG !== window) {
document.documentElement.classList.toggle('firefox', FIREFOX); document.documentElement.classList.toggle('firefox', FIREFOX);
document.documentElement.classList.toggle('opera', OPERA); document.documentElement.classList.toggle('opera', OPERA);
// TODO: remove once our manifest's minimum_chrome_version is 50+ // TODO: remove once our manifest's minimum_chrome_version is 50+
// Chrome 49 doesn't report own extension pages in webNavigation apparently // Chrome 49 doesn't report own extension pages in webNavigation apparently
if (navigator.userAgent.includes('Chrome/49.')) { if (navigator.userAgent.includes('Chrome/49.')) {
getActiveTab().then(BG.updateIcon); getActiveTab().then(BG.updateIcon);
} }
} }
function notifyAllTabs(msg) { function notifyAllTabs(msg) {
const originalMessage = msg; const originalMessage = msg;
if (msg.method == 'styleUpdated' || msg.method == 'styleAdded') { if (msg.method === 'styleUpdated' || msg.method === 'styleAdded') {
// apply/popup/manage use only meta for these two methods, // apply/popup/manage use only meta for these two methods,
// editor may need the full code but can fetch it directly, // editor may need the full code but can fetch it directly,
// so we send just the meta to avoid spamming lots of tabs with huge styles // so we send just the meta to avoid spamming lots of tabs with huge styles
msg = Object.assign({}, msg, { msg = Object.assign({}, msg, {
style: getStyleWithNoCode(msg.style) style: getStyleWithNoCode(msg.style)
}); });
} }
const affectsAll = !msg.affects || msg.affects.all; const affectsAll = !msg.affects || msg.affects.all;
const affectsOwnOriginOnly = !affectsAll && (msg.affects.editor || msg.affects.manager); const affectsOwnOriginOnly = !affectsAll && (msg.affects.editor || msg.affects.manager);
const affectsTabs = affectsAll || affectsOwnOriginOnly; const affectsTabs = affectsAll || affectsOwnOriginOnly;
const affectsIcon = affectsAll || msg.affects.icon; const affectsIcon = affectsAll || msg.affects.icon;
const affectsPopup = affectsAll || msg.affects.popup; const affectsPopup = affectsAll || msg.affects.popup;
const affectsSelf = affectsPopup || msg.prefs; const affectsSelf = affectsPopup || msg.prefs;
if (affectsTabs || affectsIcon) { if (affectsTabs || affectsIcon) {
const notifyTab = tab => { const notifyTab = tab => {
// own pages will be notified via runtime.sendMessage later // own pages will be notified via runtime.sendMessage later
if ((affectsTabs || URLS.optionsUI.includes(tab.url)) if ((affectsTabs || URLS.optionsUI.includes(tab.url))
&& !(affectsSelf && tab.url.startsWith(URLS.ownOrigin)) && !(affectsSelf && tab.url.startsWith(URLS.ownOrigin))
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
&& (!FIREFOX || tab.width)) { && (!FIREFOX || tab.width)) {
chrome.tabs.sendMessage(tab.id, msg); chrome.tabs.sendMessage(tab.id, msg);
} }
if (affectsIcon && BG) { if (affectsIcon && BG) {
BG.updateIcon(tab); BG.updateIcon(tab);
} }
}; };
// list all tabs including chrome-extension:// which can be ours // list all tabs including chrome-extension:// which can be ours
Promise.all([ Promise.all([
queryTabs(affectsOwnOriginOnly ? {url: URLS.ownOrigin + '*'} : {}), queryTabs(affectsOwnOriginOnly ? {url: URLS.ownOrigin + '*'} : {}),
getActiveTab(), getActiveTab(),
]).then(([tabs, activeTab]) => { ]).then(([tabs, activeTab]) => {
const activeTabId = activeTab && activeTab.id; const activeTabId = activeTab && activeTab.id;
for (const tab of tabs) { for (const tab of tabs) {
invokeOrPostpone(tab.id === activeTabId, notifyTab, tab); invokeOrPostpone(tab.id === activeTabId, notifyTab, tab);
} }
}); });
} }
// notify self: the message no longer is sent to the origin in new Chrome // notify self: the message no longer is sent to the origin in new Chrome
if (typeof onRuntimeMessage != 'undefined') { if (typeof onRuntimeMessage !== 'undefined') {
onRuntimeMessage(originalMessage); onRuntimeMessage(originalMessage);
} }
// notify apply.js on own pages // notify apply.js on own pages
if (typeof applyOnMessage != 'undefined') { if (typeof applyOnMessage !== 'undefined') {
applyOnMessage(originalMessage); applyOnMessage(originalMessage);
} }
// notify background page and all open popups // notify background page and all open popups
if (affectsSelf) { if (affectsSelf) {
chrome.runtime.sendMessage(msg); chrome.runtime.sendMessage(msg);
} }
} }
function queryTabs(options = {}) { function queryTabs(options = {}) {
return new Promise(resolve => return new Promise(resolve =>
chrome.tabs.query(options, tabs => chrome.tabs.query(options, tabs =>
resolve(tabs))); resolve(tabs)));
} }
function getTab(id) { function getTab(id) {
return new Promise(resolve => return new Promise(resolve =>
chrome.tabs.get(id, tab => chrome.tabs.get(id, tab =>
!chrome.runtime.lastError && resolve(tab))); !chrome.runtime.lastError && resolve(tab)));
} }
function getOwnTab() { function getOwnTab() {
return new Promise(resolve => return new Promise(resolve =>
chrome.tabs.getCurrent(tab => resolve(tab))); chrome.tabs.getCurrent(tab => resolve(tab)));
} }
function getActiveTab() { function getActiveTab() {
return queryTabs({currentWindow: true, active: true}) return queryTabs({currentWindow: true, active: true})
.then(tabs => tabs[0]); .then(tabs => tabs[0]);
} }
function getActiveTabRealURL() { function getActiveTabRealURL() {
return getActiveTab() return getActiveTab()
.then(getTabRealURL); .then(getTabRealURL);
} }
function getTabRealURL(tab) { function getTabRealURL(tab) {
return new Promise(resolve => { return new Promise(resolve => {
if (tab.url != 'chrome://newtab/') { if (tab.url !== 'chrome://newtab/') {
resolve(tab.url); resolve(tab.url);
} else { } else {
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => { chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => {
resolve(frame && frame.url || ''); resolve(frame && frame.url || '');
}); });
} }
}); });
} }
// opens a tab or activates the already opened one, // opens a tab or activates the already opened one,
// reuses the New Tab page if it's focused now // reuses the New Tab page if it's focused now
function openURL({url, currentWindow = true}) { function openURL({url, currentWindow = true}) {
if (!url.includes('://')) { if (!url.includes('://')) {
url = chrome.runtime.getURL(url); url = chrome.runtime.getURL(url);
} }
return new Promise(resolve => { return new Promise(resolve => {
// [some] chromium forks don't handle their fake branded protocols // [some] chromium forks don't handle their fake branded protocols
url = url.replace(/^(opera|vivaldi)/, 'chrome'); url = url.replace(/^(opera|vivaldi)/, 'chrome');
// FF doesn't handle moz-extension:// URLs (bug) // FF doesn't handle moz-extension:// URLs (bug)
// API doesn't handle the hash-fragment part // API doesn't handle the hash-fragment part
const urlQuery = url.startsWith('moz-extension') ? undefined : url.replace(/#.*/, ''); const urlQuery = url.startsWith('moz-extension') ? undefined : url.replace(/#.*/, '');
queryTabs({url: urlQuery, currentWindow}).then(tabs => { queryTabs({url: urlQuery, currentWindow}).then(tabs => {
for (const tab of tabs) { for (const tab of tabs) {
if (tab.url == url) { if (tab.url === url) {
activateTab(tab).then(resolve); activateTab(tab).then(resolve);
return; return;
} }
} }
getActiveTab().then(tab => { getActiveTab().then(tab => {
if (tab && tab.url == 'chrome://newtab/' if (tab && tab.url === 'chrome://newtab/'
// prevent redirecting incognito NTP to a chrome URL as it crashes Chrome // prevent redirecting incognito NTP to a chrome URL as it crashes Chrome
&& (!url.startsWith('chrome') || !tab.incognito)) { && (!url.startsWith('chrome') || !tab.incognito)) {
chrome.tabs.update({url}, resolve); chrome.tabs.update({url}, resolve);
} else { } else {
chrome.tabs.create(tab && !FIREFOX ? {url, openerTabId: tab.id} : {url}, resolve); chrome.tabs.create(tab && !FIREFOX ? {url, openerTabId: tab.id} : {url}, resolve);
} }
}); });
}); });
}); });
} }
function activateTab(tab) { function activateTab(tab) {
return Promise.all([ return Promise.all([
new Promise(resolve => { new Promise(resolve => {
chrome.tabs.update(tab.id, {active: true}, resolve); chrome.tabs.update(tab.id, {active: true}, resolve);
}), }),
new Promise(resolve => { new Promise(resolve => {
chrome.windows.update(tab.windowId, {focused: true}, resolve); chrome.windows.update(tab.windowId, {focused: true}, resolve);
}), }),
]); ]);
} }
function stringAsRegExp(s, flags) { function stringAsRegExp(s, flags) {
return new RegExp(s.replace(/[{}()[\]/\\.+?^$:=*!|]/g, '\\$&'), flags); return new RegExp(s.replace(/[{}()[\]/\\.+?^$:=*!|]/g, '\\$&'), flags);
} }
function ignoreChromeError() { function ignoreChromeError() {
chrome.runtime.lastError; // eslint-disable-line no-unused-expressions chrome.runtime.lastError; // eslint-disable-line no-unused-expressions
} }
function getStyleWithNoCode(style) { function getStyleWithNoCode(style) {
const stripped = Object.assign({}, style, {sections: []}); const stripped = Object.assign({}, style, {sections: []});
for (const section of style.sections) { for (const section of style.sections) {
stripped.sections.push(Object.assign({}, section, {code: null})); stripped.sections.push(Object.assign({}, section, {code: null}));
} }
return stripped; return stripped;
} }
// js engine can't optimize the entire function if it contains try-catch // js engine can't optimize the entire function if it contains try-catch
// so we should keep it isolated from normal code in a minimal wrapper // so we should keep it isolated from normal code in a minimal wrapper
// Update: might get fixed in V8 TurboFan in the future // Update: might get fixed in V8 TurboFan in the future
function tryCatch(func, ...args) { function tryCatch(func, ...args) {
try { try {
return func(...args); return func(...args);
} catch (e) {} } catch (e) {}
} }
function tryRegExp(regexp) { function tryRegExp(regexp) {
try { try {
return new RegExp(regexp); return new RegExp(regexp);
} catch (e) {} } catch (e) {}
} }
function tryJSONparse(jsonString) { function tryJSONparse(jsonString) {
try { try {
return JSON.parse(jsonString); return JSON.parse(jsonString);
} catch (e) {} } catch (e) {}
} }
const debounce = Object.assign((fn, delay, ...args) => { const debounce = Object.assign((fn, delay, ...args) => {
clearTimeout(debounce.timers.get(fn)); clearTimeout(debounce.timers.get(fn));
debounce.timers.set(fn, setTimeout(debounce.run, delay, fn, ...args)); debounce.timers.set(fn, setTimeout(debounce.run, delay, fn, ...args));
}, { }, {
timers: new Map(), timers: new Map(),
run(fn, ...args) { run(fn, ...args) {
debounce.timers.delete(fn); debounce.timers.delete(fn);
fn(...args); fn(...args);
}, },
unregister(fn) { unregister(fn) {
clearTimeout(debounce.timers.get(fn)); clearTimeout(debounce.timers.get(fn));
debounce.timers.delete(fn); debounce.timers.delete(fn);
}, },
}); });
function deepCopy(obj) { function deepCopy(obj) {
return obj !== null && obj !== undefined && typeof obj == 'object' return obj !== null && obj !== undefined && typeof obj === 'object'
? deepMerge(typeof obj.slice == 'function' ? [] : {}, obj) ? deepMerge(typeof obj.slice === 'function' ? [] : {}, obj)
: obj; : obj;
} }
function deepMerge(target, ...args) { function deepMerge(target, ...args) {
const isArray = typeof target.slice == 'function'; const isArray = typeof target.slice === 'function';
for (const obj of args) { for (const obj of args) {
if (isArray && obj !== null && obj !== undefined) { if (isArray && obj !== null && obj !== undefined) {
for (const element of obj) { for (const element of obj) {
target.push(deepCopy(element)); target.push(deepCopy(element));
} }
continue; continue;
} }
for (const k in obj) { for (const k in obj) {
const value = obj[k]; const value = obj[k];
if (k in target && typeof value == 'object' && value !== null) { if (k in target && typeof value === 'object' && value !== null) {
deepMerge(target[k], value); deepMerge(target[k], value);
} else { } else {
target[k] = deepCopy(value); target[k] = deepCopy(value);
} }
} }
} }
return target; return target;
} }
function sessionStorageHash(name) { function sessionStorageHash(name) {
return { return {
name, name,
value: tryCatch(JSON.parse, sessionStorage[name]) || {}, value: tryCatch(JSON.parse, sessionStorage[name]) || {},
set(k, v) { set(k, v) {
this.value[k] = v; this.value[k] = v;
this.updateStorage(); this.updateStorage();
}, },
unset(k) { unset(k) {
delete this.value[k]; delete this.value[k];
this.updateStorage(); this.updateStorage();
}, },
updateStorage() { updateStorage() {
sessionStorage[this.name] = JSON.stringify(this.value); sessionStorage[this.name] = JSON.stringify(this.value);
} }
}; };
} }
function onBackgroundReady() { function onBackgroundReady() {
return BG && BG.getStyles ? Promise.resolve() : new Promise(function ping(resolve) { return BG && BG.getStyles ? Promise.resolve() : new Promise(function ping(resolve) {
chrome.runtime.sendMessage({method: 'healthCheck'}, health => { chrome.runtime.sendMessage({method: 'healthCheck'}, health => {
if (health !== undefined) { if (health !== undefined) {
BG = chrome.extension.getBackgroundPage(); BG = chrome.extension.getBackgroundPage();
resolve(); resolve();
} else { } else {
setTimeout(ping, 0, resolve); setTimeout(ping, 0, resolve);
} }
}); });
}); });
} }
// in case Chrome haven't yet loaded the bg page and displays our page like edit/manage // in case Chrome haven't yet loaded the bg page and displays our page like edit/manage
function getStylesSafe(options) { function getStylesSafe(options) {
return onBackgroundReady() return onBackgroundReady()
.then(() => BG.getStyles(options)); .then(() => BG.getStyles(options));
} }
function saveStyleSafe(style) { function saveStyleSafe(style) {
return onBackgroundReady() return onBackgroundReady()
.then(() => BG.saveStyle(BG.deepCopy(style))) .then(() => BG.saveStyle(BG.deepCopy(style)))
.then(savedStyle => { .then(savedStyle => {
if (style.notify === false) { if (style.notify === false) {
handleUpdate(savedStyle, style); handleUpdate(savedStyle, style);
} }
return savedStyle; return savedStyle;
}); });
} }
function deleteStyleSafe({id, notify = true} = {}) { function deleteStyleSafe({id, notify = true} = {}) {
return onBackgroundReady() return onBackgroundReady()
.then(() => BG.deleteStyle({id, notify})) .then(() => BG.deleteStyle({id, notify}))
.then(() => { .then(() => {
if (!notify) { if (!notify) {
handleDelete(id); handleDelete(id);
} }
return id; return id;
}); });
} }
function download(url) { function download(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.timeout = 10e3; xhr.timeout = 10e3;
xhr.onloadend = () => (xhr.status == 200 xhr.onloadend = () => (xhr.status === 200
? resolve(xhr.responseText) ? resolve(xhr.responseText)
: reject(xhr.status)); : reject(xhr.status));
const [mainUrl, query] = url.split('?'); const [mainUrl, query] = url.split('?');
xhr.open(query ? 'POST' : 'GET', mainUrl, true); xhr.open(query ? 'POST' : 'GET', mainUrl, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(query); xhr.send(query);
}); });
} }
function doTimeout(ms = 0, ...args) { function doTimeout(ms = 0, ...args) {
return ms > 0 return ms > 0
? () => new Promise(resolve => setTimeout(resolve, ms, ...args)) ? () => new Promise(resolve => setTimeout(resolve, ms, ...args))
: new Promise(resolve => setTimeout(resolve, 0, ...args)); : new Promise(resolve => setTimeout(resolve, 0, ...args));
} }
function invokeOrPostpone(isInvoke, fn, ...args) { function invokeOrPostpone(isInvoke, fn, ...args) {
return isInvoke return isInvoke
? fn(...args) ? fn(...args)
: setTimeout(invokeOrPostpone, 0, true, fn, ...args); : setTimeout(invokeOrPostpone, 0, true, fn, ...args);
} }

View File

@ -115,10 +115,10 @@ var prefs = new function Prefs() {
defineReadonlyProperty(this.readOnlyValues, key, value); defineReadonlyProperty(this.readOnlyValues, key, value);
const hasChanged = !equal(value, oldValue); const hasChanged = !equal(value, oldValue);
if (!fromBroadcast) { if (!fromBroadcast) {
if (BG && BG != window) { if (BG && BG !== window) {
BG.prefs.set(key, BG.deepCopy(value), {broadcast, sync}); BG.prefs.set(key, BG.deepCopy(value), {broadcast, sync});
} else { } else {
localStorage[key] = typeof defaults[key] == 'object' localStorage[key] = typeof defaults[key] === 'object'
? JSON.stringify(value) ? JSON.stringify(value)
: value; : value;
if (broadcast && hasChanged) { if (broadcast && hasChanged) {
@ -166,7 +166,7 @@ var prefs = new function Prefs() {
for (const key in defaults) { for (const key in defaults) {
const defaultValue = defaults[key]; const defaultValue = defaults[key];
let value = localStorage[key]; let value = localStorage[key];
if (typeof value == 'string') { if (typeof value === 'string') {
switch (typeof defaultValue) { switch (typeof defaultValue) {
case 'boolean': case 'boolean':
value = value.toLowerCase() === 'true'; value = value.toLowerCase() === 'true';
@ -181,7 +181,7 @@ var prefs = new function Prefs() {
} else { } else {
value = defaultValue; value = defaultValue;
} }
if (BG == window) { if (BG === window) {
// when in bg page, .set() will write to localStorage // when in bg page, .set() will write to localStorage
this.set(key, value, {broadcast: false, sync: false}); this.set(key, value, {broadcast: false, sync: false});
} else { } else {
@ -190,13 +190,13 @@ var prefs = new function Prefs() {
} }
} }
if (!BG || BG == window) { if (!BG || BG === window) {
affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false})); affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false}));
getSync().get('settings', ({settings: synced} = {}) => { getSync().get('settings', ({settings: synced} = {}) => {
if (synced) { if (synced) {
for (const key in defaults) { for (const key in defaults) {
if (key == 'popupWidth' && synced[key] != values.popupWidth) { if (key === 'popupWidth' && synced[key] !== values.popupWidth) {
// this is a fix for the period when popupWidth wasn't synced // this is a fix for the period when popupWidth wasn't synced
// TODO: remove it in a couple of months // TODO: remove it in a couple of months
continue; continue;
@ -209,7 +209,7 @@ var prefs = new function Prefs() {
}); });
chrome.storage.onChanged.addListener((changes, area) => { chrome.storage.onChanged.addListener((changes, area) => {
if (area == 'sync' && 'settings' in changes) { if (area === 'sync' && 'settings' in changes) {
const synced = changes.settings.newValue; const synced = changes.settings.newValue;
if (synced) { if (synced) {
for (const key in defaults) { for (const key in defaults) {
@ -283,21 +283,21 @@ var prefs = new function Prefs() {
function defineReadonlyProperty(obj, key, value) { function defineReadonlyProperty(obj, key, value) {
const copy = deepCopy(value); const copy = deepCopy(value);
if (typeof copy == 'object') { if (typeof copy === 'object') {
Object.freeze(copy); Object.freeze(copy);
} }
Object.defineProperty(obj, key, {value: copy, configurable: true}); Object.defineProperty(obj, key, {value: copy, configurable: true});
} }
function equal(a, b) { function equal(a, b) {
if (!a || !b || typeof a != 'object' || typeof b != 'object') { if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {
return a === b; return a === b;
} }
if (Object.keys(a).length != Object.keys(b).length) { if (Object.keys(a).length !== Object.keys(b).length) {
return false; return false;
} }
for (const k in a) { for (const k in a) {
if (typeof a[k] == 'object') { if (typeof a[k] === 'object') {
if (!equal(a[k], b[k])) { if (!equal(a[k], b[k])) {
return false; return false;
} }
@ -315,7 +315,7 @@ var prefs = new function Prefs() {
// Chrome and co. // Chrome and co.
/Safari\/[\d.]+$/.test(navigator.userAgent) && /Safari\/[\d.]+$/.test(navigator.userAgent) &&
// skip forks with Flash as those are likely to have the menu e.g. CentBrowser // skip forks with Flash as those are likely to have the menu e.g. CentBrowser
!Array.from(navigator.plugins).some(p => p.name == 'Shockwave Flash') !Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')
); );
} }
}(); }();
@ -330,7 +330,7 @@ function setupLivePrefs(
const checkedProps = {}; const checkedProps = {};
for (const id of IDs) { for (const id of IDs) {
const element = document.getElementById(id); const element = document.getElementById(id);
checkedProps[id] = element.type == 'checkbox' ? 'checked' : 'value'; checkedProps[id] = element.type === 'checkbox' ? 'checked' : 'value';
updateElement({id, element, force: true}); updateElement({id, element, force: true});
element.addEventListener('change', onChange); element.addEventListener('change', onChange);
} }
@ -338,7 +338,7 @@ function setupLivePrefs(
function onChange() { function onChange() {
const value = this[checkedProps[this.id]]; const value = this[checkedProps[this.id]];
if (prefs.get(this.id) != value) { if (prefs.get(this.id) !== value) {
prefs.set(this.id, value); prefs.set(this.id, value);
} }
} }
@ -349,7 +349,7 @@ function setupLivePrefs(
force, force,
}) { }) {
const prop = checkedProps[id]; const prop = checkedProps[id];
if (force || element[prop] != value) { if (force || element[prop] !== value) {
element[prop] = value; element[prop] = value;
element.dispatchEvent(new Event('change', {bubbles: true, cancelable: true})); element.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
} }

View File

@ -3,7 +3,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title i18n-text="manageTitle"></title> <title i18n-text="manageTitle"></title>
<link rel="stylesheet" href="manage.css"> <link rel="stylesheet" href="manage/manage.css">
<link rel="stylesheet" href="msgbox/msgbox.css"> <link rel="stylesheet" href="msgbox/msgbox.css">
<style id="style-overrides"></style> <style id="style-overrides"></style>
@ -121,12 +121,12 @@
</details> </details>
</template> </template>
<script src="dom.js"></script> <script src="js/dom.js"></script>
<script src="messaging.js"></script> <script src="js/messaging.js"></script>
<script src="prefs.js"></script> <script src="js/prefs.js"></script>
<script src="apply.js"></script> <script src="content/apply.js"></script>
<script src="localization.js"></script> <script src="js/localization.js"></script>
<script src="manage.js"></script> <script src="manage/manage.js"></script>
</head> </head>
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage"> <body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
@ -216,7 +216,7 @@
</div> </div>
<div id="installed"></div> <div id="installed"></div>
<script src="backup/fileSaveLoad.js"></script> <script src="manage/fileSaveLoad.js"></script>
<script src="msgbox/msgbox.js"></script> <script src="msgbox/msgbox.js"></script>
</body> </body>

View File

@ -1,395 +1,395 @@
/* global messageBox, handleUpdate, applyOnMessage */ /* global messageBox, handleUpdate, applyOnMessage */
'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';
function importFromFile({fileTypeFilter, file} = {}) { function importFromFile({fileTypeFilter, file} = {}) {
return new Promise(resolve => { return new Promise(resolve => {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
if (file) { if (file) {
readFile(); readFile();
return; return;
} }
fileInput.style.display = 'none'; fileInput.style.display = 'none';
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = fileTypeFilter || STYLISH_DUMP_FILE_EXT; fileInput.accept = fileTypeFilter || STYLISH_DUMP_FILE_EXT;
fileInput.acceptCharset = 'utf-8'; fileInput.acceptCharset = 'utf-8';
document.body.appendChild(fileInput); document.body.appendChild(fileInput);
fileInput.initialValue = fileInput.value; fileInput.initialValue = fileInput.value;
fileInput.onchange = readFile; fileInput.onchange = readFile;
fileInput.click(); fileInput.click();
function readFile() { function readFile() {
if (file || fileInput.value !== fileInput.initialValue) { if (file || fileInput.value !== fileInput.initialValue) {
file = file || fileInput.files[0]; file = file || fileInput.files[0];
if (file.size > 100e6) { if (file.size > 100e6) {
console.warn("100MB backup? I don't believe you."); console.warn("100MB backup? I don't believe you.");
importFromString('').then(resolve); importFromString('').then(resolve);
return; return;
} }
document.body.style.cursor = 'wait'; document.body.style.cursor = 'wait';
const fReader = new FileReader(); const fReader = new FileReader();
fReader.onloadend = event => { fReader.onloadend = event => {
fileInput.remove(); fileInput.remove();
importFromString(event.target.result).then(numStyles => { importFromString(event.target.result).then(numStyles => {
document.body.style.cursor = ''; document.body.style.cursor = '';
resolve(numStyles); resolve(numStyles);
}); });
}; };
fReader.readAsText(file, 'utf-8'); fReader.readAsText(file, 'utf-8');
} }
} }
}); });
} }
function importFromString(jsonString) { function importFromString(jsonString) {
if (!BG) { if (!BG) {
onBackgroundReady().then(() => importFromString(jsonString)); onBackgroundReady().then(() => importFromString(jsonString));
return; return;
} }
// create objects in background context // create objects in background context
const json = BG.tryJSONparse(jsonString) || []; const json = BG.tryJSONparse(jsonString) || [];
if (typeof json.slice != 'function') { if (typeof json.slice !== 'function') {
json.length = 0; json.length = 0;
} }
const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []); const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []);
const oldStylesByName = json.length && new Map( const oldStylesByName = json.length && new Map(
oldStyles.map(style => [style.name.trim(), style])); oldStyles.map(style => [style.name.trim(), style]));
const stats = { const stats = {
added: {names: [], ids: [], legend: 'importReportLegendAdded'}, added: {names: [], ids: [], legend: 'importReportLegendAdded'},
unchanged: {names: [], ids: [], legend: 'importReportLegendIdentical'}, unchanged: {names: [], ids: [], legend: 'importReportLegendIdentical'},
metaAndCode: {names: [], ids: [], legend: 'importReportLegendUpdatedBoth'}, metaAndCode: {names: [], ids: [], legend: 'importReportLegendUpdatedBoth'},
metaOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedMeta'}, metaOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedMeta'},
codeOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedCode'}, codeOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedCode'},
invalid: {names: [], legend: 'importReportLegendInvalid'}, invalid: {names: [], legend: 'importReportLegendInvalid'},
}; };
let index = 0; let index = 0;
let lastRenderTime = performance.now(); let lastRenderTime = performance.now();
const renderQueue = []; const renderQueue = [];
const RENDER_NAP_TIME_MAX = 1000; // ms const RENDER_NAP_TIME_MAX = 1000; // ms
const RENDER_QUEUE_MAX = 50; // number of styles const RENDER_QUEUE_MAX = 50; // number of styles
const SAVE_OPTIONS = {reason: 'import', notify: false}; const SAVE_OPTIONS = {reason: 'import', notify: false};
return new Promise(proceed); return new Promise(proceed);
function proceed(resolve) { function proceed(resolve) {
while (index < json.length) { while (index < json.length) {
const item = json[index++]; const item = json[index++];
const info = analyze(item); const info = analyze(item);
if (info) { if (info) {
// using saveStyle directly since json was parsed in background page context // using saveStyle directly since json was parsed in background page context
return BG.saveStyle(Object.assign(item, SAVE_OPTIONS)) return BG.saveStyle(Object.assign(item, SAVE_OPTIONS))
.then(style => account({style, info, resolve})); .then(style => account({style, info, resolve}));
} }
} }
renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); renderQueue.forEach(style => handleUpdate(style, {reason: 'import'}));
renderQueue.length = 0; renderQueue.length = 0;
done(resolve); done(resolve);
} }
function analyze(item) { function analyze(item) {
if (!item || !item.name || !item.name.trim() || typeof item != 'object' if (!item || !item.name || !item.name.trim() || typeof item !== 'object'
|| (item.sections && typeof item.sections.slice != 'function')) { || (item.sections && typeof item.sections.slice !== 'function')) {
stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`); stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`);
return; return;
} }
item.name = item.name.trim(); item.name = item.name.trim();
const byId = BG.cachedStyles.byId.get(item.id); const byId = BG.cachedStyles.byId.get(item.id);
const byName = oldStylesByName.get(item.name); const byName = oldStylesByName.get(item.name);
oldStylesByName.delete(item.name); oldStylesByName.delete(item.name);
let oldStyle; let oldStyle;
if (byId) { if (byId) {
if (sameStyle(byId, item)) { if (sameStyle(byId, item)) {
oldStyle = byId; oldStyle = byId;
} else { } else {
item.id = null; item.id = null;
} }
} }
if (!oldStyle && byName) { if (!oldStyle && byName) {
item.id = byName.id; item.id = byName.id;
oldStyle = byName; oldStyle = byName;
} }
const oldStyleKeys = oldStyle && Object.keys(oldStyle); const oldStyleKeys = oldStyle && Object.keys(oldStyle);
const metaEqual = oldStyleKeys && const metaEqual = oldStyleKeys &&
oldStyleKeys.length == Object.keys(item).length && oldStyleKeys.length === Object.keys(item).length &&
oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]); oldStyleKeys.every(k => k === 'sections' || oldStyle[k] === item[k]);
const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item); const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item);
if (metaEqual && codeEqual) { if (metaEqual && codeEqual) {
stats.unchanged.names.push(oldStyle.name); stats.unchanged.names.push(oldStyle.name);
stats.unchanged.ids.push(oldStyle.id); stats.unchanged.ids.push(oldStyle.id);
return; return;
} }
return {oldStyle, metaEqual, codeEqual}; return {oldStyle, metaEqual, codeEqual};
} }
function sameStyle(oldStyle, newStyle) { function sameStyle(oldStyle, newStyle) {
return oldStyle.name.trim() === newStyle.name.trim() || return oldStyle.name.trim() === newStyle.name.trim() ||
['updateUrl', 'originalMd5', 'originalDigest'] ['updateUrl', 'originalMd5', 'originalDigest']
.some(field => oldStyle[field] && oldStyle[field] == newStyle[field]); .some(field => oldStyle[field] && oldStyle[field] === newStyle[field]);
} }
function account({style, info, resolve}) { function account({style, info, resolve}) {
renderQueue.push(style); renderQueue.push(style);
if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX
|| renderQueue.length > RENDER_QUEUE_MAX) { || renderQueue.length > RENDER_QUEUE_MAX) {
renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); renderQueue.forEach(style => handleUpdate(style, {reason: 'import'}));
setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id)); setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id));
renderQueue.length = 0; renderQueue.length = 0;
lastRenderTime = performance.now(); lastRenderTime = performance.now();
} }
setTimeout(proceed, 0, resolve); setTimeout(proceed, 0, resolve);
const {oldStyle, metaEqual, codeEqual} = info; const {oldStyle, metaEqual, codeEqual} = info;
if (!oldStyle) { if (!oldStyle) {
stats.added.names.push(style.name); stats.added.names.push(style.name);
stats.added.ids.push(style.id); stats.added.ids.push(style.id);
return; return;
} }
if (!metaEqual && !codeEqual) { if (!metaEqual && !codeEqual) {
stats.metaAndCode.names.push(reportNameChange(oldStyle, style)); stats.metaAndCode.names.push(reportNameChange(oldStyle, style));
stats.metaAndCode.ids.push(style.id); stats.metaAndCode.ids.push(style.id);
return; return;
} }
if (!codeEqual) { if (!codeEqual) {
stats.codeOnly.names.push(style.name); stats.codeOnly.names.push(style.name);
stats.codeOnly.ids.push(style.id); stats.codeOnly.ids.push(style.id);
return; return;
} }
stats.metaOnly.names.push(reportNameChange(oldStyle, style)); stats.metaOnly.names.push(reportNameChange(oldStyle, style));
stats.metaOnly.ids.push(style.id); stats.metaOnly.ids.push(style.id);
} }
function done(resolve) { function done(resolve) {
const numChanged = stats.metaAndCode.names.length + const numChanged = stats.metaAndCode.names.length +
stats.metaOnly.names.length + stats.metaOnly.names.length +
stats.codeOnly.names.length + stats.codeOnly.names.length +
stats.added.names.length; stats.added.names.length;
Promise.resolve(numChanged && refreshAllTabs()).then(() => { Promise.resolve(numChanged && refreshAllTabs()).then(() => {
const report = Object.keys(stats) const report = Object.keys(stats)
.filter(kind => stats[kind].names.length) .filter(kind => stats[kind].names.length)
.map(kind => { .map(kind => {
const {ids, names, legend} = stats[kind]; const {ids, names, legend} = stats[kind];
const listItemsWithId = (name, i) => const listItemsWithId = (name, i) =>
$element({dataset: {id: ids[i]}, textContent: name}); $element({dataset: {id: ids[i]}, textContent: name});
const listItems = name => const listItems = name =>
$element({textContent: name}); $element({textContent: name});
const block = const block =
$element({tag: 'details', dataset: {id: kind}, appendChild: [ $element({tag: 'details', dataset: {id: kind}, appendChild: [
$element({tag: 'summary', appendChild: $element({tag: 'summary', appendChild:
$element({tag: 'b', textContent: names.length + ' ' + t(legend)}) $element({tag: 'b', textContent: names.length + ' ' + t(legend)})
}), }),
$element({tag: 'small', appendChild: $element({tag: 'small', appendChild:
names.map(ids ? listItemsWithId : listItems) names.map(ids ? listItemsWithId : listItems)
}), }),
]}); ]});
return block; return block;
}); });
scrollTo(0, 0); scrollTo(0, 0);
messageBox({ messageBox({
title: t('importReportTitle'), title: t('importReportTitle'),
contents: report.length ? report : t('importReportUnchanged'), contents: report.length ? report : t('importReportUnchanged'),
buttons: [t('confirmOK'), numChanged && t('undo')], buttons: [t('confirmOK'), numChanged && t('undo')],
onshow: bindClick, onshow: bindClick,
}).then(({button, enter, esc}) => { }).then(({button}) => {
if (button == 1) { if (button === 1) {
undo(); undo();
} }
}); });
resolve(numChanged); resolve(numChanged);
}); });
} }
function undo() { function undo() {
const oldStylesById = new Map(oldStyles.map(style => [style.id, style])); const oldStylesById = new Map(oldStyles.map(style => [style.id, style]));
const newIds = [ const newIds = [
...stats.metaAndCode.ids, ...stats.metaAndCode.ids,
...stats.metaOnly.ids, ...stats.metaOnly.ids,
...stats.codeOnly.ids, ...stats.codeOnly.ids,
...stats.added.ids, ...stats.added.ids,
]; ];
let resolve; let resolve;
index = 0; index = 0;
return new Promise(resolve_ => { return new Promise(resolve_ => {
resolve = resolve_; resolve = resolve_;
undoNextId(); undoNextId();
}).then(refreshAllTabs) }).then(refreshAllTabs)
.then(() => messageBox({ .then(() => messageBox({
title: t('importReportUndoneTitle'), title: t('importReportUndoneTitle'),
contents: newIds.length + ' ' + t('importReportUndone'), contents: newIds.length + ' ' + t('importReportUndone'),
buttons: [t('confirmOK')], buttons: [t('confirmOK')],
})); }));
function undoNextId() { function undoNextId() {
if (index == newIds.length) { if (index === newIds.length) {
resolve(); resolve();
return; return;
} }
const id = newIds[index++]; const id = newIds[index++];
deleteStyleSafe({id, notify: false}).then(id => { deleteStyleSafe({id, notify: false}).then(id => {
const oldStyle = oldStylesById.get(id); const oldStyle = oldStylesById.get(id);
if (oldStyle) { if (oldStyle) {
saveStyleSafe(Object.assign(oldStyle, SAVE_OPTIONS)) saveStyleSafe(Object.assign(oldStyle, SAVE_OPTIONS))
.then(undoNextId); .then(undoNextId);
} else { } else {
undoNextId(); undoNextId();
} }
}); });
} }
} }
function bindClick(box) { function bindClick() {
const highlightElement = event => { const highlightElement = event => {
const styleElement = $('#style-' + event.target.dataset.id); const styleElement = $('#style-' + event.target.dataset.id);
if (styleElement) { if (styleElement) {
scrollElementIntoView(styleElement); scrollElementIntoView(styleElement);
animateElement(styleElement); animateElement(styleElement);
} }
}; };
for (const block of $$('details')) { for (const block of $$('details')) {
if (block.dataset.id != 'invalid') { if (block.dataset.id !== 'invalid') {
block.style.cursor = 'pointer'; block.style.cursor = 'pointer';
block.onclick = highlightElement; block.onclick = highlightElement;
} }
} }
} }
function limitString(s, limit = 100) { function limitString(s, limit = 100) {
return s.length <= limit ? s : s.substr(0, limit) + '...'; return s.length <= limit ? s : s.substr(0, limit) + '...';
} }
function reportNameChange(oldStyle, newStyle) { function reportNameChange(oldStyle, newStyle) {
return newStyle.name != oldStyle.name return newStyle.name !== oldStyle.name
? oldStyle.name + ' —> ' + newStyle.name ? oldStyle.name + ' —> ' + newStyle.name
: oldStyle.name; : oldStyle.name;
} }
function refreshAllTabs() { function refreshAllTabs() {
return Promise.all([ return Promise.all([
getActiveTab(), getActiveTab(),
getOwnTab(), getOwnTab(),
]).then(([activeTab, ownTab]) => new Promise(resolve => { ]).then(([activeTab, ownTab]) => new Promise(resolve => {
// list all tabs including chrome-extension:// which can be ours // list all tabs including chrome-extension:// which can be ours
queryTabs().then(tabs => { queryTabs().then(tabs => {
const lastTab = tabs[tabs.length - 1]; const lastTab = tabs[tabs.length - 1];
for (const tab of tabs) { for (const tab of tabs) {
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
if (FIREFOX && !tab.width) { if (FIREFOX && !tab.width) {
if (tab == lastTab) { if (tab === lastTab) {
resolve(); resolve();
} }
continue; continue;
} }
getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => { getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => {
const message = {method: 'styleReplaceAll', styles}; const message = {method: 'styleReplaceAll', styles};
if (tab.id == ownTab.id) { if (tab.id === ownTab.id) {
applyOnMessage(message); applyOnMessage(message);
} else { } else {
invokeOrPostpone(tab.id == activeTab.id, invokeOrPostpone(tab.id === activeTab.id,
chrome.tabs.sendMessage, tab.id, message, ignoreChromeError); chrome.tabs.sendMessage, tab.id, message, ignoreChromeError);
} }
setTimeout(BG.updateIcon, 0, tab, styles); setTimeout(BG.updateIcon, 0, tab, styles);
if (tab == lastTab) { if (tab === lastTab) {
resolve(); resolve();
} }
}); });
} }
}); });
})); }));
} }
} }
$('#file-all-styles').onclick = () => { $('#file-all-styles').onclick = () => {
getStylesSafe().then(styles => { getStylesSafe().then(styles => {
const text = JSON.stringify(styles, null, '\t'); const text = JSON.stringify(styles, null, '\t');
const url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text); const url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
return url; return url;
// for long URLs; https://github.com/schomery/stylus/issues/13#issuecomment-284582600 // for long URLs; https://github.com/schomery/stylus/issues/13#issuecomment-284582600
}).then(fetch) }).then(fetch)
.then(res => res.blob()) .then(res => res.blob())
.then(blob => { .then(blob => {
const objectURL = URL.createObjectURL(blob); const objectURL = URL.createObjectURL(blob);
let link = $element({ let link = $element({
tag:'a', tag:'a',
href: objectURL, href: objectURL,
type: 'application/json', type: 'application/json',
download: generateFileName(), download: generateFileName(),
}); });
// TODO: remove the fallback when FF multi-process bug is fixed // TODO: remove the fallback when FF multi-process bug is fixed
if (!FIREFOX) { if (!FIREFOX) {
link.dispatchEvent(new MouseEvent('click')); link.dispatchEvent(new MouseEvent('click'));
setTimeout(() => URL.revokeObjectURL(objectURL)); setTimeout(() => URL.revokeObjectURL(objectURL));
} else { } else {
const iframe = document.body.appendChild($element({ const iframe = document.body.appendChild($element({
tag: 'iframe', tag: 'iframe',
style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'), style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'),
})); }));
doTimeout().then(() => { doTimeout().then(() => {
link = iframe.contentDocument.importNode(link, true); link = iframe.contentDocument.importNode(link, true);
iframe.contentDocument.body.appendChild(link); iframe.contentDocument.body.appendChild(link);
}) })
.then(doTimeout) .then(doTimeout)
.then(() => link.dispatchEvent(new MouseEvent('click'))) .then(() => link.dispatchEvent(new MouseEvent('click')))
.then(doTimeout(1000)) .then(doTimeout(1000))
.then(() => { .then(() => {
URL.revokeObjectURL(objectURL); URL.revokeObjectURL(objectURL);
iframe.remove(); iframe.remove();
}); });
} }
}); });
function generateFileName() { function generateFileName() {
const today = new Date(); const today = new Date();
const dd = ('0' + today.getDate()).substr(-2); const dd = ('0' + today.getDate()).substr(-2);
const mm = ('0' + (today.getMonth() + 1)).substr(-2); const mm = ('0' + (today.getMonth() + 1)).substr(-2);
const yyyy = today.getFullYear(); const yyyy = today.getFullYear();
return `stylus-${yyyy}-${mm}-${dd}${STYLUS_BACKUP_FILE_EXT}`; return `stylus-${yyyy}-${mm}-${dd}${STYLUS_BACKUP_FILE_EXT}`;
} }
}; };
$('#unfile-all-styles').onclick = () => { $('#unfile-all-styles').onclick = () => {
importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT}); importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT});
}; };
Object.assign(document.body, { Object.assign(document.body, {
ondragover(event) { ondragover(event) {
const hasFiles = event.dataTransfer.types.includes('Files'); const hasFiles = event.dataTransfer.types.includes('Files');
event.dataTransfer.dropEffect = hasFiles || event.target.type == 'search' ? 'copy' : 'none'; event.dataTransfer.dropEffect = hasFiles || event.target.type === 'search' ? 'copy' : 'none';
this.classList.toggle('dropzone', hasFiles); this.classList.toggle('dropzone', hasFiles);
if (hasFiles) { if (hasFiles) {
event.preventDefault(); event.preventDefault();
clearTimeout(this.fadeoutTimer); clearTimeout(this.fadeoutTimer);
this.classList.remove('fadeout'); this.classList.remove('fadeout');
} }
}, },
ondragend(event) { ondragend() {
animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => { animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => {
this.style.animationDuration = ''; this.style.animationDuration = '';
}); });
}, },
ondragleave(event) { ondragleave(event) {
try { try {
// in Firefox event.target could be XUL browser and hence there is no permission to access it // in Firefox event.target could be XUL browser and hence there is no permission to access it
if (event.target === this) { if (event.target === this) {
this.ondragend(); this.ondragend();
} }
} catch (e) { } catch (e) {
this.ondragend(); this.ondragend();
} }
}, },
ondrop(event) { ondrop(event) {
this.ondragend(); this.ondragend();
if (event.dataTransfer.files.length) { if (event.dataTransfer.files.length) {
event.preventDefault(); event.preventDefault();
if ($('#onlyUpdates input').checked) { if ($('#onlyUpdates input').checked) {
$('#onlyUpdates input').click(); $('#onlyUpdates input').click();
} }
importFromFile({file: event.dataTransfer.files[0]}); importFromFile({file: event.dataTransfer.files[0]});
} }
}, },
}); });

View File

@ -73,7 +73,7 @@ function initGlobalEvents() {
// focus search field on / key // focus search field on / key
document.onkeypress = event => { document.onkeypress = event => {
if ((event.keyCode || event.which) == 47 if ((event.keyCode || event.which) === 47
&& !event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey
&& !event.target.matches('[type="text"], [type="search"]')) { && !event.target.matches('[type="text"], [type="search"]')) {
event.preventDefault(); event.preventDefault();
@ -114,7 +114,7 @@ function initGlobalEvents() {
function showStyles(styles = []) { function showStyles(styles = []) {
const sorted = styles const sorted = styles
.map(style => ({name: style.name.toLocaleLowerCase(), style})) .map(style => ({name: style.name.toLocaleLowerCase(), style}))
.sort((a, b) => (a.name < b.name ? -1 : a.name == b.name ? 0 : 1)); .sort((a, b) => (a.name < b.name ? -1 : a.name === b.name ? 0 : 1));
let index = 0; let index = 0;
const scrollY = (history.state || {}).scrollY; const scrollY = (history.state || {}).scrollY;
const shouldRenderAll = scrollY > window.innerHeight || sessionStorage.justEditedStyleId; const shouldRenderAll = scrollY > window.innerHeight || sessionStorage.justEditedStyleId;
@ -128,8 +128,11 @@ function showStyles(styles = []) {
function renderStyles() { function renderStyles() {
const t0 = performance.now(); const t0 = performance.now();
let rendered = 0; let rendered = 0;
while (index < sorted.length while (
&& (shouldRenderAll || ++rendered < 10 || performance.now() - t0 < 10)) { index < sorted.length &&
// eslint-disable-next-line no-unmodified-loop-condition
(shouldRenderAll || ++rendered < 10 || performance.now() - t0 < 10)
) {
renderBin.appendChild(createStyleElement(sorted[index++])); renderBin.appendChild(createStyleElement(sorted[index++]));
} }
filterAndAppend({container: renderBin}); filterAndAppend({container: renderBin});
@ -225,18 +228,18 @@ function createStyleTargetsElement({entry, style, postponeFavicons}) {
displayed.add(targetValue); displayed.add(targetValue);
const element = template.appliesToTarget.cloneNode(true); const element = template.appliesToTarget.cloneNode(true);
if (!newUI.enabled) { if (!newUI.enabled) {
if (numTargets == 10) { if (numTargets === 10) {
container = container.appendChild(template.extraAppliesTo.cloneNode(true)); container = container.appendChild(template.extraAppliesTo.cloneNode(true));
} else if (numTargets > 1) { } else if (numTargets > 1) {
container.appendChild(template.appliesToSeparator.cloneNode(true)); container.appendChild(template.appliesToSeparator.cloneNode(true));
} }
} else if (newUI.favicons) { } else if (newUI.favicons) {
let favicon = ''; let favicon = '';
if (type == 'domains') { if (type === 'domains') {
favicon = GET_FAVICON_URL + targetValue; favicon = GET_FAVICON_URL + targetValue;
} else if (targetValue.startsWith('chrome-extension:')) { } else if (targetValue.startsWith('chrome-extension:')) {
favicon = OWN_ICON; favicon = OWN_ICON;
} else if (type != 'regexps') { } else if (type !== 'regexps') {
favicon = targetValue.includes('://') && targetValue.match(/^.*?:\/\/([^/]+)/); favicon = targetValue.includes('://') && targetValue.match(/^.*?:\/\/([^/]+)/);
favicon = favicon ? GET_FAVICON_URL + favicon[1] : ''; favicon = favicon ? GET_FAVICON_URL + favicon[1] : '';
} }
@ -289,7 +292,7 @@ Object.assign(handleEvent, {
const target = event.target; const target = event.target;
const entry = target.closest('.entry'); const entry = target.closest('.entry');
for (const selector in handleEvent.ENTRY_ROUTES) { for (const selector in handleEvent.ENTRY_ROUTES) {
for (let el = target; el && el != entry; el = el.parentElement) { for (let el = target; el && el !== entry; el = el.parentElement) {
if (el.matches(selector)) { if (el.matches(selector)) {
const handler = handleEvent.ENTRY_ROUTES[selector]; const handler = handleEvent.ENTRY_ROUTES[selector];
return handleEvent[handler].call(el, event, entry); return handleEvent[handler].call(el, event, entry);
@ -304,8 +307,8 @@ Object.assign(handleEvent, {
} }
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const left = event.button == 0; const left = event.button === 0;
const middle = event.button == 1; const middle = event.button === 1;
const shift = event.shiftKey; const shift = event.shiftKey;
const ctrl = event.ctrlKey; const ctrl = event.ctrlKey;
const openWindow = left && shift && !ctrl; const openWindow = left && shift && !ctrl;
@ -357,8 +360,8 @@ Object.assign(handleEvent, {
className: 'danger center', className: 'danger center',
buttons: [t('confirmDelete'), t('confirmCancel')], buttons: [t('confirmDelete'), t('confirmCancel')],
}) })
.then(({button, enter, esc}) => { .then(({button, enter}) => {
if (button == 0 || enter) { if (button === 0 || enter) {
deleteStyleSafe({id}); deleteStyleSafe({id});
} }
}); });
@ -383,10 +386,10 @@ Object.assign(handleEvent, {
}, },
filterOnChange({target: el, forceRefilter}) { filterOnChange({target: el, forceRefilter}) {
const getValue = el => (el.type == 'checkbox' ? el.checked : el.value.trim()); const getValue = el => (el.type === 'checkbox' ? el.checked : el.value.trim());
if (!forceRefilter) { if (!forceRefilter) {
const value = getValue(el); const value = getValue(el);
if (value == el.lastValue) { if (value === el.lastValue) {
return; return;
} }
el.lastValue = value; el.lastValue = value;
@ -412,22 +415,22 @@ Object.assign(handleEvent, {
function handleUpdate(style, {reason, method} = {}) { function handleUpdate(style, {reason, method} = {}) {
let entry; let entry;
let oldEntry = $(ENTRY_ID_PREFIX + style.id); let oldEntry = $(ENTRY_ID_PREFIX + style.id);
if (oldEntry && method == 'styleUpdated') { if (oldEntry && method === 'styleUpdated') {
handleToggledOrCodeOnly(); handleToggledOrCodeOnly();
} }
entry = entry || createStyleElement({style}); entry = entry || createStyleElement({style});
if (oldEntry) { if (oldEntry) {
if (oldEntry.styleNameLowerCase == entry.styleNameLowerCase) { if (oldEntry.styleNameLowerCase === entry.styleNameLowerCase) {
installed.replaceChild(entry, oldEntry); installed.replaceChild(entry, oldEntry);
} else { } else {
oldEntry.remove(); oldEntry.remove();
} }
} }
if (reason == 'update' && entry.matches('.updatable')) { if (reason === 'update' && entry.matches('.updatable')) {
handleUpdateInstalled(); handleUpdateInstalled();
} }
filterAndAppend({entry}); filterAndAppend({entry});
if (!entry.matches('.hidden') && reason != 'import') { if (!entry.matches('.hidden') && reason !== 'import') {
animateElement(entry); animateElement(entry);
scrollElementIntoView(entry); scrollElementIntoView(entry);
} }
@ -435,12 +438,12 @@ function handleUpdate(style, {reason, method} = {}) {
function handleToggledOrCodeOnly() { function handleToggledOrCodeOnly() {
const newStyleMeta = getStyleWithNoCode(style); const newStyleMeta = getStyleWithNoCode(style);
const diff = objectDiff(oldEntry.styleMeta, newStyleMeta); const diff = objectDiff(oldEntry.styleMeta, newStyleMeta);
if (diff.length == 0) { if (diff.length === 0) {
// only code was modified // only code was modified
entry = oldEntry; entry = oldEntry;
oldEntry = null; oldEntry = null;
} }
if (diff.length == 1 && diff[0].key == 'enabled') { if (diff.length === 1 && diff[0].key === 'enabled') {
oldEntry.classList.toggle('enabled', style.enabled); oldEntry.classList.toggle('enabled', style.enabled);
oldEntry.classList.toggle('disabled', !style.enabled); oldEntry.classList.toggle('disabled', !style.enabled);
$$('.checker', oldEntry).forEach(el => (el.checked = style.enabled)); $$('.checker', oldEntry).forEach(el => (el.checked = style.enabled));
@ -478,8 +481,8 @@ function switchUI({styleOnly} = {}) {
// ensure the global option is processed first // ensure the global option is processed first
for (const el of [$('#manage.newUI'), ...$$('[id^="manage.newUI."]')]) { for (const el of [$('#manage.newUI'), ...$$('[id^="manage.newUI."]')]) {
const id = el.id.replace(/^manage\.newUI\.?/, '') || 'enabled'; const id = el.id.replace(/^manage\.newUI\.?/, '') || 'enabled';
const value = el.type == 'checkbox' ? el.checked : Number(el.value); const value = el.type === 'checkbox' ? el.checked : Number(el.value);
const valueChanged = value !== newUI[id] && (id == 'enabled' || current.enabled); const valueChanged = value !== newUI[id] && (id === 'enabled' || current.enabled);
current[id] = value; current[id] = value;
changed[id] = valueChanged; changed[id] = valueChanged;
someChanged |= valueChanged; someChanged |= valueChanged;
@ -565,7 +568,7 @@ function checkUpdateAll() {
$('#apply-all-updates').classList.add('hidden'); $('#apply-all-updates').classList.add('hidden');
$('#update-all-no-updates').classList.add('hidden'); $('#update-all-no-updates').classList.add('hidden');
const ignoreDigest = this && this.id == 'check-all-updates-force'; const ignoreDigest = this && this.id === 'check-all-updates-force';
$$('.updatable:not(.can-update)' + (ignoreDigest ? '' : ':not(.update-problem)')) $$('.updatable:not(.can-update)' + (ignoreDigest ? '' : ':not(.update-problem)'))
.map(el => checkUpdate(el, {single: false})); .map(el => checkUpdate(el, {single: false}));
@ -582,7 +585,7 @@ function checkUpdateAll() {
total = value; total = value;
break; break;
case BG.updater.UPDATED: case BG.updater.UPDATED:
if (++updated == 1) { if (++updated === 1) {
$('#apply-all-updates').disabled = true; $('#apply-all-updates').disabled = true;
$('#apply-all-updates').classList.remove('hidden'); $('#apply-all-updates').classList.remove('hidden');
} }
@ -590,7 +593,7 @@ function checkUpdateAll() {
// fallthrough // fallthrough
case BG.updater.SKIPPED: case BG.updater.SKIPPED:
checked++; checked++;
if (details == BG.updater.EDITED || details == BG.updater.MAYBE_EDITED) { if (details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED) {
skippedEdited++; skippedEdited++;
} }
reportUpdateState(state, value, details); reportUpdateState(state, value, details);
@ -603,13 +606,13 @@ function checkUpdateAll() {
function done() { function done() {
document.body.classList.remove('update-in-progress'); document.body.classList.remove('update-in-progress');
$('#check-all-updates').disabled = total == 0; $('#check-all-updates').disabled = total === 0;
$('#apply-all-updates').disabled = false; $('#apply-all-updates').disabled = false;
renderUpdatesOnlyFilter({check: updated + skippedEdited > 0}); renderUpdatesOnlyFilter({check: updated + skippedEdited > 0});
if (!updated) { if (!updated) {
$('#update-all-no-updates').dataset.skippedEdited = skippedEdited > 0; $('#update-all-no-updates').dataset.skippedEdited = skippedEdited > 0;
$('#update-all-no-updates').classList.remove('hidden'); $('#update-all-no-updates').classList.remove('hidden');
$('#check-all-updates-force').classList.toggle('hidden', skippedEdited == 0); $('#check-all-updates-force').classList.toggle('hidden', skippedEdited === 0);
} }
} }
} }
@ -645,16 +648,16 @@ function reportUpdateState(state, style, details) {
if (entry.classList.contains('can-update')) { if (entry.classList.contains('can-update')) {
break; break;
} }
const same = details == BG.updater.SAME_MD5 || details == BG.updater.SAME_CODE; const same = details === BG.updater.SAME_MD5 || details === BG.updater.SAME_CODE;
const edited = details == BG.updater.EDITED || details == BG.updater.MAYBE_EDITED; const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED;
entry.dataset.details = details; entry.dataset.details = details;
if (!details) { if (!details) {
details = t('updateCheckFailServerUnreachable'); details = t('updateCheckFailServerUnreachable');
} else if (typeof details == 'number') { } else if (typeof details === 'number') {
details = t('updateCheckFailBadResponseCode', [details]); details = t('updateCheckFailBadResponseCode', [details]);
} else if (details == BG.updater.EDITED) { } else if (details === BG.updater.EDITED) {
details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
} else if (details == BG.updater.MAYBE_EDITED) { } else if (details === BG.updater.MAYBE_EDITED) {
details = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); details = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
} }
const message = same ? t('updateCheckSucceededNoUpdate') : details; const message = same ? t('updateCheckSucceededNoUpdate') : details;
@ -716,7 +719,7 @@ function searchStyles({immediately, container}) {
const searchElement = $('#search'); const searchElement = $('#search');
const query = searchElement.value.toLocaleLowerCase(); const query = searchElement.value.toLocaleLowerCase();
const queryPrev = searchElement.lastValue || ''; const queryPrev = searchElement.lastValue || '';
if (query == queryPrev && !immediately && !container) { if (query === queryPrev && !immediately && !container) {
return; return;
} }
if (!immediately) { if (!immediately) {
@ -738,7 +741,7 @@ function searchStyles({immediately, container}) {
style.url && isMatchingText(style.url) || style.url && isMatchingText(style.url) ||
isMatchingStyle(style))); isMatchingStyle(style)));
} }
if (entry.classList.contains('not-matching') != !isMatching) { if (entry.classList.contains('not-matching') !== !isMatching) {
entry.classList.toggle('not-matching', !isMatching); entry.classList.toggle('not-matching', !isMatching);
needsRefilter = true; needsRefilter = true;
} }
@ -847,7 +850,7 @@ function reapplyFilter(container = installed) {
shuffle(false); shuffle(false);
setTimeout(shuffle, 0, true); setTimeout(shuffle, 0, true);
// single-element job from handleEvent(): add the last wraith // single-element job from handleEvent(): add the last wraith
if (toHide.length == 1 && toHide[0].parentElement != installed) { if (toHide.length === 1 && toHide[0].parentElement !== installed) {
installed.appendChild(toHide[0]); installed.appendChild(toHide[0]);
} }
return; return;
@ -882,7 +885,7 @@ function reapplyFilter(container = installed) {
const skipGroup = state => { const skipGroup = state => {
const start = i; const start = i;
const first = entry; const first = entry;
while (entry && entry.classList.contains('hidden') == state) { while (entry && entry.classList.contains('hidden') === state) {
entry = entry.nextElementSibling; entry = entry.nextElementSibling;
i++; i++;
} }
@ -900,7 +903,7 @@ function reapplyFilter(container = installed) {
// 3. move the shortest group; repeat 2-3 // 3. move the shortest group; repeat 2-3
if (hidden.len < visible.len && (fullPass || hidden.len % 2)) { if (hidden.len < visible.len && (fullPass || hidden.len % 2)) {
// 3a. move hidden under the horizon // 3a. move hidden under the horizon
for (let j = 0; j < (fullPass ? hidden.len : 1); j++) { for (let j = 0; j < (fullPass ? hidden.len : 1); j++) {
const entry = entries[hidden.start]; const entry = entries[hidden.start];
installed.insertBefore(entry, horizon); installed.insertBefore(entry, horizon);
horizon = entry; horizon = entry;
@ -975,14 +978,19 @@ function objectDiff(first, second, path = '') {
diff.push({path, key, values: [a], type: 'removed'}); diff.push({path, key, values: [a], type: 'removed'});
continue; continue;
} }
if (a && typeof a.filter == 'function' && b && typeof b.filter == 'function') { if (a && typeof a.filter === 'function' && b && typeof b.filter === 'function') {
if (a.length != b.length if (
|| a.some((el, i) => !el || typeof el != 'object' ? el != b[i] a.length !== b.length ||
: objectDiff(el, b[i], path + key + '[' + i + '].').length) a.some((el, i) => {
const result = !el || typeof el !== 'object'
? el !== b[i]
: objectDiff(el, b[i], path + key + '[' + i + '].').length;
return result;
})
) { ) {
diff.push({path, key, values: [a, b], type: 'changed'}); diff.push({path, key, values: [a, b], type: 'changed'});
} }
} else if (typeof a == 'object' && typeof b == 'object') { } else if (typeof a === 'object' && typeof b === 'object') {
diff.push(...objectDiff(a, b, path + key + '.')); diff.push(...objectDiff(a, b, path + key + '.'));
} else { } else {
diff.push({path, key, values: [a, b], type: 'changed'}); diff.push({path, key, values: [a, b], type: 'changed'});

View File

@ -19,7 +19,13 @@
"<all_urls>" "<all_urls>"
], ],
"background": { "background": {
"scripts": ["messaging.js", "storage.js", "prefs.js", "background.js", "update.js"] "scripts": [
"js/messaging.js",
"background/storage.js",
"js/prefs.js",
"background/background.js",
"background/update.js"
]
}, },
"commands": { "commands": {
"openManage": { "openManage": {
@ -35,13 +41,13 @@
"run_at": "document_start", "run_at": "document_start",
"all_frames": true, "all_frames": true,
"match_about_blank": true, "match_about_blank": true,
"js": ["apply.js"] "js": ["content/apply.js"]
}, },
{ {
"matches": ["http://userstyles.org/*", "https://userstyles.org/*"], "matches": ["http://userstyles.org/*", "https://userstyles.org/*"],
"run_at": "document_start", "run_at": "document_start",
"all_frames": false, "all_frames": false,
"js": ["install.js"] "js": ["content/install.js"]
} }
], ],
"browser_action": { "browser_action": {
@ -56,7 +62,7 @@
}, },
"default_locale": "en", "default_locale": "en",
"options_ui": { "options_ui": {
"page": "options/index.html", "page": "options.html",
"chrome_style": true "chrome_style": true
} }
} }

View File

@ -30,9 +30,9 @@ function messageBox({
key(event) { key(event) {
const keyCode = event.keyCode || event.which; const keyCode = event.keyCode || event.which;
if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey
&& (keyCode == 13 || keyCode == 27)) { && (keyCode === 13 || keyCode === 27)) {
event.preventDefault(); event.preventDefault();
resolveWith(keyCode == 13 ? {enter: true} : {esc: true}); resolveWith(keyCode === 13 ? {enter: true} : {esc: true});
} }
}, },
scroll() { scroll() {
@ -52,7 +52,7 @@ function messageBox({
unbindAndRemoveSelf(); unbindAndRemoveSelf();
} }
const id = 'message-box'; const id = 'message-box';
const putAs = typeof contents == 'string' ? 'innerHTML' : 'appendChild'; const putAs = typeof contents === 'string' ? 'innerHTML' : 'appendChild';
messageBox.element = $element({id, className, appendChild: [ messageBox.element = $element({id, className, appendChild: [
$element({appendChild: [ $element({appendChild: [
$element({id: `${id}-title`, innerHTML: title}), $element({id: `${id}-title`, innerHTML: title}),

View File

@ -2,12 +2,12 @@
<html id="stylus"> <html id="stylus">
<head> <head>
<title i18n-text-append="optionsHeading">Stylus </title> <title i18n-text-append="optionsHeading">Stylus </title>
<link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="options/index.css">
<script src="/dom.js"></script> <script src="js/dom.js"></script>
<script src="/messaging.js"></script> <script src="js/messaging.js"></script>
<script src="/localization.js"></script> <script src="js/localization.js"></script>
<script src="/prefs.js"></script> <script src="js/prefs.js"></script>
<script src="/apply.js"></script> <script src="content/apply.js"></script>
</head> </head>
<body> <body>
@ -128,6 +128,6 @@
</ol> </ol>
</div> </div>
<script src="index.js"></script> <script src="options/index.js"></script>
</body> </body>
</html> </html>

View File

@ -81,6 +81,7 @@ label:not([disabled]) > :first-child {
label:not([disabled]):hover > :first-child { label:not([disabled]):hover > :first-child {
text-shadow: 0 0 0.01px rgba(0, 0, 0, .25); text-shadow: 0 0 0.01px rgba(0, 0, 0, .25);
cursor: pointer;
} }
button, button,

View File

@ -15,7 +15,7 @@ document.onclick = e => {
switch (target.dataset.cmd) { switch (target.dataset.cmd) {
case 'open-manage': case 'open-manage':
openURL({url: '/manage.html'}); openURL({url: 'manage.html'});
break; break;
case 'check-updates': case 'check-updates':
@ -65,9 +65,9 @@ function checkUpdates() {
function setupRadioButtons() { function setupRadioButtons() {
const sets = {}; const sets = {};
const onChange = function() { const onChange = function () {
const newValue = sets[this.name].indexOf(this); const newValue = sets[this.name].indexOf(this);
if (newValue >= 0 && prefs.get(this.name) != newValue) { if (newValue >= 0 && prefs.get(this.name) !== newValue) {
prefs.set(this.name, newValue); prefs.set(this.name, newValue);
} }
}; };

View File

@ -2,7 +2,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" href="popup.css"> <link rel="stylesheet" href="popup/popup.css">
<!-- Notes: <!-- Notes:
* Chrome doesn't garbage-collect (or even leaks) SVG <symbol> referenced via <use> so we'll embed the code directly * Chrome doesn't garbage-collect (or even leaks) SVG <symbol> referenced via <use> so we'll embed the code directly
@ -55,12 +55,12 @@
</div> </div>
</template> </template>
<script src="dom.js"></script> <script src="js/dom.js"></script>
<script src="messaging.js"></script> <script src="js/messaging.js"></script>
<script src="localization.js"></script> <script src="js/localization.js"></script>
<script src="prefs.js"></script> <script src="js/prefs.js"></script>
<script src="apply.js"></script> <script src="content/apply.js"></script>
<script src="popup.js"></script> <script src="popup/popup.js"></script>
</head> </head>
<body id="stylus-popup"> <body id="stylus-popup">

View File

@ -79,7 +79,7 @@ function initPopup(url) {
} }
// action buttons // action buttons
$('#disableAll').onchange = function() { $('#disableAll').onchange = function () {
installed.classList.toggle('disabled', this.checked); installed.classList.toggle('disabled', this.checked);
}; };
setupLivePrefs(); setupLivePrefs();
@ -278,7 +278,7 @@ Object.assign(handleEvent, {
toggle(event) { toggle(event) {
saveStyleSafe({ saveStyleSafe({
id: handleEvent.getClickedStyleId(event), id: handleEvent.getClickedStyleId(event),
enabled: this.type == 'checkbox' ? this.checked : this.matches('.enable'), enabled: this.type === 'checkbox' ? this.checked : this.matches('.enable'),
}); });
}, },
@ -293,9 +293,9 @@ Object.assign(handleEvent, {
window.onkeydown = event => { window.onkeydown = event => {
const keyCode = event.keyCode || event.which; const keyCode = event.keyCode || event.which;
if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey
&& (keyCode == 13 || keyCode == 27)) { && (keyCode === 13 || keyCode === 27)) {
event.preventDefault(); event.preventDefault();
confirm(keyCode == 13); confirm(keyCode === 13);
} }
}; };
function confirm(ok) { function confirm(ok) {
@ -342,9 +342,9 @@ Object.assign(handleEvent, {
maybeEdit(event) { maybeEdit(event) {
if (!( if (!(
event.button == 0 && (event.ctrlKey || event.metaKey) || event.button === 0 && (event.ctrlKey || event.metaKey) ||
event.button == 1 || event.button === 1 ||
event.button == 2)) { event.button === 2)) {
return; return;
} }
// open an editor on middleclick // open an editor on middleclick
@ -401,10 +401,10 @@ function detectSloppyRegexps({entry, style}) {
for (const section of style.sections) { for (const section of style.sections) {
for (const regexp of section.regexps) { for (const regexp of section.regexps) {
for (let pass = 1; pass <= 2; pass++) { for (let pass = 1; pass <= 2; pass++) {
const cacheKey = pass == 1 ? regexp : BG.SLOPPY_REGEXP_PREFIX + regexp; const cacheKey = pass === 1 ? regexp : BG.SLOPPY_REGEXP_PREFIX + regexp;
if (!rxCache.has(cacheKey)) { if (!rxCache.has(cacheKey)) {
// according to CSS4 @document specification the entire URL must match // according to CSS4 @document specification the entire URL must match
const anchored = pass == 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$'; const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
const rx = tryRegExp(anchored); const rx = tryRegExp(anchored);
rxCache.set(cacheKey, rx || false); rxCache.set(cacheKey, rx || false);
} }

View File

@ -22,7 +22,7 @@ project = transifex.project(project_slug)
project.languages.each do |language| project.languages.each do |language|
code = language.language_code code = language.language_code
puts "Getting locale #{code}" puts "Getting locale #{code}"
dir_name = "_locales/#{code}" dir_name = "../_locales/#{code}"
Dir.mkdir(dir_name) if !Dir.exist?(dir_name) Dir.mkdir(dir_name) if !Dir.exist?(dir_name)
has_content = false has_content = false
project.resources.each do |resource| project.resources.each do |resource|

0
pull_locales.sh → tools/pull_locales.sh Executable file → Normal file
View File

View File

@ -2,19 +2,19 @@
import io, os, json, re import io, os, json, re
from collections import OrderedDict from collections import OrderedDict
with io.open('_locales/en/messages.json', 'r', encoding='utf-8') as f: with io.open('../_locales/en/messages.json', 'r', encoding='utf-8') as f:
items = json.load(f).items() items = json.load(f).items()
english = [(k, v['message']) for k, v in items if 'message' in v] english = [(k, v['message']) for k, v in items if 'message' in v]
english_placeholders = [(k, v['placeholders']) for k,v in items english_placeholders = [(k, v['placeholders']) for k,v in items
if 'placeholders' in v] if 'placeholders' in v]
for locale_name in os.listdir('_locales'): for locale_name in os.listdir('../_locales'):
if locale_name == 'en': if locale_name == 'en':
continue continue
if not re.match(r'^\w{2}(_\w{2,3})?$', locale_name): if not re.match(r'^\w{2}(_\w{2,3})?$', locale_name):
print('Skipped %s: not a locale dir' % locale_name) print('Skipped %s: not a locale dir' % locale_name)
continue continue
loc_path = '_locales/' + locale_name + '/messages.json' loc_path = '../_locales/' + locale_name + '/messages.json'
with io.open(loc_path, 'r+', encoding='utf-8') as f: with io.open(loc_path, 'r+', encoding='utf-8') as f:
loc = json.load(f, object_pairs_hook=OrderedDict) loc = json.load(f, object_pairs_hook=OrderedDict)

View File

@ -364,7 +364,8 @@
} }
} }
outputPosCol = 0; outputPosCol = 0;
let i = output.length, token; let i = output.length;
let token;
while (--i >= 0 && (token = output[i]) != '\n') { while (--i >= 0 && (token = output[i]) != '\n') {
outputPosCol += token.length; outputPosCol += token.length;
} }

Some files were not shown because too many files have changed in this diff Show More