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
1 change: 1 addition & 0 deletions packages/super-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
"@types/mdast": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/uuid": "catalog:",
"@vitejs/plugin-vue": "catalog:",
"@vue/test-utils": "catalog:",
"canvas": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
import { TrackDeleteMarkName, TrackFormatMarkName, TrackedFormatMarkNames } from '../constants.js';
import { v4 as uuidv4 } from 'uuid';
import { TrackChangesBasePluginKey } from '../plugins/trackChangesBasePlugin.js';
Expand All @@ -18,11 +19,13 @@ import { getLiveInlineMarksInRange } from './getLiveInlineMarksInRange.js';
* @param {import('prosemirror-transform').AddMarkStep} options.step Step.
* @param {import('prosemirror-state').Transaction} options.newTr New transaction.
* @param {import('prosemirror-model').Node} options.doc Doc.
* @param {object} options.user User object ({ name, email }).
* @param {import('../../../core/types/EditorConfig.js').User} options.user User object ({ name, email }).
* @param {string} options.date Date.
*/
export const addMarkStep = ({ state, step, newTr, doc, user, date }) => {
/** @type {{ formatMark?: import('prosemirror-model').Mark, step?: import('prosemirror-transform').AddMarkStep }} */
const meta = {};
/** @type {string | null} */
let sharedWid = null;

doc.nodesBetween(step.from, step.to, (node, pos) => {
Expand All @@ -37,6 +40,7 @@ export const addMarkStep = ({ state, step, newTr, doc, user, date }) => {
const rangeFrom = Math.max(step.from, pos);
const rangeTo = Math.min(step.to, pos + node.nodeSize);

/** @type {import('prosemirror-model').Mark[]} */
const liveMarks = getLiveInlineMarksInRange({
doc: newTr.doc,
from: rangeFrom,
Expand All @@ -51,24 +55,28 @@ export const addMarkStep = ({ state, step, newTr, doc, user, date }) => {
if (TrackedFormatMarkNames.includes(step.mark.type.name) && !hasMatchingMark(liveMarks, step.mark)) {
const formatChangeMark = liveMarks.find((mark) => mark.type.name === TrackFormatMarkName);

/** @type {{ type?: string, attrs?: Record<string, unknown> }[]} */
let after = [];
/** @type {{ type?: string, attrs?: Record<string, unknown> }[]} */
let before = [];

if (formatChangeMark) {
let foundBefore = formatChangeMark.attrs.before.find((mark) =>
markSnapshotMatchesStepMark(mark, step.mark, true),
const beforeSnapshots = /** @type {{ type?: string, attrs?: Record<string, unknown> }[]} */ (
formatChangeMark.attrs.before || []
);
const afterSnapshots = /** @type {{ type?: string, attrs?: Record<string, unknown> }[]} */ (
formatChangeMark.attrs.after || []
);
let foundBefore = beforeSnapshots.find((mark) => markSnapshotMatchesStepMark(mark, step.mark, true));

if (foundBefore) {
before = [
...formatChangeMark.attrs.before.filter((mark) => !markSnapshotMatchesStepMark(mark, step.mark, true)),
];
before = [...beforeSnapshots.filter((mark) => !markSnapshotMatchesStepMark(mark, step.mark, true))];
// The step restores the original mark for this type — remove the
// corresponding "after" entry since the change has been reverted.
after = formatChangeMark.attrs.after.filter((mark) => getTypeName(mark) !== step.mark.type.name);
after = afterSnapshots.filter((mark) => getTypeName(mark) !== step.mark.type.name);
} else {
before = [...formatChangeMark.attrs.before];
after = upsertMarkSnapshotByType(formatChangeMark.attrs.after, {
before = [...beforeSnapshots];
after = upsertMarkSnapshotByType(afterSnapshots, {
type: step.mark.type.name,
attrs: { ...step.mark.attrs },
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
import { Mapping, ReplaceStep } from 'prosemirror-transform';
import { Slice } from 'prosemirror-model';
import { v4 as uuidv4 } from 'uuid';
Expand All @@ -10,28 +11,37 @@ import { findTrackedMarkBetween } from './findTrackedMarkBetween.js';
* @param {import('prosemirror-state').Transaction} options.tr Transaction.
* @param {number} options.from From position.
* @param {number} options.to To position.
* @param {object} options.user User object ({ name, email }).
* @param {import('../../../core/types/EditorConfig.js').User} options.user User object ({ name, email }).
* @param {string} options.date Date.
* @param {string} [options.id] Optional ID to use (for replace operations where insertion and deletion share the same ID).
* @returns {Object} Deletion map and deletionMark
* @returns {{ deletionMark: import('prosemirror-model').Mark, deletionMap: Mapping, nodes: import('prosemirror-model').Node[] }} Deletion map and deletion mark.
*/
export const markDeletion = ({ tr, from, to, user, date, id: providedId }) => {
/**
* @param {unknown} value
*/
const normalizeEmail = (value) => (typeof value === 'string' ? value.trim().toLowerCase() : '');
const userEmail = normalizeEmail(user?.email);
/**
* @param {import('prosemirror-model').Mark | null | undefined} mark
*/
const isOwnInsertion = (mark) => {
const authorEmail = normalizeEmail(mark?.attrs?.authorEmail);
// Word imports often omit authorEmail, treat missing as "own" to allow deletion.
if (!authorEmail || !userEmail) return true;
return authorEmail === userEmail;
};

let trackedMark = findTrackedMarkBetween({
tr,
from,
to,
markName: TrackDeleteMarkName,
attrs: { authorEmail: user.email || '' },
});
const trackedMark =
/** @type {{ from: number, to: number, mark: import('prosemirror-model').Mark } | null | undefined} */ (
findTrackedMarkBetween({
tr,
from,
to,
markName: TrackDeleteMarkName,
attrs: { authorEmail: user.email || '' },
})
);

let id;
if (providedId) {
Expand Down Expand Up @@ -59,6 +69,7 @@ export const markDeletion = ({ tr, from, to, user, date, id: providedId }) => {
// - Own insertions are removed (collapsed).
// - Existing deletions are reassigned to the new deletion mark ID.
// - Non-deleted inline nodes are marked as deleted.
/** @type {import('prosemirror-model').Node[]} */
let nodes = [];
tr.doc.nodesBetween(from, to, (node, pos) => {
if (node.type.name.includes('table')) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
import { v4 as uuidv4 } from 'uuid';
import { TrackInsertMarkName, TrackDeleteMarkName } from '../constants.js';
import { findTrackedMarkBetween } from './findTrackedMarkBetween.js';
Expand All @@ -8,7 +9,7 @@ import { findTrackedMarkBetween } from './findTrackedMarkBetween.js';
* @param {import('prosemirror-state').Transaction} options.tr Transaction.
* @param {number} options.from From position.
* @param {number} options.to To position.
* @param {object} options.user User object ({ name, email }).
* @param {import('../../../core/types/EditorConfig.js').User} options.user User object ({ name, email }).
* @param {string} options.date Date.
* @param {string} [options.id] Optional ID to use (for replace operations where insertion and deletion share the same ID).
* @returns {import('prosemirror-model').Mark} Insertion mark.
Expand All @@ -17,13 +18,16 @@ export const markInsertion = ({ tr, from, to, user, date, id: providedId }) => {
tr.removeMark(from, to, tr.doc.type.schema.marks[TrackDeleteMarkName]);
tr.removeMark(from, to, tr.doc.type.schema.marks[TrackInsertMarkName]);

let trackedMark = findTrackedMarkBetween({
tr,
from,
to,
markName: TrackInsertMarkName,
attrs: { authorEmail: user.email || '' },
});
const trackedMark =
/** @type {{ from: number, to: number, mark: import('prosemirror-model').Mark } | null | undefined} */ (
findTrackedMarkBetween({
tr,
from,
to,
markName: TrackInsertMarkName,
attrs: { authorEmail: user.email || '' },
})
);

let id;
if (providedId) {
Expand Down
Loading
Loading