From 7a9d629ec867a317f48f7960daefc1ea7a95eb92 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 12 Jul 2017 13:17:04 -0500 Subject: [PATCH 01/22] Restructure folders --- .eslintignore | 5 +- background.js => background/background.js | 0 messaging.js => background/messaging.js | 742 ++++++++-------- storage.js => background/storage.js | 0 update.js => background/update.js | 0 backup/fileSaveLoad.js | 395 --------- apply.js => content/apply.js | 0 install.js => content/install.js | 720 ++++++++-------- edit.js => edit/edit.js | 0 options/index.html => index.html | 0 dom.js => js/dom.js | 0 localization.js => js/localization.js | 0 prefs.js => js/prefs.js | 0 manage/fileSaveLoad.js | 791 ++++++++++++++++++ manage.css => manage/manage.css | 0 manage.js => manage/manage.js | 0 popup.css => popup/popup.css | 0 popup.js => popup/popup.js | 0 pull_locales.rb => tools/pull_locales.rb | 0 pull_locales.sh => tools/pull_locales.sh | 0 .../pull_locales_postprocess.py | 0 .../beautify}/beautify-css-mod.js | 0 .../beautify}/beautify-css.js | 0 .../codemirror}/addon/lint/css-lint.js | 0 .../addon/search/match-highlighter.js | 0 {codemirror => vendor/codemirror}/LICENSE | 0 .../codemirror}/addon/comment/comment.js | 0 .../codemirror}/addon/dialog/dialog.css | 0 .../codemirror}/addon/dialog/dialog.js | 0 .../codemirror}/addon/edit/matchbrackets.js | 0 .../codemirror}/addon/fold/brace-fold.js | 0 .../codemirror}/addon/fold/comment-fold.js | 0 .../codemirror}/addon/fold/foldcode.js | 0 .../codemirror}/addon/fold/foldgutter.css | 0 .../codemirror}/addon/fold/foldgutter.js | 0 .../codemirror}/addon/hint/css-hint.js | 0 .../codemirror}/addon/hint/show-hint.css | 0 .../codemirror}/addon/hint/show-hint.js | 0 .../codemirror}/addon/lint/css-lint.js | 0 .../codemirror}/addon/lint/lint.css | 0 .../codemirror}/addon/lint/lint.js | 0 .../addon/scroll/annotatescrollbar.js | 0 .../addon/search/matchesonscrollbar.css | 0 .../addon/search/matchesonscrollbar.js | 0 .../codemirror}/addon/search/search.js | 0 .../codemirror}/addon/search/searchcursor.js | 0 .../addon/selection/active-line.js | 0 .../codemirror}/keymap/emacs.js | 0 .../codemirror}/keymap/sublime.js | 0 .../codemirror}/keymap/vim.js | 0 .../codemirror}/lib/codemirror.css | 0 .../codemirror}/lib/codemirror.js | 0 .../codemirror}/mode/css/css.js | 0 .../codemirror}/mode/css/gss.html | 0 .../codemirror}/mode/css/gss_test.js | 0 .../codemirror}/mode/css/index.html | 0 .../codemirror}/mode/css/less.html | 0 .../codemirror}/mode/css/less_test.js | 0 .../codemirror}/mode/css/scss.html | 0 .../codemirror}/mode/css/scss_test.js | 0 .../codemirror}/mode/css/test.js | 0 .../codemirror}/theme/3024-day.css | 0 .../codemirror}/theme/3024-night.css | 0 .../codemirror}/theme/abcdef.css | 0 .../codemirror}/theme/ambiance-mobile.css | 0 .../codemirror}/theme/ambiance.css | 0 .../codemirror}/theme/base16-dark.css | 0 .../codemirror}/theme/base16-light.css | 0 .../codemirror}/theme/bespin.css | 0 .../codemirror}/theme/blackboard.css | 0 .../codemirror}/theme/cobalt.css | 0 .../codemirror}/theme/colorforth.css | 0 .../codemirror}/theme/dracula.css | 0 .../codemirror}/theme/duotone-dark.css | 0 .../codemirror}/theme/duotone-light.css | 0 .../codemirror}/theme/eclipse.css | 0 .../codemirror}/theme/elegant.css | 0 .../codemirror}/theme/erlang-dark.css | 0 .../codemirror}/theme/hopscotch.css | 0 .../codemirror}/theme/icecoder.css | 0 .../codemirror}/theme/isotope.css | 0 .../codemirror}/theme/lesser-dark.css | 0 .../codemirror}/theme/liquibyte.css | 0 .../codemirror}/theme/material.css | 0 .../codemirror}/theme/mbo.css | 0 .../codemirror}/theme/mdn-like.css | 0 .../codemirror}/theme/midnight.css | 0 .../codemirror}/theme/monokai.css | 0 .../codemirror}/theme/neat.css | 0 .../codemirror}/theme/neo.css | 0 .../codemirror}/theme/night.css | 0 .../codemirror}/theme/panda-syntax.css | 0 .../codemirror}/theme/paraiso-dark.css | 0 .../codemirror}/theme/paraiso-light.css | 0 .../codemirror}/theme/pastel-on-dark.css | 0 .../codemirror}/theme/railscasts.css | 0 .../codemirror}/theme/rubyblue.css | 0 .../codemirror}/theme/seti.css | 0 .../codemirror}/theme/solarized.css | 0 .../codemirror}/theme/the-matrix.css | 0 .../theme/tomorrow-night-bright.css | 0 .../theme/tomorrow-night-eighties.css | 0 .../codemirror}/theme/ttcn.css | 0 .../codemirror}/theme/twilight.css | 0 .../codemirror}/theme/vibrant-ink.css | 0 .../codemirror}/theme/xq-dark.css | 0 .../codemirror}/theme/xq-light.css | 0 .../codemirror}/theme/yeti.css | 0 .../codemirror}/theme/zenburn.css | 0 {csslint => vendor/csslint}/WARNING.txt | 0 {csslint => vendor/csslint}/csslint-worker.js | 0 111 files changed, 1524 insertions(+), 1129 deletions(-) rename background.js => background/background.js (100%) rename messaging.js => background/messaging.js (96%) rename storage.js => background/storage.js (100%) rename update.js => background/update.js (100%) delete mode 100644 backup/fileSaveLoad.js rename apply.js => content/apply.js (100%) rename install.js => content/install.js (96%) rename edit.js => edit/edit.js (100%) rename options/index.html => index.html (100%) rename dom.js => js/dom.js (100%) rename localization.js => js/localization.js (100%) rename prefs.js => js/prefs.js (100%) create mode 100644 manage/fileSaveLoad.js rename manage.css => manage/manage.css (100%) rename manage.js => manage/manage.js (100%) rename popup.css => popup/popup.css (100%) rename popup.js => popup/popup.js (100%) rename pull_locales.rb => tools/pull_locales.rb (100%) rename pull_locales.sh => tools/pull_locales.sh (100%) mode change 100755 => 100644 rename pull_locales_postprocess.py => tools/pull_locales_postprocess.py (100%) rename {beautify => vendor-overwrites/beautify}/beautify-css-mod.js (100%) rename {beautify => vendor-overwrites/beautify}/beautify-css.js (100%) rename {codemirror-overwrites => vendor-overwrites/codemirror}/addon/lint/css-lint.js (100%) rename {codemirror-overwrites => vendor-overwrites/codemirror}/addon/search/match-highlighter.js (100%) rename {codemirror => vendor/codemirror}/LICENSE (100%) rename {codemirror => vendor/codemirror}/addon/comment/comment.js (100%) rename {codemirror => vendor/codemirror}/addon/dialog/dialog.css (100%) rename {codemirror => vendor/codemirror}/addon/dialog/dialog.js (100%) rename {codemirror => vendor/codemirror}/addon/edit/matchbrackets.js (100%) rename {codemirror => vendor/codemirror}/addon/fold/brace-fold.js (100%) rename {codemirror => vendor/codemirror}/addon/fold/comment-fold.js (100%) rename {codemirror => vendor/codemirror}/addon/fold/foldcode.js (100%) rename {codemirror => vendor/codemirror}/addon/fold/foldgutter.css (100%) rename {codemirror => vendor/codemirror}/addon/fold/foldgutter.js (100%) rename {codemirror => vendor/codemirror}/addon/hint/css-hint.js (100%) rename {codemirror => vendor/codemirror}/addon/hint/show-hint.css (100%) rename {codemirror => vendor/codemirror}/addon/hint/show-hint.js (100%) rename {codemirror => vendor/codemirror}/addon/lint/css-lint.js (100%) rename {codemirror => vendor/codemirror}/addon/lint/lint.css (100%) rename {codemirror => vendor/codemirror}/addon/lint/lint.js (100%) rename {codemirror => vendor/codemirror}/addon/scroll/annotatescrollbar.js (100%) rename {codemirror => vendor/codemirror}/addon/search/matchesonscrollbar.css (100%) rename {codemirror => vendor/codemirror}/addon/search/matchesonscrollbar.js (100%) rename {codemirror => vendor/codemirror}/addon/search/search.js (100%) rename {codemirror => vendor/codemirror}/addon/search/searchcursor.js (100%) rename {codemirror => vendor/codemirror}/addon/selection/active-line.js (100%) rename {codemirror => vendor/codemirror}/keymap/emacs.js (100%) rename {codemirror => vendor/codemirror}/keymap/sublime.js (100%) rename {codemirror => vendor/codemirror}/keymap/vim.js (100%) rename {codemirror => vendor/codemirror}/lib/codemirror.css (100%) rename {codemirror => vendor/codemirror}/lib/codemirror.js (100%) rename {codemirror => vendor/codemirror}/mode/css/css.js (100%) rename {codemirror => vendor/codemirror}/mode/css/gss.html (100%) rename {codemirror => vendor/codemirror}/mode/css/gss_test.js (100%) rename {codemirror => vendor/codemirror}/mode/css/index.html (100%) rename {codemirror => vendor/codemirror}/mode/css/less.html (100%) rename {codemirror => vendor/codemirror}/mode/css/less_test.js (100%) rename {codemirror => vendor/codemirror}/mode/css/scss.html (100%) rename {codemirror => vendor/codemirror}/mode/css/scss_test.js (100%) rename {codemirror => vendor/codemirror}/mode/css/test.js (100%) rename {codemirror => vendor/codemirror}/theme/3024-day.css (100%) rename {codemirror => vendor/codemirror}/theme/3024-night.css (100%) rename {codemirror => vendor/codemirror}/theme/abcdef.css (100%) rename {codemirror => vendor/codemirror}/theme/ambiance-mobile.css (100%) rename {codemirror => vendor/codemirror}/theme/ambiance.css (100%) rename {codemirror => vendor/codemirror}/theme/base16-dark.css (100%) rename {codemirror => vendor/codemirror}/theme/base16-light.css (100%) rename {codemirror => vendor/codemirror}/theme/bespin.css (100%) rename {codemirror => vendor/codemirror}/theme/blackboard.css (100%) rename {codemirror => vendor/codemirror}/theme/cobalt.css (100%) rename {codemirror => vendor/codemirror}/theme/colorforth.css (100%) rename {codemirror => vendor/codemirror}/theme/dracula.css (100%) rename {codemirror => vendor/codemirror}/theme/duotone-dark.css (100%) rename {codemirror => vendor/codemirror}/theme/duotone-light.css (100%) rename {codemirror => vendor/codemirror}/theme/eclipse.css (100%) rename {codemirror => vendor/codemirror}/theme/elegant.css (100%) rename {codemirror => vendor/codemirror}/theme/erlang-dark.css (100%) rename {codemirror => vendor/codemirror}/theme/hopscotch.css (100%) rename {codemirror => vendor/codemirror}/theme/icecoder.css (100%) rename {codemirror => vendor/codemirror}/theme/isotope.css (100%) rename {codemirror => vendor/codemirror}/theme/lesser-dark.css (100%) rename {codemirror => vendor/codemirror}/theme/liquibyte.css (100%) rename {codemirror => vendor/codemirror}/theme/material.css (100%) rename {codemirror => vendor/codemirror}/theme/mbo.css (100%) rename {codemirror => vendor/codemirror}/theme/mdn-like.css (100%) rename {codemirror => vendor/codemirror}/theme/midnight.css (100%) rename {codemirror => vendor/codemirror}/theme/monokai.css (100%) rename {codemirror => vendor/codemirror}/theme/neat.css (100%) rename {codemirror => vendor/codemirror}/theme/neo.css (100%) rename {codemirror => vendor/codemirror}/theme/night.css (100%) rename {codemirror => vendor/codemirror}/theme/panda-syntax.css (100%) rename {codemirror => vendor/codemirror}/theme/paraiso-dark.css (100%) rename {codemirror => vendor/codemirror}/theme/paraiso-light.css (100%) rename {codemirror => vendor/codemirror}/theme/pastel-on-dark.css (100%) rename {codemirror => vendor/codemirror}/theme/railscasts.css (100%) rename {codemirror => vendor/codemirror}/theme/rubyblue.css (100%) rename {codemirror => vendor/codemirror}/theme/seti.css (100%) rename {codemirror => vendor/codemirror}/theme/solarized.css (100%) rename {codemirror => vendor/codemirror}/theme/the-matrix.css (100%) rename {codemirror => vendor/codemirror}/theme/tomorrow-night-bright.css (100%) rename {codemirror => vendor/codemirror}/theme/tomorrow-night-eighties.css (100%) rename {codemirror => vendor/codemirror}/theme/ttcn.css (100%) rename {codemirror => vendor/codemirror}/theme/twilight.css (100%) rename {codemirror => vendor/codemirror}/theme/vibrant-ink.css (100%) rename {codemirror => vendor/codemirror}/theme/xq-dark.css (100%) rename {codemirror => vendor/codemirror}/theme/xq-light.css (100%) rename {codemirror => vendor/codemirror}/theme/yeti.css (100%) rename {codemirror => vendor/codemirror}/theme/zenburn.css (100%) rename {csslint => vendor/csslint}/WARNING.txt (100%) rename {csslint => vendor/csslint}/csslint-worker.js (100%) diff --git a/.eslintignore b/.eslintignore index 325be71d..a710e413 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,2 @@ -beautify/ -codemirror/ -csslint/ +vendor/ +vendor-overwrites/ diff --git a/background.js b/background/background.js similarity index 100% rename from background.js rename to background/background.js diff --git a/messaging.js b/background/messaging.js similarity index 96% rename from messaging.js rename to background/messaging.js index 42f8b593..ac529c92 100644 --- a/messaging.js +++ b/background/messaging.js @@ -1,371 +1,371 @@ -/* global BG: true, onRuntimeMessage, applyOnMessage, handleUpdate, handleDelete */ -'use strict'; - -// keep message channel open for sendResponse in chrome.runtime.onMessage listener -const KEEP_CHANNEL_OPEN = true; - -const FIREFOX = /Firefox/.test(navigator.userAgent); -const OPERA = /OPR/.test(navigator.userAgent); - -const URLS = { - ownOrigin: chrome.runtime.getURL(''), - - optionsUI: [ - chrome.runtime.getURL('options/index.html'), - 'chrome://extensions/?options=' + chrome.runtime.id, - ], - - configureCommands: - OPERA ? 'opera://settings/configureCommands' - : 'chrome://extensions/configureCommands', - - // CWS cannot be scripted in chromium, see ChromeExtensionsClient::IsScriptableURL - // https://cs.chromium.org/chromium/src/chrome/common/extensions/chrome_extensions_client.cc - chromeWebStore: FIREFOX ? 'https://addons.mozilla.org/' : ( - OPERA ? 'https://addons.opera.com/' : 'https://chrome.google.com/webstore/' - ), - - supported: new RegExp( - '^(file|ftps?|http)://|' + - `^https://${FIREFOX ? '(?!addons\\.mozilla\\.org)' : ( - OPERA ? '(?!addons\\.opera\\.com)' : '(?!chrome\\.google\\.com/webstore)' - )}|` + - '^' + chrome.runtime.getURL('')), -}; - -let BG = chrome.extension.getBackgroundPage(); - -if (!BG || BG != window) { - document.documentElement.classList.toggle('firefox', FIREFOX); - document.documentElement.classList.toggle('opera', OPERA); - // TODO: remove once our manifest's minimum_chrome_version is 50+ - // Chrome 49 doesn't report own extension pages in webNavigation apparently - if (navigator.userAgent.includes('Chrome/49.')) { - getActiveTab().then(BG.updateIcon); - } -} - -function notifyAllTabs(msg) { - const originalMessage = msg; - if (msg.method == 'styleUpdated' || msg.method == 'styleAdded') { - // apply/popup/manage use only meta for these two methods, - // 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 - msg = Object.assign({}, msg, { - style: getStyleWithNoCode(msg.style) - }); - } - const affectsAll = !msg.affects || msg.affects.all; - const affectsOwnOriginOnly = !affectsAll && (msg.affects.editor || msg.affects.manager); - const affectsTabs = affectsAll || affectsOwnOriginOnly; - const affectsIcon = affectsAll || msg.affects.icon; - const affectsPopup = affectsAll || msg.affects.popup; - const affectsSelf = affectsPopup || msg.prefs; - if (affectsTabs || affectsIcon) { - const notifyTab = tab => { - // own pages will be notified via runtime.sendMessage later - if ((affectsTabs || URLS.optionsUI.includes(tab.url)) - && !(affectsSelf && tab.url.startsWith(URLS.ownOrigin)) - // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF - && (!FIREFOX || tab.width)) { - chrome.tabs.sendMessage(tab.id, msg); - } - if (affectsIcon && BG) { - BG.updateIcon(tab); - } - }; - // list all tabs including chrome-extension:// which can be ours - Promise.all([ - queryTabs(affectsOwnOriginOnly ? {url: URLS.ownOrigin + '*'} : {}), - getActiveTab(), - ]).then(([tabs, activeTab]) => { - const activeTabId = activeTab && activeTab.id; - for (const tab of tabs) { - invokeOrPostpone(tab.id === activeTabId, notifyTab, tab); - } - }); - } - // notify self: the message no longer is sent to the origin in new Chrome - if (typeof onRuntimeMessage != 'undefined') { - onRuntimeMessage(originalMessage); - } - // notify apply.js on own pages - if (typeof applyOnMessage != 'undefined') { - applyOnMessage(originalMessage); - } - // notify background page and all open popups - if (affectsSelf) { - chrome.runtime.sendMessage(msg); - } -} - - -function queryTabs(options = {}) { - return new Promise(resolve => - chrome.tabs.query(options, tabs => - resolve(tabs))); -} - - -function getTab(id) { - return new Promise(resolve => - chrome.tabs.get(id, tab => - !chrome.runtime.lastError && resolve(tab))); -} - - -function getOwnTab() { - return new Promise(resolve => - chrome.tabs.getCurrent(tab => resolve(tab))); -} - - -function getActiveTab() { - return queryTabs({currentWindow: true, active: true}) - .then(tabs => tabs[0]); -} - - -function getActiveTabRealURL() { - return getActiveTab() - .then(getTabRealURL); -} - - -function getTabRealURL(tab) { - return new Promise(resolve => { - if (tab.url != 'chrome://newtab/') { - resolve(tab.url); - } else { - chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => { - resolve(frame && frame.url || ''); - }); - } - }); -} - - -// opens a tab or activates the already opened one, -// reuses the New Tab page if it's focused now -function openURL({url, currentWindow = true}) { - if (!url.includes('://')) { - url = chrome.runtime.getURL(url); - } - return new Promise(resolve => { - // [some] chromium forks don't handle their fake branded protocols - url = url.replace(/^(opera|vivaldi)/, 'chrome'); - // FF doesn't handle moz-extension:// URLs (bug) - // API doesn't handle the hash-fragment part - const urlQuery = url.startsWith('moz-extension') ? undefined : url.replace(/#.*/, ''); - queryTabs({url: urlQuery, currentWindow}).then(tabs => { - for (const tab of tabs) { - if (tab.url == url) { - activateTab(tab).then(resolve); - return; - } - } - getActiveTab().then(tab => { - if (tab && tab.url == 'chrome://newtab/' - // prevent redirecting incognito NTP to a chrome URL as it crashes Chrome - && (!url.startsWith('chrome') || !tab.incognito)) { - chrome.tabs.update({url}, resolve); - } else { - chrome.tabs.create(tab && !FIREFOX ? {url, openerTabId: tab.id} : {url}, resolve); - } - }); - }); - }); -} - - -function activateTab(tab) { - return Promise.all([ - new Promise(resolve => { - chrome.tabs.update(tab.id, {active: true}, resolve); - }), - new Promise(resolve => { - chrome.windows.update(tab.windowId, {focused: true}, resolve); - }), - ]); -} - - -function stringAsRegExp(s, flags) { - return new RegExp(s.replace(/[{}()[\]/\\.+?^$:=*!|]/g, '\\$&'), flags); -} - - -function ignoreChromeError() { - chrome.runtime.lastError; // eslint-disable-line no-unused-expressions -} - - -function getStyleWithNoCode(style) { - const stripped = Object.assign({}, style, {sections: []}); - for (const section of style.sections) { - stripped.sections.push(Object.assign({}, section, {code: null})); - } - return stripped; -} - - -// 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 -// Update: might get fixed in V8 TurboFan in the future -function tryCatch(func, ...args) { - try { - return func(...args); - } catch (e) {} -} - - -function tryRegExp(regexp) { - try { - return new RegExp(regexp); - } catch (e) {} -} - - -function tryJSONparse(jsonString) { - try { - return JSON.parse(jsonString); - } catch (e) {} -} - - -const debounce = Object.assign((fn, delay, ...args) => { - clearTimeout(debounce.timers.get(fn)); - debounce.timers.set(fn, setTimeout(debounce.run, delay, fn, ...args)); -}, { - timers: new Map(), - run(fn, ...args) { - debounce.timers.delete(fn); - fn(...args); - }, - unregister(fn) { - clearTimeout(debounce.timers.get(fn)); - debounce.timers.delete(fn); - }, -}); - - -function deepCopy(obj) { - return obj !== null && obj !== undefined && typeof obj == 'object' - ? deepMerge(typeof obj.slice == 'function' ? [] : {}, obj) - : obj; -} - - -function deepMerge(target, ...args) { - const isArray = typeof target.slice == 'function'; - for (const obj of args) { - if (isArray && obj !== null && obj !== undefined) { - for (const element of obj) { - target.push(deepCopy(element)); - } - continue; - } - for (const k in obj) { - const value = obj[k]; - if (k in target && typeof value == 'object' && value !== null) { - deepMerge(target[k], value); - } else { - target[k] = deepCopy(value); - } - } - } - return target; -} - - -function sessionStorageHash(name) { - return { - name, - value: tryCatch(JSON.parse, sessionStorage[name]) || {}, - set(k, v) { - this.value[k] = v; - this.updateStorage(); - }, - unset(k) { - delete this.value[k]; - this.updateStorage(); - }, - updateStorage() { - sessionStorage[this.name] = JSON.stringify(this.value); - } - }; -} - - -function onBackgroundReady() { - return BG && BG.getStyles ? Promise.resolve() : new Promise(function ping(resolve) { - chrome.runtime.sendMessage({method: 'healthCheck'}, health => { - if (health !== undefined) { - BG = chrome.extension.getBackgroundPage(); - resolve(); - } else { - setTimeout(ping, 0, resolve); - } - }); - }); -} - - -// in case Chrome haven't yet loaded the bg page and displays our page like edit/manage -function getStylesSafe(options) { - return onBackgroundReady() - .then(() => BG.getStyles(options)); -} - - -function saveStyleSafe(style) { - return onBackgroundReady() - .then(() => BG.saveStyle(BG.deepCopy(style))) - .then(savedStyle => { - if (style.notify === false) { - handleUpdate(savedStyle, style); - } - return savedStyle; - }); -} - - -function deleteStyleSafe({id, notify = true} = {}) { - return onBackgroundReady() - .then(() => BG.deleteStyle({id, notify})) - .then(() => { - if (!notify) { - handleDelete(id); - } - return id; - }); -} - - -function download(url) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.timeout = 10e3; - xhr.onloadend = () => (xhr.status == 200 - ? resolve(xhr.responseText) - : reject(xhr.status)); - const [mainUrl, query] = url.split('?'); - xhr.open(query ? 'POST' : 'GET', mainUrl, true); - xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhr.send(query); - }); -} - - -function doTimeout(ms = 0, ...args) { - return ms > 0 - ? () => new Promise(resolve => setTimeout(resolve, ms, ...args)) - : new Promise(resolve => setTimeout(resolve, 0, ...args)); -} - - -function invokeOrPostpone(isInvoke, fn, ...args) { - return isInvoke - ? fn(...args) - : setTimeout(invokeOrPostpone, 0, true, fn, ...args); -} +/* global BG: true, onRuntimeMessage, applyOnMessage, handleUpdate, handleDelete */ +'use strict'; + +// keep message channel open for sendResponse in chrome.runtime.onMessage listener +const KEEP_CHANNEL_OPEN = true; + +const FIREFOX = /Firefox/.test(navigator.userAgent); +const OPERA = /OPR/.test(navigator.userAgent); + +const URLS = { + ownOrigin: chrome.runtime.getURL(''), + + optionsUI: [ + chrome.runtime.getURL('options/index.html'), + 'chrome://extensions/?options=' + chrome.runtime.id, + ], + + configureCommands: + OPERA ? 'opera://settings/configureCommands' + : 'chrome://extensions/configureCommands', + + // CWS cannot be scripted in chromium, see ChromeExtensionsClient::IsScriptableURL + // https://cs.chromium.org/chromium/src/chrome/common/extensions/chrome_extensions_client.cc + chromeWebStore: FIREFOX ? 'https://addons.mozilla.org/' : ( + OPERA ? 'https://addons.opera.com/' : 'https://chrome.google.com/webstore/' + ), + + supported: new RegExp( + '^(file|ftps?|http)://|' + + `^https://${FIREFOX ? '(?!addons\\.mozilla\\.org)' : ( + OPERA ? '(?!addons\\.opera\\.com)' : '(?!chrome\\.google\\.com/webstore)' + )}|` + + '^' + chrome.runtime.getURL('')), +}; + +let BG = chrome.extension.getBackgroundPage(); + +if (!BG || BG != window) { + document.documentElement.classList.toggle('firefox', FIREFOX); + document.documentElement.classList.toggle('opera', OPERA); + // TODO: remove once our manifest's minimum_chrome_version is 50+ + // Chrome 49 doesn't report own extension pages in webNavigation apparently + if (navigator.userAgent.includes('Chrome/49.')) { + getActiveTab().then(BG.updateIcon); + } +} + +function notifyAllTabs(msg) { + const originalMessage = msg; + if (msg.method == 'styleUpdated' || msg.method == 'styleAdded') { + // apply/popup/manage use only meta for these two methods, + // 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 + msg = Object.assign({}, msg, { + style: getStyleWithNoCode(msg.style) + }); + } + const affectsAll = !msg.affects || msg.affects.all; + const affectsOwnOriginOnly = !affectsAll && (msg.affects.editor || msg.affects.manager); + const affectsTabs = affectsAll || affectsOwnOriginOnly; + const affectsIcon = affectsAll || msg.affects.icon; + const affectsPopup = affectsAll || msg.affects.popup; + const affectsSelf = affectsPopup || msg.prefs; + if (affectsTabs || affectsIcon) { + const notifyTab = tab => { + // own pages will be notified via runtime.sendMessage later + if ((affectsTabs || URLS.optionsUI.includes(tab.url)) + && !(affectsSelf && tab.url.startsWith(URLS.ownOrigin)) + // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF + && (!FIREFOX || tab.width)) { + chrome.tabs.sendMessage(tab.id, msg); + } + if (affectsIcon && BG) { + BG.updateIcon(tab); + } + }; + // list all tabs including chrome-extension:// which can be ours + Promise.all([ + queryTabs(affectsOwnOriginOnly ? {url: URLS.ownOrigin + '*'} : {}), + getActiveTab(), + ]).then(([tabs, activeTab]) => { + const activeTabId = activeTab && activeTab.id; + for (const tab of tabs) { + invokeOrPostpone(tab.id === activeTabId, notifyTab, tab); + } + }); + } + // notify self: the message no longer is sent to the origin in new Chrome + if (typeof onRuntimeMessage != 'undefined') { + onRuntimeMessage(originalMessage); + } + // notify apply.js on own pages + if (typeof applyOnMessage != 'undefined') { + applyOnMessage(originalMessage); + } + // notify background page and all open popups + if (affectsSelf) { + chrome.runtime.sendMessage(msg); + } +} + + +function queryTabs(options = {}) { + return new Promise(resolve => + chrome.tabs.query(options, tabs => + resolve(tabs))); +} + + +function getTab(id) { + return new Promise(resolve => + chrome.tabs.get(id, tab => + !chrome.runtime.lastError && resolve(tab))); +} + + +function getOwnTab() { + return new Promise(resolve => + chrome.tabs.getCurrent(tab => resolve(tab))); +} + + +function getActiveTab() { + return queryTabs({currentWindow: true, active: true}) + .then(tabs => tabs[0]); +} + + +function getActiveTabRealURL() { + return getActiveTab() + .then(getTabRealURL); +} + + +function getTabRealURL(tab) { + return new Promise(resolve => { + if (tab.url != 'chrome://newtab/') { + resolve(tab.url); + } else { + chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => { + resolve(frame && frame.url || ''); + }); + } + }); +} + + +// opens a tab or activates the already opened one, +// reuses the New Tab page if it's focused now +function openURL({url, currentWindow = true}) { + if (!url.includes('://')) { + url = chrome.runtime.getURL(url); + } + return new Promise(resolve => { + // [some] chromium forks don't handle their fake branded protocols + url = url.replace(/^(opera|vivaldi)/, 'chrome'); + // FF doesn't handle moz-extension:// URLs (bug) + // API doesn't handle the hash-fragment part + const urlQuery = url.startsWith('moz-extension') ? undefined : url.replace(/#.*/, ''); + queryTabs({url: urlQuery, currentWindow}).then(tabs => { + for (const tab of tabs) { + if (tab.url == url) { + activateTab(tab).then(resolve); + return; + } + } + getActiveTab().then(tab => { + if (tab && tab.url == 'chrome://newtab/' + // prevent redirecting incognito NTP to a chrome URL as it crashes Chrome + && (!url.startsWith('chrome') || !tab.incognito)) { + chrome.tabs.update({url}, resolve); + } else { + chrome.tabs.create(tab && !FIREFOX ? {url, openerTabId: tab.id} : {url}, resolve); + } + }); + }); + }); +} + + +function activateTab(tab) { + return Promise.all([ + new Promise(resolve => { + chrome.tabs.update(tab.id, {active: true}, resolve); + }), + new Promise(resolve => { + chrome.windows.update(tab.windowId, {focused: true}, resolve); + }), + ]); +} + + +function stringAsRegExp(s, flags) { + return new RegExp(s.replace(/[{}()[\]/\\.+?^$:=*!|]/g, '\\$&'), flags); +} + + +function ignoreChromeError() { + chrome.runtime.lastError; // eslint-disable-line no-unused-expressions +} + + +function getStyleWithNoCode(style) { + const stripped = Object.assign({}, style, {sections: []}); + for (const section of style.sections) { + stripped.sections.push(Object.assign({}, section, {code: null})); + } + return stripped; +} + + +// 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 +// Update: might get fixed in V8 TurboFan in the future +function tryCatch(func, ...args) { + try { + return func(...args); + } catch (e) {} +} + + +function tryRegExp(regexp) { + try { + return new RegExp(regexp); + } catch (e) {} +} + + +function tryJSONparse(jsonString) { + try { + return JSON.parse(jsonString); + } catch (e) {} +} + + +const debounce = Object.assign((fn, delay, ...args) => { + clearTimeout(debounce.timers.get(fn)); + debounce.timers.set(fn, setTimeout(debounce.run, delay, fn, ...args)); +}, { + timers: new Map(), + run(fn, ...args) { + debounce.timers.delete(fn); + fn(...args); + }, + unregister(fn) { + clearTimeout(debounce.timers.get(fn)); + debounce.timers.delete(fn); + }, +}); + + +function deepCopy(obj) { + return obj !== null && obj !== undefined && typeof obj == 'object' + ? deepMerge(typeof obj.slice == 'function' ? [] : {}, obj) + : obj; +} + + +function deepMerge(target, ...args) { + const isArray = typeof target.slice == 'function'; + for (const obj of args) { + if (isArray && obj !== null && obj !== undefined) { + for (const element of obj) { + target.push(deepCopy(element)); + } + continue; + } + for (const k in obj) { + const value = obj[k]; + if (k in target && typeof value == 'object' && value !== null) { + deepMerge(target[k], value); + } else { + target[k] = deepCopy(value); + } + } + } + return target; +} + + +function sessionStorageHash(name) { + return { + name, + value: tryCatch(JSON.parse, sessionStorage[name]) || {}, + set(k, v) { + this.value[k] = v; + this.updateStorage(); + }, + unset(k) { + delete this.value[k]; + this.updateStorage(); + }, + updateStorage() { + sessionStorage[this.name] = JSON.stringify(this.value); + } + }; +} + + +function onBackgroundReady() { + return BG && BG.getStyles ? Promise.resolve() : new Promise(function ping(resolve) { + chrome.runtime.sendMessage({method: 'healthCheck'}, health => { + if (health !== undefined) { + BG = chrome.extension.getBackgroundPage(); + resolve(); + } else { + setTimeout(ping, 0, resolve); + } + }); + }); +} + + +// in case Chrome haven't yet loaded the bg page and displays our page like edit/manage +function getStylesSafe(options) { + return onBackgroundReady() + .then(() => BG.getStyles(options)); +} + + +function saveStyleSafe(style) { + return onBackgroundReady() + .then(() => BG.saveStyle(BG.deepCopy(style))) + .then(savedStyle => { + if (style.notify === false) { + handleUpdate(savedStyle, style); + } + return savedStyle; + }); +} + + +function deleteStyleSafe({id, notify = true} = {}) { + return onBackgroundReady() + .then(() => BG.deleteStyle({id, notify})) + .then(() => { + if (!notify) { + handleDelete(id); + } + return id; + }); +} + + +function download(url) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.timeout = 10e3; + xhr.onloadend = () => (xhr.status == 200 + ? resolve(xhr.responseText) + : reject(xhr.status)); + const [mainUrl, query] = url.split('?'); + xhr.open(query ? 'POST' : 'GET', mainUrl, true); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhr.send(query); + }); +} + + +function doTimeout(ms = 0, ...args) { + return ms > 0 + ? () => new Promise(resolve => setTimeout(resolve, ms, ...args)) + : new Promise(resolve => setTimeout(resolve, 0, ...args)); +} + + +function invokeOrPostpone(isInvoke, fn, ...args) { + return isInvoke + ? fn(...args) + : setTimeout(invokeOrPostpone, 0, true, fn, ...args); +} diff --git a/storage.js b/background/storage.js similarity index 100% rename from storage.js rename to background/storage.js diff --git a/update.js b/background/update.js similarity index 100% rename from update.js rename to background/update.js diff --git a/backup/fileSaveLoad.js b/backup/fileSaveLoad.js deleted file mode 100644 index 7626a1b2..00000000 --- a/backup/fileSaveLoad.js +++ /dev/null @@ -1,395 +0,0 @@ -/* global messageBox, handleUpdate, applyOnMessage */ -'use strict'; - -const STYLISH_DUMP_FILE_EXT = '.txt'; -const STYLUS_BACKUP_FILE_EXT = '.json'; - - -function importFromFile({fileTypeFilter, file} = {}) { - return new Promise(resolve => { - const fileInput = document.createElement('input'); - if (file) { - readFile(); - return; - } - fileInput.style.display = 'none'; - fileInput.type = 'file'; - fileInput.accept = fileTypeFilter || STYLISH_DUMP_FILE_EXT; - fileInput.acceptCharset = 'utf-8'; - - document.body.appendChild(fileInput); - fileInput.initialValue = fileInput.value; - fileInput.onchange = readFile; - fileInput.click(); - - function readFile() { - if (file || fileInput.value !== fileInput.initialValue) { - file = file || fileInput.files[0]; - if (file.size > 100e6) { - console.warn("100MB backup? I don't believe you."); - importFromString('').then(resolve); - return; - } - document.body.style.cursor = 'wait'; - const fReader = new FileReader(); - fReader.onloadend = event => { - fileInput.remove(); - importFromString(event.target.result).then(numStyles => { - document.body.style.cursor = ''; - resolve(numStyles); - }); - }; - fReader.readAsText(file, 'utf-8'); - } - } - }); -} - - -function importFromString(jsonString) { - if (!BG) { - onBackgroundReady().then(() => importFromString(jsonString)); - return; - } - // create objects in background context - const json = BG.tryJSONparse(jsonString) || []; - if (typeof json.slice != 'function') { - json.length = 0; - } - const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []); - const oldStylesByName = json.length && new Map( - oldStyles.map(style => [style.name.trim(), style])); - - const stats = { - added: {names: [], ids: [], legend: 'importReportLegendAdded'}, - unchanged: {names: [], ids: [], legend: 'importReportLegendIdentical'}, - metaAndCode: {names: [], ids: [], legend: 'importReportLegendUpdatedBoth'}, - metaOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedMeta'}, - codeOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedCode'}, - invalid: {names: [], legend: 'importReportLegendInvalid'}, - }; - - let index = 0; - let lastRenderTime = performance.now(); - const renderQueue = []; - const RENDER_NAP_TIME_MAX = 1000; // ms - const RENDER_QUEUE_MAX = 50; // number of styles - const SAVE_OPTIONS = {reason: 'import', notify: false}; - - return new Promise(proceed); - - function proceed(resolve) { - while (index < json.length) { - const item = json[index++]; - const info = analyze(item); - if (info) { - // using saveStyle directly since json was parsed in background page context - return BG.saveStyle(Object.assign(item, SAVE_OPTIONS)) - .then(style => account({style, info, resolve})); - } - } - renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); - renderQueue.length = 0; - done(resolve); - } - - function analyze(item) { - if (!item || !item.name || !item.name.trim() || typeof item != 'object' - || (item.sections && typeof item.sections.slice != 'function')) { - stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`); - return; - } - item.name = item.name.trim(); - const byId = BG.cachedStyles.byId.get(item.id); - const byName = oldStylesByName.get(item.name); - oldStylesByName.delete(item.name); - let oldStyle; - if (byId) { - if (sameStyle(byId, item)) { - oldStyle = byId; - } else { - item.id = null; - } - } - if (!oldStyle && byName) { - item.id = byName.id; - oldStyle = byName; - } - const oldStyleKeys = oldStyle && Object.keys(oldStyle); - const metaEqual = oldStyleKeys && - oldStyleKeys.length == Object.keys(item).length && - oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]); - const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item); - if (metaEqual && codeEqual) { - stats.unchanged.names.push(oldStyle.name); - stats.unchanged.ids.push(oldStyle.id); - return; - } - return {oldStyle, metaEqual, codeEqual}; - } - - function sameStyle(oldStyle, newStyle) { - return oldStyle.name.trim() === newStyle.name.trim() || - ['updateUrl', 'originalMd5', 'originalDigest'] - .some(field => oldStyle[field] && oldStyle[field] == newStyle[field]); - } - - function account({style, info, resolve}) { - renderQueue.push(style); - if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX - || renderQueue.length > RENDER_QUEUE_MAX) { - renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); - setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id)); - renderQueue.length = 0; - lastRenderTime = performance.now(); - } - setTimeout(proceed, 0, resolve); - const {oldStyle, metaEqual, codeEqual} = info; - if (!oldStyle) { - stats.added.names.push(style.name); - stats.added.ids.push(style.id); - return; - } - if (!metaEqual && !codeEqual) { - stats.metaAndCode.names.push(reportNameChange(oldStyle, style)); - stats.metaAndCode.ids.push(style.id); - return; - } - if (!codeEqual) { - stats.codeOnly.names.push(style.name); - stats.codeOnly.ids.push(style.id); - return; - } - stats.metaOnly.names.push(reportNameChange(oldStyle, style)); - stats.metaOnly.ids.push(style.id); - } - - function done(resolve) { - const numChanged = stats.metaAndCode.names.length + - stats.metaOnly.names.length + - stats.codeOnly.names.length + - stats.added.names.length; - Promise.resolve(numChanged && refreshAllTabs()).then(() => { - const report = Object.keys(stats) - .filter(kind => stats[kind].names.length) - .map(kind => { - const {ids, names, legend} = stats[kind]; - const listItemsWithId = (name, i) => - $element({dataset: {id: ids[i]}, textContent: name}); - const listItems = name => - $element({textContent: name}); - const block = - $element({tag: 'details', dataset: {id: kind}, appendChild: [ - $element({tag: 'summary', appendChild: - $element({tag: 'b', textContent: names.length + ' ' + t(legend)}) - }), - $element({tag: 'small', appendChild: - names.map(ids ? listItemsWithId : listItems) - }), - ]}); - return block; - }); - scrollTo(0, 0); - messageBox({ - title: t('importReportTitle'), - contents: report.length ? report : t('importReportUnchanged'), - buttons: [t('confirmOK'), numChanged && t('undo')], - onshow: bindClick, - }).then(({button, enter, esc}) => { - if (button == 1) { - undo(); - } - }); - resolve(numChanged); - }); - } - - function undo() { - const oldStylesById = new Map(oldStyles.map(style => [style.id, style])); - const newIds = [ - ...stats.metaAndCode.ids, - ...stats.metaOnly.ids, - ...stats.codeOnly.ids, - ...stats.added.ids, - ]; - let resolve; - index = 0; - return new Promise(resolve_ => { - resolve = resolve_; - undoNextId(); - }).then(refreshAllTabs) - .then(() => messageBox({ - title: t('importReportUndoneTitle'), - contents: newIds.length + ' ' + t('importReportUndone'), - buttons: [t('confirmOK')], - })); - function undoNextId() { - if (index == newIds.length) { - resolve(); - return; - } - const id = newIds[index++]; - deleteStyleSafe({id, notify: false}).then(id => { - const oldStyle = oldStylesById.get(id); - if (oldStyle) { - saveStyleSafe(Object.assign(oldStyle, SAVE_OPTIONS)) - .then(undoNextId); - } else { - undoNextId(); - } - }); - } - } - - function bindClick(box) { - const highlightElement = event => { - const styleElement = $('#style-' + event.target.dataset.id); - if (styleElement) { - scrollElementIntoView(styleElement); - animateElement(styleElement); - } - }; - for (const block of $$('details')) { - if (block.dataset.id != 'invalid') { - block.style.cursor = 'pointer'; - block.onclick = highlightElement; - } - } - } - - function limitString(s, limit = 100) { - return s.length <= limit ? s : s.substr(0, limit) + '...'; - } - - function reportNameChange(oldStyle, newStyle) { - return newStyle.name != oldStyle.name - ? oldStyle.name + ' —> ' + newStyle.name - : oldStyle.name; - } - - function refreshAllTabs() { - return Promise.all([ - getActiveTab(), - getOwnTab(), - ]).then(([activeTab, ownTab]) => new Promise(resolve => { - // list all tabs including chrome-extension:// which can be ours - queryTabs().then(tabs => { - const lastTab = tabs[tabs.length - 1]; - for (const tab of tabs) { - // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF - if (FIREFOX && !tab.width) { - if (tab == lastTab) { - resolve(); - } - continue; - } - getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => { - const message = {method: 'styleReplaceAll', styles}; - if (tab.id == ownTab.id) { - applyOnMessage(message); - } else { - invokeOrPostpone(tab.id == activeTab.id, - chrome.tabs.sendMessage, tab.id, message, ignoreChromeError); - } - setTimeout(BG.updateIcon, 0, tab, styles); - if (tab == lastTab) { - resolve(); - } - }); - } - }); - })); - } -} - - -$('#file-all-styles').onclick = () => { - getStylesSafe().then(styles => { - const text = JSON.stringify(styles, null, '\t'); - const url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text); - return url; - // for long URLs; https://github.com/schomery/stylus/issues/13#issuecomment-284582600 - }).then(fetch) - .then(res => res.blob()) - .then(blob => { - const objectURL = URL.createObjectURL(blob); - let link = $element({ - tag:'a', - href: objectURL, - type: 'application/json', - download: generateFileName(), - }); - // TODO: remove the fallback when FF multi-process bug is fixed - if (!FIREFOX) { - link.dispatchEvent(new MouseEvent('click')); - setTimeout(() => URL.revokeObjectURL(objectURL)); - } else { - const iframe = document.body.appendChild($element({ - tag: 'iframe', - style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'), - })); - doTimeout().then(() => { - link = iframe.contentDocument.importNode(link, true); - iframe.contentDocument.body.appendChild(link); - }) - .then(doTimeout) - .then(() => link.dispatchEvent(new MouseEvent('click'))) - .then(doTimeout(1000)) - .then(() => { - URL.revokeObjectURL(objectURL); - iframe.remove(); - }); - } - }); - - function generateFileName() { - const today = new Date(); - const dd = ('0' + today.getDate()).substr(-2); - const mm = ('0' + (today.getMonth() + 1)).substr(-2); - const yyyy = today.getFullYear(); - return `stylus-${yyyy}-${mm}-${dd}${STYLUS_BACKUP_FILE_EXT}`; - } -}; - - -$('#unfile-all-styles').onclick = () => { - importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT}); -}; - -Object.assign(document.body, { - ondragover(event) { - const hasFiles = event.dataTransfer.types.includes('Files'); - event.dataTransfer.dropEffect = hasFiles || event.target.type == 'search' ? 'copy' : 'none'; - this.classList.toggle('dropzone', hasFiles); - if (hasFiles) { - event.preventDefault(); - clearTimeout(this.fadeoutTimer); - this.classList.remove('fadeout'); - } - }, - ondragend(event) { - animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => { - this.style.animationDuration = ''; - }); - }, - ondragleave(event) { - try { - // in Firefox event.target could be XUL browser and hence there is no permission to access it - if (event.target === this) { - this.ondragend(); - } - } catch (e) { - this.ondragend(); - } - }, - ondrop(event) { - this.ondragend(); - if (event.dataTransfer.files.length) { - event.preventDefault(); - if ($('#onlyUpdates input').checked) { - $('#onlyUpdates input').click(); - } - importFromFile({file: event.dataTransfer.files[0]}); - } - }, -}); diff --git a/apply.js b/content/apply.js similarity index 100% rename from apply.js rename to content/apply.js diff --git a/install.js b/content/install.js similarity index 96% rename from install.js rename to content/install.js index 7ce385e3..f8bba479 100644 --- a/install.js +++ b/content/install.js @@ -1,360 +1,360 @@ -'use strict'; - -const CHROMIUM = /Chromium/.test(navigator.userAgent); // non-Windows Chromium -const FIREFOX = /Firefox/.test(navigator.userAgent); -const VIVALDI = /Vivaldi/.test(navigator.userAgent); -const OPERA = /OPR/.test(navigator.userAgent); - -document.addEventListener('stylishUpdate', onUpdateClicked); -document.addEventListener('stylishUpdateChrome', onUpdateClicked); -document.addEventListener('stylishUpdateOpera', onUpdateClicked); - -document.addEventListener('stylishInstall', onInstallClicked); -document.addEventListener('stylishInstallChrome', onInstallClicked); -document.addEventListener('stylishInstallOpera', onInstallClicked); - -chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { - // orphaned content script check - if (msg.method == 'ping') { - sendResponse(true); - } -}); - -// TODO: remove the following statement when USO is fixed -document.documentElement.appendChild(document.createElement('script')).text = '(' + - function() { - let settings; - document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) { - document.removeEventListener('stylusFixBuggyUSOsettings', _); - settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search); - }); - const originalResponseJson = Response.prototype.json; - Response.prototype.json = function(...args) { - return originalResponseJson.call(this, ...args).then(json => { - Response.prototype.json = originalResponseJson; - if (!settings || typeof ((json || {}).style_settings || {}).every != 'function') { - return json; - } - const images = new Map(); - for (const jsonSetting of json.style_settings) { - let value = settings.get('ik-' + jsonSetting.install_key); - if (!value - || !jsonSetting.style_setting_options - || !jsonSetting.style_setting_options[0]) { - continue; - } - if (value.startsWith('ik-')) { - value = value.replace(/^ik-/, ''); - const defaultItem = jsonSetting.style_setting_options.find(item => item.default); - if (!defaultItem || defaultItem.install_key != value) { - if (defaultItem) { - defaultItem.default = false; - } - jsonSetting.style_setting_options.some(item => { - if (item.install_key == value) { - item.default = true; - return true; - } - }); - } - } else if (jsonSetting.setting_type == 'image') { - jsonSetting.style_setting_options.some(item => { - if (item.default) { - item.default = false; - return true; - } - }); - images.set(jsonSetting.install_key, value); - } else { - const item = jsonSetting.style_setting_options[0]; - if (item.value !== value && item.install_key == 'placeholder') { - item.value = value; - } - } - } - if (images.size) { - new MutationObserver((_, observer) => { - if (!document.getElementById('style-settings')) { - return; - } - observer.disconnect(); - for (const [name, url] of images.entries()) { - const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`); - const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url')); - if (elUrl) { - elUrl.value = url; - } - } - }).observe(document, {childList: true, subtree: true}); - } - return json; - }); - }; - } + ')()'; - -// TODO: remove the following statement when USO pagination is fixed -if (location.search.includes('category=')) { - document.addEventListener('DOMContentLoaded', function _() { - document.removeEventListener('DOMContentLoaded', _); - new MutationObserver((_, observer) => { - if (!document.getElementById('pagination')) { - return; - } - observer.disconnect(); - const category = '&' + location.search.match(/category=[^&]+/)[0]; - const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])'); - for (let i = 0; i < links.length; i++) { - links[i].href += category; - } - }).observe(document, {childList: true, subtree: true}); - }); -} - -new MutationObserver((mutations, observer) => { - if (document.body) { - observer.disconnect(); - // TODO: remove the following statement when USO pagination title is fixed - document.title = document.title.replace(/^\d+&category=/, ''); - chrome.runtime.sendMessage({ - method: 'getStyles', - url: getMeta('stylish-id-url') || location.href - }, checkUpdatability); - } -}).observe(document.documentElement, {childList: true}); - -/* 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, - we need to fix this URL using "stylish-update-url" meta key -*/ -function getStyleURL() { - const url = getMeta('stylish-code-chrome'); - // TODO: remove when USO is fixed - const directUrl = getMeta('stylish-update-url'); - if (directUrl.includes('?') && !url.includes('?')) { - /* get custom settings from the update url */ - return Object.assign(new URL(url), { - search: (new URL(directUrl)).search - }).href; - } - return url; -} - -function checkUpdatability([installedStyle]) { - // TODO: remove the following statement when USO is fixed - document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', { - detail: installedStyle && installedStyle.updateUrl, - })); - if (!installedStyle) { - sendEvent('styleCanBeInstalledChrome'); - return; - } - const md5Url = getMeta('stylish-md5-url'); - if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) { - getResource(md5Url).then(md5 => { - reportUpdatable(md5 != installedStyle.originalMd5); - }); - } else { - getResource(getStyleURL()).then(code => { - reportUpdatable(code === null || - !styleSectionsEqual(JSON.parse(code), installedStyle)); - }); - } - - function reportUpdatable(isUpdatable) { - sendEvent( - isUpdatable - ? 'styleCanBeUpdatedChrome' - : 'styleAlreadyInstalledChrome', - { - updateUrl: installedStyle.updateUrl - } - ); - } -} - - -function sendEvent(type, detail = null) { - if (FIREFOX) { - type = type.replace('Chrome', ''); - } else if (OPERA || VIVALDI) { - type = type.replace('Chrome', 'Opera'); - } - detail = {detail}; - if (typeof cloneInto != 'undefined') { - // Firefox requires explicit cloning, however USO can't process our messages anyway - // because USO tries to use a global "event" variable deprecated in Firefox - detail = cloneInto(detail, document); // eslint-disable-line no-undef - } - onDOMready().then(() => { - document.dispatchEvent(new CustomEvent(type, detail)); - }); -} - - -function onInstallClicked() { - if (!orphanCheck || !orphanCheck()) { - return; - } - getResource(getMeta('stylish-description')) - .then(name => saveStyleCode('styleInstall', name)) - .then(() => getResource(getMeta('stylish-install-ping-url-chrome'))); -} - - -function onUpdateClicked() { - if (!orphanCheck || !orphanCheck()) { - return; - } - chrome.runtime.sendMessage({ - method: 'getStyles', - url: getMeta('stylish-id-url') || location.href, - }, ([style]) => { - saveStyleCode('styleUpdate', style.name, {id: style.id}); - }); -} - - -function saveStyleCode(message, name, addProps) { - return new Promise(resolve => { - if (!confirm(chrome.i18n.getMessage(message, [name]))) { - return; - } - enableUpdateButton(false); - getResource(getStyleURL()).then(code => { - chrome.runtime.sendMessage( - Object.assign(JSON.parse(code), addProps, { - method: 'saveStyle', - reason: 'update', - }), - style => { - if (message == 'styleUpdate' && style.updateUrl.includes('?')) { - enableUpdateButton(true); - } else { - sendEvent('styleInstalledChrome'); - } - } - ); - resolve(); - }); - }); - - function enableUpdateButton(state) { - const button = document.getElementById('update_style_button'); - if (button) { - button.style.cssText = state ? '' : - 'pointer-events: none !important; opacity: .25 !important;'; - } - } -} - - -function getMeta(name) { - const e = document.querySelector(`link[rel="${name}"]`); - return e ? e.getAttribute('href') : null; -} - - -function getResource(url) { - return new Promise(resolve => { - if (url.startsWith('#')) { - resolve(document.getElementById(url.slice(1)).textContent); - } else { - chrome.runtime.sendMessage({method: 'download', url}, resolve); - } - }); -} - - -function styleSectionsEqual({sections: a}, {sections: b}) { - if (!a || !b) { - return undefined; - } - if (a.length != b.length) { - return false; - } - const checkedInB = []; - return a.every(sectionA => b.some(sectionB => { - if (!checkedInB.includes(sectionB) && propertiesEqual(sectionA, sectionB)) { - checkedInB.push(sectionB); - return true; - } - })); - - function propertiesEqual(secA, secB) { - for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) { - if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) { - return false; - } - } - return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a == b); - } - - function equalOrEmpty(a, b, telltale, comparator) { - const typeA = a && typeof a[telltale] == 'function'; - const typeB = b && typeof b[telltale] == 'function'; - return ( - (a === null || a === undefined || (typeA && !a.length)) && - (b === null || b === undefined || (typeB && !b.length)) - ) || typeA && typeB && a.length == b.length && comparator(a, b); - } - - function arrayMirrors(array1, array2) { - for (const el of array1) { - if (array2.indexOf(el) < 0) { - return false; - } - } - for (const el of array2) { - if (array1.indexOf(el) < 0) { - return false; - } - } - return true; - } -} - - -function onDOMready() { - if (document.readyState != 'loading') { - return Promise.resolve(); - } - return new Promise(resolve => { - document.addEventListener('DOMContentLoaded', function _() { - document.removeEventListener('DOMContentLoaded', _); - resolve(); - }); - }); -} - - -function orphanCheck() { - const port = chrome.runtime.connect(); - if (port) { - port.disconnect(); - return true; - } - // we're orphaned due to an extension update - // we can detach event listeners - document.removeEventListener('stylishUpdate', onUpdateClicked); - document.removeEventListener('stylishUpdateChrome', onUpdateClicked); - document.removeEventListener('stylishUpdateOpera', onUpdateClicked); - - document.removeEventListener('stylishInstall', onInstallClicked); - document.removeEventListener('stylishInstallChrome', onInstallClicked); - document.removeEventListener('stylishInstallOpera', onInstallClicked); - - // 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 - [ - 'checkUpdatability', - 'getMeta', - 'getResource', - 'onDOMready', - 'onInstallClicked', - 'onUpdateClicked', - 'orphanCheck', - 'saveStyleCode', - 'sendEvent', - 'styleSectionsEqual', - ].forEach(fn => (window[fn] = null)); -} +'use strict'; + +const CHROMIUM = /Chromium/.test(navigator.userAgent); // non-Windows Chromium +const FIREFOX = /Firefox/.test(navigator.userAgent); +const VIVALDI = /Vivaldi/.test(navigator.userAgent); +const OPERA = /OPR/.test(navigator.userAgent); + +document.addEventListener('stylishUpdate', onUpdateClicked); +document.addEventListener('stylishUpdateChrome', onUpdateClicked); +document.addEventListener('stylishUpdateOpera', onUpdateClicked); + +document.addEventListener('stylishInstall', onInstallClicked); +document.addEventListener('stylishInstallChrome', onInstallClicked); +document.addEventListener('stylishInstallOpera', onInstallClicked); + +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + // orphaned content script check + if (msg.method == 'ping') { + sendResponse(true); + } +}); + +// TODO: remove the following statement when USO is fixed +document.documentElement.appendChild(document.createElement('script')).text = '(' + + function() { + let settings; + document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) { + document.removeEventListener('stylusFixBuggyUSOsettings', _); + settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search); + }); + const originalResponseJson = Response.prototype.json; + Response.prototype.json = function(...args) { + return originalResponseJson.call(this, ...args).then(json => { + Response.prototype.json = originalResponseJson; + if (!settings || typeof ((json || {}).style_settings || {}).every != 'function') { + return json; + } + const images = new Map(); + for (const jsonSetting of json.style_settings) { + let value = settings.get('ik-' + jsonSetting.install_key); + if (!value + || !jsonSetting.style_setting_options + || !jsonSetting.style_setting_options[0]) { + continue; + } + if (value.startsWith('ik-')) { + value = value.replace(/^ik-/, ''); + const defaultItem = jsonSetting.style_setting_options.find(item => item.default); + if (!defaultItem || defaultItem.install_key != value) { + if (defaultItem) { + defaultItem.default = false; + } + jsonSetting.style_setting_options.some(item => { + if (item.install_key == value) { + item.default = true; + return true; + } + }); + } + } else if (jsonSetting.setting_type == 'image') { + jsonSetting.style_setting_options.some(item => { + if (item.default) { + item.default = false; + return true; + } + }); + images.set(jsonSetting.install_key, value); + } else { + const item = jsonSetting.style_setting_options[0]; + if (item.value !== value && item.install_key == 'placeholder') { + item.value = value; + } + } + } + if (images.size) { + new MutationObserver((_, observer) => { + if (!document.getElementById('style-settings')) { + return; + } + observer.disconnect(); + for (const [name, url] of images.entries()) { + const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`); + const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url')); + if (elUrl) { + elUrl.value = url; + } + } + }).observe(document, {childList: true, subtree: true}); + } + return json; + }); + }; + } + ')()'; + +// TODO: remove the following statement when USO pagination is fixed +if (location.search.includes('category=')) { + document.addEventListener('DOMContentLoaded', function _() { + document.removeEventListener('DOMContentLoaded', _); + new MutationObserver((_, observer) => { + if (!document.getElementById('pagination')) { + return; + } + observer.disconnect(); + const category = '&' + location.search.match(/category=[^&]+/)[0]; + const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])'); + for (let i = 0; i < links.length; i++) { + links[i].href += category; + } + }).observe(document, {childList: true, subtree: true}); + }); +} + +new MutationObserver((mutations, observer) => { + if (document.body) { + observer.disconnect(); + // TODO: remove the following statement when USO pagination title is fixed + document.title = document.title.replace(/^\d+&category=/, ''); + chrome.runtime.sendMessage({ + method: 'getStyles', + url: getMeta('stylish-id-url') || location.href + }, checkUpdatability); + } +}).observe(document.documentElement, {childList: true}); + +/* 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, + we need to fix this URL using "stylish-update-url" meta key +*/ +function getStyleURL() { + const url = getMeta('stylish-code-chrome'); + // TODO: remove when USO is fixed + const directUrl = getMeta('stylish-update-url'); + if (directUrl.includes('?') && !url.includes('?')) { + /* get custom settings from the update url */ + return Object.assign(new URL(url), { + search: (new URL(directUrl)).search + }).href; + } + return url; +} + +function checkUpdatability([installedStyle]) { + // TODO: remove the following statement when USO is fixed + document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', { + detail: installedStyle && installedStyle.updateUrl, + })); + if (!installedStyle) { + sendEvent('styleCanBeInstalledChrome'); + return; + } + const md5Url = getMeta('stylish-md5-url'); + if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) { + getResource(md5Url).then(md5 => { + reportUpdatable(md5 != installedStyle.originalMd5); + }); + } else { + getResource(getStyleURL()).then(code => { + reportUpdatable(code === null || + !styleSectionsEqual(JSON.parse(code), installedStyle)); + }); + } + + function reportUpdatable(isUpdatable) { + sendEvent( + isUpdatable + ? 'styleCanBeUpdatedChrome' + : 'styleAlreadyInstalledChrome', + { + updateUrl: installedStyle.updateUrl + } + ); + } +} + + +function sendEvent(type, detail = null) { + if (FIREFOX) { + type = type.replace('Chrome', ''); + } else if (OPERA || VIVALDI) { + type = type.replace('Chrome', 'Opera'); + } + detail = {detail}; + if (typeof cloneInto != 'undefined') { + // Firefox requires explicit cloning, however USO can't process our messages anyway + // because USO tries to use a global "event" variable deprecated in Firefox + detail = cloneInto(detail, document); // eslint-disable-line no-undef + } + onDOMready().then(() => { + document.dispatchEvent(new CustomEvent(type, detail)); + }); +} + + +function onInstallClicked() { + if (!orphanCheck || !orphanCheck()) { + return; + } + getResource(getMeta('stylish-description')) + .then(name => saveStyleCode('styleInstall', name)) + .then(() => getResource(getMeta('stylish-install-ping-url-chrome'))); +} + + +function onUpdateClicked() { + if (!orphanCheck || !orphanCheck()) { + return; + } + chrome.runtime.sendMessage({ + method: 'getStyles', + url: getMeta('stylish-id-url') || location.href, + }, ([style]) => { + saveStyleCode('styleUpdate', style.name, {id: style.id}); + }); +} + + +function saveStyleCode(message, name, addProps) { + return new Promise(resolve => { + if (!confirm(chrome.i18n.getMessage(message, [name]))) { + return; + } + enableUpdateButton(false); + getResource(getStyleURL()).then(code => { + chrome.runtime.sendMessage( + Object.assign(JSON.parse(code), addProps, { + method: 'saveStyle', + reason: 'update', + }), + style => { + if (message == 'styleUpdate' && style.updateUrl.includes('?')) { + enableUpdateButton(true); + } else { + sendEvent('styleInstalledChrome'); + } + } + ); + resolve(); + }); + }); + + function enableUpdateButton(state) { + const button = document.getElementById('update_style_button'); + if (button) { + button.style.cssText = state ? '' : + 'pointer-events: none !important; opacity: .25 !important;'; + } + } +} + + +function getMeta(name) { + const e = document.querySelector(`link[rel="${name}"]`); + return e ? e.getAttribute('href') : null; +} + + +function getResource(url) { + return new Promise(resolve => { + if (url.startsWith('#')) { + resolve(document.getElementById(url.slice(1)).textContent); + } else { + chrome.runtime.sendMessage({method: 'download', url}, resolve); + } + }); +} + + +function styleSectionsEqual({sections: a}, {sections: b}) { + if (!a || !b) { + return undefined; + } + if (a.length != b.length) { + return false; + } + const checkedInB = []; + return a.every(sectionA => b.some(sectionB => { + if (!checkedInB.includes(sectionB) && propertiesEqual(sectionA, sectionB)) { + checkedInB.push(sectionB); + return true; + } + })); + + function propertiesEqual(secA, secB) { + for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) { + if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) { + return false; + } + } + return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a == b); + } + + function equalOrEmpty(a, b, telltale, comparator) { + const typeA = a && typeof a[telltale] == 'function'; + const typeB = b && typeof b[telltale] == 'function'; + return ( + (a === null || a === undefined || (typeA && !a.length)) && + (b === null || b === undefined || (typeB && !b.length)) + ) || typeA && typeB && a.length == b.length && comparator(a, b); + } + + function arrayMirrors(array1, array2) { + for (const el of array1) { + if (array2.indexOf(el) < 0) { + return false; + } + } + for (const el of array2) { + if (array1.indexOf(el) < 0) { + return false; + } + } + return true; + } +} + + +function onDOMready() { + if (document.readyState != 'loading') { + return Promise.resolve(); + } + return new Promise(resolve => { + document.addEventListener('DOMContentLoaded', function _() { + document.removeEventListener('DOMContentLoaded', _); + resolve(); + }); + }); +} + + +function orphanCheck() { + const port = chrome.runtime.connect(); + if (port) { + port.disconnect(); + return true; + } + // we're orphaned due to an extension update + // we can detach event listeners + document.removeEventListener('stylishUpdate', onUpdateClicked); + document.removeEventListener('stylishUpdateChrome', onUpdateClicked); + document.removeEventListener('stylishUpdateOpera', onUpdateClicked); + + document.removeEventListener('stylishInstall', onInstallClicked); + document.removeEventListener('stylishInstallChrome', onInstallClicked); + document.removeEventListener('stylishInstallOpera', onInstallClicked); + + // 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 + [ + 'checkUpdatability', + 'getMeta', + 'getResource', + 'onDOMready', + 'onInstallClicked', + 'onUpdateClicked', + 'orphanCheck', + 'saveStyleCode', + 'sendEvent', + 'styleSectionsEqual', + ].forEach(fn => (window[fn] = null)); +} diff --git a/edit.js b/edit/edit.js similarity index 100% rename from edit.js rename to edit/edit.js diff --git a/options/index.html b/index.html similarity index 100% rename from options/index.html rename to index.html diff --git a/dom.js b/js/dom.js similarity index 100% rename from dom.js rename to js/dom.js diff --git a/localization.js b/js/localization.js similarity index 100% rename from localization.js rename to js/localization.js diff --git a/prefs.js b/js/prefs.js similarity index 100% rename from prefs.js rename to js/prefs.js diff --git a/manage/fileSaveLoad.js b/manage/fileSaveLoad.js new file mode 100644 index 00000000..125ed9e0 --- /dev/null +++ b/manage/fileSaveLoad.js @@ -0,0 +1,791 @@ +/* global messageBox, handleUpdate, applyOnMessage */ +'use strict'; + +const STYLISH_DUMP_FILE_EXT = '.txt'; +const STYLUS_BACKUP_FILE_EXT = '.json'; + + +function importFromFile({fileTypeFilter, file} = {}) { + return new Promise(resolve => { + const fileInput = document.createElement('input'); + if (file) { + readFile(); + return; + } + fileInput.style.display = 'none'; + fileInput.type = 'file'; + fileInput.accept = fileTypeFilter || STYLISH_DUMP_FILE_EXT; + fileInput.acceptCharset = 'utf-8'; + + document.body.appendChild(fileInput); + fileInput.initialValue = fileInput.value; + fileInput.onchange = readFile; + fileInput.click(); + + function readFile() { + if (file || fileInput.value !== fileInput.initialValue) { + file = file || fileInput.files[0]; + if (file.size > 100e6) { + console.warn("100MB backup? I don't believe you."); + importFromString('').then(resolve); + return; + } + document.body.style.cursor = 'wait'; + const fReader = new FileReader(); + fReader.onloadend = event => { + fileInput.remove(); + importFromString(event.target.result).then(numStyles => { + document.body.style.cursor = ''; + resolve(numStyles); + }); + }; + fReader.readAsText(file, 'utf-8'); + } + } + }); +} + + +function importFromString(jsonString) { + if (!BG) { + onBackgroundReady().then(() => importFromString(jsonString)); + return; + } + // create objects in background context + const json = BG.tryJSONparse(jsonString) || []; + if (typeof json.slice != 'function') { + json.length = 0; + } + const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []); + const oldStylesByName = json.length && new Map( + oldStyles.map(style => [style.name.trim(), style])); + + const stats = { + added: {names: [], ids: [], legend: 'importReportLegendAdded'}, + unchanged: {names: [], ids: [], legend: 'importReportLegendIdentical'}, + metaAndCode: {names: [], ids: [], legend: 'importReportLegendUpdatedBoth'}, + metaOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedMeta'}, + codeOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedCode'}, + invalid: {names: [], legend: 'importReportLegendInvalid'}, + }; + + let index = 0; + let lastRenderTime = performance.now(); + const renderQueue = []; + const RENDER_NAP_TIME_MAX = 1000; // ms + const RENDER_QUEUE_MAX = 50; // number of styles + const SAVE_OPTIONS = {reason: 'import', notify: false}; + + return new Promise(proceed); + + function proceed(resolve) { + while (index < json.length) { + const item = json[index++]; + const info = analyze(item); + if (info) { + // using saveStyle directly since json was parsed in background page context + return BG.saveStyle(Object.assign(item, SAVE_OPTIONS)) + .then(style => account({style, info, resolve})); + } + } + renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); + renderQueue.length = 0; + done(resolve); + } + + function analyze(item) { + if (!item || !item.name || !item.name.trim() || typeof item != 'object' + || (item.sections && typeof item.sections.slice != 'function')) { + stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`); + return; + } + item.name = item.name.trim(); + const byId = BG.cachedStyles.byId.get(item.id); + const byName = oldStylesByName.get(item.name); + oldStylesByName.delete(item.name); + let oldStyle; + if (byId) { + if (sameStyle(byId, item)) { + oldStyle = byId; + } else { + item.id = null; + } + } + if (!oldStyle && byName) { + item.id = byName.id; + oldStyle = byName; + } + const oldStyleKeys = oldStyle && Object.keys(oldStyle); + const metaEqual = oldStyleKeys && + oldStyleKeys.length == Object.keys(item).length && + oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]); + const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item); + if (metaEqual && codeEqual) { + stats.unchanged.names.push(oldStyle.name); + stats.unchanged.ids.push(oldStyle.id); + return; + } + return {oldStyle, metaEqual, codeEqual}; + } + + function sameStyle(oldStyle, newStyle) { + return oldStyle.name.trim() === newStyle.name.trim() || + ['updateUrl', 'originalMd5', 'originalDigest'] + .some(field => oldStyle[field] && oldStyle[field] == newStyle[field]); + } + + function account({style, info, resolve}) { + renderQueue.push(style); + if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX + || renderQueue.length > RENDER_QUEUE_MAX) { + renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); + setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id)); + renderQueue.length = 0; + lastRenderTime = performance.now(); + } + setTimeout(proceed, 0, resolve); + const {oldStyle, metaEqual, codeEqual} = info; + if (!oldStyle) { + stats.added.names.push(style.name); + stats.added.ids.push(style.id); + return; + } + if (!metaEqual && !codeEqual) { + stats.metaAndCode.names.push(reportNameChange(oldStyle, style)); + stats.metaAndCode.ids.push(style.id); + return; + } + if (!codeEqual) { + stats.codeOnly.names.push(style.name); + stats.codeOnly.ids.push(style.id); + return; + } + stats.metaOnly.names.push(reportNameChange(oldStyle, style)); + stats.metaOnly.ids.push(style.id); + } + + function done(resolve) { + const numChanged = stats.metaAndCode.names.length + + stats.metaOnly.names.length + + stats.codeOnly.names.length + + stats.added.names.length; + Promise.resolve(numChanged && refreshAllTabs()).then(() => { + const report = Object.keys(stats) + .filter(kind => stats[kind].names.length) + .map(kind => { + const {ids, names, legend} = stats[kind]; + const listItemsWithId = (name, i) => + $element({dataset: {id: ids[i]}, textContent: name}); + const listItems = name => + $element({textContent: name}); + const block = + $element({tag: 'details', dataset: {id: kind}, appendChild: [ + $element({tag: 'summary', appendChild: + $element({tag: 'b', textContent: names.length + ' ' + t(legend)}) + }), + $element({tag: 'small', appendChild: + names.map(ids ? listItemsWithId : listItems) + }), + ]}); + return block; + }); + scrollTo(0, 0); + messageBox({ + title: t('importReportTitle'), + contents: report.length ? report : t('importReportUnchanged'), + buttons: [t('confirmOK'), numChanged && t('undo')], + onshow: bindClick, + }).then(({button, enter, esc}) => { + if (button == 1) { + undo(); + } + }); + resolve(numChanged); + }); + } + + function undo() { + const oldStylesById = new Map(oldStyles.map(style => [style.id, style])); + const newIds = [ + ...stats.metaAndCode.ids, + ...stats.metaOnly.ids, + ...stats.codeOnly.ids, + ...stats.added.ids, + ]; + let resolve; + index = 0; + return new Promise(resolve_ => { + resolve = resolve_; + undoNextId(); + }).then(refreshAllTabs) + .then(() => messageBox({ + title: t('importReportUndoneTitle'), + contents: newIds.length + ' ' + t('importReportUndone'), + buttons: [t('confirmOK')], + })); + function undoNextId() { + if (index == newIds.length) { + resolve(); + return; + } + const id = newIds[index++]; + deleteStyleSafe({id, notify: false}).then(id => { + const oldStyle = oldStylesById.get(id); + if (oldStyle) { + saveStyleSafe(Object.assign(oldStyle, SAVE_OPTIONS)) + .then(undoNextId); + } else { + undoNextId(); + } + }); + } + } + + function bindClick(box) { + const highlightElement = event => { + const styleElement = $('#style-' + event.target.dataset.id); + if (styleElement) { + scrollElementIntoView(styleElement); + animateElement(styleElement); + } + }; + for (const block of $$('details')) { + if (block.dataset.id != 'invalid') { + block.style.cursor = 'pointer'; + block.onclick = highlightElement; + } + } + } + + function limitString(s, limit = 100) { + return s.length <= limit ? s : s.substr(0, limit) + '...'; + } + + function reportNameChange(oldStyle, newStyle) { + return newStyle.name != oldStyle.name + ? oldStyle.name + ' —> ' + newStyle.name + : oldStyle.name; + } + + function refreshAllTabs() { + return Promise.all([ + getActiveTab(), + getOwnTab(), + ]).then(([activeTab, ownTab]) => new Promise(resolve => { + // list all tabs including chrome-extension:// which can be ours + queryTabs().then(tabs => { + const lastTab = tabs[tabs.length - 1]; + for (const tab of tabs) { + // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF + if (FIREFOX && !tab.width) { + if (tab == lastTab) { + resolve(); + } + continue; + } + getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => { + const message = {method: 'styleReplaceAll', styles}; + if (tab.id == ownTab.id) { + applyOnMessage(message); + } else { + invokeOrPostpone(tab.id == activeTab.id, + chrome.tabs.sendMessage, tab.id, message, ignoreChromeError); + } + setTimeout(BG.updateIcon, 0, tab, styles); + if (tab == lastTab) { + resolve(); + } + }); + } + }); + })); + } +} + + +$('#file-all-styles').onclick = () => { + getStylesSafe().then(styles => { + const text = JSON.stringify(styles, null, '\t'); + const url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text); + return url; + // for long URLs; https://github.com/schomery/stylus/issues/13#issuecomment-284582600 + }).then(fetch) + .then(res => res.blob()) + .then(blob => { + const objectURL = URL.createObjectURL(blob); + let link = $element({ + tag:'a', + href: objectURL, + type: 'application/json', + download: generateFileName(), + }); + // TODO: remove the fallback when FF multi-process bug is fixed + if (!FIREFOX) { + link.dispatchEvent(new MouseEvent('click')); + setTimeout(() => URL.revokeObjectURL(objectURL)); + } else { + const iframe = document.body.appendChild($element({ + tag: 'iframe', + style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'), + })); + doTimeout().then(() => { + link = iframe.contentDocument.importNode(link, true); + iframe.contentDocument.body.appendChild(link); + }) + .then(doTimeout) + .then(() => link.dispatchEvent(new MouseEvent('click'))) + .then(doTimeout(1000)) + .then(() => { + URL.revokeObjectURL(objectURL); + iframe.remove(); + }); + } + }); + + function generateFileName() { + const today = new Date(); + const dd = ('0' + today.getDate()).substr(-2); + const mm = ('0' + (today.getMonth() + 1)).substr(-2); + const yyyy = today.getFullYear(); + return `stylus-${yyyy}-${mm}-${dd}${STYLUS_BACKUP_FILE_EXT}`; + } +}; + + +$('#unfile-all-styles').onclick = () => { + importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT}); +}; + +Object.assign(document.body, { + ondragover(event) { + const hasFiles = event.dataTransfer.types.includes('Files'); + event.dataTransfer.dropEffect = hasFiles || event.target.type == 'search' ? 'copy' : 'none'; + this.classList.toggle('dropzone', hasFiles); + if (hasFiles) { + event.preventDefault(); + clearTimeout(this.fadeoutTimer); + this.classList.remove('fadeout'); + } + }, + ondragend(event) { + animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => { + this.style.animationDuration = ''; + }); + }, + ondragleave(event) { + try { + // in Firefox event.target could be XUL browser and hence there is no permission to access it + if (event.target === this) { + this.ondragend(); + } + } catch (e) { + this.ondragend(); + } + }, + ondrop(event) { + this.ondragend(); + if (event.dataTransfer.files.length) { + event.preventDefault(); + if ($('#onlyUpdates input').checked) { + $('#onlyUpdates input').click(); + } + importFromFile({file: event.dataTransfer.files[0]}); + } + }, +}); +======= +/* global messageBox, handleUpdate, applyOnMessage */ +'use strict'; + +const STYLISH_DUMP_FILE_EXT = '.txt'; +const STYLUS_BACKUP_FILE_EXT = '.json'; + + +function importFromFile({fileTypeFilter, file} = {}) { + return new Promise(resolve => { + const fileInput = document.createElement('input'); + if (file) { + readFile(); + return; + } + fileInput.style.display = 'none'; + fileInput.type = 'file'; + fileInput.accept = fileTypeFilter || STYLISH_DUMP_FILE_EXT; + fileInput.acceptCharset = 'utf-8'; + + document.body.appendChild(fileInput); + fileInput.initialValue = fileInput.value; + fileInput.onchange = readFile; + fileInput.click(); + + function readFile() { + if (file || fileInput.value !== fileInput.initialValue) { + file = file || fileInput.files[0]; + if (file.size > 100e6) { + console.warn("100MB backup? I don't believe you."); + importFromString('').then(resolve); + return; + } + document.body.style.cursor = 'wait'; + const fReader = new FileReader(); + fReader.onloadend = event => { + fileInput.remove(); + importFromString(event.target.result).then(numStyles => { + document.body.style.cursor = ''; + resolve(numStyles); + }); + }; + fReader.readAsText(file, 'utf-8'); + } + } + }); +} + + +function importFromString(jsonString) { + if (!BG) { + onBackgroundReady().then(() => importFromString(jsonString)); + return; + } + // create objects in background context + const json = BG.tryJSONparse(jsonString) || []; + if (typeof json.slice != 'function') { + json.length = 0; + } + const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []); + const oldStylesByName = json.length && new Map( + oldStyles.map(style => [style.name.trim(), style])); + + const stats = { + added: {names: [], ids: [], legend: 'importReportLegendAdded'}, + unchanged: {names: [], ids: [], legend: 'importReportLegendIdentical'}, + metaAndCode: {names: [], ids: [], legend: 'importReportLegendUpdatedBoth'}, + metaOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedMeta'}, + codeOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedCode'}, + invalid: {names: [], legend: 'importReportLegendInvalid'}, + }; + + let index = 0; + let lastRenderTime = performance.now(); + const renderQueue = []; + const RENDER_NAP_TIME_MAX = 1000; // ms + const RENDER_QUEUE_MAX = 50; // number of styles + const SAVE_OPTIONS = {reason: 'import', notify: false}; + + return new Promise(proceed); + + function proceed(resolve) { + while (index < json.length) { + const item = json[index++]; + const info = analyze(item); + if (info) { + // using saveStyle directly since json was parsed in background page context + return BG.saveStyle(Object.assign(item, SAVE_OPTIONS)) + .then(style => account({style, info, resolve})); + } + } + renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); + renderQueue.length = 0; + done(resolve); + } + + function analyze(item) { + if (!item || !item.name || !item.name.trim() || typeof item != 'object' + || (item.sections && typeof item.sections.slice != 'function')) { + stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`); + return; + } + item.name = item.name.trim(); + const byId = BG.cachedStyles.byId.get(item.id); + const byName = oldStylesByName.get(item.name); + oldStylesByName.delete(item.name); + let oldStyle; + if (byId) { + if (sameStyle(byId, item)) { + oldStyle = byId; + } else { + item.id = null; + } + } + if (!oldStyle && byName) { + item.id = byName.id; + oldStyle = byName; + } + const oldStyleKeys = oldStyle && Object.keys(oldStyle); + const metaEqual = oldStyleKeys && + oldStyleKeys.length == Object.keys(item).length && + oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]); + const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item); + if (metaEqual && codeEqual) { + stats.unchanged.names.push(oldStyle.name); + stats.unchanged.ids.push(oldStyle.id); + return; + } + return {oldStyle, metaEqual, codeEqual}; + } + + function sameStyle(oldStyle, newStyle) { + return oldStyle.name.trim() === newStyle.name.trim() || + ['updateUrl', 'originalMd5', 'originalDigest'] + .some(field => oldStyle[field] && oldStyle[field] == newStyle[field]); + } + + function account({style, info, resolve}) { + renderQueue.push(style); + if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX + || renderQueue.length > RENDER_QUEUE_MAX) { + renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); + setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id)); + renderQueue.length = 0; + lastRenderTime = performance.now(); + } + setTimeout(proceed, 0, resolve); + const {oldStyle, metaEqual, codeEqual} = info; + if (!oldStyle) { + stats.added.names.push(style.name); + stats.added.ids.push(style.id); + return; + } + if (!metaEqual && !codeEqual) { + stats.metaAndCode.names.push(reportNameChange(oldStyle, style)); + stats.metaAndCode.ids.push(style.id); + return; + } + if (!codeEqual) { + stats.codeOnly.names.push(style.name); + stats.codeOnly.ids.push(style.id); + return; + } + stats.metaOnly.names.push(reportNameChange(oldStyle, style)); + stats.metaOnly.ids.push(style.id); + } + + function done(resolve) { + const numChanged = stats.metaAndCode.names.length + + stats.metaOnly.names.length + + stats.codeOnly.names.length + + stats.added.names.length; + Promise.resolve(numChanged && refreshAllTabs()).then(() => { + const report = Object.keys(stats) + .filter(kind => stats[kind].names.length) + .map(kind => { + const {ids, names, legend} = stats[kind]; + const listItemsWithId = (name, i) => + $element({dataset: {id: ids[i]}, textContent: name}); + const listItems = name => + $element({textContent: name}); + const block = + $element({tag: 'details', dataset: {id: kind}, appendChild: [ + $element({tag: 'summary', appendChild: + $element({tag: 'b', textContent: names.length + ' ' + t(legend)}) + }), + $element({tag: 'small', appendChild: + names.map(ids ? listItemsWithId : listItems) + }), + ]}); + return block; + }); + scrollTo(0, 0); + messageBox({ + title: t('importReportTitle'), + contents: report.length ? report : t('importReportUnchanged'), + buttons: [t('confirmOK'), numChanged && t('undo')], + onshow: bindClick, + }).then(({button, enter, esc}) => { + if (button == 1) { + undo(); + } + }); + resolve(numChanged); + }); + } + + function undo() { + const oldStylesById = new Map(oldStyles.map(style => [style.id, style])); + const newIds = [ + ...stats.metaAndCode.ids, + ...stats.metaOnly.ids, + ...stats.codeOnly.ids, + ...stats.added.ids, + ]; + let resolve; + index = 0; + return new Promise(resolve_ => { + resolve = resolve_; + undoNextId(); + }).then(refreshAllTabs) + .then(() => messageBox({ + title: t('importReportUndoneTitle'), + contents: newIds.length + ' ' + t('importReportUndone'), + buttons: [t('confirmOK')], + })); + function undoNextId() { + if (index == newIds.length) { + resolve(); + return; + } + const id = newIds[index++]; + deleteStyleSafe({id, notify: false}).then(id => { + const oldStyle = oldStylesById.get(id); + if (oldStyle) { + saveStyleSafe(Object.assign(oldStyle, SAVE_OPTIONS)) + .then(undoNextId); + } else { + undoNextId(); + } + }); + } + } + + function bindClick(box) { + const highlightElement = event => { + const styleElement = $('#style-' + event.target.dataset.id); + if (styleElement) { + scrollElementIntoView(styleElement); + animateElement(styleElement); + } + }; + for (const block of $$('details')) { + if (block.dataset.id != 'invalid') { + block.style.cursor = 'pointer'; + block.onclick = highlightElement; + } + } + } + + function limitString(s, limit = 100) { + return s.length <= limit ? s : s.substr(0, limit) + '...'; + } + + function reportNameChange(oldStyle, newStyle) { + return newStyle.name != oldStyle.name + ? oldStyle.name + ' —> ' + newStyle.name + : oldStyle.name; + } + + function refreshAllTabs() { + return Promise.all([ + getActiveTab(), + getOwnTab(), + ]).then(([activeTab, ownTab]) => new Promise(resolve => { + // list all tabs including chrome-extension:// which can be ours + queryTabs().then(tabs => { + const lastTab = tabs[tabs.length - 1]; + for (const tab of tabs) { + // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF + if (FIREFOX && !tab.width) { + if (tab == lastTab) { + resolve(); + } + continue; + } + getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => { + const message = {method: 'styleReplaceAll', styles}; + if (tab.id == ownTab.id) { + applyOnMessage(message); + } else { + invokeOrPostpone(tab.id == activeTab.id, + chrome.tabs.sendMessage, tab.id, message, ignoreChromeError); + } + setTimeout(BG.updateIcon, 0, tab, styles); + if (tab == lastTab) { + resolve(); + } + }); + } + }); + })); + } +} + + +$('#file-all-styles').onclick = () => { + getStylesSafe().then(styles => { + const text = JSON.stringify(styles, null, '\t'); + const url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text); + return url; + // for long URLs; https://github.com/schomery/stylus/issues/13#issuecomment-284582600 + }).then(fetch) + .then(res => res.blob()) + .then(blob => { + const objectURL = URL.createObjectURL(blob); + let link = $element({ + tag:'a', + href: objectURL, + type: 'application/json', + download: generateFileName(), + }); + // TODO: remove the fallback when FF multi-process bug is fixed + if (!FIREFOX) { + link.dispatchEvent(new MouseEvent('click')); + setTimeout(() => URL.revokeObjectURL(objectURL)); + } else { + const iframe = document.body.appendChild($element({ + tag: 'iframe', + style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'), + })); + doTimeout().then(() => { + link = iframe.contentDocument.importNode(link, true); + iframe.contentDocument.body.appendChild(link); + }) + .then(doTimeout) + .then(() => link.dispatchEvent(new MouseEvent('click'))) + .then(doTimeout(1000)) + .then(() => { + URL.revokeObjectURL(objectURL); + iframe.remove(); + }); + } + }); + + function generateFileName() { + const today = new Date(); + const dd = ('0' + today.getDate()).substr(-2); + const mm = ('0' + (today.getMonth() + 1)).substr(-2); + const yyyy = today.getFullYear(); + return `stylus-${mm}-${dd}-${yyyy}${STYLUS_BACKUP_FILE_EXT}`; + } +}; + + +$('#unfile-all-styles').onclick = () => { + importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT}); +}; + +Object.assign(document.body, { + ondragover(event) { + const hasFiles = event.dataTransfer.types.includes('Files'); + event.dataTransfer.dropEffect = hasFiles || event.target.type == 'search' ? 'copy' : 'none'; + this.classList.toggle('dropzone', hasFiles); + if (hasFiles) { + event.preventDefault(); + clearTimeout(this.fadeoutTimer); + this.classList.remove('fadeout'); + } + }, + ondragend(event) { + animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => { + this.style.animationDuration = ''; + }); + }, + ondragleave(event) { + try { + // in Firefox event.target could be XUL browser and hence there is no permission to access it + if (event.target === this) { + this.ondragend(); + } + } catch (e) { + this.ondragend(); + } + }, + ondrop(event) { + this.ondragend(); + if (event.dataTransfer.files.length) { + event.preventDefault(); + if ($('#onlyUpdates input').checked) { + $('#onlyUpdates input').click(); + } + importFromFile({file: event.dataTransfer.files[0]}); + } + }, +}); diff --git a/manage.css b/manage/manage.css similarity index 100% rename from manage.css rename to manage/manage.css diff --git a/manage.js b/manage/manage.js similarity index 100% rename from manage.js rename to manage/manage.js diff --git a/popup.css b/popup/popup.css similarity index 100% rename from popup.css rename to popup/popup.css diff --git a/popup.js b/popup/popup.js similarity index 100% rename from popup.js rename to popup/popup.js diff --git a/pull_locales.rb b/tools/pull_locales.rb similarity index 100% rename from pull_locales.rb rename to tools/pull_locales.rb diff --git a/pull_locales.sh b/tools/pull_locales.sh old mode 100755 new mode 100644 similarity index 100% rename from pull_locales.sh rename to tools/pull_locales.sh diff --git a/pull_locales_postprocess.py b/tools/pull_locales_postprocess.py similarity index 100% rename from pull_locales_postprocess.py rename to tools/pull_locales_postprocess.py diff --git a/beautify/beautify-css-mod.js b/vendor-overwrites/beautify/beautify-css-mod.js similarity index 100% rename from beautify/beautify-css-mod.js rename to vendor-overwrites/beautify/beautify-css-mod.js diff --git a/beautify/beautify-css.js b/vendor-overwrites/beautify/beautify-css.js similarity index 100% rename from beautify/beautify-css.js rename to vendor-overwrites/beautify/beautify-css.js diff --git a/codemirror-overwrites/addon/lint/css-lint.js b/vendor-overwrites/codemirror/addon/lint/css-lint.js similarity index 100% rename from codemirror-overwrites/addon/lint/css-lint.js rename to vendor-overwrites/codemirror/addon/lint/css-lint.js diff --git a/codemirror-overwrites/addon/search/match-highlighter.js b/vendor-overwrites/codemirror/addon/search/match-highlighter.js similarity index 100% rename from codemirror-overwrites/addon/search/match-highlighter.js rename to vendor-overwrites/codemirror/addon/search/match-highlighter.js diff --git a/codemirror/LICENSE b/vendor/codemirror/LICENSE similarity index 100% rename from codemirror/LICENSE rename to vendor/codemirror/LICENSE diff --git a/codemirror/addon/comment/comment.js b/vendor/codemirror/addon/comment/comment.js similarity index 100% rename from codemirror/addon/comment/comment.js rename to vendor/codemirror/addon/comment/comment.js diff --git a/codemirror/addon/dialog/dialog.css b/vendor/codemirror/addon/dialog/dialog.css similarity index 100% rename from codemirror/addon/dialog/dialog.css rename to vendor/codemirror/addon/dialog/dialog.css diff --git a/codemirror/addon/dialog/dialog.js b/vendor/codemirror/addon/dialog/dialog.js similarity index 100% rename from codemirror/addon/dialog/dialog.js rename to vendor/codemirror/addon/dialog/dialog.js diff --git a/codemirror/addon/edit/matchbrackets.js b/vendor/codemirror/addon/edit/matchbrackets.js similarity index 100% rename from codemirror/addon/edit/matchbrackets.js rename to vendor/codemirror/addon/edit/matchbrackets.js diff --git a/codemirror/addon/fold/brace-fold.js b/vendor/codemirror/addon/fold/brace-fold.js similarity index 100% rename from codemirror/addon/fold/brace-fold.js rename to vendor/codemirror/addon/fold/brace-fold.js diff --git a/codemirror/addon/fold/comment-fold.js b/vendor/codemirror/addon/fold/comment-fold.js similarity index 100% rename from codemirror/addon/fold/comment-fold.js rename to vendor/codemirror/addon/fold/comment-fold.js diff --git a/codemirror/addon/fold/foldcode.js b/vendor/codemirror/addon/fold/foldcode.js similarity index 100% rename from codemirror/addon/fold/foldcode.js rename to vendor/codemirror/addon/fold/foldcode.js diff --git a/codemirror/addon/fold/foldgutter.css b/vendor/codemirror/addon/fold/foldgutter.css similarity index 100% rename from codemirror/addon/fold/foldgutter.css rename to vendor/codemirror/addon/fold/foldgutter.css diff --git a/codemirror/addon/fold/foldgutter.js b/vendor/codemirror/addon/fold/foldgutter.js similarity index 100% rename from codemirror/addon/fold/foldgutter.js rename to vendor/codemirror/addon/fold/foldgutter.js diff --git a/codemirror/addon/hint/css-hint.js b/vendor/codemirror/addon/hint/css-hint.js similarity index 100% rename from codemirror/addon/hint/css-hint.js rename to vendor/codemirror/addon/hint/css-hint.js diff --git a/codemirror/addon/hint/show-hint.css b/vendor/codemirror/addon/hint/show-hint.css similarity index 100% rename from codemirror/addon/hint/show-hint.css rename to vendor/codemirror/addon/hint/show-hint.css diff --git a/codemirror/addon/hint/show-hint.js b/vendor/codemirror/addon/hint/show-hint.js similarity index 100% rename from codemirror/addon/hint/show-hint.js rename to vendor/codemirror/addon/hint/show-hint.js diff --git a/codemirror/addon/lint/css-lint.js b/vendor/codemirror/addon/lint/css-lint.js similarity index 100% rename from codemirror/addon/lint/css-lint.js rename to vendor/codemirror/addon/lint/css-lint.js diff --git a/codemirror/addon/lint/lint.css b/vendor/codemirror/addon/lint/lint.css similarity index 100% rename from codemirror/addon/lint/lint.css rename to vendor/codemirror/addon/lint/lint.css diff --git a/codemirror/addon/lint/lint.js b/vendor/codemirror/addon/lint/lint.js similarity index 100% rename from codemirror/addon/lint/lint.js rename to vendor/codemirror/addon/lint/lint.js diff --git a/codemirror/addon/scroll/annotatescrollbar.js b/vendor/codemirror/addon/scroll/annotatescrollbar.js similarity index 100% rename from codemirror/addon/scroll/annotatescrollbar.js rename to vendor/codemirror/addon/scroll/annotatescrollbar.js diff --git a/codemirror/addon/search/matchesonscrollbar.css b/vendor/codemirror/addon/search/matchesonscrollbar.css similarity index 100% rename from codemirror/addon/search/matchesonscrollbar.css rename to vendor/codemirror/addon/search/matchesonscrollbar.css diff --git a/codemirror/addon/search/matchesonscrollbar.js b/vendor/codemirror/addon/search/matchesonscrollbar.js similarity index 100% rename from codemirror/addon/search/matchesonscrollbar.js rename to vendor/codemirror/addon/search/matchesonscrollbar.js diff --git a/codemirror/addon/search/search.js b/vendor/codemirror/addon/search/search.js similarity index 100% rename from codemirror/addon/search/search.js rename to vendor/codemirror/addon/search/search.js diff --git a/codemirror/addon/search/searchcursor.js b/vendor/codemirror/addon/search/searchcursor.js similarity index 100% rename from codemirror/addon/search/searchcursor.js rename to vendor/codemirror/addon/search/searchcursor.js diff --git a/codemirror/addon/selection/active-line.js b/vendor/codemirror/addon/selection/active-line.js similarity index 100% rename from codemirror/addon/selection/active-line.js rename to vendor/codemirror/addon/selection/active-line.js diff --git a/codemirror/keymap/emacs.js b/vendor/codemirror/keymap/emacs.js similarity index 100% rename from codemirror/keymap/emacs.js rename to vendor/codemirror/keymap/emacs.js diff --git a/codemirror/keymap/sublime.js b/vendor/codemirror/keymap/sublime.js similarity index 100% rename from codemirror/keymap/sublime.js rename to vendor/codemirror/keymap/sublime.js diff --git a/codemirror/keymap/vim.js b/vendor/codemirror/keymap/vim.js similarity index 100% rename from codemirror/keymap/vim.js rename to vendor/codemirror/keymap/vim.js diff --git a/codemirror/lib/codemirror.css b/vendor/codemirror/lib/codemirror.css similarity index 100% rename from codemirror/lib/codemirror.css rename to vendor/codemirror/lib/codemirror.css diff --git a/codemirror/lib/codemirror.js b/vendor/codemirror/lib/codemirror.js similarity index 100% rename from codemirror/lib/codemirror.js rename to vendor/codemirror/lib/codemirror.js diff --git a/codemirror/mode/css/css.js b/vendor/codemirror/mode/css/css.js similarity index 100% rename from codemirror/mode/css/css.js rename to vendor/codemirror/mode/css/css.js diff --git a/codemirror/mode/css/gss.html b/vendor/codemirror/mode/css/gss.html similarity index 100% rename from codemirror/mode/css/gss.html rename to vendor/codemirror/mode/css/gss.html diff --git a/codemirror/mode/css/gss_test.js b/vendor/codemirror/mode/css/gss_test.js similarity index 100% rename from codemirror/mode/css/gss_test.js rename to vendor/codemirror/mode/css/gss_test.js diff --git a/codemirror/mode/css/index.html b/vendor/codemirror/mode/css/index.html similarity index 100% rename from codemirror/mode/css/index.html rename to vendor/codemirror/mode/css/index.html diff --git a/codemirror/mode/css/less.html b/vendor/codemirror/mode/css/less.html similarity index 100% rename from codemirror/mode/css/less.html rename to vendor/codemirror/mode/css/less.html diff --git a/codemirror/mode/css/less_test.js b/vendor/codemirror/mode/css/less_test.js similarity index 100% rename from codemirror/mode/css/less_test.js rename to vendor/codemirror/mode/css/less_test.js diff --git a/codemirror/mode/css/scss.html b/vendor/codemirror/mode/css/scss.html similarity index 100% rename from codemirror/mode/css/scss.html rename to vendor/codemirror/mode/css/scss.html diff --git a/codemirror/mode/css/scss_test.js b/vendor/codemirror/mode/css/scss_test.js similarity index 100% rename from codemirror/mode/css/scss_test.js rename to vendor/codemirror/mode/css/scss_test.js diff --git a/codemirror/mode/css/test.js b/vendor/codemirror/mode/css/test.js similarity index 100% rename from codemirror/mode/css/test.js rename to vendor/codemirror/mode/css/test.js diff --git a/codemirror/theme/3024-day.css b/vendor/codemirror/theme/3024-day.css similarity index 100% rename from codemirror/theme/3024-day.css rename to vendor/codemirror/theme/3024-day.css diff --git a/codemirror/theme/3024-night.css b/vendor/codemirror/theme/3024-night.css similarity index 100% rename from codemirror/theme/3024-night.css rename to vendor/codemirror/theme/3024-night.css diff --git a/codemirror/theme/abcdef.css b/vendor/codemirror/theme/abcdef.css similarity index 100% rename from codemirror/theme/abcdef.css rename to vendor/codemirror/theme/abcdef.css diff --git a/codemirror/theme/ambiance-mobile.css b/vendor/codemirror/theme/ambiance-mobile.css similarity index 100% rename from codemirror/theme/ambiance-mobile.css rename to vendor/codemirror/theme/ambiance-mobile.css diff --git a/codemirror/theme/ambiance.css b/vendor/codemirror/theme/ambiance.css similarity index 100% rename from codemirror/theme/ambiance.css rename to vendor/codemirror/theme/ambiance.css diff --git a/codemirror/theme/base16-dark.css b/vendor/codemirror/theme/base16-dark.css similarity index 100% rename from codemirror/theme/base16-dark.css rename to vendor/codemirror/theme/base16-dark.css diff --git a/codemirror/theme/base16-light.css b/vendor/codemirror/theme/base16-light.css similarity index 100% rename from codemirror/theme/base16-light.css rename to vendor/codemirror/theme/base16-light.css diff --git a/codemirror/theme/bespin.css b/vendor/codemirror/theme/bespin.css similarity index 100% rename from codemirror/theme/bespin.css rename to vendor/codemirror/theme/bespin.css diff --git a/codemirror/theme/blackboard.css b/vendor/codemirror/theme/blackboard.css similarity index 100% rename from codemirror/theme/blackboard.css rename to vendor/codemirror/theme/blackboard.css diff --git a/codemirror/theme/cobalt.css b/vendor/codemirror/theme/cobalt.css similarity index 100% rename from codemirror/theme/cobalt.css rename to vendor/codemirror/theme/cobalt.css diff --git a/codemirror/theme/colorforth.css b/vendor/codemirror/theme/colorforth.css similarity index 100% rename from codemirror/theme/colorforth.css rename to vendor/codemirror/theme/colorforth.css diff --git a/codemirror/theme/dracula.css b/vendor/codemirror/theme/dracula.css similarity index 100% rename from codemirror/theme/dracula.css rename to vendor/codemirror/theme/dracula.css diff --git a/codemirror/theme/duotone-dark.css b/vendor/codemirror/theme/duotone-dark.css similarity index 100% rename from codemirror/theme/duotone-dark.css rename to vendor/codemirror/theme/duotone-dark.css diff --git a/codemirror/theme/duotone-light.css b/vendor/codemirror/theme/duotone-light.css similarity index 100% rename from codemirror/theme/duotone-light.css rename to vendor/codemirror/theme/duotone-light.css diff --git a/codemirror/theme/eclipse.css b/vendor/codemirror/theme/eclipse.css similarity index 100% rename from codemirror/theme/eclipse.css rename to vendor/codemirror/theme/eclipse.css diff --git a/codemirror/theme/elegant.css b/vendor/codemirror/theme/elegant.css similarity index 100% rename from codemirror/theme/elegant.css rename to vendor/codemirror/theme/elegant.css diff --git a/codemirror/theme/erlang-dark.css b/vendor/codemirror/theme/erlang-dark.css similarity index 100% rename from codemirror/theme/erlang-dark.css rename to vendor/codemirror/theme/erlang-dark.css diff --git a/codemirror/theme/hopscotch.css b/vendor/codemirror/theme/hopscotch.css similarity index 100% rename from codemirror/theme/hopscotch.css rename to vendor/codemirror/theme/hopscotch.css diff --git a/codemirror/theme/icecoder.css b/vendor/codemirror/theme/icecoder.css similarity index 100% rename from codemirror/theme/icecoder.css rename to vendor/codemirror/theme/icecoder.css diff --git a/codemirror/theme/isotope.css b/vendor/codemirror/theme/isotope.css similarity index 100% rename from codemirror/theme/isotope.css rename to vendor/codemirror/theme/isotope.css diff --git a/codemirror/theme/lesser-dark.css b/vendor/codemirror/theme/lesser-dark.css similarity index 100% rename from codemirror/theme/lesser-dark.css rename to vendor/codemirror/theme/lesser-dark.css diff --git a/codemirror/theme/liquibyte.css b/vendor/codemirror/theme/liquibyte.css similarity index 100% rename from codemirror/theme/liquibyte.css rename to vendor/codemirror/theme/liquibyte.css diff --git a/codemirror/theme/material.css b/vendor/codemirror/theme/material.css similarity index 100% rename from codemirror/theme/material.css rename to vendor/codemirror/theme/material.css diff --git a/codemirror/theme/mbo.css b/vendor/codemirror/theme/mbo.css similarity index 100% rename from codemirror/theme/mbo.css rename to vendor/codemirror/theme/mbo.css diff --git a/codemirror/theme/mdn-like.css b/vendor/codemirror/theme/mdn-like.css similarity index 100% rename from codemirror/theme/mdn-like.css rename to vendor/codemirror/theme/mdn-like.css diff --git a/codemirror/theme/midnight.css b/vendor/codemirror/theme/midnight.css similarity index 100% rename from codemirror/theme/midnight.css rename to vendor/codemirror/theme/midnight.css diff --git a/codemirror/theme/monokai.css b/vendor/codemirror/theme/monokai.css similarity index 100% rename from codemirror/theme/monokai.css rename to vendor/codemirror/theme/monokai.css diff --git a/codemirror/theme/neat.css b/vendor/codemirror/theme/neat.css similarity index 100% rename from codemirror/theme/neat.css rename to vendor/codemirror/theme/neat.css diff --git a/codemirror/theme/neo.css b/vendor/codemirror/theme/neo.css similarity index 100% rename from codemirror/theme/neo.css rename to vendor/codemirror/theme/neo.css diff --git a/codemirror/theme/night.css b/vendor/codemirror/theme/night.css similarity index 100% rename from codemirror/theme/night.css rename to vendor/codemirror/theme/night.css diff --git a/codemirror/theme/panda-syntax.css b/vendor/codemirror/theme/panda-syntax.css similarity index 100% rename from codemirror/theme/panda-syntax.css rename to vendor/codemirror/theme/panda-syntax.css diff --git a/codemirror/theme/paraiso-dark.css b/vendor/codemirror/theme/paraiso-dark.css similarity index 100% rename from codemirror/theme/paraiso-dark.css rename to vendor/codemirror/theme/paraiso-dark.css diff --git a/codemirror/theme/paraiso-light.css b/vendor/codemirror/theme/paraiso-light.css similarity index 100% rename from codemirror/theme/paraiso-light.css rename to vendor/codemirror/theme/paraiso-light.css diff --git a/codemirror/theme/pastel-on-dark.css b/vendor/codemirror/theme/pastel-on-dark.css similarity index 100% rename from codemirror/theme/pastel-on-dark.css rename to vendor/codemirror/theme/pastel-on-dark.css diff --git a/codemirror/theme/railscasts.css b/vendor/codemirror/theme/railscasts.css similarity index 100% rename from codemirror/theme/railscasts.css rename to vendor/codemirror/theme/railscasts.css diff --git a/codemirror/theme/rubyblue.css b/vendor/codemirror/theme/rubyblue.css similarity index 100% rename from codemirror/theme/rubyblue.css rename to vendor/codemirror/theme/rubyblue.css diff --git a/codemirror/theme/seti.css b/vendor/codemirror/theme/seti.css similarity index 100% rename from codemirror/theme/seti.css rename to vendor/codemirror/theme/seti.css diff --git a/codemirror/theme/solarized.css b/vendor/codemirror/theme/solarized.css similarity index 100% rename from codemirror/theme/solarized.css rename to vendor/codemirror/theme/solarized.css diff --git a/codemirror/theme/the-matrix.css b/vendor/codemirror/theme/the-matrix.css similarity index 100% rename from codemirror/theme/the-matrix.css rename to vendor/codemirror/theme/the-matrix.css diff --git a/codemirror/theme/tomorrow-night-bright.css b/vendor/codemirror/theme/tomorrow-night-bright.css similarity index 100% rename from codemirror/theme/tomorrow-night-bright.css rename to vendor/codemirror/theme/tomorrow-night-bright.css diff --git a/codemirror/theme/tomorrow-night-eighties.css b/vendor/codemirror/theme/tomorrow-night-eighties.css similarity index 100% rename from codemirror/theme/tomorrow-night-eighties.css rename to vendor/codemirror/theme/tomorrow-night-eighties.css diff --git a/codemirror/theme/ttcn.css b/vendor/codemirror/theme/ttcn.css similarity index 100% rename from codemirror/theme/ttcn.css rename to vendor/codemirror/theme/ttcn.css diff --git a/codemirror/theme/twilight.css b/vendor/codemirror/theme/twilight.css similarity index 100% rename from codemirror/theme/twilight.css rename to vendor/codemirror/theme/twilight.css diff --git a/codemirror/theme/vibrant-ink.css b/vendor/codemirror/theme/vibrant-ink.css similarity index 100% rename from codemirror/theme/vibrant-ink.css rename to vendor/codemirror/theme/vibrant-ink.css diff --git a/codemirror/theme/xq-dark.css b/vendor/codemirror/theme/xq-dark.css similarity index 100% rename from codemirror/theme/xq-dark.css rename to vendor/codemirror/theme/xq-dark.css diff --git a/codemirror/theme/xq-light.css b/vendor/codemirror/theme/xq-light.css similarity index 100% rename from codemirror/theme/xq-light.css rename to vendor/codemirror/theme/xq-light.css diff --git a/codemirror/theme/yeti.css b/vendor/codemirror/theme/yeti.css similarity index 100% rename from codemirror/theme/yeti.css rename to vendor/codemirror/theme/yeti.css diff --git a/codemirror/theme/zenburn.css b/vendor/codemirror/theme/zenburn.css similarity index 100% rename from codemirror/theme/zenburn.css rename to vendor/codemirror/theme/zenburn.css diff --git a/csslint/WARNING.txt b/vendor/csslint/WARNING.txt similarity index 100% rename from csslint/WARNING.txt rename to vendor/csslint/WARNING.txt diff --git a/csslint/csslint-worker.js b/vendor/csslint/csslint-worker.js similarity index 100% rename from csslint/csslint-worker.js rename to vendor/csslint/csslint-worker.js From 57ff0b6cb730dedaa534196b775e7e5f772491c8 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 12 Jul 2017 13:45:50 -0500 Subject: [PATCH 02/22] Update eslint rule --- .eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index a0e8a12e..c1fb1656 100644 --- a/.eslintrc +++ b/.eslintrc @@ -249,7 +249,7 @@ rules: sort-imports: [0] sort-keys: [0] space-before-blocks: [2, always] - space-before-function-paren: [2, never] + space-before-function-paren: [1, never] space-in-parens: [2, never] space-infix-ops: [2] space-unary-ops: [2] From bb33ff3981be9f77a7621cd407b4ab5debc7dff2 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 12 Jul 2017 13:52:44 -0500 Subject: [PATCH 03/22] Fix links & split out edit.css --- background/background.js | 2 +- edit.html | 977 ++++++------------------------ edit/edit.css | 576 ++++++++++++++++++ edit/edit.js | 8 +- index.html | 14 +- manage.html | 16 +- manifest.json | 14 +- options/index.js | 2 +- popup.html | 14 +- tools/pull_locales_postprocess.py | 4 +- 10 files changed, 815 insertions(+), 812 deletions(-) create mode 100644 edit/edit.css diff --git a/background/background.js b/background/background.js index a2ae0627..fc5a7f30 100644 --- a/background/background.js +++ b/background/background.js @@ -98,7 +98,7 @@ updateIcon({id: undefined}, {}); // browser commands browserCommands = { openManage() { - openURL({url: '/manage.html'}); + openURL({url: 'manage.html'}); }, styleDisableAll(info) { prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll')); diff --git a/edit.html b/edit.html index 2ec63cc2..fbb1ed9c 100644 --- a/edit.html +++ b/edit.html @@ -1,792 +1,213 @@ - - + + - - - - - - + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - + - - - - + + + + - - - + + + - - - + + + + - - - - - - - - - - - - - - - - - - -
-

-
-
-
-
-
- - - - - - - - - - - - - - + diff --git a/edit/edit.css b/edit/edit.css new file mode 100644 index 00000000..f41e93cb --- /dev/null +++ b/edit/edit.css @@ -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; + } +} diff --git a/edit/edit.js b/edit/edit.js index 48ee1bad..413f2a13 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -42,7 +42,7 @@ new MutationObserver((mutations, observer) => { const themeElement = document.getElementById("cm-theme"); if (themeElement) { themeElement.href = prefs.get("editor.theme") == "default" ? "" - : "codemirror/theme/" + prefs.get("editor.theme") + ".css"; + : "vendor/codemirror/theme/" + prefs.get("editor.theme") + ".css"; observer.disconnect(); } }).observe(document, {subtree: true, childList: true}); @@ -305,7 +305,7 @@ function acmeEventListener(event) { el.selectedIndex = 0; break; } - var url = chrome.runtime.getURL("codemirror/theme/" + value + ".css"); + var url = chrome.runtime.getURL("vendor/codemirror/theme/" + value + ".css"); if (themeLink.href == url) { // preloaded in initCodeMirror() break; } @@ -1116,7 +1116,7 @@ function beautify(event) { doBeautify(); } else { var script = document.head.appendChild(document.createElement("script")); - script.src = "beautify/beautify-css-mod.js"; + script.src = "vendor-overwrites/beautify/beautify-css-mod.js"; script.onload = doBeautify; } function doBeautify() { @@ -2019,7 +2019,7 @@ function getCodeMirrorThemes() { } return new Promise(resolve => { chrome.runtime.getPackageDirectoryEntry(rootDir => { - rootDir.getDirectory('codemirror/theme', {create: false}, themeDir => { + rootDir.getDirectory('vendor/codemirror/theme', {create: false}, themeDir => { themeDir.createReader().readEntries(entries => { const themes = [ chrome.i18n.getMessage('defaultTheme') diff --git a/index.html b/index.html index 3a7318be..ea19ac4f 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,12 @@ Stylus - - - - - - + + + + + + @@ -128,6 +128,6 @@ - + diff --git a/manage.html b/manage.html index 5fc34411..855db9eb 100644 --- a/manage.html +++ b/manage.html @@ -3,7 +3,7 @@ - + @@ -121,12 +121,12 @@ - - - - - - + + + + + + @@ -216,7 +216,7 @@
- + diff --git a/manifest.json b/manifest.json index 4f2ccca1..97b600e4 100644 --- a/manifest.json +++ b/manifest.json @@ -19,7 +19,13 @@ "" ], "background": { - "scripts": ["messaging.js", "storage.js", "prefs.js", "background.js", "update.js"] + "scripts": [ + "background/messaging.js", + "background/storage.js", + "js/prefs.js", + "background/background.js", + "background/update.js" + ] }, "commands": { "openManage": { @@ -35,13 +41,13 @@ "run_at": "document_start", "all_frames": true, "match_about_blank": true, - "js": ["apply.js"] + "js": ["content/apply.js"] }, { "matches": ["http://userstyles.org/*", "https://userstyles.org/*"], "run_at": "document_start", "all_frames": false, - "js": ["install.js"] + "js": ["content/install.js"] } ], "browser_action": { @@ -56,7 +62,7 @@ }, "default_locale": "en", "options_ui": { - "page": "options/index.html", + "page": "index.html", "chrome_style": true } } diff --git a/options/index.js b/options/index.js index decc038d..68e6b955 100644 --- a/options/index.js +++ b/options/index.js @@ -15,7 +15,7 @@ document.onclick = e => { switch (target.dataset.cmd) { case 'open-manage': - openURL({url: '/manage.html'}); + openURL({url: 'manage.html'}); break; case 'check-updates': diff --git a/popup.html b/popup.html index 7684eb05..fe5c967f 100644 --- a/popup.html +++ b/popup.html @@ -2,7 +2,7 @@ - + +* 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. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..a44bd728 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ + + +* **Browser**: +* **Operating System**: +* **Screenshot**: + +* **HTML of the section where the issue occurs**: + + + +````html + +```` diff --git a/README.md b/README.md index f5e0f4ff..cc1c379b 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,15 @@ See the [help docs](http://userstyles.org/help/stylish_chrome) or [ask in userst ## 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 -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 From f49747e1bd12c087de883709a1208b80419019af Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Thu, 13 Jul 2017 19:50:24 -0500 Subject: [PATCH 07/22] Adjust pull_locales paths --- tools/pull_locales.rb | 2 +- tools/pull_locales_postprocess.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/pull_locales.rb b/tools/pull_locales.rb index 967cee9b..23ff1225 100644 --- a/tools/pull_locales.rb +++ b/tools/pull_locales.rb @@ -22,7 +22,7 @@ project = transifex.project(project_slug) project.languages.each do |language| code = language.language_code puts "Getting locale #{code}" - dir_name = "_locales/#{code}" + dir_name = "../_locales/#{code}" Dir.mkdir(dir_name) if !Dir.exist?(dir_name) has_content = false project.resources.each do |resource| diff --git a/tools/pull_locales_postprocess.py b/tools/pull_locales_postprocess.py index d9d0b43a..a4cdb6c2 100644 --- a/tools/pull_locales_postprocess.py +++ b/tools/pull_locales_postprocess.py @@ -2,19 +2,19 @@ import io, os, json, re 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() english = [(k, v['message']) for k, v in items if 'message' in v] english_placeholders = [(k, v['placeholders']) for k,v in items if 'placeholders' in v] -for locale_name in os.listdir('_locales'): +for locale_name in os.listdir('../_locales'): if locale_name == 'en': continue if not re.match(r'^\w{2}(_\w{2,3})?$', locale_name): print('Skipped %s: not a locale dir' % locale_name) 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: loc = json.load(f, object_pairs_hook=OrderedDict) From ac91e25eb42cae49198a735adadd7c2b4de31df3 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Thu, 13 Jul 2017 19:52:40 -0500 Subject: [PATCH 08/22] Move messaging.js file & references --- edit.html | 2 +- index.html | 2 +- {background => js}/messaging.js | 0 manage.html | 2 +- manifest.json | 2 +- popup.html | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename {background => js}/messaging.js (100%) diff --git a/edit.html b/edit.html index fbb1ed9c..5df54d54 100644 --- a/edit.html +++ b/edit.html @@ -3,7 +3,7 @@ - + diff --git a/index.html b/index.html index ea19ac4f..c673ac44 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ Stylus - + diff --git a/background/messaging.js b/js/messaging.js similarity index 100% rename from background/messaging.js rename to js/messaging.js diff --git a/manage.html b/manage.html index 855db9eb..b3bb0545 100644 --- a/manage.html +++ b/manage.html @@ -122,7 +122,7 @@ - + diff --git a/manifest.json b/manifest.json index 97b600e4..2070b4ba 100644 --- a/manifest.json +++ b/manifest.json @@ -20,7 +20,7 @@ ], "background": { "scripts": [ - "background/messaging.js", + "js/messaging.js", "background/storage.js", "js/prefs.js", "background/background.js", diff --git a/popup.html b/popup.html index fe5c967f..933df669 100644 --- a/popup.html +++ b/popup.html @@ -56,7 +56,7 @@ - + From 370a0a2c35e85aae73b877f40585472ac6a9ac56 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 03:21:07 -0500 Subject: [PATCH 09/22] Remove duplicated code in fileSaveLoad.js --- manage/fileSaveLoad.js | 396 ----------------------------------------- 1 file changed, 396 deletions(-) diff --git a/manage/fileSaveLoad.js b/manage/fileSaveLoad.js index 125ed9e0..9d37fc1b 100644 --- a/manage/fileSaveLoad.js +++ b/manage/fileSaveLoad.js @@ -352,402 +352,6 @@ $('#file-all-styles').onclick = () => { }; -$('#unfile-all-styles').onclick = () => { - importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT}); -}; - -Object.assign(document.body, { - ondragover(event) { - const hasFiles = event.dataTransfer.types.includes('Files'); - event.dataTransfer.dropEffect = hasFiles || event.target.type == 'search' ? 'copy' : 'none'; - this.classList.toggle('dropzone', hasFiles); - if (hasFiles) { - event.preventDefault(); - clearTimeout(this.fadeoutTimer); - this.classList.remove('fadeout'); - } - }, - ondragend(event) { - animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => { - this.style.animationDuration = ''; - }); - }, - ondragleave(event) { - try { - // in Firefox event.target could be XUL browser and hence there is no permission to access it - if (event.target === this) { - this.ondragend(); - } - } catch (e) { - this.ondragend(); - } - }, - ondrop(event) { - this.ondragend(); - if (event.dataTransfer.files.length) { - event.preventDefault(); - if ($('#onlyUpdates input').checked) { - $('#onlyUpdates input').click(); - } - importFromFile({file: event.dataTransfer.files[0]}); - } - }, -}); -======= -/* global messageBox, handleUpdate, applyOnMessage */ -'use strict'; - -const STYLISH_DUMP_FILE_EXT = '.txt'; -const STYLUS_BACKUP_FILE_EXT = '.json'; - - -function importFromFile({fileTypeFilter, file} = {}) { - return new Promise(resolve => { - const fileInput = document.createElement('input'); - if (file) { - readFile(); - return; - } - fileInput.style.display = 'none'; - fileInput.type = 'file'; - fileInput.accept = fileTypeFilter || STYLISH_DUMP_FILE_EXT; - fileInput.acceptCharset = 'utf-8'; - - document.body.appendChild(fileInput); - fileInput.initialValue = fileInput.value; - fileInput.onchange = readFile; - fileInput.click(); - - function readFile() { - if (file || fileInput.value !== fileInput.initialValue) { - file = file || fileInput.files[0]; - if (file.size > 100e6) { - console.warn("100MB backup? I don't believe you."); - importFromString('').then(resolve); - return; - } - document.body.style.cursor = 'wait'; - const fReader = new FileReader(); - fReader.onloadend = event => { - fileInput.remove(); - importFromString(event.target.result).then(numStyles => { - document.body.style.cursor = ''; - resolve(numStyles); - }); - }; - fReader.readAsText(file, 'utf-8'); - } - } - }); -} - - -function importFromString(jsonString) { - if (!BG) { - onBackgroundReady().then(() => importFromString(jsonString)); - return; - } - // create objects in background context - const json = BG.tryJSONparse(jsonString) || []; - if (typeof json.slice != 'function') { - json.length = 0; - } - const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []); - const oldStylesByName = json.length && new Map( - oldStyles.map(style => [style.name.trim(), style])); - - const stats = { - added: {names: [], ids: [], legend: 'importReportLegendAdded'}, - unchanged: {names: [], ids: [], legend: 'importReportLegendIdentical'}, - metaAndCode: {names: [], ids: [], legend: 'importReportLegendUpdatedBoth'}, - metaOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedMeta'}, - codeOnly: {names: [], ids: [], legend: 'importReportLegendUpdatedCode'}, - invalid: {names: [], legend: 'importReportLegendInvalid'}, - }; - - let index = 0; - let lastRenderTime = performance.now(); - const renderQueue = []; - const RENDER_NAP_TIME_MAX = 1000; // ms - const RENDER_QUEUE_MAX = 50; // number of styles - const SAVE_OPTIONS = {reason: 'import', notify: false}; - - return new Promise(proceed); - - function proceed(resolve) { - while (index < json.length) { - const item = json[index++]; - const info = analyze(item); - if (info) { - // using saveStyle directly since json was parsed in background page context - return BG.saveStyle(Object.assign(item, SAVE_OPTIONS)) - .then(style => account({style, info, resolve})); - } - } - renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); - renderQueue.length = 0; - done(resolve); - } - - function analyze(item) { - if (!item || !item.name || !item.name.trim() || typeof item != 'object' - || (item.sections && typeof item.sections.slice != 'function')) { - stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`); - return; - } - item.name = item.name.trim(); - const byId = BG.cachedStyles.byId.get(item.id); - const byName = oldStylesByName.get(item.name); - oldStylesByName.delete(item.name); - let oldStyle; - if (byId) { - if (sameStyle(byId, item)) { - oldStyle = byId; - } else { - item.id = null; - } - } - if (!oldStyle && byName) { - item.id = byName.id; - oldStyle = byName; - } - const oldStyleKeys = oldStyle && Object.keys(oldStyle); - const metaEqual = oldStyleKeys && - oldStyleKeys.length == Object.keys(item).length && - oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]); - const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item); - if (metaEqual && codeEqual) { - stats.unchanged.names.push(oldStyle.name); - stats.unchanged.ids.push(oldStyle.id); - return; - } - return {oldStyle, metaEqual, codeEqual}; - } - - function sameStyle(oldStyle, newStyle) { - return oldStyle.name.trim() === newStyle.name.trim() || - ['updateUrl', 'originalMd5', 'originalDigest'] - .some(field => oldStyle[field] && oldStyle[field] == newStyle[field]); - } - - function account({style, info, resolve}) { - renderQueue.push(style); - if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX - || renderQueue.length > RENDER_QUEUE_MAX) { - renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); - setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id)); - renderQueue.length = 0; - lastRenderTime = performance.now(); - } - setTimeout(proceed, 0, resolve); - const {oldStyle, metaEqual, codeEqual} = info; - if (!oldStyle) { - stats.added.names.push(style.name); - stats.added.ids.push(style.id); - return; - } - if (!metaEqual && !codeEqual) { - stats.metaAndCode.names.push(reportNameChange(oldStyle, style)); - stats.metaAndCode.ids.push(style.id); - return; - } - if (!codeEqual) { - stats.codeOnly.names.push(style.name); - stats.codeOnly.ids.push(style.id); - return; - } - stats.metaOnly.names.push(reportNameChange(oldStyle, style)); - stats.metaOnly.ids.push(style.id); - } - - function done(resolve) { - const numChanged = stats.metaAndCode.names.length + - stats.metaOnly.names.length + - stats.codeOnly.names.length + - stats.added.names.length; - Promise.resolve(numChanged && refreshAllTabs()).then(() => { - const report = Object.keys(stats) - .filter(kind => stats[kind].names.length) - .map(kind => { - const {ids, names, legend} = stats[kind]; - const listItemsWithId = (name, i) => - $element({dataset: {id: ids[i]}, textContent: name}); - const listItems = name => - $element({textContent: name}); - const block = - $element({tag: 'details', dataset: {id: kind}, appendChild: [ - $element({tag: 'summary', appendChild: - $element({tag: 'b', textContent: names.length + ' ' + t(legend)}) - }), - $element({tag: 'small', appendChild: - names.map(ids ? listItemsWithId : listItems) - }), - ]}); - return block; - }); - scrollTo(0, 0); - messageBox({ - title: t('importReportTitle'), - contents: report.length ? report : t('importReportUnchanged'), - buttons: [t('confirmOK'), numChanged && t('undo')], - onshow: bindClick, - }).then(({button, enter, esc}) => { - if (button == 1) { - undo(); - } - }); - resolve(numChanged); - }); - } - - function undo() { - const oldStylesById = new Map(oldStyles.map(style => [style.id, style])); - const newIds = [ - ...stats.metaAndCode.ids, - ...stats.metaOnly.ids, - ...stats.codeOnly.ids, - ...stats.added.ids, - ]; - let resolve; - index = 0; - return new Promise(resolve_ => { - resolve = resolve_; - undoNextId(); - }).then(refreshAllTabs) - .then(() => messageBox({ - title: t('importReportUndoneTitle'), - contents: newIds.length + ' ' + t('importReportUndone'), - buttons: [t('confirmOK')], - })); - function undoNextId() { - if (index == newIds.length) { - resolve(); - return; - } - const id = newIds[index++]; - deleteStyleSafe({id, notify: false}).then(id => { - const oldStyle = oldStylesById.get(id); - if (oldStyle) { - saveStyleSafe(Object.assign(oldStyle, SAVE_OPTIONS)) - .then(undoNextId); - } else { - undoNextId(); - } - }); - } - } - - function bindClick(box) { - const highlightElement = event => { - const styleElement = $('#style-' + event.target.dataset.id); - if (styleElement) { - scrollElementIntoView(styleElement); - animateElement(styleElement); - } - }; - for (const block of $$('details')) { - if (block.dataset.id != 'invalid') { - block.style.cursor = 'pointer'; - block.onclick = highlightElement; - } - } - } - - function limitString(s, limit = 100) { - return s.length <= limit ? s : s.substr(0, limit) + '...'; - } - - function reportNameChange(oldStyle, newStyle) { - return newStyle.name != oldStyle.name - ? oldStyle.name + ' —> ' + newStyle.name - : oldStyle.name; - } - - function refreshAllTabs() { - return Promise.all([ - getActiveTab(), - getOwnTab(), - ]).then(([activeTab, ownTab]) => new Promise(resolve => { - // list all tabs including chrome-extension:// which can be ours - queryTabs().then(tabs => { - const lastTab = tabs[tabs.length - 1]; - for (const tab of tabs) { - // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF - if (FIREFOX && !tab.width) { - if (tab == lastTab) { - resolve(); - } - continue; - } - getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => { - const message = {method: 'styleReplaceAll', styles}; - if (tab.id == ownTab.id) { - applyOnMessage(message); - } else { - invokeOrPostpone(tab.id == activeTab.id, - chrome.tabs.sendMessage, tab.id, message, ignoreChromeError); - } - setTimeout(BG.updateIcon, 0, tab, styles); - if (tab == lastTab) { - resolve(); - } - }); - } - }); - })); - } -} - - -$('#file-all-styles').onclick = () => { - getStylesSafe().then(styles => { - const text = JSON.stringify(styles, null, '\t'); - const url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text); - return url; - // for long URLs; https://github.com/schomery/stylus/issues/13#issuecomment-284582600 - }).then(fetch) - .then(res => res.blob()) - .then(blob => { - const objectURL = URL.createObjectURL(blob); - let link = $element({ - tag:'a', - href: objectURL, - type: 'application/json', - download: generateFileName(), - }); - // TODO: remove the fallback when FF multi-process bug is fixed - if (!FIREFOX) { - link.dispatchEvent(new MouseEvent('click')); - setTimeout(() => URL.revokeObjectURL(objectURL)); - } else { - const iframe = document.body.appendChild($element({ - tag: 'iframe', - style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'), - })); - doTimeout().then(() => { - link = iframe.contentDocument.importNode(link, true); - iframe.contentDocument.body.appendChild(link); - }) - .then(doTimeout) - .then(() => link.dispatchEvent(new MouseEvent('click'))) - .then(doTimeout(1000)) - .then(() => { - URL.revokeObjectURL(objectURL); - iframe.remove(); - }); - } - }); - - function generateFileName() { - const today = new Date(); - const dd = ('0' + today.getDate()).substr(-2); - const mm = ('0' + (today.getMonth() + 1)).substr(-2); - const yyyy = today.getFullYear(); - return `stylus-${mm}-${dd}-${yyyy}${STYLUS_BACKUP_FILE_EXT}`; - } -}; - - $('#unfile-all-styles').onclick = () => { importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT}); }; From 4cbd48b9f959fe2c81cb47e641c92fa247388551 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 03:25:33 -0500 Subject: [PATCH 10/22] Rename options.html & fix path --- js/messaging.js | 2 +- manifest.json | 2 +- index.html => options.html | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename index.html => options.html (100%) diff --git a/js/messaging.js b/js/messaging.js index ac529c92..a1e1c4b0 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -11,7 +11,7 @@ const URLS = { ownOrigin: chrome.runtime.getURL(''), optionsUI: [ - chrome.runtime.getURL('options/index.html'), + chrome.runtime.getURL('options.html'), 'chrome://extensions/?options=' + chrome.runtime.id, ], diff --git a/manifest.json b/manifest.json index 2070b4ba..958f5a5e 100644 --- a/manifest.json +++ b/manifest.json @@ -62,7 +62,7 @@ }, "default_locale": "en", "options_ui": { - "page": "index.html", + "page": "options.html", "chrome_style": true } } diff --git a/index.html b/options.html similarity index 100% rename from index.html rename to options.html From 61327bfaf1f7826bacd0912d3e782ced29844fa4 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 03:33:59 -0500 Subject: [PATCH 11/22] Restore case declarations in edit.js --- edit/edit.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/edit/edit.js b/edit/edit.js index ed9dd8d6..7e1d4856 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -295,13 +295,13 @@ function acmeEventListener(event) { return; } let value = el.type == 'checkbox' ? el.checked : el.value; - let url; - const themeLink = document.getElementById('cm-theme'); switch (option) { case 'tabSize': CodeMirror.setOption('indentUnit', Number(value)); break; + /* eslint-disable no-case-declarations */ case 'theme': + const themeLink = document.getElementById('cm-theme'); // use non-localized 'default' internally if (!value || value == 'default' || value == t('defaultTheme')) { value = 'default'; @@ -312,7 +312,7 @@ function acmeEventListener(event) { el.selectedIndex = 0; break; } - url = chrome.runtime.getURL('vendor/codemirror/theme/' + value + '.css'); + const url = chrome.runtime.getURL('vendor/codemirror/theme/' + value + '.css'); if (themeLink.href == url) { // preloaded in initCodeMirror() break; } @@ -327,6 +327,7 @@ function acmeEventListener(event) { }, 100); })(); return; + /* eslint-enable no-case-declarations */ case 'autocompleteOnTyping': editors.forEach(cm => { const onOff = el.checked ? 'on' : 'off'; From 1a630033bd5b7b00e8a9a4cb5f4d50abf20b40f6 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 03:46:10 -0500 Subject: [PATCH 12/22] Use a single declaration per line --- background/storage.js | 6 ++++-- edit/edit.js | 11 +++++++---- vendor-overwrites/beautify/beautify-css-mod.js | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/background/storage.js b/background/storage.js index 0791d6b3..00b4f392 100644 --- a/background/storage.js +++ b/background/storage.js @@ -189,7 +189,8 @@ function filterStylesInternal({ const needSections = asHash || matchUrl !== null; - for (let i = 0, style; (style = styles[i]); i++) { + let style; + for (let i = 0; (style = styles[i]); i++) { if ((enabled === null || style.enabled == enabled) && (url === null || style.url == url) && (id === null || style.id == id)) { @@ -230,7 +231,8 @@ function saveStyle(style) { if (!style.name) { delete style.name; } - let existed, codeIsUpdated; + let existed; + let codeIsUpdated; if (reason == 'update' || reason == 'update-digest') { return calcStyleDigest(style).then(digest => { style.originalDigest = digest; diff --git a/edit/edit.js b/edit/edit.js index 7e1d4856..6997f63c 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -602,8 +602,8 @@ function addSection(event, section) { } function removeAppliesTo(event) { - const appliesTo = event.target.parentNode, - appliesToList = appliesTo.parentNode; + const appliesTo = event.target.parentNode; + const appliesToList = appliesTo.parentNode; removeAreaAndSetDirty(appliesTo); if (!appliesToList.hasChildNodes()) { addAppliesTo(appliesToList); @@ -744,7 +744,8 @@ function setupGlobalSearch() { && searchAppliesTo(activeCM)) { return; } - for (let i = 0, cm = activeCM; i < editors.length; i++) { + let cm = activeCM; + for (let i = 0; i < editors.length; i++) { state = updateState(cm); if (!cm.hasFocus()) { pos = reverse ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(0, 0); @@ -799,7 +800,9 @@ function setupGlobalSearch() { } function replace(activeCM, all) { - let queue, query, replacement; + let queue; + let query; + let replacement; activeCM = focusClosestCM(activeCM); customizeOpenDialog(activeCM, template[all ? 'replaceAll' : 'replace'], function(txt) { query = txt; diff --git a/vendor-overwrites/beautify/beautify-css-mod.js b/vendor-overwrites/beautify/beautify-css-mod.js index b5baa1fd..a03fd7d8 100644 --- a/vendor-overwrites/beautify/beautify-css-mod.js +++ b/vendor-overwrites/beautify/beautify-css-mod.js @@ -364,7 +364,8 @@ } } outputPosCol = 0; - let i = output.length, token; + let i = output.length; + let token; while (--i >= 0 && (token = output[i]) != '\n') { outputPosCol += token.length; } From a3e149a30ed1fa6ba06571bcd57ff4ebe6904eb0 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 03:57:28 -0500 Subject: [PATCH 13/22] Fix & rename scoped state variable --- edit/edit.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/edit/edit.js b/edit/edit.js index 6997f63c..9c4ac192 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -1014,7 +1014,8 @@ function updateLintReport(cm, delay) { setTimeout(cm => { cm.performLint(); update(cm); }, delay, cm); return; } - const state = cm.state.lint; + // eslint-disable-next-line no-var + var state = cm.state.lint; if (!state) { return; } @@ -1028,13 +1029,12 @@ function updateLintReport(cm, delay) { const scope = cm ? [cm] : editors; let changed = false; let fixedOldIssues = false; - let state; scope.forEach(function(cm) { - state = cm.state.lint || {}; - const oldMarkers = state.markedLast || {}; + const scopedState = cm.state.lint || {}; + const oldMarkers = scopedState.markedLast || {}; const newMarkers = {}; - const html = !state.marked || state.marked.length == 0 ? '' : '' + - state.marked.map(function(mark) { + const html = !scopedState.marked || scopedState.marked.length == 0 ? '' : '' + + scopedState.marked.map(function(mark) { const info = mark.__annotation; const isActiveLine = info.from.line == cm.getCursor().line; const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch); @@ -1054,10 +1054,10 @@ function updateLintReport(cm, delay) { '' + (info.from.ch + 1) + '' + '' + message + ''; }).join('') + ''; - state.markedLast = newMarkers; - fixedOldIssues |= state.reportDisplayed && Object.keys(oldMarkers).length > 0; - if (state.html != html) { - state.html = html; + scopedState.markedLast = newMarkers; + fixedOldIssues |= scopedState.reportDisplayed && Object.keys(oldMarkers).length > 0; + if (scopedState.html != html) { + scopedState.html = html; changed = true; } }); From 01e63d5b1a89a18613259073558a930d6ab74841 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 03:59:13 -0500 Subject: [PATCH 14/22] Remove escaped single quote --- edit/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit/edit.js b/edit/edit.js index 9c4ac192..c607ad85 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -1073,7 +1073,7 @@ function updateLintReport(cm, delay) { } } function escapeHtml(html) { - const chars = {'&': '&', '<': '<', '>': '>', '"': '"', '\'': ''', '/': '/'}; + const chars = {'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/'}; return html.replace(/[&<>"'/]/g, function(char) { return chars[char]; }); } } From 81484bbc37f71decf20aa060d1e8de9752779f19 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 04:02:04 -0500 Subject: [PATCH 15/22] Fix for-loop index declarations --- edit/edit.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/edit/edit.js b/edit/edit.js index c607ad85..2415642b 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -547,8 +547,7 @@ function addSection(event, section) { if (section) { codeElement.value = section.code; - let i; - for (i in propertyToCss) { + for (const i in propertyToCss) { if (section[i]) { section[i].forEach(function(url) { addAppliesTo(appliesTo, propertyToCss[i], url); @@ -1234,8 +1233,7 @@ function init() { // This is an add tE('heading', 'addStyleTitle'); const section = {code: ''}; - let i; - for (i in CssToProperty) { + for (const i in CssToProperty) { if (params[i]) { section[CssToProperty[i]] = [params[i]]; } @@ -1518,8 +1516,7 @@ function showMozillaFormat() { function toMozillaFormat() { return getSectionsHashes().map(function(section) { let cssMds = []; - let i; - for (i in propertyToCss) { + for (const i in propertyToCss) { if (section[i]) { cssMds = cssMds.concat(section[i].map(function(v) { return propertyToCss[i] + '("' + v.replace(/\\/g, '\\\\') + '")'; From 11d8687af7bc6c3c54cca762a0d8540e074b2e86 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 14 Jul 2017 04:42:55 -0500 Subject: [PATCH 16/22] Wrap case statement to fix eslint issue --- edit/edit.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/edit/edit.js b/edit/edit.js index 2415642b..53053f95 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -299,8 +299,7 @@ function acmeEventListener(event) { case 'tabSize': CodeMirror.setOption('indentUnit', Number(value)); break; - /* eslint-disable no-case-declarations */ - case 'theme': + case 'theme': { const themeLink = document.getElementById('cm-theme'); // use non-localized 'default' internally if (!value || value == 'default' || value == t('defaultTheme')) { @@ -327,7 +326,7 @@ function acmeEventListener(event) { }, 100); })(); return; - /* eslint-enable no-case-declarations */ + } case 'autocompleteOnTyping': editors.forEach(cm => { const onOff = el.checked ? 'on' : 'off'; From a1bec922ef0b372b6e249a43a4fee55e967b21ed Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Jul 2017 08:55:16 -0500 Subject: [PATCH 17/22] Update eslint rules --- .eslintrc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.eslintrc b/.eslintrc index c1fb1656..04989f20 100644 --- a/.eslintrc +++ b/.eslintrc @@ -132,7 +132,7 @@ rules: no-duplicate-imports: [2] no-else-return: [0] no-empty-character-class: [2] - no-empty-function: [0] + no-empty-function: [1] no-empty-pattern: [2] no-empty: [2, {allowEmptyCatch: true}] no-eq-null: [2] @@ -166,7 +166,7 @@ rules: no-mixed-operators: [0] no-mixed-requires: [2, true] no-mixed-spaces-and-tabs: [2] - no-multi-spaces: [0] + no-multi-spaces: [2, {ignoreEOLComments: true}] no-multi-str: [2] no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}] no-native-reassign: [2] @@ -249,7 +249,7 @@ rules: sort-imports: [0] sort-keys: [0] space-before-blocks: [2, always] - space-before-function-paren: [1, never] + space-before-function-paren: [2, {anonymous: always, asyncArrow: always, named: never}] space-in-parens: [2, never] space-infix-ops: [2] space-unary-ops: [2] From 1940318f0fc21b67fb06209a84104b61e8d13ff4 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Jul 2017 11:49:31 -0500 Subject: [PATCH 18/22] Fix eslint issues & use arrow functions --- content/install.js | 4 +- edit/edit.js | 213 ++++++++++++++++++++++----------------------- manage/manage.js | 2 +- options/index.js | 2 +- popup/popup.js | 2 +- 5 files changed, 110 insertions(+), 113 deletions(-) diff --git a/content/install.js b/content/install.js index f8bba479..89600266 100644 --- a/content/install.js +++ b/content/install.js @@ -22,14 +22,14 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { // TODO: remove the following statement when USO is fixed document.documentElement.appendChild(document.createElement('script')).text = '(' + - function() { + function () { let settings; document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) { document.removeEventListener('stylusFixBuggyUSOsettings', _); settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search); }); const originalResponseJson = Response.prototype.json; - Response.prototype.json = function(...args) { + Response.prototype.json = function (...args) { return originalResponseJson.call(this, ...args).then(json => { Response.prototype.json = originalResponseJson; if (!settings || typeof ((json || {}).style_settings || {}).every != 'function') { diff --git a/edit/edit.js b/edit/edit.js index 53053f95..6eb0406b 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -16,7 +16,7 @@ const CssToProperty = {'url': 'urls', 'url-prefix': 'urlPrefixes', 'domain': 'do onBackgroundReady(); // make querySelectorAll enumeration code readable -['forEach', 'some', 'indexOf', 'map'].forEach(function(method) { +['forEach', 'some', 'indexOf', 'map'].forEach(method => { NodeList.prototype[method] = Array.prototype[method]; }); @@ -24,7 +24,7 @@ onBackgroundReady(); Element.prototype.matches = Element.prototype.matches || Element.prototype.webkitMatchesSelector; // Chrome pre-41 polyfill -Element.prototype.closest = Element.prototype.closest || function(selector) { +Element.prototype.closest = Element.prototype.closest || function (selector) { let e; // eslint-disable-next-line no-empty for (e = this; e && !e.matches(selector); e = e.parentElement) {} @@ -32,14 +32,14 @@ Element.prototype.closest = Element.prototype.closest || function(selector) { }; // eslint-disable-next-line no-extend-native -Array.prototype.rotate = function(amount) { // negative amount == rotate left +Array.prototype.rotate = function (amount) { // negative amount == rotate left const r = this.slice(-amount, this.length); Array.prototype.push.apply(r, this.slice(0, this.length - r.length)); return r; }; // eslint-disable-next-line no-extend-native -Object.defineProperty(Array.prototype, 'last', {get: function() { return this[this.length - 1]; }}); +Object.defineProperty(Array.prototype, 'last', {get: function () { return this[this.length - 1]; }}); // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs() new MutationObserver((mutations, observer) => { @@ -60,12 +60,12 @@ const hotkeyRerouter = { find: true, findNext: true, findPrev: true, replace: true, replaceAll: true, toggleStyle: true, }, - setState: function(enable) { - setTimeout(function() { + setState: enable => { + setTimeout(() => { document[(enable ? 'add' : 'remove') + 'EventListener']('keydown', hotkeyRerouter.eventHandler); }, 0); }, - eventHandler: function(event) { + eventHandler: event => { const keyName = CodeMirror.keyName(event); if ( CodeMirror.lookupKey(keyName, CodeMirror.getOption('keyMap'), handleCommand) == 'handled' || @@ -139,7 +139,7 @@ function setCleanGlobal() { } function setCleanSection(section) { - section.querySelectorAll('.style-contributor').forEach(function(node) { setCleanItem(node, true); }); + section.querySelectorAll('.style-contributor').forEach(node => { setCleanItem(node, true); }); // #header section has no codemirror const cm = section.CodeMirror; @@ -182,10 +182,10 @@ function initCodeMirror() { // additional commands CM.commands.jumpToLine = jumpToLine; - CM.commands.nextEditor = function(cm) { nextPrevEditor(cm, 1); }; - CM.commands.prevEditor = function(cm) { nextPrevEditor(cm, -1); }; + CM.commands.nextEditor = cm => { nextPrevEditor(cm, 1); }; + CM.commands.prevEditor = cm => { nextPrevEditor(cm, -1); }; CM.commands.save = save; - CM.commands.blockComment = function(cm) { + CM.commands.blockComment = cm => { cm.blockComment(cm.getCursor('from'), cm.getCursor('to'), {fullLines: false}); }; CM.commands.toggleStyle = toggleStyle; @@ -193,7 +193,7 @@ function initCodeMirror() { // 'basic' keymap only has basic keys by design, so we skip it const extraKeysCommands = {}; - Object.keys(CM.defaults.extraKeys).forEach(function(key) { + Object.keys(CM.defaults.extraKeys).forEach(key => { extraKeysCommands[CM.defaults.extraKeys[key]] = true; }); if (!extraKeysCommands.jumpToLine) { @@ -222,18 +222,18 @@ function initCodeMirror() { } // try to remap non-interceptable Ctrl-(Shift-)N/T/W hotkeys - ['N', 'T', 'W'].forEach(function(char) { + ['N', 'T', 'W'].forEach(char => { [{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']}, {from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']} // Note: modifier order in CM is S-C-A - ].forEach(function(remap) { + ].forEach(remap => { const oldKey = remap.from + char; - Object.keys(CM.keyMap).forEach(function(keyMapName) { + Object.keys(CM.keyMap).forEach(keyMapName => { const keyMap = CM.keyMap[keyMapName]; const command = keyMap[oldKey]; if (!command) { return; } - remap.to.some(function(newMod) { + remap.to.some(newMod => { const newKey = newMod + char; if (!(newKey in keyMap)) { delete keyMap[oldKey]; @@ -247,23 +247,21 @@ function initCodeMirror() { } // user option values - CM.getOption = function(o) { - return CodeMirror.defaults[o]; - }; - CM.setOption = function(o, v) { + CM.getOption = o => CodeMirror.defaults[o]; + CM.setOption = (o, v) => { CodeMirror.defaults[o] = v; - editors.forEach(function(editor) { + editors.forEach(editor => { editor.setOption(o, v); }); }; - CM.prototype.getSection = function() { + CM.prototype.getSection = function () { return this.display.wrapper.parentNode; }; // initialize global editor controls function optionsHtmlFromArray(options) { - return options.map(function(opt) { return ''; }).join(''); + return options.map(opt => '').join(''); } const themeControl = document.getElementById('editor.theme'); const themeList = localStorage.codeMirrorThemes; @@ -318,8 +316,8 @@ function acmeEventListener(event) { // avoid flicker: wait for the second stylesheet to load, then apply the theme document.head.insertAdjacentHTML('beforeend', ''); - (function() { - setTimeout(function() { + (() => { + setTimeout(() => { CodeMirror.setOption(option, value); themeLink.remove(); document.getElementById('cm-theme2').id = 'cm-theme'; @@ -426,13 +424,13 @@ function getSections() { // remind Chrome to repaint a previously invisible editor box by toggling any element's transform // this bug is present in some versions of Chrome (v37-40 or something) -document.addEventListener('scroll', function() { +document.addEventListener('scroll', () => { const style = document.getElementById('name').style; style.webkitTransform = style.webkitTransform ? '' : 'scale(1)'; }); // Shift-Ctrl-Wheel scrolls entire page even when mouse is over a code editor -document.addEventListener('wheel', function(event) { +document.addEventListener('wheel', event => { if (event.shiftKey && event.ctrlKey && !event.altKey && !event.metaKey) { // Chrome scrolls horizontally when Shift is pressed but on some PCs this might be different window.scrollBy(0, event.deltaX || event.deltaY); @@ -450,7 +448,7 @@ queryTabs({currentWindow: true}).then(tabs => { chrome.windows.update(windowId, prefs.get('windowPosition')); } if (tabs.length == 1 && window.history.length == 1) { - chrome.windows.getAll(function(windows) { + chrome.windows.getAll(windows => { if (windows.length > 1) { sessionStorageHash('saveSizeOnClose').set(windowId, true); saveSizeOnClose = true; @@ -460,7 +458,7 @@ queryTabs({currentWindow: true}).then(tabs => { saveSizeOnClose = sessionStorageHash('saveSizeOnClose').value[windowId]; } } - chrome.tabs.onRemoved.addListener(function(tabId, info) { + chrome.tabs.onRemoved.addListener((tabId, info) => { sessionStorageHash('manageStylesHistory').unset(tabId); if (info.windowId == windowId && info.isWindowClosing) { sessionStorageHash('saveSizeOnClose').unset(windowId); @@ -489,7 +487,7 @@ function isWindowMaximized() { && window.outerHeight == screen.availHeight; } -window.onbeforeunload = function() { +window.onbeforeunload = () => { if (saveSizeOnClose && !isWindowMaximized()) { prefs.set('windowPosition', { left: screenLeft, @@ -527,7 +525,7 @@ function addAppliesTo(list, name, value) { } else { e = template.appliesToEverything.cloneNode(true); } - e.querySelector('.add-applies-to').addEventListener('click', function() { + e.querySelector('.add-applies-to').addEventListener('click', function () { addAppliesTo(this.parentNode.parentNode); }, false); list.appendChild(e); @@ -548,7 +546,7 @@ function addSection(event, section) { codeElement.value = section.code; for (const i in propertyToCss) { if (section[i]) { - section[i].forEach(function(url) { + section[i].forEach(url => { addAppliesTo(appliesTo, propertyToCss[i], url); appliesToAdded = true; }); @@ -621,7 +619,7 @@ function removeAreaAndSetDirty(area) { if (!contributors.length) { setCleanItem(area, false); } - contributors.some(function(node) { + contributors.some(node => { if (node.savedValue) { // it's a saved section, so make it dirty and stop the enumeration setCleanItem(area, false); @@ -688,11 +686,11 @@ function setupGlobalSearch() { // temporarily overrides the original openDialog with the provided template's innerHTML function customizeOpenDialog(cm, template, callback) { - cm.openDialog = function(tmpl, cb, opt) { + cm.openDialog = (tmpl, cb, opt) => { // invoke 'callback' and bind 'this' to the original callback originalOpenDialog.call(cm, template.innerHTML, callback.bind(cb), opt); }; - setTimeout(function() { cm.openDialog = originalOpenDialog; }, 0); + setTimeout(() => { cm.openDialog = originalOpenDialog; }, 0); refocusMinidialog(cm); } @@ -707,13 +705,13 @@ function setupGlobalSearch() { function find(activeCM) { activeCM = focusClosestCM(activeCM); - customizeOpenDialog(activeCM, template.find, function(query) { + customizeOpenDialog(activeCM, template.find, function (query) { this(query); curState = activeCM.state.search; if (editors.length == 1 || !curState.query) { return; } - editors.forEach(function(cm) { + editors.forEach(cm => { if (cm != activeCM) { cm.execCommand('clearSearch'); updateState(cm, curState); @@ -776,14 +774,14 @@ function setupGlobalSearch() { inputs = inputs.reverse(); } inputs.splice(0, inputs.indexOf(document.activeElement) + 1); - return inputs.some(function(input) { + return inputs.some(input => { const match = rxQuery.exec(input.value); if (match) { input.focus(); const end = match.index + match[0].length; // scroll selected part into view in long inputs, // works only outside of current event handlers chain, hence timeout=0 - setTimeout(function() { + setTimeout(() => { input.setSelectionRange(end, end); input.setSelectionRange(match.index, end); }, 0); @@ -802,9 +800,9 @@ function setupGlobalSearch() { let query; let replacement; activeCM = focusClosestCM(activeCM); - customizeOpenDialog(activeCM, template[all ? 'replaceAll' : 'replace'], function(txt) { + customizeOpenDialog(activeCM, template[all ? 'replaceAll' : 'replace'], txt => { query = txt; - customizeOpenDialog(activeCM, template.replaceWith, function(txt) { + customizeOpenDialog(activeCM, template.replaceWith, txt => { replacement = txt; queue = editors.rotate(-editors.indexOf(activeCM)); if (all) { @@ -826,8 +824,8 @@ function setupGlobalSearch() { return; } // hide the first two dialogs (replace, replaceWith) - cm.openDialog = function(tmpl, callback, opt) { - cm.openDialog = function(tmpl, callback, opt) { + cm.openDialog = (tmpl, callback, opt) => { + cm.openDialog = (tmpl, callback, opt) => { cm.openDialog = originalOpenDialog; if (all) { callback(replacement); @@ -848,25 +846,23 @@ function setupGlobalSearch() { let wrapAround = false; const origPos = cm.getCursor(); cm.openConfirm = function overrideConfirm(tmpl, callbacks, opt) { - const ovrCallbacks = callbacks.map(function(callback) { - return function() { - makeSectionVisible(cm); - cm.openConfirm = overrideConfirm; - setTimeout(function() { cm.openConfirm = originalOpenConfirm; }, 0); + const ovrCallbacks = callbacks.map(callback => () => { + makeSectionVisible(cm); + cm.openConfirm = overrideConfirm; + setTimeout(() => { cm.openConfirm = originalOpenConfirm; }, 0); - const pos = cm.getCursor(); - callback(); - const cmp = CodeMirror.cmpPos(cm.getCursor(), pos); - wrapAround |= cmp <= 0; + const pos = cm.getCursor(); + callback(); + const cmp = CodeMirror.cmpPos(cm.getCursor(), pos); + wrapAround |= cmp <= 0; - const dlg = cm.getWrapperElement().querySelector('.CodeMirror-dialog'); - if (!dlg || cmp == 0 || wrapAround && CodeMirror.cmpPos(cm.getCursor(), origPos) >= 0) { - if (dlg) { - dlg.remove(); - } - doReplace(); + const dlg = cm.getWrapperElement().querySelector('.CodeMirror-dialog'); + if (!dlg || cmp == 0 || wrapAround && CodeMirror.cmpPos(cm.getCursor(), origPos) >= 0) { + if (dlg) { + dlg.remove(); } - }; + doReplace(); + } }); originalOpenConfirm.call(cm, template.replaceConfirm.innerHTML, ovrCallbacks, opt); }; @@ -887,7 +883,7 @@ function setupGlobalSearch() { function jumpToLine(cm) { const cur = cm.getCursor(); refocusMinidialog(cm); - cm.openDialog(template.jumpToLine.innerHTML, function(str) { + cm.openDialog(template.jumpToLine.innerHTML, str => { const m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/); if (m) { cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch); @@ -959,7 +955,7 @@ function refocusMinidialog(cm) { // close the currently opened minidialog cm.focus(); // make sure to focus the input in newly opened minidialog - setTimeout(function() { + setTimeout(() => { section.querySelector('.CodeMirror-dialog').focus(); }, 0); } @@ -980,8 +976,8 @@ function getEditorInSight(nearbyElement) { } if (!cm || offscreenDistance(cm) > 0) { const sorted = editors - .map(function(cm, index) { return {cm: cm, distance: offscreenDistance(cm), index: index}; }) - .sort(function(a, b) { return a.distance - b.distance || a.index - b.index; }); + .map((cm, index) => ({cm: cm, distance: offscreenDistance(cm), index: index})) + .sort((a, b) => a.distance - b.distance || a.index - b.index); cm = sorted[0].cm; if (sorted[0].distance > 0) { makeSectionVisible(cm); @@ -1027,12 +1023,12 @@ function updateLintReport(cm, delay) { const scope = cm ? [cm] : editors; let changed = false; let fixedOldIssues = false; - scope.forEach(function(cm) { + scope.forEach(cm => { const scopedState = cm.state.lint || {}; const oldMarkers = scopedState.markedLast || {}; const newMarkers = {}; const html = !scopedState.marked || scopedState.marked.length == 0 ? '' : '' + - scopedState.marked.map(function(mark) { + scopedState.marked.map(mark => { const info = mark.__annotation; const isActiveLine = info.from.line == cm.getCursor().line; const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch); @@ -1064,7 +1060,7 @@ function updateLintReport(cm, delay) { if (!state || !state.postponeNewIssues || fixedOldIssues) { renderLintReport(true); } else { - state.renderTimeout = setTimeout(function() { + state.renderTimeout = setTimeout(() => { renderLintReport(true); }, CodeMirror.defaults.lintReportDelay); } @@ -1072,7 +1068,7 @@ function updateLintReport(cm, delay) { } function escapeHtml(html) { const chars = {'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/'}; - return html.replace(/[&<>"'/]/g, function(char) { return chars[char]; }); + return html.replace(/[&<>"'/]/g, char => chars[char]); } } @@ -1082,7 +1078,7 @@ function renderLintReport(someBlockChanged) { const label = t('sectionCode'); const newContent = content.cloneNode(false); let issueCount = 0; - editors.forEach(function(cm, index) { + editors.forEach((cm, index) => { if (cm.state.lint && cm.state.lint.html) { const newBlock = newContent.appendChild(document.createElement('table')); const html = '' + label + ' ' + (index + 1) + '' + cm.state.lint.html; @@ -1165,9 +1161,9 @@ function beautify(event) { const undoButton = document.querySelector('#help-popup button[role="undo"]'); undoButton.textContent = t(scope.length == 1 ? 'undo' : 'undoGlobal'); - undoButton.addEventListener('click', function() { + undoButton.addEventListener('click', () => { let undoable = false; - scope.forEach(function(cm) { + scope.forEach(cm => { if (cm.beautifyChange && cm.beautifyChange[cm.changeGeneration()]) { delete cm.beautifyChange[cm.changeGeneration()]; cm.undo(); @@ -1178,8 +1174,8 @@ function beautify(event) { undoButton.disabled = !undoable; }); - scope.forEach(function(cm) { - setTimeout(function() { + scope.forEach(cm => { + setTimeout(() => { const pos = options.translate_positions = [].concat.apply([], cm.doc.sel.ranges.map(r => [Object.assign({}, r.anchor), Object.assign({}, r.head)])); @@ -1283,7 +1279,7 @@ function initWithStyle({style, codeIsUpdated}) { } // if this was done in response to an update, we need to clear existing sections - getSections().forEach(function(div) { div.remove(); }); + getSections().forEach(div => { div.remove(); }); const queue = style.sections.length ? style.sections.slice() : [{code: ''}]; const queueStart = new Date().getTime(); // after 100ms the sections will be added asynchronously @@ -1310,7 +1306,7 @@ function initWithStyle({style, codeIsUpdated}) { } function initHooks() { - document.querySelectorAll('#header .style-contributor').forEach(function(node) { + document.querySelectorAll('#header .style-contributor').forEach(node => { node.addEventListener('change', onChange); node.addEventListener('input', onChange); }); @@ -1378,7 +1374,7 @@ function maximizeCodeHeight(sectionDiv, isLast) { } stats.totalHeight += stats.firstSectionTop; if (stats.totalHeight <= window.innerHeight) { - editors.forEach(function(cm, index) { + editors.forEach((cm, index) => { cm.setSize(null, stats.deltas[index] + stats.cmActualHeight); }); return; @@ -1390,10 +1386,10 @@ function maximizeCodeHeight(sectionDiv, isLast) { if (available <= 0) { return; } - const totalDelta = stats.deltas.reduce(function(sum, d) { return sum + d; }, 0); + const totalDelta = stats.deltas.reduce((sum, d) => sum + d, 0); const q = available / totalDelta; const baseHeight = stats.cmActualHeight - stats.sectionMarginTop; - stats.deltas.forEach(function(delta, index) { + stats.deltas.forEach((delta, index) => { editors[index].setSize(null, baseHeight + Math.floor(q * delta)); }); } @@ -1413,8 +1409,8 @@ function validate() { return t('styleMissingName'); } // validate the regexps - if (document.querySelectorAll('.applies-to-list').some(function(list) { - return list.childNodes.some(function(li) { + if (document.querySelectorAll('.applies-to-list').some(list => { + list.childNodes.some(li => { if (li.className == template.appliesToEverything.className) { return false; } @@ -1466,7 +1462,7 @@ function save() { function getSectionsHashes() { const sections = []; - getSections().forEach(function(div) { + getSections().forEach(div => { const meta = getMeta(div); const code = div.CodeMirror.getValue(); if (/^\s*$/.test(code) && Object.keys(meta).length == 0) { @@ -1480,7 +1476,7 @@ function getSectionsHashes() { function getMeta(e) { const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []}; - e.querySelector('.applies-to-list').childNodes.forEach(function(li) { + e.querySelector('.applies-to-list').childNodes.forEach(li => { if (li.className == template.appliesToEverything.className) { return; } @@ -1513,13 +1509,13 @@ function showMozillaFormat() { } function toMozillaFormat() { - return getSectionsHashes().map(function(section) { + return getSectionsHashes().map(section => { let cssMds = []; for (const i in propertyToCss) { if (section[i]) { - cssMds = cssMds.concat(section[i].map(function(v) { - return propertyToCss[i] + '("' + v.replace(/\\/g, '\\\\') + '")'; - })); + cssMds = cssMds.concat(section[i].map(v => + propertyToCss[i] + '("' + v.replace(/\\/g, '\\\\') + '")' + )); } } return cssMds.length ? '@-moz-document ' + cssMds.join(', ') + ' {\n' + section.code + '\n}' : section.code; @@ -1540,9 +1536,9 @@ function fromMozillaFormat() { popup.querySelector('[name="import-append"]').addEventListener('click', doImport); popup.querySelector('[name="import-replace"]').addEventListener('click', doImport); - popup.codebox.on('change', function() { + popup.codebox.on('change', () => { clearTimeout(popup.mozillaTimeout); - popup.mozillaTimeout = setTimeout(function() { + popup.mozillaTimeout = setTimeout(() => { popup.classList.toggle('ready', trimNewLines(popup.codebox.getValue())); }, 100); }); @@ -1558,7 +1554,7 @@ function fromMozillaFormat() { // let oldSectionCount = editors.length; let firstAddedCM; - parser.addListener('startdocument', function(e) { + parser.addListener('startdocument', function (e) { let outerText = getRange(sectionStack.last.start, (--e.col, e)); const gapComment = outerText.match(/(\/\*[\s\S]*?\*\/)[\s\n]*$/); const section = {code: '', start: backtrackTo(this, parserlib.css.Tokens.LBRACE, 'end')}; @@ -1572,7 +1568,7 @@ function fromMozillaFormat() { doAddSection(sectionStack.last); sectionStack.last.code = ''; } - e.functions.forEach(function(f) { + e.functions.forEach(f => { const m = f.match(/^(url|url-prefix|domain|regexp)\((['"]?)(.+?)\2?\)$/); const aType = CssToProperty[m[1]]; const aValue = aType != 'regexps' ? m[3] : m[3].replace(/\\\\/g, '\\'); @@ -1581,7 +1577,7 @@ function fromMozillaFormat() { sectionStack.push(section); }); - parser.addListener('enddocument', function() { + parser.addListener('enddocument', function () { const end = backtrackTo(this, parserlib.css.Tokens.RBRACE, 'start'); const section = sectionStack.pop(); section.code += getRange(section.start, end); @@ -1589,14 +1585,14 @@ function fromMozillaFormat() { doAddSection(section); }); - parser.addListener('endstylesheet', function() { + parser.addListener('endstylesheet', () => { // add nonclosed outer sections (either broken or the last global one) const endOfText = {line: lines.length, col: lines.last.length + 1}; sectionStack.last.code += getRange(sectionStack.last.start, endOfText); sectionStack.forEach(doAddSection); delete maximizeCodeHeight.stats; - editors.forEach(function(cm) { + editors.forEach(cm => { maximizeCodeHeight(cm.getSection(), cm == editors.last); }); @@ -1608,7 +1604,7 @@ function fromMozillaFormat() { } }); - parser.addListener('error', function(e) { + parser.addListener('error', e => { errors += e.line + ':' + e.col + ' ' + e.message.replace(/ at line \d.+$/, '') + '
'; }); @@ -1655,7 +1651,7 @@ function fromMozillaFormat() { return false; } if (replaceOldStyle) { - editors.slice(0).reverse().forEach(function(cm) { + editors.slice(0).reverse().forEach(cm => { removeSection({target: cm.getSection().firstElementChild}); }); } else if (!editors.last.getValue()) { @@ -1699,16 +1695,16 @@ function showToggleStyleHelp() { function showKeyMapHelp() { const keyMap = mergeKeyMaps({}, prefs.get('editor.keyMap'), CodeMirror.defaults.extraKeys); const keyMapSorted = Object.keys(keyMap) - .map(function(key) { return {key: key, cmd: keyMap[key]}; }) + .map(key => ({key: key, cmd: keyMap[key]})) .concat([{key: 'Shift-Ctrl-Wheel', cmd: 'scrollWindow'}]) - .sort(function(a, b) { return a.cmd < b.cmd || (a.cmd == b.cmd && a.key < b.key) ? -1 : 1; }); + .sort((a, b) => (a.cmd < b.cmd || (a.cmd == b.cmd && a.key < b.key) ? -1 : 1)); showHelp(t('cm_keyMap') + ': ' + prefs.get('editor.keyMap'), '' + '' + '' + - '' + keyMapSorted.map(function(value) { - return ''; - }).join('') + + '' + keyMapSorted.map(value => + '' + ).join('') + '' + '
' + value.key + '' + value.cmd + '
' + value.key + '' + value.cmd + '
'); @@ -1734,12 +1730,13 @@ function showKeyMapHelp() { this.value = normalizedKey.replace('-dummy', ''); filterTable(event); } + function filterTable(event) { const input = event.target; const query = stringAsRegExp(input.value, 'gi'); const col = input.parentNode.cellIndex; inputs[1 - col].value = ''; - table.tBodies[0].childNodes.forEach(function(row) { + table.tBodies[0].childNodes.forEach(row => { let cell = row.children[col]; cell.innerHTML = cell.textContent.replace(query, '$&'); row.style.display = query.test(cell.textContent) ? '' : 'none'; @@ -1753,7 +1750,7 @@ function showKeyMapHelp() { if (typeof keyMap == 'string') { keyMap = CodeMirror.keyMap[keyMap]; } - Object.keys(keyMap).forEach(function(key) { + Object.keys(keyMap).forEach(key => { let cmd = keyMap[key]; // filter out '...', 'attach', etc. (hotkeys start with an uppercase letter) if (!merged[key] && !key.match(/^[a-z]/) && cmd != '...') { @@ -1777,9 +1774,9 @@ function showKeyMapHelp() { function showLintHelp() { showHelp(t('issues'), t('issuesHelp') + '
    ' + - CSSLint.getRules().map(function(rule) { - return '
  • ' + rule.name + '
    ' + rule.desc + '
  • '; - }).join('') + '
' + CSSLint.getRules().map(rule => + '
  • ' + rule.name + '
    ' + rule.desc + '
  • ' + ).join('') + '' ); } @@ -1932,8 +1929,8 @@ function showCodeMirrorPopup(title, html, options) { keyMap: prefs.get('editor.keyMap') }, options)); popup.codebox.focus(); - popup.codebox.on('focus', function() { hotkeyRerouter.setState(false); }); - popup.codebox.on('blur', function() { hotkeyRerouter.setState(true); }); + popup.codebox.on('focus', () => { hotkeyRerouter.setState(false); }); + popup.codebox.on('blur', () => { hotkeyRerouter.setState(true); }); return popup; } @@ -1943,7 +1940,7 @@ function getParams() { if (urlParts.length == 1) { return params; } - urlParts[1].split('&').forEach(function(keyValue) { + urlParts[1].split('&').forEach(keyValue => { const splitKeyValue = keyValue.split('=', 2); params[decodeURIComponent(splitKeyValue[0])] = decodeURIComponent(splitKeyValue[1]); }); @@ -1969,7 +1966,7 @@ function onRuntimeMessage(request) { break; case 'styleDeleted': if (styleId && styleId == request.id) { - window.onbeforeunload = function() {}; + window.onbeforeunload = () => {}; window.close(); break; } diff --git a/manage/manage.js b/manage/manage.js index 434e7b49..e8fcb481 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -903,7 +903,7 @@ function reapplyFilter(container = installed) { // 3. move the shortest group; repeat 2-3 if (hidden.len < visible.len && (fullPass || hidden.len % 2)) { // 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]; installed.insertBefore(entry, horizon); horizon = entry; diff --git a/options/index.js b/options/index.js index 68e6b955..6404eefb 100644 --- a/options/index.js +++ b/options/index.js @@ -65,7 +65,7 @@ function checkUpdates() { function setupRadioButtons() { const sets = {}; - const onChange = function() { + const onChange = function () { const newValue = sets[this.name].indexOf(this); if (newValue >= 0 && prefs.get(this.name) != newValue) { prefs.set(this.name, newValue); diff --git a/popup/popup.js b/popup/popup.js index f82b978f..42dd69e3 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -79,7 +79,7 @@ function initPopup(url) { } // action buttons - $('#disableAll').onchange = function() { + $('#disableAll').onchange = function () { installed.classList.toggle('disabled', this.checked); }; setupLivePrefs(); From 24dd0cb562378f5c43c779e0168efd35a5d3479b Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Jul 2017 12:01:39 -0500 Subject: [PATCH 19/22] More cleanup --- edit/edit.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/edit/edit.js b/edit/edit.js index 6eb0406b..f8e63006 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -441,9 +441,11 @@ document.addEventListener('wheel', event => { queryTabs({currentWindow: true}).then(tabs => { const windowId = tabs[0].windowId; if (prefs.get('openEditInWindow')) { - if (sessionStorage.saveSizeOnClose - && 'left' in prefs.get('windowPosition', {}) - && !isWindowMaximized()) { + if ( + sessionStorage.saveSizeOnClose && + 'left' in prefs.get('windowPosition', {}) && + !isWindowMaximized() + ) { // window was reopened via Ctrl-Shift-T etc. chrome.windows.update(windowId, prefs.get('windowPosition')); } @@ -481,10 +483,10 @@ function goBackToManage(event) { } function isWindowMaximized() { - return window.screenLeft == 0 - && window.screenTop == 0 - && window.outerWidth == screen.availWidth - && window.outerHeight == screen.availHeight; + return window.screenLeft == 0 && + window.screenTop == 0 && + window.outerWidth == screen.availWidth && + window.outerHeight == screen.availHeight; } window.onbeforeunload = () => { @@ -570,8 +572,10 @@ function addSection(event, section) { item.querySelector('.applies-value').value.trim()); div.classList.toggle('has-regexp', show); appliesTo.oninput = appliesTo.oninput || show && (event => { - if (event.target.matches('.applies-value') - && event.target.parentElement.querySelector('.applies-type').value == 'regexp') { + if ( + event.target.matches('.applies-value') && + event.target.parentElement.querySelector('.applies-type').value == 'regexp' + ) { showRegExpTester(null, div); } }); @@ -736,8 +740,11 @@ function setupGlobalSearch() { const rxQuery = typeof state.query == 'object' ? state.query : stringAsRegExp(state.query, shouldIgnoreCase(state.query) ? 'i' : ''); - if (document.activeElement && document.activeElement.name == 'applies-value' - && searchAppliesTo(activeCM)) { + if ( + document.activeElement && + document.activeElement.name == 'applies-value' && + searchAppliesTo(activeCM) + ) { return; } let cm = activeCM; From 417e3b5de3424d8b6b2e53c1168ee945938beef5 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Jul 2017 13:02:00 -0500 Subject: [PATCH 20/22] Add eqeqeq definition to eslint --- .eslintrc | 2 +- background/background.js | 22 +++---- background/storage.js | 54 +++++++-------- background/update.js | 10 +-- content/apply.js | 18 ++--- content/install.js | 30 ++++----- edit/edit.js | 138 +++++++++++++++++++-------------------- js/dom.js | 8 +-- js/localization.js | 12 ++-- js/messaging.js | 24 +++---- js/prefs.js | 30 ++++----- manage/fileSaveLoad.js | 30 ++++----- manage/manage.js | 76 ++++++++++----------- msgbox/msgbox.js | 6 +- options/index.js | 2 +- popup/popup.js | 16 ++--- 16 files changed, 240 insertions(+), 238 deletions(-) diff --git a/.eslintrc b/.eslintrc index 04989f20..2e335468 100644 --- a/.eslintrc +++ b/.eslintrc @@ -82,7 +82,7 @@ rules: dot-location: [2, property] dot-notation: [0] eol-last: [2] - eqeqeq: [0] + eqeqeq: [1, always] func-call-spacing: [2, never] func-name-matching: [0] func-names: [0] diff --git a/background/background.js b/background/background.js index fc5a7f30..3b4f1aa7 100644 --- a/background/background.js +++ b/background/background.js @@ -33,7 +33,7 @@ chrome.tabs.onAttached.addListener((tabId, data) => { if (tab.url.startsWith(URLS.ownOrigin + 'edit.html')) { chrome.windows.get(tab.windowId, {populate: true}, win => { // 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(); // Open FAQs page once after installation to guide new users. // Do not display it in development mode. - if (reason == 'install' && manifest.update_url) { + if (reason === 'install' && manifest.update_url) { setTimeout(openURL, 100, { url: 'http://add0n.com/stylus.html' }); } // reset L10N cache on update - if (reason == 'update') { + if (reason === 'update') { localStorage.L10N = JSON.stringify({ browserUIlanguage: chrome.i18n.getUILanguage(), }); @@ -138,7 +138,7 @@ contextMenus = Object.assign({ const item = Object.assign({id}, contextMenus[id]); const prefValue = prefs.readOnlyValues[id]; item.title = chrome.i18n.getMessage(item.title); - if (!item.type && typeof prefValue == 'boolean') { + if (!item.type && typeof prefValue === 'boolean') { item.type = 'checkbox'; item.checked = prefValue; } @@ -151,7 +151,7 @@ contextMenus = Object.assign({ }; createContextMenus(); prefs.subscribe((id, checked) => { - if (id == 'editor.contextDelete') { + if (id === 'editor.contextDelete') { if (checked) { createContextMenus([id]); } else { @@ -160,7 +160,7 @@ contextMenus = Object.assign({ } else { 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); for (const cs of contentScripts) { 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}) => { cs.matches.some(match => { - if ((match == ALL_URLS || url.match(match)) - && (!url.startsWith('chrome') || url == NTP)) { + if ((match === ALL_URLS || url.match(match)) + && (!url.startsWith('chrome') || url === NTP)) { chrome.tabs.sendMessage(id, PING, pong => { if (!pong) { injectCS(cs, id); @@ -229,7 +229,7 @@ function webNavigationListener(method, {url, tabId, frameId}) { }); } // main page frame id is 0 - if (frameId == 0) { + if (frameId === 0) { updateIcon({id: tabId, url}, styles); } }); @@ -258,7 +258,7 @@ function updateIcon(tab, styles) { } } 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 text = prefs.get('show-badge') && numStyles ? String(numStyles) : ''; const iconset = ['', 'light/'][prefs.get('iconset')] || ''; diff --git a/background/storage.js b/background/storage.js index 00b4f392..7531f0fb 100644 --- a/background/storage.js +++ b/background/storage.js @@ -63,7 +63,7 @@ function dbExec(method, data) { reject(event); }, onupgradeneeded(event) { - if (event.oldVersion == 0) { + if (event.oldVersion === 0) { event.target.result.createObjectStore('styles', { keyPath: 'id', autoIncrement: true, @@ -111,15 +111,17 @@ function filterStyles({ asHash = null, strictRegexp = true, // used by the popup to detect bad regexps } = {}) { - enabled = enabled === null || typeof enabled == 'boolean' ? enabled : - typeof enabled == 'string' ? enabled == 'true' : null; + enabled = enabled === null || typeof enabled === 'boolean' ? enabled : + typeof enabled === 'string' ? enabled === 'true' : null; id = id === null ? null : Number(id); - if (enabled === null - && url === null - && id === null - && matchUrl === null - && asHash != true) { + if ( + enabled === null && + url === null && + id === null && + matchUrl === null && + asHash !== true + ) { return cachedStyles.list; } const blankHash = asHash && { @@ -191,9 +193,9 @@ function filterStylesInternal({ let style; for (let i = 0; (style = styles[i]); i++) { - if ((enabled === null || style.enabled == enabled) - && (url === null || style.url == url) - && (id === null || style.id == id)) { + if ((enabled === null || style.enabled === enabled) + && (url === null || style.url === url) + && (id === null || style.id === id)) { const sections = needSections && getApplicableSections({style, matchUrl, strictRegexp, stopOnFirst: !asHash}); if (asHash) { @@ -233,16 +235,16 @@ function saveStyle(style) { } let existed; let codeIsUpdated; - if (reason == 'update' || reason == 'update-digest') { + if (reason === 'update' || reason === 'update-digest') { return calcStyleDigest(style).then(digest => { style.originalDigest = digest; return decide(); }); } - if (reason == 'import') { + if (reason === 'import') { style.originalDigest = style.originalDigest || 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; } } @@ -255,7 +257,7 @@ function saveStyle(style) { return dbExec('get', id).then((event, store) => { const oldStyle = event.target.result; existed = Boolean(oldStyle); - if (reason == 'update-digest' && oldStyle.originalDigest == style.originalDigest) { + if (reason === 'update-digest' && oldStyle.originalDigest === style.originalDigest) { return style; } codeIsUpdated = !existed || 'sections' in style && !styleSectionsEqual(style, oldStyle); @@ -289,7 +291,7 @@ function saveStyle(style) { } function done(event) { - if (reason == 'update-digest') { + if (reason === 'update-digest') { return style; } style.id = style.id || event.target.result; @@ -367,14 +369,14 @@ function getApplicableSections({style, matchUrl, strictRegexp = true, stopOnFirs function arraySomeMatches(array, matchUrl, strictRegexp) { for (const regexp of array) { 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); - if (rx == false) { + if (rx === false) { // invalid regexp break; } if (!rx) { - const anchored = pass == 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$'; + const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$'; rx = tryRegExp(anchored); cachedStyles.regexps.set(cacheKey, rx || false); if (!rx) { @@ -415,7 +417,7 @@ function styleSectionsEqual({sections: a}, {sections: b}) { if (!a || !b) { return undefined; } - if (a.length != b.length) { + if (a.length !== b.length) { return false; } const checkedInB = []; @@ -432,16 +434,16 @@ function styleSectionsEqual({sections: a}, {sections: b}) { 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) { - const typeA = a && typeof a[telltale] == 'function'; - const typeB = b && typeof b[telltale] == 'function'; + const typeA = a && typeof a[telltale] === 'function'; + const typeB = b && typeof b[telltale] === 'function'; return ( (a === null || a === undefined || (typeA && !a.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) { @@ -525,12 +527,12 @@ function cleanupCachedFilters({force = false} = {}) { function getDomains(url) { - if (url.indexOf('file:') == 0) { + if (url.indexOf('file:') === 0) { return []; } let d = /.*?:\/*([^/:]+)/.exec(url)[1]; const domains = [d]; - while (d.indexOf('.') != -1) { + while (d.indexOf('.') !== -1) { d = d.substring(d.indexOf('.') + 1); domains.push(d); } diff --git a/background/update.js b/background/update.js index a536098c..04f368f5 100644 --- a/background/update.js +++ b/background/update.js @@ -68,17 +68,17 @@ var updater = { }); function maybeFetchMd5(digest) { - if (!ignoreDigest && style.originalDigest && style.originalDigest != digest) { + if (!ignoreDigest && style.originalDigest && style.originalDigest !== digest) { return Promise.reject(updater.EDITED); } return download(style.md5Url); } function maybeFetchCode(md5) { - if (!md5 || md5.length != 32) { + if (!md5 || md5.length !== 32) { 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 download(style.updateUrl); @@ -109,8 +109,8 @@ var updater = { return json && json.sections && json.sections.length - && typeof json.sections.every == 'function' - && typeof json.sections[0].code == 'string'; + && typeof json.sections.every === 'function' + && typeof json.sections[0].code === 'string'; } }, diff --git a/content/apply.js b/content/apply.js index 6e310f05..0cb626e0 100644 --- a/content/apply.js +++ b/content/apply.js @@ -28,7 +28,7 @@ function requestStyles(options, callback = applyStyles) { // 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 try { - if (window != parent) { + if (window !== parent) { matchUrl = parent.location.href; } } catch (e) {} @@ -49,7 +49,7 @@ function requestStyles(options, callback = applyStyles) { 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 // which is faster because IPC messaging JSON-ifies everything internally requestStyles({}, styles => { @@ -114,7 +114,7 @@ function doDisableAll(disable = disableAll) { disableAll = disable; Array.prototype.forEach.call(document.styleSheets, stylesheet => { if (stylesheet.ownerNode.matches(`STYLE.stylus[id^="${ID_PREFIX}"]`) - && stylesheet.disabled != disable) { + && stylesheet.disabled !== disable) { stylesheet.disabled = disable; } }); @@ -122,14 +122,14 @@ function doDisableAll(disable = disableAll) { function doExposeIframes(state = exposeIframes) { - if (state === exposeIframes || window == parent) { + if (state === exposeIframes || window === parent) { return; } exposeIframes = state; const attr = document.documentElement.getAttribute('stylus-iframe'); - if (state && attr != '') { + if (state && attr !== '') { document.documentElement.setAttribute('stylus-iframe', ''); - } else if (!state && attr == '') { + } else if (!state && attr === '') { document.documentElement.removeAttribute('stylus-iframe'); } } @@ -193,7 +193,7 @@ function applyStyles(styles) { } if (document.head && 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 // under document.documentElement as plain text so we need to move them into HEAD // which is already autogenerated at this moment @@ -293,7 +293,7 @@ function initDocRewriteObserver() { for (let m = mutations.length; --m >= 0;) { const added = mutations[m].addedNodes; for (let n = added.length; --n >= 0;) { - if (added[n].localName == 'html') { + if (added[n].localName === 'html') { reinjectStyles(); return; } @@ -303,7 +303,7 @@ function initDocRewriteObserver() { docRewriteObserver.observe(document, {childList: true}); // detect dynamic iframes rewritten after creation by the embedder i.e. externally setTimeout(() => { - if (document.documentElement != ROOT) { + if (document.documentElement !== ROOT) { reinjectStyles(); } }); diff --git a/content/install.js b/content/install.js index 89600266..0445af7f 100644 --- a/content/install.js +++ b/content/install.js @@ -15,7 +15,7 @@ document.addEventListener('stylishInstallOpera', onInstallClicked); chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { // orphaned content script check - if (msg.method == 'ping') { + if (msg.method === 'ping') { sendResponse(true); } }); @@ -32,7 +32,7 @@ document.documentElement.appendChild(document.createElement('script')).text = '( Response.prototype.json = function (...args) { return originalResponseJson.call(this, ...args).then(json => { Response.prototype.json = originalResponseJson; - if (!settings || typeof ((json || {}).style_settings || {}).every != 'function') { + if (!settings || typeof ((json || {}).style_settings || {}).every !== 'function') { return json; } const images = new Map(); @@ -46,18 +46,18 @@ document.documentElement.appendChild(document.createElement('script')).text = '( if (value.startsWith('ik-')) { value = value.replace(/^ik-/, ''); const defaultItem = jsonSetting.style_setting_options.find(item => item.default); - if (!defaultItem || defaultItem.install_key != value) { + if (!defaultItem || defaultItem.install_key !== value) { if (defaultItem) { defaultItem.default = false; } jsonSetting.style_setting_options.some(item => { - if (item.install_key == value) { + if (item.install_key === value) { item.default = true; return true; } }); } - } else if (jsonSetting.setting_type == 'image') { + } else if (jsonSetting.setting_type === 'image') { jsonSetting.style_setting_options.some(item => { if (item.default) { item.default = false; @@ -67,7 +67,7 @@ document.documentElement.appendChild(document.createElement('script')).text = '( images.set(jsonSetting.install_key, value); } else { 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; } } @@ -151,7 +151,7 @@ function checkUpdatability([installedStyle]) { const md5Url = getMeta('stylish-md5-url'); if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) { getResource(md5Url).then(md5 => { - reportUpdatable(md5 != installedStyle.originalMd5); + reportUpdatable(md5 !== installedStyle.originalMd5); }); } else { getResource(getStyleURL()).then(code => { @@ -180,7 +180,7 @@ function sendEvent(type, detail = null) { type = type.replace('Chrome', 'Opera'); } detail = {detail}; - if (typeof cloneInto != 'undefined') { + if (typeof cloneInto !== 'undefined') { // Firefox requires explicit cloning, however USO can't process our messages anyway // because USO tries to use a global "event" variable deprecated in Firefox detail = cloneInto(detail, document); // eslint-disable-line no-undef @@ -227,7 +227,7 @@ function saveStyleCode(message, name, addProps) { reason: 'update', }), style => { - if (message == 'styleUpdate' && style.updateUrl.includes('?')) { + if (message === 'styleUpdate' && style.updateUrl.includes('?')) { enableUpdateButton(true); } else { sendEvent('styleInstalledChrome'); @@ -269,7 +269,7 @@ function styleSectionsEqual({sections: a}, {sections: b}) { if (!a || !b) { return undefined; } - if (a.length != b.length) { + if (a.length !== b.length) { return false; } const checkedInB = []; @@ -286,16 +286,16 @@ function styleSectionsEqual({sections: a}, {sections: b}) { 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) { - const typeA = a && typeof a[telltale] == 'function'; - const typeB = b && typeof b[telltale] == 'function'; + const typeA = a && typeof a[telltale] === 'function'; + const typeB = b && typeof b[telltale] === 'function'; return ( (a === null || a === undefined || (typeA && !a.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) { @@ -315,7 +315,7 @@ function styleSectionsEqual({sections: a}, {sections: b}) { function onDOMready() { - if (document.readyState != 'loading') { + if (document.readyState !== 'loading') { return Promise.resolve(); } return new Promise(resolve => { diff --git a/edit/edit.js b/edit/edit.js index f8e63006..8afb891e 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -45,7 +45,7 @@ Object.defineProperty(Array.prototype, 'last', {get: function () { return this[t new MutationObserver((mutations, observer) => { const themeElement = document.getElementById('cm-theme'); if (themeElement) { - themeElement.href = prefs.get('editor.theme') == 'default' ? '' + themeElement.href = prefs.get('editor.theme') === 'default' ? '' : 'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css'; observer.disconnect(); } @@ -68,8 +68,8 @@ const hotkeyRerouter = { eventHandler: event => { const keyName = CodeMirror.keyName(event); if ( - CodeMirror.lookupKey(keyName, CodeMirror.getOption('keyMap'), handleCommand) == 'handled' || - CodeMirror.lookupKey(keyName, CodeMirror.defaults.extraKeys, handleCommand) == 'handled' + CodeMirror.lookupKey(keyName, CodeMirror.getOption('keyMap'), handleCommand) === 'handled' || + CodeMirror.lookupKey(keyName, CodeMirror.defaults.extraKeys, handleCommand) === 'handled' ) { event.preventDefault(); event.stopPropagation(); @@ -90,7 +90,7 @@ function onChange(event) { setCleanItem(node, node.savedValue === currentValue); } else { // the manually added section's applies-to is dirty only when the value is non-empty - setCleanItem(node, node.localName != 'input' || !node.value.trim()); + setCleanItem(node, node.localName !== 'input' || !node.value.trim()); delete node.savedValue; // only valid when actually saved } updateTitle(); @@ -122,7 +122,7 @@ function setCleanItem(node, isClean) { } function isCleanGlobal() { - const clean = Object.keys(dirty).length == 0; + const clean = Object.keys(dirty).length === 0; setDirtyClass(document.body, !clean); // let saveBtn = document.getElementById('save-button') // if (clean){ @@ -270,7 +270,7 @@ function initCodeMirror() { } else { // Chrome is starting up and shows our edit.html, but the background page isn't loaded yet const theme = prefs.get('editor.theme'); - themeControl.innerHTML = optionsHtmlFromArray([theme == 'default' ? t('defaultTheme') : theme]); + themeControl.innerHTML = optionsHtmlFromArray([theme === 'default' ? t('defaultTheme') : theme]); getCodeMirrorThemes().then(() => { const themes = (localStorage.codeMirrorThemes || '').split(/\s+/); themeControl.innerHTML = optionsHtmlFromArray(themes); @@ -292,7 +292,7 @@ function acmeEventListener(event) { console.error('acmeEventListener: no "cm_option" %O', el); return; } - let value = el.type == 'checkbox' ? el.checked : el.value; + let value = el.type === 'checkbox' ? el.checked : el.value; switch (option) { case 'tabSize': CodeMirror.setOption('indentUnit', Number(value)); @@ -300,9 +300,9 @@ function acmeEventListener(event) { case 'theme': { const themeLink = document.getElementById('cm-theme'); // use non-localized 'default' internally - if (!value || value == 'default' || value == t('defaultTheme')) { + if (!value || value === 'default' || value === t('defaultTheme')) { value = 'default'; - if (prefs.get(el.id) != value) { + if (prefs.get(el.id) !== value) { prefs.set(el.id, value); } themeLink.href = ''; @@ -310,7 +310,7 @@ function acmeEventListener(event) { break; } const url = chrome.runtime.getURL('vendor/codemirror/theme/' + value + '.css'); - if (themeLink.href == url) { // preloaded in initCodeMirror() + if (themeLink.href === url) { // preloaded in initCodeMirror() break; } // avoid flicker: wait for the second stylesheet to load, then apply the theme @@ -337,7 +337,7 @@ function acmeEventListener(event) { case 'token': case 'selection': document.body.dataset[option] = value; - value = {showToken: value == 'token' && /[#.\-\w]/, annotateScrollbar: true}; + value = {showToken: value === 'token' && /[#.\-\w]/, annotateScrollbar: true}; break; default: value = null; @@ -372,7 +372,7 @@ function setupCodeMirror(textarea, index) { let lastClickTime = 0; const resizeGrip = wrapper.appendChild(template.resizeGrip.cloneNode(true)); resizeGrip.onmousedown = event => { - if (event.button != 0) { + if (event.button !== 0) { return; } event.preventDefault(); @@ -390,7 +390,7 @@ function setupCodeMirror(textarea, index) { function resize(e) { const cmPageY = wrapper.getBoundingClientRect().top + window.scrollY; const height = Math.max(minHeight, e.pageY - cmPageY); - if (height != wrapper.clientHeight) { + if (height !== wrapper.clientHeight) { cm.setSize(null, height); } } @@ -449,7 +449,7 @@ queryTabs({currentWindow: true}).then(tabs => { // window was reopened via Ctrl-Shift-T etc. chrome.windows.update(windowId, prefs.get('windowPosition')); } - if (tabs.length == 1 && window.history.length == 1) { + if (tabs.length === 1 && window.history.length === 1) { chrome.windows.getAll(windows => { if (windows.length > 1) { sessionStorageHash('saveSizeOnClose').set(windowId, true); @@ -462,14 +462,14 @@ queryTabs({currentWindow: true}).then(tabs => { } chrome.tabs.onRemoved.addListener((tabId, info) => { sessionStorageHash('manageStylesHistory').unset(tabId); - if (info.windowId == windowId && info.isWindowClosing) { + if (info.windowId === windowId && info.isWindowClosing) { sessionStorageHash('saveSizeOnClose').unset(windowId); } }); }); getActiveTab().then(tab => { - useHistoryBack = sessionStorageHash('manageStylesHistory').value[tab.id] == location.href; + useHistoryBack = sessionStorageHash('manageStylesHistory').value[tab.id] === location.href; }); function goBackToManage(event) { @@ -483,10 +483,10 @@ function goBackToManage(event) { } function isWindowMaximized() { - return window.screenLeft == 0 && - window.screenTop == 0 && - window.outerWidth == screen.availWidth && - window.outerHeight == screen.availHeight; + return window.screenLeft === 0 && + window.screenTop === 0 && + window.outerWidth === screen.availWidth && + window.outerHeight === screen.availHeight; } window.onbeforeunload = () => { @@ -568,13 +568,13 @@ function addSection(event, section) { function toggleTestRegExpVisibility() { const show = [...appliesTo.children].some(item => !item.matches('.applies-to-everything') && - item.querySelector('.applies-type').value == 'regexp' && + item.querySelector('.applies-type').value === 'regexp' && item.querySelector('.applies-value').value.trim()); div.classList.toggle('has-regexp', show); appliesTo.oninput = appliesTo.oninput || show && (event => { if ( event.target.matches('.applies-value') && - event.target.parentElement.querySelector('.applies-type').value == 'regexp' + event.target.parentElement.querySelector('.applies-type').value === 'regexp' ) { showRegExpTester(null, div); } @@ -666,7 +666,7 @@ function setupGlobalSearch() { let curState; // cm.state.search for last used 'find' function shouldIgnoreCase(query) { // treat all-lowercase non-regexp queries as case-insensitive - return typeof query == 'string' && query == query.toLowerCase(); + return typeof query === 'string' && query === query.toLowerCase(); } function updateState(cm, newState) { @@ -701,7 +701,7 @@ function setupGlobalSearch() { function focusClosestCM(activeCM) { editors.lastActive = activeCM; const cm = getEditorInSight(); - if (cm != activeCM) { + if (cm !== activeCM) { cm.focus(); } return cm; @@ -712,16 +712,16 @@ function setupGlobalSearch() { customizeOpenDialog(activeCM, template.find, function (query) { this(query); curState = activeCM.state.search; - if (editors.length == 1 || !curState.query) { + if (editors.length === 1 || !curState.query) { return; } editors.forEach(cm => { - if (cm != activeCM) { + if (cm !== activeCM) { cm.execCommand('clearSearch'); updateState(cm, curState); } }); - if (CodeMirror.cmpPos(curState.posFrom, curState.posTo) == 0) { + if (CodeMirror.cmpPos(curState.posFrom, curState.posTo) === 0) { findNext(activeCM); } }); @@ -737,12 +737,12 @@ function setupGlobalSearch() { let pos = activeCM.getCursor(reverse ? 'from' : 'to'); activeCM.setSelection(activeCM.getCursor()); // clear the selection, don't move the cursor - const rxQuery = typeof state.query == 'object' + const rxQuery = typeof state.query === 'object' ? state.query : stringAsRegExp(state.query, shouldIgnoreCase(state.query) ? 'i' : ''); if ( document.activeElement && - document.activeElement.name == 'applies-value' && + document.activeElement.name === 'applies-value' && searchAppliesTo(activeCM) ) { return; @@ -864,7 +864,7 @@ function setupGlobalSearch() { wrapAround |= cmp <= 0; const dlg = cm.getWrapperElement().querySelector('.CodeMirror-dialog'); - if (!dlg || cmp == 0 || wrapAround && CodeMirror.cmpPos(cm.getCursor(), origPos) >= 0) { + if (!dlg || cmp === 0 || wrapAround && CodeMirror.cmpPos(cm.getCursor(), origPos) >= 0) { if (dlg) { dlg.remove(); } @@ -1006,7 +1006,7 @@ function getEditorInSight(nearbyElement) { } function updateLintReport(cm, delay) { - if (delay == 0) { + if (delay === 0) { // immediately show pending csslint messages in onbeforeunload and save update(cm); return; @@ -1024,7 +1024,7 @@ function updateLintReport(cm, delay) { // or update it as soon as possible (default: 500ms lint + 100ms) in case an existing issue was just fixed clearTimeout(state.reportTimeout); state.reportTimeout = setTimeout(update, state.options.delay + 100, cm); - state.postponeNewIssues = delay == undefined || delay === null; + state.postponeNewIssues = delay === undefined || delay === null; function update(cm) { const scope = cm ? [cm] : editors; @@ -1034,16 +1034,16 @@ function updateLintReport(cm, delay) { const scopedState = cm.state.lint || {}; const oldMarkers = scopedState.markedLast || {}; const newMarkers = {}; - const html = !scopedState.marked || scopedState.marked.length == 0 ? '' : '' + + const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '' + scopedState.marked.map(mark => { const info = mark.__annotation; - const isActiveLine = info.from.line == cm.getCursor().line; + const isActiveLine = info.from.line === cm.getCursor().line; const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch); let message = escapeHtml(info.message.replace(/ at line \d.+$/, '')); if (message.length > 100) { message = message.substr(0, 100) + '...'; } - if (isActiveLine || oldMarkers[pos] == message) { + if (isActiveLine || oldMarkers[pos] === message) { delete oldMarkers[pos]; } newMarkers[pos] = message; @@ -1057,7 +1057,7 @@ function updateLintReport(cm, delay) { }).join('') + ''; scopedState.markedLast = newMarkers; fixedOldIssues |= scopedState.reportDisplayed && Object.keys(oldMarkers).length > 0; - if (scopedState.html != html) { + if (scopedState.html !== html) { scopedState.html = html; changed = true; } @@ -1094,12 +1094,12 @@ function renderLintReport(someBlockChanged) { issueCount += newBlock.rows.length; const block = content.children[newContent.children.length - 1]; - const blockChanged = !block || cm != block.cm || html != block.innerHTML; + const blockChanged = !block || cm !== block.cm || html !== block.innerHTML; someBlockChanged |= blockChanged; cm.state.lint.reportDisplayed = blockChanged; } }); - if (someBlockChanged || newContent.children.length != content.children.length) { + if (someBlockChanged || newContent.children.length !== content.children.length) { document.getElementById('issue-count').textContent = issueCount; container.replaceChild(newContent, content); container.style.display = newContent.children.length ? 'block' : 'none'; @@ -1112,7 +1112,7 @@ function resizeLintReport(event, content) { if (content.children.length) { const bounds = content.getBoundingClientRect(); const newMaxHeight = bounds.bottom <= innerHeight ? '' : (innerHeight - bounds.top) + 'px'; - if (newMaxHeight != content.style.maxHeight) { + if (newMaxHeight !== content.style.maxHeight) { content.style.maxHeight = newMaxHeight; } } @@ -1167,7 +1167,7 @@ function beautify(event) { '
    '); const undoButton = document.querySelector('#help-popup button[role="undo"]'); - undoButton.textContent = t(scope.length == 1 ? 'undo' : 'undoGlobal'); + undoButton.textContent = t(scope.length === 1 ? 'undo' : 'undoGlobal'); undoButton.addEventListener('click', () => { let undoable = false; scope.forEach(cm => { @@ -1188,7 +1188,7 @@ function beautify(event) { [Object.assign({}, r.anchor), Object.assign({}, r.head)])); const text = cm.getValue(); const newText = exports.css_beautify(text, options); - if (newText != text) { + if (newText !== text) { if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) { // clear the list if last change wasn't a css-beautify cm.beautifyChange = {}; @@ -1206,7 +1206,7 @@ function beautify(event) { }); document.querySelector('.beautify-options').onchange = ({target}) => { - const value = target.type == 'checkbox' ? target.checked : target.selectedIndex > 0; + const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0; prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value})); if (target.parentNode.hasAttribute('newline')) { target.parentNode.setAttribute('newline', value.toString()); @@ -1264,7 +1264,7 @@ function init() { window.onload = null; initWithStyle({style}); }; - if (document.readyState != 'loading') { + if (document.readyState !== 'loading') { window.onload(); } }); @@ -1346,10 +1346,10 @@ function initHooks() { function toggleContextMenuDelete(event) { - if (event.button == 2 && prefs.get('editor.contextDelete')) { + if (event.button === 2 && prefs.get('editor.contextDelete')) { chrome.contextMenus.update('editor.contextDelete', { enabled: Boolean( - this.selectionStart != this.selectionEnd || + this.selectionStart !== this.selectionEnd || this.somethingSelected && this.somethingSelected() ), }, ignoreChromeError); @@ -1412,20 +1412,20 @@ function updateTitle() { function validate() { const name = document.getElementById('name').value; - if (name == '') { + if (name === '') { return t('styleMissingName'); } // validate the regexps if (document.querySelectorAll('.applies-to-list').some(list => { list.childNodes.some(li => { - if (li.className == template.appliesToEverything.className) { + if (li.className === template.appliesToEverything.className) { return false; } const valueElement = li.querySelector('[name=applies-value]'); const type = li.querySelector('[name=applies-type]').value; const value = valueElement.value; if (type && value) { - if (type == 'regexp') { + if (type === 'regexp') { try { new RegExp(value); } catch (ex) { @@ -1472,7 +1472,7 @@ function getSectionsHashes() { getSections().forEach(div => { const meta = getMeta(div); const code = div.CodeMirror.getValue(); - if (/^\s*$/.test(code) && Object.keys(meta).length == 0) { + if (/^\s*$/.test(code) && Object.keys(meta).length === 0) { return; } meta.code = code; @@ -1484,7 +1484,7 @@ function getSectionsHashes() { function getMeta(e) { const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []}; e.querySelector('.applies-to-list').childNodes.forEach(li => { - if (li.className == template.appliesToEverything.className) { + if (li.className === template.appliesToEverything.className) { return; } const type = li.querySelector('[name=applies-type]').value; @@ -1502,7 +1502,7 @@ function saveComplete(style) { setCleanGlobal(); // Go from new style URL to edit style URL - if (location.href.indexOf('id=') == -1) { + if (location.href.indexOf('id=') === -1) { history.replaceState({}, document.title, 'edit.html?id=' + style.id); tE('heading', 'editStyleHeading', null, false); } @@ -1551,7 +1551,7 @@ function fromMozillaFormat() { }); function doImport() { - const replaceOldStyle = this.name == 'import-replace'; + const replaceOldStyle = this.name === 'import-replace'; popup.querySelector('.dismiss').onclick(); const mozStyle = trimNewLines(popup.codebox.getValue()); const parser = new parserlib.css.Parser(); @@ -1578,7 +1578,7 @@ function fromMozillaFormat() { e.functions.forEach(f => { const m = f.match(/^(url|url-prefix|domain|regexp)\((['"]?)(.+?)\2?\)$/); const aType = CssToProperty[m[1]]; - const aValue = aType != 'regexps' ? m[3] : m[3].replace(/\\\\/g, '\\'); + const aValue = aType !== 'regexps' ? m[3] : m[3].replace(/\\\\/g, '\\'); (section[aType] = section[aType] || []).push(aValue); }); sectionStack.push(section); @@ -1600,7 +1600,7 @@ function fromMozillaFormat() { delete maximizeCodeHeight.stats; editors.forEach(cm => { - maximizeCodeHeight(cm.getSection(), cm == editors.last); + maximizeCodeHeight(cm.getSection(), cm === editors.last); }); makeSectionVisible(firstAddedCM); @@ -1622,7 +1622,7 @@ function fromMozillaFormat() { const C1 = start.col - 1; const L2 = end.line - 1; const C2 = end.col - 1; - if (L1 == L2) { + if (L1 === L2) { return lines[L1].substr(C1, C2 - C1 + 1); } else { const middle = lines.slice(L1 + 1, L2).join('\n'); @@ -1673,7 +1673,7 @@ function fromMozillaFormat() { function backtrackTo(parser, tokenType, startEnd) { const tokens = parser._tokenStream._lt; for (let i = parser._tokenStream._ltIndex - 1; i >= 0; --i) { - if (tokens[i].type == tokenType) { + if (tokens[i].type === tokenType) { return {line: tokens[i][startEnd + 'Line'], col: tokens[i][startEnd + 'Col']}; } } @@ -1704,7 +1704,7 @@ function showKeyMapHelp() { const keyMapSorted = Object.keys(keyMap) .map(key => ({key: key, cmd: keyMap[key]})) .concat([{key: 'Shift-Ctrl-Wheel', cmd: 'scrollWindow'}]) - .sort((a, b) => (a.cmd < b.cmd || (a.cmd == b.cmd && a.key < b.key) ? -1 : 1)); + .sort((a, b) => (a.cmd < b.cmd || (a.cmd === b.cmd && a.key < b.key) ? -1 : 1)); showHelp(t('cm_keyMap') + ': ' + prefs.get('editor.keyMap'), '' + '' + @@ -1724,7 +1724,7 @@ function showKeyMapHelp() { function hotkeyHandler(event) { const keyName = CodeMirror.keyName(event); - if (keyName == 'Esc' || keyName == 'Tab' || keyName == 'Shift-Tab') { + if (keyName === 'Esc' || keyName === 'Tab' || keyName === 'Shift-Tab') { return; } event.preventDefault(); @@ -1754,14 +1754,14 @@ function showKeyMapHelp() { } function mergeKeyMaps(merged, ...more) { more.forEach(keyMap => { - if (typeof keyMap == 'string') { + if (typeof keyMap === 'string') { keyMap = CodeMirror.keyMap[keyMap]; } Object.keys(keyMap).forEach(key => { let cmd = keyMap[key]; // filter out '...', 'attach', etc. (hotkeys start with an uppercase letter) - if (!merged[key] && !key.match(/^[a-z]/) && cmd != '...') { - if (typeof cmd == 'function') { + if (!merged[key] && !key.match(/^[a-z]/) && cmd !== '...') { + if (typeof cmd === 'function') { // for 'emacs' keymap: provide at least something meaningful (hotkeys and the function body) // for 'vim*' keymaps: almost nothing as it doesn't rely on CM keymap mechanism cmd = cmd.toString().replace(/^function.*?\{[\s\r\n]*([\s\S]+?)[\s\r\n]*\}$/, '$1'); @@ -1795,7 +1795,7 @@ function showRegExpTester(event, section = getSectionForChild(this)) { const regexps = [...section.querySelector('.applies-to-list').children] .map(item => !item.matches('.applies-to-everything') && - item.querySelector('.applies-type').value == 'regexp' && + item.querySelector('.applies-type').value === 'regexp' && item.querySelector('.applies-value').value.trim()) .filter(item => item) .map(text => { @@ -1857,7 +1857,7 @@ function showRegExpTester(event, section = getSectionForChild(this)) { ? OWN_ICON : GET_FAVICON_URL + new URL(url).hostname; const icon = ``; - if (match.length == url.length) { + if (match.length === url.length) { full.push(`
    ${icon + url}
    `); } else { partial.push(`
    ${icon}${match}` + @@ -1898,7 +1898,7 @@ function showHelp(title, text) { div.querySelector('.contents').innerHTML = text; div.querySelector('.title').innerHTML = title; - if (getComputedStyle(div).display == 'none') { + if (getComputedStyle(div).display === 'none') { document.addEventListener('keydown', closeHelp); div.querySelector('.dismiss').onclick = closeHelp; // avoid chaining on multiple showHelp() calls } @@ -1909,8 +1909,8 @@ function showHelp(title, text) { function closeHelp(e) { if ( !e || - e.type == 'click' || - ((e.keyCode || e.which) == 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) + e.type === 'click' || + ((e.keyCode || e.which) === 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) ) { div.style.display = ''; document.querySelector('.contents').innerHTML = ''; @@ -1944,7 +1944,7 @@ function showCodeMirrorPopup(title, html, options) { function getParams() { const params = {}; const urlParts = location.href.split('?', 2); - if (urlParts.length == 1) { + if (urlParts.length === 1) { return params; } urlParts[1].split('&').forEach(keyValue => { @@ -1959,7 +1959,7 @@ chrome.runtime.onMessage.addListener(onRuntimeMessage); function onRuntimeMessage(request) { switch (request.method) { case 'styleUpdated': - if (styleId && styleId == request.style.id && request.reason != 'editSave') { + if (styleId && styleId === request.style.id && request.reason !== 'editSave') { if ((request.style.sections[0] || {}).code === null) { // the code-less style came from notifyAllTabs onBackgroundReady().then(() => { @@ -1972,7 +1972,7 @@ function onRuntimeMessage(request) { } break; case 'styleDeleted': - if (styleId && styleId == request.id) { + if (styleId && styleId === request.id) { window.onbeforeunload = () => {}; window.close(); break; diff --git a/js/dom.js b/js/dom.js index 88fd44b6..6a71a10a 100644 --- a/js/dom.js +++ b/js/dom.js @@ -27,7 +27,7 @@ navigator.userAgent.includes('Firefox') && setTimeout(() => { function onDOMready() { - if (document.readyState != 'loading') { + if (document.readyState !== 'loading') { return Promise.resolve(); } return new Promise(resolve => { @@ -79,9 +79,9 @@ function enforceInputRange(element) { const max = Number(element.max); const doNotify = () => element.dispatchEvent(new Event('change', {bubbles: true})); const onChange = ({type}) => { - if (type == 'input' && element.checkValidity()) { + if (type === 'input' && element.checkValidity()) { doNotify(); - } else if (type == 'change' && !element.checkValidity()) { + } else if (type === 'change' && !element.checkValidity()) { element.value = Math.max(min, Math.min(max, Number(element.value))); doNotify(); } @@ -113,7 +113,7 @@ function $element(opt) { ? opt.tag.split('#') : [null, opt.tag]; 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'); (opt.appendChild instanceof Array ? opt.appendChild : [opt.appendChild]) .forEach(child => child && element.appendChild(child)); diff --git a/js/localization.js b/js/localization.js index 0c5f578c..1db7577b 100644 --- a/js/localization.js +++ b/js/localization.js @@ -7,7 +7,7 @@ tDocLoader(); function t(key, params) { const cache = !params && t.cache[key]; const s = cache || chrome.i18n.getMessage(key, params); - if (s == '') { + if (s === '') { throw `Missing string "${key}"`; } if (!params && !cache) { @@ -20,7 +20,7 @@ function t(key, params) { function tE(id, key, attr, esc) { if (attr) { 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))); } else { document.getElementById(id).innerHTML = t(key); @@ -43,10 +43,10 @@ function tNodeList(nodes) { for (let n = nodes.length; --n >= 0;) { const node = nodes[n]; // skip non-ELEMENT_NODE - if (node.nodeType != 1) { + if (node.nodeType !== 1) { continue; } - if (node.localName == 'template') { + if (node.localName === 'template') { const elements = node.content.querySelectorAll('*'); tNodeList(elements); template[node.dataset.id] = elements[0]; @@ -94,7 +94,7 @@ function tDocLoader() { // reset L10N cache on UI language change const UIlang = chrome.i18n.getUILanguage(); - if (t.cache.browserUIlanguage != UIlang) { + if (t.cache.browserUIlanguage !== UIlang) { t.cache = {browserUIlanguage: UIlang}; localStorage.L10N = JSON.stringify(t.cache); } @@ -114,7 +114,7 @@ function tDocLoader() { const onLoad = () => { tDocLoader.stop(); process(observer.takeRecords()); - if (cacheLength != Object.keys(t.cache).length) { + if (cacheLength !== Object.keys(t.cache).length) { localStorage.L10N = JSON.stringify(t.cache); } }; diff --git a/js/messaging.js b/js/messaging.js index a1e1c4b0..386f7409 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -35,7 +35,7 @@ const URLS = { let BG = chrome.extension.getBackgroundPage(); -if (!BG || BG != window) { +if (!BG || BG !== window) { document.documentElement.classList.toggle('firefox', FIREFOX); document.documentElement.classList.toggle('opera', OPERA); // TODO: remove once our manifest's minimum_chrome_version is 50+ @@ -47,7 +47,7 @@ if (!BG || BG != window) { function notifyAllTabs(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, // 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 @@ -86,11 +86,11 @@ function notifyAllTabs(msg) { }); } // notify self: the message no longer is sent to the origin in new Chrome - if (typeof onRuntimeMessage != 'undefined') { + if (typeof onRuntimeMessage !== 'undefined') { onRuntimeMessage(originalMessage); } // notify apply.js on own pages - if (typeof applyOnMessage != 'undefined') { + if (typeof applyOnMessage !== 'undefined') { applyOnMessage(originalMessage); } // notify background page and all open popups @@ -134,7 +134,7 @@ function getActiveTabRealURL() { function getTabRealURL(tab) { return new Promise(resolve => { - if (tab.url != 'chrome://newtab/') { + if (tab.url !== 'chrome://newtab/') { resolve(tab.url); } else { chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => { @@ -159,13 +159,13 @@ function openURL({url, currentWindow = true}) { const urlQuery = url.startsWith('moz-extension') ? undefined : url.replace(/#.*/, ''); queryTabs({url: urlQuery, currentWindow}).then(tabs => { for (const tab of tabs) { - if (tab.url == url) { + if (tab.url === url) { activateTab(tab).then(resolve); return; } } 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 && (!url.startsWith('chrome') || !tab.incognito)) { chrome.tabs.update({url}, resolve); @@ -250,14 +250,14 @@ const debounce = Object.assign((fn, delay, ...args) => { function deepCopy(obj) { - return obj !== null && obj !== undefined && typeof obj == 'object' - ? deepMerge(typeof obj.slice == 'function' ? [] : {}, obj) + return obj !== null && obj !== undefined && typeof obj === 'object' + ? deepMerge(typeof obj.slice === 'function' ? [] : {}, obj) : obj; } function deepMerge(target, ...args) { - const isArray = typeof target.slice == 'function'; + const isArray = typeof target.slice === 'function'; for (const obj of args) { if (isArray && obj !== null && obj !== undefined) { for (const element of obj) { @@ -267,7 +267,7 @@ function deepMerge(target, ...args) { } for (const k in obj) { 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); } else { target[k] = deepCopy(value); @@ -346,7 +346,7 @@ function download(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.timeout = 10e3; - xhr.onloadend = () => (xhr.status == 200 + xhr.onloadend = () => (xhr.status === 200 ? resolve(xhr.responseText) : reject(xhr.status)); const [mainUrl, query] = url.split('?'); diff --git a/js/prefs.js b/js/prefs.js index c34a0546..2326036b 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -115,10 +115,10 @@ var prefs = new function Prefs() { defineReadonlyProperty(this.readOnlyValues, key, value); const hasChanged = !equal(value, oldValue); if (!fromBroadcast) { - if (BG && BG != window) { + if (BG && BG !== window) { BG.prefs.set(key, BG.deepCopy(value), {broadcast, sync}); } else { - localStorage[key] = typeof defaults[key] == 'object' + localStorage[key] = typeof defaults[key] === 'object' ? JSON.stringify(value) : value; if (broadcast && hasChanged) { @@ -166,7 +166,7 @@ var prefs = new function Prefs() { for (const key in defaults) { const defaultValue = defaults[key]; let value = localStorage[key]; - if (typeof value == 'string') { + if (typeof value === 'string') { switch (typeof defaultValue) { case 'boolean': value = value.toLowerCase() === 'true'; @@ -181,7 +181,7 @@ var prefs = new function Prefs() { } else { value = defaultValue; } - if (BG == window) { + if (BG === window) { // when in bg page, .set() will write to localStorage this.set(key, value, {broadcast: false, sync: false}); } 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})); getSync().get('settings', ({settings: synced} = {}) => { if (synced) { 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 // TODO: remove it in a couple of months continue; @@ -209,7 +209,7 @@ var prefs = new function Prefs() { }); chrome.storage.onChanged.addListener((changes, area) => { - if (area == 'sync' && 'settings' in changes) { + if (area === 'sync' && 'settings' in changes) { const synced = changes.settings.newValue; if (synced) { for (const key in defaults) { @@ -283,21 +283,21 @@ var prefs = new function Prefs() { function defineReadonlyProperty(obj, key, value) { const copy = deepCopy(value); - if (typeof copy == 'object') { + if (typeof copy === 'object') { Object.freeze(copy); } Object.defineProperty(obj, key, {value: copy, configurable: true}); } 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; } - if (Object.keys(a).length != Object.keys(b).length) { + if (Object.keys(a).length !== Object.keys(b).length) { return false; } for (const k in a) { - if (typeof a[k] == 'object') { + if (typeof a[k] === 'object') { if (!equal(a[k], b[k])) { return false; } @@ -315,7 +315,7 @@ var prefs = new function Prefs() { // Chrome and co. /Safari\/[\d.]+$/.test(navigator.userAgent) && // 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 = {}; for (const id of IDs) { const element = document.getElementById(id); - checkedProps[id] = element.type == 'checkbox' ? 'checked' : 'value'; + checkedProps[id] = element.type === 'checkbox' ? 'checked' : 'value'; updateElement({id, element, force: true}); element.addEventListener('change', onChange); } @@ -338,7 +338,7 @@ function setupLivePrefs( function onChange() { const value = this[checkedProps[this.id]]; - if (prefs.get(this.id) != value) { + if (prefs.get(this.id) !== value) { prefs.set(this.id, value); } } @@ -349,7 +349,7 @@ function setupLivePrefs( force, }) { const prop = checkedProps[id]; - if (force || element[prop] != value) { + if (force || element[prop] !== value) { element[prop] = value; element.dispatchEvent(new Event('change', {bubbles: true, cancelable: true})); } diff --git a/manage/fileSaveLoad.js b/manage/fileSaveLoad.js index 9d37fc1b..ec54517d 100644 --- a/manage/fileSaveLoad.js +++ b/manage/fileSaveLoad.js @@ -53,7 +53,7 @@ function importFromString(jsonString) { } // create objects in background context const json = BG.tryJSONparse(jsonString) || []; - if (typeof json.slice != 'function') { + if (typeof json.slice !== 'function') { json.length = 0; } const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []); @@ -94,8 +94,8 @@ function importFromString(jsonString) { } function analyze(item) { - if (!item || !item.name || !item.name.trim() || typeof item != 'object' - || (item.sections && typeof item.sections.slice != 'function')) { + if (!item || !item.name || !item.name.trim() || typeof item !== 'object' + || (item.sections && typeof item.sections.slice !== 'function')) { stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`); return; } @@ -117,8 +117,8 @@ function importFromString(jsonString) { } const oldStyleKeys = oldStyle && Object.keys(oldStyle); const metaEqual = oldStyleKeys && - oldStyleKeys.length == Object.keys(item).length && - oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]); + oldStyleKeys.length === Object.keys(item).length && + oldStyleKeys.every(k => k === 'sections' || oldStyle[k] === item[k]); const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item); if (metaEqual && codeEqual) { stats.unchanged.names.push(oldStyle.name); @@ -131,7 +131,7 @@ function importFromString(jsonString) { function sameStyle(oldStyle, newStyle) { return oldStyle.name.trim() === newStyle.name.trim() || ['updateUrl', 'originalMd5', 'originalDigest'] - .some(field => oldStyle[field] && oldStyle[field] == newStyle[field]); + .some(field => oldStyle[field] && oldStyle[field] === newStyle[field]); } function account({style, info, resolve}) { @@ -196,7 +196,7 @@ function importFromString(jsonString) { buttons: [t('confirmOK'), numChanged && t('undo')], onshow: bindClick, }).then(({button, enter, esc}) => { - if (button == 1) { + if (button === 1) { undo(); } }); @@ -224,7 +224,7 @@ function importFromString(jsonString) { buttons: [t('confirmOK')], })); function undoNextId() { - if (index == newIds.length) { + if (index === newIds.length) { resolve(); return; } @@ -250,7 +250,7 @@ function importFromString(jsonString) { } }; for (const block of $$('details')) { - if (block.dataset.id != 'invalid') { + if (block.dataset.id !== 'invalid') { block.style.cursor = 'pointer'; block.onclick = highlightElement; } @@ -262,7 +262,7 @@ function importFromString(jsonString) { } function reportNameChange(oldStyle, newStyle) { - return newStyle.name != oldStyle.name + return newStyle.name !== oldStyle.name ? oldStyle.name + ' —> ' + newStyle.name : oldStyle.name; } @@ -278,21 +278,21 @@ function importFromString(jsonString) { for (const tab of tabs) { // skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF if (FIREFOX && !tab.width) { - if (tab == lastTab) { + if (tab === lastTab) { resolve(); } continue; } getStylesSafe({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => { const message = {method: 'styleReplaceAll', styles}; - if (tab.id == ownTab.id) { + if (tab.id === ownTab.id) { applyOnMessage(message); } else { - invokeOrPostpone(tab.id == activeTab.id, + invokeOrPostpone(tab.id === activeTab.id, chrome.tabs.sendMessage, tab.id, message, ignoreChromeError); } setTimeout(BG.updateIcon, 0, tab, styles); - if (tab == lastTab) { + if (tab === lastTab) { resolve(); } }); @@ -359,7 +359,7 @@ $('#unfile-all-styles').onclick = () => { Object.assign(document.body, { ondragover(event) { 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); if (hasFiles) { event.preventDefault(); diff --git a/manage/manage.js b/manage/manage.js index e8fcb481..124c7d72 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -73,7 +73,7 @@ function initGlobalEvents() { // focus search field on / key document.onkeypress = event => { - if ((event.keyCode || event.which) == 47 + if ((event.keyCode || event.which) === 47 && !event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.target.matches('[type="text"], [type="search"]')) { event.preventDefault(); @@ -114,7 +114,7 @@ function initGlobalEvents() { function showStyles(styles = []) { const sorted = styles .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; const scrollY = (history.state || {}).scrollY; const shouldRenderAll = scrollY > window.innerHeight || sessionStorage.justEditedStyleId; @@ -228,18 +228,18 @@ function createStyleTargetsElement({entry, style, postponeFavicons}) { displayed.add(targetValue); const element = template.appliesToTarget.cloneNode(true); if (!newUI.enabled) { - if (numTargets == 10) { + if (numTargets === 10) { container = container.appendChild(template.extraAppliesTo.cloneNode(true)); } else if (numTargets > 1) { container.appendChild(template.appliesToSeparator.cloneNode(true)); } } else if (newUI.favicons) { let favicon = ''; - if (type == 'domains') { + if (type === 'domains') { favicon = GET_FAVICON_URL + targetValue; } else if (targetValue.startsWith('chrome-extension:')) { favicon = OWN_ICON; - } else if (type != 'regexps') { + } else if (type !== 'regexps') { favicon = targetValue.includes('://') && targetValue.match(/^.*?:\/\/([^/]+)/); favicon = favicon ? GET_FAVICON_URL + favicon[1] : ''; } @@ -292,7 +292,7 @@ Object.assign(handleEvent, { const target = event.target; const entry = target.closest('.entry'); 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)) { const handler = handleEvent.ENTRY_ROUTES[selector]; return handleEvent[handler].call(el, event, entry); @@ -307,8 +307,8 @@ Object.assign(handleEvent, { } event.preventDefault(); event.stopPropagation(); - const left = event.button == 0; - const middle = event.button == 1; + const left = event.button === 0; + const middle = event.button === 1; const shift = event.shiftKey; const ctrl = event.ctrlKey; const openWindow = left && shift && !ctrl; @@ -361,7 +361,7 @@ Object.assign(handleEvent, { buttons: [t('confirmDelete'), t('confirmCancel')], }) .then(({button, enter, esc}) => { - if (button == 0 || enter) { + if (button === 0 || enter) { deleteStyleSafe({id}); } }); @@ -386,10 +386,10 @@ Object.assign(handleEvent, { }, 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) { const value = getValue(el); - if (value == el.lastValue) { + if (value === el.lastValue) { return; } el.lastValue = value; @@ -415,22 +415,22 @@ Object.assign(handleEvent, { function handleUpdate(style, {reason, method} = {}) { let entry; let oldEntry = $(ENTRY_ID_PREFIX + style.id); - if (oldEntry && method == 'styleUpdated') { + if (oldEntry && method === 'styleUpdated') { handleToggledOrCodeOnly(); } entry = entry || createStyleElement({style}); if (oldEntry) { - if (oldEntry.styleNameLowerCase == entry.styleNameLowerCase) { + if (oldEntry.styleNameLowerCase === entry.styleNameLowerCase) { installed.replaceChild(entry, oldEntry); } else { oldEntry.remove(); } } - if (reason == 'update' && entry.matches('.updatable')) { + if (reason === 'update' && entry.matches('.updatable')) { handleUpdateInstalled(); } filterAndAppend({entry}); - if (!entry.matches('.hidden') && reason != 'import') { + if (!entry.matches('.hidden') && reason !== 'import') { animateElement(entry); scrollElementIntoView(entry); } @@ -438,12 +438,12 @@ function handleUpdate(style, {reason, method} = {}) { function handleToggledOrCodeOnly() { const newStyleMeta = getStyleWithNoCode(style); const diff = objectDiff(oldEntry.styleMeta, newStyleMeta); - if (diff.length == 0) { + if (diff.length === 0) { // only code was modified entry = oldEntry; 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('disabled', !style.enabled); $$('.checker', oldEntry).forEach(el => (el.checked = style.enabled)); @@ -481,8 +481,8 @@ function switchUI({styleOnly} = {}) { // ensure the global option is processed first for (const el of [$('#manage.newUI'), ...$$('[id^="manage.newUI."]')]) { const id = el.id.replace(/^manage\.newUI\.?/, '') || 'enabled'; - const value = el.type == 'checkbox' ? el.checked : Number(el.value); - const valueChanged = value !== newUI[id] && (id == 'enabled' || current.enabled); + const value = el.type === 'checkbox' ? el.checked : Number(el.value); + const valueChanged = value !== newUI[id] && (id === 'enabled' || current.enabled); current[id] = value; changed[id] = valueChanged; someChanged |= valueChanged; @@ -568,7 +568,7 @@ function checkUpdateAll() { $('#apply-all-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)')) .map(el => checkUpdate(el, {single: false})); @@ -585,7 +585,7 @@ function checkUpdateAll() { total = value; break; case BG.updater.UPDATED: - if (++updated == 1) { + if (++updated === 1) { $('#apply-all-updates').disabled = true; $('#apply-all-updates').classList.remove('hidden'); } @@ -593,7 +593,7 @@ function checkUpdateAll() { // fallthrough case BG.updater.SKIPPED: checked++; - if (details == BG.updater.EDITED || details == BG.updater.MAYBE_EDITED) { + if (details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED) { skippedEdited++; } reportUpdateState(state, value, details); @@ -606,13 +606,13 @@ function checkUpdateAll() { function done() { document.body.classList.remove('update-in-progress'); - $('#check-all-updates').disabled = total == 0; + $('#check-all-updates').disabled = total === 0; $('#apply-all-updates').disabled = false; renderUpdatesOnlyFilter({check: updated + skippedEdited > 0}); if (!updated) { $('#update-all-no-updates').dataset.skippedEdited = skippedEdited > 0; $('#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); } } } @@ -648,16 +648,16 @@ function reportUpdateState(state, style, details) { if (entry.classList.contains('can-update')) { break; } - const same = details == BG.updater.SAME_MD5 || details == BG.updater.SAME_CODE; - const edited = details == BG.updater.EDITED || details == BG.updater.MAYBE_EDITED; + const same = details === BG.updater.SAME_MD5 || details === BG.updater.SAME_CODE; + const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED; entry.dataset.details = details; if (!details) { details = t('updateCheckFailServerUnreachable'); - } else if (typeof details == 'number') { + } else if (typeof details === 'number') { details = t('updateCheckFailBadResponseCode', [details]); - } else if (details == BG.updater.EDITED) { + } else if (details === BG.updater.EDITED) { 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'); } const message = same ? t('updateCheckSucceededNoUpdate') : details; @@ -719,7 +719,7 @@ function searchStyles({immediately, container}) { const searchElement = $('#search'); const query = searchElement.value.toLocaleLowerCase(); const queryPrev = searchElement.lastValue || ''; - if (query == queryPrev && !immediately && !container) { + if (query === queryPrev && !immediately && !container) { return; } if (!immediately) { @@ -741,7 +741,7 @@ function searchStyles({immediately, container}) { style.url && isMatchingText(style.url) || isMatchingStyle(style))); } - if (entry.classList.contains('not-matching') != !isMatching) { + if (entry.classList.contains('not-matching') !== !isMatching) { entry.classList.toggle('not-matching', !isMatching); needsRefilter = true; } @@ -850,7 +850,7 @@ function reapplyFilter(container = installed) { shuffle(false); setTimeout(shuffle, 0, true); // 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]); } return; @@ -885,7 +885,7 @@ function reapplyFilter(container = installed) { const skipGroup = state => { const start = i; const first = entry; - while (entry && entry.classList.contains('hidden') == state) { + while (entry && entry.classList.contains('hidden') === state) { entry = entry.nextElementSibling; i++; } @@ -978,19 +978,19 @@ function objectDiff(first, second, path = '') { diff.push({path, key, values: [a], type: 'removed'}); 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 || + a.length !== b.length || a.some((el, i) => { - const result = !el || typeof el != 'object' - ? el != b[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'}); } - } else if (typeof a == 'object' && typeof b == 'object') { + } else if (typeof a === 'object' && typeof b === 'object') { diff.push(...objectDiff(a, b, path + key + '.')); } else { diff.push({path, key, values: [a, b], type: 'changed'}); diff --git a/msgbox/msgbox.js b/msgbox/msgbox.js index a7ebbe2a..8faf8d60 100644 --- a/msgbox/msgbox.js +++ b/msgbox/msgbox.js @@ -30,9 +30,9 @@ function messageBox({ key(event) { const keyCode = event.keyCode || event.which; if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey - && (keyCode == 13 || keyCode == 27)) { + && (keyCode === 13 || keyCode === 27)) { event.preventDefault(); - resolveWith(keyCode == 13 ? {enter: true} : {esc: true}); + resolveWith(keyCode === 13 ? {enter: true} : {esc: true}); } }, scroll() { @@ -52,7 +52,7 @@ function messageBox({ unbindAndRemoveSelf(); } const id = 'message-box'; - const putAs = typeof contents == 'string' ? 'innerHTML' : 'appendChild'; + const putAs = typeof contents === 'string' ? 'innerHTML' : 'appendChild'; messageBox.element = $element({id, className, appendChild: [ $element({appendChild: [ $element({id: `${id}-title`, innerHTML: title}), diff --git a/options/index.js b/options/index.js index 6404eefb..10404e5b 100644 --- a/options/index.js +++ b/options/index.js @@ -67,7 +67,7 @@ function setupRadioButtons() { const sets = {}; const onChange = function () { 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); } }; diff --git a/popup/popup.js b/popup/popup.js index 42dd69e3..52accdf1 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -278,7 +278,7 @@ Object.assign(handleEvent, { toggle(event) { saveStyleSafe({ 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 => { const keyCode = event.keyCode || event.which; if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey - && (keyCode == 13 || keyCode == 27)) { + && (keyCode === 13 || keyCode === 27)) { event.preventDefault(); - confirm(keyCode == 13); + confirm(keyCode === 13); } }; function confirm(ok) { @@ -342,9 +342,9 @@ Object.assign(handleEvent, { maybeEdit(event) { if (!( - event.button == 0 && (event.ctrlKey || event.metaKey) || - event.button == 1 || - event.button == 2)) { + event.button === 0 && (event.ctrlKey || event.metaKey) || + event.button === 1 || + event.button === 2)) { return; } // open an editor on middleclick @@ -401,10 +401,10 @@ function detectSloppyRegexps({entry, style}) { for (const section of style.sections) { for (const regexp of section.regexps) { 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)) { // 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); rxCache.set(cacheKey, rx || false); } From 8cfb37351d94cbb39a43b2979d0b23324f665774 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Jul 2017 14:40:13 -0500 Subject: [PATCH 21/22] Remove unused variables --- background/background.js | 2 +- edit/edit.js | 4 ++-- manage/fileSaveLoad.js | 6 +++--- manage/manage.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/background/background.js b/background/background.js index 3b4f1aa7..13d12e2f 100644 --- a/background/background.js +++ b/background/background.js @@ -26,7 +26,7 @@ chrome.webNavigation.onHistoryStateUpdated.addListener(data => chrome.webNavigation.onReferenceFragmentUpdated.addListener(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 // so we can do the same to the next one to open. chrome.tabs.get(tabId, tab => { diff --git a/edit/edit.js b/edit/edit.js index 8afb891e..d7edd6cc 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -831,8 +831,8 @@ function setupGlobalSearch() { return; } // hide the first two dialogs (replace, replaceWith) - cm.openDialog = (tmpl, callback, opt) => { - cm.openDialog = (tmpl, callback, opt) => { + cm.openDialog = (tmpl, callback) => { + cm.openDialog = (tmpl, callback) => { cm.openDialog = originalOpenDialog; if (all) { callback(replacement); diff --git a/manage/fileSaveLoad.js b/manage/fileSaveLoad.js index ec54517d..4a8397ec 100644 --- a/manage/fileSaveLoad.js +++ b/manage/fileSaveLoad.js @@ -195,7 +195,7 @@ function importFromString(jsonString) { contents: report.length ? report : t('importReportUnchanged'), buttons: [t('confirmOK'), numChanged && t('undo')], onshow: bindClick, - }).then(({button, enter, esc}) => { + }).then(({button}) => { if (button === 1) { undo(); } @@ -241,7 +241,7 @@ function importFromString(jsonString) { } } - function bindClick(box) { + function bindClick() { const highlightElement = event => { const styleElement = $('#style-' + event.target.dataset.id); if (styleElement) { @@ -367,7 +367,7 @@ Object.assign(document.body, { this.classList.remove('fadeout'); } }, - ondragend(event) { + ondragend() { animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => { this.style.animationDuration = ''; }); diff --git a/manage/manage.js b/manage/manage.js index 124c7d72..3d11308a 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -360,7 +360,7 @@ Object.assign(handleEvent, { className: 'danger center', buttons: [t('confirmDelete'), t('confirmCancel')], }) - .then(({button, enter, esc}) => { + .then(({button, enter}) => { if (button === 0 || enter) { deleteStyleSafe({id}); } From a46684f8926f84e77fe183e1eb96a03987a57f7d Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Jul 2017 15:04:40 -0500 Subject: [PATCH 22/22] Style options cursor --- options/index.css | 1 + 1 file changed, 1 insertion(+) diff --git a/options/index.css b/options/index.css index fbc0e803..1b9508f7 100644 --- a/options/index.css +++ b/options/index.css @@ -81,6 +81,7 @@ label:not([disabled]) > :first-child { label:not([disabled]):hover > :first-child { text-shadow: 0 0 0.01px rgba(0, 0, 0, .25); + cursor: pointer; } button,