| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945 |
- /* eslint no-tabs: 0, no-var: 0, indent: [2, tab, {VariableDeclarator: 0, SwitchCase: 1}], quotes: 0 */
- /* global CodeMirror */
- "use strict";
- var styleId = null;
- var dirty = {}; // only the actually dirty items here
- var editors = []; // array of all CodeMirror instances
- var saveSizeOnClose;
- var useHistoryBack; // use browser history back when "back to manage" is clicked
- // direct & reverse mapping of @-moz-document keywords and internal property names
- var propertyToCss = {urls: "url", urlPrefixes: "url-prefix", domains: "domain", regexps: "regexp"};
- var CssToProperty = {"url": "urls", "url-prefix": "urlPrefixes", "domain": "domains", "regexp": "regexps"};
- // if background page hasn't been loaded yet, increase the chances it has before DOMContentLoaded
- onBackgroundReady();
- // make querySelectorAll enumeration code readable
- ["forEach", "some", "indexOf", "map"].forEach(function(method) {
- NodeList.prototype[method]= Array.prototype[method];
- });
- // Chrome pre-34
- Element.prototype.matches = Element.prototype.matches || Element.prototype.webkitMatchesSelector;
- // Chrome pre-41 polyfill
- Element.prototype.closest = Element.prototype.closest || function(selector) {
- for (var e = this; e && !e.matches(selector); e = e.parentElement) {}
- return e;
- };
- Array.prototype.rotate = function(amount) { // negative amount == rotate left
- var r = this.slice(-amount, this.length);
- Array.prototype.push.apply(r, this.slice(0, this.length - r.length));
- return r;
- };
- 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) => {
- const themeElement = document.getElementById("cm-theme");
- if (themeElement) {
- themeElement.href = prefs.get("editor.theme") == "default" ? ""
- : "codemirror/theme/" + prefs.get("editor.theme") + ".css";
- observer.disconnect();
- }
- }).observe(document, {subtree: true, childList: true});
- getCodeMirrorThemes();
- // reroute handling to nearest editor when keypress resolves to one of these commands
- var hotkeyRerouter = {
- commands: {
- save: true, jumpToLine: true, nextEditor: true, prevEditor: true,
- find: true, findNext: true, findPrev: true, replace: true, replaceAll: true
- },
- setState: function(enable) {
- setTimeout(function() {
- document[(enable ? "add" : "remove") + "EventListener"]("keydown", hotkeyRerouter.eventHandler);
- }, 0);
- },
- eventHandler: function(event) {
- var keyName = CodeMirror.keyName(event);
- if ("handled" == CodeMirror.lookupKey(keyName, CodeMirror.getOption("keyMap"), handleCommand)
- || "handled" == CodeMirror.lookupKey(keyName, CodeMirror.defaults.extraKeys, handleCommand)) {
- event.preventDefault();
- event.stopPropagation();
- }
- function handleCommand(command) {
- if (hotkeyRerouter.commands[command] === true) {
- CodeMirror.commands[command](getEditorInSight(event.target));
- return true;
- }
- }
- }
- };
- function onChange(event) {
- var node = event.target;
- if ("savedValue" in node) {
- var currentValue = "checkbox" === node.type ? node.checked : node.value;
- 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());
- delete node.savedValue; // only valid when actually saved
- }
- updateTitle();
- }
- // Set .dirty on stylesheet contributors that have changed
- function setDirtyClass(node, isDirty) {
- node.classList.toggle("dirty", isDirty);
- }
- function setCleanItem(node, isClean) {
- if (!node.id) {
- node.id = Date.now().toString(32).substr(-6);
- }
- if (isClean) {
- delete dirty[node.id];
- // code sections have .CodeMirror property
- if (node.CodeMirror) {
- node.savedValue = node.CodeMirror.changeGeneration();
- } else {
- node.savedValue = "checkbox" === node.type ? node.checked : node.value;
- }
- } else {
- dirty[node.id] = true;
- }
- setDirtyClass(node, !isClean);
- }
- function isCleanGlobal() {
- var clean = Object.keys(dirty).length == 0;
- setDirtyClass(document.body, !clean);
- var saveBtn = document.getElementById("save-button")
- if (clean){
- //saveBtn.removeAttribute('disabled');
- }else{
- //saveBtn.setAttribute('disabled', true);
- }
- return clean;
- }
- function setCleanGlobal() {
- document.querySelectorAll("#header, #sections > div").forEach(setCleanSection);
- dirty = {}; // forget the dirty applies-to ids from a deleted section after the style was saved
- }
- function setCleanSection(section) {
- section.querySelectorAll(".style-contributor").forEach(function(node) { setCleanItem(node, true) });
- // #header section has no codemirror
- var cm = section.CodeMirror;
- if (cm) {
- section.savedValue = cm.changeGeneration();
- indicateCodeChange(cm);
- }
- }
- function initCodeMirror() {
- var CM = CodeMirror;
- var isWindowsOS = navigator.appVersion.indexOf("Windows") > 0;
- // CodeMirror miserably fails on keyMap="" so let's ensure it's not
- if (!prefs.get('editor.keyMap')) {
- prefs.reset('editor.keyMap');
- }
- // default option values
- Object.assign(CM.defaults, {
- mode: 'css',
- lineNumbers: true,
- lineWrapping: true,
- foldGutter: true,
- gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
- matchBrackets: true,
- highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true},
- lint: {getAnnotations: CodeMirror.lint.css, delay: prefs.get("editor.lintDelay")},
- lintReportDelay: prefs.get("editor.lintReportDelay"),
- styleActiveLine: true,
- theme: "default",
- keyMap: prefs.get("editor.keyMap"),
- extraKeys: { // independent of current keyMap
- "Alt-PageDown": "nextEditor",
- "Alt-PageUp": "prevEditor"
- }
- }, prefs.get("editor.options"));
- // additional commands
- CM.commands.jumpToLine = jumpToLine;
- CM.commands.nextEditor = function(cm) { nextPrevEditor(cm, 1) };
- CM.commands.prevEditor = function(cm) { nextPrevEditor(cm, -1) };
- CM.commands.save = save;
- CM.commands.blockComment = function(cm) {
- cm.blockComment(cm.getCursor("from"), cm.getCursor("to"), {fullLines: false});
- };
- // "basic" keymap only has basic keys by design, so we skip it
- var extraKeysCommands = {};
- Object.keys(CM.defaults.extraKeys).forEach(function(key) {
- extraKeysCommands[CM.defaults.extraKeys[key]] = true;
- });
- if (!extraKeysCommands.jumpToLine) {
- CM.keyMap.sublime["Ctrl-G"] = "jumpToLine";
- CM.keyMap.emacsy["Ctrl-G"] = "jumpToLine";
- CM.keyMap.pcDefault["Ctrl-J"] = "jumpToLine";
- CM.keyMap.macDefault["Cmd-J"] = "jumpToLine";
- }
- if (!extraKeysCommands.autocomplete) {
- CM.keyMap.pcDefault["Ctrl-Space"] = "autocomplete"; // will be used by "sublime" on PC via fallthrough
- CM.keyMap.macDefault["Alt-Space"] = "autocomplete"; // OSX uses Ctrl-Space and Cmd-Space for something else
- CM.keyMap.emacsy["Alt-/"] = "autocomplete"; // copied from "emacs" keymap
- // "vim" and "emacs" define their own autocomplete hotkeys
- }
- if (!extraKeysCommands.blockComment) {
- CM.keyMap.sublime["Shift-Ctrl-/"] = "blockComment";
- }
- if (isWindowsOS) {
- // "pcDefault" keymap on Windows should have F3/Shift-F3
- if (!extraKeysCommands.findNext) {
- CM.keyMap.pcDefault["F3"] = "findNext";
- }
- if (!extraKeysCommands.findPrev) {
- CM.keyMap.pcDefault["Shift-F3"] = "findPrev";
- }
- // try to remap non-interceptable Ctrl-(Shift-)N/T/W hotkeys
- ["N", "T", "W"].forEach(function(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) {
- var oldKey = remap.from + char;
- Object.keys(CM.keyMap).forEach(function(keyMapName) {
- var keyMap = CM.keyMap[keyMapName];
- var command = keyMap[oldKey];
- if (!command) {
- return;
- }
- remap.to.some(function(newMod) {
- var newKey = newMod + char;
- if (!(newKey in keyMap)) {
- delete keyMap[oldKey];
- keyMap[newKey] = command;
- return true;
- }
- });
- });
- });
- });
- }
- // user option values
- CM.getOption = function (o) {
- return CodeMirror.defaults[o];
- };
- CM.setOption = function (o, v) {
- CodeMirror.defaults[o] = v;
- editors.forEach(function(editor) {
- editor.setOption(o, v);
- });
- };
- CM.prototype.getSection = function() {
- return this.display.wrapper.parentNode;
- };
- // initialize global editor controls
- function optionsHtmlFromArray(options) {
- return options.map(function(opt) { return "<option>" + opt + "</option>"; }).join("");
- }
- var themeControl = document.getElementById("editor.theme");
- const themeList = localStorage.codeMirrorThemes;
- if (themeList) {
- themeControl.innerHTML = optionsHtmlFromArray(themeList.split(/\s+/));
- } 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]);
- getCodeMirrorThemes().then(() => {
- const themes = (localStorage.codeMirrorThemes || '').split(/\s+/);
- themeControl.innerHTML = optionsHtmlFromArray(themes);
- themeControl.selectedIndex = Math.max(0, themes.indexOf(theme));
- });
- }
- document.getElementById("editor.keyMap").innerHTML = optionsHtmlFromArray(Object.keys(CM.keyMap).sort());
- document.getElementById("options").addEventListener("change", acmeEventListener, false);
- setupLivePrefs();
- hotkeyRerouter.setState(true);
- }
- function acmeEventListener(event) {
- 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.get(el.id) != value) {
- prefs.set(el.id, value);
- }
- themeLink.href = "";
- el.selectedIndex = 0;
- break;
- }
- var url = chrome.runtime.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;
- case "highlightSelectionMatches":
- switch (value) {
- case 'token':
- case 'selection':
- document.body.dataset[option] = value;
- value = {showToken: value == 'token' && /[#.\-\w]/, annotateScrollbar: true};
- break;
- default:
- value = null;
- }
- }
- CodeMirror.setOption(option, value);
- }
- // replace given textarea with the CodeMirror editor
- function setupCodeMirror(textarea, index) {
- var cm = CodeMirror.fromTextArea(textarea, {lint: null});
- cm.on("change", indicateCodeChange);
- cm.on("blur", function(cm) {
- editors.lastActive = cm;
- hotkeyRerouter.setState(true);
- setTimeout(function() {
- var cm = editors.lastActive;
- var childFocused = cm.display.wrapper.contains(document.activeElement);
- cm.display.wrapper.classList.toggle("CodeMirror-active", childFocused);
- }, 0);
- });
- cm.on("focus", function() {
- hotkeyRerouter.setState(false);
- cm.display.wrapper.classList.add("CodeMirror-active");
- });
- cm.on('mousedown', (cm, event) => toggleContextMenuDelete.call(cm, event));
- var resizeGrip = cm.display.wrapper.appendChild(document.createElement("div"));
- resizeGrip.className = "resize-grip";
- resizeGrip.addEventListener("mousedown", function(e) {
- e.preventDefault();
- var cm = e.target.parentNode.CodeMirror;
- var minHeight = cm.defaultTextHeight()
- + cm.display.lineDiv.offsetParent.offsetTop /* .CodeMirror-lines padding */
- + cm.display.wrapper.offsetHeight - cm.display.wrapper.scrollHeight /* borders */;
- function resize(e) {
- cm.setSize(null, Math.max(minHeight, cm.display.wrapper.scrollHeight + e.movementY));
- }
- document.addEventListener("mousemove", resize);
- document.addEventListener("mouseup", function resizeStop() {
- document.removeEventListener("mouseup", resizeStop);
- document.removeEventListener("mousemove", resize);
- });
- });
- // resizeGrip has enough space when scrollbars.horiz is visible
- if (cm.display.scrollbars.horiz.style.display != "") {
- cm.display.scrollbars.vert.style.marginBottom = "0";
- }
- // resizeGrip space adjustment in case a long line was entered/deleted by a user
- new MutationObserver(function(mutations) {
- var hScrollbar = mutations[0].target;
- var hScrollbarVisible = hScrollbar.style.display != "";
- var vScrollbar = hScrollbar.parentNode.CodeMirror.display.scrollbars.vert;
- vScrollbar.style.marginBottom = hScrollbarVisible ? "0" : "";
- }).observe(cm.display.scrollbars.horiz, {
- attributes: true,
- attributeFilter: ["style"]
- });
- editors.splice(index || editors.length, 0, cm);
- return cm;
- }
- function indicateCodeChange(cm) {
- var section = cm.getSection();
- setCleanItem(section, cm.isClean(section.savedValue));
- updateTitle();
- updateLintReport(cm);
- }
- function getSectionForChild(e) {
- return e.closest("#sections > div");
- }
- function getSections() {
- return document.querySelectorAll("#sections > div");
- }
- // 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(event) {
- var 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) {
- 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);
- event.preventDefault();
- }
- });
- chrome.tabs.query({currentWindow: true}, function(tabs) {
- var windowId = tabs[0].windowId;
- if (prefs.get("openEditInWindow")) {
- if (sessionStorage.saveSizeOnClose && 'left' in prefs.get('windowPosition', {})) {
- // window was reopened via Ctrl-Shift-T etc.
- chrome.windows.update(windowId, prefs.get('windowPosition'));
- }
- if (tabs.length == 1 && window.history.length == 1) {
- chrome.windows.getAll(function(windows) {
- if (windows.length > 1) {
- sessionStorageHash("saveSizeOnClose").set(windowId, true);
- saveSizeOnClose = true;
- }
- });
- } else {
- saveSizeOnClose = sessionStorageHash("saveSizeOnClose").value[windowId];
- }
- }
- chrome.tabs.onRemoved.addListener(function(tabId, info) {
- sessionStorageHash("manageStylesHistory").unset(tabId);
- if (info.windowId == windowId && info.isWindowClosing) {
- sessionStorageHash("saveSizeOnClose").unset(windowId);
- }
- });
- });
- getActiveTab().then(tab => {
- useHistoryBack = sessionStorageHash("manageStylesHistory").value[tab.id] == location.href;
- });
- function goBackToManage(event) {
- if (useHistoryBack) {
- event.stopPropagation();
- event.preventDefault();
- history.back();
- }
- }
- window.onbeforeunload = function() {
- if (saveSizeOnClose) {
- prefs.set("windowPosition", {
- left: screenLeft,
- top: screenTop,
- width: outerWidth,
- height: outerHeight
- });
- }
- document.activeElement.blur();
- if (isCleanGlobal()) {
- return;
- }
- updateLintReport(null, 0);
- return confirm(t('styleChangesNotSaved'));
- };
- function addAppliesTo(list, name, value) {
- var showingEverything = list.querySelector(".applies-to-everything") != null;
- // blow away "Everything" if it's there
- if (showingEverything) {
- list.removeChild(list.firstChild);
- }
- var e;
- if (name && value) {
- e = template.appliesTo.cloneNode(true);
- e.querySelector("[name=applies-type]").value = name;
- e.querySelector("[name=applies-value]").value = value;
- e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false);
- } else if (showingEverything || list.hasChildNodes()) {
- e = template.appliesTo.cloneNode(true);
- if (list.hasChildNodes()) {
- e.querySelector("[name=applies-type]").value = list.querySelector("li:last-child [name='applies-type']").value;
- }
- e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false);
- } else {
- e = template.appliesToEverything.cloneNode(true);
- }
- e.querySelector(".add-applies-to").addEventListener("click", function() {addAppliesTo(this.parentNode.parentNode)}, false);
- list.appendChild(e);
- }
- function addSection(event, section) {
- var div = template.section.cloneNode(true);
- div.querySelector(".applies-to-help").addEventListener("click", showAppliesToHelp, false);
- div.querySelector(".remove-section").addEventListener("click", removeSection, false);
- div.querySelector(".add-section").addEventListener("click", addSection, false);
- div.querySelector(".beautify-section").addEventListener("click", beautify);
- var codeElement = div.querySelector(".code");
- var appliesTo = div.querySelector(".applies-to-list");
- var appliesToAdded = false;
- if (section) {
- codeElement.value = section.code;
- for (var i in propertyToCss) {
- if (section[i]) {
- section[i].forEach(function(url) {
- addAppliesTo(appliesTo, propertyToCss[i], url);
- appliesToAdded = true;
- });
- }
- }
- }
- if (!appliesToAdded) {
- addAppliesTo(appliesTo);
- }
- appliesTo.addEventListener("change", onChange);
- appliesTo.addEventListener("input", onChange);
- toggleTestRegExpVisibility();
- appliesTo.addEventListener('change', toggleTestRegExpVisibility);
- div.querySelector('.test-regexp').onclick = showRegExpTester;
- function toggleTestRegExpVisibility() {
- const show = [...appliesTo.children].some(item =>
- !item.matches('.applies-to-everything') &&
- 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') {
- showRegExpTester(null, div);
- }
- });
- }
- var sections = document.getElementById("sections");
- if (event) {
- var clickedSection = getSectionForChild(event.target);
- sections.insertBefore(div, clickedSection.nextElementSibling);
- var newIndex = getSections().indexOf(clickedSection) + 1;
- var cm = setupCodeMirror(codeElement, newIndex);
- makeSectionVisible(cm);
- cm.focus()
- renderLintReport();
- } else {
- sections.appendChild(div);
- var cm = setupCodeMirror(codeElement);
- }
- div.CodeMirror = cm;
- setCleanSection(div);
- return div;
- }
- function removeAppliesTo(event) {
- var appliesTo = event.target.parentNode,
- appliesToList = appliesTo.parentNode;
- removeAreaAndSetDirty(appliesTo);
- if (!appliesToList.hasChildNodes()) {
- addAppliesTo(appliesToList);
- }
- }
- function removeSection(event) {
- var section = getSectionForChild(event.target);
- var cm = section.CodeMirror;
- removeAreaAndSetDirty(section);
- editors.splice(editors.indexOf(cm), 1);
- renderLintReport();
- }
- function removeAreaAndSetDirty(area) {
- var contributors = area.querySelectorAll('.style-contributor');
- if(!contributors.length){
- setCleanItem(area, false);
- }
- contributors.some(function(node) {
- if (node.savedValue) {
- // it's a saved section, so make it dirty and stop the enumeration
- setCleanItem(area, false);
- return true;
- } else {
- // it's an empty section, so undirty the applies-to items,
- // otherwise orphaned ids would keep the style dirty
- setCleanItem(node, true);
- }
- });
- updateTitle();
- area.parentNode.removeChild(area);
- }
- function makeSectionVisible(cm) {
- var section = cm.getSection();
- var bounds = section.getBoundingClientRect();
- if ((bounds.bottom > window.innerHeight && bounds.top > 0) || (bounds.top < 0 && bounds.bottom < window.innerHeight)) {
- if (bounds.top < 0) {
- window.scrollBy(0, bounds.top - 1);
- } else {
- window.scrollBy(0, bounds.bottom - window.innerHeight + 1);
- }
- }
- }
- function setupGlobalSearch() {
- var originalCommand = {
- find: CodeMirror.commands.find,
- findNext: CodeMirror.commands.findNext,
- findPrev: CodeMirror.commands.findPrev,
- replace: CodeMirror.commands.replace
- };
- var originalOpenDialog = CodeMirror.prototype.openDialog;
- var originalOpenConfirm = CodeMirror.prototype.openConfirm;
- var 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();
- }
- function updateState(cm, newState) {
- if (!newState) {
- if (cm.state.search) {
- return cm.state.search;
- }
- if (!curState) {
- return null;
- }
- newState = curState;
- }
- cm.state.search = {
- query: newState.query,
- overlay: newState.overlay,
- annotate: cm.showMatchesOnScrollbar(newState.query, shouldIgnoreCase(newState.query))
- }
- cm.addOverlay(newState.overlay);
- return cm.state.search;
- }
- // temporarily overrides the original openDialog with the provided template's innerHTML
- function customizeOpenDialog(cm, template, callback) {
- cm.openDialog = function(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);
- refocusMinidialog(cm);
- }
- function focusClosestCM(activeCM) {
- editors.lastActive = activeCM;
- var cm = getEditorInSight();
- if (cm != activeCM) {
- cm.focus();
- }
- return cm;
- }
- function find(activeCM) {
- activeCM = focusClosestCM(activeCM);
- customizeOpenDialog(activeCM, template.find, function(query) {
- this(query);
- curState = activeCM.state.search;
- if (editors.length == 1 || !curState.query) {
- return;
- }
- editors.forEach(function(cm) {
- if (cm != activeCM) {
- cm.execCommand("clearSearch");
- updateState(cm, curState);
- }
- });
- if (CodeMirror.cmpPos(curState.posFrom, curState.posTo) == 0) {
- findNext(activeCM);
- }
- });
- originalCommand.find(activeCM);
- }
- function findNext(activeCM, reverse) {
- var state = updateState(activeCM);
- if (!state || !state.query) {
- find(activeCM);
- return;
- }
- var pos = activeCM.getCursor(reverse ? "from" : "to");
- activeCM.setSelection(activeCM.getCursor()); // clear the selection, don't move the cursor
- var rxQuery = typeof state.query == "object"
- ? state.query : stringAsRegExp(state.query, shouldIgnoreCase(state.query) ? "i" : "");
- if (document.activeElement && document.activeElement.name == "applies-value"
- && searchAppliesTo(activeCM)) {
- return;
- }
- for (var i=0, cm=activeCM; i < editors.length; i++) {
- state = updateState(cm);
- if (!cm.hasFocus()) {
- pos = reverse ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(0, 0);
- }
- var searchCursor = cm.getSearchCursor(state.query, pos, shouldIgnoreCase(state.query));
- if (searchCursor.find(reverse)) {
- if (editors.length > 1) {
- makeSectionVisible(cm);
- cm.focus();
- }
- // speedup the original findNext
- state.posFrom = reverse ? searchCursor.to() : searchCursor.from();
- state.posTo = CodeMirror.Pos(state.posFrom.line, state.posFrom.ch);
- originalCommand[reverse ? "findPrev" : "findNext"](cm);
- return;
- } else if (!reverse && searchAppliesTo(cm)) {
- return;
- }
- cm = editors[(editors.indexOf(cm) + (reverse ? -1 + editors.length : 1)) % editors.length];
- if (reverse && searchAppliesTo(cm)) {
- return;
- }
- }
- // nothing found so far, so call the original search with wrap-around
- originalCommand[reverse ? "findPrev" : "findNext"](activeCM);
- function searchAppliesTo(cm) {
- var inputs = [].slice.call(cm.getSection().querySelectorAll(".applies-value"));
- if (reverse) {
- inputs = inputs.reverse();
- }
- inputs.splice(0, inputs.indexOf(document.activeElement) + 1);
- return inputs.some(function(input) {
- var match = rxQuery.exec(input.value);
- if (match) {
- input.focus();
- var 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() {
- input.setSelectionRange(end, end);
- input.setSelectionRange(match.index, end)
- }, 0);
- return true;
- }
- });
- }
- }
- function findPrev(cm) {
- findNext(cm, true);
- }
- function replace(activeCM, all) {
- var queue, query, replacement;
- activeCM = focusClosestCM(activeCM);
- customizeOpenDialog(activeCM, template[all ? "replaceAll" : "replace"], function(txt) {
- query = txt;
- customizeOpenDialog(activeCM, template.replaceWith, function(txt) {
- replacement = txt;
- queue = editors.rotate(-editors.indexOf(activeCM));
- all ? editors.forEach(doReplace) : doReplace();
- });
- this(query);
- });
- originalCommand.replace(activeCM, all);
- function doReplace() {
- var cm = queue.shift();
- if (!cm) {
- if (!all) {
- editors.lastActive.focus();
- }
- return;
- }
- // hide the first two dialogs (replace, replaceWith)
- cm.openDialog = function(tmpl, callback, opt) {
- cm.openDialog = function(tmpl, callback, opt) {
- cm.openDialog = originalOpenDialog;
- if (all) {
- callback(replacement);
- } else {
- doConfirm(cm);
- callback(replacement);
- if (!cm.getWrapperElement().querySelector(".CodeMirror-dialog")) {
- // no dialog == nothing found in the current CM, move to the next
- doReplace();
- }
- }
- };
- callback(query);
- };
- originalCommand.replace(cm, all);
- }
- function doConfirm(cm) {
- var wrapAround = false;
- var origPos = cm.getCursor();
- cm.openConfirm = function overrideConfirm(tmpl, callbacks, opt) {
- var ovrCallbacks = callbacks.map(function(callback) {
- return function() {
- makeSectionVisible(cm);
- cm.openConfirm = overrideConfirm;
- setTimeout(function() { cm.openConfirm = originalOpenConfirm; }, 0);
- var pos = cm.getCursor();
- callback();
- var cmp = CodeMirror.cmpPos(cm.getCursor(), pos);
- wrapAround |= cmp <= 0;
- var 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);
- };
- }
- }
- function replaceAll(cm) {
- replace(cm, true);
- }
- CodeMirror.commands.find = find;
- CodeMirror.commands.findNext = findNext;
- CodeMirror.commands.findPrev = findPrev;
- CodeMirror.commands.replace = replace;
- CodeMirror.commands.replaceAll = replaceAll;
- }
- function jumpToLine(cm) {
- var cur = cm.getCursor();
- refocusMinidialog(cm);
- cm.openDialog(template.jumpToLine.innerHTML, function(str) {
- var m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/);
- if (m) {
- cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch);
- }
- }, {value: cur.line+1});
- }
- function refocusMinidialog(cm) {
- var section = cm.getSection();
- if (!section.querySelector(".CodeMirror-dialog")) {
- return;
- }
- // close the currently opened minidialog
- cm.focus();
- // make sure to focus the input in newly opened minidialog
- setTimeout(function() {
- section.querySelector(".CodeMirror-dialog").focus();
- }, 0);
- }
- function nextPrevEditor(cm, direction) {
- cm = editors[(editors.indexOf(cm) + direction + editors.length) % editors.length];
- makeSectionVisible(cm);
- cm.focus();
- }
- function getEditorInSight(nearbyElement) {
- // priority: 1. associated CM for applies-to element 2. last active if visible 3. first visible
- var cm;
- if (nearbyElement && nearbyElement.className.indexOf("applies-") >= 0) {
- cm = getSectionForChild(nearbyElement).CodeMirror;
- } else {
- cm = editors.lastActive;
- }
- if (!cm || offscreenDistance(cm) > 0) {
- var 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 });
- cm = sorted[0].cm;
- if (sorted[0].distance > 0) {
- makeSectionVisible(cm)
- }
- }
- return cm;
- function offscreenDistance(cm) {
- var LINES_VISIBLE = 2; // closest editor should have at least # lines visible
- var bounds = cm.getSection().getBoundingClientRect();
- if (bounds.top < 0) {
- return -bounds.top;
- } else if (bounds.top < window.innerHeight - cm.defaultTextHeight() * LINES_VISIBLE) {
- return 0;
- } else {
- return bounds.top - bounds.height;
- }
- }
- }
- function updateLintReport(cm, delay) {
- if (delay == 0) {
- // immediately show pending csslint messages in onbeforeunload and save
- update(cm);
- return;
- }
- if (delay > 0) {
- setTimeout(cm => { cm.performLint(); update(cm) }, delay, cm);
- return;
- }
- var state = cm.state.lint;
- if (!state) {
- return;
- }
- // user is editing right now: postpone updating the report for the new issues (default: 500ms lint + 4500ms)
- // 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;
- function update(cm) {
- var scope = cm ? [cm] : editors;
- var changed = false;
- var fixedOldIssues = false;
- scope.forEach(function(cm) {
- var state = cm.state.lint || {};
- var oldMarkers = state.markedLast || {};
- var newMarkers = {};
- var html = !state.marked || state.marked.length == 0 ? "" : "<tbody>" +
- state.marked.map(function(mark) {
- var info = mark.__annotation;
- var isActiveLine = info.from.line == cm.getCursor().line;
- var pos = isActiveLine ? "cursor" : (info.from.line + "," + info.from.ch);
- var message = escapeHtml(info.message.replace(/ at line \d.+$/, ""));
- if (message.length > 100) {
- message = message.substr(0, 100) + "...";
- }
- if (isActiveLine || oldMarkers[pos] == message) {
- delete oldMarkers[pos];
- }
- newMarkers[pos] = message;
- return "<tr class='" + info.severity + "'>" +
- "<td role='severity' class='CodeMirror-lint-marker-" + info.severity + "'>" +
- info.severity + "</td>" +
- "<td role='line'>" + (info.from.line+1) + "</td>" +
- "<td role='sep'>:</td>" +
- "<td role='col'>" + (info.from.ch+1) + "</td>" +
- "<td role='message'>" + message + "</td></tr>";
- }).join("") + "</tbody>";
- state.markedLast = newMarkers;
- fixedOldIssues |= state.reportDisplayed && Object.keys(oldMarkers).length > 0;
- if (state.html != html) {
- state.html = html;
- changed = true;
- }
- });
- if (changed) {
- clearTimeout(state ? state.renderTimeout : undefined);
- if (!state || !state.postponeNewIssues || fixedOldIssues) {
- renderLintReport(true);
- } else {
- state.renderTimeout = setTimeout(function() {
- renderLintReport(true);
- }, CodeMirror.defaults.lintReportDelay);
- }
- }
- }
- function escapeHtml(html) {
- var chars = {"&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/'};
- return html.replace(/[&<>"'\/]/g, function(char) { return chars[char] });
- }
- }
- function renderLintReport(someBlockChanged) {
- var container = document.getElementById("lint");
- var content = container.children[1];
- var label = t("sectionCode");
- var newContent = content.cloneNode(false);
- var issueCount = 0;
- editors.forEach(function(cm, index) {
- if (cm.state.lint && cm.state.lint.html) {
- var newBlock = newContent.appendChild(document.createElement("table"));
- var html = "<caption>" + label + " " + (index+1) + "</caption>" + cm.state.lint.html;
- newBlock.innerHTML = html;
- newBlock.cm = cm;
- issueCount += newBlock.rows.length;
- var block = content.children[newContent.children.length - 1];
- var blockChanged = !block || cm != block.cm || html != block.innerHTML;
- someBlockChanged |= blockChanged;
- cm.state.lint.reportDisplayed = blockChanged;
- }
- });
- 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";
- resizeLintReport(null, newContent);
- }
- }
- function resizeLintReport(event, content) {
- content = content || document.getElementById("lint").children[1];
- if (content.children.length) {
- var bounds = content.getBoundingClientRect();
- var newMaxHeight = bounds.bottom <= innerHeight ? '' : (innerHeight - bounds.top) + "px";
- if (newMaxHeight != content.style.maxHeight) {
- content.style.maxHeight = newMaxHeight;
- }
- }
- }
- function gotoLintIssue(event) {
- var issue = event.target.closest("tr");
- if (!issue) {
- return;
- }
- var block = issue.closest("table");
- makeSectionVisible(block.cm);
- block.cm.focus();
- block.cm.setSelection({
- line: parseInt(issue.querySelector("td[role='line']").textContent) - 1,
- ch: parseInt(issue.querySelector("td[role='col']").textContent) - 1
- });
- }
- function toggleLintReport() {
- document.getElementById("lint").classList.toggle("collapsed");
- }
- function beautify(event) {
- if (exports.css_beautify) { // thanks to csslint's definition of 'exports'
- doBeautify();
- } else {
- var script = document.head.appendChild(document.createElement("script"));
- script.src = "beautify/beautify-css-mod.js";
- script.onload = doBeautify;
- }
- function doBeautify() {
- var tabs = prefs.get("editor.indentWithTabs");
- var options = prefs.get("editor.beautify");
- options.indent_size = tabs ? 1 : prefs.get("editor.tabSize");
- options.indent_char = tabs ? "\t" : " ";
- var section = getSectionForChild(event.target);
- var scope = section ? [section.CodeMirror] : editors;
- showHelp(t("styleBeautify"), "<div class='beautify-options'>" +
- optionHtml(".selector1,", "selector_separator_newline") +
- optionHtml(".selector2,", "newline_before_open_brace") +
- optionHtml("{", "newline_after_open_brace") +
- optionHtml("border: none;", "newline_between_properties", true) +
- optionHtml("display: block;", "newline_before_close_brace", true) +
- optionHtml("}", "newline_between_rules") +
- "</div>" +
- "<div><button role='undo'></button></div>");
- var undoButton = document.querySelector("#help-popup button[role='undo']");
- undoButton.textContent = t(scope.length == 1 ? "undo" : "undoGlobal");
- undoButton.addEventListener("click", function() {
- var undoable = false;
- scope.forEach(function(cm) {
- if (cm.beautifyChange && cm.beautifyChange[cm.changeGeneration()]) {
- delete cm.beautifyChange[cm.changeGeneration()];
- cm.undo();
- cm.scrollIntoView(cm.getCursor());
- undoable |= cm.beautifyChange[cm.changeGeneration()];
- }
- });
- undoButton.disabled = !undoable;
- });
- scope.forEach(function(cm) {
- setTimeout(function() {
- const pos = options.translate_positions =
- [].concat.apply([], cm.doc.sel.ranges.map(r =>
- [Object.assign({}, r.anchor), Object.assign({}, r.head)]));
- var text = cm.getValue();
- var newText = exports.css_beautify(text, options);
- if (newText != text) {
- if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
- // clear the list if last change wasn't a css-beautify
- cm.beautifyChange = {};
- }
- cm.setValue(newText);
- const selections = [];
- for (let i = 0; i < pos.length; i += 2) {
- selections.push({anchor: pos[i], head: pos[i + 1]});
- }
- cm.setSelections(selections);
- cm.beautifyChange[cm.changeGeneration()] = true;
- undoButton.disabled = false;
- }
- }, 0);
- });
- document.querySelector(".beautify-options").addEventListener("change", function(event) {
- var value = event.target.selectedIndex > 0;
- options[event.target.dataset.option] = value;
- prefs.set("editor.beautify", options);
- event.target.parentNode.setAttribute("newline", value.toString());
- doBeautify();
- });
- function optionHtml(label, optionName, indent) {
- var value = options[optionName];
- return "<div newline='" + value.toString() + "'>" +
- "<span" + (indent ? " indent" : "") + ">" + label + "</span>" +
- "<select data-option='" + optionName + "'>" +
- "<option" + (value ? "" : " selected") + "> </option>" +
- "<option" + (value ? " selected" : "") + ">\\n</option>" +
- "</select></div>";
- }
- }
- }
- document.addEventListener("DOMContentLoaded", init);
- function init() {
- initCodeMirror();
- var params = getParams();
- if (!params.id) { // match should be 2 - one for the whole thing, one for the parentheses
- // This is an add
- tE("heading", "addStyleTitle");
- var section = {code: ""}
- for (var i in CssToProperty) {
- if (params[i]) {
- section[CssToProperty[i]] = [params[i]];
- }
- }
- window.onload = () => {
- window.onload = null;
- addSection(null, section);
- // default to enabled
- document.getElementById("enabled").checked = true
- initHooks();
- };
- return;
- }
- // This is an edit
- tE("heading", "editStyleHeading", null, false);
- getStylesSafe({id: params.id}).then(styles => {
- let style = styles[0];
- if (!style) {
- style = {id: null, sections: []};
- history.replaceState({}, document.title, location.pathname);
- }
- styleId = style.id;
- setStyleMeta(style);
- window.onload = () => {
- window.onload = null;
- initWithStyle({style});
- };
- if (document.readyState != 'loading') {
- window.onload();
- }
- });
- }
- function setStyleMeta(style) {
- document.getElementById("name").value = style.name;
- document.getElementById("enabled").checked = style.enabled;
- document.getElementById("url").href = style.url;
- }
- function initWithStyle({style, codeIsUpdated}) {
- setStyleMeta(style);
- if (codeIsUpdated === false) {
- setCleanGlobal();
- updateTitle();
- return;
- }
- // if this was done in response to an update, we need to clear existing sections
- getSections().forEach(function(div) { div.remove(); });
- var queue = style.sections.length ? style.sections.slice() : [{code: ""}];
- var queueStart = new Date().getTime();
- // after 100ms the sections will be added asynchronously
- while (new Date().getTime() - queueStart <= 100 && queue.length) {
- add();
- }
- (function processQueue() {
- if (queue.length) {
- add();
- setTimeout(processQueue, 0);
- }
- })();
- initHooks();
- function add() {
- var sectionDiv = addSection(null, queue.shift());
- maximizeCodeHeight(sectionDiv, !queue.length);
- const cm = sectionDiv.CodeMirror;
- setTimeout(() => {
- cm.setOption('lint', CodeMirror.defaults.lint);
- updateLintReport(cm, 0);
- }, prefs.get("editor.lintDelay"));
- }
- }
- function initHooks() {
- document.querySelectorAll("#header .style-contributor").forEach(function(node) {
- 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("from-mozilla").addEventListener("click", fromMozillaFormat);
- document.getElementById("beautify").addEventListener("click", beautify);
- document.getElementById("save-button").addEventListener("click", save, false);
- document.getElementById("sections-help").addEventListener("click", showSectionHelp, false);
- document.getElementById("keyMap-help").addEventListener("click", showKeyMapHelp, false);
- document.getElementById("cancel-button").addEventListener("click", goBackToManage);
- document.getElementById("lint-help").addEventListener("click", showLintHelp);
- document.getElementById("lint").addEventListener("click", gotoLintIssue);
- window.addEventListener("resize", resizeLintReport);
- // touch devices don't have onHover events so the element we'll be toggled via clicking (touching)
- if ("ontouchstart" in document.body) {
- document.querySelector("#lint h2").addEventListener("click", toggleLintReport);
- }
- document.querySelectorAll(
- 'input:not([type]), input[type="text"], input[type="search"], input[type="number"]')
- .forEach(e => e.addEventListener('mousedown', toggleContextMenuDelete));
- setupGlobalSearch();
- setCleanGlobal();
- updateTitle();
- }
- function toggleContextMenuDelete(event) {
- if (event.button == 2 && prefs.get('editor.contextDelete')) {
- chrome.contextMenus.update('editor.contextDelete', {
- enabled: Boolean(
- this.selectionStart != this.selectionEnd ||
- this.somethingSelected && this.somethingSelected()
- ),
- }, ignoreChromeError);
- }
- }
- function maximizeCodeHeight(sectionDiv, isLast) {
- var cm = sectionDiv.CodeMirror;
- var stats = maximizeCodeHeight.stats = maximizeCodeHeight.stats || {totalHeight: 0, deltas: []};
- if (!stats.cmActualHeight) {
- stats.cmActualHeight = getComputedHeight(cm.display.wrapper);
- }
- if (!stats.sectionMarginTop) {
- stats.sectionMarginTop = parseFloat(getComputedStyle(sectionDiv).marginTop);
- }
- var sectionTop = sectionDiv.getBoundingClientRect().top - stats.sectionMarginTop;
- if (!stats.firstSectionTop) {
- stats.firstSectionTop = sectionTop;
- }
- var extrasHeight = getComputedHeight(sectionDiv) - stats.cmActualHeight;
- var cmMaxHeight = window.innerHeight - extrasHeight - sectionTop - stats.sectionMarginTop;
- var cmDesiredHeight = cm.display.sizer.clientHeight + 2*cm.defaultTextHeight();
- var cmGrantableHeight = Math.max(stats.cmActualHeight, Math.min(cmMaxHeight, cmDesiredHeight));
- stats.deltas.push(cmGrantableHeight - stats.cmActualHeight);
- stats.totalHeight += cmGrantableHeight + extrasHeight;
- if (!isLast) {
- return;
- }
- stats.totalHeight += stats.firstSectionTop;
- if (stats.totalHeight <= window.innerHeight) {
- editors.forEach(function(cm, index) {
- cm.setSize(null, stats.deltas[index] + stats.cmActualHeight);
- });
- return;
- }
- // scale heights to fill the gap between last section and bottom edge of the window
- var sections = document.getElementById("sections");
- var available = window.innerHeight - sections.getBoundingClientRect().bottom -
- parseFloat(getComputedStyle(sections).marginBottom);
- if (available <= 0) {
- return;
- }
- var totalDelta = stats.deltas.reduce(function(sum, d) { return sum + d; }, 0);
- var q = available / totalDelta;
- var baseHeight = stats.cmActualHeight - stats.sectionMarginTop;
- stats.deltas.forEach(function(delta, index) {
- editors[index].setSize(null, baseHeight + Math.floor(q * delta));
- });
- }
- function updateTitle() {
- var DIRTY_TITLE = "* $";
- var name = document.getElementById("name").savedValue;
- var clean = isCleanGlobal();
- var title = styleId === null ? t("addStyleTitle") : t('editStyleTitle', [name]);
- document.title = clean ? title : DIRTY_TITLE.replace("$", title);
- }
- function validate() {
- var name = document.getElementById("name").value;
- if (name == "") {
- return t("styleMissingName");
- }
- // validate the regexps
- if (document.querySelectorAll(".applies-to-list").some(function(list) {
- return list.childNodes.some(function(li) {
- if (li.className == template.appliesToEverything.className) {
- return false;
- }
- var valueElement = li.querySelector("[name=applies-value]");
- var type = li.querySelector("[name=applies-type]").value;
- var value = valueElement.value;
- if (type && value) {
- if (type == "regexp") {
- try {
- new RegExp(value);
- } catch (ex) {
- valueElement.focus();
- return true;
- }
- }
- }
- return false;
- });
- })) {
- return t("styleBadRegexp");
- }
- return null;
- }
- function save() {
- updateLintReport(null, 0);
- // save the contents of the CodeMirror editors back into the textareas
- for (var i=0; i < editors.length; i++) {
- editors[i].save();
- }
- var error = validate();
- if (error) {
- alert(error);
- return;
- }
- var name = document.getElementById("name").value;
- var enabled = document.getElementById("enabled").checked;
- saveStyleSafe({
- id: styleId,
- name: name,
- enabled: enabled,
- reason: 'editSave',
- sections: getSectionsHashes()
- })
- .then(saveComplete);
- }
- function getSectionsHashes() {
- var sections = [];
- getSections().forEach(function(div) {
- var meta = getMeta(div);
- var code = div.CodeMirror.getValue();
- if (/^\s*$/.test(code) && Object.keys(meta).length == 0) {
- return;
- }
- meta.code = code;
- sections.push(meta);
- });
- return sections;
- }
- function getMeta(e) {
- var meta = {urls: [], urlPrefixes: [], domains: [], regexps: []};
- e.querySelector(".applies-to-list").childNodes.forEach(function(li) {
- if (li.className == template.appliesToEverything.className) {
- return;
- }
- var type = li.querySelector("[name=applies-type]").value;
- var value = li.querySelector("[name=applies-value]").value;
- if (type && value) {
- var property = CssToProperty[type];
- meta[property].push(value);
- }
- });
- return meta;
- }
- function saveComplete(style) {
- styleId = style.id;
- setCleanGlobal();
- // Go from new style URL to edit style URL
- if (location.href.indexOf("id=") == -1) {
- history.replaceState({}, document.title, "edit.html?id=" + style.id);
- tE("heading", "editStyleHeading", null, false);
- }
- updateTitle();
- }
- function showMozillaFormat() {
- var popup = showCodeMirrorPopup(t("styleToMozillaFormatTitle"), "", {readOnly: true});
- popup.codebox.setValue(toMozillaFormat());
- popup.codebox.execCommand("selectAll");
- }
- function toMozillaFormat() {
- return getSectionsHashes().map(function(section) {
- var cssMds = [];
- for (var i in propertyToCss) {
- if (section[i]) {
- cssMds = cssMds.concat(section[i].map(function (v){
- return propertyToCss[i] + "(\"" + v.replace(/\\/g, "\\\\") + "\")";
- }));
- }
- }
- return cssMds.length ? "@-moz-document " + cssMds.join(", ") + " {\n" + section.code + "\n}" : section.code;
- }).join("\n\n");
- }
- function fromMozillaFormat() {
- var popup = showCodeMirrorPopup(t("styleFromMozillaFormatPrompt"), tHTML("<div>\
- <button name='import-append' i18n-text='importAppendLabel' i18n-title='importAppendTooltip'></button>\
- <button name='import-replace' i18n-text='importReplaceLabel' i18n-title='importReplaceTooltip'></button>\
- </div>").innerHTML);
- var contents = popup.querySelector(".contents");
- contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
- popup.codebox.focus();
- popup.querySelector("[name='import-append']").addEventListener("click", doImport);
- popup.querySelector("[name='import-replace']").addEventListener("click", doImport);
- popup.codebox.on("change", function() {
- clearTimeout(popup.mozillaTimeout);
- popup.mozillaTimeout = setTimeout(function() {
- popup.classList.toggle("ready", trimNewLines(popup.codebox.getValue()));
- }, 100);
- });
- function doImport() {
- var replaceOldStyle = this.name == "import-replace";
- popup.querySelector(".dismiss").onclick();
- var mozStyle = trimNewLines(popup.codebox.getValue());
- var parser = new parserlib.css.Parser(), lines = mozStyle.split("\n");
- var sectionStack = [{code: "", start: {line: 1, col: 1}}];
- var errors = "", oldSectionCount = editors.length;
- var firstAddedCM;
- parser.addListener("startdocument", function(e) {
- var outerText = getRange(sectionStack.last.start, (--e.col, e));
- var gapComment = outerText.match(/(\/\*[\s\S]*?\*\/)[\s\n]*$/);
- var section = {code: "", start: backtrackTo(this, parserlib.css.Tokens.LBRACE, "end")};
- // move last comment before @-moz-document inside the section
- if (gapComment && !gapComment[1].match(/\/\*\s*AGENT_SHEET\s*\*\//)) {
- section.code = gapComment[1] + "\n";
- outerText = trimNewLines(outerText.substring(0, gapComment.index));
- }
- if (outerText.trim()) {
- sectionStack.last.code = outerText;
- doAddSection(sectionStack.last);
- sectionStack.last.code = "";
- }
- e.functions.forEach(function(f) {
- var m = f.match(/^(url|url-prefix|domain|regexp)\((['"]?)(.+?)\2?\)$/);
- var aType = CssToProperty[m[1]];
- var aValue = aType != "regexps" ? m[3] : m[3].replace(/\\\\/g, "\\");
- (section[aType] = section[aType] || []).push(aValue);
- });
- sectionStack.push(section);
- });
- parser.addListener("enddocument", function(e) {
- var end = backtrackTo(this, parserlib.css.Tokens.RBRACE, "start");
- var section = sectionStack.pop();
- section.code += getRange(section.start, end);
- sectionStack.last.start = (++end.col, end);
- doAddSection(section);
- });
- parser.addListener("endstylesheet", function() {
- // add nonclosed outer sections (either broken or the last global one)
- var 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) {
- maximizeCodeHeight(cm.getSection(), cm == editors.last);
- });
- makeSectionVisible(firstAddedCM);
- firstAddedCM.focus();
- if (errors) {
- showHelp(t("issues"), errors);
- }
- });
- parser.addListener("error", function(e) {
- errors += e.line + ":" + e.col + " " + e.message.replace(/ at line \d.+$/, "") + "<br>";
- });
- parser.parse(mozStyle);
- function getRange( start, end) {
- const L1 = start.line - 1, C1 = start.col - 1;
- const L2 = end.line - 1, C2 = end.col - 1;
- if (L1 == L2) {
- return lines[L1].substr(C1, C2 - C1 + 1);
- } else {
- const middle = lines.slice(L1 + 1, L2).join('\n');
- return lines[L1].substr(C1) + '\n' + middle +
- (L2 >= lines.length ? '' : ((middle ? '\n' : '') + lines[L2].substring(0, C2)));
- }
- }
- function doAddSection(section) {
- section.code = section.code.trim();
- // don't add empty sections
- if (!section.code
- && !section.urls
- && !section.urlPrefixes
- && !section.domains
- && !section.regexps) {
- return;
- }
- if (!firstAddedCM) {
- if (!initFirstSection(section)) {
- return;
- }
- }
- setCleanItem(addSection(null, section), false);
- firstAddedCM = firstAddedCM || editors.last;
- }
- // do onetime housekeeping as the imported text is confirmed to be a valid style
- function initFirstSection(section) {
- // skip adding the first global section when there's no code/comments
- if (!section.code.replace("@namespace url(http://www.w3.org/1999/xhtml);", "") /* ignore boilerplate NS */
- .replace(/[\s\n]/g, "")) { /* ignore all whitespace including new lines */
- return false;
- }
- if (replaceOldStyle) {
- editors.slice(0).reverse().forEach(function(cm) {
- removeSection({target: cm.getSection().firstElementChild});
- });
- } else if (!editors.last.getValue()) {
- // nuke the last blank section
- if (editors.last.getSection().querySelector(".applies-to-everything")) {
- removeSection({target: editors.last.getSection()});
- }
- }
- return true;
- }
- }
- function backtrackTo(parser, tokenType, startEnd) {
- var tokens = parser._tokenStream._lt;
- for (var i = tokens.length - 2; i >= 0; --i) {
- if (tokens[i].type == tokenType) {
- return {line: tokens[i][startEnd+"Line"], col: tokens[i][startEnd+"Col"]};
- }
- }
- }
- function trimNewLines(s) {
- return s.replace(/^[\s\n]+/, "").replace(/[\s\n]+$/, "");
- }
- }
- function showSectionHelp() {
- showHelp(t("styleSectionsTitle"), t("sectionHelp"));
- }
- function showAppliesToHelp() {
- showHelp(t("appliesLabel"), t("appliesHelp"));
- }
- function showToMozillaHelp() {
- showHelp(t("styleMozillaFormatHeading"), t("styleToMozillaFormatHelp"));
- }
- function showKeyMapHelp() {
- var keyMap = mergeKeyMaps({}, prefs.get("editor.keyMap"), CodeMirror.defaults.extraKeys);
- var keyMapSorted = Object.keys(keyMap)
- .map(function(key) { return {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 });
- showHelp(t("cm_keyMap") + ": " + prefs.get("editor.keyMap"),
- '<table class="keymap-list">' +
- '<thead><tr><th><input placeholder="' + t("helpKeyMapHotkey") + '" type="search"></th>' +
- '<th><input placeholder="' + t("helpKeyMapCommand") + '" type="search"></th></tr></thead>' +
- "<tbody>" + keyMapSorted.map(function(value) {
- return "<tr><td>" + value.key + "</td><td>" + value.cmd + "</td></tr>";
- }).join("") +
- "</tbody>" +
- "</table>");
- var table = document.querySelector("#help-popup table");
- table.addEventListener("input", filterTable);
- var inputs = table.querySelectorAll("input");
- inputs[0].addEventListener("keydown", hotkeyHandler);
- inputs[1].focus();
- function hotkeyHandler(event) {
- var keyName = CodeMirror.keyName(event);
- if (keyName == "Esc" || keyName == "Tab" || keyName == "Shift-Tab") {
- return;
- }
- event.preventDefault();
- event.stopPropagation();
- // normalize order of modifiers,
- // for modifier-only keys ("Ctrl-Shift") a dummy main key has to be temporarily added
- var keyMap = {};
- keyMap[keyName.replace(/(Shift|Ctrl|Alt|Cmd)$/, "$&-dummy")] = "";
- var normalizedKey = Object.keys(CodeMirror.normalizeKeyMap(keyMap))[0];
- this.value = normalizedKey.replace("-dummy", "");
- filterTable(event);
- }
- function filterTable(event) {
- var input = event.target;
- var query = stringAsRegExp(input.value, "gi");
- var col = input.parentNode.cellIndex;
- inputs[1 - col].value = "";
- table.tBodies[0].childNodes.forEach(function(row) {
- var cell = row.children[col];
- cell.innerHTML = cell.textContent.replace(query, "<mark>$&</mark>");
- row.style.display = query.test(cell.textContent) ? "" : "none";
- // clear highlight from the other column
- cell = row.children[1 - col];
- cell.innerHTML = cell.textContent;
- });
- }
- function mergeKeyMaps(merged, ...more) {
- more.forEach(keyMap => {
- if (typeof keyMap == "string") {
- keyMap = CodeMirror.keyMap[keyMap];
- }
- Object.keys(keyMap).forEach(function(key) {
- var 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") {
- // 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");
- merged[key] = cmd.length <= 200 ? cmd : cmd.substr(0, 200) + "...";
- } else {
- merged[key] = cmd;
- }
- }
- });
- if (keyMap.fallthrough) {
- merged = mergeKeyMaps(merged, keyMap.fallthrough);
- }
- });
- return merged;
- }
- }
- function showLintHelp() {
- showHelp(t("issues"), t("issuesHelp") + "<ul>" +
- CSSLint.getRules().map(function(rule) {
- return "<li><b>" + rule.name + "</b><br>" + rule.desc + "</li>";
- }).join("") + "</ul>"
- );
- }
- function showRegExpTester(event, section = getSectionForChild(this)) {
- const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
- const OWN_ICON = chrome.runtime.getManifest().icons['16'];
- const cachedRegexps = showRegExpTester.cachedRegexps =
- showRegExpTester.cachedRegexps || new Map();
- const regexps = [...section.querySelector('.applies-to-list').children]
- .map(item =>
- !item.matches('.applies-to-everything') &&
- item.querySelector('.applies-type').value == 'regexp' &&
- item.querySelector('.applies-value').value.trim())
- .filter(item => item)
- .map(text => {
- const rxData = Object.assign({text}, cachedRegexps.get(text));
- if (!rxData.urls) {
- cachedRegexps.set(text, Object.assign(rxData, {
- rx: tryRegExp(text),
- urls: new Map(),
- }));
- }
- return rxData;
- });
- chrome.tabs.onUpdated.addListener(function _(tabId, info) {
- if (document.querySelector('.regexp-report')) {
- if (info.url) {
- showRegExpTester(event, section);
- }
- } else {
- chrome.tabs.onUpdated.removeListener(_);
- }
- });
- chrome.tabs.query({}, tabs => {
- const supported = tabs.map(tab => tab.url)
- .filter(url => URLS.supported.test(url));
- const unique = [...new Set(supported).values()];
- for (const rxData of regexps) {
- const {rx, urls} = rxData;
- if (rx) {
- const urlsNow = new Map();
- for (const url of unique) {
- const match = urls.get(url) || (url.match(rx) || [])[0];
- if (match) {
- urlsNow.set(url, match);
- }
- }
- rxData.urls = urlsNow;
- }
- }
- const moreInfoLink = template.regexpTestPartial.outerHTML;
- const stats = {
- full: {data: [], label: t('styleRegexpTestFull')},
- partial: {data: [], label: t('styleRegexpTestPartial') + moreInfoLink},
- none: {data: [], label: t('styleRegexpTestNone')},
- invalid: {data: [], label: t('styleRegexpTestInvalid')},
- };
- for (const {text, rx, urls} of regexps) {
- if (!rx) {
- stats.invalid.data.push({text});
- continue;
- }
- if (!urls.size) {
- stats.none.data.push({text});
- continue;
- }
- const full = [];
- const partial = [];
- for (const [url, match] of urls.entries()) {
- const faviconUrl = url.startsWith(URLS.ownOrigin)
- ? OWN_ICON
- : GET_FAVICON_URL + new URL(url).hostname;
- const icon = `<img src="${faviconUrl}">`;
- if (match.length == url.length) {
- full.push(`<div>${icon + url}</div>`);
- } else {
- partial.push(`<div>${icon}<mark>${match}</mark>` +
- url.substr(match.length) + '</div>');
- }
- }
- if (full.length) {
- stats.full.data.push({text, urls: full});
- }
- if (partial.length) {
- stats.partial.data.push({text, urls: partial});
- }
- }
- showHelp(t('styleRegexpTestTitle'),
- '<div class="regexp-report">' +
- Object.keys(stats).map(type => (!stats[type].data.length ? '' :
- `<details open data-type="${type}">
- <summary>${stats[type].label}</summary>` +
- stats[type].data.map(({text, urls}) => (!urls ? text :
- `<details open><summary>${text}</summary>${urls.join('')}</details>`
- )).join('<br>') +
- '</details>'
- )).join('') +
- '</div>');
- document.querySelector('.regexp-report').onclick = event => {
- const target = event.target.closest('a, .regexp-report div');
- if (target) {
- openURL({url: target.href || target.textContent});
- event.preventDefault();
- }
- };
- });
- }
- function showHelp(title, text) {
- var div = document.getElementById("help-popup");
- div.classList.remove("big");
- div.querySelector(".contents").innerHTML = text;
- div.querySelector(".title").innerHTML = title;
- if (getComputedStyle(div).display == "none") {
- document.addEventListener("keydown", closeHelp);
- div.querySelector(".dismiss").onclick = closeHelp; // avoid chaining on multiple showHelp() calls
- }
- div.style.display = "block";
- return div;
- function closeHelp(e) {
- if (!e
- || e.type == "click"
- || ((e.keyCode || e.which) == 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey)) {
- div.style.display = "";
- document.querySelector(".contents").innerHTML = "";
- document.removeEventListener("keydown", closeHelp);
- }
- }
- }
- function showCodeMirrorPopup(title, html, options) {
- var popup = showHelp(title, html);
- popup.classList.add("big");
- popup.codebox = CodeMirror(popup.querySelector(".contents"), Object.assign({
- mode: "css",
- lineNumbers: true,
- lineWrapping: true,
- foldGutter: true,
- gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
- matchBrackets: true,
- lint: {getAnnotations: CodeMirror.lint.css, delay: 0},
- styleActiveLine: true,
- theme: prefs.get("editor.theme"),
- 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) });
- return popup;
- }
- function getParams() {
- var params = {};
- var urlParts = location.href.split("?", 2);
- if (urlParts.length == 1) {
- return params;
- }
- urlParts[1].split("&").forEach(function(keyValue) {
- var splitKeyValue = keyValue.split("=", 2);
- params[decodeURIComponent(splitKeyValue[0])] = decodeURIComponent(splitKeyValue[1]);
- });
- return params;
- }
- chrome.runtime.onMessage.addListener(onRuntimeMessage);
- function onRuntimeMessage(request) {
- switch (request.method) {
- case "styleUpdated":
- 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(() => {
- request.style = BG.cachedStyles.byId.get(request.style.id);
- initWithStyle(request);
- });
- } else {
- initWithStyle(request);
- }
- }
- break;
- case "styleDeleted":
- if (styleId && styleId == request.id) {
- window.onbeforeunload = function() {};
- window.close();
- break;
- }
- break;
- case "prefChanged":
- if ('editor.smartIndent' in request.prefs) {
- CodeMirror.setOption('smartIndent', request.prefs['editor.smartIndent']);
- }
- break;
- case 'editDeleteText':
- document.execCommand('delete');
- break;
- }
- }
- function getComputedHeight(el) {
- var compStyle = getComputedStyle(el);
- return el.getBoundingClientRect().height +
- parseFloat(compStyle.marginTop) + parseFloat(compStyle.marginBottom);
- }
- function getCodeMirrorThemes() {
- if (!chrome.runtime.getPackageDirectoryEntry) {
- const themes = Promise.resolve([
- chrome.i18n.getMessage('defaultTheme'),
- '3024-day',
- '3024-night',
- 'abcdef',
- 'ambiance',
- 'ambiance-mobile',
- 'base16-dark',
- 'base16-light',
- 'bespin',
- 'blackboard',
- 'cobalt',
- 'colorforth',
- 'dracula',
- 'duotone-dark',
- 'duotone-light',
- 'eclipse',
- 'elegant',
- 'erlang-dark',
- 'hopscotch',
- 'icecoder',
- 'isotope',
- 'lesser-dark',
- 'liquibyte',
- 'material',
- 'mbo',
- 'mdn-like',
- 'midnight',
- 'monokai',
- 'neat',
- 'neo',
- 'night',
- 'panda-syntax',
- 'paraiso-dark',
- 'paraiso-light',
- 'pastel-on-dark',
- 'railscasts',
- 'rubyblue',
- 'seti',
- 'solarized',
- 'the-matrix',
- 'tomorrow-night-bright',
- 'tomorrow-night-eighties',
- 'ttcn',
- 'twilight',
- 'vibrant-ink',
- 'xq-dark',
- 'xq-light',
- 'yeti',
- 'zenburn',
- ]);
- localStorage.codeMirrorThemes = themes.join(' ');
- }
- return new Promise(resolve => {
- chrome.runtime.getPackageDirectoryEntry(rootDir => {
- rootDir.getDirectory('codemirror/theme', {create: false}, themeDir => {
- themeDir.createReader().readEntries(entries => {
- const themes = [
- chrome.i18n.getMessage('defaultTheme')
- ].concat(
- entries.filter(entry => entry.isFile)
- .sort((a, b) => (a.name < b.name ? -1 : 1))
- .map(entry => entry.name.replace(/\.css$/, ''))
- );
- localStorage.codeMirrorThemes = themes.join(' ');
- resolve(themes);
- });
- });
- });
- });
- }
|