From 6a838e9d5e8afc2a757b6f2b654fc2db174afdd0 Mon Sep 17 00:00:00 2001
From: tophf
Date: Thu, 7 Dec 2017 20:26:41 +0300
Subject: [PATCH 01/10] make sure all pre-create hooks finished
---
edit/codemirror-editing-hooks.js | 4 +-
edit/edit.js | 65 ++++++++++++++++----------------
2 files changed, 36 insertions(+), 33 deletions(-)
diff --git a/edit/codemirror-editing-hooks.js b/edit/codemirror-editing-hooks.js
index 46550b8e..7cb66a69 100644
--- a/edit/codemirror-editing-hooks.js
+++ b/edit/codemirror-editing-hooks.js
@@ -5,7 +5,9 @@ global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
*/
'use strict';
-onDOMready().then(() => {
+addEventListener('init:allDone', function _() {
+ removeEventListener('init:allDone', _);
+
CodeMirror.defaults.lint = linterConfig.getForCodeMirror();
const COMMANDS = {
diff --git a/edit/edit.js b/edit/edit.js
index 0e95c192..255cc975 100644
--- a/edit/edit.js
+++ b/edit/edit.js
@@ -26,47 +26,23 @@ const CssToProperty = {'url': 'urls', 'url-prefix': 'urlPrefixes', 'domain': 'do
let editor;
-preinit();
window.onbeforeunload = beforeUnload;
chrome.runtime.onMessage.addListener(onRuntimeMessage);
+preinit();
+
Promise.all([
- initStyleData().then(style => {
- styleId = style.id;
- sessionStorage.justEditedStyleId = styleId;
- // we set "usercss" class on when is empty
- // so there'll be no flickering of the elements that depend on it
- if (isUsercss(style)) {
- document.documentElement.classList.add('usercss');
- }
- // strip URL parameters when invoked for a non-existent id
- if (!styleId) {
- history.replaceState({}, document.title, location.pathname);
- }
- return style;
- }),
+ initStyleData(),
onDOMready(),
- onBackgroundReady(),
])
.then(([style]) => Promise.all([
style,
initColorpicker(),
initCollapsibles(),
initHooksCommon(),
+ dispatchEvent(new Event('init:allDone')),
]))
-.then(([style]) => {
- const usercss = isUsercss(style);
- $('#heading').textContent = t(styleId ? 'editStyleHeading' : 'addStyleTitle');
- $('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
- $('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
- $('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
- if (usercss) {
- editor = createSourceEditor(style);
- } else {
- initWithSectionStyle({style});
- document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
- }
-});
+.then(createEditor);
function preinit() {
// make querySelectorAll enumeration code readable
@@ -178,6 +154,20 @@ function preinit() {
});
}
+function createEditor([style]) {
+ const usercss = isUsercss(style);
+ $('#heading').textContent = t(styleId ? 'editStyleHeading' : 'addStyleTitle');
+ $('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
+ $('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
+ $('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
+ if (usercss) {
+ editor = createSourceEditor(style);
+ } else {
+ initWithSectionStyle({style});
+ document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
+ }
+}
+
function onRuntimeMessage(request) {
switch (request.method) {
case 'styleUpdated':
@@ -258,9 +248,20 @@ function initStyleData() {
)
],
});
- return !id ?
- Promise.resolve(createEmptyStyle()) :
- getStylesSafe({id}).then(([style]) => style || createEmptyStyle());
+ return getStylesSafe({id: id || -1})
+ .then(([style = createEmptyStyle()]) => {
+ styleId = sessionStorage.justEditedStyleId = style.id;
+ // we set "usercss" class on when is empty
+ // so there'll be no flickering of the elements that depend on it
+ if (isUsercss(style)) {
+ document.documentElement.classList.add('usercss');
+ }
+ // strip URL parameters when invoked for a non-existent id
+ if (!styleId) {
+ history.replaceState({}, document.title, location.pathname);
+ }
+ return style;
+ });
}
function initHooks() {
From fbcd3cc96524d0cbba9a58aabff5161efd43087e Mon Sep 17 00:00:00 2001
From: tophf
Date: Thu, 7 Dec 2017 20:58:02 +0300
Subject: [PATCH 02/10] clarify the tooltip for USO userstyle "configure" icon
---
_locales/en/messages.json | 6 +++++-
popup/popup.js | 1 +
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index a1529384..72953360 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -105,7 +105,11 @@
},
"configureStyle": {
"message": "Configure",
- "description": "Label for the button to configure userstyle"
+ "description": "Label for the button to configure usercss userstyle"
+ },
+ "configureStyleOnHomepage": {
+ "message": "Configure on homepage",
+ "description": "Label for the button to configure userstyles.org userstyle"
},
"checkForUpdate": {
"message": "Check for update",
diff --git a/popup/popup.js b/popup/popup.js
index 3041696f..bd7c06be 100644
--- a/popup/popup.js
+++ b/popup/popup.js
@@ -282,6 +282,7 @@ function createStyleElement({
if (!style.usercssData && style.updateUrl && style.updateUrl.includes('?') && style.url) {
config.href = style.url;
config.target = '_blank';
+ config.title = t('configureStyleOnHomepage');
$('use', config).attributes['xlink:href'].nodeValue = '#svg-icon-config-uso';
} else if (!style.usercssData || !Object.keys(style.usercssData.vars || {}).length) {
config.style.display = 'none';
From 99cce55a8e93ec7ecbc5eb7e0838bcc840177ff6 Mon Sep 17 00:00:00 2001
From: tophf
Date: Thu, 7 Dec 2017 23:21:27 +0300
Subject: [PATCH 03/10] ensure long words break before breaking the layout
supersedes 40075a0d
---
.eslintrc | 1 +
edit/edit.js | 24 --------
js/localization.js | 136 +++++++++++++++++++++++++++++++--------------
manage/manage.js | 2 +-
4 files changed, 95 insertions(+), 68 deletions(-)
diff --git a/.eslintrc b/.eslintrc
index 6eb52b33..0d866fff 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -48,6 +48,7 @@ globals:
tHTML: false
tNodeList: false
tDocLoader: false
+ tWordBreak: false
# dom.js
onDOMready: false
scrollElementIntoView: false
diff --git a/edit/edit.js b/edit/edit.js
index 255cc975..a050e2fe 100644
--- a/edit/edit.js
+++ b/edit/edit.js
@@ -75,30 +75,6 @@ function preinit() {
'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css'
}));
- // forcefully break long labels in aligned options to prevent the entire block layout from breaking
- onDOMready().then(() => new Promise(requestAnimationFrame)).then(() => {
- const maxWidth2ndChild = $$('#options .aligned > :nth-child(2)')
- .sort((a, b) => b.offsetWidth - a.offsetWidth)[0].offsetWidth;
- const widthFor1stChild = $('#options').offsetWidth - maxWidth2ndChild;
- if (widthFor1stChild > 50) {
- for (const el of $$('#options .aligned > :nth-child(1)')) {
- if (el.offsetWidth > widthFor1stChild) {
- el.style.cssText = 'word-break: break-all; hyphens: auto;';
- }
- }
- } else {
- const width = $('#options').clientWidth;
- document.head.appendChild($create('style', `
- #options .aligned > nth-child(1) {
- max-width: 70px;
- }
- #options .aligned > nth-child(2) {
- max-width: ${width - 70}px;
- }
- `));
- }
- });
-
if (chrome.windows) {
queryTabs({currentWindow: true}).then(tabs => {
const windowId = tabs[0].windowId;
diff --git a/js/localization.js b/js/localization.js
index 79b242e9..c0c5a28f 100644
--- a/js/localization.js
+++ b/js/localization.js
@@ -48,26 +48,14 @@ function tHTML(html, tag) {
function tNodeList(nodes) {
const PREFIX = 'i18n-';
+
for (let n = nodes.length; --n >= 0;) {
const node = nodes[n];
- // skip non-ELEMENT_NODE
- if (node.nodeType !== 1) {
+ if (node.nodeType !== Node.ELEMENT_NODE) {
continue;
}
if (node.localName === 'template') {
- const elements = node.content.querySelectorAll('*');
- tNodeList(elements);
- template[node.dataset.id] = elements[0];
- // compress inter-tag whitespace to reduce number of DOM nodes by 25%
- const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
- const toRemove = [];
- while (walker.nextNode()) {
- const textNode = walker.currentNode;
- if (!textNode.nodeValue.trim()) {
- toRemove.push(textNode);
- }
- }
- toRemove.forEach(el => el.remove());
+ createTemplate(node);
continue;
}
for (let a = node.attributes.length; --a >= 0;) {
@@ -78,26 +66,71 @@ function tNodeList(nodes) {
}
const type = name.substr(PREFIX.length);
const value = t(attr.value);
+ let toInsert, before;
switch (type) {
+ case 'word-break':
+ // we already know that: hasWordBreak
+ break;
case 'text':
- node.insertBefore(document.createTextNode(value), node.firstChild);
- break;
+ before = node.firstChild;
+ // fallthrough to text-append
case 'text-append':
- node.appendChild(document.createTextNode(value));
+ toInsert = createText(value);
break;
- case 'html':
- // localized strings only allow having text nodes and links
- node.textContent = '';
- [...tHTML(value, 'div').childNodes]
- .filter(a => a.nodeType === a.TEXT_NODE || a.tagName === 'A')
- .forEach(n => node.appendChild(n));
+ case 'html': {
+ toInsert = createHtml(value);
break;
+ }
default:
node.setAttribute(type, value);
}
+ tDocLoader.pause();
+ if (toInsert) {
+ node.insertBefore(toInsert, before || null);
+ }
node.removeAttribute(name);
}
}
+
+ function createTemplate(node) {
+ const elements = node.content.querySelectorAll('*');
+ tNodeList(elements);
+ template[node.dataset.id] = elements[0];
+ // compress inter-tag whitespace to reduce number of DOM nodes by 25%
+ const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
+ const toRemove = [];
+ while (walker.nextNode()) {
+ const textNode = walker.currentNode;
+ if (!textNode.nodeValue.trim()) {
+ toRemove.push(textNode);
+ }
+ }
+ tDocLoader.pause();
+ toRemove.forEach(el => el.remove());
+ }
+
+ function createText(str) {
+ return document.createTextNode(tWordBreak(str));
+ }
+
+ function createHtml(value) {
+ // bar are the only recognizable HTML elements
+ const rx = /(?:]*)>([^<]*)<\/a>)?([^<]*)/gi;
+ const bin = document.createDocumentFragment();
+ for (let m; (m = rx.exec(value)) && m[0];) {
+ const [, linkParams, linkText, nextText] = m;
+ if (linkText) {
+ const href = /\bhref\s*=\s*(\S+)/.exec(linkParams);
+ const a = bin.appendChild(document.createElement('a'));
+ a.href = href && href[1].replace(/^(["'])(.*)\1$/, '$2') || '';
+ a.appendChild(createText(linkText));
+ }
+ if (nextText) {
+ bin.appendChild(createText(nextText));
+ }
+ }
+ return bin;
+ }
}
@@ -115,33 +148,50 @@ function tDocLoader() {
t.cache = {browserUIlanguage: UIlang};
localStorage.L10N = JSON.stringify(t.cache);
}
-
const cacheLength = Object.keys(t.cache).length;
- // localize HEAD
- tNodeList(document.getElementsByTagName('*'));
+ Object.assign(tDocLoader, {
+ observer: new MutationObserver(process),
+ start() {
+ if (!tDocLoader.observing) {
+ tDocLoader.observing = true;
+ tDocLoader.observer.observe(document, {subtree: true, childList: true});
+ }
+ },
+ stop() {
+ tDocLoader.pause();
+ document.removeEventListener('DOMContentLoaded', onLoad);
+ },
+ pause() {
+ if (tDocLoader.observing) {
+ tDocLoader.observing = false;
+ tDocLoader.observer.disconnect();
+ }
+ },
+ });
- // localize BODY
- const process = mutations => {
+ tNodeList(document.getElementsByTagName('*'));
+ tDocLoader.start();
+ document.addEventListener('DOMContentLoaded', onLoad);
+
+ function process(mutations) {
for (const mutation of mutations) {
tNodeList(mutation.addedNodes);
}
- };
- const observer = new MutationObserver(process);
- const onLoad = () => {
+ tDocLoader.start();
+ }
+
+ function onLoad() {
tDocLoader.stop();
- process(observer.takeRecords());
+ process(tDocLoader.observer.takeRecords());
if (cacheLength !== Object.keys(t.cache).length) {
localStorage.L10N = JSON.stringify(t.cache);
}
- };
- tDocLoader.start = () => {
- observer.observe(document, {subtree: true, childList: true});
- };
- tDocLoader.stop = () => {
- observer.disconnect();
- document.removeEventListener('DOMContentLoaded', onLoad);
- };
- tDocLoader.start();
- document.addEventListener('DOMContentLoaded', onLoad);
+ }
+}
+
+
+function tWordBreak(text) {
+ // adds soft hyphens every 10 characters to ensure the long words break before breaking the layout
+ return text.length <= 10 ? text : text.replace(/[\d\w\u0080-\uFFFF]{10}|((?!\s)\W){10}/g, '$&\u00AD');
}
diff --git a/manage/manage.js b/manage/manage.js
index 4069337a..506bdc31 100644
--- a/manage/manage.js
+++ b/manage/manage.js
@@ -179,7 +179,7 @@ function createStyleElement({style, name}) {
}
const parts = createStyleElement.parts;
parts.checker.checked = style.enabled;
- parts.nameLink.textContent = style.name;
+ parts.nameLink.textContent = tWordBreak(style.name);
parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id;
parts.homepage.href = parts.homepage.title = style.url || '';
From 3318db19995fe52e212b98e01285f3457425c73a Mon Sep 17 00:00:00 2001
From: tophf
Date: Fri, 8 Dec 2017 01:08:25 +0300
Subject: [PATCH 04/10] show .config-error on failure to save
---
manage/config-dialog.css | 32 +++++++++++++++++++++++
manage/config-dialog.js | 55 +++++++++++++++++++++++++++-------------
2 files changed, 69 insertions(+), 18 deletions(-)
diff --git a/manage/config-dialog.css b/manage/config-dialog.css
index 84849efb..4c7cc5bd 100644
--- a/manage/config-dialog.css
+++ b/manage/config-dialog.css
@@ -102,6 +102,29 @@
display: inline-flex;
}
+#message-box-buttons {
+ position: relative;
+}
+
+.config-error {
+ position: absolute;
+ z-index: 99;
+ left: 0;
+ right: 0;
+ bottom: -1rem;
+ padding: 0 .75rem;
+ line-height: 24px;
+ height: 24px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ background-color: red;
+ color: white;
+ font-weight: bold;
+ text-shadow: 0.5px 0.5px 6px #400;
+ animation: fadein .5s;
+}
+
.cm-colorview::before,
.color-swatch {
width: var(--onoffswitch-width) !important;
@@ -124,3 +147,12 @@
border: none !important;
box-shadow: 3px 3px 50px rgba(0,0,0,.5) !important;
}
+
+@keyframes fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
diff --git a/manage/config-dialog.js b/manage/config-dialog.js
index 08da4c69..7216c158 100644
--- a/manage/config-dialog.js
+++ b/manage/config-dialog.js
@@ -2,6 +2,7 @@
'use strict';
function configDialog(style) {
+ const AUTOSAVE_DELAY = 500;
const data = style.usercssData;
const varsHash = deepCopy(data.vars) || {};
const varNames = Object.keys(varsHash);
@@ -77,7 +78,7 @@ function configDialog(style) {
if (va) {
va.dirty = varsInitial[va.name] !== (isDefault(va) ? va.default : va.value);
if (prefs.get('config.autosave')) {
- debounce(save);
+ debounce(save, 0, {anyChangeIsDirty: true});
} else {
target.closest('label').classList.toggle('dirty', va.dirty);
updateButtons();
@@ -92,8 +93,9 @@ function configDialog(style) {
buttons.close.textContent = t(someDirty ? 'confirmCancel' : 'confirmClose');
}
- function save() {
- if (!vars.length || !vars.some(va => va.dirty)) {
+ function save({anyChangeIsDirty = false} = {}) {
+ if (!vars.length ||
+ !vars.some(va => va.dirty || anyChangeIsDirty && va.value !== va.savedValue)) {
return;
}
style.enabled = true;
@@ -117,10 +119,11 @@ function configDialog(style) {
!isDefault(va) &&
bgva.options.every(o => o.name !== va.value)) {
error = `'${va.value}' not in the updated '${va.type}' list`;
- } else if (!va.dirty) {
+ } else if (!va.dirty && (!anyChangeIsDirty || va.value === va.savedValue)) {
continue;
} else {
styleVars[va.name].value = va.value;
+ va.savedValue = va.value;
numValid++;
continue;
}
@@ -147,8 +150,13 @@ function configDialog(style) {
varsInitial = getInitialValues(deepCopy(saved.usercssData.vars));
vars.forEach(va => onchange({target: va.input}));
updateButtons();
+ $.remove('.config-error');
})
- .catch(errors => onhide() + messageBox.alert(Array.isArray(errors) ? errors.join('\n') : errors));
+ .catch(errors => {
+ const el = $('.config-error', messageBox.element) ||
+ $('#message-box-buttons').insertAdjacentElement('afterbegin', $create('.config-error'));
+ el.textContent = el.title = Array.isArray(errors) ? errors.join('\n') : errors;
+ });
}
function useDefault() {
@@ -184,9 +192,7 @@ function configDialog(style) {
va.input = $create('input.slider', {
va,
type: 'checkbox',
- onchange() {
- va.value = va.input.checked ? '1' : '0';
- },
+ onchange: updateVarOnChange,
}),
$create('span'),
]),
@@ -201,9 +207,7 @@ function configDialog(style) {
$create('.select-resizer', [
va.input = $create('select', {
va,
- onchange() {
- va.value = this.value;
- }
+ onchange: updateVarOnChange,
},
va.options.map(o =>
$create('option', {value: o.name}, o.label))),
@@ -218,10 +222,8 @@ function configDialog(style) {
va.input = $create('input', {
va,
type: 'text',
- oninput() {
- va.value = this.value;
- this.dispatchEvent(new Event('change', {bubbles: true}));
- },
+ onchange: updateVarOnChange,
+ oninput: updateVarOnInput,
}),
];
break;
@@ -234,6 +236,18 @@ function configDialog(style) {
}
}
+ function updateVarOnChange() {
+ this.va.value = this.value;
+ }
+
+ function updateVarOnInput(event, debounced = false) {
+ if (debounced) {
+ event.target.dispatchEvent(new Event('change', {bubbles: true}));
+ } else {
+ debounce(updateVarOnInput, AUTOSAVE_DELAY, event, true);
+ }
+ }
+
function renderValues() {
for (const va of vars) {
const value = isDefault(va) ? va.default : va.value;
@@ -287,13 +301,18 @@ function configDialog(style) {
const colorpicker = document.body.appendChild(
$create('.colorpicker-popup', {style: 'display: none!important'}));
+ const PADDING = 50;
const MIN_WIDTH = parseFloat(getComputedStyle(colorpicker).width) || 350;
- const MIN_HEIGHT = 250;
+ const MIN_HEIGHT = 250 + PADDING;
colorpicker.remove();
- width = Math.max(Math.min(width / 0.9 + 2, 800), MIN_WIDTH);
- height = Math.max(Math.min(height / 0.9 + 2, 600), MIN_HEIGHT);
+ width = constrain(MIN_WIDTH, 800, width + PADDING);
+ height = constrain(MIN_HEIGHT, 600, height + PADDING);
document.body.style.setProperty('min-width', width + 'px', 'important');
document.body.style.setProperty('min-height', height + 'px', 'important');
}
+
+ function constrain(min, max, value) {
+ return value < min ? min : value > max ? max : value;
+ }
}
From 2deffbc6228fc8e1465f6d2357f347cac9211369 Mon Sep 17 00:00:00 2001
From: tophf
Date: Fri, 8 Dec 2017 03:23:09 +0300
Subject: [PATCH 05/10] show "x" to reset non-default values in usercss config
individually
also:
* simplified CSS selectors where possible
* .config-name = var name, 1st element in
-
+
diff --git a/manage/manage.css b/manage/manage.css
index c48c1604..984dc3ee 100644
--- a/manage/manage.css
+++ b/manage/manage.css
@@ -84,23 +84,28 @@ select {
#header a[href^="edit"] {
text-decoration: none;
+ margin-right: 8px;
}
-#add-style-label {
- margin-right: .25em;
- margin-bottom: .25em;
+#add-style-wrapper {
+ display: flex;
+ align-items: center;
+ padding-bottom: 1.5em;
}
#add-style-as-usercss-wrapper {
display: inline-flex;
+ margin-top: 3px;
}
#add-style-as-usercss-wrapper:not(:hover) input:not(:checked) ~ a svg {
fill: #aaa;
}
-#usercss-wiki svg {
- margin-top: -4px;
+#add-style-as-usercss-wrapper #usercss-wiki {
+ position: absolute;
+ right: -20px;
+ top: -3px;
}
#installed {
@@ -256,8 +261,7 @@ select {
}
/* collapsibles */
-#header details:not(#filters),
-#add-style-wrapper {
+#header details:not(#filters) {
padding-bottom: .7em;
}
@@ -825,8 +829,14 @@ input[id^="manage.newUI"] {
#search {
flex-grow: 1;
margin: 0.25rem 0 0;
- padding-left: 0.25rem;
- border-width: 1px;
+ background: #fff;
+ height: 20px;
+ box-sizing: border-box;
+ padding: 3px 3px 3px 4px;
+ font: 400 12px Arial;
+ color: #000;
+ border: 1px solid hsl(0, 0%, 66%);
+ border-radius: 0.25rem;
}
#search-wrapper .info {
diff --git a/options/options.css b/options/options.css
index 468e1a39..e885e47a 100644
--- a/options/options.css
+++ b/options/options.css
@@ -27,6 +27,56 @@ body {
max-width: 800px;
}
+.firefox button {
+ -moz-appearance: none;
+ user-select: none;
+ padding: 3px 7px;
+ border: 1px solid hsl(0, 0%, 62%);
+ font: 400 13px Arial;
+ line-height: 13px;
+ color: #000;
+ background-color: hsl(0, 0%, 100%);
+ background: url(../images/button.png)repeat-x;
+ background-size: 100% 100%;
+ transition: background-color .25s, border-color .25s;
+}
+
+.firefox button:hover {
+ background-color: hsl(0, 0%, 95%);
+ border-color: hsl(0, 0%, 52%);
+}
+
+.firefox.moz-appearance-bug button {
+ padding: 2px 4px;
+}
+
+:-webkit-any(button,input[type="button"],input[type="submit"]) {
+ -webkit-appearance: none;
+ user-select: none;
+ padding: 3px 7px;
+ border: 1px solid hsl(0, 0%, 62%);
+ border-radius: 0;
+ font: 400 13.3333px Arial;
+ color: hsl(0, 0%, 0%);
+ background-color: hsl(0, 0%, 100%);
+ background-image: url(../images/button.png);
+ background-repeat: repeat-x;
+ background-size: 100% 100%;
+ transition: background-color .25s, border-color .25s;
+ text-shadow: none;
+ box-shadow: none;
+ min-height: unset;
+}
+
+:enabled:hover:-webkit-any(select,input[type="checkbox"],input[type="radio"],:-webkit-any(button,input[type="button"],input[type="submit"])) {
+ background-color: hsl(0, 0%, 95%);
+ background-image: url(../images/button.png);
+ background-repeat: repeat-x;
+ text-shadow: none;
+ box-shadow: none;
+ border-color: hsl(0, 0%, 52%);
+}
+
@media (min-width: 401px) {
.firefox body {
width: calc(100% - 6px); /* TODO: rework to avoid compensating padding of 'html.firefox .block' */
From 0413736a299785a9dfa226cefbfc25623d62065c Mon Sep 17 00:00:00 2001
From: tophf
Date: Fri, 8 Dec 2017 06:20:30 +0300
Subject: [PATCH 07/10] remove the now redundant "chrome_style" from the
options page
---
manifest.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/manifest.json b/manifest.json
index cd2c8f13..bdcb0b33 100644
--- a/manifest.json
+++ b/manifest.json
@@ -77,6 +77,6 @@
"default_locale": "en",
"options_ui": {
"page": "options.html",
- "chrome_style": true
+ "chrome_style": false
}
}
From 1c68ac1a3a658397725b5c6afef0485ab8522b9a Mon Sep 17 00:00:00 2001
From: tophf
Date: Fri, 8 Dec 2017 05:45:27 +0300
Subject: [PATCH 08/10] initialize editor page fully in First Meaningful Paint
frame
* previously it wasn't the case when colorpicker option was enabled
* the cost of always loading colorview is ~1ms for >200ms here
---
.eslintrc | 1 +
edit.html | 6 +-
edit/codemirror-default.js | 4 +-
edit/codemirror-editing-hooks.js | 37 +++++++--
edit/colorpicker-helper.js | 26 +++---
edit/edit.js | 92 ++++++++--------------
js/dom.js | 5 ++
js/script-loader.js | 57 ++++++++++++++
manage/manage.js | 4 -
vendor-overwrites/colorpicker/colorview.js | 5 +-
10 files changed, 145 insertions(+), 92 deletions(-)
diff --git a/.eslintrc b/.eslintrc
index 0d866fff..bcfa68e3 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -51,6 +51,7 @@ globals:
tWordBreak: false
# dom.js
onDOMready: false
+ onDOMscriptReady: false
scrollElementIntoView: false
enforceInputRange: false
animateElement: false
diff --git a/edit.html b/edit.html
index c1966108..476c571d 100644
--- a/edit.html
+++ b/edit.html
@@ -33,6 +33,7 @@
+
@@ -67,8 +68,11 @@
+
+
+
+
-
diff --git a/edit/codemirror-default.js b/edit/codemirror-default.js
index e68d9f35..3a619cc3 100644
--- a/edit/codemirror-default.js
+++ b/edit/codemirror-default.js
@@ -25,12 +25,12 @@
styleActiveLine: true,
theme: 'default',
keyMap: prefs.get('editor.keyMap'),
- extraKeys: {
+ extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
// independent of current keyMap
'Alt-Enter': 'toggleStyle',
'Alt-PageDown': 'nextEditor',
'Alt-PageUp': 'prevEditor'
- },
+ }),
maxHighlightLength: 100e3,
};
diff --git a/edit/codemirror-editing-hooks.js b/edit/codemirror-editing-hooks.js
index 7cb66a69..5f2d16c0 100644
--- a/edit/codemirror-editing-hooks.js
+++ b/edit/codemirror-editing-hooks.js
@@ -5,8 +5,7 @@ global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
*/
'use strict';
-addEventListener('init:allDone', function _() {
- removeEventListener('init:allDone', _);
+onDOMscriptReady('/codemirror.js').then(() => {
CodeMirror.defaults.lint = linterConfig.getForCodeMirror();
@@ -48,11 +47,16 @@ addEventListener('init:allDone', function _() {
// cm.state.search for last used 'find'
let searchState;
- // N.B. the event listener should be registered before setupLivePrefs()
- $('#options').addEventListener('change', onOptionElementChanged);
- buildOptionsElements();
- setupLivePrefs();
- rerouteHotkeys(true);
+ onDOMready().then(() => {
+ prefs.subscribe(['editor.keyMap'], showKeyInSaveButtonTooltip);
+ showKeyInSaveButtonTooltip();
+
+ // N.B. the event listener should be registered before setupLivePrefs()
+ $('#options').addEventListener('change', onOptionElementChanged);
+ buildOptionsElements();
+
+ rerouteHotkeys(true);
+ });
return;
@@ -679,4 +683,23 @@ addEventListener('init:allDone', function _() {
});
});
}
+
+ function showKeyInSaveButtonTooltip(prefName, value) {
+ $('#save-button').title = findKeyForCommand('save', value);
+ }
+
+ function findKeyForCommand(command, mapName = CodeMirror.defaults.keyMap) {
+ const map = CodeMirror.keyMap[mapName];
+ let key = Object.keys(map).find(k => map[k] === command);
+ if (key) {
+ return key;
+ }
+ for (const ft of Array.isArray(map.fallthrough) ? map.fallthrough : [map.fallthrough]) {
+ key = ft && findKeyForCommand(command, ft);
+ if (key) {
+ return key;
+ }
+ }
+ return '';
+ }
});
diff --git a/edit/colorpicker-helper.js b/edit/colorpicker-helper.js
index ef53452a..03714092 100644
--- a/edit/colorpicker-helper.js
+++ b/edit/colorpicker-helper.js
@@ -1,34 +1,23 @@
/* global CodeMirror loadScript editors showHelp */
'use strict';
-// eslint-disable-next-line no-var
-var initColorpicker = () => {
+onDOMscriptReady('/colorview.js').then(() => {
initOverlayHooks();
onDOMready().then(() => {
$('#colorpicker-settings').onclick = configureColorpicker;
});
- const scripts = [
- '/vendor-overwrites/colorpicker/colorpicker.css',
- '/vendor-overwrites/colorpicker/colorpicker.js',
- '/vendor-overwrites/colorpicker/colorview.js',
- ];
prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey);
- prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand);
- return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true);
-
- function colorpickerOnDemand(id, enabled) {
- return loadScript(enabled && scripts)
- .then(() => setColorpickerOption(id, enabled));
- }
+ prefs.subscribe(['editor.colorpicker'], setColorpickerOption);
+ setColorpickerOption(null, prefs.get('editor.colorpicker'));
function setColorpickerOption(id, enabled) {
const defaults = CodeMirror.defaults;
const keyName = prefs.get('editor.colorpicker.hotkey');
- delete defaults.extraKeys[keyName];
defaults.colorpicker = enabled;
if (enabled) {
if (keyName) {
CodeMirror.commands.colorpicker = invokeColorpicker;
+ defaults.extraKeys = defaults.extraKeys || {};
defaults.extraKeys[keyName] = 'colorpicker';
}
defaults.colorpicker = {
@@ -45,6 +34,11 @@ var initColorpicker = () => {
},
},
};
+ } else {
+ CodeMirror.modeExtensions.css.unregisterColorviewHooks();
+ if (defaults.extraKeys) {
+ delete defaults.extraKeys[keyName];
+ }
}
// on page load runs before CodeMirror.setOption is defined
editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
@@ -162,4 +156,4 @@ var initColorpicker = () => {
return style;
}
}
-};
+});
diff --git a/edit/edit.js b/edit/edit.js
index a050e2fe..873841a3 100644
--- a/edit/edit.js
+++ b/edit/edit.js
@@ -3,8 +3,6 @@ global CodeMirror parserlib loadScript
global CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter
global mozParser createSourceEditor
global closeCurrentTab regExpTester messageBox
-global initColorpicker
-global initCollapsibles
global setupCodeMirror
global beautify
global initWithSectionStyle addSections removeSection getSectionsHashes
@@ -17,8 +15,6 @@ let dirty = {};
// array of all CodeMirror instances
const editors = [];
let saveSizeOnClose;
-// use browser history back when 'back to manage' is clicked
-let useHistoryBack;
// direct & reverse mapping of @-moz-document keywords and internal property names
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
@@ -35,14 +31,25 @@ Promise.all([
initStyleData(),
onDOMready(),
])
-.then(([style]) => Promise.all([
- style,
- initColorpicker(),
- initCollapsibles(),
- initHooksCommon(),
- dispatchEvent(new Event('init:allDone')),
-]))
-.then(createEditor);
+.then(([style]) => {
+ setupLivePrefs();
+
+ const usercss = isUsercss(style);
+ $('#heading').textContent = t(styleId ? 'editStyleHeading' : 'addStyleTitle');
+ $('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
+ $('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
+
+ $('#beautify').onclick = beautify;
+ $('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
+ window.addEventListener('resize', () => debounce(rememberWindowSize, 100));
+
+ if (usercss) {
+ editor = createSourceEditor(style);
+ } else {
+ initWithSectionStyle({style});
+ document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
+ }
+});
function preinit() {
// make querySelectorAll enumeration code readable
@@ -103,7 +110,18 @@ function preinit() {
getOwnTab().then(tab => {
const ownTabId = tab.id;
- useHistoryBack = sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href;
+
+ // use browser history back when 'back to manage' is clicked
+ if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
+ onDOMready().then(() => {
+ $('#cancel-button').onclick = event => {
+ event.stopPropagation();
+ event.preventDefault();
+ history.back();
+ };
+ });
+ }
+ // no windows on android
if (!chrome.windows) {
return;
}
@@ -130,20 +148,6 @@ function preinit() {
});
}
-function createEditor([style]) {
- const usercss = isUsercss(style);
- $('#heading').textContent = t(styleId ? 'editStyleHeading' : 'addStyleTitle');
- $('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
- $('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
- $('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
- if (usercss) {
- editor = createSourceEditor(style);
- } else {
- initWithSectionStyle({style});
- document.addEventListener('wheel', scrollEntirePageOnCtrlShift);
- }
-}
-
function onRuntimeMessage(request) {
switch (request.method) {
case 'styleUpdated':
@@ -270,40 +274,6 @@ function initHooks() {
}
// common for usercss and classic
-function initHooksCommon() {
- $('#cancel-button').addEventListener('click', goBackToManage);
- $('#beautify').addEventListener('click', beautify);
-
- prefs.subscribe(['editor.keyMap'], showKeyInSaveButtonTooltip);
- showKeyInSaveButtonTooltip();
-
- window.addEventListener('resize', () => debounce(rememberWindowSize, 100));
-
- function goBackToManage(event) {
- if (useHistoryBack) {
- event.stopPropagation();
- event.preventDefault();
- history.back();
- }
- }
- function showKeyInSaveButtonTooltip(prefName, value) {
- $('#save-button').title = findKeyForCommand('save', value);
- }
- function findKeyForCommand(command, mapName = CodeMirror.defaults.keyMap) {
- const map = CodeMirror.keyMap[mapName];
- let key = Object.keys(map).find(k => map[k] === command);
- if (key) {
- return key;
- }
- for (const ft of Array.isArray(map.fallthrough) ? map.fallthrough : [map.fallthrough]) {
- key = ft && findKeyForCommand(command, ft);
- if (key) {
- return key;
- }
- }
- return '';
- }
-}
function onChange(event) {
const node = event.target;
diff --git a/js/dom.js b/js/dom.js
index c9a6246f..fc0abbbe 100644
--- a/js/dom.js
+++ b/js/dom.js
@@ -55,6 +55,7 @@ $$.remove = (selector, base = document) => {
onDOMready().then(() => {
$.remove('#firefox-transitions-bug-suppressor');
+ initCollapsibles();
});
if (!chrome.app && chrome.windows) {
@@ -278,9 +279,13 @@ function $createLink(href = '', content) {
}
+// makes with [data-pref] save/restore their state
function initCollapsibles({bindClickOn = 'h2'} = {}) {
const prefMap = {};
const elements = $$('details[data-pref]');
+ if (!elements.length) {
+ return;
+ }
for (const el of elements) {
const key = el.dataset.pref;
diff --git a/js/script-loader.js b/js/script-loader.js
index 35d0ecde..8352bb38 100644
--- a/js/script-loader.js
+++ b/js/script-loader.js
@@ -44,3 +44,60 @@ var loadScript = (() => {
return Promise.all(files.map(f => (typeof f === 'string' ? inject(f) : f)));
};
})();
+
+
+(() => {
+ let subscribers, observer;
+ // natively declared