keep scroll position and selections in tab's session

This commit is contained in:
tophf 2020-11-13 19:09:01 +03:00
parent 1d226aac8b
commit 3905722cdf
4 changed files with 55 additions and 19 deletions

View File

@ -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

View File

@ -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();

View File

@ -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);
}

View File

@ -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();