Skip to content

Commit ebdcbdd

Browse files
fix(bookmarks): allocate bookmark IDs across all stories to prevent cross-story collisions
allocateBookmarkId only scanned the story-local doc, so inserting a bookmark in a header could reuse an ID already present in the body. Reuse findAllBookmarksInDocument to scan all stories, matching the scope of the existing name-uniqueness check.
1 parent 12aa601 commit ebdcbdd

2 files changed

Lines changed: 12 additions & 7 deletions

File tree

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/bookmark-wrappers.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ describe('bookmarksInsertWrapper', () => {
133133
{ type: { name: 'bookmarkEnd' }, attrs: { id: '9' } },
134134
{ type: { name: 'bookmarkStart' }, attrs: { id: 'not-a-number' } },
135135
]);
136+
const existingEntries = [
137+
{ name: 'a', bookmarkId: '2', storyKey: 'body' },
138+
{ name: 'b', bookmarkId: '9', storyKey: 'body' },
139+
{ name: 'c', bookmarkId: 'not-a-number', storyKey: 'body' },
140+
];
141+
// Called twice: once for bookmarkExistsAnywhere, once for allocateBookmarkId.
142+
vi.mocked(findAllBookmarksInDocument).mockReturnValueOnce(existingEntries).mockReturnValueOnce(existingEntries);
136143

137144
const result = bookmarksInsertWrapper(editor, makeInput());
138145

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/bookmark-wrappers.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,12 @@ function parseBookmarkId(raw: unknown): number | null {
5757
return Number.isInteger(parsed) && parsed >= 0 ? parsed : null;
5858
}
5959

60-
function allocateBookmarkId(doc: import('prosemirror-model').Node): string {
60+
function allocateBookmarkId(editor: Editor): string {
6161
let maxId = -1;
62-
doc.descendants((node) => {
63-
if (node.type.name !== 'bookmarkStart' && node.type.name !== 'bookmarkEnd') return true;
64-
const id = parseBookmarkId(node.attrs?.id);
62+
for (const entry of findAllBookmarksInDocument(editor)) {
63+
const id = parseBookmarkId(entry.bookmarkId);
6564
if (id !== null && id > maxId) maxId = id;
66-
return true;
67-
});
65+
}
6866
return String(maxId + 1);
6967
}
7068

@@ -142,7 +140,7 @@ export function bookmarksInsertWrapper(
142140
const receipt = executeDomainCommand(
143141
storyEditor,
144142
() => {
145-
const bookmarkId = allocateBookmarkId(storyEditor.state.doc);
143+
const bookmarkId = allocateBookmarkId(editor);
146144
const startAttrs: Record<string, unknown> = {
147145
name: input.name,
148146
id: bookmarkId,

0 commit comments

Comments
 (0)