diff --git a/_locales/en/messages.json b/_locales/en/messages.json index bf57d82a..a5b09863 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" }, + "defaultTheme": { + "message": "default", + "description": "Default CodeMirror CSS theme option on the edit style page" + }, "deleteStyleLabel": { "message": "Delete", "description": "Label for the button to delete a style" diff --git a/apply.js b/apply.js index 64fe523e..cc8fc8a0 100644 --- a/apply.js +++ b/apply.js @@ -1,7 +1,18 @@ -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() { + // 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(); + if (bg && bg.getStyles) { + bg.getStyles(request, applyStyles); + return; + } + } chrome.extension.sendMessage(request, applyStyles); } @@ -63,6 +74,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 fe199f13..8ad9d131 100644 --- a/background.js +++ b/background.js @@ -392,3 +392,8 @@ function openURL(options) { } }); } + +var codeMirrorThemes; +getCodeMirrorThemes(function(themes) { + codeMirrorThemes = themes; +}); 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..aa957415 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,80 @@ function initCodeMirror() { }); } + // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->loadPrefs() + var theme = prefs.getPref("editor.theme"); + document.getElementById("cm-theme").href = theme == "default" ? "" : "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 optionsHtmlFromArray(options) { + return options.map(function(opt) { return ""; }).join(""); + } + var themeControl = document.getElementById("editor.theme"); + var bg = chrome.extension.getBackgroundPage(); + if (bg && bg.codeMirrorThemes) { + 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 = optionsHtmlFromArray([theme == "default" ? t("defaultTheme") : theme]); + getCodeMirrorThemes(function(themes) { + themeControl.innerHTML = optionsHtmlFromArray(themes); + themeControl.selectedIndex = Math.max(0, themes.indexOf(theme)); + }); + } + 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]; + }); + 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 || value == "default" || value == t("defaultTheme")) { + 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 @@ -598,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) { @@ -626,6 +683,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(); @@ -787,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) { @@ -806,8 +866,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/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 a60254d6..6e03b152 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?", @@ -222,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("defaultTheme")]; + 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); + } + }); + }); + }); +}