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 @@ -100,8 +100,9 @@ export const getCommentDefinition = (comment, commentId, allComments) => {
'w15:paraId': comment.commentParaId,
'custom:internalId': comment.commentId || comment.internalId,
'custom:trackedChange': comment.trackedChange,
'custom:trackedChangeText': comment.trackedChangeText,
'custom:trackedChangeText': comment.trackedChangeText || null,
'custom:trackedChangeType': comment.trackedChangeType,
'custom:trackedDeletedText': comment.deletedText || null,
};

// Add the w15:paraIdParent attribute if the comment has a parent
Expand Down Expand Up @@ -173,6 +174,7 @@ export const updateCommentsXml = (commentDefs = [], commentsXml) => {
'custom:trackedChange': commentDef.attributes['custom:trackedChange'],
'custom:trackedChangeText': commentDef.attributes['custom:trackedChangeText'],
'custom:trackedChangeType': commentDef.attributes['custom:trackedChangeType'],
'custom:trackedDeletedText': commentDef.attributes['custom:trackedDeletedText'],
'xmlns:custom': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ export function importCommentData({ docx }) {
const createdDate = attributes['w:date'];
const internalId = attributes['custom:internalId'];
const trackedChange = attributes['custom:trackedChange'] === 'true';
const trackedChangeText = attributes['custom:trackedChangeText'];
const trackedChangeType = attributes['custom:trackedChangeType'];
const trackedChangeText = attributes['custom:trackedChangeText'] !== 'null'
? attributes['custom:trackedChangeText']
: null;
const trackedDeletedText = attributes['custom:trackedDeletedText'] !== 'null'
? attributes['custom:trackedDeletedText']
: null;

const date = new Date(createdDate);
const unixTimestampMs = date.getTime();
Expand Down Expand Up @@ -59,6 +64,7 @@ export function importCommentData({ docx }) {
trackedChange,
trackedChangeText,
trackedChangeType,
trackedDeletedText,
};
});

Expand Down
103 changes: 73 additions & 30 deletions packages/super-editor/src/extensions/comment/comments-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ const getActiveCommentId = (doc, selection) => {
// If we have a tracked change, we can return it right away
const trackedChangeNode = getTrackedChangeNode(nodeAtPos);
if (trackedChangeNode) {
return trackedChangeNode.attrs.wid;
return trackedChangeNode.attrs.id;
}

// Otherwise, we need to check for comment nodes
Expand Down Expand Up @@ -408,19 +408,30 @@ const getActiveCommentId = (doc, selection) => {
const getTrackedChangeNode = (node) => {
if (!node) return;
const nodeMarks = node.marks;
const trackedChangeMark = nodeMarks?.find((mark) => mark.type.name === TrackInsertMarkName);
const trackedInsertMark = nodeMarks?.find((mark) => mark.type.name === TrackInsertMarkName);
const trackedDeleteMark = nodeMarks?.find((mark) => mark.type.name === TrackDeleteMarkName);
const trackedFormatMark = nodeMarks?.find((mark) => mark.type.name === TrackFormatMarkName);
return trackedChangeMark || trackedDeleteMark || trackedFormatMark;
return trackedInsertMark || trackedDeleteMark || trackedFormatMark;
};

const handleTrackedChangeTransaction = (trackedChangeMeta, trackedChanges, newEditorState, editor) => {
const { deletionMark, insertedMark, formatMark, deletionNodes } = trackedChangeMeta;
if (!deletionMark && !insertedMark && !formatMark) return;
const {
insertedMark,
deletionMark,
formatMark,
deletionNodes,
} = trackedChangeMeta;

if (!insertedMark && !deletionMark && !formatMark) {
return;
}

const newTrackedChanges = { ...trackedChanges };
let id = insertedMark?.attrs?.id || deletionMark?.attrs?.id || formatMark?.attrs?.id;
if (!id) return trackedChanges;

if (!id) {
return trackedChanges;
}

// Maintain a map of tracked changes with their inserted/deleted ids
let isNewChange = false;
Expand All @@ -446,6 +457,7 @@ const handleTrackedChangeTransaction = (trackedChangeMeta, trackedChanges, newEd
};
});
}

const emitParams = createOrUpdateTrackedChangeComment({
documentId: editor.options.documentId,
event: isNewChange ? 'add' : 'update',
Expand All @@ -455,7 +467,7 @@ const handleTrackedChangeTransaction = (trackedChangeMeta, trackedChanges, newEd
formatMark,
},
deletionNodes,
nodes: nodes,
nodes,
newEditorState,
});

Expand All @@ -464,21 +476,47 @@ const handleTrackedChangeTransaction = (trackedChangeMeta, trackedChanges, newEd
return newTrackedChanges;
};

const getTrackedChangeText = ({ node, mark, trackedChangeType, isDeletionInsertion, deletionNodes = [] }) => {
const deletionText = deletionNodes.length ? deletionNodes[0].text : null;
const getTrackedChangeText = ({
state,
node,
mark,
marks,
trackedChangeType,
isDeletionInsertion,
deletionNodes = [],
}) => {
let trackedChangeText = '';
let deletionText = '';

let nextNode = null; //
let trackedChangeText = isDeletionInsertion ? nextNode?.text : node?.text;
if (!trackedChangeText) trackedChangeText = '';
if (trackedChangeType === TrackInsertMarkName) {
trackedChangeText = node?.text ?? '';
}

// If this is a format change, let's get the string of what changes were made
const isFormatChange = trackedChangeType === TrackFormatMarkName;
if (isFormatChange) trackedChangeText = translateFormatChangesToEnglish(mark.attrs)
if (trackedChangeType === TrackFormatMarkName) {
trackedChangeText = translateFormatChangesToEnglish(mark.attrs);
}

if (trackedChangeType === TrackDeleteMarkName || isDeletionInsertion) {
deletionText = node?.text ?? '';

if (isDeletionInsertion) {
let { id } = marks.deletionMark.attrs;
let deletionNode = findNode(state.doc, (node) => {
const { marks = [] } = node;
const changeMarks = marks.filter((mark) => TRACK_CHANGE_MARKS.includes(mark.type.name));
if (!changeMarks.length) return false;
const hasMatchingId = changeMarks.find((mark) => mark.attrs.id === id);
if (hasMatchingId) return true;
});
deletionText = deletionNode?.node.text ?? '';
}
}

return {
deletionText,
trackedChangeText,
}
};
};

const createOrUpdateTrackedChangeComment = ({ event, marks, deletionNodes, nodes, newEditorState, documentId }) => {
Expand All @@ -487,39 +525,34 @@ const createOrUpdateTrackedChangeComment = ({ event, marks, deletionNodes, nodes

const { name: trackedChangeType } = type;
const { author, authorEmail, date } = attrs;

let id = attrs.id;

// if (!nodes.length) {
// return;
// }
const id = attrs.id;

const node = nodes[0];
const nextTrackedNode = null; //
const isDeletionInsertion = (
trackedChangeType === TrackDeleteMarkName && nextTrackedNode?.type?.name === TrackInsertMarkName
);
const isDeletionInsertion = !!(marks.insertedMark && marks.deletionMark);

let existingNode;
newEditorState.doc.descendants((node, pos) => {
const { marks = [] } = node;
const changeMarks = marks.filter((mark) => TRACK_CHANGE_MARKS.includes(mark.type.name));
if (!changeMarks.length) return;

const hasMatchingId = changeMarks.find((mark) => mark.attrs.id === id);
if (hasMatchingId) existingNode = node;
if (!existingNode) return false;
if (existingNode) return false;
});

const { deletionText, trackedChangeText } = getTrackedChangeText({
state: newEditorState,
node: existingNode || node,
mark: trackedMark,
marks,
trackedChangeType,
isDeletionInsertion,
deletionNodes
deletionNodes,
});

if (!deletionText && !trackedChangeText) return;
if (!deletionText && !trackedChangeText) {
return;
}

const params = {
event: comments_module_events.ADD,
Expand All @@ -536,5 +569,15 @@ const createOrUpdateTrackedChangeComment = ({ event, marks, deletionNodes, nodes

if (event === 'add') params.event = comments_module_events.ADD;
else if (event === 'update') params.event = comments_module_events.UPDATE;

return params;
};
};

function findNode(node, predicate) {
let found = null;
node.descendants((node, pos) => {
if (predicate(node)) found = { node, pos };
if (found) return false;
});
return found;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Node } from 'prosemirror-model';
import { TrackDeleteMarkName, TrackFormatMarkName } from '../constants.js';
import { v4 as uuidv4 } from 'uuid';
import { objectIncludes } from '@core/utilities/objectIncludes.js';
import { TrackChangesBasePluginKey } from '../plugins/index.js';
import { TrackChangesBasePluginKey } from '../plugins/trackChangesBasePlugin.js';

/**
* Add mark step.
Expand All @@ -19,6 +19,7 @@ import { TrackChangesBasePluginKey } from '../plugins/index.js';
*/
export const addMarkStep = ({ state, tr, step, newTr, map, doc, user, date }) => {
const meta = {};

doc.nodesBetween(step.from, step.to, (node, pos) => {
if (!node.isInline) {
return;
Expand Down Expand Up @@ -91,8 +92,10 @@ export const addMarkStep = ({ state, tr, step, newTr, map, doc, user, date }) =>
step.to, // Math.min(step.to, pos + node.nodeSize),
newFormatMark,
);

meta.formatMark = newFormatMark;
meta.step = step;

newTr.setMeta(TrackChangesBasePluginKey, meta);
} else if (formatChangeMark) {
newTr.removeMark(Math.max(step.from, pos), Math.min(step.to, pos + node.nodeSize), formatChangeMark);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Mapping, RemoveMarkStep } from 'prosemirror-transform';
import { Node } from 'prosemirror-model';
import { v4 as uuidv4 } from 'uuid';
import { TrackDeleteMarkName, TrackFormatMarkName } from '../constants.js';
import { TrackChangesBasePluginKey } from '../plugins/trackChangesBasePlugin.js';

/**
* Remove mark step.
Expand All @@ -16,6 +17,8 @@ import { TrackDeleteMarkName, TrackFormatMarkName } from '../constants.js';
* @param {string} options.date Date.
*/
export const removeMarkStep = ({ state, tr, step, newTr, map, doc, user, date }) => {
const meta = {};

doc.nodesBetween(step.from, step.to, (node, pos) => {
if (!node.isInline) {
return true;
Expand Down Expand Up @@ -62,18 +65,25 @@ export const removeMarkStep = ({ state, tr, step, newTr, map, doc, user, date })
}

if (after.length || before.length) {
const newFormatMark = state.schema.marks[TrackFormatMarkName].create({
id: uuidv4(),
author: user.name,
authorEmail: user.email,
date,
before,
after,
});

newTr.addMark(
Math.max(step.from, pos),
Math.min(step.to, pos + node.nodeSize),
state.schema.marks[TrackFormatMarkName].create({
id: uuidv4(),
author: user.name,
authorEmail: user.email,
date,
before,
after,
}),
newFormatMark,
);

meta.formatMark = newFormatMark;
meta.step = step;

newTr.setMeta(TrackChangesBasePluginKey, meta);
} else if (formatChangeMark) {
newTr.removeMark(Math.max(step.from, pos), Math.min(step.to, pos + node.nodeSize), formatChangeMark);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ export const replaceStep = ({ state, tr, step, newTr, map, doc, user, date, orig
const mirrorIndex = map.maps.length - 1;
map.appendMap(condensedStep.getMap(), mirrorIndex);

// Prepare meta for the transaction
meta.insertedMark = insertedMark;
meta.step = condensedStep;
if (newStep.from !== mappedNewStepTo) {
meta.insertedMark = insertedMark;
meta.step = condensedStep;
}

if (!newTr.selection.eq(trTemp.selection)) {
newTr.setSelection(trTemp.selection);
Expand All @@ -78,8 +79,10 @@ export const replaceStep = ({ state, tr, step, newTr, map, doc, user, date, orig
user,
date,
});

meta.deletionNodes = deletionNodes;
meta.deletionMark = deletionMark;

map.appendMapping(deletionMap);
}

Expand Down
15 changes: 10 additions & 5 deletions packages/superdoc/src/stores/comments-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,22 @@ export const useCommentsStore = defineStore('comments', () => {
}
});

// If this is a new tracked change, add it to our comments
if (event === 'add') {
// If this is a new tracked change, add it to our comments
addComment({ superdoc, comment });
}

// If we have an update event, simply update the composable comment
else if (event === 'update') {
} else if (event === 'update') {
// If we have an update event, simply update the composable comment
const existingTrackedChange = commentsList.value.find(
(comment) => comment.commentId === changeId
);
if (!existingTrackedChange) return;

existingTrackedChange.trackedChangeText = trackedChangeText;

if (deletedText) {
existingTrackedChange.deletedText = deletedText;
}

const emitData = {
type: COMMENT_EVENTS.UPDATE,
comment: existingTrackedChange.getValues(),
Expand Down Expand Up @@ -474,6 +478,7 @@ export const useCommentsStore = defineStore('comments', () => {
trackedChange: comment.trackedChange || false,
trackedChangeText: comment.trackedChangeText,
trackedChangeType: comment.trackedChangeType,
deletedText: comment.trackedDeletedText,
});

addComment({ superdoc, comment: newComment });
Expand Down