diff --git a/edit/edit.js b/edit/edit.js index d86adc92..0b5c89c8 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -56,13 +56,23 @@ lazyInit(); .then(initTheme), onDOMready(), ]); + const scrollInfo = style.id && tryJSONparse(sessionStore['editorScrollInfo' + style.id]); /** @namespace EditorBase */ Object.assign(editor, { style, dirty, + scrollInfo, updateName, updateToc, toggleStyle, + applyScrollInfo(cm, si = ((scrollInfo || {}).cms || [])[0]) { + if (si && si.sel) { + cm.operation(() => { + cm.setSelections(...si.sel, {scroll: false}); + cm.scrollIntoView(cm.getCursor(), si.parentHeight / 2); + }); + } + } }); prefs.subscribe('editor.linter', updateLinter); prefs.subscribe('editor.keyMap', showHotkeyInTooltip); @@ -410,6 +420,15 @@ function onRuntimeMessage(request) { function beforeUnload(e) { sessionStore.windowPos = JSON.stringify(canSaveWindowPos() && prefs.get('windowPosition')); + sessionStore['editorScrollInfo' + editor.style.id] = JSON.stringify({ + scrollY: window.scrollY, + cms: editor.getEditors().map(cm => /** @namespace EditorScrollInfo */({ + focus: cm.hasFocus(), + height: cm.display.wrapper.style.height.replace('100vh', ''), + parentHeight: cm.display.wrapper.parentElement.offsetHeight, + sel: cm.isClean() && [cm.doc.sel.ranges, cm.doc.sel.primIndex], + })), + }); const activeElement = document.activeElement; if (activeElement) { // blurring triggers 'change' or 'input' event if needed diff --git a/edit/sections-editor-section.js b/edit/sections-editor-section.js index d1446881..0bd7a35a 100644 --- a/edit/sections-editor-section.js +++ b/edit/sections-editor-section.js @@ -17,20 +17,26 @@ /* exported createSection */ -/** @returns {EditorSection} */ -function createSection(originalSection, genId) { +/** + * @param {StyleSection} originalSection + * @param {function():number} genId + * @param {EditorScrollInfo} [si] + * @returns {EditorSection} + */ +function createSection(originalSection, genId, si) { const {dirty} = editor; const sectionId = genId(); const el = template.section.cloneNode(true); const elLabel = $('.code-label', el); const cm = cmFactory.create(wrapper => { // making it tall during initial load so IntersectionObserver sees only one adjacent CM - wrapper.style.height = '100vh'; + wrapper.style.height = si ? si.height : '100vh'; elLabel.after(wrapper); }, { value: originalSection.code, }); el.CodeMirror = cm; // used by getAssociatedEditor + editor.applyScrollInfo(cm, si); const changeListeners = new Set(); diff --git a/edit/sections-editor.js b/edit/sections-editor.js index 134397c4..80bf791c 100644 --- a/edit/sections-editor.js +++ b/edit/sections-editor.js @@ -486,7 +486,7 @@ function SectionsEditor() { livePreview.update(getModel()); } - function initSections(originalSections, { + function initSections(src, { focusOn = 0, replace = false, pristine = false, @@ -497,26 +497,35 @@ function SectionsEditor() { container.textContent = ''; } let done; - const total = originalSections.length; - originalSections = originalSections.slice(); + let index = 0; + let y = 0; + const total = src.length; + let si = editor.scrollInfo; + if (si && si.cms && si.cms.length === src.length) { + si.scrollY2 = si.scrollY + window.innerHeight; + container.style.height = si.scrollY2 + 'px'; + scrollTo(0, si.scrollY); + } else { + si = null; + } return new Promise(resolve => { done = resolve; - chunk(true); + chunk(!si); }); function chunk(forceRefresh) { const t0 = performance.now(); - while (originalSections.length && performance.now() - t0 < 100) { - insertSectionAfter(originalSections.shift(), undefined, forceRefresh); + while (index < total && performance.now() - t0 < 100) { + if (si) forceRefresh = y < si.scrollY2 && (y += si.cms[index].parentHeight) > si.scrollY; + insertSectionAfter(src[index], undefined, forceRefresh, si && si.cms[index]); if (pristine) dirty.clear(); - if (focusOn !== false && sections[focusOn]) { - sections[focusOn].cm.focus(); - focusOn = false; - } + if (index === focusOn && !si) sections[index].cm.focus(); + index++; } - setGlobalProgress(total - originalSections.length, total); - if (!originalSections.length) { + setGlobalProgress(index, total); + if (index === total) { setGlobalProgress(); - requestAnimationFrame(fitToAvailableSpace); + if (!si) requestAnimationFrame(fitToAvailableSpace); + container.style.removeProperty('height'); done(); } else { setTimeout(chunk); @@ -565,18 +574,19 @@ function SectionsEditor() { * @param {StyleSection} [init] * @param {EditorSection} [base] * @param {boolean} [forceRefresh] + * @param {EditorScrollInfo} [si] */ - function insertSectionAfter(init, base, forceRefresh) { + function insertSectionAfter(init, base, forceRefresh, si) { if (!init) { init = {code: '', urlPrefixes: ['http://example.com']}; } - const section = createSection(init, genId); + const section = createSection(init, genId, si); const {cm} = section; sections.splice(base ? sections.indexOf(base) + 1 : sections.length, 0, section); container.insertBefore(section.el, base ? base.el.nextSibling : null); refreshOnView(cm, forceRefresh); registerEvents(section); - if (!base || init.code) { + if ((!si || !si.height) && (!base || init.code)) { // Fit a) during startup or b) when the clone button is clicked on a section with some code fitToContent(section); } diff --git a/edit/source-editor.js b/edit/source-editor.js index eaa95e7b..8cafc93d 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -75,6 +75,7 @@ function SourceEditor() { 'editor.appliesToLineWidget': (k, val) => sectionWidget.toggle(val), 'editor.toc.expanded': (k, val) => sectionFinder.onOff(editor.updateToc, val), }, {now: true}); + editor.applyScrollInfo(cm); cm.clearHistory(); cm.markClean(); savedGeneration = cm.changeGeneration();