Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand All @@ -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,
Expand Down Expand Up @@ -88,7 +88,7 @@ const generateCommentsWithExtendedData = ({ docx, comments }) => {

const newComment = {
...comment,
commentId: superdocCommentId,
commentId: superdocCommentId || uuidv4(),
isDone,
parentCommentId: parentComment?.id,
};
Expand Down
41 changes: 23 additions & 18 deletions packages/super-editor/src/extensions/comment/comments-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand All @@ -28,21 +27,19 @@ 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;
const commentMark = marks.find((mark) => mark.type.name === CommentMarkName);

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 });
}
}
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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,
};
}
Expand All @@ -163,27 +159,36 @@ 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 });
}

// 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,
};
Expand Down
70 changes: 33 additions & 37 deletions packages/super-editor/src/extensions/comment/comments-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
})
Expand All @@ -108,17 +105,18 @@ 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 })
},
}
},

addPmPlugins() {
const editor = this.editor
let shouldUpdate;
let activeThreadId;

const commentsPlugin = new Plugin({
key: CommentsPluginKey,
Expand Down Expand Up @@ -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;
Expand All @@ -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 = []
Expand All @@ -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}`,
Expand All @@ -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
Expand All @@ -280,6 +275,7 @@ export const CommentsPlugin = Extension.create({

// Remember the new decorations for next time
prevDecorations = decorationSet
shouldUpdate = false;
},
}
},
Expand Down
5 changes: 2 additions & 3 deletions packages/superdoc/src/stores/comments-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading