Fix Android IME typing: editor clobbered its own autosave echo
The $: block applying note changes to CodeMirror listed the autosave bookkeeping vars (saveTimer, lastKnown) as dependencies, so the 400 ms autosave re-ran it before App delivered the updated note prop. That stale run misread the save echo as an external sync change and replaced the whole doc mid-typing: on Android this aborted every IME composition and threw the cursor to the top of the note. Apply note/query via $: fn(prop) so the statements re-run only when the prop itself changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,39 +106,52 @@
|
||||
|
||||
// Swap document when a different note is opened; apply external (sync) changes
|
||||
// without clobbering in-flight typing.
|
||||
$: if (view && note) {
|
||||
if (note.id !== currentId) {
|
||||
function applyNote(n: Note) {
|
||||
if (!view || !n) return;
|
||||
if (n.id !== currentId) {
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = null;
|
||||
flushSave(currentId, view.state.doc.toString());
|
||||
}
|
||||
currentId = note.id;
|
||||
lastKnown = note.content;
|
||||
view.setState(makeState(note.content));
|
||||
} else if (note.content !== lastKnown) {
|
||||
currentId = n.id;
|
||||
lastKnown = n.content;
|
||||
view.setState(makeState(n.content));
|
||||
} else if (n.content !== lastKnown) {
|
||||
// Store changed underneath us (sync pull / conflict replacement) — not an
|
||||
// echo of our own autosave. Replace the doc.
|
||||
lastKnown = note.content;
|
||||
lastKnown = n.content;
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = null;
|
||||
}
|
||||
if (note.content !== view.state.doc.toString()) {
|
||||
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: note.content } });
|
||||
if (n.content !== view.state.doc.toString()) {
|
||||
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: n.content } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lastQuery = '';
|
||||
$: if (view && query !== lastQuery) {
|
||||
lastQuery = query;
|
||||
view.dispatch({ effects: searchMark.reconfigure(searchExtension(query)) });
|
||||
function applyQuery(q: string) {
|
||||
if (!view || q === lastQuery) return;
|
||||
lastQuery = q;
|
||||
view.dispatch({ effects: searchMark.reconfigure(searchExtension(q)) });
|
||||
}
|
||||
|
||||
// Deliberately `$: fn(prop)` instead of inlining the logic in a reactive
|
||||
// block: these must re-run ONLY when the prop itself changes. Svelte tracks
|
||||
// every variable a `$:` statement mentions (including writes like
|
||||
// `saveTimer = null` deep in the autosave path), and a re-run triggered by
|
||||
// our own bookkeeping sees a stale `note`, misreads the autosave echo as an
|
||||
// external sync change, and replaces the doc mid-typing — which on Android
|
||||
// aborts IME composition and throws the cursor to the top of the note.
|
||||
$: applyNote(note);
|
||||
$: applyQuery(query);
|
||||
|
||||
onMount(() => {
|
||||
currentId = note.id;
|
||||
lastKnown = note.content;
|
||||
lastQuery = query; // makeState below bakes in the current query
|
||||
view = new EditorView({ state: makeState(note.content), parent: host });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user