feat: debounced caret-only saves without broadcast

This commit is contained in:
Fabio Montefuscolo 2026-08-03 22:12:03 +02:00
parent 6db41de485
commit 79a552f620
2 changed files with 48 additions and 1 deletions

View file

@ -8,7 +8,7 @@
## 2. Caret persistence in the side panel ## 2. Caret persistence in the side panel
- [x] 2.1 In `sidepanel/sidepanel.js`, pass current caret (`selectionStart`/`selectionEnd`) and `scrollTop` to `saveNote` in the existing autosave (`doSave`) - [x] 2.1 In `sidepanel/sidepanel.js`, pass current caret (`selectionStart`/`selectionEnd`) and `scrollTop` to `saveNote` in the existing autosave (`doSave`)
- [ ] 2.2 Track caret-only movement via `document` `selectionchange` (filtered to the editor) and editor `scroll`; debounce ~800ms into a caret-only save that calls `saveNote` without broadcasting; skip it while a content autosave is pending - [x] 2.2 Track caret-only movement via `document` `selectionchange` (filtered to the editor) and editor `scroll`; debounce ~800ms into a caret-only save that calls `saveNote` without broadcasting; skip it while a content autosave is pending
- [ ] 2.3 Flush any pending caret-only save on editor `blur` and on `visibilitychange` → hidden - [ ] 2.3 Flush any pending caret-only save on editor `blur` and on `visibilitychange` → hidden
## 3. Focus and caret/scroll restore ## 3. Focus and caret/scroll restore

View file

@ -221,6 +221,53 @@ editor.addEventListener('blur', () => {
} }
}); });
// ---------------------------------------------------------------------------
// Caret-only persistence
//
// Caret moves (selectionchange / scroll) without content edits are saved on
// a debounce. These saves never broadcast: a remote caret update must not
// make other panels reload and lose their own caret.
// ---------------------------------------------------------------------------
let caretTimer = null;
const CARET_DEBOUNCE_MS = 800;
function scheduleCaretSave() {
if (!currentKey || isLoadingContent) return;
// A pending content autosave will capture the caret anyway.
if (saveTimer) return;
if (caretTimer) clearTimeout(caretTimer);
caretTimer = setTimeout(doCaretSave, CARET_DEBOUNCE_MS);
}
async function doCaretSave() {
caretTimer = null;
if (!currentKey || currentTabId == null) return;
// A content save got scheduled meanwhile — it captures the caret.
if (saveTimer) return;
// A remote content change is waiting for blur; our content is stale and
// must not be written back over it.
if (deferredReload) return;
try {
const saved = await saveNote(currentKey, editor.value, {}, {
caretStart: editor.selectionStart,
caretEnd: editor.selectionEnd,
scrollTop: editor.scrollTop,
});
currentNote = saved;
} catch (err) {
console.error('Side Notes: caret save failed', err);
}
}
document.addEventListener('selectionchange', () => {
if (document.activeElement === editor) {
scheduleCaretSave();
}
});
editor.addEventListener('scroll', scheduleCaretSave);
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// In-panel Find // In-panel Find
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------