From bc6476bc523496d87326e2d79c2cb28c26485521 Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 11 Apr 2015 13:28:25 +0300 Subject: [PATCH 1/3] Editor: option to select CodeMirror CSS theme --- _locales/en/messages.json | 8 ++++ background.js | 14 +++++++ edit.html | 18 +++++++- edit.js | 87 ++++++++++++++++++++++++++++----------- storage.js | 1 + 5 files changed, 103 insertions(+), 25 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index bf57d82a..4ea59264 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -96,10 +96,18 @@ "message": "Tab size", "description": "Label for the text box controlling tab size option for the style editor." }, + "cm_theme": { + "message": "Theme", + "description": "Label for the style editor's CSS theme." + }, "dbError": { "message": "An error has occurred using the Stylish database. Would you like to visit a web page with possible solutions?", "description": "Prompt when a DB error is encountered" }, + "default": { + "message": "default", + "description": "Default item in various lists" + }, "deleteStyleLabel": { "message": "Delete", "description": "Label for the button to delete a style" diff --git a/background.js b/background.js index fe199f13..2044a0d6 100644 --- a/background.js +++ b/background.js @@ -392,3 +392,17 @@ function openURL(options) { } }); } + +var codeMirrorThemes = [chrome.i18n.getMessage("default")]; +chrome.runtime.getPackageDirectoryEntry(function(rootDir) { + rootDir.getDirectory("codemirror/theme", {create: false}, function(themeDir) { + themeDir.createReader().readEntries(function(entries) { + entries + .filter(function(entry) { return entry.isFile }) + .sort(function(a, b) { return a.name < b.name ? -1 : 1 }) + .forEach(function(entry) { + codeMirrorThemes.push(entry.name.replace(/\.css$/, "")); + }); + }); + }); +}); diff --git a/edit.html b/edit.html index 60f6100f..f26b2521 100644 --- a/edit.html +++ b/edit.html @@ -276,12 +276,25 @@ #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.js b/edit.js index 469213f6..83198ca5 100644 --- a/edit.js +++ b/edit.js @@ -128,6 +128,7 @@ function initCodeMirror() { matchBrackets: true, lint: CodeMirror.lint.css, keyMap: "sublime", + theme: "default", extraKeys: {"Ctrl-Space": "autocomplete"} } mergeOptions(stylishOptions, CM.defaults); @@ -169,31 +170,70 @@ function initCodeMirror() { }); } + // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->loadPrefs() + var theme = prefs.getPref("editor.theme"); + var themes = chrome.extension.getBackgroundPage().codeMirrorThemes; + document.getElementById("cm-theme").href = themes.indexOf(theme) <= 0 ? "" : "codemirror/theme/" + theme + ".css"; + // initialize global editor controls - document.getElementById("options").addEventListener("change", acmeEventListener, false); - - var keymapControl = document.getElementById("editor.keyMap"); - Object.keys(CodeMirror.keyMap).sort().forEach(function(map) { - keymapControl.appendChild(document.createElement("option")).textContent = map; + document.addEventListener("DOMContentLoaded", function() { + function concatOption(html, option) { + return html + ""; + } + document.getElementById("editor.theme").innerHTML = themes.reduce(concatOption, ""); + document.getElementById("editor.keyMap").innerHTML = Object.keys(CM.keyMap).sort().reduce(concatOption, ""); + var controlPrefs = {}; + document.querySelectorAll("#options *[data-option][id^='editor.']").forEach(function(option) { + controlPrefs[option.id] = CM.defaults[option.dataset.option]; + }); + document.getElementById("options").addEventListener("change", acmeEventListener, false); + loadPrefs(controlPrefs); }); - - var controlPrefs = {}, - controlOptions = ["smartIndent", "indentWithTabs", "tabSize", "keyMap", "lineWrapping"]; - controlOptions.forEach(function(option) { - controlPrefs["editor." + option] = CM.defaults[option]; - }); - loadPrefs(controlPrefs); - } initCodeMirror(); function acmeEventListener(event) { - var option = event.target.dataset.option; - console.log("acmeEventListener heard %s on %s", event.type, event.target.id); - if (!option) console.error("acmeEventListener: no 'cm_option' %O", event.target); - else CodeMirror.setOption(option, event.target[isCheckbox(event.target) ? "checked" : "value"]); - - if ("tabSize" === option) CodeMirror.setOption("indentUnit", CodeMirror.getOption("tabSize")); + var el = event.target; + var option = el.dataset.option; + //console.log("acmeEventListener heard %s on %s", event.type, el.id); + if (!option) { + console.error("acmeEventListener: no 'cm_option' %O", el); + return; + } + var value = el.type == "checkbox" ? el.checked : el.value; + switch (option) { + case "tabSize": + CodeMirror.setOption("indentUnit", value); + break; + case "theme": + var themeLink = document.getElementById("cm-theme"); + // use non-localized "default" internally + if (!value || el.selectedIndex <= 0) { + value = "default"; + if (prefs.getPref(el.id) != value) { + prefs.setPref(el.id, value); + } + themeLink.href = ""; + el.selectedIndex = 0; + break; + } + var url = chrome.extension.getURL("codemirror/theme/" + value + ".css"); + if (themeLink.href == url) { // preloaded in initCodeMirror() + break; + } + // avoid flicker: wait for the second stylesheet to load, then apply the theme + document.head.insertAdjacentHTML("beforeend", + ''); + (function() { + setTimeout(function() { + CodeMirror.setOption(option, value); + themeLink.remove(); + document.getElementById("cm-theme2").id = "cm-theme"; + }, 100); + })(); + return; + } + CodeMirror.setOption(option, value); } // replace given textarea with the CodeMirror editor @@ -626,6 +666,10 @@ function initHooks() { node.addEventListener("change", onChange); node.addEventListener("input", onChange); }); + document.getElementById("to-mozilla").addEventListener("click", showMozillaFormat, false); + document.getElementById("to-mozilla-help").addEventListener("click", showToMozillaHelp, false); + document.getElementById("save-button").addEventListener("click", save, false); + document.getElementById("sections-help").addEventListener("click", showSectionHelp, false); setupGlobalSearch(); setCleanGlobal(); @@ -806,8 +850,3 @@ chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { } } }); - -document.getElementById("to-mozilla").addEventListener("click", showMozillaFormat, false); -document.getElementById("to-mozilla-help").addEventListener("click", showToMozillaHelp, false); -document.getElementById("save-button").addEventListener("click", save, false); -document.getElementById("sections-help").addEventListener("click", showSectionHelp, false); diff --git a/storage.js b/storage.js index a60254d6..b44767ae 100644 --- a/storage.js +++ b/storage.js @@ -178,6 +178,7 @@ var prefs = { "editor.indentWithTabs": false,// smart indent with tabs "editor.tabSize": 4, // tab width, in spaces "editor.keyMap": "sublime", // keymap + "editor.theme": "default", // CSS theme NO_DEFAULT_PREFERENCE: "No default preference for '%s'", UNHANDLED_DATA_TYPE: "Default '%s' is of type '%s' - what should be done with it?", From a0c5674f6f4251e6dcc442fae72d5799325760da Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 5 May 2015 18:21:45 +0300 Subject: [PATCH 2/3] Wait for background page to load on Chrome startup At startup Chrome lazy-loads extension's background page, thus occasionally breaking apply.js and Stylish own pages --- apply.js | 19 +++++++++++++++---- background.js | 15 +++------------ edit.js | 36 ++++++++++++++++++++++++++---------- health.js | 14 +++++++++----- manage.js | 4 ++++ storage.js | 19 +++++++++++++++++++ 6 files changed, 76 insertions(+), 31 deletions(-) diff --git a/apply.js b/apply.js index 64fe523e..6d31bf5a 100644 --- a/apply.js +++ b/apply.js @@ -1,7 +1,14 @@ -var request = {method: "getStyles", matchUrl: location.href, enabled: true, asHash: true}; -if (location.href.indexOf(chrome.extension.getURL("")) == 0) { - chrome.extension.getBackgroundPage().getStyles(request, applyStyles); -} else { +requestStyles(); + +function requestStyles() { + var request = {method: "getStyles", matchUrl: location.href, enabled: true, asHash: true}; + if (location.href.indexOf(chrome.extension.getURL("")) == 0) { + var bg = chrome.extension.getBackgroundPage(); + if (bg && bg.getStyles) { + bg.getStyles(request, applyStyles); + return; + } + } chrome.extension.sendMessage(request, applyStyles); } @@ -63,6 +70,10 @@ function removeStyle(id, doc) { } function applyStyles(styleHash) { + if (!styleHash) { // Chrome is starting up + requestStyles(); + return; + } if ("disableAll" in styleHash) { disableAll(styleHash.disableAll); delete styleHash.disableAll; diff --git a/background.js b/background.js index 2044a0d6..8ad9d131 100644 --- a/background.js +++ b/background.js @@ -393,16 +393,7 @@ function openURL(options) { }); } -var codeMirrorThemes = [chrome.i18n.getMessage("default")]; -chrome.runtime.getPackageDirectoryEntry(function(rootDir) { - rootDir.getDirectory("codemirror/theme", {create: false}, function(themeDir) { - themeDir.createReader().readEntries(function(entries) { - entries - .filter(function(entry) { return entry.isFile }) - .sort(function(a, b) { return a.name < b.name ? -1 : 1 }) - .forEach(function(entry) { - codeMirrorThemes.push(entry.name.replace(/\.css$/, "")); - }); - }); - }); +var codeMirrorThemes; +getCodeMirrorThemes(function(themes) { + codeMirrorThemes = themes; }); diff --git a/edit.js b/edit.js index 83198ca5..64cb30ba 100644 --- a/edit.js +++ b/edit.js @@ -172,15 +172,25 @@ function initCodeMirror() { // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->loadPrefs() var theme = prefs.getPref("editor.theme"); - var themes = chrome.extension.getBackgroundPage().codeMirrorThemes; - document.getElementById("cm-theme").href = themes.indexOf(theme) <= 0 ? "" : "codemirror/theme/" + theme + ".css"; + document.getElementById("cm-theme").href = theme == "default" ? "" : "codemirror/theme/" + theme + ".css"; // initialize global editor controls document.addEventListener("DOMContentLoaded", function() { function concatOption(html, option) { return html + ""; } - document.getElementById("editor.theme").innerHTML = themes.reduce(concatOption, ""); + var bg = chrome.extension.getBackgroundPage(); + var themeControl = document.getElementById("editor.theme"); + if (bg && bg.codeMirrorThemes) { + themeControl.innerHTML = bg.codeMirrorThemes.reduce(concatOption, ""); + } else { + // Chrome is starting up and shows our edit.html, but the background page isn't loaded yet + themeControl.innerHTML = concatOption("", theme == "default" ? t(theme) : theme); + getCodeMirrorThemes(function(themes) { + themeControl.innerHTML = themes.reduce(concatOption, ""); + themeControl.selectedIndex = Math.max(0, themes.indexOf(theme)); + }); + } document.getElementById("editor.keyMap").innerHTML = Object.keys(CM.keyMap).sort().reduce(concatOption, ""); var controlPrefs = {}; document.querySelectorAll("#options *[data-option][id^='editor.']").forEach(function(option) { @@ -208,7 +218,7 @@ function acmeEventListener(event) { case "theme": var themeLink = document.getElementById("cm-theme"); // use non-localized "default" internally - if (!value || el.selectedIndex <= 0) { + if (!value || value == "default" || value == t("default")) { value = "default"; if (prefs.getPref(el.id) != value) { prefs.setPref(el.id, value); @@ -638,11 +648,18 @@ function init() { return; } // This is an edit - chrome.extension.sendMessage({method: "getStyles", id: params.id}, function(styles) { - var style = styles[0]; - styleId = style.id; - initWithStyle(style); - }); + requestStyle(); + function requestStyle() { + chrome.extension.sendMessage({method: "getStyles", id: params.id}, function callback(styles) { + if (!styles) { // Chrome is starting up and shows edit.html + requestStyle(); + return; + } + var style = styles[0]; + styleId = style.id; + initWithStyle(style); + }); + } } function initWithStyle(style) { @@ -831,7 +848,6 @@ function getParams() { } chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { - var installed = document.getElementById("installed"); switch (request.method) { case "styleUpdated": if (styleId == request.id) { diff --git a/health.js b/health.js index 37faf120..8b04a875 100644 --- a/health.js +++ b/health.js @@ -1,7 +1,11 @@ -chrome.extension.sendMessage({method: "healthCheck"}, function(ok) { - if (!ok) { - if (confirm(t("dbError"))) { +healthCheck(); + +function healthCheck() { + chrome.extension.sendMessage({method: "healthCheck"}, function(ok) { + if (ok === undefined) { // Chrome is starting up + healthCheck(); + } else if (!ok && confirm(t("dbError"))) { window.open("http://userstyles.org/dberror"); } - } -}); + }); +} diff --git a/manage.js b/manage.js index ed66579b..9b514811 100644 --- a/manage.js +++ b/manage.js @@ -28,6 +28,10 @@ loadPrefs({ }); function showStyles(styles) { + if (!styles) { // Chrome is starting up + chrome.extension.sendMessage({method: "getStyles"}, showStyles); + return; + } styles.sort(function(a, b) { return a.name.localeCompare(b.name)}); var installed = document.getElementById("installed"); styles.map(createStyleElement).forEach(function(e) { diff --git a/storage.js b/storage.js index b44767ae..945c4632 100644 --- a/storage.js +++ b/storage.js @@ -223,3 +223,22 @@ var prefs = { }, removePref: function(key) { setPref(key, undefined) } }; + +function getCodeMirrorThemes(callback) { + chrome.runtime.getPackageDirectoryEntry(function(rootDir) { + rootDir.getDirectory("codemirror/theme", {create: false}, function(themeDir) { + themeDir.createReader().readEntries(function(entries) { + var themes = [chrome.i18n.getMessage("default")]; + entries + .filter(function(entry) { return entry.isFile }) + .sort(function(a, b) { return a.name < b.name ? -1 : 1 }) + .forEach(function(entry) { + themes.push(entry.name.replace(/\.css$/, "")); + }); + if (callback) { + callback(themes); + } + }); + }); + }); +} From 539be4ce43757248fad83fcca3d193414da12ae2 Mon Sep 17 00:00:00 2001 From: tophf Date: Tue, 5 May 2015 23:42:38 +0300 Subject: [PATCH 3/3] Code review & cosmetics --- _locales/en/messages.json | 4 ++-- apply.js | 4 ++++ edit.js | 16 ++++++++-------- storage.js | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4ea59264..a5b09863 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -104,9 +104,9 @@ "message": "An error has occurred using the Stylish database. Would you like to visit a web page with possible solutions?", "description": "Prompt when a DB error is encountered" }, - "default": { + "defaultTheme": { "message": "default", - "description": "Default item in various lists" + "description": "Default CodeMirror CSS theme option on the edit style page" }, "deleteStyleLabel": { "message": "Delete", diff --git a/apply.js b/apply.js index 6d31bf5a..cc8fc8a0 100644 --- a/apply.js +++ b/apply.js @@ -1,6 +1,10 @@ requestStyles(); function requestStyles() { + // If this is a Stylish page (Edit Style or Manage Styles), + // we'll request the styles directly to minimize delay and flicker, + // unless Chrome still starts up and the background page isn't fully loaded. + // (Note: in this case the function may be invoked again from applyStyles.) var request = {method: "getStyles", matchUrl: location.href, enabled: true, asHash: true}; if (location.href.indexOf(chrome.extension.getURL("")) == 0) { var bg = chrome.extension.getBackgroundPage(); diff --git a/edit.js b/edit.js index 64cb30ba..aa957415 100644 --- a/edit.js +++ b/edit.js @@ -176,22 +176,22 @@ function initCodeMirror() { // initialize global editor controls document.addEventListener("DOMContentLoaded", function() { - function concatOption(html, option) { - return html + ""; + function optionsHtmlFromArray(options) { + return options.map(function(opt) { return ""; }).join(""); } - var bg = chrome.extension.getBackgroundPage(); var themeControl = document.getElementById("editor.theme"); + var bg = chrome.extension.getBackgroundPage(); if (bg && bg.codeMirrorThemes) { - themeControl.innerHTML = bg.codeMirrorThemes.reduce(concatOption, ""); + themeControl.innerHTML = optionsHtmlFromArray(bg.codeMirrorThemes); } else { // Chrome is starting up and shows our edit.html, but the background page isn't loaded yet - themeControl.innerHTML = concatOption("", theme == "default" ? t(theme) : theme); + themeControl.innerHTML = optionsHtmlFromArray([theme == "default" ? t("defaultTheme") : theme]); getCodeMirrorThemes(function(themes) { - themeControl.innerHTML = themes.reduce(concatOption, ""); + themeControl.innerHTML = optionsHtmlFromArray(themes); themeControl.selectedIndex = Math.max(0, themes.indexOf(theme)); }); } - document.getElementById("editor.keyMap").innerHTML = Object.keys(CM.keyMap).sort().reduce(concatOption, ""); + document.getElementById("editor.keyMap").innerHTML = optionsHtmlFromArray(Object.keys(CM.keyMap).sort()); var controlPrefs = {}; document.querySelectorAll("#options *[data-option][id^='editor.']").forEach(function(option) { controlPrefs[option.id] = CM.defaults[option.dataset.option]; @@ -218,7 +218,7 @@ function acmeEventListener(event) { case "theme": var themeLink = document.getElementById("cm-theme"); // use non-localized "default" internally - if (!value || value == "default" || value == t("default")) { + if (!value || value == "default" || value == t("defaultTheme")) { value = "default"; if (prefs.getPref(el.id) != value) { prefs.setPref(el.id, value); diff --git a/storage.js b/storage.js index 945c4632..6e03b152 100644 --- a/storage.js +++ b/storage.js @@ -228,7 +228,7 @@ function getCodeMirrorThemes(callback) { chrome.runtime.getPackageDirectoryEntry(function(rootDir) { rootDir.getDirectory("codemirror/theme", {create: false}, function(themeDir) { themeDir.createReader().readEntries(function(entries) { - var themes = [chrome.i18n.getMessage("default")]; + var themes = [chrome.i18n.getMessage("defaultTheme")]; entries .filter(function(entry) { return entry.isFile }) .sort(function(a, b) { return a.name < b.name ? -1 : 1 })