@pixerate/editor 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -11
- package/dist/agent/index.cjs +8 -8
- package/dist/agent/index.d.cts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +2 -2
- package/dist/canvas/index.cjs +1269 -13
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +432 -6
- package/dist/canvas/index.d.ts +432 -6
- package/dist/canvas/index.js +1264 -8
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-U6NDQFOK.cjs → chunk-6M3BCTBD.cjs} +427 -138
- package/dist/chunk-6M3BCTBD.cjs.map +1 -0
- package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
- package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
- package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
- package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
- package/dist/{chunk-A7DKIA72.js → chunk-DENSCKFC.js} +642 -69
- package/dist/chunk-DENSCKFC.js.map +1 -0
- package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
- package/dist/chunk-GVSMRPHB.cjs.map +1 -0
- package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
- package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
- package/dist/chunk-MJZBNWG7.js.map +1 -0
- package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
- package/dist/chunk-QGHGJ56O.js.map +1 -0
- package/dist/{chunk-GVW4P7BG.js → chunk-RU52I7QO.js} +305 -16
- package/dist/chunk-RU52I7QO.js.map +1 -0
- package/dist/{chunk-JYRIOFEN.cjs → chunk-SAZRENEZ.cjs} +647 -74
- package/dist/chunk-SAZRENEZ.cjs.map +1 -0
- package/dist/{controller-OUdxPY-X.d.ts → controller-B7GIybc4.d.cts} +139 -7
- package/dist/{controller-OUdxPY-X.d.cts → controller-B7GIybc4.d.ts} +139 -7
- package/dist/extensions/index.cjs +14 -4
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +3 -3
- package/dist/extensions/index.d.ts +3 -3
- package/dist/extensions/index.js +13 -3
- package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
- package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
- package/dist/index.cjs +250 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -8
- package/dist/index.d.ts +294 -8
- package/dist/index.js +241 -7
- package/dist/index.js.map +1 -1
- package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
- package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
- package/dist/serializers/index.cjs +3 -3
- package/dist/serializers/index.d.cts +1 -1
- package/dist/serializers/index.d.ts +1 -1
- package/dist/serializers/index.js +2 -2
- package/dist/spreadsheet/index.cjs +7 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +33 -4
- package/dist/spreadsheet/index.d.ts +33 -4
- package/dist/spreadsheet/index.js +6 -2
- package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
- package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
- package/dist/{types-DMe0vFB1.d.cts → types-CJ4U-HHI.d.cts} +1 -1
- package/dist/{types-DMe0vFB1.d.ts → types-CJ4U-HHI.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-3TYMNULT.js.map +0 -1
- package/dist/chunk-4QPLEUL2.cjs.map +0 -1
- package/dist/chunk-A7DKIA72.js.map +0 -1
- package/dist/chunk-CYFJCKC4.js.map +0 -1
- package/dist/chunk-GVW4P7BG.js.map +0 -1
- package/dist/chunk-JYRIOFEN.cjs.map +0 -1
- package/dist/chunk-TVRC4KVT.cjs.map +0 -1
- package/dist/chunk-U6NDQFOK.cjs.map +0 -1
- /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
|
@@ -245,11 +245,32 @@ import {
|
|
|
245
245
|
} from "@tiptap/pm/state";
|
|
246
246
|
import {
|
|
247
247
|
Fragment,
|
|
248
|
+
Mark as ProsemirrorMark,
|
|
249
|
+
NodeRange,
|
|
248
250
|
Slice
|
|
249
251
|
} from "@tiptap/pm/model";
|
|
250
|
-
import {
|
|
252
|
+
import {
|
|
253
|
+
AddMarkStep,
|
|
254
|
+
AttrStep,
|
|
255
|
+
Mapping,
|
|
256
|
+
RemoveMarkStep,
|
|
257
|
+
ReplaceAroundStep,
|
|
258
|
+
ReplaceStep,
|
|
259
|
+
Transform,
|
|
260
|
+
canJoin,
|
|
261
|
+
liftTarget
|
|
262
|
+
} from "@tiptap/pm/transform";
|
|
251
263
|
var INSERTION_MARK_NAME = "insertion";
|
|
252
264
|
var DELETION_MARK_NAME = "deletion";
|
|
265
|
+
var FORMAT_CHANGE_MARK_NAME = "formatChange";
|
|
266
|
+
var BLOCK_CHANGE_ATTRIBUTE = "blockChange";
|
|
267
|
+
var REVIEW_MARK_NAMES = /* @__PURE__ */ new Set([
|
|
268
|
+
INSERTION_MARK_NAME,
|
|
269
|
+
DELETION_MARK_NAME,
|
|
270
|
+
FORMAT_CHANGE_MARK_NAME,
|
|
271
|
+
"comment",
|
|
272
|
+
"bookmark"
|
|
273
|
+
]);
|
|
253
274
|
var TRACK_CHANGES_SKIP_META = "trackChangesSkip";
|
|
254
275
|
var TrackChangesStore = class {
|
|
255
276
|
snapshot;
|
|
@@ -281,6 +302,14 @@ var trackChangesPluginKey = new PluginKey("trackChanges");
|
|
|
281
302
|
function changeAttributes(mark) {
|
|
282
303
|
return mark.attrs;
|
|
283
304
|
}
|
|
305
|
+
function parseJSON(value, fallback) {
|
|
306
|
+
if (!value) return fallback;
|
|
307
|
+
try {
|
|
308
|
+
return JSON.parse(value);
|
|
309
|
+
} catch {
|
|
310
|
+
return fallback;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
284
313
|
var sharedAttributes = () => ({
|
|
285
314
|
changeId: {
|
|
286
315
|
default: null,
|
|
@@ -367,6 +396,172 @@ var TrackedDeletion = Mark.create({
|
|
|
367
396
|
];
|
|
368
397
|
}
|
|
369
398
|
});
|
|
399
|
+
var TrackedFormatChange = Mark.create({
|
|
400
|
+
name: FORMAT_CHANGE_MARK_NAME,
|
|
401
|
+
inclusive: false,
|
|
402
|
+
addOptions() {
|
|
403
|
+
return {
|
|
404
|
+
HTMLAttributes: {
|
|
405
|
+
class: "pixerate-format-change underline decoration-dotted decoration-2 decoration-sky-500 underline-offset-4"
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
addStorage() {
|
|
410
|
+
return { markdown: { serialize: { open: "", close: "" }, parse: {} } };
|
|
411
|
+
},
|
|
412
|
+
addAttributes() {
|
|
413
|
+
return {
|
|
414
|
+
...sharedAttributes(),
|
|
415
|
+
before: {
|
|
416
|
+
default: [],
|
|
417
|
+
parseHTML: (el) => parseJSON(el.getAttribute("data-format-before"), []),
|
|
418
|
+
renderHTML: (attrs) => ({ "data-format-before": JSON.stringify(attrs.before ?? []) })
|
|
419
|
+
},
|
|
420
|
+
after: {
|
|
421
|
+
default: [],
|
|
422
|
+
parseHTML: (el) => parseJSON(el.getAttribute("data-format-after"), []),
|
|
423
|
+
renderHTML: (attrs) => ({ "data-format-after": JSON.stringify(attrs.after ?? []) })
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
parseHTML() {
|
|
428
|
+
return [{ tag: 'span[data-change-type="format"]', priority: 100 }];
|
|
429
|
+
},
|
|
430
|
+
renderHTML({ HTMLAttributes }) {
|
|
431
|
+
return [
|
|
432
|
+
"span",
|
|
433
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
434
|
+
"data-change-type": "format"
|
|
435
|
+
}),
|
|
436
|
+
0
|
|
437
|
+
];
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
var MARK_LABELS = {
|
|
441
|
+
bold: "Bold",
|
|
442
|
+
italic: "Italic",
|
|
443
|
+
underline: "Underline",
|
|
444
|
+
strike: "Strikethrough",
|
|
445
|
+
code: "Code",
|
|
446
|
+
link: "Link",
|
|
447
|
+
highlight: "Highlight",
|
|
448
|
+
subscript: "Subscript",
|
|
449
|
+
superscript: "Superscript",
|
|
450
|
+
textStyle: "Text style"
|
|
451
|
+
};
|
|
452
|
+
var TEXT_STYLE_LABELS = {
|
|
453
|
+
fontSize: "Font size",
|
|
454
|
+
fontFamily: "Font",
|
|
455
|
+
color: "Color",
|
|
456
|
+
backgroundColor: "Background color",
|
|
457
|
+
lineHeight: "Line height"
|
|
458
|
+
};
|
|
459
|
+
var BLOCK_LABELS = {
|
|
460
|
+
paragraph: "Paragraph",
|
|
461
|
+
codeBlock: "Code block",
|
|
462
|
+
blockquote: "Quote",
|
|
463
|
+
bulletList: "Bulleted list",
|
|
464
|
+
orderedList: "Numbered list",
|
|
465
|
+
taskList: "Task list",
|
|
466
|
+
listItem: "List item",
|
|
467
|
+
taskItem: "Task item"
|
|
468
|
+
};
|
|
469
|
+
function humanize(name) {
|
|
470
|
+
const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").toLowerCase();
|
|
471
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
472
|
+
}
|
|
473
|
+
function stableStringify(value) {
|
|
474
|
+
if (value === void 0) return "null";
|
|
475
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
476
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
477
|
+
const obj = value;
|
|
478
|
+
return `{${Object.keys(obj).filter((k) => obj[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
479
|
+
}
|
|
480
|
+
var sameValue = (a, b) => stableStringify(a) === stableStringify(b);
|
|
481
|
+
function markLabels(mark, other) {
|
|
482
|
+
if (mark.type === "textStyle") {
|
|
483
|
+
const attrs = mark.attrs ?? {};
|
|
484
|
+
const otherAttrs = other?.attrs ?? {};
|
|
485
|
+
const keys = Array.from(/* @__PURE__ */ new Set([...Object.keys(attrs), ...Object.keys(otherAttrs)])).filter(
|
|
486
|
+
(key) => other ? !sameValue(attrs[key], otherAttrs[key]) : attrs[key] != null
|
|
487
|
+
);
|
|
488
|
+
return keys.length ? keys.map((key) => TEXT_STYLE_LABELS[key] ?? humanize(key)) : ["Text style"];
|
|
489
|
+
}
|
|
490
|
+
return [MARK_LABELS[mark.type] ?? humanize(mark.type)];
|
|
491
|
+
}
|
|
492
|
+
function diffFormatting(before, after) {
|
|
493
|
+
const summary = { added: [], removed: [], changed: [] };
|
|
494
|
+
const push = (list, labels) => labels.forEach((label) => {
|
|
495
|
+
if (!list.includes(label)) list.push(label);
|
|
496
|
+
});
|
|
497
|
+
after.forEach((mark) => {
|
|
498
|
+
const old = before.find((m) => m.type === mark.type);
|
|
499
|
+
if (!old) push(summary.added, markLabels(mark));
|
|
500
|
+
else if (!sameValue(old.attrs ?? {}, mark.attrs ?? {})) push(summary.changed, markLabels(mark, old));
|
|
501
|
+
});
|
|
502
|
+
before.forEach((mark) => {
|
|
503
|
+
if (!after.some((m) => m.type === mark.type)) push(summary.removed, markLabels(mark));
|
|
504
|
+
});
|
|
505
|
+
return summary;
|
|
506
|
+
}
|
|
507
|
+
function mergeSummary(target, source) {
|
|
508
|
+
["added", "removed", "changed"].forEach(
|
|
509
|
+
(key) => source[key].forEach((label) => {
|
|
510
|
+
if (!target[key].includes(label)) target[key].push(label);
|
|
511
|
+
})
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
function describeFormatChange(summary) {
|
|
515
|
+
const parts = [];
|
|
516
|
+
if (summary.added.length) parts.push(`Formatted: ${summary.added.join(", ")}`);
|
|
517
|
+
if (summary.removed.length) parts.push(`Removed formatting: ${summary.removed.join(", ")}`);
|
|
518
|
+
if (summary.changed.length) parts.push(`Changed formatting: ${summary.changed.join(", ")}`);
|
|
519
|
+
return parts.join("; ") || "Formatted";
|
|
520
|
+
}
|
|
521
|
+
function blockLabel(type, attrs) {
|
|
522
|
+
if (type === "heading" && attrs?.level) return `Heading ${attrs.level}`;
|
|
523
|
+
return BLOCK_LABELS[type] ?? humanize(type);
|
|
524
|
+
}
|
|
525
|
+
var containerLabels = (wrappers) => wrappers.filter((w) => w.type !== "listItem" && w.type !== "taskItem").map((w) => blockLabel(w.type, w.attrs));
|
|
526
|
+
var isPrefix = (a, b) => a.every((label, i) => b[i] === label);
|
|
527
|
+
function describeBlockChange(previous, current, ignoredAttributes = ["id"]) {
|
|
528
|
+
const parts = [];
|
|
529
|
+
const before = blockLabel(previous.type, previous.attrs);
|
|
530
|
+
const after = blockLabel(current.type, current.attrs);
|
|
531
|
+
if (before !== after) {
|
|
532
|
+
parts.push(`Changed ${before} to ${after}`);
|
|
533
|
+
} else {
|
|
534
|
+
const keys = Array.from(
|
|
535
|
+
/* @__PURE__ */ new Set([...Object.keys(previous.attrs ?? {}), ...Object.keys(current.attrs ?? {})])
|
|
536
|
+
).filter(
|
|
537
|
+
(key) => key !== BLOCK_CHANGE_ATTRIBUTE && !ignoredAttributes.includes(key) && !(current.type === "heading" && key === "level") && !sameValue(previous.attrs?.[key], current.attrs?.[key])
|
|
538
|
+
);
|
|
539
|
+
keys.forEach((key) => {
|
|
540
|
+
if (key === "textAlign") {
|
|
541
|
+
parts.push(`Changed alignment to ${humanize(String(current.attrs?.textAlign ?? "default"))}`);
|
|
542
|
+
} else {
|
|
543
|
+
parts.push(`Changed ${humanize(key).toLowerCase()} of ${after}`);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
const oldTypes = previous.wrappers.map((w) => w.type).join("/");
|
|
548
|
+
const newTypes = current.wrappers.map((w) => w.type).join("/");
|
|
549
|
+
if (oldTypes !== newTypes) {
|
|
550
|
+
const was = containerLabels(previous.wrappers);
|
|
551
|
+
const now = containerLabels(current.wrappers);
|
|
552
|
+
const last = (list) => list[list.length - 1];
|
|
553
|
+
if (was.length === 0 && now.length > 0) parts.push(`Wrapped in ${last(now)}`);
|
|
554
|
+
else if (now.length === 0 && was.length > 0) parts.push(`Removed from ${last(was)}`);
|
|
555
|
+
else if (now.length > was.length && isPrefix(was, now)) parts.push(`Indented in ${last(now)}`);
|
|
556
|
+
else if (now.length < was.length && isPrefix(now, was)) parts.push(`Outdented from ${last(was)}`);
|
|
557
|
+
else if (was.length && now.length) parts.push(`Changed ${last(was)} to ${last(now)}`);
|
|
558
|
+
else parts.push("Changed nesting");
|
|
559
|
+
}
|
|
560
|
+
return parts.join("; ") || `Changed ${after}`;
|
|
561
|
+
}
|
|
562
|
+
function truncate(text, max = 40) {
|
|
563
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
564
|
+
}
|
|
370
565
|
function toDoc(source) {
|
|
371
566
|
if (!source) return null;
|
|
372
567
|
if (source.state?.doc) return source.state.doc;
|
|
@@ -374,55 +569,136 @@ function toDoc(source) {
|
|
|
374
569
|
if (typeof source.descendants === "function") return source;
|
|
375
570
|
return null;
|
|
376
571
|
}
|
|
572
|
+
function stripBlockChange(attrs) {
|
|
573
|
+
const copy = { ...attrs };
|
|
574
|
+
delete copy[BLOCK_CHANGE_ATTRIBUTE];
|
|
575
|
+
return copy;
|
|
576
|
+
}
|
|
577
|
+
function blockSnapshot(doc, pos) {
|
|
578
|
+
const $pos = doc.resolve(pos);
|
|
579
|
+
const node = $pos.nodeAfter;
|
|
580
|
+
const wrappers = [];
|
|
581
|
+
for (let depth = 1; depth <= $pos.depth; depth++) {
|
|
582
|
+
const wrapper = $pos.node(depth);
|
|
583
|
+
wrappers.push({ type: wrapper.type.name, attrs: stripBlockChange(wrapper.attrs) });
|
|
584
|
+
}
|
|
585
|
+
return { type: node.type.name, attrs: stripBlockChange(node.attrs), wrappers };
|
|
586
|
+
}
|
|
587
|
+
function sameBlock(a, b, ignored) {
|
|
588
|
+
if (a.type !== b.type) return false;
|
|
589
|
+
const filter = (attrs) => Object.fromEntries(
|
|
590
|
+
Object.entries(attrs ?? {}).filter(
|
|
591
|
+
([key]) => key !== BLOCK_CHANGE_ATTRIBUTE && !ignored.has(key)
|
|
592
|
+
)
|
|
593
|
+
);
|
|
594
|
+
if (!sameValue(filter(a.attrs), filter(b.attrs))) return false;
|
|
595
|
+
if (a.wrappers.length !== b.wrappers.length) return false;
|
|
596
|
+
return a.wrappers.every((w, i) => w.type === b.wrappers[i].type);
|
|
597
|
+
}
|
|
598
|
+
var isMarkChange = (type) => type === INSERTION_MARK_NAME || type === DELETION_MARK_NAME || type === FORMAT_CHANGE_MARK_NAME;
|
|
377
599
|
function getTrackedChanges(source) {
|
|
378
600
|
const doc = toDoc(source);
|
|
379
601
|
if (!doc) return [];
|
|
380
602
|
const byKey = /* @__PURE__ */ new Map();
|
|
603
|
+
const add = (key, type, attrs, from, to, text) => {
|
|
604
|
+
let change = byKey.get(key);
|
|
605
|
+
if (!change) {
|
|
606
|
+
change = {
|
|
607
|
+
id: attrs.changeId,
|
|
608
|
+
type,
|
|
609
|
+
author: {
|
|
610
|
+
id: attrs.authorId,
|
|
611
|
+
name: attrs.authorName,
|
|
612
|
+
...attrs.authorColor ? { color: attrs.authorColor } : {}
|
|
613
|
+
},
|
|
614
|
+
createdAt: attrs.createdAt,
|
|
615
|
+
from,
|
|
616
|
+
to,
|
|
617
|
+
ranges: [],
|
|
618
|
+
text: "",
|
|
619
|
+
description: ""
|
|
620
|
+
};
|
|
621
|
+
byKey.set(key, change);
|
|
622
|
+
}
|
|
623
|
+
const last = change.ranges[change.ranges.length - 1];
|
|
624
|
+
if (last && last.to === from) {
|
|
625
|
+
last.to = to;
|
|
626
|
+
} else {
|
|
627
|
+
change.ranges.push({ from, to });
|
|
628
|
+
}
|
|
629
|
+
change.to = to;
|
|
630
|
+
change.text += text;
|
|
631
|
+
return change;
|
|
632
|
+
};
|
|
381
633
|
doc.descendants((node, pos) => {
|
|
634
|
+
const block = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE];
|
|
635
|
+
if (block?.changeId && node.isTextblock) {
|
|
636
|
+
const change = add(
|
|
637
|
+
`block:${block.changeId}`,
|
|
638
|
+
"block",
|
|
639
|
+
block,
|
|
640
|
+
pos,
|
|
641
|
+
pos + node.nodeSize,
|
|
642
|
+
node.textContent
|
|
643
|
+
);
|
|
644
|
+
if (!change.block) {
|
|
645
|
+
change.block = { previous: block.previous, current: blockSnapshot(doc, pos) };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
382
648
|
if (!node.isInline) return;
|
|
383
649
|
node.marks.forEach((mark) => {
|
|
384
|
-
const
|
|
385
|
-
if (
|
|
650
|
+
const markType = mark.type.name;
|
|
651
|
+
if (!isMarkChange(markType)) return;
|
|
386
652
|
const attrs = changeAttributes(mark);
|
|
387
653
|
if (!attrs.changeId) return;
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
createdAt: attrs.createdAt,
|
|
402
|
-
from: pos,
|
|
403
|
-
to: end,
|
|
404
|
-
ranges: [],
|
|
405
|
-
text: ""
|
|
406
|
-
};
|
|
407
|
-
byKey.set(key, change);
|
|
654
|
+
const type = markType === FORMAT_CHANGE_MARK_NAME ? "format" : markType;
|
|
655
|
+
const change = add(
|
|
656
|
+
`${type}:${attrs.changeId}`,
|
|
657
|
+
type,
|
|
658
|
+
attrs,
|
|
659
|
+
pos,
|
|
660
|
+
pos + node.nodeSize,
|
|
661
|
+
node.isText ? node.text ?? "" : ""
|
|
662
|
+
);
|
|
663
|
+
if (type === "format") {
|
|
664
|
+
const summary = diffFormatting(mark.attrs.before ?? [], mark.attrs.after ?? []);
|
|
665
|
+
if (!change.format) change.format = { added: [], removed: [], changed: [] };
|
|
666
|
+
mergeSummary(change.format, summary);
|
|
408
667
|
}
|
|
409
|
-
const last = change.ranges[change.ranges.length - 1];
|
|
410
|
-
if (last && last.to === pos) {
|
|
411
|
-
last.to = end;
|
|
412
|
-
} else {
|
|
413
|
-
change.ranges.push({ from: pos, to: end });
|
|
414
|
-
}
|
|
415
|
-
change.to = end;
|
|
416
|
-
change.text += text;
|
|
417
668
|
});
|
|
418
669
|
});
|
|
419
|
-
|
|
670
|
+
const changes = Array.from(byKey.values());
|
|
671
|
+
changes.forEach((change) => {
|
|
672
|
+
switch (change.type) {
|
|
673
|
+
case "insertion":
|
|
674
|
+
change.description = change.text ? `Inserted "${truncate(change.text)}"` : "Inserted content";
|
|
675
|
+
break;
|
|
676
|
+
case "deletion":
|
|
677
|
+
change.description = change.text ? `Deleted "${truncate(change.text)}"` : "Deleted content";
|
|
678
|
+
break;
|
|
679
|
+
case "format":
|
|
680
|
+
change.description = describeFormatChange(change.format);
|
|
681
|
+
break;
|
|
682
|
+
case "block":
|
|
683
|
+
change.description = describeBlockChange(change.block.previous, change.block.current);
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
return changes.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
420
688
|
}
|
|
421
689
|
function getTrackedChangeAt(state, pos = state.selection.from, changes = getTrackedChanges(state.doc)) {
|
|
422
690
|
const { from, to } = state.selection;
|
|
423
|
-
const exact = changes.find((c) =>
|
|
691
|
+
const exact = changes.find((c) => {
|
|
692
|
+
const [start, end] = selectionRange(c);
|
|
693
|
+
return start === from && end === to;
|
|
694
|
+
});
|
|
424
695
|
if (exact) return exact;
|
|
425
|
-
|
|
696
|
+
const inline = changes.filter((c) => c.type !== "block");
|
|
697
|
+
const blocks = changes.filter((c) => c.type === "block");
|
|
698
|
+
return inline.find((c) => c.ranges.some((r) => r.from < pos && pos < r.to)) ?? inline.find((c) => c.ranges.some((r) => r.from <= pos && pos <= r.to)) ?? blocks.find((c) => c.ranges.some((r) => r.from < pos && pos < r.to)) ?? null;
|
|
699
|
+
}
|
|
700
|
+
function selectionRange(change) {
|
|
701
|
+
return change.type === "block" ? [change.from + 1, change.to - 1] : [change.from, change.to];
|
|
426
702
|
}
|
|
427
703
|
function isTrackChangesEnabled(source) {
|
|
428
704
|
const state = source?.state ?? source;
|
|
@@ -432,10 +708,225 @@ function isTrackChangesEnabled(source) {
|
|
|
432
708
|
function getTrackChangesStore(editor) {
|
|
433
709
|
return editor?.storage?.trackChanges?.store ?? null;
|
|
434
710
|
}
|
|
711
|
+
function marksFromJSON(schema, marks) {
|
|
712
|
+
const result = [];
|
|
713
|
+
(marks ?? []).forEach((json) => {
|
|
714
|
+
try {
|
|
715
|
+
if (schema.marks[json.type]) result.push(schema.markFromJSON(json));
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
return result;
|
|
720
|
+
}
|
|
721
|
+
function decideFormat(tr, changes, decision) {
|
|
722
|
+
const schema = tr.doc.type.schema;
|
|
723
|
+
const formatType = schema.marks[FORMAT_CHANGE_MARK_NAME];
|
|
724
|
+
if (!formatType) return;
|
|
725
|
+
const ids = new Set(changes.map((c) => c.id));
|
|
726
|
+
const segments = [];
|
|
727
|
+
changes.forEach(
|
|
728
|
+
(change) => change.ranges.forEach(
|
|
729
|
+
({ from, to }) => tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
730
|
+
if (!node.isInline) return true;
|
|
731
|
+
const mark = formatType.isInSet(node.marks);
|
|
732
|
+
if (mark && ids.has(mark.attrs.changeId)) {
|
|
733
|
+
segments.push({ from: Math.max(pos, from), to: Math.min(pos + node.nodeSize, to), node, mark });
|
|
734
|
+
}
|
|
735
|
+
return false;
|
|
736
|
+
})
|
|
737
|
+
)
|
|
738
|
+
);
|
|
739
|
+
segments.forEach(({ from, to, node, mark }) => {
|
|
740
|
+
tr.removeMark(from, to, formatType);
|
|
741
|
+
if (decision === "accept") return;
|
|
742
|
+
const before = mark.attrs.before ?? [];
|
|
743
|
+
const after = mark.attrs.after ?? [];
|
|
744
|
+
const involved = new Set([...before, ...after].map((m) => m.type));
|
|
745
|
+
node.marks.forEach((current) => {
|
|
746
|
+
if (involved.has(current.type.name)) tr.removeMark(from, to, current);
|
|
747
|
+
});
|
|
748
|
+
marksFromJSON(schema, before).forEach((restored) => tr.addMark(from, to, restored));
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
var LIST_ITEM_TYPES = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
|
|
752
|
+
function listItemAt(tr, contentPos) {
|
|
753
|
+
const $pos = tr.doc.resolve(contentPos);
|
|
754
|
+
const itemDepth = $pos.depth - 1;
|
|
755
|
+
if (itemDepth < 2) return null;
|
|
756
|
+
if (!LIST_ITEM_TYPES.has($pos.node(itemDepth).type.name) || $pos.index(itemDepth) !== 0) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
return { $pos, itemDepth };
|
|
760
|
+
}
|
|
761
|
+
function sinkItem(tr, contentPos) {
|
|
762
|
+
const info = listItemAt(tr, contentPos);
|
|
763
|
+
if (!info) return false;
|
|
764
|
+
const { $pos, itemDepth } = info;
|
|
765
|
+
const list = $pos.node(itemDepth - 1);
|
|
766
|
+
const index = $pos.index(itemDepth - 1);
|
|
767
|
+
const itemType = $pos.node(itemDepth).type;
|
|
768
|
+
if (index === 0 || list.child(index - 1).type !== itemType) return false;
|
|
769
|
+
const nodeBefore = list.child(index - 1);
|
|
770
|
+
const nestedBefore = nodeBefore.lastChild?.type === list.type;
|
|
771
|
+
const inner = Fragment.from(nestedBefore ? itemType.create() : null);
|
|
772
|
+
const slice = new Slice(
|
|
773
|
+
Fragment.from(itemType.create(null, Fragment.from(list.type.create(null, inner)))),
|
|
774
|
+
nestedBefore ? 3 : 1,
|
|
775
|
+
0
|
|
776
|
+
);
|
|
777
|
+
const before = $pos.before(itemDepth);
|
|
778
|
+
const after = $pos.after(itemDepth);
|
|
779
|
+
tr.step(
|
|
780
|
+
new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, before, after, slice, 1, true)
|
|
781
|
+
);
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
function outdentItem(tr, contentPos) {
|
|
785
|
+
const info = listItemAt(tr, contentPos);
|
|
786
|
+
if (!info) return false;
|
|
787
|
+
const { $pos, itemDepth } = info;
|
|
788
|
+
const listDepth = itemDepth - 1;
|
|
789
|
+
if (listDepth < 2 || !LIST_ITEM_TYPES.has($pos.node(listDepth - 1).type.name)) return false;
|
|
790
|
+
const itemType = $pos.node(itemDepth).type;
|
|
791
|
+
let range = new NodeRange(
|
|
792
|
+
tr.doc.resolve($pos.before(itemDepth)),
|
|
793
|
+
tr.doc.resolve($pos.after(itemDepth)),
|
|
794
|
+
listDepth
|
|
795
|
+
);
|
|
796
|
+
const end = range.end;
|
|
797
|
+
const endOfList = range.$to.end(range.depth);
|
|
798
|
+
if (end < endOfList) {
|
|
799
|
+
tr.step(
|
|
800
|
+
new ReplaceAroundStep(
|
|
801
|
+
end - 1,
|
|
802
|
+
endOfList,
|
|
803
|
+
end,
|
|
804
|
+
endOfList,
|
|
805
|
+
new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0),
|
|
806
|
+
1,
|
|
807
|
+
true
|
|
808
|
+
)
|
|
809
|
+
);
|
|
810
|
+
range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
|
|
811
|
+
}
|
|
812
|
+
const target = liftTarget(range);
|
|
813
|
+
if (target == null) return false;
|
|
814
|
+
tr.lift(range, target);
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
function restoreWrappers(tr, contentPos, wrappers) {
|
|
818
|
+
const schema = tr.doc.type.schema;
|
|
819
|
+
const step = (fn) => {
|
|
820
|
+
const index = tr.steps.length;
|
|
821
|
+
const result = fn();
|
|
822
|
+
contentPos = tr.mapping.slice(index).map(contentPos);
|
|
823
|
+
return result !== false && tr.steps.length > index;
|
|
824
|
+
};
|
|
825
|
+
const chain = () => {
|
|
826
|
+
const $pos = tr.doc.resolve(contentPos);
|
|
827
|
+
const types = [];
|
|
828
|
+
for (let depth = 1; depth < $pos.depth; depth++) types.push($pos.node(depth).type.name);
|
|
829
|
+
return types;
|
|
830
|
+
};
|
|
831
|
+
const target = wrappers.map((w) => w.type);
|
|
832
|
+
for (let guard = 0; guard < 24; guard++) {
|
|
833
|
+
const current = chain();
|
|
834
|
+
if (current.join("/") === target.join("/")) return;
|
|
835
|
+
if (current.length === target.length) {
|
|
836
|
+
const index = current.findIndex((type2, i) => type2 !== target[i]);
|
|
837
|
+
const type = schema.nodes[target[index]];
|
|
838
|
+
if (type && LIST_ITEM_TYPES.has(type.name) === LIST_ITEM_TYPES.has(current[index])) {
|
|
839
|
+
const $pos = tr.doc.resolve(contentPos);
|
|
840
|
+
if (step(() => tr.setNodeMarkup($pos.before(index + 1), type, wrappers[index].attrs))) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
let common = 0;
|
|
846
|
+
while (common < current.length && common < target.length && current[common] === target[common]) {
|
|
847
|
+
common++;
|
|
848
|
+
}
|
|
849
|
+
if (current.length > common) {
|
|
850
|
+
if (step(() => outdentItem(tr, contentPos))) continue;
|
|
851
|
+
const $from2 = tr.doc.resolve(contentPos);
|
|
852
|
+
const range2 = $from2.blockRange(tr.doc.resolve($from2.end()));
|
|
853
|
+
const lift = range2 ? liftTarget(range2) : null;
|
|
854
|
+
if (!range2 || lift == null || !step(() => tr.lift(range2, lift))) return;
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const missing = wrappers.slice(current.length);
|
|
858
|
+
if (missing.length >= 2 && LIST_ITEM_TYPES.has(missing[1].type) && step(() => sinkItem(tr, contentPos))) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
const types = missing.map((w) => ({ type: schema.nodes[w.type], attrs: w.attrs }));
|
|
862
|
+
if (types.some((t) => !t.type)) return;
|
|
863
|
+
const $from = tr.doc.resolve(contentPos);
|
|
864
|
+
const range = $from.blockRange(tr.doc.resolve($from.end()));
|
|
865
|
+
if (!range) return;
|
|
866
|
+
const start = range.start;
|
|
867
|
+
step(() => tr.wrap(range, types));
|
|
868
|
+
const wrapper = tr.doc.nodeAt(start);
|
|
869
|
+
if (!wrapper) return;
|
|
870
|
+
const end = start + wrapper.nodeSize;
|
|
871
|
+
if (tr.doc.resolve(end).nodeAfter?.type === wrapper.type && canJoin(tr.doc, end)) {
|
|
872
|
+
step(() => tr.join(end));
|
|
873
|
+
}
|
|
874
|
+
if (tr.doc.resolve(start).nodeBefore?.type === wrapper.type && canJoin(tr.doc, start)) {
|
|
875
|
+
step(() => tr.join(start));
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function decideBlocks(tr, changes, decision) {
|
|
881
|
+
const ids = new Set(changes.map((c) => c.id));
|
|
882
|
+
const items = [];
|
|
883
|
+
tr.doc.descendants((node, pos) => {
|
|
884
|
+
const record = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE];
|
|
885
|
+
if (node.isTextblock && record?.changeId && ids.has(record.changeId)) {
|
|
886
|
+
items.push({ contentPos: pos + 1, record });
|
|
887
|
+
}
|
|
888
|
+
return !node.isTextblock;
|
|
889
|
+
});
|
|
890
|
+
const schema = tr.doc.type.schema;
|
|
891
|
+
const startIndex = tr.steps.length;
|
|
892
|
+
items.reverse().forEach(({ contentPos: original, record }) => {
|
|
893
|
+
const contentPos = tr.mapping.slice(startIndex).map(original);
|
|
894
|
+
const $pos = tr.doc.resolve(contentPos);
|
|
895
|
+
if (!$pos.parent.isTextblock || $pos.parentOffset !== 0) return;
|
|
896
|
+
const pos = $pos.before();
|
|
897
|
+
if (decision === "accept") {
|
|
898
|
+
tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const previous = record.previous;
|
|
902
|
+
const type = previous && schema.nodes[previous.type];
|
|
903
|
+
if (!type) {
|
|
904
|
+
tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const attrs = { ...previous.attrs, [BLOCK_CHANGE_ATTRIBUTE]: null };
|
|
908
|
+
try {
|
|
909
|
+
tr.setNodeMarkup(pos, type, attrs);
|
|
910
|
+
} catch {
|
|
911
|
+
try {
|
|
912
|
+
tr.setBlockType(pos + 1, pos + 1, type, attrs);
|
|
913
|
+
} catch {
|
|
914
|
+
tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
try {
|
|
918
|
+
restoreWrappers(tr, tr.mapping.slice(startIndex).map(original), previous.wrappers ?? []);
|
|
919
|
+
} catch {
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
}
|
|
435
923
|
function applyDecision(tr, changes, decision) {
|
|
436
924
|
const schema = tr.doc.type.schema;
|
|
925
|
+
const format = changes.filter((c) => c.type === "format");
|
|
926
|
+
if (format.length) decideFormat(tr, format, decision);
|
|
437
927
|
const deletions = [];
|
|
438
928
|
changes.forEach((change) => {
|
|
929
|
+
if (change.type !== "insertion" && change.type !== "deletion") return;
|
|
439
930
|
const markType = schema.marks[change.type];
|
|
440
931
|
if (!markType) return;
|
|
441
932
|
const keep = change.type === "insertion" === (decision === "accept");
|
|
@@ -450,6 +941,8 @@ function applyDecision(tr, changes, decision) {
|
|
|
450
941
|
deletions.sort((a, b) => b.from - a.from).forEach(({ from, to }) => {
|
|
451
942
|
tr.delete(tr.mapping.map(from), tr.mapping.map(to));
|
|
452
943
|
});
|
|
944
|
+
const blocks = changes.filter((c) => c.type === "block");
|
|
945
|
+
if (blocks.length) decideBlocks(tr, blocks, decision);
|
|
453
946
|
}
|
|
454
947
|
function resolveTrackedChanges(doc, view = "accepted") {
|
|
455
948
|
if (view === "all") return doc;
|
|
@@ -468,14 +961,27 @@ function hasInlineContent(fragment) {
|
|
|
468
961
|
});
|
|
469
962
|
return found;
|
|
470
963
|
}
|
|
471
|
-
function acceptSlice(slice,
|
|
472
|
-
|
|
964
|
+
function acceptSlice(slice, schema) {
|
|
965
|
+
const insertion = schema.marks[INSERTION_MARK_NAME];
|
|
966
|
+
const deletion = schema.marks[DELETION_MARK_NAME];
|
|
967
|
+
const format = schema.marks[FORMAT_CHANGE_MARK_NAME];
|
|
473
968
|
const mapFragment = (fragment) => {
|
|
474
969
|
const children = [];
|
|
475
970
|
fragment.forEach((child) => {
|
|
476
971
|
if (child.isInline) {
|
|
477
972
|
if (deletion && deletion.isInSet(child.marks)) return;
|
|
478
|
-
|
|
973
|
+
let marks = child.marks;
|
|
974
|
+
if (insertion) marks = insertion.removeFromSet(marks);
|
|
975
|
+
if (format) marks = format.removeFromSet(marks);
|
|
976
|
+
children.push(marks === child.marks ? child : child.mark(marks));
|
|
977
|
+
} else if (child.attrs?.[BLOCK_CHANGE_ATTRIBUTE]) {
|
|
978
|
+
children.push(
|
|
979
|
+
child.type.create(
|
|
980
|
+
{ ...child.attrs, [BLOCK_CHANGE_ATTRIBUTE]: null },
|
|
981
|
+
mapFragment(child.content),
|
|
982
|
+
child.marks
|
|
983
|
+
)
|
|
984
|
+
);
|
|
479
985
|
} else {
|
|
480
986
|
children.push(child.copy(mapFragment(child.content)));
|
|
481
987
|
}
|
|
@@ -484,6 +990,13 @@ function acceptSlice(slice, insertion, deletion) {
|
|
|
484
990
|
};
|
|
485
991
|
return new Slice(mapFragment(slice.content), slice.openStart, slice.openEnd);
|
|
486
992
|
}
|
|
993
|
+
function stripMarks(fragment) {
|
|
994
|
+
const children = [];
|
|
995
|
+
fragment.forEach((child) => {
|
|
996
|
+
children.push(child.isInline ? child.mark(ProsemirrorMark.none) : child.copy(stripMarks(child.content)));
|
|
997
|
+
});
|
|
998
|
+
return Fragment.fromArray(children);
|
|
999
|
+
}
|
|
487
1000
|
function newAttributes(ctx) {
|
|
488
1001
|
return {
|
|
489
1002
|
changeId: ctx.generateId(),
|
|
@@ -493,6 +1006,15 @@ function newAttributes(ctx) {
|
|
|
493
1006
|
createdAt: ctx.now()
|
|
494
1007
|
};
|
|
495
1008
|
}
|
|
1009
|
+
function baseAttributes(attrs) {
|
|
1010
|
+
return {
|
|
1011
|
+
changeId: attrs.changeId,
|
|
1012
|
+
authorId: attrs.authorId,
|
|
1013
|
+
authorName: attrs.authorName,
|
|
1014
|
+
authorColor: attrs.authorColor ?? null,
|
|
1015
|
+
createdAt: attrs.createdAt
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
496
1018
|
function adjacentAttributes(doc, from, to, type, ctx) {
|
|
497
1019
|
const candidates = [doc.resolve(from).nodeBefore, doc.resolve(to).nodeAfter];
|
|
498
1020
|
for (const node of candidates) {
|
|
@@ -540,27 +1062,191 @@ function shouldSkip(tr) {
|
|
|
540
1062
|
!!tr.getMeta("y-sync$") || // Housekeeping appended by other plugins (e.g. trailing nodes).
|
|
541
1063
|
!!tr.getMeta("appendedTransaction");
|
|
542
1064
|
}
|
|
1065
|
+
function formattingOf(marks, ctx) {
|
|
1066
|
+
return marks.filter((m) => !REVIEW_MARK_NAMES.has(m.type.name) && !ctx.ignoredMarks.has(m.type.name)).map((m) => m.toJSON());
|
|
1067
|
+
}
|
|
1068
|
+
function mergeRanges(ranges) {
|
|
1069
|
+
const sorted = ranges.filter((r) => r.to > r.from).sort((a, b) => a.from - b.from);
|
|
1070
|
+
const merged = [];
|
|
1071
|
+
sorted.forEach((range) => {
|
|
1072
|
+
const last = merged[merged.length - 1];
|
|
1073
|
+
if (last && range.from <= last.to) last.to = Math.max(last.to, range.to);
|
|
1074
|
+
else merged.push({ ...range });
|
|
1075
|
+
});
|
|
1076
|
+
return merged;
|
|
1077
|
+
}
|
|
1078
|
+
function trackFormatting(tr, ranges, oldDoc, back, inserted, ctx) {
|
|
1079
|
+
const format = ctx.format;
|
|
1080
|
+
const doc = tr.doc;
|
|
1081
|
+
let changed = false;
|
|
1082
|
+
let batch = null;
|
|
1083
|
+
mergeRanges(ranges).forEach(({ from: a, to: b }) => {
|
|
1084
|
+
const segments = [];
|
|
1085
|
+
doc.nodesBetween(a, b, (node, pos, parent) => {
|
|
1086
|
+
if (!node.isInline) return true;
|
|
1087
|
+
const s = Math.max(a, pos);
|
|
1088
|
+
const e = Math.min(b, pos + node.nodeSize);
|
|
1089
|
+
if (e <= s || !parent || !parent.type.allowsMarkType(format)) return false;
|
|
1090
|
+
if (ctx.insertion.isInSet(node.marks)) return false;
|
|
1091
|
+
if (inserted.some((r) => s < r.to && e > r.from)) return false;
|
|
1092
|
+
const os = back.map(s, 1);
|
|
1093
|
+
const oe = back.map(e, -1);
|
|
1094
|
+
if (oe - os !== e - s) return false;
|
|
1095
|
+
oldDoc.nodesBetween(os, oe, (oldNode, oldPos) => {
|
|
1096
|
+
if (!oldNode.isInline) return true;
|
|
1097
|
+
const ss = Math.max(os, oldPos);
|
|
1098
|
+
const ee = Math.min(oe, oldPos + oldNode.nodeSize);
|
|
1099
|
+
if (ee > ss && !ctx.insertion.isInSet(oldNode.marks)) {
|
|
1100
|
+
segments.push({ from: s + (ss - os), to: s + (ee - os), oldNode, node });
|
|
1101
|
+
}
|
|
1102
|
+
return false;
|
|
1103
|
+
});
|
|
1104
|
+
return false;
|
|
1105
|
+
});
|
|
1106
|
+
segments.forEach(({ from, to, oldNode, node }) => {
|
|
1107
|
+
const existing = format.isInSet(oldNode.marks);
|
|
1108
|
+
const original = existing ? existing.attrs.before ?? [] : formattingOf(oldNode.marks, ctx);
|
|
1109
|
+
const current = formattingOf(node.marks, ctx);
|
|
1110
|
+
if (sameValue(original, current)) {
|
|
1111
|
+
if (existing || format.isInSet(node.marks)) {
|
|
1112
|
+
tr.removeMark(from, to, format);
|
|
1113
|
+
changed = true;
|
|
1114
|
+
}
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
let attrs;
|
|
1118
|
+
if (existing && existing.attrs.authorId === ctx.author.id && existing.attrs.changeId) {
|
|
1119
|
+
attrs = baseAttributes(changeAttributes(existing));
|
|
1120
|
+
} else {
|
|
1121
|
+
if (!batch) {
|
|
1122
|
+
const description = describeFormatChange(diffFormatting(original, current));
|
|
1123
|
+
const neighbours = [doc.resolve(from).nodeBefore, doc.resolve(to).nodeAfter];
|
|
1124
|
+
for (const neighbour of neighbours) {
|
|
1125
|
+
const mark = neighbour && format.isInSet(neighbour.marks);
|
|
1126
|
+
if (mark && mark.attrs.authorId === ctx.author.id && mark.attrs.changeId && describeFormatChange(diffFormatting(mark.attrs.before ?? [], mark.attrs.after ?? [])) === description) {
|
|
1127
|
+
batch = baseAttributes(changeAttributes(mark));
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
batch = batch ?? newAttributes(ctx);
|
|
1132
|
+
}
|
|
1133
|
+
attrs = batch;
|
|
1134
|
+
}
|
|
1135
|
+
tr.addMark(from, to, format.create({ ...attrs, before: original, after: current }));
|
|
1136
|
+
changed = true;
|
|
1137
|
+
});
|
|
1138
|
+
});
|
|
1139
|
+
return changed;
|
|
1140
|
+
}
|
|
1141
|
+
function trackBlocks(tr, pending, ctx) {
|
|
1142
|
+
let changed = false;
|
|
1143
|
+
let batch = null;
|
|
1144
|
+
pending.forEach(({ previous, existing }, contentPos) => {
|
|
1145
|
+
if (contentPos < 0 || contentPos > tr.doc.content.size) return;
|
|
1146
|
+
const $pos = tr.doc.resolve(contentPos);
|
|
1147
|
+
const node = $pos.parent;
|
|
1148
|
+
if (!node.isTextblock || $pos.parentOffset !== 0 || $pos.depth === 0) return;
|
|
1149
|
+
if (!node.type.spec.attrs || !(BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs)) return;
|
|
1150
|
+
const pos = $pos.before();
|
|
1151
|
+
const original = existing?.previous ?? previous;
|
|
1152
|
+
const current = blockSnapshot(tr.doc, pos);
|
|
1153
|
+
if (sameBlock(original, current, ctx.ignoredAttributes)) {
|
|
1154
|
+
if (node.attrs[BLOCK_CHANGE_ATTRIBUTE]) {
|
|
1155
|
+
tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);
|
|
1156
|
+
changed = true;
|
|
1157
|
+
}
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const base = existing && existing.authorId === ctx.author.id && existing.changeId ? baseAttributes(existing) : batch = batch ?? newAttributes(ctx);
|
|
1161
|
+
const value = { ...base, previous: original };
|
|
1162
|
+
if (!sameValue(node.attrs[BLOCK_CHANGE_ATTRIBUTE], value)) {
|
|
1163
|
+
tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, value);
|
|
1164
|
+
changed = true;
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
return changed;
|
|
1168
|
+
}
|
|
1169
|
+
function collectBlockChanges(step, docBefore, docAfter, rest, pending, ctx) {
|
|
1170
|
+
const record = (oldPos, newPos) => {
|
|
1171
|
+
const key = rest.map(newPos + 1);
|
|
1172
|
+
if (pending.has(key)) return;
|
|
1173
|
+
const oldNode = docBefore.nodeAt(oldPos);
|
|
1174
|
+
const previous = blockSnapshot(docBefore, oldPos);
|
|
1175
|
+
if (sameBlock(previous, blockSnapshot(docAfter, newPos), ctx.ignoredAttributes)) return;
|
|
1176
|
+
pending.set(key, {
|
|
1177
|
+
previous,
|
|
1178
|
+
existing: oldNode.attrs[BLOCK_CHANGE_ATTRIBUTE] ?? null
|
|
1179
|
+
});
|
|
1180
|
+
};
|
|
1181
|
+
if (step instanceof AttrStep) {
|
|
1182
|
+
const node = docBefore.nodeAt(step.pos);
|
|
1183
|
+
if (node?.isTextblock && step.attr !== BLOCK_CHANGE_ATTRIBUTE && !ctx.ignoredAttributes.has(step.attr)) {
|
|
1184
|
+
record(step.pos, step.pos);
|
|
1185
|
+
}
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const map = step.getMap();
|
|
1189
|
+
const back = map.invert();
|
|
1190
|
+
const start = map.map(step.from, -1);
|
|
1191
|
+
const end = map.map(step.to, 1);
|
|
1192
|
+
docAfter.nodesBetween(start, end, (node, pos) => {
|
|
1193
|
+
if (!node.isTextblock) return true;
|
|
1194
|
+
const oldContent = back.map(pos + 1);
|
|
1195
|
+
if (oldContent < 1 || oldContent > docBefore.content.size) return false;
|
|
1196
|
+
const $old = docBefore.resolve(oldContent);
|
|
1197
|
+
if ($old.parent.isTextblock && $old.parentOffset === 0 && $old.parent.content.size === node.content.size) {
|
|
1198
|
+
record($old.before(), pos);
|
|
1199
|
+
}
|
|
1200
|
+
return false;
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
543
1203
|
function trackTransactions(transactions, oldState, newState, ctx) {
|
|
544
1204
|
const maps = [];
|
|
545
1205
|
transactions.forEach((t) => maps.push(...t.mapping.maps));
|
|
546
1206
|
const inserted = [];
|
|
547
1207
|
const restores = [];
|
|
1208
|
+
const formatRanges = [];
|
|
1209
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
548
1210
|
let mapIndex = 0;
|
|
549
1211
|
const single = transactions.length === 1 && transactions[0].steps.length === 1;
|
|
550
1212
|
transactions.forEach((t) => {
|
|
551
1213
|
const skip = shouldSkip(t);
|
|
552
1214
|
t.steps.forEach((step, i) => {
|
|
553
1215
|
const index = mapIndex + i;
|
|
554
|
-
if (skip
|
|
555
|
-
const { from, to, slice } = step;
|
|
1216
|
+
if (skip) return;
|
|
556
1217
|
const rest = new Mapping(maps.slice(index + 1));
|
|
1218
|
+
const docBefore = t.docs[i];
|
|
1219
|
+
if (step instanceof AddMarkStep || step instanceof RemoveMarkStep) {
|
|
1220
|
+
const name = step.mark.type.name;
|
|
1221
|
+
if (ctx.format && !REVIEW_MARK_NAMES.has(name) && !ctx.ignoredMarks.has(name)) {
|
|
1222
|
+
formatRanges.push({ from: rest.map(step.from, 1), to: rest.map(step.to, -1) });
|
|
1223
|
+
}
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
if (step instanceof ReplaceAroundStep || step instanceof AttrStep) {
|
|
1227
|
+
if (ctx.trackBlocks) {
|
|
1228
|
+
const docAfter = t.docs[i + 1] ?? t.doc;
|
|
1229
|
+
collectBlockChanges(step, docBefore, docAfter, rest, blocks, ctx);
|
|
1230
|
+
}
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (!(step instanceof ReplaceStep)) return;
|
|
1234
|
+
const { from, to, slice } = step;
|
|
1235
|
+
if (to > from && slice.size === to - from) {
|
|
1236
|
+
const deleted = docBefore.slice(from, to);
|
|
1237
|
+
if (slice.openStart === deleted.openStart && slice.openEnd === deleted.openEnd && stripMarks(slice.content).eq(stripMarks(deleted.content))) {
|
|
1238
|
+
if (ctx.format && !slice.content.eq(deleted.content)) {
|
|
1239
|
+
formatRanges.push({ from: rest.map(from, 1), to: rest.map(to, -1) });
|
|
1240
|
+
}
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
557
1244
|
if (slice.size > 0 && hasInlineContent(slice.content)) {
|
|
558
1245
|
const start = rest.map(from, 1);
|
|
559
1246
|
const end = rest.map(from + slice.size, -1);
|
|
560
1247
|
if (end > start) inserted.push({ from: start, to: end });
|
|
561
1248
|
}
|
|
562
1249
|
if (to > from) {
|
|
563
|
-
const docBefore = t.docs[i];
|
|
564
1250
|
const deleted = docBefore.slice(from, to);
|
|
565
1251
|
if (!hasInlineContent(deleted.content)) return;
|
|
566
1252
|
const $from = docBefore.resolve(from);
|
|
@@ -577,12 +1263,22 @@ function trackTransactions(transactions, oldState, newState, ctx) {
|
|
|
577
1263
|
});
|
|
578
1264
|
mapIndex += t.steps.length;
|
|
579
1265
|
});
|
|
580
|
-
if (inserted.length === 0 && restores.length === 0)
|
|
1266
|
+
if (inserted.length === 0 && restores.length === 0 && formatRanges.length === 0 && blocks.size === 0) {
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
581
1269
|
const tr = newState.tr;
|
|
582
1270
|
let changed = false;
|
|
1271
|
+
if (formatRanges.length && ctx.format) {
|
|
1272
|
+
const back = new Mapping(maps).invert();
|
|
1273
|
+
changed = trackFormatting(tr, formatRanges, oldState.doc, back, inserted, ctx) || changed;
|
|
1274
|
+
}
|
|
1275
|
+
if (blocks.size) {
|
|
1276
|
+
changed = trackBlocks(tr, blocks, ctx) || changed;
|
|
1277
|
+
}
|
|
583
1278
|
inserted.forEach(({ from, to }) => {
|
|
584
1279
|
tr.removeMark(from, to, ctx.deletion);
|
|
585
1280
|
tr.removeMark(from, to, ctx.insertion);
|
|
1281
|
+
if (ctx.format) tr.removeMark(from, to, ctx.format);
|
|
586
1282
|
const attrs = adjacentAttributes(tr.doc, from, to, ctx.insertion, ctx) ?? newAttributes(ctx);
|
|
587
1283
|
tr.addMark(from, to, ctx.insertion.create(attrs));
|
|
588
1284
|
changed = true;
|
|
@@ -624,6 +1320,19 @@ function buildSnapshot(state, author) {
|
|
|
624
1320
|
currentChangeId: getTrackedChangeAt(state, state.selection.from, changes)?.id ?? null
|
|
625
1321
|
};
|
|
626
1322
|
}
|
|
1323
|
+
function allowTrackingInCodeBlocks(schema) {
|
|
1324
|
+
const marks = [schema.marks[INSERTION_MARK_NAME], schema.marks[DELETION_MARK_NAME]].filter(
|
|
1325
|
+
Boolean
|
|
1326
|
+
);
|
|
1327
|
+
Object.values(schema.nodes).forEach((type) => {
|
|
1328
|
+
const markSet = type.markSet;
|
|
1329
|
+
if (!type.isTextblock || !type.spec.code || markSet === null) return;
|
|
1330
|
+
const missing = marks.filter((mark) => !markSet.includes(mark));
|
|
1331
|
+
if (missing.length) {
|
|
1332
|
+
type.markSet = [...markSet, ...missing];
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
627
1336
|
var TrackChanges = Extension.create({
|
|
628
1337
|
name: "trackChanges",
|
|
629
1338
|
addOptions() {
|
|
@@ -633,7 +1342,15 @@ var TrackChanges = Extension.create({
|
|
|
633
1342
|
now: () => Date.now(),
|
|
634
1343
|
generateId: () => createReviewId("change"),
|
|
635
1344
|
insertionHTMLAttributes: {},
|
|
636
|
-
deletionHTMLAttributes: {}
|
|
1345
|
+
deletionHTMLAttributes: {},
|
|
1346
|
+
formatChangeHTMLAttributes: {},
|
|
1347
|
+
blockChangeClass: "pixerate-block-change border-l-2 border-amber-400 pl-2 dark:border-amber-500",
|
|
1348
|
+
trackFormatting: true,
|
|
1349
|
+
trackBlocks: true,
|
|
1350
|
+
trackCodeBlocks: true,
|
|
1351
|
+
blockTypes: ["paragraph", "heading", "codeBlock"],
|
|
1352
|
+
ignoredMarks: [],
|
|
1353
|
+
ignoredBlockAttributes: ["id"]
|
|
637
1354
|
};
|
|
638
1355
|
},
|
|
639
1356
|
addStorage() {
|
|
@@ -646,14 +1363,43 @@ var TrackChanges = Extension.create({
|
|
|
646
1363
|
})
|
|
647
1364
|
};
|
|
648
1365
|
},
|
|
1366
|
+
addGlobalAttributes() {
|
|
1367
|
+
if (this.options?.trackBlocks === false) return [];
|
|
1368
|
+
const blockClass = this.options?.blockChangeClass ?? "";
|
|
1369
|
+
return [
|
|
1370
|
+
{
|
|
1371
|
+
types: this.options?.blockTypes ?? ["paragraph", "heading", "codeBlock"],
|
|
1372
|
+
attributes: {
|
|
1373
|
+
[BLOCK_CHANGE_ATTRIBUTE]: {
|
|
1374
|
+
default: null,
|
|
1375
|
+
keepOnSplit: false,
|
|
1376
|
+
parseHTML: (el) => parseJSON(el.getAttribute("data-block-change"), null),
|
|
1377
|
+
renderHTML: (attrs) => {
|
|
1378
|
+
const value = attrs[BLOCK_CHANGE_ATTRIBUTE];
|
|
1379
|
+
if (!value) return {};
|
|
1380
|
+
return {
|
|
1381
|
+
"data-block-change": JSON.stringify(value),
|
|
1382
|
+
...blockClass ? { class: blockClass } : {}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
];
|
|
1389
|
+
},
|
|
649
1390
|
addExtensions() {
|
|
650
1391
|
const insertionAttrs = this.options?.insertionHTMLAttributes ?? {};
|
|
651
1392
|
const deletionAttrs = this.options?.deletionHTMLAttributes ?? {};
|
|
1393
|
+
const formatAttrs = this.options?.formatChangeHTMLAttributes ?? {};
|
|
652
1394
|
return [
|
|
653
1395
|
Object.keys(insertionAttrs).length ? TrackedInsertion.configure({ HTMLAttributes: insertionAttrs }) : TrackedInsertion,
|
|
654
|
-
Object.keys(deletionAttrs).length ? TrackedDeletion.configure({ HTMLAttributes: deletionAttrs }) : TrackedDeletion
|
|
1396
|
+
Object.keys(deletionAttrs).length ? TrackedDeletion.configure({ HTMLAttributes: deletionAttrs }) : TrackedDeletion,
|
|
1397
|
+
Object.keys(formatAttrs).length ? TrackedFormatChange.configure({ HTMLAttributes: formatAttrs }) : TrackedFormatChange
|
|
655
1398
|
];
|
|
656
1399
|
},
|
|
1400
|
+
onBeforeCreate() {
|
|
1401
|
+
if (this.options.trackCodeBlocks) allowTrackingInCodeBlocks(this.editor.schema);
|
|
1402
|
+
},
|
|
657
1403
|
addCommands() {
|
|
658
1404
|
const decide = (decision, id) => ({ state, tr, dispatch }) => {
|
|
659
1405
|
const all = getTrackedChanges(state.doc);
|
|
@@ -669,9 +1415,11 @@ var TrackChanges = Extension.create({
|
|
|
669
1415
|
const changes = getTrackedChanges(state.doc);
|
|
670
1416
|
if (changes.length === 0) return false;
|
|
671
1417
|
const { from } = state.selection;
|
|
672
|
-
const
|
|
1418
|
+
const start = (c) => selectionRange(c)[0];
|
|
1419
|
+
const target = direction === 1 ? changes.find((c) => start(c) > from) ?? changes[0] : [...changes].reverse().find((c) => start(c) < from) ?? changes[changes.length - 1];
|
|
673
1420
|
if (dispatch) {
|
|
674
|
-
|
|
1421
|
+
const [anchor, head] = selectionRange(target);
|
|
1422
|
+
tr.setSelection(TextSelection.create(tr.doc, anchor, head)).scrollIntoView();
|
|
675
1423
|
}
|
|
676
1424
|
return true;
|
|
677
1425
|
};
|
|
@@ -702,6 +1450,8 @@ var TrackChanges = Extension.create({
|
|
|
702
1450
|
addProseMirrorPlugins() {
|
|
703
1451
|
const options = this.options;
|
|
704
1452
|
const store = this.storage.store;
|
|
1453
|
+
const ignoredMarks = new Set(options.ignoredMarks ?? []);
|
|
1454
|
+
const ignoredAttributes = new Set(options.ignoredBlockAttributes ?? []);
|
|
705
1455
|
let editorView = null;
|
|
706
1456
|
let pending = null;
|
|
707
1457
|
let flushTimer = null;
|
|
@@ -757,15 +1507,15 @@ var TrackChanges = Extension.create({
|
|
|
757
1507
|
now: options.now,
|
|
758
1508
|
generateId: options.generateId,
|
|
759
1509
|
insertion: newState.schema.marks[INSERTION_MARK_NAME],
|
|
760
|
-
deletion: newState.schema.marks[DELETION_MARK_NAME]
|
|
1510
|
+
deletion: newState.schema.marks[DELETION_MARK_NAME],
|
|
1511
|
+
format: options.trackFormatting !== false ? newState.schema.marks[FORMAT_CHANGE_MARK_NAME] : void 0,
|
|
1512
|
+
trackBlocks: options.trackBlocks !== false,
|
|
1513
|
+
ignoredMarks,
|
|
1514
|
+
ignoredAttributes
|
|
761
1515
|
});
|
|
762
1516
|
},
|
|
763
1517
|
props: {
|
|
764
|
-
transformPasted: (slice, view) => acceptSlice(
|
|
765
|
-
slice,
|
|
766
|
-
view.state.schema.marks[INSERTION_MARK_NAME],
|
|
767
|
-
view.state.schema.marks[DELETION_MARK_NAME]
|
|
768
|
-
),
|
|
1518
|
+
transformPasted: (slice, view) => acceptSlice(slice, view.state.schema),
|
|
769
1519
|
handleDOMEvents: {
|
|
770
1520
|
compositionend: () => {
|
|
771
1521
|
if (pending) scheduleFlush();
|
|
@@ -1336,11 +2086,16 @@ export {
|
|
|
1336
2086
|
formatReviewTime,
|
|
1337
2087
|
INSERTION_MARK_NAME,
|
|
1338
2088
|
DELETION_MARK_NAME,
|
|
2089
|
+
FORMAT_CHANGE_MARK_NAME,
|
|
2090
|
+
BLOCK_CHANGE_ATTRIBUTE,
|
|
1339
2091
|
TRACK_CHANGES_SKIP_META,
|
|
1340
2092
|
TrackChangesStore,
|
|
1341
2093
|
trackChangesPluginKey,
|
|
1342
2094
|
TrackedInsertion,
|
|
1343
2095
|
TrackedDeletion,
|
|
2096
|
+
TrackedFormatChange,
|
|
2097
|
+
describeFormatChange,
|
|
2098
|
+
describeBlockChange,
|
|
1344
2099
|
getTrackedChanges,
|
|
1345
2100
|
getTrackedChangeAt,
|
|
1346
2101
|
isTrackChangesEnabled,
|
|
@@ -1364,4 +2119,4 @@ export {
|
|
|
1364
2119
|
markdownToTipTapHtml,
|
|
1365
2120
|
getEditorMarkdown
|
|
1366
2121
|
};
|
|
1367
|
-
//# sourceMappingURL=chunk-
|
|
2122
|
+
//# sourceMappingURL=chunk-MJZBNWG7.js.map
|