From 13703522099530623db2e7af54e8862afc386b2b Mon Sep 17 00:00:00 2001 From: cam Date: Sun, 18 Jan 2026 22:16:57 -0800 Subject: [PATCH] feat(comments): add position-ordered comments getter --- .../superdoc/src/stores/comments-store.js | 42 +++++++++++++++++-- .../src/stores/comments-store.test.js | 31 ++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/packages/superdoc/src/stores/comments-store.js b/packages/superdoc/src/stores/comments-store.js index 7d0644390..41b47a151 100644 --- a/packages/superdoc/src/stores/comments-store.js +++ b/packages/superdoc/src/stores/comments-store.js @@ -237,7 +237,36 @@ export const useCommentsStore = defineStore('comments', () => { * Generate the comments list separating resolved and active * We only return parent comments here, since CommentDialog.vue will handle threaded comments */ - const getGroupedComments = computed(() => { + const getCommentPositionKey = (comment) => comment?.commentId ?? comment?.importedId; + + const getPositionSortValue = (comment) => { + const key = getCommentPositionKey(comment); + if (!key) return null; + const position = editorCommentPositions.value?.[key]; + if (!position) return null; + if (Number.isFinite(position.start)) return position.start; + if (Number.isFinite(position.pos)) return position.pos; + if (Number.isFinite(position.from)) return position.from; + if (Number.isFinite(position.to)) return position.to; + return null; + }; + + const compareByCreatedTime = (a, b) => (a.createdTime ?? 0) - (b.createdTime ?? 0); + + const compareByPosition = (a, b) => { + const posA = getPositionSortValue(a); + const posB = getPositionSortValue(b); + + const hasA = Number.isFinite(posA); + const hasB = Number.isFinite(posB); + + if (hasA && hasB && posA !== posB) return posA - posB; + if (hasA && !hasB) return -1; + if (!hasA && hasB) return 1; + return compareByCreatedTime(a, b); + }; + + const buildGroupedComments = (sorter) => { const parentComments = []; const resolvedComments = []; const childCommentMap = new Map(); @@ -264,14 +293,18 @@ export const useCommentsStore = defineStore('comments', () => { }); // Return only parent comments - const sortedParentComments = parentComments.sort((a, b) => a.createdTime - b.createdTime); - const sortedResolvedComments = resolvedComments.sort((a, b) => a.createdTime - b.createdTime); + const sortedParentComments = parentComments.sort(sorter); + const sortedResolvedComments = resolvedComments.sort(sorter); return { parentComments: sortedParentComments, resolvedComments: sortedResolvedComments, }; - }); + }; + + const getGroupedComments = computed(() => buildGroupedComments(compareByCreatedTime)); + + const getCommentsByPosition = computed(() => buildGroupedComments(compareByPosition)); const hasOverlapId = (id) => overlappedIds.includes(id); const documentsWithConverations = computed(() => { @@ -710,6 +743,7 @@ export const useCommentsStore = defineStore('comments', () => { getConfig, documentsWithConverations, getGroupedComments, + getCommentsByPosition, getFloatingComments, // Actions diff --git a/packages/superdoc/src/stores/comments-store.test.js b/packages/superdoc/src/stores/comments-store.test.js index 65dd1272c..3ca1b31d8 100644 --- a/packages/superdoc/src/stores/comments-store.test.js +++ b/packages/superdoc/src/stores/comments-store.test.js @@ -394,4 +394,35 @@ describe('comments-store', () => { expect(store.getGroupedComments.parentComments).toEqual([]); }); }); + + describe('getCommentsByPosition', () => { + it('orders parent comments by document position when available', () => { + store.commentsList = [ + { commentId: 'c-1', createdTime: 2 }, + { commentId: 'c-2', createdTime: 1 }, + { commentId: 'c-3', createdTime: 3 }, + ]; + + store.editorCommentPositions = { + 'c-1': { start: 40, end: 50 }, + 'c-2': { start: 10, end: 20 }, + }; + + const ordered = store.getCommentsByPosition.parentComments.map((c) => c.commentId); + expect(ordered).toEqual(['c-2', 'c-1', 'c-3']); + }); + + it('falls back to createdTime for comments without positions', () => { + store.commentsList = [ + { commentId: 'c-1', createdTime: 3 }, + { commentId: 'c-2', createdTime: 1 }, + { commentId: 'c-3', createdTime: 2 }, + ]; + + store.editorCommentPositions = {}; + + const ordered = store.getCommentsByPosition.parentComments.map((c) => c.commentId); + expect(ordered).toEqual(['c-2', 'c-3', 'c-1']); + }); + }); });