From ce48d77cd8c44761fb69b5d4ff8251847b0c6766 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Sun, 23 Aug 2026 15:38:57 +0200 Subject: [PATCH] fix: stop wiki links extending after autocomplete --- src/lib/components/Editor.svelte | 45 ++++++++++++-------------------- src/lib/editor/wikiLinks.ts | 22 ++++++++++++++++ 2 files changed, 39 insertions(+), 28 deletions(-) create mode 100644 src/lib/editor/wikiLinks.ts diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index c8ccd3f..841a3c8 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -54,6 +54,7 @@ import { convertListNode, type MixedListName } from '$lib/editor/mixedLists'; import { clearFormatting } from '$lib/editor/clearFormatting'; import { serializeInlineMarkdown } from '$lib/editor/markdown'; + import { replaceWithWikiLink } from '$lib/editor/wikiLinks'; import { assetSourceToMarkdown, assetUrlToLocalPath, normalizeLocalAssetPath, resolveVaultFilePath } from '$lib/utils/paths'; import GraphView from './GraphView.svelte'; import TagSuggestInput from './TagSuggestInput.svelte'; @@ -2547,10 +2548,7 @@ function insertWikiLink(entry: NoteTitleEntry, originalRef?: string) { if (!editor || !wikiLinkMenu) return; const { from } = wikiLinkMenu; - // Delete the [[ trigger and query text const to = editor.state.selection.from; - editor.chain().focus().deleteRange({ from, to }).run(); - // Insert the wiki-link mark // For ambiguous titles, use vault-relative path as the ref so it survives source-mode roundtrips const displayText = entry.title; let titleAttr = originalRef || entry.title; @@ -2562,17 +2560,16 @@ titleAttr = relPath + anchor; } } - tick().then(() => { - if (!editor) return; - editor.chain().focus() - .insertContent({ - type: 'text', - text: displayText, - marks: [{ type: 'wikiLink', attrs: { title: titleAttr, path: entry.path, aliased: displayText !== titleAttr } }], - }) - .run(); - }); + const transaction = replaceWithWikiLink( + editor.state, + from, + to, + displayText, + { title: titleAttr, path: entry.path || '', aliased: displayText !== titleAttr }, + ); closeWikiLinkMenu(); + editor.view.dispatch(transaction); + editor.view.focus(); } function executeWikiLinkCommand(index: number) { @@ -2759,21 +2756,13 @@ closeWikiLinkMenu(); tick().then(() => { if (!editor) return; - // Use a single ProseMirror transaction to replace [[query] with the - // wiki-link and clear stored marks atomically, preventing the inclusive - // mark from bleeding into subsequent text. Do NOT call deleteRange first - // — that would shift positions and make menuFrom/curTo invalid here. - const { tr, schema } = editor.view.state; - const wikiLinkMark = schema.marks.wikiLink.create({ - title: noteRef, - path: '', - aliased: display !== noteRef, - }); - const textNode = schema.text(display, [wikiLinkMark]); - tr.replaceWith(menuFrom, curTo, textNode); - tr.setSelection(TextSelection.create(tr.doc, menuFrom + display.length)); - tr.setStoredMarks([]); - editor.view.dispatch(tr); + editor.view.dispatch(replaceWithWikiLink( + editor.state, + menuFrom, + curTo, + display, + { title: noteRef, path: '', aliased: display !== noteRef }, + )); }); } } else { diff --git a/src/lib/editor/wikiLinks.ts b/src/lib/editor/wikiLinks.ts new file mode 100644 index 0000000..76c209d --- /dev/null +++ b/src/lib/editor/wikiLinks.ts @@ -0,0 +1,22 @@ +import { TextSelection, type EditorState, type Transaction } from '@tiptap/pm/state'; + +export type WikiLinkAttributes = { + title: string; + path: string; + aliased: boolean; +}; + +export function replaceWithWikiLink( + state: EditorState, + from: number, + to: number, + text: string, + attrs: WikiLinkAttributes +): Transaction { + const wikiLinkMark = state.schema.marks.wikiLink.create(attrs); + const textNode = state.schema.text(text, [wikiLinkMark]); + const transaction = state.tr.replaceWith(from, to, textNode); + transaction.setSelection(TextSelection.create(transaction.doc, from + text.length)); + transaction.setStoredMarks([]); + return transaction; +}