diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.test.ts b/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.test.ts new file mode 100644 index 00000000..bdb65b0c --- /dev/null +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.test.ts @@ -0,0 +1,32 @@ +import { Editor, getMarkRange } from '@tiptap/core'; +import { beforeAll, describe, expect, it } from 'vitest'; + +import { createMarkdownExtensions } from './markdown-editor-extensions'; +import { setupEditorJsdom } from './markdown-editor-test-setup'; + +beforeAll(setupEditorJsdom); + +describe('link handle popover key is stable while typing inside a link (LINK-REMOUNT)', () => { + it('keeps range.from fixed as range.to grows, so a from-based key does not remount', () => { + const editor = new Editor({ + content: '[label](https://example.com)', + contentType: 'markdown', + extensions: createMarkdownExtensions(), + }); + const linkType = editor.schema.marks.link!; + + editor.commands.setTextSelection(3); + const before = getMarkRange(editor.state.selection.$from, linkType); + + editor.commands.insertContent('X'); + const after = getMarkRange(editor.state.selection.$from, linkType); + + editor.destroy(); + + expect(before && after).toBeTruthy(); + // The fix keys the popover on range.from alone: stable here, so the edit form is not remounted. + expect(after!.from).toBe(before!.from); + // range.to grew by the inserted char — the old `${from}-${to}` key would have remounted every keystroke. + expect(after!.to).toBe(before!.to + 1); + }); +}); diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.tsx b/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.tsx index 4fba7411..406e6a0f 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.tsx +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-link-handle.tsx @@ -97,7 +97,10 @@ function useLinkHandle(editor: Editor) { const rect = posToDOMRect(editor.view, range.from, range.to); const href = (editor.getAttributes('link').href as string | undefined) ?? ''; - setTarget({ href, key: `${range.from}-${range.to}`, rect }); + // Key on the link's START only. Typing inside the link grows range.to every keystroke, and + // would then unmount/remount and re-seed its URL field from initialUrl, + // silently discarding an in-progress edit. from is stable while the caret stays in the same link. + setTarget({ href, key: `${range.from}`, rect }); }; const clear = () => setTarget(null);