diff --git a/src/edit/codemirror-factory.js b/src/edit/codemirror-factory.js index 7dacde8234..08e2097af2 100644 --- a/src/edit/codemirror-factory.js +++ b/src/edit/codemirror-factory.js @@ -17,7 +17,11 @@ const cms = new Set(); const cmDefaults = CodeMirror.defaults; const cmFactory = { - /** @return {CodeMirror.Editor} */ + /** + * @param {HTMLElement | ((host: HTMLElement) => void)} place + * @param {CodeMirror.EditorConfiguration} [options] + * @return {CodeMirror.Editor} + */ create(place, options) { const cm = CodeMirror(place, options); cm.display.lineDiv.on('mousewheel', plusMinusOnWheel.bind(cm), true); diff --git a/src/edit/compact-header.js b/src/edit/compact-header.js index 1d5c234dbc..d0ca49371d 100644 --- a/src/edit/compact-header.js +++ b/src/edit/compact-header.js @@ -1,8 +1,13 @@ import {$create, mqCompact} from '@/js/dom'; import {important} from '@/js/dom-util'; +import {template} from '@/js/localization'; import * as prefs from '@/js/prefs'; import editor from './editor'; +const h = template.body.$('#header'); +export const toggleSticky = val => h.classList.toggle('sticky', val); +export let sticky; + export default function CompactHeader() { // Set up mini-header on scroll const {isUsercss} = editor; @@ -40,9 +45,8 @@ export default function CompactHeader() { /** @param {IntersectionObserverEntry[]} entries */ function onScrolled(entries) { - const h = $id('header'); - const sticky = !entries.pop().intersectionRatio; + sticky = !entries.pop().intersectionRatio; if (!isUsercss) scroller.style.paddingTop = sticky ? h.offsetHeight + 'px' : ''; - h.classList.toggle('sticky', sticky); + toggleSticky(sticky); } } diff --git a/src/edit/editor.js b/src/edit/editor.js index bcfe2e3e1c..ea328338e8 100644 --- a/src/edit/editor.js +++ b/src/edit/editor.js @@ -1,6 +1,7 @@ import {$create} from '@/js/dom'; import * as prefs from '@/js/prefs'; import {clipString, debounce, deepEqual, mapObj, sessionStore, t} from '@/js/util'; +import {sticky} from './compact-header'; import DirtyReporter from './dirty-reporter'; const dirty = DirtyReporter(); @@ -28,6 +29,7 @@ const editor = self.editor = { }, regexps, saving: false, + /** @type {EditorScrollInfoContainer} */ scrollInfo: {}, get style() { return style; @@ -44,14 +46,17 @@ const editor = self.editor = { cm.setSelections(...si.sel, {scroll: false}); Object.assign(cm.display.scroller, si.scroll); // for source editor Object.assign(cm.doc, si.scroll); // for sectioned editor + return si; } }, cancel: () => location.assign('/manage.html'), makeScrollInfo() { - return { + return /** @namespace EditorScrollInfoContainer */ { + sticky, scrollY: window.scrollY, + /** @type {EditorScrollInfo[]} */ cms: editor.getEditors().map(cm => /** @namespace EditorScrollInfo */({ bookmarks: (cm.state.sublimeBookmarks || []).map(b => b.find()), focus: cm.hasFocus(), @@ -59,6 +64,7 @@ const editor = self.editor = { parentHeight: cm.display.wrapper.parentElement.offsetHeight, scroll: mapObj(cm.doc, null, ['scrollLeft', 'scrollTop']), sel: [cm.doc.sel.ranges, cm.doc.sel.primIndex], + viewTo: cm.display.viewTo, })), }; }, diff --git a/src/edit/index.js b/src/edit/index.js index 45834cc2f6..f6b15423ff 100644 --- a/src/edit/index.js +++ b/src/edit/index.js @@ -2,7 +2,7 @@ import '@/js/dom-init'; import {tBody} from '@/js/localization'; import * as prefs from '@/js/prefs'; import {CodeMirror} from '@/cm'; -import CompactHeader from './compact-header'; +import CompactHeader, {toggleSticky} from './compact-header'; import editor from './editor'; import EditorHeader from './editor-header'; import * as linterMan from './linter'; @@ -23,6 +23,7 @@ tBody(); (async () => { if (loading) await loading; + if (editor.scrollInfo.sticky) toggleSticky(true); EditorHeader(); USWIntegration(); // TODO: load respective js on demand? diff --git a/src/edit/moz-section-finder.js b/src/edit/moz-section-finder.js index da33b05753..92f58703ae 100644 --- a/src/edit/moz-section-finder.js +++ b/src/edit/moz-section-finder.js @@ -23,6 +23,7 @@ export default function MozSectionFinder(cm) { let updTo; let scheduled; + /** @namespace MozSectionFinder */ const finder = { IGNORE_ORIGIN: KEY, EQ_SKIP_KEYS: [ diff --git a/src/edit/moz-section-widget.js b/src/edit/moz-section-widget.js index 29c0ba6cba..2e319c3025 100644 --- a/src/edit/moz-section-widget.js +++ b/src/edit/moz-section-widget.js @@ -229,11 +229,12 @@ export default function MozSectionWidget(cm, finder = MozSectionFinder(cm)) { const isDelayed = added.isDelayed && (cm.startOperation(), true); const toDelay = []; const t0 = performance.now(); - let {viewFrom, viewTo} = cm.display; + let viewTo = editor.viewTo || cm.display.viewTo; for (const sec of added) { const i = removed.findIndex(isReusableWidget, sec); const old = removed[i]; - if (isDelayed || old || sec.end.line >= viewFrom && sec.start.line < viewTo) { + if (isDelayed || old + || sec.start.line < viewTo /* must add preceding ones to calc scrollTop*/) { renderWidget(sec, old); viewTo -= (sec.funcs.length || 1) * 1.25; if (old) removed[i] = null; diff --git a/src/edit/sections-editor-section.js b/src/edit/sections-editor-section.js index daaa9aaa15..87138252fe 100644 --- a/src/edit/sections-editor-section.js +++ b/src/edit/sections-editor-section.js @@ -37,6 +37,9 @@ export default class EditorSection { at[prefs.get('editor.targetsFirst') ? 'after' : 'before'](wrapper); }, { value: sectionData.code, + finishInit(_) { + editor.applyScrollInfo(_, si); + }, }); this.elLabelText = elLabel.lastChild; cm.el = el; @@ -66,7 +69,6 @@ export default class EditorSection { if (cssName && arr) for (const v of arr) this.addTarget(cssName, v); } if (!this.targets.length) this.addTarget(); - editor.applyScrollInfo(cm, si); initBeautifyButton(el.$('.beautify-section'), [cm]); prefs.subscribe('editor.toc.expanded', this.updateTocPrefToggled.bind(this), true); new ResizeGrip(cm); // eslint-disable-line no-use-before-define diff --git a/src/edit/source-editor.js b/src/edit/source-editor.js index 169bb901a1..439a6661bd 100644 --- a/src/edit/source-editor.js +++ b/src/edit/source-editor.js @@ -27,6 +27,9 @@ export default function SourceEditor() { `.replace(/^\s+/gm, ''); let savedGeneration; let prevMode = NaN; + /** @type {MozSectionFinder} */ + let sectionFinder; + let sectionWidget; $$remove('.sectioned-only'); $id('header').on('wheel', headerOnScroll); @@ -34,10 +37,23 @@ export default function SourceEditor() { $id('sections').appendChild($create('.single-editor')); $id('save-button').on('split-btn', saveTemplate); - const cm = cmFactory.create($('.single-editor')); + const cm = cmFactory.create($('.single-editor'), { + value: style.id ? style.sourceCode : setupNewStyle(editor.template), + finishInit(me) { + const si = editor.applyScrollInfo(me) || {}; + editor.viewTo = si.viewTo; + sectionFinder = MozSectionFinder(me); + sectionWidget = MozSectionWidget(me, sectionFinder); + prefs.subscribe('editor.linter', updateLinterSwitch, true); + prefs.subscribe('editor.appliesToLineWidget', + (k, val) => sectionWidget.toggle(val), true); + prefs.subscribe('editor.toc.expanded', + (k, val) => sectionFinder.onOff(editor.updateToc, val), true); + Object.assign(me.curOp, si.scroll); + editor.viewTo = 0; + }, + }); const cmpPos = CodeMirror.cmpPos; - const sectionFinder = MozSectionFinder(cm); - const sectionWidget = MozSectionWidget(cm, sectionFinder); const metaCompiler = createMetaCompiler(meta => { const {vars} = style[UCD] || {}; if (vars) { @@ -53,7 +69,6 @@ export default function SourceEditor() { style.url = meta.homepageURL || style.installationUrl; updateMeta(); }); - if (!style.id) setupNewStyle(editor.template); updateMeta(); /** @namespace Editor */ @@ -110,17 +125,6 @@ export default function SourceEditor() { scrollToEditor: () => {}, }); - prefs.subscribe('editor.linter', updateLinterSwitch, true); - prefs.subscribe('editor.appliesToLineWidget', - (k, val) => sectionWidget.toggle(val), true); - prefs.subscribe('editor.toc.expanded', - (k, val) => sectionFinder.onOff(editor.updateToc, val), true); - - if (style.id) { - cm.setValue(style.sourceCode); - cm.clearHistory(); - cm.markClean(); - } savedGeneration = cm.changeGeneration(); cm.on('changes', (_, changes) => { dirty.modify('sourceGeneration', savedGeneration, cm.changeGeneration()); @@ -139,7 +143,6 @@ export default function SourceEditor() { if (!$isTextInput(document.activeElement)) { cm.focus(); } - editor.applyScrollInfo(cm); // WARNING! Place it after all cm.XXX calls that change scroll pos /** Shows the console.log output from the background worker stored in `log` property */ function showLog(log) { @@ -183,19 +186,14 @@ export default function SourceEditor() { if (Object.keys(sec0).length === 1) { // the only key is 'code' sec0.domains = ['example.com']; } - style.sourceCode = (tpl || DEFAULT_TEMPLATE) + return (style.sourceCode = (tpl || DEFAULT_TEMPLATE) .replace(/(@name)(?:([\t\x20]+).*|\n)/, (_, k, space) => `${k}${space || ' '}${style.name}`) .replace(/\s*@-moz-document[^{]*{([^}]*)}\s*$/g, // stripping dummy sections (s, body) => body.trim() === comment ? '\n\n' : s) .trim() + '\n\n' + - styleToCss(style); - cm.startOperation(); - cm.setValue(style.sourceCode); - cm.clearHistory(); - cm.markClean(); - cm.endOperation(); - dirty.clear('sourceGeneration'); + styleToCss(style) + ); } function updateMeta() { diff --git a/src/js/color/color-view.js b/src/js/color/color-view.js index e649124aa4..5e4955ef6e 100644 --- a/src/js/color/color-view.js +++ b/src/js/color/color-view.js @@ -11,7 +11,7 @@ export const COLORVIEW_SWATCH_PROP = `--${COLORVIEW_SWATCH_CLASS}`; const CLOSE_POPUP_EVENT = 'close-colorpicker-popup'; const {RX_COLOR, testAt} = colorConverter; -const RX_UNSUPPORTED = (s => s && new RegExp(s))([ +const RX_UNSUPPORTED = !__.MV3 && (s => s && new RegExp(s))([ !CSS.supports('color', '#abcd') && /#(.{4}){1,2}$/, !CSS.supports('color', 'hwb(1 0% 0%)') && /^hwb\(/, !CSS.supports('color', 'rgb(1e2,0,0)') && /\de/,