Skip to content

Commit 2f6ecc9

Browse files
committed
fix: initial comments loading when only story based tcs in place
1 parent 950b8e5 commit 2f6ecc9

2 files changed

Lines changed: 114 additions & 3 deletions

File tree

packages/superdoc/src/stores/comments-store.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,29 @@ export const useCommentsStore = defineStore('comments', () => {
10331033
return normalizedName || null;
10341034
};
10351035

1036+
/**
1037+
* Bootstrap tracked-change comment threads after a DOCX import finishes.
1038+
*
1039+
* Initial import historically rebuilt only body tracked-change threads so
1040+
* resolved imported body comments stayed resolved. Header/footer and note
1041+
* tracked changes live outside the body PM state, so they need an additional
1042+
* story-aware bootstrap pass here.
1043+
*
1044+
* We intentionally keep the existing body-only rebuild instead of switching
1045+
* to the broader syncTrackedChangeComments() path so imported resolved body
1046+
* tracked-change threads preserve their initial resolved state.
1047+
*
1048+
* @param {Object | null | undefined} editor
1049+
* @param {Object | null | undefined} superdoc
1050+
* @returns {void}
1051+
*/
1052+
const bootstrapImportedTrackedChangeComments = (editor, superdoc) => {
1053+
if (!editor || !superdoc) return;
1054+
1055+
createCommentForTrackChanges(editor, superdoc);
1056+
syncStoryTrackedChangeComments({ superdoc, editor });
1057+
};
1058+
10361059
/**
10371060
* Initialize loaded comments into SuperDoc by mapping the imported
10381061
* comment data to SuperDoc useComment objects.
@@ -1096,9 +1119,9 @@ export const useCommentsStore = defineStore('comments', () => {
10961119
});
10971120

10981121
setTimeout(() => {
1099-
// do not block the first rendering of the doc
1100-
// and create comments asynchronously.
1101-
createCommentForTrackChanges(editor, superdoc);
1122+
// Do not block the first rendering of the doc. Rebuild tracked-change
1123+
// threads asynchronously once the editor is ready for comment sync.
1124+
bootstrapImportedTrackedChangeComments(editor, superdoc);
11021125
}, 0);
11031126
};
11041127

packages/superdoc/src/stores/comments-store.test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,94 @@ describe('comments-store', () => {
15811581
expect(editorDispatch).toHaveBeenCalledWith(tr);
15821582
});
15831583

1584+
it('bootstraps story tracked-change comments during initial DOCX load', async () => {
1585+
const editorDispatch = vi.fn();
1586+
const tr = { setMeta: vi.fn() };
1587+
const editor = {
1588+
converter: { commentThreadingProfile: 'range-based' },
1589+
state: { doc: { type: 'body-doc' } },
1590+
view: { state: { tr }, dispatch: editorDispatch },
1591+
options: { documentId: 'doc-1' },
1592+
};
1593+
const storyState = { doc: { type: 'header-doc' } };
1594+
const snapshot = {
1595+
type: 'insert',
1596+
excerpt: 'header test',
1597+
anchorKey: 'tc::hf:part:rId9::raw-hf-1',
1598+
author: 'Alice',
1599+
authorEmail: 'alice@example.com',
1600+
authorImage: null,
1601+
date: 123,
1602+
story: { kind: 'story', storyType: 'headerFooterPart', refId: 'rId9' },
1603+
storyKind: 'headerFooter',
1604+
storyLabel: 'Header',
1605+
runtimeRef: { rawId: 'raw-hf-1' },
1606+
};
1607+
1608+
__mockSuperdoc.documents.value = [{ id: 'doc-1', type: 'docx' }];
1609+
1610+
trackChangesHelpersMock.getTrackChanges.mockImplementation((state, id) => {
1611+
if (state === storyState && id === 'raw-hf-1') {
1612+
return [{ mark: { type: { name: 'trackInsert' }, attrs: { id: 'raw-hf-1' } } }];
1613+
}
1614+
return [];
1615+
});
1616+
groupChangesMock.mockReturnValue([]);
1617+
getTrackedChangeIndexMock.mockReturnValue({
1618+
getAll: vi.fn(() => [snapshot]),
1619+
invalidate: vi.fn(),
1620+
invalidateAll: vi.fn(),
1621+
subscribe: vi.fn(() => () => {}),
1622+
dispose: vi.fn(),
1623+
});
1624+
resolveTrackedChangeInStoryMock.mockReturnValue({
1625+
editor: { state: storyState },
1626+
story: snapshot.story,
1627+
runtimeRef: { storyKey: 'hf:part:rId9', rawId: 'raw-hf-1' },
1628+
change: { rawId: 'raw-hf-1' },
1629+
});
1630+
createOrUpdateTrackedChangeCommentMock.mockReturnValue({
1631+
event: 'add',
1632+
changeId: 'raw-hf-1',
1633+
trackedChangeType: 'insert',
1634+
trackedChangeDisplayType: 'insert',
1635+
trackedChangeText: 'header test',
1636+
deletedText: null,
1637+
author: 'Alice',
1638+
authorEmail: 'alice@example.com',
1639+
documentId: 'doc-1',
1640+
coords: {},
1641+
});
1642+
1643+
store.processLoadedDocxComments({
1644+
superdoc: __mockSuperdoc,
1645+
editor,
1646+
comments: [],
1647+
documentId: 'doc-1',
1648+
});
1649+
1650+
vi.runAllTimers();
1651+
await nextTick();
1652+
1653+
expect(resolveTrackedChangeInStoryMock).toHaveBeenCalledWith(editor, {
1654+
kind: 'entity',
1655+
entityType: 'trackedChange',
1656+
entityId: 'raw-hf-1',
1657+
story: snapshot.story,
1658+
});
1659+
expect(store.commentsList).toEqual([
1660+
expect.objectContaining({
1661+
commentId: 'raw-hf-1',
1662+
trackedChange: true,
1663+
trackedChangeText: 'header test',
1664+
trackedChangeStoryKind: 'headerFooter',
1665+
trackedChangeAnchorKey: 'tc::hf:part:rId9::raw-hf-1',
1666+
}),
1667+
]);
1668+
expect(tr.setMeta).toHaveBeenCalledWith('CommentsPluginKey', { type: 'force' });
1669+
expect(editorDispatch).toHaveBeenCalledWith(tr);
1670+
});
1671+
15841672
it('should load comments with correct created time', () => {
15851673
store.init({
15861674
readOnly: true,

0 commit comments

Comments
 (0)