Merge pull request #103 from tophf/editor-theme

Editor: option to select CodeMirror CSS theme
This commit is contained in:
Jason Barnabe 2015-05-05 20:57:08 -05:00
commit b6444c86ec
8 changed files with 167 additions and 40 deletions

View File

@ -96,10 +96,18 @@
"message": "Tab size", "message": "Tab size",
"description": "Label for the text box controlling tab size option for the style editor." "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": { "dbError": {
"message": "An error has occurred using the Stylish database. Would you like to visit a web page with possible solutions?", "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" "description": "Prompt when a DB error is encountered"
}, },
"defaultTheme": {
"message": "default",
"description": "Default CodeMirror CSS theme option on the edit style page"
},
"deleteStyleLabel": { "deleteStyleLabel": {
"message": "Delete", "message": "Delete",
"description": "Label for the button to delete a style" "description": "Label for the button to delete a style"

View File

@ -1,7 +1,18 @@
var request = {method: "getStyles", matchUrl: location.href, enabled: true, asHash: true}; requestStyles();
if (location.href.indexOf(chrome.extension.getURL("")) == 0) {
chrome.extension.getBackgroundPage().getStyles(request, applyStyles); function requestStyles() {
} else { // 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); chrome.extension.sendMessage(request, applyStyles);
} }
@ -63,6 +74,10 @@ function removeStyle(id, doc) {
} }
function applyStyles(styleHash) { function applyStyles(styleHash) {
if (!styleHash) { // Chrome is starting up
requestStyles();
return;
}
if ("disableAll" in styleHash) { if ("disableAll" in styleHash) {
disableAll(styleHash.disableAll); disableAll(styleHash.disableAll);
delete styleHash.disableAll; delete styleHash.disableAll;

View File

@ -392,3 +392,8 @@ function openURL(options) {
} }
}); });
} }
var codeMirrorThemes;
getCodeMirrorThemes(function(themes) {
codeMirrorThemes = themes;
});

View File

@ -276,12 +276,25 @@
#sections > *:not(h2) { #sections > *:not(h2) {
padding-left: 0.4rem; padding-left: 0.4rem;
} }
.applies-type {
width: 30%;
}
}
@media(max-width:500px) {
#options {
-webkit-column-count: 1;
}
#options #tabSize-label {
position: static;
}
} }
</style> </style>
<link id="cm-theme" rel="stylesheet">
<script src="storage.js"></script> <script src="storage.js"></script>
<script src="messaging.js"></script> <script src="messaging.js"></script>
<script src="localization.js"></script> <script src="localization.js"></script>
<script src="apply.js"></script> <script src="apply.js"></script>
<script src="edit.js"></script>
</head> </head>
<body id="stylish-edit"> <body id="stylish-edit">
<div id="header"> <div id="header">
@ -322,11 +335,14 @@
<label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label> <label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label>
<select data-option="keyMap" id="editor.keyMap"></select> <select data-option="keyMap" id="editor.keyMap"></select>
</div> </div>
<div class="option aligned">
<label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label>
<select data-option="theme" id="editor.theme"></select>
</div>
</section> </section>
</div> </div>
<section id="sections"> <section id="sections">
<h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><img id="sections-help" src="help.png" i18n-alt="helpAlt"></h2> <h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><img id="sections-help" src="help.png" i18n-alt="helpAlt"></h2>
</section> </section>
<script src="edit.js"></script>
</body> </body>
</html> </html>

115
edit.js
View File

@ -128,6 +128,7 @@ function initCodeMirror() {
matchBrackets: true, matchBrackets: true,
lint: CodeMirror.lint.css, lint: CodeMirror.lint.css,
keyMap: "sublime", keyMap: "sublime",
theme: "default",
extraKeys: {"Ctrl-Space": "autocomplete"} extraKeys: {"Ctrl-Space": "autocomplete"}
} }
mergeOptions(stylishOptions, CM.defaults); 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 // initialize global editor controls
document.getElementById("options").addEventListener("change", acmeEventListener, false); document.addEventListener("DOMContentLoaded", function() {
function optionsHtmlFromArray(options) {
var keymapControl = document.getElementById("editor.keyMap"); return options.map(function(opt) { return "<option>" + opt + "</option>"; }).join("");
Object.keys(CodeMirror.keyMap).sort().forEach(function(map) { }
keymapControl.appendChild(document.createElement("option")).textContent = map; 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(); initCodeMirror();
function acmeEventListener(event) { function acmeEventListener(event) {
var option = event.target.dataset.option; var el = event.target;
console.log("acmeEventListener heard %s on %s", event.type, event.target.id); var option = el.dataset.option;
if (!option) console.error("acmeEventListener: no 'cm_option' %O", event.target); //console.log("acmeEventListener heard %s on %s", event.type, el.id);
else CodeMirror.setOption(option, event.target[isCheckbox(event.target) ? "checked" : "value"]); if (!option) {
console.error("acmeEventListener: no 'cm_option' %O", el);
if ("tabSize" === option) CodeMirror.setOption("indentUnit", CodeMirror.getOption("tabSize")); 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",
'<link id="cm-theme2" rel="stylesheet" href="' + url + '">');
(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 // replace given textarea with the CodeMirror editor
@ -598,11 +648,18 @@ function init() {
return; return;
} }
// This is an edit // This is an edit
chrome.extension.sendMessage({method: "getStyles", id: params.id}, function(styles) { requestStyle();
var style = styles[0]; function requestStyle() {
styleId = style.id; chrome.extension.sendMessage({method: "getStyles", id: params.id}, function callback(styles) {
initWithStyle(style); if (!styles) { // Chrome is starting up and shows edit.html
}); requestStyle();
return;
}
var style = styles[0];
styleId = style.id;
initWithStyle(style);
});
}
} }
function initWithStyle(style) { function initWithStyle(style) {
@ -626,6 +683,10 @@ function initHooks() {
node.addEventListener("change", onChange); node.addEventListener("change", onChange);
node.addEventListener("input", 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(); setupGlobalSearch();
setCleanGlobal(); setCleanGlobal();
@ -787,7 +848,6 @@ function getParams() {
} }
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
var installed = document.getElementById("installed");
switch (request.method) { switch (request.method) {
case "styleUpdated": case "styleUpdated":
if (styleId == request.id) { 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);

View File

@ -1,7 +1,11 @@
chrome.extension.sendMessage({method: "healthCheck"}, function(ok) { healthCheck();
if (!ok) {
if (confirm(t("dbError"))) { 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"); window.open("http://userstyles.org/dberror");
} }
} });
}); }

View File

@ -28,6 +28,10 @@ loadPrefs({
}); });
function showStyles(styles) { 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)}); styles.sort(function(a, b) { return a.name.localeCompare(b.name)});
var installed = document.getElementById("installed"); var installed = document.getElementById("installed");
styles.map(createStyleElement).forEach(function(e) { styles.map(createStyleElement).forEach(function(e) {

View File

@ -178,6 +178,7 @@ var prefs = {
"editor.indentWithTabs": false,// smart indent with tabs "editor.indentWithTabs": false,// smart indent with tabs
"editor.tabSize": 4, // tab width, in spaces "editor.tabSize": 4, // tab width, in spaces
"editor.keyMap": "sublime", // keymap "editor.keyMap": "sublime", // keymap
"editor.theme": "default", // CSS theme
NO_DEFAULT_PREFERENCE: "No default preference for '%s'", NO_DEFAULT_PREFERENCE: "No default preference for '%s'",
UNHANDLED_DATA_TYPE: "Default '%s' is of type '%s' - what should be done with it?", 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) } 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);
}
});
});
});
}