diff --git a/packages/super-editor/src/core/super-converter/v2/exporter/commentsExporter.js b/packages/super-editor/src/core/super-converter/v2/exporter/commentsExporter.js index 9547b92af2..c1db5a5d42 100644 --- a/packages/super-editor/src/core/super-converter/v2/exporter/commentsExporter.js +++ b/packages/super-editor/src/core/super-converter/v2/exporter/commentsExporter.js @@ -18,7 +18,7 @@ export function translateCommentNode(params, type) { // Check if the comment is resolved const originalComment = params.comments.find((comment) => { - return comment.commentId == nodeId || comment.importedId == nodeId; + return comment.commentId == nodeId; }); if (!originalComment) return; @@ -90,8 +90,8 @@ export const getCommentDefinition = (comment, commentId, allComments) => { const attributes = { 'w:id': String(commentId), - 'w:author': comment.creatorName, - 'w:email': comment.creatorEmail, + 'w:author': comment.creatorName || comment.importedAuthor?.name, + 'w:email': comment.creatorEmail || comment.importedAuthor?.email, 'w:date': toIsoNoFractional(comment.createdTime), 'w:initials': getInitials(comment.creatorName), 'w:done': comment.resolvedTime ? '1' : '0', diff --git a/packages/super-editor/src/core/super-converter/v2/importer/documentCommentsImporter.js b/packages/super-editor/src/core/super-converter/v2/importer/documentCommentsImporter.js index e13736ae4a..ae4896342d 100644 --- a/packages/super-editor/src/core/super-converter/v2/importer/documentCommentsImporter.js +++ b/packages/super-editor/src/core/super-converter/v2/importer/documentCommentsImporter.js @@ -24,7 +24,7 @@ export function importCommentData({ docx }) { const extractedComments = allComments.map((el) => { const { attributes } = el; - const commentId = attributes['w:id']; + const importedId = attributes['w:id']; const authorName = attributes['w:author']; const authorEmail = attributes['w:email']; const initials = attributes['w:initials']; @@ -44,8 +44,8 @@ export function importCommentData({ docx }) { const paraId = attrs['w14:paraId']; return { - id: uuidv4(), - importedId: commentId, + commentId: uuidv4(), + importedId, creatorName: authorName, creatorEmail: authorEmail, createdTime: unixTimestampMs, @@ -88,7 +88,7 @@ const generateCommentsWithExtendedData = ({ docx, comments }) => { const newComment = { ...comment, - commentId: superdocCommentId, + commentId: superdocCommentId || uuidv4(), isDone, parentCommentId: parentComment?.id, }; diff --git a/packages/super-editor/src/extensions/comment/comments-helpers.js b/packages/super-editor/src/extensions/comment/comments-helpers.js index 3b85e1f640..1f22bcf135 100644 --- a/packages/super-editor/src/extensions/comment/comments-helpers.js +++ b/packages/super-editor/src/extensions/comment/comments-helpers.js @@ -7,14 +7,13 @@ import { CommentsPluginKey } from './comments-plugin.js'; * * @param {Object} param0 * @param {string} param0.commentId The comment ID - * @param {string} param0.importedId The imported ID * @param {import('prosemirror-state').EditorState} state The current editor state * @param {import('prosemirror-state').Transaction} tr The current transaction * @param {Function} param0.dispatch The dispatch function * @returns {void} */ -export const removeCommentsById = ({ commentId, importedId, state, tr, dispatch }) => { - const positions = getCommentPositionsById(commentId, importedId, state.doc); +export const removeCommentsById = ({ commentId, state, tr, dispatch }) => { + const positions = getCommentPositionsById(commentId, state.doc); // Remove the mark positions.forEach(({ from, to }) => { @@ -28,11 +27,10 @@ export const removeCommentsById = ({ commentId, importedId, state, tr, dispatch * Get the positions of a comment by ID * * @param {String} commentId The comment ID - * @param {String} importedId The imported ID * @param {import('prosemirror-model').Node} doc The prosemirror document * @returns {Array} The positions of the comment */ -export const getCommentPositionsById = (commentId, importedId, doc) => { +export const getCommentPositionsById = (commentId, doc) => { const positions = []; doc.descendants((node, pos) => { const { marks } = node; @@ -40,9 +38,8 @@ export const getCommentPositionsById = (commentId, importedId, doc) => { if (commentMark) { const { attrs } = commentMark; - const { commentId: currentCommentId, importedId: currentImportedId } = attrs; - const wid = currentCommentId || currentImportedId; - if (wid == commentId || wid == importedId) { + const { commentId: currentCommentId, } = attrs; + if (commentId === currentCommentId) { positions.push({ from: pos, to: pos + node.nodeSize }); } } @@ -71,11 +68,11 @@ export const prepareCommentsForExport = (doc, tr, schema, comments = []) => { if (commentMark) { const { attrs = {} } = commentMark; - const { commentId, importedId } = attrs; + const { commentId } = attrs; if (commentId === 'pending') return; - if (seen.has(commentId || importedId)) return; - seen.add(commentId || importedId); + if (seen.has(commentId)) return; + seen.add(commentId); const commentStartNodeAttrs = getPreparedComment(commentMark.attrs); const startNode = schema.nodes.commentRangeStart.create(commentStartNodeAttrs); @@ -90,7 +87,7 @@ export const prepareCommentsForExport = (doc, tr, schema, comments = []) => { node: endNode, }); - const parentId = commentId || importedId; + const parentId = commentId; if (parentId) { const childComments = comments .filter((c) => c.parentCommentId == parentId || c.parentCommentId == parentId) @@ -140,10 +137,9 @@ export const prepareCommentsForExport = (doc, tr, schema, comments = []) => { * @returns {Object} The prepared comment attributes */ export const getPreparedComment = (attrs) => { - const { commentId, importedId, internal } = attrs; - const wid = commentId ? commentId : importedId; + const { commentId, internal } = attrs; return { - 'w:id': wid, + 'w:id': commentId, internal: internal, }; } @@ -163,15 +159,23 @@ export const prepareCommentsForImport = (doc, tr, schema, converter) => { doc.descendants((node, pos) => { const { type } = node; + + const commentNodes = ['commentRangeStart', 'commentRangeEnd', 'commentReference']; + if (!commentNodes.includes(type.name)) return; + + const matchingImportedComment = converter.comments?.find((c) => c.importedId == node.attrs['w:id']) || {}; + const { commentId } = matchingImportedComment; + if (!commentId) return; // If the node is a commentRangeStart, record it so we can place a mark once we find the end. if (type.name === 'commentRangeStart') { toMark.push({ - 'w:id': node.attrs['w:id'], + 'w:id': commentId, + importedId: node.attrs['w:id'], internal: false, start: pos, }); - + // We'll remove this node from the final doc toDelete.push({ start: pos, end: pos + 1 }); } @@ -179,11 +183,12 @@ export const prepareCommentsForImport = (doc, tr, schema, converter) => { // When we reach the commentRangeEnd, add a mark spanning from start to current pos, // then mark it for deletion as well. else if (type.name === 'commentRangeEnd') { - const itemToMark = toMark.find((p) => p['w:id'] === node.attrs['w:id']); + const itemToMark = toMark.find((p) => p.importedId === node.attrs['w:id']); if (!itemToMark) return; // No matching start? just skip const { start } = itemToMark; const markAttrs = { + commentId, importedId: node.attrs['w:id'], internal: itemToMark.internal, }; diff --git a/packages/super-editor/src/extensions/comment/comments-plugin.js b/packages/super-editor/src/extensions/comment/comments-plugin.js index 83da0e1cff..81ab8bc910 100644 --- a/packages/super-editor/src/extensions/comment/comments-plugin.js +++ b/packages/super-editor/src/extensions/comment/comments-plugin.js @@ -43,48 +43,45 @@ export const CommentsPlugin = Extension.create({ ) dispatch(tr) - return true + return true; }, removeComment: ({ commentId, importedId }) => ({ tr, dispatch, state }) => { - tr.setMeta(CommentsPluginKey, { event: 'deleted' }) - removeCommentsById({ commentId, importedId, state, tr, dispatch }) + tr.setMeta(CommentsPluginKey, { event: 'deleted' }); + removeCommentsById({ commentId, importedId, state, tr, dispatch }); }, setActiveComment: - ({ commentId, importedId }) => + ({ commentId }) => ({ tr, dispatch }) => { - let activeThreadId = importedId - if (importedId === undefined || importedId === null) { - activeThreadId = commentId - } - tr.setMeta(CommentsPluginKey, { type: 'setActiveComment', activeThreadId }) - return true + let activeThreadId = commentId; + tr.setMeta(CommentsPluginKey, { type: 'setActiveComment', activeThreadId }); + return true; }, setCommentInternal: - ({ commentId, importedId, isInternal }) => + ({ commentId, isInternal }) => ({ tr, dispatch, state }) => { - const { doc } = state - let foundStartNode - let foundPos + const { doc } = state; + let foundStartNode; + let foundPos; // Find the commentRangeStart node that matches the comment ID tr.setMeta(CommentsPluginKey, { event: 'update' }) doc.descendants((node, pos) => { - if (foundStartNode) return + if (foundStartNode) return; - const { marks = [] } = node - const commentMark = marks.find((mark) => mark.type.name === CommentMarkName) + const { marks = [] } = node; + const commentMark = marks.find((mark) => mark.type.name === CommentMarkName); if (commentMark) { - const { attrs } = commentMark - const wid = attrs.commentId || attrs.importedId - if (wid == commentId || wid == importedId) { - foundStartNode = node - foundPos = pos + const { attrs } = commentMark; + const wid = attrs.commentId; + if (wid === commentId) { + foundStartNode = node; + foundPos = pos; } } }) @@ -108,10 +105,10 @@ export const CommentsPlugin = Extension.create({ }, resolveComment: - ({ commentId, importedId }) => + ({ commentId }) => ({ tr, dispatch, state }) => { tr.setMeta(CommentsPluginKey, { event: 'update' }) - removeCommentsById({ commentId, importedId, state, tr, dispatch }) + removeCommentsById({ commentId, state, tr, dispatch }) }, } }, @@ -119,6 +116,7 @@ export const CommentsPlugin = Extension.create({ addPmPlugins() { const editor = this.editor let shouldUpdate; + let activeThreadId; const commentsPlugin = new Plugin({ key: CommentsPluginKey, @@ -164,26 +162,24 @@ export const CommentsPlugin = Extension.create({ ); }; + // Check for changes in the actively selected comment const trChangedActiveComment = meta?.type === 'setActiveComment'; if ((!tr.docChanged && tr.selectionSet) || trChangedActiveComment) { - const { selection } = tr; - let activeThreadId = getActiveCommentId(newEditorState.doc, selection); + const currentActiveThread = getActiveCommentId(newEditorState.doc, selection); if (trChangedActiveComment) activeThreadId = meta.activeThreadId; - const previousSelectionId = pluginState.activeThreadId; - if (previousSelectionId !== activeThreadId || trChangedActiveComment) { - pluginState.activeThreadId = activeThreadId; + const previousSelectionId = activeThreadId; + if (previousSelectionId !== currentActiveThread) { + activeThreadId = currentActiveThread; const update = { type: comments_module_events.SELECTED, activeCommentId: activeThreadId ? activeThreadId : null }; + shouldUpdate = true; editor.emit('commentsUpdate', update); - pluginState.changedActiveThread = true; - } else { - pluginState.changedActiveThread = false; - } + }; }; const { allCommentIds, allCommentPositions } = pluginState; @@ -207,11 +203,8 @@ export const CommentsPlugin = Extension.create({ update(view, prevState) { const { state } = view const { doc, tr } = state - - const pluginState = CommentsPluginKey.getState(state) - const { activeThreadId} = pluginState; - if (prevDoc && prevDoc.eq(doc) || !shouldUpdate) return; + if (prevDoc && prevDoc.eq(doc) && !shouldUpdate) return; prevDoc = doc; const decorations = [] @@ -237,6 +230,7 @@ export const CommentsPlugin = Extension.create({ }); const isInternal = attrs.internal; + const color = getHighlightColor({ activeThreadId, threadId, isInternal, editor }); const deco = Decoration.inline(pos, pos + node.nodeSize, { style: `background-color: ${color}`, @@ -263,6 +257,7 @@ export const CommentsPlugin = Extension.create({ const decorationSet = DecorationSet.create(doc, decorations) // Compare new decorations with the old state to avoid infinite loop + const pluginState = CommentsPluginKey.getState(state) const oldDecorations = pluginState.decorations // We only dispatch if something actually changed @@ -280,6 +275,7 @@ export const CommentsPlugin = Extension.create({ // Remember the new decorations for next time prevDecorations = decorationSet + shouldUpdate = false; }, } }, diff --git a/packages/superdoc/src/stores/comments-store.js b/packages/superdoc/src/stores/comments-store.js index 49ab02e529..b3fcc746ad 100644 --- a/packages/superdoc/src/stores/comments-store.js +++ b/packages/superdoc/src/stores/comments-store.js @@ -445,14 +445,13 @@ export const useCommentsStore = defineStore('comments', () => { const document = superdocStore.getDocument(documentId); if (__IS_DEBUG__) console.debug('[processLoadedDocxComments] processing comments...', comments); - + comments.forEach((comment) => { const importedName = `${comment.creatorName.replace('(imported)', '')} (imported)` const newComment = useComment({ fileId: documentId, fileType: document.type, - importedId: comment.importedId ? Number(comment.importedId): null, - commentId: comment.id, + commentId: comment.commentId, isInternal: false, parentCommentId: comment.parentCommentId, importedAuthor: {