diff --git a/background/background.js b/background/background.js index 7bf69067..7e242e2e 100644 --- a/background/background.js +++ b/background/background.js @@ -15,6 +15,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, { deleteStyle: styleManager.deleteStyle, getStylesInfoByUrl: styleManager.getStylesInfoByUrl, installStyle: styleManager.installStyle, + editSave: styleManager.editSave, getStyleFromDB: id => db.exec('get', id).then(event => event.target.result), @@ -96,7 +97,10 @@ navigatorUtil.onUrlChange(({tabId, frameId}, type) => { // styles would be updated when content script is injected. return; } - msg.sendTab(tabId, {method: 'urlChanged'}, {frameId}); + msg.sendTab(tabId, {method: 'urlChanged'}, {frameId}) + .catch(err => { + console.warn(tabId, frameId, err); + }); }); if (FIREFOX) { diff --git a/background/search-db.js b/background/search-db.js index 9d5bece6..d6aef03d 100644 --- a/background/search-db.js +++ b/background/search-db.js @@ -1,4 +1,4 @@ -/* global API_METHODS filterStyles cachedStyles */ +/* global API_METHODS styleManager */ 'use strict'; (() => { @@ -25,7 +25,8 @@ if (/^url:/i.test(query)) { matchUrl = query.slice(query.indexOf(':') + 1).trim(); if (matchUrl) { - return filterStyles({matchUrl}).map(style => style.id); + return styleManager.getStylesInfoByUrl(matchUrl) + .then(styles => styles.map(style => style.id)); } } if (query.startsWith('/') && /^\/(.+?)\/([gimsuy]*)$/.test(query)) { @@ -44,25 +45,32 @@ } const results = []; - for (const item of ids || cachedStyles.list) { - const id = isNaN(item) ? item.id : item; - if (!query || words && !words.length) { - results.push(id); - continue; + return styleManager.getAllStyles(styles => { + if (ids) { + const idSet = new Set(ids); + return styles.filter(s => idSet.has(s.id)); } - const style = isNaN(item) ? item : cachedStyles.byId.get(item); - if (!style) continue; - for (const part in PARTS) { - const text = style[part]; - if (text && PARTS[part](text, rx, words, icase)) { - results.push(id); - break; + return styles; + }) + .then(styles => { + for (const style of styles) { + const id = style.id; + if (!query || words && !words.length) { + results.push(id); + continue; + } + for (const part in PARTS) { + const text = style[part]; + if (text && PARTS[part](text, rx, words, icase)) { + results.push(id); + break; + } + } } - } - } - if (cache.size) debounce(clearCache, 60e3); - return results; + if (cache.size) debounce(clearCache, 60e3); + return results; + }); }; function searchText(text, rx, words, icase) { diff --git a/edit.html b/edit.html index e40afd44..b1908c79 100644 --- a/edit.html +++ b/edit.html @@ -460,11 +460,15 @@
-

+ +

diff --git a/edit/colorpicker-helper.js b/edit/colorpicker-helper.js index 3d1bde61..0c28939e 100644 --- a/edit/colorpicker-helper.js +++ b/edit/colorpicker-helper.js @@ -1,7 +1,7 @@ /* global CodeMirror loadScript showHelp cmFactory */ 'use strict'; -onDOMscriptReady('/colorview.js').then(() => { +(() => { onDOMready().then(() => { $('#colorpicker-settings').onclick = configureColorpicker; }); @@ -112,4 +112,4 @@ onDOMscriptReady('/colorview.js').then(() => { } input.focus(); } -}); +})(); diff --git a/edit/edit.css b/edit/edit.css index 38eee093..cf18f632 100644 --- a/edit/edit.css +++ b/edit/edit.css @@ -358,7 +358,8 @@ input:invalid { } .section.removed .code-label, .section.removed .applies-to, -.section.removed .edit-actions { +.section.removed .edit-actions, +.section.removed .CodeMirror { display: none; } .move-section-up:after { diff --git a/edit/edit.js b/edit/edit.js index 14780a9e..6744edb2 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -23,6 +23,7 @@ const CssToProperty = Object.entries(propertyToCss) let editor; document.addEventListener('visibilitychange', beforeUnload); +window.addEventListener('beforeunload', beforeUnload); msg.onExtension(onRuntimeMessage); preinit(); @@ -375,7 +376,7 @@ function onRuntimeMessage(request) { * > Never add a beforeunload listener unconditionally or use it as an end-of-session signal. * > Only add it when a user has unsaved work, and remove it as soon as that work has been saved. */ -function beforeUnload() { +function beforeUnload(e) { if (saveSizeOnClose) rememberWindowSize(); const activeElement = document.activeElement; if (activeElement) { @@ -384,9 +385,13 @@ function beforeUnload() { // refocus if unloading was canceled setTimeout(() => activeElement.focus()); } - if (editor.isDirty()) { + if (editor && editor.isDirty()) { // neither confirm() nor custom messages work in modern browsers but just in case - return t('styleChangesNotSaved'); + if (e) { + e.returnValue = t('styleChangesNotSaved'); + } else { + return t('styleChangesNotSaved'); + } } } diff --git a/edit/live-preview.js b/edit/live-preview.js index 778ca9f7..81ab76c5 100644 --- a/edit/live-preview.js +++ b/edit/live-preview.js @@ -10,7 +10,7 @@ function createLivePreview(preprocess) { prefs.subscribe(['editor.livePreview'], (key, value) => { if (value && data && data.id && data.enabled) { - previewer = createPreviewer; + previewer = createPreviewer(); previewer.update(data); } if (!value && previewer) { diff --git a/edit/sections-editor.js b/edit/sections-editor.js index 8fb4cadc..6cb737d0 100644 --- a/edit/sections-editor.js +++ b/edit/sections-editor.js @@ -1,9 +1,9 @@ /* global dirtyReporter showToMozillaHelp showSectionHelp toggleContextMenuDelete setGlobalProgress maximizeCodeHeight CodeMirror nextPrevEditorOnKeydown showAppliesToHelp propertyToCss - regExpTester linter cssToProperty createLivePreview showCodeMirrorPopup + regExpTester linter createLivePreview showCodeMirrorPopup sectionsToMozFormat editorWorker messageBox clipString beautify - rerouteHotkeys cmFactory + rerouteHotkeys cmFactory CssToProperty */ 'use strict'; @@ -76,7 +76,7 @@ function createResizeGrip(cm) { function createSectionsEditor(style) { let INC_ID = 0; // an increment id that is used by various object to track the order const dirty = dirtyReporter(); - dirty.onChange(() => updateTitle); + dirty.onChange(updateTitle); const container = $('#sections'); const sections = []; @@ -91,6 +91,7 @@ function createSectionsEditor(style) { enabledEl.addEventListener('change', () => { dirty.modify('enabled', style.enabled, enabledEl.checked); style.enabled = enabledEl.checked; + updateLivePreview(); }); $('#to-mozilla').addEventListener('click', showMozillaFormat); @@ -120,14 +121,13 @@ function createSectionsEditor(style) { dirty.clear(); rerouteHotkeys(true); resolve(); + updateHeader(); } })); const livePreview = createLivePreview(); livePreview.show(Boolean(style.id)); - updateHeader(); - return { ready: () => initializing, replaceStyle, @@ -137,7 +137,7 @@ function createSectionsEditor(style) { scrollToEditor, getStyleId: () => style.id, getEditorTitle: cm => { - const index = sections.filter(s => !s.isRemoved()).findIndex(s => s.cm === cm) + 1; + const index = sections.filter(s => !s.isRemoved()).findIndex(s => s.cm === cm); return `${t('sectionCode')} ${index + 1}`; }, save: saveStyle, @@ -162,6 +162,7 @@ function createSectionsEditor(style) { nearbyElement instanceof Node && (nearbyElement.closest('#sections > .section') || {}).CodeMirror || getLastActivatedEditor(); + console.log(cm); if (nearbyElement instanceof Node && cm) { const {left, top} = nearbyElement.getBoundingClientRect(); const bounds = cm.display.wrapper.getBoundingClientRect(); @@ -281,8 +282,9 @@ function createSectionsEditor(style) { if (section.isRemoved()) { continue; } - if (!result || section.getLastActive() > result.getLastActive) { - result = section; + // .lastActive is initiated by codemirror-factory + if (!result || section.cm.lastActive > result.lastActive) { + result = section.cm; } } return result; @@ -431,11 +433,12 @@ function createSectionsEditor(style) { sectionOrder = validSections.map(s => s.id).join(','); dirty.modify('sectionOrder', oldOrder, sectionOrder); container.dataset.sectionCount = validSections.length; + linter.refreshReport(); } function getModel() { return Object.assign({}, style, { - sections: sections.map(s => s.getModel()) + sections: sections.filter(s => !s.isRemoved()).map(s => s.getModel()) }); } @@ -557,12 +560,13 @@ function createSectionsEditor(style) { init = {code: '', urlPrefixes: ['http://example.com']}; } const section = createSection(init); - container.appendChild(section.el); if (base) { const index = sections.indexOf(base); - sections.splice(index, 0, section); + sections.splice(index + 1, 0, section); + container.insertBefore(section.el, base.el.nextSibling); } else { sections.push(section); + container.appendChild(section.el); } section.render(); // maximizeCodeHeight(section.el); @@ -623,7 +627,6 @@ function createSectionsEditor(style) { createResizeGrip(cm); linter.enableForEditor(cm); - linter.refreshReport(); let lastActive = 0; @@ -640,6 +643,7 @@ function createSectionsEditor(style) { onChange, off, getLastActive: () => lastActive, + appliesTo }; return section; @@ -665,7 +669,7 @@ function createSectionsEditor(style) { if (apply.all) { continue; } - const key = cssToProperty(apply.getType()); + const key = CssToProperty[apply.getType()]; if (!section[key]) { section[key] = []; } @@ -767,7 +771,7 @@ function createSectionsEditor(style) { const apply = createApply(init); if (base) { const index = appliesTo.indexOf(base); - appliesTo.splice(index, 0, apply); + appliesTo.splice(index + 1, 0, apply); appliesToContainer.insertBefore(apply.el, base.el.nextSibling); } else { appliesTo.push(apply); @@ -792,7 +796,7 @@ function createSectionsEditor(style) { emitSectionChange(); } - function createApply({type, value, all}) { + function createApply({type = 'url', value, all = false}) { const applyId = INC_ID++; const dirtyPrefix = `section.${sectionId}.apply.${applyId}`; const el = all ? template.appliesToEverything.cloneNode(true) : @@ -834,7 +838,8 @@ function createSectionsEditor(style) { restore, el, getType: () => type, - getValue: () => value + getValue: () => value, + valueEl }; const removeButton = $('.remove-applies-to', el); @@ -867,13 +872,13 @@ function createSectionsEditor(style) { } } - function replaceSections(sections) { + function replaceSections(originalSections) { for (const section of sections) { section.remove(true); } sections.length = 0; container.textContent = ''; - return new Promise(resolve => initSection({sections, done: resolve})); + return new Promise(resolve => initSection({sections: originalSections, done: resolve})); } function replaceStyle(newStyle, codeIsUpdated) { diff --git a/edit/source-editor.js b/edit/source-editor.js index e9652aff..6415ae38 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -21,7 +21,6 @@ function createSourceEditor(style) { const dirty = dirtyReporter(); dirty.onChange(() => { const isDirty = dirty.isDirty(); - window.onbeforeunload = isDirty ? beforeUnload : null; document.body.classList.toggle('dirty', isDirty); $('#save-button').disabled = !isDirty; updateTitle(); diff --git a/js/cache.js b/js/cache.js index 4d5815f1..31e3df6e 100644 --- a/js/cache.js +++ b/js/cache.js @@ -11,8 +11,16 @@ function createCache(size = 1000) { delete: delete_, clear, has: id => map.has(id), - entries: () => map.entries(), - values: () => map.values(), + entries: function *() { + for (const [id, item] of map) { + yield [id, item.data]; + } + }, + values: function *() { + for (const item of map.values()) { + yield item.data; + } + }, get size() { return map.size; } diff --git a/js/msg.js b/js/msg.js index f79e242b..a4bc52f6 100644 --- a/js/msg.js +++ b/js/msg.js @@ -100,7 +100,7 @@ const msg = (() => { !tab.url.startsWith('chrome://newtab/') && !isExtension || isExtension && ignoreExtension || - !filter(tab) + filter && !filter(tab) ) { continue; } diff --git a/js/script-loader.js b/js/script-loader.js index 4cab8a2b..c2f0ca04 100644 --- a/js/script-loader.js +++ b/js/script-loader.js @@ -65,7 +65,10 @@ var loadScript = (() => { subscribers.set(srcSuffix, [resolve]); } // a resolved Promise won't reject anymore - setTimeout(() => emptyAfterCleanup(srcSuffix) + reject(), timeout); + setTimeout(() => { + emptyAfterCleanup(srcSuffix); + reject(new Error('Timeout')); + }, timeout); }); };