@pixerate/editor 0.24.0 → 0.26.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.
Files changed (162) hide show
  1. package/README.md +101 -1
  2. package/THIRD_PARTY_NOTICES.md +40 -0
  3. package/dist/agent/index.cjs +175 -11
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +18 -4
  6. package/dist/agent/index.d.ts +18 -4
  7. package/dist/agent/index.js +168 -4
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/{arrangeShortcuts-B8O3f8-P.d.cts → arrange-BICutYPk.d.cts} +243 -29
  10. package/dist/{arrangeShortcuts-sLlD2rSX.d.ts → arrange-BICutYPk.d.ts} +243 -29
  11. package/dist/builtins-CdPcLwwF.d.cts +176 -0
  12. package/dist/builtins-Chj6zv_O.d.ts +176 -0
  13. package/dist/canvas/index.cjs +8588 -1857
  14. package/dist/canvas/index.cjs.map +1 -1
  15. package/dist/canvas/index.d.cts +1847 -9
  16. package/dist/canvas/index.d.ts +1847 -9
  17. package/dist/canvas/index.js +8577 -1846
  18. package/dist/canvas/index.js.map +1 -1
  19. package/dist/chunk-3CC7OPUQ.cjs +831 -0
  20. package/dist/chunk-3CC7OPUQ.cjs.map +1 -0
  21. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  22. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  23. package/dist/chunk-BVP3TSLH.cjs +69 -0
  24. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  25. package/dist/{chunk-OLZJXL4R.cjs → chunk-CS6NZWWA.cjs} +59 -25
  26. package/dist/chunk-CS6NZWWA.cjs.map +1 -0
  27. package/dist/{chunk-MCSJFWAO.cjs → chunk-EWCOFSCQ.cjs} +685 -100
  28. package/dist/chunk-EWCOFSCQ.cjs.map +1 -0
  29. package/dist/chunk-HDYMLS3X.cjs +409 -0
  30. package/dist/chunk-HDYMLS3X.cjs.map +1 -0
  31. package/dist/chunk-HRMEDT2L.js +1406 -0
  32. package/dist/chunk-HRMEDT2L.js.map +1 -0
  33. package/dist/{chunk-D27YFC46.cjs → chunk-IFHKKVXH.cjs} +382 -63
  34. package/dist/chunk-IFHKKVXH.cjs.map +1 -0
  35. package/dist/{chunk-PGNZKFQS.js → chunk-JOIREKL7.js} +137 -58
  36. package/dist/chunk-JOIREKL7.js.map +1 -0
  37. package/dist/chunk-KRGXUQ2Y.js +1696 -0
  38. package/dist/chunk-KRGXUQ2Y.js.map +1 -0
  39. package/dist/{chunk-3LV6RRPH.js → chunk-MHEPLBFB.js} +2 -2
  40. package/dist/chunk-MR2764X2.js +831 -0
  41. package/dist/chunk-MR2764X2.js.map +1 -0
  42. package/dist/{chunk-XNORY7M6.cjs → chunk-NFVBT3UZ.cjs} +166 -87
  43. package/dist/chunk-NFVBT3UZ.cjs.map +1 -0
  44. package/dist/chunk-NQF32I6Z.cjs +6478 -0
  45. package/dist/chunk-NQF32I6Z.cjs.map +1 -0
  46. package/dist/chunk-O5RYH2PG.js +69 -0
  47. package/dist/chunk-O5RYH2PG.js.map +1 -0
  48. package/dist/{chunk-ITBSKI26.js → chunk-Q2OHWD2A.js} +1064 -360
  49. package/dist/chunk-Q2OHWD2A.js.map +1 -0
  50. package/dist/{chunk-FODNLX5N.js → chunk-QJSOZFCZ.js} +38 -4
  51. package/dist/chunk-QJSOZFCZ.js.map +1 -0
  52. package/dist/{chunk-SML64VJG.cjs → chunk-RRREAOVM.cjs} +4 -4
  53. package/dist/{chunk-SML64VJG.cjs.map → chunk-RRREAOVM.cjs.map} +1 -1
  54. package/dist/{chunk-5S3Z4PPF.js → chunk-SWE3NPOU.js} +347 -28
  55. package/dist/chunk-SWE3NPOU.js.map +1 -0
  56. package/dist/chunk-TXXNBDQX.cjs +1696 -0
  57. package/dist/chunk-TXXNBDQX.cjs.map +1 -0
  58. package/dist/chunk-UBCFIABU.js +409 -0
  59. package/dist/chunk-UBCFIABU.js.map +1 -0
  60. package/dist/chunk-X7HXL2SB.js +6478 -0
  61. package/dist/chunk-X7HXL2SB.js.map +1 -0
  62. package/dist/{chunk-SBCZ6RN5.cjs → chunk-YOMYSFJU.cjs} +1163 -459
  63. package/dist/chunk-YOMYSFJU.cjs.map +1 -0
  64. package/dist/{chunk-3CN2WD3X.js → chunk-ZRNHGAQT.js} +661 -76
  65. package/dist/chunk-ZRNHGAQT.js.map +1 -0
  66. package/dist/commands/index.cjs +425 -4
  67. package/dist/commands/index.cjs.map +1 -1
  68. package/dist/commands/index.d.cts +39 -77
  69. package/dist/commands/index.d.ts +39 -77
  70. package/dist/commands/index.js +424 -3
  71. package/dist/commands/index.js.map +1 -1
  72. package/dist/compare/index.cjs +3 -3
  73. package/dist/compare/index.d.cts +1 -1
  74. package/dist/compare/index.d.ts +1 -1
  75. package/dist/compare/index.js +2 -2
  76. package/dist/controller-CxXGOEmy.d.cts +2038 -0
  77. package/dist/controller-CxXGOEmy.d.ts +2038 -0
  78. package/dist/{documents-BQjzJNe2.d.ts → documents-CHT1U5Vs.d.ts} +10 -0
  79. package/dist/{documents-KRTPhvst.d.cts → documents-Gp7wb0Me.d.cts} +10 -0
  80. package/dist/export/index.cjs +36 -0
  81. package/dist/export/index.cjs.map +1 -0
  82. package/dist/export/index.d.cts +243 -0
  83. package/dist/export/index.d.ts +243 -0
  84. package/dist/export/index.js +36 -0
  85. package/dist/export/index.js.map +1 -0
  86. package/dist/extensions/index.cjs +101 -6
  87. package/dist/extensions/index.cjs.map +1 -1
  88. package/dist/extensions/index.d.cts +18 -5
  89. package/dist/extensions/index.d.ts +18 -5
  90. package/dist/extensions/index.js +101 -6
  91. package/dist/generation/index.cjs +1013 -69
  92. package/dist/generation/index.cjs.map +1 -1
  93. package/dist/generation/index.d.cts +474 -13
  94. package/dist/generation/index.d.ts +474 -13
  95. package/dist/generation/index.js +976 -32
  96. package/dist/generation/index.js.map +1 -1
  97. package/dist/image-editor/index.cjs +3385 -1275
  98. package/dist/image-editor/index.cjs.map +1 -1
  99. package/dist/image-editor/index.d.cts +693 -128
  100. package/dist/image-editor/index.d.ts +693 -128
  101. package/dist/image-editor/index.js +3325 -1215
  102. package/dist/image-editor/index.js.map +1 -1
  103. package/dist/{index-fJrlkJCd.d.ts → index-B273Zllk.d.ts} +500 -7
  104. package/dist/{index-BhJBSm2d.d.cts → index-jcJwmBYt.d.cts} +500 -7
  105. package/dist/index.cjs +1603 -45
  106. package/dist/index.cjs.map +1 -1
  107. package/dist/index.d.cts +154 -27
  108. package/dist/index.d.ts +154 -27
  109. package/dist/index.js +1584 -26
  110. package/dist/index.js.map +1 -1
  111. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  112. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  113. package/dist/model-C1zApQM8.d.cts +219 -0
  114. package/dist/model-C1zApQM8.d.ts +219 -0
  115. package/dist/persistence/index.d.cts +3 -120
  116. package/dist/persistence/index.d.ts +3 -120
  117. package/dist/registry-CED5C0lA.d.cts +204 -0
  118. package/dist/registry-CED5C0lA.d.ts +204 -0
  119. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.cts} +12 -1
  120. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.ts} +12 -1
  121. package/dist/{registry-D32LpIc4.d.cts → registry-D0GADKc7.d.cts} +1 -1
  122. package/dist/{registry-evKTP4ls.d.ts → registry-DhVUZioj.d.ts} +1 -1
  123. package/dist/serializers/index.cjs +12 -4
  124. package/dist/serializers/index.cjs.map +1 -1
  125. package/dist/serializers/index.d.cts +41 -13
  126. package/dist/serializers/index.d.ts +41 -13
  127. package/dist/serializers/index.js +12 -4
  128. package/dist/spreadsheet/index.cjs +157 -3
  129. package/dist/spreadsheet/index.cjs.map +1 -1
  130. package/dist/spreadsheet/index.d.cts +534 -11
  131. package/dist/spreadsheet/index.d.ts +534 -11
  132. package/dist/spreadsheet/index.js +157 -3
  133. package/dist/storage-BpFfspHm.d.cts +120 -0
  134. package/dist/storage-BpFfspHm.d.ts +120 -0
  135. package/dist/tokens/index.cjs +115 -0
  136. package/dist/tokens/index.cjs.map +1 -0
  137. package/dist/tokens/index.d.cts +212 -0
  138. package/dist/tokens/index.d.ts +212 -0
  139. package/dist/tokens/index.js +115 -0
  140. package/dist/tokens/index.js.map +1 -0
  141. package/dist/{track-changes-BK5sVf4_.d.cts → track-changes-D_VAPELi.d.cts} +95 -11
  142. package/dist/{track-changes-CDjCrwKe.d.ts → track-changes-Db_fGEvx.d.ts} +95 -11
  143. package/package.json +15 -1
  144. package/dist/chunk-3CN2WD3X.js.map +0 -1
  145. package/dist/chunk-5S3Z4PPF.js.map +0 -1
  146. package/dist/chunk-D27YFC46.cjs.map +0 -1
  147. package/dist/chunk-FODNLX5N.js.map +0 -1
  148. package/dist/chunk-ITBSKI26.js.map +0 -1
  149. package/dist/chunk-MA2F5KHF.cjs +0 -3223
  150. package/dist/chunk-MA2F5KHF.cjs.map +0 -1
  151. package/dist/chunk-MCSJFWAO.cjs.map +0 -1
  152. package/dist/chunk-OLZJXL4R.cjs.map +0 -1
  153. package/dist/chunk-PGNZKFQS.js.map +0 -1
  154. package/dist/chunk-SBCZ6RN5.cjs.map +0 -1
  155. package/dist/chunk-U5KWZU4X.js +0 -3223
  156. package/dist/chunk-U5KWZU4X.js.map +0 -1
  157. package/dist/chunk-XNORY7M6.cjs.map +0 -1
  158. package/dist/controller-BhLLAoi8.d.cts +0 -1000
  159. package/dist/controller-BhLLAoi8.d.ts +0 -1000
  160. package/dist/types-DX4Mh4SL.d.cts +0 -217
  161. package/dist/types-DX4Mh4SL.d.ts +0 -217
  162. /package/dist/{chunk-3LV6RRPH.js.map → chunk-MHEPLBFB.js.map} +0 -0
@@ -264,6 +264,7 @@ var INSERTION_MARK_NAME = "insertion";
264
264
  var DELETION_MARK_NAME = "deletion";
265
265
  var FORMAT_CHANGE_MARK_NAME = "formatChange";
266
266
  var BLOCK_CHANGE_ATTRIBUTE = "blockChange";
267
+ var MOVE_ID_ATTRIBUTE = "moveId";
267
268
  var REVIEW_MARK_NAMES = /* @__PURE__ */ new Set([
268
269
  INSERTION_MARK_NAME,
269
270
  DELETION_MARK_NAME,
@@ -337,6 +338,15 @@ var sharedAttributes = () => ({
337
338
  renderHTML: (attrs) => ({ "data-created-at": String(attrs.createdAt) })
338
339
  }
339
340
  });
341
+ var moveAttribute = () => ({
342
+ [MOVE_ID_ATTRIBUTE]: {
343
+ default: null,
344
+ parseHTML: (el) => el.getAttribute("data-move-id"),
345
+ renderHTML: (attrs) => attrs[MOVE_ID_ATTRIBUTE] ? { "data-move-id": attrs[MOVE_ID_ATTRIBUTE] } : {}
346
+ }
347
+ });
348
+ var MOVE_TO_CLASS = "pixerate-move-to underline decoration-double decoration-emerald-600 text-emerald-700 dark:text-emerald-400";
349
+ var MOVE_FROM_CLASS = "pixerate-move-from line-through decoration-double decoration-emerald-600 text-emerald-700 dark:text-emerald-400";
340
350
  var TrackedInsertion = Mark.create({
341
351
  name: INSERTION_MARK_NAME,
342
352
  inclusive: false,
@@ -344,20 +354,22 @@ var TrackedInsertion = Mark.create({
344
354
  return {
345
355
  HTMLAttributes: {
346
356
  class: "pixerate-insertion underline decoration-emerald-500 text-emerald-700 dark:text-emerald-400"
347
- }
357
+ },
358
+ moveHTMLAttributes: { class: MOVE_TO_CLASS }
348
359
  };
349
360
  },
350
361
  addStorage() {
351
362
  return { markdown: { serialize: { open: "", close: "" }, parse: {} } };
352
363
  },
353
- addAttributes: sharedAttributes,
364
+ addAttributes: () => ({ ...sharedAttributes(), ...moveAttribute() }),
354
365
  parseHTML() {
355
366
  return [{ tag: 'ins[data-change-type="insertion"]', priority: 100 }];
356
367
  },
357
- renderHTML({ HTMLAttributes }) {
368
+ renderHTML({ mark, HTMLAttributes }) {
369
+ const base = mark.attrs[MOVE_ID_ATTRIBUTE] ? this.options.moveHTMLAttributes : this.options.HTMLAttributes;
358
370
  return [
359
371
  "ins",
360
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
372
+ mergeAttributes(base, HTMLAttributes, {
361
373
  "data-change-type": "insertion"
362
374
  }),
363
375
  0
@@ -371,7 +383,8 @@ var TrackedDeletion = Mark.create({
371
383
  return {
372
384
  HTMLAttributes: {
373
385
  class: "pixerate-deletion line-through decoration-red-500 text-red-600 dark:text-red-400"
374
- }
386
+ },
387
+ moveHTMLAttributes: { class: MOVE_FROM_CLASS }
375
388
  };
376
389
  },
377
390
  addStorage() {
@@ -382,14 +395,15 @@ var TrackedDeletion = Mark.create({
382
395
  }
383
396
  };
384
397
  },
385
- addAttributes: sharedAttributes,
398
+ addAttributes: () => ({ ...sharedAttributes(), ...moveAttribute() }),
386
399
  parseHTML() {
387
400
  return [{ tag: 'del[data-change-type="deletion"]', priority: 100 }];
388
401
  },
389
- renderHTML({ HTMLAttributes }) {
402
+ renderHTML({ mark, HTMLAttributes }) {
403
+ const base = mark.attrs[MOVE_ID_ATTRIBUTE] ? this.options.moveHTMLAttributes : this.options.HTMLAttributes;
390
404
  return [
391
405
  "del",
392
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
406
+ mergeAttributes(base, HTMLAttributes, {
393
407
  "data-change-type": "deletion"
394
408
  }),
395
409
  0
@@ -539,6 +553,8 @@ function describeBlockChange(previous, current, ignoredAttributes = ["id"]) {
539
553
  keys.forEach((key) => {
540
554
  if (key === "textAlign") {
541
555
  parts.push(`Changed alignment to ${humanize(String(current.attrs?.textAlign ?? "default"))}`);
556
+ } else if (key === "styleId") {
557
+ parts.push(`Changed style to ${current.attrs?.styleId ?? "default"}`);
542
558
  } else {
543
559
  parts.push(`Changed ${humanize(key).toLowerCase()} of ${after}`);
544
560
  }
@@ -559,6 +575,20 @@ function describeBlockChange(previous, current, ignoredAttributes = ["id"]) {
559
575
  }
560
576
  return parts.join("; ") || `Changed ${after}`;
561
577
  }
578
+ function trackedChangeLabel(change) {
579
+ switch (change.type) {
580
+ case "insertion":
581
+ return "Inserted";
582
+ case "deletion":
583
+ return "Deleted";
584
+ case "format":
585
+ return "Formatted";
586
+ case "move":
587
+ return "Moved";
588
+ default:
589
+ return "Changed";
590
+ }
591
+ }
562
592
  function truncate(text, max = 40) {
563
593
  return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
564
594
  }
@@ -631,10 +661,22 @@ function getTrackedChanges(source) {
631
661
  return change;
632
662
  };
633
663
  const blockCounts = /* @__PURE__ */ new Map();
664
+ const blockCells = /* @__PURE__ */ new Map();
634
665
  doc.descendants((node, pos) => {
635
666
  const block = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE];
636
667
  if (block?.changeId && node.isTextblock) {
637
668
  blockCounts.set(block.changeId, (blockCounts.get(block.changeId) ?? 0) + 1);
669
+ if (block.operation && blockCells.get(block.changeId) !== null) {
670
+ const $pos = doc.resolve(pos);
671
+ let cell = -1;
672
+ for (let depth = $pos.depth; depth > 0 && cell < 0; depth--) {
673
+ const role = $pos.node(depth).type.spec.tableRole;
674
+ if (role === "cell" || role === "header_cell") cell = $pos.before(depth);
675
+ }
676
+ const cells = blockCells.get(block.changeId) ?? /* @__PURE__ */ new Set();
677
+ if (cell < 0) blockCells.set(block.changeId, null);
678
+ else blockCells.set(block.changeId, cells.add(cell));
679
+ }
638
680
  const change = add(
639
681
  `block:${block.changeId}`,
640
682
  "block",
@@ -657,6 +699,20 @@ function getTrackedChanges(source) {
657
699
  if (!isMarkChange(markType)) return;
658
700
  const attrs = changeAttributes(mark);
659
701
  if (!attrs.changeId) return;
702
+ const text = node.isText ? node.text ?? "" : "";
703
+ const moveId = markType !== FORMAT_CHANGE_MARK_NAME ? attrs[MOVE_ID_ATTRIBUTE] : null;
704
+ if (moveId) {
705
+ const change2 = add(`move:${moveId}`, "move", { ...attrs, changeId: moveId }, pos, pos + node.nodeSize, text);
706
+ if (!change2.move) change2.move = { from: [], to: [], fromText: "", toText: "" };
707
+ const source2 = markType === DELETION_MARK_NAME;
708
+ const list = source2 ? change2.move.from : change2.move.to;
709
+ const last = list[list.length - 1];
710
+ if (last && last.to === pos) last.to = pos + node.nodeSize;
711
+ else list.push({ from: pos, to: pos + node.nodeSize });
712
+ if (source2) change2.move.fromText += text;
713
+ else change2.move.toText += text;
714
+ return;
715
+ }
660
716
  const type = markType === FORMAT_CHANGE_MARK_NAME ? "format" : markType;
661
717
  const change = add(
662
718
  `${type}:${attrs.changeId}`,
@@ -664,7 +720,7 @@ function getTrackedChanges(source) {
664
720
  attrs,
665
721
  pos,
666
722
  pos + node.nodeSize,
667
- node.isText ? node.text ?? "" : ""
723
+ text
668
724
  );
669
725
  if (type === "format") {
670
726
  const summary = diffFormatting(mark.attrs.before ?? [], mark.attrs.after ?? []);
@@ -685,12 +741,19 @@ function getTrackedChanges(source) {
685
741
  case "format":
686
742
  change.description = describeFormatChange(change.format);
687
743
  break;
744
+ case "move": {
745
+ const move = change.move;
746
+ change.text = move.toText || move.fromText;
747
+ change.description = change.text ? `Moved "${truncate(change.text)}"` : "Moved content";
748
+ break;
749
+ }
688
750
  case "block": {
689
751
  const { previous, current, operation } = change.block;
690
752
  if (operation) {
691
753
  const count = blockCounts.get(change.id) ?? 1;
692
754
  const verb = operation === "insert" ? "Inserted" : "Deleted";
693
- change.description = count > 1 ? `${verb} ${count} blocks` : `${verb} ${blockLabel(current.type, current.attrs)}`;
755
+ const cells = blockCells.get(change.id)?.size;
756
+ change.description = cells ? `${verb} ${cells === 1 ? "a table cell" : `${cells} table cells`}` : count > 1 ? `${verb} ${count} blocks` : `${verb} ${blockLabel(current.type, current.attrs)}`;
694
757
  } else {
695
758
  change.description = describeBlockChange(previous, current);
696
759
  }
@@ -712,7 +775,12 @@ function getTrackedChangeAt(state, pos = state.selection.from, changes = getTrac
712
775
  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;
713
776
  }
714
777
  function selectionRange(change) {
715
- return change.type === "block" ? [change.from + 1, change.to - 1] : [change.from, change.to];
778
+ if (change.type === "block") return [change.from + 1, change.to - 1];
779
+ if (change.type === "move" && change.move) {
780
+ const ranges = change.move.to.length ? change.move.to : change.move.from;
781
+ if (ranges.length) return [ranges[0].from, ranges[ranges.length - 1].to];
782
+ }
783
+ return [change.from, change.to];
716
784
  }
717
785
  function isTrackChangesEnabled(source) {
718
786
  const state = source?.state ?? source;
@@ -982,12 +1050,36 @@ function decideBlocks(tr, changes, decision) {
982
1050
  }
983
1051
  });
984
1052
  }
1053
+ function deleteWholeBlock(tr, from, to) {
1054
+ const $from = tr.doc.resolve(from);
1055
+ const block = $from.parent;
1056
+ if ($from.depth !== 1 || !block.isTextblock || from !== $from.start() || to !== $from.end() || tr.doc.childCount < 2) {
1057
+ return false;
1058
+ }
1059
+ try {
1060
+ tr.delete($from.before(), $from.after());
1061
+ return true;
1062
+ } catch {
1063
+ return false;
1064
+ }
1065
+ }
985
1066
  function applyDecision(tr, changes, decision) {
986
1067
  const schema = tr.doc.type.schema;
987
1068
  const format = changes.filter((c) => c.type === "format");
988
1069
  if (format.length) decideFormat(tr, format, decision);
989
1070
  const deletions = [];
990
1071
  changes.forEach((change) => {
1072
+ if (change.type === "move" && change.move) {
1073
+ const accept = decision === "accept";
1074
+ const keepType = schema.marks[accept ? INSERTION_MARK_NAME : DELETION_MARK_NAME];
1075
+ (accept ? change.move.to : change.move.from).forEach(({ from, to }) => {
1076
+ if (keepType) tr.removeMark(from, to, keepType);
1077
+ });
1078
+ (accept ? change.move.from : change.move.to).forEach(
1079
+ ({ from, to }) => deletions.push({ from, to, wholeBlocks: true })
1080
+ );
1081
+ return;
1082
+ }
991
1083
  if (change.type !== "insertion" && change.type !== "deletion") return;
992
1084
  const markType = schema.marks[change.type];
993
1085
  if (!markType) return;
@@ -1000,8 +1092,11 @@ function applyDecision(tr, changes, decision) {
1000
1092
  }
1001
1093
  });
1002
1094
  });
1003
- deletions.sort((a, b) => b.from - a.from).forEach(({ from, to }) => {
1004
- tr.delete(tr.mapping.map(from), tr.mapping.map(to));
1095
+ deletions.sort((a, b) => b.from - a.from).forEach(({ from, to, wholeBlocks }) => {
1096
+ const start = tr.mapping.map(from);
1097
+ const end = tr.mapping.map(to);
1098
+ if (wholeBlocks && deleteWholeBlock(tr, start, end)) return;
1099
+ tr.delete(start, end);
1005
1100
  });
1006
1101
  const blocks = changes.filter((c) => c.type === "block");
1007
1102
  if (blocks.length) decideBlocks(tr, blocks, decision);
@@ -1059,6 +1154,52 @@ function stripMarks(fragment) {
1059
1154
  });
1060
1155
  return Fragment.fromArray(children);
1061
1156
  }
1157
+ var SENTENCE_END = /[.!?…]$/;
1158
+ var TRAILING_CLOSERS = /["'”’)]+$/;
1159
+ var TRAILING_SPACES = /[  ]+$/;
1160
+ function isMoveSized(text) {
1161
+ const trimmed = text.replace(TRAILING_SPACES, "");
1162
+ if (trimmed.includes("\n")) return true;
1163
+ const sentence = trimmed.replace(TRAILING_CLOSERS, "");
1164
+ return SENTENCE_END.test(sentence) && sentence.trim().split(/\s+/).length >= 2;
1165
+ }
1166
+ var normalizeMoveText = (text) => text.replace(/\s+/g, "");
1167
+ function deletedText(slice, ctx) {
1168
+ let text = "";
1169
+ let blocks = 0;
1170
+ slice.content.descendants((node) => {
1171
+ if (node.isTextblock) {
1172
+ if (blocks++ > 0) text += "\n";
1173
+ return true;
1174
+ }
1175
+ if (!node.isText) return true;
1176
+ const insertion = ctx.insertion.isInSet(node.marks);
1177
+ if (insertion && insertion.attrs.authorId === ctx.author.id) return false;
1178
+ if (ctx.deletion.isInSet(node.marks)) return false;
1179
+ text += node.text ?? "";
1180
+ return false;
1181
+ });
1182
+ return text;
1183
+ }
1184
+ var rangeText = (doc, from, to) => doc.textBetween(from, to, "\n", "");
1185
+ function linkMoveSource(tr, pending, ctx) {
1186
+ const segments = [];
1187
+ tr.doc.descendants((node, pos) => {
1188
+ if (!node.isInline) return true;
1189
+ const mark = ctx.deletion.isInSet(node.marks);
1190
+ if (mark && mark.attrs.changeId === pending.id && !mark.attrs[MOVE_ID_ATTRIBUTE]) {
1191
+ segments.push({ from: pos, to: pos + node.nodeSize, mark });
1192
+ }
1193
+ return false;
1194
+ });
1195
+ if (segments.length === 0) return false;
1196
+ const text = segments.map((s) => rangeText(tr.doc, s.from, s.to)).join("");
1197
+ if (normalizeMoveText(text) !== pending.text) return false;
1198
+ segments.forEach(
1199
+ ({ from, to, mark }) => tr.addMark(from, to, ctx.deletion.create({ ...mark.attrs, [MOVE_ID_ATTRIBUTE]: pending.id }))
1200
+ );
1201
+ return true;
1202
+ }
1062
1203
  function newAttributes(ctx) {
1063
1204
  return {
1064
1205
  changeId: ctx.generateId(),
@@ -1081,7 +1222,7 @@ function adjacentAttributes(doc, from, to, type, ctx) {
1081
1222
  const candidates = [doc.resolve(from).nodeBefore, doc.resolve(to).nodeAfter];
1082
1223
  for (const node of candidates) {
1083
1224
  const mark = node && type.isInSet(node.marks);
1084
- if (mark && mark.attrs.authorId === ctx.author.id && mark.attrs.changeId) {
1225
+ if (mark && mark.attrs.authorId === ctx.author.id && mark.attrs.changeId && !mark.attrs[MOVE_ID_ATTRIBUTE]) {
1085
1226
  return changeAttributes(mark);
1086
1227
  }
1087
1228
  }
@@ -1263,11 +1404,78 @@ function collectBlockChanges(step, docBefore, docAfter, rest, pending, ctx) {
1263
1404
  return false;
1264
1405
  });
1265
1406
  }
1407
+ var TABLE_ROLES = /* @__PURE__ */ new Set(["table", "row", "cell", "header_cell"]);
1408
+ var isTableStructure = (node) => TABLE_ROLES.has(node.type.spec.tableRole);
1409
+ var supportsBlockChange = (node) => !!node.type.spec.attrs && BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs;
1410
+ function isWholeTableSlice(slice) {
1411
+ if (slice.openStart !== 0 || slice.openEnd !== 0 || slice.content.childCount === 0) return false;
1412
+ let whole = true;
1413
+ slice.content.forEach((child) => {
1414
+ if (!isTableStructure(child)) whole = false;
1415
+ });
1416
+ return whole;
1417
+ }
1418
+ function canMarkTableBlocks(slice) {
1419
+ let ok = true;
1420
+ let any = false;
1421
+ slice.content.descendants((node) => {
1422
+ if (!ok) return false;
1423
+ if (node.isTextblock) {
1424
+ any = true;
1425
+ if (!supportsBlockChange(node)) ok = false;
1426
+ return false;
1427
+ }
1428
+ return true;
1429
+ });
1430
+ return ok && any;
1431
+ }
1432
+ function isOwnTableInsertion(slice, ctx) {
1433
+ let own = true;
1434
+ slice.content.descendants((node) => {
1435
+ if (!own) return false;
1436
+ if (node.isTextblock) {
1437
+ const record = node.attrs[BLOCK_CHANGE_ATTRIBUTE];
1438
+ if (record?.operation !== "insert" || record.authorId !== ctx.author.id) own = false;
1439
+ return false;
1440
+ }
1441
+ return true;
1442
+ });
1443
+ return own;
1444
+ }
1445
+ function collectInsertedTableNodes(slice, from, rest, out) {
1446
+ slice.content.descendants((node, offset) => {
1447
+ if (!isTableStructure(node)) return !node.isTextblock;
1448
+ const pos = from - slice.openStart + offset;
1449
+ if (pos < from) return true;
1450
+ const start = rest.map(pos, 1);
1451
+ const end = rest.map(pos + node.nodeSize, -1);
1452
+ if (end - start === node.nodeSize) out.push({ from: start, to: end });
1453
+ return false;
1454
+ });
1455
+ }
1456
+ function markTableBlocks(tr, from, to, operation, attrs) {
1457
+ const positions = [];
1458
+ tr.doc.nodesBetween(from, to, (node, pos) => {
1459
+ if (!node.isTextblock) return true;
1460
+ if (pos >= from && pos + node.nodeSize <= to && supportsBlockChange(node)) positions.push(pos);
1461
+ return false;
1462
+ });
1463
+ positions.forEach((pos) => {
1464
+ const record = {
1465
+ ...baseAttributes(attrs),
1466
+ previous: blockSnapshot(tr.doc, pos),
1467
+ operation
1468
+ };
1469
+ tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, record);
1470
+ });
1471
+ return positions.length > 0;
1472
+ }
1266
1473
  function trackTransactions(transactions, oldState, newState, ctx) {
1267
1474
  const maps = [];
1268
1475
  transactions.forEach((t) => maps.push(...t.mapping.maps));
1269
1476
  const inserted = [];
1270
1477
  const restores = [];
1478
+ const tableInserts = [];
1271
1479
  const formatRanges = [];
1272
1480
  const blocks = /* @__PURE__ */ new Map();
1273
1481
  let mapIndex = 0;
@@ -1304,6 +1512,9 @@ function trackTransactions(transactions, oldState, newState, ctx) {
1304
1512
  return;
1305
1513
  }
1306
1514
  }
1515
+ if (ctx.trackBlocks && slice.size > 0) {
1516
+ collectInsertedTableNodes(slice, from, rest, tableInserts);
1517
+ }
1307
1518
  if (slice.size > 0 && hasInlineContent(slice.content)) {
1308
1519
  const start = rest.map(from, 1);
1309
1520
  const end = rest.map(from + slice.size, -1);
@@ -1311,7 +1522,8 @@ function trackTransactions(transactions, oldState, newState, ctx) {
1311
1522
  }
1312
1523
  if (to > from) {
1313
1524
  const deleted = docBefore.slice(from, to);
1314
- if (!hasInlineContent(deleted.content)) return;
1525
+ const wholeTable = ctx.trackBlocks && isWholeTableSlice(deleted);
1526
+ if (!wholeTable && !hasInlineContent(deleted.content)) return;
1315
1527
  const $from = docBefore.resolve(from);
1316
1528
  const before = i === 0 ? t.before : null;
1317
1529
  const sel = before === oldState.doc ? oldState.selection : null;
@@ -1320,13 +1532,14 @@ function trackTransactions(transactions, oldState, newState, ctx) {
1320
1532
  slice: deleted,
1321
1533
  parentType: $from.node(Math.max(0, $from.depth - deleted.openStart)).type,
1322
1534
  pureDelete: slice.size === 0,
1535
+ wholeTable,
1323
1536
  backspace: single && slice.size === 0 && !!sel && sel.empty && sel.from === to
1324
1537
  });
1325
1538
  }
1326
1539
  });
1327
1540
  mapIndex += t.steps.length;
1328
1541
  });
1329
- if (inserted.length === 0 && restores.length === 0 && formatRanges.length === 0 && blocks.size === 0) {
1542
+ if (inserted.length === 0 && tableInserts.length === 0 && restores.length === 0 && formatRanges.length === 0 && blocks.size === 0) {
1330
1543
  return null;
1331
1544
  }
1332
1545
  const tr = newState.tr;
@@ -1338,18 +1551,78 @@ function trackTransactions(transactions, oldState, newState, ctx) {
1338
1551
  if (blocks.size) {
1339
1552
  changed = trackBlocks(tr, blocks, ctx) || changed;
1340
1553
  }
1554
+ const moves = ctx.moves;
1555
+ let insertAttrs = null;
1556
+ let deleteAttrs = null;
1557
+ const moveAttrs = (id) => ({
1558
+ changeId: id,
1559
+ authorId: ctx.author.id,
1560
+ authorName: ctx.author.name,
1561
+ authorColor: ctx.author.color ?? null,
1562
+ createdAt: ctx.now(),
1563
+ [MOVE_ID_ATTRIBUTE]: id
1564
+ });
1565
+ if (moves && inserted.length) {
1566
+ const insertedText = normalizeMoveText(
1567
+ inserted.map((r) => rangeText(tr.doc, r.from, r.to)).join("")
1568
+ );
1569
+ const pending = moves.pending;
1570
+ if (moves.event === "paste" && pending && pending.authorId === ctx.author.id && insertedText === pending.text && linkMoveSource(tr, pending, ctx)) {
1571
+ insertAttrs = moveAttrs(pending.id);
1572
+ } else if (moves.event === "drop" && restores.length) {
1573
+ const raw = restores.map((r) => deletedText(r.slice, ctx)).join("\n");
1574
+ if (insertedText && normalizeMoveText(raw) === insertedText && moves.isMove(raw)) {
1575
+ insertAttrs = deleteAttrs = moveAttrs(ctx.generateId());
1576
+ }
1577
+ }
1578
+ }
1579
+ if (moves?.event === "cut" && restores.length) {
1580
+ deleteAttrs = newAttributes(ctx);
1581
+ const raw = restores.map((r) => deletedText(r.slice, ctx)).join("\n");
1582
+ if (normalizeMoveText(raw) && moves.isMove(raw)) {
1583
+ moves.cut = { id: deleteAttrs.changeId, text: normalizeMoveText(raw), authorId: ctx.author.id };
1584
+ }
1585
+ }
1341
1586
  inserted.forEach(({ from, to }) => {
1342
1587
  tr.removeMark(from, to, ctx.deletion);
1343
1588
  tr.removeMark(from, to, ctx.insertion);
1344
1589
  if (ctx.format) tr.removeMark(from, to, ctx.format);
1345
- const attrs = adjacentAttributes(tr.doc, from, to, ctx.insertion, ctx) ?? newAttributes(ctx);
1590
+ const attrs = insertAttrs ?? adjacentAttributes(tr.doc, from, to, ctx.insertion, ctx) ?? newAttributes(ctx);
1346
1591
  tr.addMark(from, to, ctx.insertion.create(attrs));
1347
1592
  changed = true;
1348
1593
  });
1594
+ if (insertAttrs) changed = true;
1595
+ if (tableInserts.length) {
1596
+ const attrs = newAttributes(ctx);
1597
+ tableInserts.forEach(({ from, to }) => {
1598
+ const start = tr.mapping.map(from, 1);
1599
+ const end = tr.mapping.map(to, -1);
1600
+ const node = tr.doc.nodeAt(start);
1601
+ if (end <= start || !node || !isTableStructure(node) || node.nodeSize !== end - start) return;
1602
+ tr.removeMark(start, end, ctx.insertion);
1603
+ changed = markTableBlocks(tr, start, end, "insert", attrs) || changed;
1604
+ });
1605
+ }
1606
+ let tableDeleteAttrs = null;
1349
1607
  let cursor = null;
1350
1608
  restores.sort((a, b) => b.pos - a.pos).forEach((restore) => {
1351
1609
  const pos = tr.mapping.map(restore.pos, -1);
1352
- const attrs = adjacentAttributes(tr.doc, pos, pos, ctx.deletion, ctx) ?? newAttributes(ctx);
1610
+ const attrs = deleteAttrs ?? adjacentAttributes(tr.doc, pos, pos, ctx.deletion, ctx) ?? newAttributes(ctx);
1611
+ if (restore.wholeTable && canMarkTableBlocks(restore.slice)) {
1612
+ if (isOwnTableInsertion(restore.slice, ctx)) return;
1613
+ tableDeleteAttrs = tableDeleteAttrs ?? deleteAttrs ?? newAttributes(ctx);
1614
+ const sizeBefore2 = tr.doc.content.size;
1615
+ try {
1616
+ tr.replace(pos, pos, restore.slice);
1617
+ } catch {
1618
+ return;
1619
+ }
1620
+ const end = pos + (tr.doc.content.size - sizeBefore2);
1621
+ markTableBlocks(tr, pos, end, "delete", tableDeleteAttrs);
1622
+ changed = true;
1623
+ return;
1624
+ }
1625
+ if (!hasInlineContent(restore.slice.content)) return;
1353
1626
  const marked = markDeletedSlice(restore.slice, restore.parentType, attrs, ctx);
1354
1627
  if (!marked || !marked.hasContent) return;
1355
1628
  const sizeBefore = tr.doc.content.size;
@@ -1415,7 +1688,10 @@ var TrackChanges = Extension.create({
1415
1688
  trackCodeBlocks: true,
1416
1689
  blockTypes: ["paragraph", "heading", "codeBlock"],
1417
1690
  ignoredMarks: [],
1418
- ignoredBlockAttributes: ["id"]
1691
+ ignoredBlockAttributes: ["id"],
1692
+ trackMoves: true,
1693
+ isMove: isMoveSized,
1694
+ moveHTMLAttributes: {}
1419
1695
  };
1420
1696
  },
1421
1697
  addStorage() {
@@ -1462,9 +1738,18 @@ var TrackChanges = Extension.create({
1462
1738
  const insertionAttrs = this.options?.insertionHTMLAttributes ?? {};
1463
1739
  const deletionAttrs = this.options?.deletionHTMLAttributes ?? {};
1464
1740
  const formatAttrs = this.options?.formatChangeHTMLAttributes ?? {};
1741
+ const moveAttrs = this.options?.moveHTMLAttributes ?? {};
1742
+ const markOptions = (attrs) => ({
1743
+ ...Object.keys(attrs).length ? { HTMLAttributes: attrs } : {},
1744
+ ...Object.keys(moveAttrs).length ? { moveHTMLAttributes: moveAttrs } : {}
1745
+ });
1746
+ const configured = (mark, attrs) => {
1747
+ const options = markOptions(attrs);
1748
+ return Object.keys(options).length ? mark.configure(options) : mark;
1749
+ };
1465
1750
  return [
1466
- Object.keys(insertionAttrs).length ? TrackedInsertion.configure({ HTMLAttributes: insertionAttrs }) : TrackedInsertion,
1467
- Object.keys(deletionAttrs).length ? TrackedDeletion.configure({ HTMLAttributes: deletionAttrs }) : TrackedDeletion,
1751
+ configured(TrackedInsertion, insertionAttrs),
1752
+ configured(TrackedDeletion, deletionAttrs),
1468
1753
  Object.keys(formatAttrs).length ? TrackedFormatChange.configure({ HTMLAttributes: formatAttrs }) : TrackedFormatChange
1469
1754
  ];
1470
1755
  },
@@ -1472,9 +1757,9 @@ var TrackChanges = Extension.create({
1472
1757
  if (this.options.trackCodeBlocks) allowTrackingInCodeBlocks(this.editor.schema);
1473
1758
  },
1474
1759
  addCommands() {
1475
- const decide = (decision, id) => ({ state, tr, dispatch }) => {
1760
+ const decide = (decision, id, onlyMoves = false) => ({ state, tr, dispatch }) => {
1476
1761
  const all = getTrackedChanges(state.doc);
1477
- const changes = id === null ? all : all.filter((c) => c.id === id);
1762
+ const changes = id === null ? all : all.filter((c) => c.id === id && (!onlyMoves || c.type === "move"));
1478
1763
  if (changes.length === 0) return false;
1479
1764
  if (dispatch) {
1480
1765
  applyDecision(tr, changes, decision);
@@ -1511,11 +1796,22 @@ var TrackChanges = Extension.create({
1511
1796
  return true;
1512
1797
  },
1513
1798
  acceptChange: (id) => decide("accept", id),
1799
+ acceptMove: (id) => decide("accept", id, true),
1800
+ rejectMove: (id) => decide("reject", id, true),
1514
1801
  rejectChange: (id) => decide("reject", id),
1515
1802
  acceptAllChanges: () => decide("accept", null),
1516
1803
  rejectAllChanges: () => decide("reject", null),
1517
1804
  selectNextChange: () => select(1),
1518
- selectPreviousChange: () => select(-1)
1805
+ selectPreviousChange: () => select(-1),
1806
+ selectChange: (id) => ({ state, tr, dispatch }) => {
1807
+ const change = getTrackedChanges(state.doc).find((c) => c.id === id);
1808
+ if (!change) return false;
1809
+ if (dispatch) {
1810
+ const [anchor, head] = selectionRange(change);
1811
+ tr.setSelection(TextSelection.create(tr.doc, anchor, head)).scrollIntoView();
1812
+ }
1813
+ return true;
1814
+ }
1519
1815
  };
1520
1816
  },
1521
1817
  addProseMirrorPlugins() {
@@ -1526,6 +1822,8 @@ var TrackChanges = Extension.create({
1526
1822
  let editorView = null;
1527
1823
  let pending = null;
1528
1824
  let flushTimer = null;
1825
+ let pendingMove = null;
1826
+ const isMove = options.isMove ?? isMoveSized;
1529
1827
  const scheduleFlush = () => {
1530
1828
  if (flushTimer) clearTimeout(flushTimer);
1531
1829
  flushTimer = setTimeout(() => {
@@ -1556,7 +1854,10 @@ var TrackChanges = Extension.create({
1556
1854
  appendTransaction: (transactions, oldState, newState) => {
1557
1855
  const plugin = trackChangesPluginKey.getState(oldState);
1558
1856
  const tracking = !!plugin?.enabled;
1559
- if (!tracking && !pending) return null;
1857
+ if (!tracking && !pending) {
1858
+ if (transactions.some((t) => t.docChanged)) pendingMove = null;
1859
+ return null;
1860
+ }
1560
1861
  if (editorView?.composing && transactions.some((t) => t.docChanged)) {
1561
1862
  if (tracking) {
1562
1863
  if (!pending) pending = { oldState, transactions: [] };
@@ -1573,7 +1874,17 @@ var TrackChanges = Extension.create({
1573
1874
  }
1574
1875
  if (!tracking && trs === transactions) return null;
1575
1876
  const author = plugin?.author ?? options.author;
1576
- return trackTransactions(trs, fromState, newState, {
1877
+ const userTransactions = trs.filter((t) => !shouldSkip(t));
1878
+ let moves;
1879
+ if (userTransactions.length) {
1880
+ const uiEvent = userTransactions.length === 1 ? userTransactions[0].getMeta("uiEvent") : null;
1881
+ const event = uiEvent === "cut" || uiEvent === "paste" || uiEvent === "drop" ? uiEvent : null;
1882
+ if (options.trackMoves !== false) {
1883
+ moves = { event, pending: event === "paste" ? pendingMove : null, isMove };
1884
+ }
1885
+ pendingMove = null;
1886
+ }
1887
+ const result = trackTransactions(trs, fromState, newState, {
1577
1888
  author,
1578
1889
  now: options.now,
1579
1890
  generateId: options.generateId,
@@ -1582,8 +1893,11 @@ var TrackChanges = Extension.create({
1582
1893
  format: options.trackFormatting !== false ? newState.schema.marks[FORMAT_CHANGE_MARK_NAME] : void 0,
1583
1894
  trackBlocks: options.trackBlocks !== false,
1584
1895
  ignoredMarks,
1585
- ignoredAttributes
1896
+ ignoredAttributes,
1897
+ moves
1586
1898
  });
1899
+ if (moves?.cut) pendingMove = moves.cut;
1900
+ return result;
1587
1901
  },
1588
1902
  props: {
1589
1903
  transformPasted: (slice, view) => acceptSlice(slice, view.state.schema),
@@ -1625,19 +1939,24 @@ export {
1625
1939
  DELETION_MARK_NAME,
1626
1940
  FORMAT_CHANGE_MARK_NAME,
1627
1941
  BLOCK_CHANGE_ATTRIBUTE,
1942
+ MOVE_ID_ATTRIBUTE,
1628
1943
  TRACK_CHANGES_SKIP_META,
1629
1944
  TrackChangesStore,
1630
1945
  trackChangesPluginKey,
1946
+ MOVE_TO_CLASS,
1947
+ MOVE_FROM_CLASS,
1631
1948
  TrackedInsertion,
1632
1949
  TrackedDeletion,
1633
1950
  TrackedFormatChange,
1634
1951
  describeFormatChange,
1635
1952
  describeBlockChange,
1953
+ trackedChangeLabel,
1636
1954
  getTrackedChanges,
1637
1955
  getTrackedChangeAt,
1638
1956
  isTrackChangesEnabled,
1639
1957
  getTrackChangesStore,
1640
1958
  resolveTrackedChanges,
1959
+ isMoveSized,
1641
1960
  TrackChanges
1642
1961
  };
1643
- //# sourceMappingURL=chunk-5S3Z4PPF.js.map
1962
+ //# sourceMappingURL=chunk-SWE3NPOU.js.map