@pixerate/editor 0.23.0 → 0.25.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 (187) hide show
  1. package/README.md +167 -2
  2. package/THIRD_PARTY_NOTICES.md +80 -0
  3. package/dist/agent/index.cjs +10 -8
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +4 -3
  6. package/dist/agent/index.d.ts +4 -3
  7. package/dist/agent/index.js +4 -2
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +4437 -411
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +934 -4
  12. package/dist/canvas/index.d.ts +934 -4
  13. package/dist/canvas/index.js +4309 -283
  14. package/dist/canvas/index.js.map +1 -1
  15. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  16. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  17. package/dist/chunk-664LQYFJ.js +115 -0
  18. package/dist/chunk-664LQYFJ.js.map +1 -0
  19. package/dist/chunk-7WISYZFO.cjs +740 -0
  20. package/dist/chunk-7WISYZFO.cjs.map +1 -0
  21. package/dist/chunk-AL6JMGWW.cjs +783 -0
  22. package/dist/chunk-AL6JMGWW.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-RIKBJLS2.cjs → chunk-C7P22VIP.cjs} +2001 -279
  26. package/dist/chunk-C7P22VIP.cjs.map +1 -0
  27. package/dist/chunk-D6FH2YAX.cjs +131 -0
  28. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  29. package/dist/chunk-ECCCYKGL.js +740 -0
  30. package/dist/chunk-ECCCYKGL.js.map +1 -0
  31. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  32. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  33. package/dist/{chunk-QBJ22YZR.js → chunk-FH6F67DN.js} +1999 -277
  34. package/dist/chunk-FH6F67DN.js.map +1 -0
  35. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  36. package/dist/chunk-FPENXAEJ.js.map +1 -0
  37. package/dist/chunk-HLIMTC6N.js +1 -0
  38. package/dist/chunk-HLIMTC6N.js.map +1 -0
  39. package/dist/chunk-HRMEDT2L.js +1406 -0
  40. package/dist/chunk-HRMEDT2L.js.map +1 -0
  41. package/dist/{chunk-QGHGJ56O.js → chunk-IXHSYOVY.js} +2518 -122
  42. package/dist/chunk-IXHSYOVY.js.map +1 -0
  43. package/dist/chunk-MKA3AHLV.cjs +256 -0
  44. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  45. package/dist/chunk-NAXVVS5I.js +783 -0
  46. package/dist/chunk-NAXVVS5I.js.map +1 -0
  47. package/dist/chunk-O5RYH2PG.js +69 -0
  48. package/dist/chunk-O5RYH2PG.js.map +1 -0
  49. package/dist/chunk-OXSV64JX.js +689 -0
  50. package/dist/chunk-OXSV64JX.js.map +1 -0
  51. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  52. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  53. package/dist/chunk-P6ODPZ4W.js +256 -0
  54. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  55. package/dist/chunk-PL4JB3BF.cjs +1 -0
  56. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  57. package/dist/{chunk-GVSMRPHB.cjs → chunk-PO6A6QQH.cjs} +2607 -211
  58. package/dist/chunk-PO6A6QQH.cjs.map +1 -0
  59. package/dist/{chunk-JJDLPEWL.js → chunk-QGYDWPRM.js} +2 -2
  60. package/dist/chunk-RR7F5GUM.js +5726 -0
  61. package/dist/chunk-RR7F5GUM.js.map +1 -0
  62. package/dist/chunk-TNNJXF46.cjs +115 -0
  63. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  64. package/dist/chunk-U5HZSFCS.js +131 -0
  65. package/dist/chunk-U5HZSFCS.js.map +1 -0
  66. package/dist/{chunk-MJZBNWG7.js → chunk-UAKDG62Z.js} +309 -581
  67. package/dist/chunk-UAKDG62Z.js.map +1 -0
  68. package/dist/{chunk-CL37JGFW.cjs → chunk-V3CHHWOE.cjs} +4 -4
  69. package/dist/{chunk-CL37JGFW.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
  70. package/dist/chunk-WSQKHXW7.cjs +5726 -0
  71. package/dist/chunk-WSQKHXW7.cjs.map +1 -0
  72. package/dist/{chunk-AA4ZEVCC.cjs → chunk-X4N4WUKB.cjs} +336 -608
  73. package/dist/chunk-X4N4WUKB.cjs.map +1 -0
  74. package/dist/commands/index.cjs +74 -1
  75. package/dist/commands/index.cjs.map +1 -1
  76. package/dist/commands/index.d.cts +38 -353
  77. package/dist/commands/index.d.ts +38 -353
  78. package/dist/commands/index.js +73 -0
  79. package/dist/commands/index.js.map +1 -1
  80. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  81. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  82. package/dist/compare/index.cjs +15 -0
  83. package/dist/compare/index.cjs.map +1 -0
  84. package/dist/compare/index.d.cts +58 -0
  85. package/dist/compare/index.d.ts +58 -0
  86. package/dist/compare/index.js +15 -0
  87. package/dist/compare/index.js.map +1 -0
  88. package/dist/controller-BIJiNoVs.d.cts +1842 -0
  89. package/dist/controller-BIJiNoVs.d.ts +1842 -0
  90. package/dist/documents-CHT1U5Vs.d.ts +86 -0
  91. package/dist/documents-Gp7wb0Me.d.cts +86 -0
  92. package/dist/extensions/index.cjs +100 -5
  93. package/dist/extensions/index.cjs.map +1 -1
  94. package/dist/extensions/index.d.cts +16 -4
  95. package/dist/extensions/index.d.ts +16 -4
  96. package/dist/extensions/index.js +100 -5
  97. package/dist/generation/index.cjs +2317 -0
  98. package/dist/generation/index.cjs.map +1 -0
  99. package/dist/generation/index.d.cts +1015 -0
  100. package/dist/generation/index.d.ts +1015 -0
  101. package/dist/generation/index.js +2317 -0
  102. package/dist/generation/index.js.map +1 -0
  103. package/dist/grammar/index.cjs +48 -3
  104. package/dist/grammar/index.cjs.map +1 -1
  105. package/dist/grammar/index.d.cts +322 -100
  106. package/dist/grammar/index.d.ts +322 -100
  107. package/dist/grammar/index.js +54 -9
  108. package/dist/image-editor/index.cjs +4392 -481
  109. package/dist/image-editor/index.cjs.map +1 -1
  110. package/dist/image-editor/index.d.cts +1249 -97
  111. package/dist/image-editor/index.d.ts +1249 -97
  112. package/dist/image-editor/index.js +4281 -370
  113. package/dist/image-editor/index.js.map +1 -1
  114. package/dist/index-7a6bHTLy.d.ts +1754 -0
  115. package/dist/index-Csd5qI8u.d.cts +1754 -0
  116. package/dist/index.cjs +1682 -99
  117. package/dist/index.cjs.map +1 -1
  118. package/dist/index.d.cts +118 -21
  119. package/dist/index.d.ts +118 -21
  120. package/dist/index.js +1630 -47
  121. package/dist/index.js.map +1 -1
  122. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  123. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  124. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  125. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  126. package/dist/model-C1zApQM8.d.cts +219 -0
  127. package/dist/model-C1zApQM8.d.ts +219 -0
  128. package/dist/persistence/index.cjs +676 -0
  129. package/dist/persistence/index.cjs.map +1 -0
  130. package/dist/persistence/index.d.cts +230 -0
  131. package/dist/persistence/index.d.ts +230 -0
  132. package/dist/persistence/index.js +676 -0
  133. package/dist/persistence/index.js.map +1 -0
  134. package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
  135. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.cts} +12 -1
  136. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.ts} +12 -1
  137. package/dist/registry-D32LpIc4.d.cts +354 -0
  138. package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
  139. package/dist/registry-evKTP4ls.d.ts +354 -0
  140. package/dist/schema-DYDGdYL9.d.cts +41 -0
  141. package/dist/schema-DYDGdYL9.d.ts +41 -0
  142. package/dist/serializers/index.cjs +12 -3
  143. package/dist/serializers/index.cjs.map +1 -1
  144. package/dist/serializers/index.d.cts +40 -12
  145. package/dist/serializers/index.d.ts +40 -12
  146. package/dist/serializers/index.js +12 -3
  147. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  148. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  149. package/dist/spreadsheet/index.cjs +161 -3
  150. package/dist/spreadsheet/index.cjs.map +1 -1
  151. package/dist/spreadsheet/index.d.cts +611 -20
  152. package/dist/spreadsheet/index.d.ts +611 -20
  153. package/dist/spreadsheet/index.js +168 -10
  154. package/dist/storage-BpFfspHm.d.cts +120 -0
  155. package/dist/storage-BpFfspHm.d.ts +120 -0
  156. package/dist/tokens/index.cjs +115 -0
  157. package/dist/tokens/index.cjs.map +1 -0
  158. package/dist/tokens/index.d.cts +212 -0
  159. package/dist/tokens/index.d.ts +212 -0
  160. package/dist/tokens/index.js +115 -0
  161. package/dist/tokens/index.js.map +1 -0
  162. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  163. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  164. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-D_VAPELi.d.cts} +110 -144
  165. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-Db_fGEvx.d.ts} +110 -144
  166. package/package.json +21 -1
  167. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  168. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  169. package/dist/chunk-AXV2ZW34.cjs +0 -118
  170. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  171. package/dist/chunk-B2ZRLN4S.js +0 -118
  172. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  173. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  174. package/dist/chunk-K3R5NG2D.js +0 -3100
  175. package/dist/chunk-K3R5NG2D.js.map +0 -1
  176. package/dist/chunk-MJZBNWG7.js.map +0 -1
  177. package/dist/chunk-OYXSKQFR.js.map +0 -1
  178. package/dist/chunk-QBJ22YZR.js.map +0 -1
  179. package/dist/chunk-QGHGJ56O.js.map +0 -1
  180. package/dist/chunk-RIKBJLS2.cjs.map +0 -1
  181. package/dist/chunk-XDMXXC4Z.cjs +0 -3100
  182. package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
  183. package/dist/controller-pePCJLT1.d.cts +0 -959
  184. package/dist/controller-pePCJLT1.d.ts +0 -959
  185. package/dist/index-CCr_z9kF.d.cts +0 -899
  186. package/dist/index-Ck6ajwXh.d.ts +0 -899
  187. /package/dist/{chunk-JJDLPEWL.js.map → chunk-QGYDWPRM.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
  }
@@ -630,9 +660,11 @@ function getTrackedChanges(source) {
630
660
  change.text += text;
631
661
  return change;
632
662
  };
663
+ const blockCounts = /* @__PURE__ */ new Map();
633
664
  doc.descendants((node, pos) => {
634
665
  const block = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE];
635
666
  if (block?.changeId && node.isTextblock) {
667
+ blockCounts.set(block.changeId, (blockCounts.get(block.changeId) ?? 0) + 1);
636
668
  const change = add(
637
669
  `block:${block.changeId}`,
638
670
  "block",
@@ -642,7 +674,11 @@ function getTrackedChanges(source) {
642
674
  node.textContent
643
675
  );
644
676
  if (!change.block) {
645
- change.block = { previous: block.previous, current: blockSnapshot(doc, pos) };
677
+ change.block = {
678
+ previous: block.previous,
679
+ current: blockSnapshot(doc, pos),
680
+ ...block.operation ? { operation: block.operation } : {}
681
+ };
646
682
  }
647
683
  }
648
684
  if (!node.isInline) return;
@@ -651,6 +687,20 @@ function getTrackedChanges(source) {
651
687
  if (!isMarkChange(markType)) return;
652
688
  const attrs = changeAttributes(mark);
653
689
  if (!attrs.changeId) return;
690
+ const text = node.isText ? node.text ?? "" : "";
691
+ const moveId = markType !== FORMAT_CHANGE_MARK_NAME ? attrs[MOVE_ID_ATTRIBUTE] : null;
692
+ if (moveId) {
693
+ const change2 = add(`move:${moveId}`, "move", { ...attrs, changeId: moveId }, pos, pos + node.nodeSize, text);
694
+ if (!change2.move) change2.move = { from: [], to: [], fromText: "", toText: "" };
695
+ const source2 = markType === DELETION_MARK_NAME;
696
+ const list = source2 ? change2.move.from : change2.move.to;
697
+ const last = list[list.length - 1];
698
+ if (last && last.to === pos) last.to = pos + node.nodeSize;
699
+ else list.push({ from: pos, to: pos + node.nodeSize });
700
+ if (source2) change2.move.fromText += text;
701
+ else change2.move.toText += text;
702
+ return;
703
+ }
654
704
  const type = markType === FORMAT_CHANGE_MARK_NAME ? "format" : markType;
655
705
  const change = add(
656
706
  `${type}:${attrs.changeId}`,
@@ -658,7 +708,7 @@ function getTrackedChanges(source) {
658
708
  attrs,
659
709
  pos,
660
710
  pos + node.nodeSize,
661
- node.isText ? node.text ?? "" : ""
711
+ text
662
712
  );
663
713
  if (type === "format") {
664
714
  const summary = diffFormatting(mark.attrs.before ?? [], mark.attrs.after ?? []);
@@ -679,9 +729,23 @@ function getTrackedChanges(source) {
679
729
  case "format":
680
730
  change.description = describeFormatChange(change.format);
681
731
  break;
682
- case "block":
683
- change.description = describeBlockChange(change.block.previous, change.block.current);
732
+ case "move": {
733
+ const move = change.move;
734
+ change.text = move.toText || move.fromText;
735
+ change.description = change.text ? `Moved "${truncate(change.text)}"` : "Moved content";
736
+ break;
737
+ }
738
+ case "block": {
739
+ const { previous, current, operation } = change.block;
740
+ if (operation) {
741
+ const count = blockCounts.get(change.id) ?? 1;
742
+ const verb = operation === "insert" ? "Inserted" : "Deleted";
743
+ change.description = count > 1 ? `${verb} ${count} blocks` : `${verb} ${blockLabel(current.type, current.attrs)}`;
744
+ } else {
745
+ change.description = describeBlockChange(previous, current);
746
+ }
684
747
  break;
748
+ }
685
749
  }
686
750
  });
687
751
  return changes.sort((a, b) => a.from - b.from || a.to - b.to);
@@ -698,7 +762,12 @@ function getTrackedChangeAt(state, pos = state.selection.from, changes = getTrac
698
762
  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
763
  }
700
764
  function selectionRange(change) {
701
- return change.type === "block" ? [change.from + 1, change.to - 1] : [change.from, change.to];
765
+ if (change.type === "block") return [change.from + 1, change.to - 1];
766
+ if (change.type === "move" && change.move) {
767
+ const ranges = change.move.to.length ? change.move.to : change.move.from;
768
+ if (ranges.length) return [ranges[0].from, ranges[ranges.length - 1].to];
769
+ }
770
+ return [change.from, change.to];
702
771
  }
703
772
  function isTrackChangesEnabled(source) {
704
773
  const state = source?.state ?? source;
@@ -877,8 +946,56 @@ function restoreWrappers(tr, contentPos, wrappers) {
877
946
  return;
878
947
  }
879
948
  }
949
+ function removeBlocks(tr, ids, decision) {
950
+ const removed = decision === "accept" ? "delete" : "insert";
951
+ const memo = /* @__PURE__ */ new Map();
952
+ const removable = (node) => {
953
+ const cached = memo.get(node);
954
+ if (cached !== void 0) return cached;
955
+ let result;
956
+ if (node.isTextblock) {
957
+ const record = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE];
958
+ result = !!record?.changeId && record.operation === removed && ids.has(record.changeId);
959
+ } else if (node.isLeaf || node.childCount === 0) {
960
+ result = false;
961
+ } else {
962
+ result = true;
963
+ for (let i = 0; i < node.childCount && result; i++) result = removable(node.child(i));
964
+ }
965
+ memo.set(node, result);
966
+ return result;
967
+ };
968
+ const doc = tr.doc;
969
+ if (removable(doc)) {
970
+ const filler = doc.type.contentMatch.defaultType?.createAndFill();
971
+ if (filler) tr.replaceWith(0, doc.content.size, filler);
972
+ return;
973
+ }
974
+ const ranges = [];
975
+ const visit = (node, contentStart) => {
976
+ node.forEach((child, offset) => {
977
+ const from = contentStart + offset;
978
+ if (removable(child)) {
979
+ ranges.push({ from, to: from + child.nodeSize });
980
+ } else if (!child.isTextblock && !child.isLeaf) {
981
+ visit(child, from + 1);
982
+ }
983
+ });
984
+ };
985
+ visit(doc, 0);
986
+ if (ranges.length === 0) return;
987
+ const startIndex = tr.steps.length;
988
+ ranges.reverse().forEach(({ from, to }) => {
989
+ const mapping = tr.mapping.slice(startIndex);
990
+ try {
991
+ tr.delete(mapping.map(from, 1), mapping.map(to, -1));
992
+ } catch {
993
+ }
994
+ });
995
+ }
880
996
  function decideBlocks(tr, changes, decision) {
881
997
  const ids = new Set(changes.map((c) => c.id));
998
+ removeBlocks(tr, ids, decision);
882
999
  const items = [];
883
1000
  tr.doc.descendants((node, pos) => {
884
1001
  const record = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE];
@@ -894,7 +1011,7 @@ function decideBlocks(tr, changes, decision) {
894
1011
  const $pos = tr.doc.resolve(contentPos);
895
1012
  if (!$pos.parent.isTextblock || $pos.parentOffset !== 0) return;
896
1013
  const pos = $pos.before();
897
- if (decision === "accept") {
1014
+ if (decision === "accept" || record.operation) {
898
1015
  tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);
899
1016
  return;
900
1017
  }
@@ -920,12 +1037,36 @@ function decideBlocks(tr, changes, decision) {
920
1037
  }
921
1038
  });
922
1039
  }
1040
+ function deleteWholeBlock(tr, from, to) {
1041
+ const $from = tr.doc.resolve(from);
1042
+ const block = $from.parent;
1043
+ if ($from.depth !== 1 || !block.isTextblock || from !== $from.start() || to !== $from.end() || tr.doc.childCount < 2) {
1044
+ return false;
1045
+ }
1046
+ try {
1047
+ tr.delete($from.before(), $from.after());
1048
+ return true;
1049
+ } catch {
1050
+ return false;
1051
+ }
1052
+ }
923
1053
  function applyDecision(tr, changes, decision) {
924
1054
  const schema = tr.doc.type.schema;
925
1055
  const format = changes.filter((c) => c.type === "format");
926
1056
  if (format.length) decideFormat(tr, format, decision);
927
1057
  const deletions = [];
928
1058
  changes.forEach((change) => {
1059
+ if (change.type === "move" && change.move) {
1060
+ const accept = decision === "accept";
1061
+ const keepType = schema.marks[accept ? INSERTION_MARK_NAME : DELETION_MARK_NAME];
1062
+ (accept ? change.move.to : change.move.from).forEach(({ from, to }) => {
1063
+ if (keepType) tr.removeMark(from, to, keepType);
1064
+ });
1065
+ (accept ? change.move.from : change.move.to).forEach(
1066
+ ({ from, to }) => deletions.push({ from, to, wholeBlocks: true })
1067
+ );
1068
+ return;
1069
+ }
929
1070
  if (change.type !== "insertion" && change.type !== "deletion") return;
930
1071
  const markType = schema.marks[change.type];
931
1072
  if (!markType) return;
@@ -938,8 +1079,11 @@ function applyDecision(tr, changes, decision) {
938
1079
  }
939
1080
  });
940
1081
  });
941
- deletions.sort((a, b) => b.from - a.from).forEach(({ from, to }) => {
942
- tr.delete(tr.mapping.map(from), tr.mapping.map(to));
1082
+ deletions.sort((a, b) => b.from - a.from).forEach(({ from, to, wholeBlocks }) => {
1083
+ const start = tr.mapping.map(from);
1084
+ const end = tr.mapping.map(to);
1085
+ if (wholeBlocks && deleteWholeBlock(tr, start, end)) return;
1086
+ tr.delete(start, end);
943
1087
  });
944
1088
  const blocks = changes.filter((c) => c.type === "block");
945
1089
  if (blocks.length) decideBlocks(tr, blocks, decision);
@@ -997,6 +1141,52 @@ function stripMarks(fragment) {
997
1141
  });
998
1142
  return Fragment.fromArray(children);
999
1143
  }
1144
+ var SENTENCE_END = /[.!?…]$/;
1145
+ var TRAILING_CLOSERS = /["'”’)]+$/;
1146
+ var TRAILING_SPACES = /[  ]+$/;
1147
+ function isMoveSized(text) {
1148
+ const trimmed = text.replace(TRAILING_SPACES, "");
1149
+ if (trimmed.includes("\n")) return true;
1150
+ const sentence = trimmed.replace(TRAILING_CLOSERS, "");
1151
+ return SENTENCE_END.test(sentence) && sentence.trim().split(/\s+/).length >= 2;
1152
+ }
1153
+ var normalizeMoveText = (text) => text.replace(/\s+/g, "");
1154
+ function deletedText(slice, ctx) {
1155
+ let text = "";
1156
+ let blocks = 0;
1157
+ slice.content.descendants((node) => {
1158
+ if (node.isTextblock) {
1159
+ if (blocks++ > 0) text += "\n";
1160
+ return true;
1161
+ }
1162
+ if (!node.isText) return true;
1163
+ const insertion = ctx.insertion.isInSet(node.marks);
1164
+ if (insertion && insertion.attrs.authorId === ctx.author.id) return false;
1165
+ if (ctx.deletion.isInSet(node.marks)) return false;
1166
+ text += node.text ?? "";
1167
+ return false;
1168
+ });
1169
+ return text;
1170
+ }
1171
+ var rangeText = (doc, from, to) => doc.textBetween(from, to, "\n", "");
1172
+ function linkMoveSource(tr, pending, ctx) {
1173
+ const segments = [];
1174
+ tr.doc.descendants((node, pos) => {
1175
+ if (!node.isInline) return true;
1176
+ const mark = ctx.deletion.isInSet(node.marks);
1177
+ if (mark && mark.attrs.changeId === pending.id && !mark.attrs[MOVE_ID_ATTRIBUTE]) {
1178
+ segments.push({ from: pos, to: pos + node.nodeSize, mark });
1179
+ }
1180
+ return false;
1181
+ });
1182
+ if (segments.length === 0) return false;
1183
+ const text = segments.map((s) => rangeText(tr.doc, s.from, s.to)).join("");
1184
+ if (normalizeMoveText(text) !== pending.text) return false;
1185
+ segments.forEach(
1186
+ ({ from, to, mark }) => tr.addMark(from, to, ctx.deletion.create({ ...mark.attrs, [MOVE_ID_ATTRIBUTE]: pending.id }))
1187
+ );
1188
+ return true;
1189
+ }
1000
1190
  function newAttributes(ctx) {
1001
1191
  return {
1002
1192
  changeId: ctx.generateId(),
@@ -1019,7 +1209,7 @@ function adjacentAttributes(doc, from, to, type, ctx) {
1019
1209
  const candidates = [doc.resolve(from).nodeBefore, doc.resolve(to).nodeAfter];
1020
1210
  for (const node of candidates) {
1021
1211
  const mark = node && type.isInSet(node.marks);
1022
- if (mark && mark.attrs.authorId === ctx.author.id && mark.attrs.changeId) {
1212
+ if (mark && mark.attrs.authorId === ctx.author.id && mark.attrs.changeId && !mark.attrs[MOVE_ID_ATTRIBUTE]) {
1023
1213
  return changeAttributes(mark);
1024
1214
  }
1025
1215
  }
@@ -1148,6 +1338,7 @@ function trackBlocks(tr, pending, ctx) {
1148
1338
  if (!node.isTextblock || $pos.parentOffset !== 0 || $pos.depth === 0) return;
1149
1339
  if (!node.type.spec.attrs || !(BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs)) return;
1150
1340
  const pos = $pos.before();
1341
+ if (existing?.operation || node.attrs[BLOCK_CHANGE_ATTRIBUTE]?.operation) return;
1151
1342
  const original = existing?.previous ?? previous;
1152
1343
  const current = blockSnapshot(tr.doc, pos);
1153
1344
  if (sameBlock(original, current, ctx.ignoredAttributes)) {
@@ -1275,18 +1466,51 @@ function trackTransactions(transactions, oldState, newState, ctx) {
1275
1466
  if (blocks.size) {
1276
1467
  changed = trackBlocks(tr, blocks, ctx) || changed;
1277
1468
  }
1469
+ const moves = ctx.moves;
1470
+ let insertAttrs = null;
1471
+ let deleteAttrs = null;
1472
+ const moveAttrs = (id) => ({
1473
+ changeId: id,
1474
+ authorId: ctx.author.id,
1475
+ authorName: ctx.author.name,
1476
+ authorColor: ctx.author.color ?? null,
1477
+ createdAt: ctx.now(),
1478
+ [MOVE_ID_ATTRIBUTE]: id
1479
+ });
1480
+ if (moves && inserted.length) {
1481
+ const insertedText = normalizeMoveText(
1482
+ inserted.map((r) => rangeText(tr.doc, r.from, r.to)).join("")
1483
+ );
1484
+ const pending = moves.pending;
1485
+ if (moves.event === "paste" && pending && pending.authorId === ctx.author.id && insertedText === pending.text && linkMoveSource(tr, pending, ctx)) {
1486
+ insertAttrs = moveAttrs(pending.id);
1487
+ } else if (moves.event === "drop" && restores.length) {
1488
+ const raw = restores.map((r) => deletedText(r.slice, ctx)).join("\n");
1489
+ if (insertedText && normalizeMoveText(raw) === insertedText && moves.isMove(raw)) {
1490
+ insertAttrs = deleteAttrs = moveAttrs(ctx.generateId());
1491
+ }
1492
+ }
1493
+ }
1494
+ if (moves?.event === "cut" && restores.length) {
1495
+ deleteAttrs = newAttributes(ctx);
1496
+ const raw = restores.map((r) => deletedText(r.slice, ctx)).join("\n");
1497
+ if (normalizeMoveText(raw) && moves.isMove(raw)) {
1498
+ moves.cut = { id: deleteAttrs.changeId, text: normalizeMoveText(raw), authorId: ctx.author.id };
1499
+ }
1500
+ }
1278
1501
  inserted.forEach(({ from, to }) => {
1279
1502
  tr.removeMark(from, to, ctx.deletion);
1280
1503
  tr.removeMark(from, to, ctx.insertion);
1281
1504
  if (ctx.format) tr.removeMark(from, to, ctx.format);
1282
- const attrs = adjacentAttributes(tr.doc, from, to, ctx.insertion, ctx) ?? newAttributes(ctx);
1505
+ const attrs = insertAttrs ?? adjacentAttributes(tr.doc, from, to, ctx.insertion, ctx) ?? newAttributes(ctx);
1283
1506
  tr.addMark(from, to, ctx.insertion.create(attrs));
1284
1507
  changed = true;
1285
1508
  });
1509
+ if (insertAttrs) changed = true;
1286
1510
  let cursor = null;
1287
1511
  restores.sort((a, b) => b.pos - a.pos).forEach((restore) => {
1288
1512
  const pos = tr.mapping.map(restore.pos, -1);
1289
- const attrs = adjacentAttributes(tr.doc, pos, pos, ctx.deletion, ctx) ?? newAttributes(ctx);
1513
+ const attrs = deleteAttrs ?? adjacentAttributes(tr.doc, pos, pos, ctx.deletion, ctx) ?? newAttributes(ctx);
1290
1514
  const marked = markDeletedSlice(restore.slice, restore.parentType, attrs, ctx);
1291
1515
  if (!marked || !marked.hasContent) return;
1292
1516
  const sizeBefore = tr.doc.content.size;
@@ -1345,12 +1569,17 @@ var TrackChanges = Extension.create({
1345
1569
  deletionHTMLAttributes: {},
1346
1570
  formatChangeHTMLAttributes: {},
1347
1571
  blockChangeClass: "pixerate-block-change border-l-2 border-amber-400 pl-2 dark:border-amber-500",
1572
+ blockInsertedClass: "pixerate-block-inserted underline decoration-emerald-500 text-emerald-700 dark:text-emerald-400",
1573
+ blockDeletedClass: "pixerate-block-deleted line-through decoration-red-500 text-red-600 dark:text-red-400",
1348
1574
  trackFormatting: true,
1349
1575
  trackBlocks: true,
1350
1576
  trackCodeBlocks: true,
1351
1577
  blockTypes: ["paragraph", "heading", "codeBlock"],
1352
1578
  ignoredMarks: [],
1353
- ignoredBlockAttributes: ["id"]
1579
+ ignoredBlockAttributes: ["id"],
1580
+ trackMoves: true,
1581
+ isMove: isMoveSized,
1582
+ moveHTMLAttributes: {}
1354
1583
  };
1355
1584
  },
1356
1585
  addStorage() {
@@ -1366,6 +1595,8 @@ var TrackChanges = Extension.create({
1366
1595
  addGlobalAttributes() {
1367
1596
  if (this.options?.trackBlocks === false) return [];
1368
1597
  const blockClass = this.options?.blockChangeClass ?? "";
1598
+ const insertedClass = this.options?.blockInsertedClass ?? "";
1599
+ const deletedClass = this.options?.blockDeletedClass ?? "";
1369
1600
  return [
1370
1601
  {
1371
1602
  types: this.options?.blockTypes ?? ["paragraph", "heading", "codeBlock"],
@@ -1377,9 +1608,13 @@ var TrackChanges = Extension.create({
1377
1608
  renderHTML: (attrs) => {
1378
1609
  const value = attrs[BLOCK_CHANGE_ATTRIBUTE];
1379
1610
  if (!value) return {};
1611
+ const operation = value.operation;
1612
+ const extra = operation === "insert" ? insertedClass : operation === "delete" ? deletedClass : "";
1613
+ const className = [blockClass, extra].filter(Boolean).join(" ");
1380
1614
  return {
1381
1615
  "data-block-change": JSON.stringify(value),
1382
- ...blockClass ? { class: blockClass } : {}
1616
+ ...operation ? { "data-block-operation": operation } : {},
1617
+ ...className ? { class: className } : {}
1383
1618
  };
1384
1619
  }
1385
1620
  }
@@ -1391,9 +1626,18 @@ var TrackChanges = Extension.create({
1391
1626
  const insertionAttrs = this.options?.insertionHTMLAttributes ?? {};
1392
1627
  const deletionAttrs = this.options?.deletionHTMLAttributes ?? {};
1393
1628
  const formatAttrs = this.options?.formatChangeHTMLAttributes ?? {};
1629
+ const moveAttrs = this.options?.moveHTMLAttributes ?? {};
1630
+ const markOptions = (attrs) => ({
1631
+ ...Object.keys(attrs).length ? { HTMLAttributes: attrs } : {},
1632
+ ...Object.keys(moveAttrs).length ? { moveHTMLAttributes: moveAttrs } : {}
1633
+ });
1634
+ const configured = (mark, attrs) => {
1635
+ const options = markOptions(attrs);
1636
+ return Object.keys(options).length ? mark.configure(options) : mark;
1637
+ };
1394
1638
  return [
1395
- Object.keys(insertionAttrs).length ? TrackedInsertion.configure({ HTMLAttributes: insertionAttrs }) : TrackedInsertion,
1396
- Object.keys(deletionAttrs).length ? TrackedDeletion.configure({ HTMLAttributes: deletionAttrs }) : TrackedDeletion,
1639
+ configured(TrackedInsertion, insertionAttrs),
1640
+ configured(TrackedDeletion, deletionAttrs),
1397
1641
  Object.keys(formatAttrs).length ? TrackedFormatChange.configure({ HTMLAttributes: formatAttrs }) : TrackedFormatChange
1398
1642
  ];
1399
1643
  },
@@ -1401,9 +1645,9 @@ var TrackChanges = Extension.create({
1401
1645
  if (this.options.trackCodeBlocks) allowTrackingInCodeBlocks(this.editor.schema);
1402
1646
  },
1403
1647
  addCommands() {
1404
- const decide = (decision, id) => ({ state, tr, dispatch }) => {
1648
+ const decide = (decision, id, onlyMoves = false) => ({ state, tr, dispatch }) => {
1405
1649
  const all = getTrackedChanges(state.doc);
1406
- const changes = id === null ? all : all.filter((c) => c.id === id);
1650
+ const changes = id === null ? all : all.filter((c) => c.id === id && (!onlyMoves || c.type === "move"));
1407
1651
  if (changes.length === 0) return false;
1408
1652
  if (dispatch) {
1409
1653
  applyDecision(tr, changes, decision);
@@ -1440,11 +1684,22 @@ var TrackChanges = Extension.create({
1440
1684
  return true;
1441
1685
  },
1442
1686
  acceptChange: (id) => decide("accept", id),
1687
+ acceptMove: (id) => decide("accept", id, true),
1688
+ rejectMove: (id) => decide("reject", id, true),
1443
1689
  rejectChange: (id) => decide("reject", id),
1444
1690
  acceptAllChanges: () => decide("accept", null),
1445
1691
  rejectAllChanges: () => decide("reject", null),
1446
1692
  selectNextChange: () => select(1),
1447
- selectPreviousChange: () => select(-1)
1693
+ selectPreviousChange: () => select(-1),
1694
+ selectChange: (id) => ({ state, tr, dispatch }) => {
1695
+ const change = getTrackedChanges(state.doc).find((c) => c.id === id);
1696
+ if (!change) return false;
1697
+ if (dispatch) {
1698
+ const [anchor, head] = selectionRange(change);
1699
+ tr.setSelection(TextSelection.create(tr.doc, anchor, head)).scrollIntoView();
1700
+ }
1701
+ return true;
1702
+ }
1448
1703
  };
1449
1704
  },
1450
1705
  addProseMirrorPlugins() {
@@ -1455,6 +1710,8 @@ var TrackChanges = Extension.create({
1455
1710
  let editorView = null;
1456
1711
  let pending = null;
1457
1712
  let flushTimer = null;
1713
+ let pendingMove = null;
1714
+ const isMove = options.isMove ?? isMoveSized;
1458
1715
  const scheduleFlush = () => {
1459
1716
  if (flushTimer) clearTimeout(flushTimer);
1460
1717
  flushTimer = setTimeout(() => {
@@ -1485,7 +1742,10 @@ var TrackChanges = Extension.create({
1485
1742
  appendTransaction: (transactions, oldState, newState) => {
1486
1743
  const plugin = trackChangesPluginKey.getState(oldState);
1487
1744
  const tracking = !!plugin?.enabled;
1488
- if (!tracking && !pending) return null;
1745
+ if (!tracking && !pending) {
1746
+ if (transactions.some((t) => t.docChanged)) pendingMove = null;
1747
+ return null;
1748
+ }
1489
1749
  if (editorView?.composing && transactions.some((t) => t.docChanged)) {
1490
1750
  if (tracking) {
1491
1751
  if (!pending) pending = { oldState, transactions: [] };
@@ -1502,7 +1762,17 @@ var TrackChanges = Extension.create({
1502
1762
  }
1503
1763
  if (!tracking && trs === transactions) return null;
1504
1764
  const author = plugin?.author ?? options.author;
1505
- return trackTransactions(trs, fromState, newState, {
1765
+ const userTransactions = trs.filter((t) => !shouldSkip(t));
1766
+ let moves;
1767
+ if (userTransactions.length) {
1768
+ const uiEvent = userTransactions.length === 1 ? userTransactions[0].getMeta("uiEvent") : null;
1769
+ const event = uiEvent === "cut" || uiEvent === "paste" || uiEvent === "drop" ? uiEvent : null;
1770
+ if (options.trackMoves !== false) {
1771
+ moves = { event, pending: event === "paste" ? pendingMove : null, isMove };
1772
+ }
1773
+ pendingMove = null;
1774
+ }
1775
+ const result = trackTransactions(trs, fromState, newState, {
1506
1776
  author,
1507
1777
  now: options.now,
1508
1778
  generateId: options.generateId,
@@ -1511,8 +1781,11 @@ var TrackChanges = Extension.create({
1511
1781
  format: options.trackFormatting !== false ? newState.schema.marks[FORMAT_CHANGE_MARK_NAME] : void 0,
1512
1782
  trackBlocks: options.trackBlocks !== false,
1513
1783
  ignoredMarks,
1514
- ignoredAttributes
1784
+ ignoredAttributes,
1785
+ moves
1515
1786
  });
1787
+ if (moves?.cut) pendingMove = moves.cut;
1788
+ return result;
1516
1789
  },
1517
1790
  props: {
1518
1791
  transformPasted: (slice, view) => acceptSlice(slice, view.state.schema),
@@ -1545,540 +1818,6 @@ var TrackChanges = Extension.create({
1545
1818
  }
1546
1819
  });
1547
1820
 
1548
- // src/serializers/plain-text.ts
1549
- import { Slice as Slice2, Fragment as Fragment2, Node as ProsemirrorNode } from "@tiptap/pm/model";
1550
- var TRACKED_DELETION_REGEX = /<del\b(?=[^>]*\bdata-change-type=["']deletion["'])[^>]*>[\s\S]*?<\/del>/gi;
1551
- var TRACKED_INSERTION_REGEX = /<ins\b(?=[^>]*\bdata-change-type=["']insertion["'])[^>]*>[\s\S]*?<\/ins>/gi;
1552
- function stripTrackedChanges(html, view) {
1553
- if (view === "all" || !html.includes("data-change-type")) return html;
1554
- return html.replace(
1555
- view === "accepted" ? TRACKED_DELETION_REGEX : TRACKED_INSERTION_REGEX,
1556
- ""
1557
- );
1558
- }
1559
- function escapeHtml(text) {
1560
- if (!text) return "";
1561
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1562
- }
1563
- var PARAGRAPH_HTML_REGEX = /^\s*<p[\s>][\s\S]*<\/p>\s*$/i;
1564
- function plainTextToTipTapHtml(text) {
1565
- if (!text) return "<p></p>";
1566
- if (PARAGRAPH_HTML_REGEX.test(text)) {
1567
- return text;
1568
- }
1569
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => `<p>${escapeHtml(line)}</p>`).join("");
1570
- }
1571
- function plainTextToSlice(text, schema) {
1572
- const nodes = [];
1573
- text.replace(/\r\n?/g, "\n").split("\n").forEach((line) => {
1574
- const nodeJson = { type: "paragraph" };
1575
- if (line.length > 0) {
1576
- nodeJson.content = [{ type: "text", text: line }];
1577
- }
1578
- try {
1579
- nodes.push(ProsemirrorNode.fromJSON(schema, nodeJson));
1580
- } catch {
1581
- }
1582
- });
1583
- return Slice2.maxOpen(Fragment2.fromArray(nodes));
1584
- }
1585
- var BLOCK_TAGS = /* @__PURE__ */ new Set([
1586
- "P",
1587
- "DIV",
1588
- "LI",
1589
- "UL",
1590
- "OL",
1591
- "PRE",
1592
- "BLOCKQUOTE",
1593
- "TABLE",
1594
- "TR",
1595
- "H1",
1596
- "H2",
1597
- "H3",
1598
- "H4",
1599
- "H5",
1600
- "H6",
1601
- "HR"
1602
- ]);
1603
- var MENTION_SPAN_REGEX = /<span\b(?=[^>]*\bdata-type=["']mention["'])([^>]*)>[\s\S]*?<\/span>/gi;
1604
- function getHtmlAttribute(attrs, name) {
1605
- const match = new RegExp(`\\b${name}=(?:"([^"]*)"|'([^']*)')`, "i").exec(
1606
- attrs
1607
- );
1608
- return match ? match[1] ?? match[2] : void 0;
1609
- }
1610
- function parseHtmlFragment(html) {
1611
- const template = document.createElement("template");
1612
- template.innerHTML = html;
1613
- return template.content;
1614
- }
1615
- function decodeHtmlEntities(text) {
1616
- return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0*39;/g, "'").replace(/&nbsp;/g, "\xA0").replace(/&amp;/g, "&");
1617
- }
1618
- function htmlToPlainText(html, options = {}) {
1619
- if (!html) return "";
1620
- const mentionNormalized = stripTrackedChanges(
1621
- html,
1622
- options.trackedChanges ?? "accepted"
1623
- ).replace(MENTION_SPAN_REGEX, (match, attrs) => {
1624
- const label = getHtmlAttribute(attrs, "data-label") || getHtmlAttribute(attrs, "data-id");
1625
- return label ? `@${label}` : match;
1626
- });
1627
- if (typeof document !== "undefined") {
1628
- const fragment = parseHtmlFragment(mentionNormalized);
1629
- fragment.querySelectorAll("br.ProseMirror-trailingBreak").forEach((br) => br.remove());
1630
- const lines = [];
1631
- let current = null;
1632
- const flush = () => {
1633
- if (current !== null) {
1634
- lines.push(current);
1635
- current = null;
1636
- }
1637
- };
1638
- const visit = (node) => {
1639
- if (node.nodeType === 3) {
1640
- const value = node.nodeValue ?? "";
1641
- if (current === null && value.trim() === "") return;
1642
- current = (current ?? "") + value;
1643
- return;
1644
- }
1645
- if (node.nodeType !== 1) return;
1646
- const tag = node.tagName.toUpperCase();
1647
- if (tag === "BR") {
1648
- current = (current ?? "") + "\n";
1649
- return;
1650
- }
1651
- if (!BLOCK_TAGS.has(tag)) {
1652
- node.childNodes.forEach(visit);
1653
- return;
1654
- }
1655
- flush();
1656
- const linesBefore = lines.length;
1657
- node.childNodes.forEach(visit);
1658
- flush();
1659
- if (lines.length === linesBefore && tag !== "HR") {
1660
- lines.push("");
1661
- }
1662
- };
1663
- fragment.childNodes.forEach(visit);
1664
- flush();
1665
- return lines.join("\n");
1666
- }
1667
- const text = mentionNormalized.replace(/<br\s*\/?>/gi, "\n").replace(
1668
- /<\/(?:p|div|li|pre|blockquote|h[1-6])>(?:\s*<\/(?:li|ul|ol|blockquote|div)>)*/gi,
1669
- "\n"
1670
- ).replace(/<[^>]+>/g, "");
1671
- return decodeHtmlEntities(text).replace(/\n$/, "");
1672
- }
1673
- function getEditorText(editor, options = {}) {
1674
- if (!editor) return "";
1675
- const html = typeof editor.getHTML === "function" ? editor.getHTML() : "";
1676
- if (html) {
1677
- return htmlToPlainText(html, options);
1678
- }
1679
- if (typeof editor.getText === "function") {
1680
- try {
1681
- return editor.getText({ blockSeparator: "\n" });
1682
- } catch {
1683
- return editor.getText();
1684
- }
1685
- }
1686
- return "";
1687
- }
1688
- function extractMentionsFromDoc(docOrEditor) {
1689
- if (!docOrEditor) return [];
1690
- const mentions = [];
1691
- const doc = docOrEditor?.state?.doc || (typeof docOrEditor?.descendants === "function" ? docOrEditor : null);
1692
- if (doc && typeof doc.descendants === "function") {
1693
- doc.descendants((node) => {
1694
- if (node.type?.name === "mention" && node.attrs) {
1695
- mentions.push({
1696
- id: node.attrs.id || "",
1697
- label: node.attrs.label || "",
1698
- type: node.attrs.type,
1699
- avatarUrl: node.attrs.avatarUrl,
1700
- color: node.attrs.color,
1701
- ...node.attrs
1702
- });
1703
- }
1704
- });
1705
- return mentions;
1706
- }
1707
- if (typeof docOrEditor === "object" && docOrEditor.content) {
1708
- const walk = (nodes) => {
1709
- for (const node of nodes) {
1710
- if (node.type === "mention" && node.attrs) {
1711
- mentions.push({
1712
- id: node.attrs.id || "",
1713
- label: node.attrs.label || "",
1714
- type: node.attrs.type,
1715
- avatarUrl: node.attrs.avatarUrl,
1716
- color: node.attrs.color,
1717
- ...node.attrs
1718
- });
1719
- }
1720
- if (Array.isArray(node.content)) {
1721
- walk(node.content);
1722
- }
1723
- }
1724
- };
1725
- walk(docOrEditor.content);
1726
- return mentions;
1727
- }
1728
- if (typeof docOrEditor === "string") {
1729
- if (typeof document !== "undefined") {
1730
- parseHtmlFragment(docOrEditor).querySelectorAll('span[data-type="mention"]').forEach((el) => {
1731
- mentions.push({
1732
- id: el.getAttribute("data-id") || "",
1733
- label: el.getAttribute("data-label") || "",
1734
- type: el.getAttribute("data-type-name") || void 0,
1735
- avatarUrl: el.getAttribute("data-avatar-url") || void 0,
1736
- color: el.getAttribute("data-color") || void 0
1737
- });
1738
- });
1739
- } else {
1740
- const spanRegex = /<span([^>]*data-type=["']mention["'][^>]*)>/gi;
1741
- let match;
1742
- while ((match = spanRegex.exec(docOrEditor)) !== null) {
1743
- const attrStr = match[1];
1744
- const getAttr = (name) => {
1745
- const m = new RegExp(`${name}=["']([^"']*)["']`, "i").exec(attrStr);
1746
- return m ? m[1] : void 0;
1747
- };
1748
- mentions.push({
1749
- id: getAttr("data-id") || "",
1750
- label: getAttr("data-label") || "",
1751
- type: getAttr("data-type-name") || getAttr("data-entity-type"),
1752
- avatarUrl: getAttr("data-avatar-url"),
1753
- color: getAttr("data-color")
1754
- });
1755
- }
1756
- }
1757
- if (mentions.length === 0) {
1758
- const rawHandles = extractMentions(docOrEditor);
1759
- for (const handle of rawHandles) {
1760
- mentions.push({
1761
- id: handle,
1762
- label: handle,
1763
- type: "user"
1764
- });
1765
- }
1766
- }
1767
- }
1768
- return mentions;
1769
- }
1770
-
1771
- // src/serializers/mentions.ts
1772
- var MENTION_REGEX = /(?:^|[\s>(*_"';\\/[\]{}~`:]|&quot;|&gt;|&lt;|&amp;)@(?:"([^"]+)"|&quot;([^&]+)&quot;|([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*))/g;
1773
- var MARKDOWN_MENTION_SPLIT_REGEX = /(```[\s\S]*?```|~~~[\s\S]*?~~~|<pre[\s\S]*?<\/pre>|<code[\s\S]*?<\/code>|`[^`\n]+`|<span\b[^>]*\b(?:data-type=["']mention["']|class=["'][^"']*pixerate-mention-node[^"']*["']|data-mention=["'][^"']*["'])[^>]*>[\s\S]*?<\/span>|<[^>]+>|\]\([^)]+\)|(?:https?|file|vscode|cursor|mailto):\/\/[^\s<)]+)/gi;
1774
- function stripMarkdownCode(content) {
1775
- if (!content || typeof content !== "string") return "";
1776
- return content.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/<pre[\s\S]*?<\/pre>/gi, "").replace(/<code[\s\S]*?<\/code>/gi, "").replace(/`[^`\n]*`/g, "");
1777
- }
1778
- function extractMentions(content) {
1779
- if (!content || typeof content !== "string") return [];
1780
- const normalizedMentionNodes = content.replace(
1781
- /<span\b[^>]*\b(?:data-type=["']mention["']|class=["'][^"']*pixerate-mention-node[^"']*["'])[^>]*>([\s\S]*?)<\/span>/gi,
1782
- (match2, inner) => {
1783
- const labelMatch = match2.match(/\bdata-label=["']([^"']+)["']/i);
1784
- if (labelMatch) return ` @${labelMatch[1]} `;
1785
- const idMatch = match2.match(/\bdata-id=["']([^"']+)["']/i);
1786
- if (idMatch) return ` @${idMatch[1]} `;
1787
- return ` ${inner} `;
1788
- }
1789
- );
1790
- const cleanContent = stripMarkdownCode(normalizedMentionNodes);
1791
- const withoutUrls = cleanContent.replace(/(?:https?|ftp|file|vscode|cursor|mailto):\/\/[^\s<>()]+/gi, "").replace(/\]\([^)]*\)/g, "]");
1792
- const textOnly = withoutUrls.replace(/<[^>]+>/g, " ");
1793
- const mentions = [];
1794
- const seen = /* @__PURE__ */ new Set();
1795
- const regex = new RegExp(MENTION_REGEX.source, "g");
1796
- let match;
1797
- while ((match = regex.exec(textOnly)) !== null) {
1798
- const handle = (match[1] ?? match[2] ?? match[3] ?? "").trim();
1799
- if (handle && !seen.has(handle)) {
1800
- seen.add(handle);
1801
- mentions.push(handle);
1802
- }
1803
- }
1804
- return mentions;
1805
- }
1806
- function parseMentionSegments(content) {
1807
- if (!content) return [];
1808
- const segments = [];
1809
- const regex = new RegExp(MENTION_REGEX.source, "g");
1810
- let lastIndex = 0;
1811
- let match;
1812
- while ((match = regex.exec(content)) !== null) {
1813
- const fullMatch = match[0];
1814
- const atIndex = fullMatch.indexOf("@");
1815
- const prefix = atIndex > 0 ? fullMatch.slice(0, atIndex) : "";
1816
- const mentionToken = fullMatch.slice(atIndex);
1817
- const handle = match[1] ?? match[2] ?? match[3];
1818
- const matchStart = match.index + prefix.length;
1819
- if (matchStart > lastIndex) {
1820
- segments.push({
1821
- type: "text",
1822
- value: content.slice(lastIndex, matchStart)
1823
- });
1824
- }
1825
- segments.push({
1826
- type: "mention",
1827
- value: mentionToken,
1828
- handle
1829
- });
1830
- lastIndex = matchStart + mentionToken.length;
1831
- }
1832
- if (lastIndex < content.length) {
1833
- segments.push({
1834
- type: "text",
1835
- value: content.slice(lastIndex)
1836
- });
1837
- }
1838
- return segments;
1839
- }
1840
- function preprocessMarkdownMentions(content, optionsOrMentionables = []) {
1841
- if (!content || typeof content !== "string") return "";
1842
- const options = Array.isArray(optionsOrMentionables) ? { mentionables: optionsOrMentionables } : optionsOrMentionables;
1843
- const allMentionables = options.mentionables || [];
1844
- const defaultType = options.defaultType || "user";
1845
- const resolveMention = options.resolveMention;
1846
- const parts = content.split(MARKDOWN_MENTION_SPLIT_REGEX);
1847
- return parts.map((part) => {
1848
- if (!part) return "";
1849
- if (part.startsWith("```") || part.startsWith("~~~") || part.startsWith("`") || part.startsWith("](") || /^(?:https?|file|vscode|cursor|mailto):\/\//i.test(part) || /^<pre[\s>]/i.test(part) || /^<code[\s>]/i.test(part) || /^<[^>]+>$/i.test(part) || /^<span\b[^>]*\b(?:data-type=["']mention["']|class=["'][^"']*pixerate-mention-node[^"']*["']|data-mention=["'][^"']*["'])/i.test(
1850
- part
1851
- )) {
1852
- return part;
1853
- }
1854
- const regex = new RegExp(MENTION_REGEX.source, "g");
1855
- return part.replace(regex, (match, quotedHandle, escapedQuotedHandle, standardHandle) => {
1856
- const atIndex = match.indexOf("@");
1857
- const prefix = atIndex > 0 ? match.slice(0, atIndex) : "";
1858
- const rawHandle = (quotedHandle || escapedQuotedHandle || standardHandle || "").trim();
1859
- if (!rawHandle) return match;
1860
- const lowerHandle = rawHandle.toLowerCase();
1861
- const entity = allMentionables.find((e) => {
1862
- if (e.label?.toLowerCase() === lowerHandle) return true;
1863
- if (e.handle?.toLowerCase() === lowerHandle) return true;
1864
- if (e.name?.toLowerCase() === lowerHandle) return true;
1865
- if (e.id?.toLowerCase() === lowerHandle) return true;
1866
- if (e.type === "agent" && `agent${(e.handle || e.label || "").toLowerCase()}` === lowerHandle) return true;
1867
- if (e.aliases?.some(
1868
- (a) => a.toLowerCase() === lowerHandle || e.type === "agent" && `agent${a.toLowerCase()}` === lowerHandle
1869
- )) {
1870
- return true;
1871
- }
1872
- return false;
1873
- });
1874
- let id = entity?.id;
1875
- let label = entity?.label || entity?.handle || rawHandle;
1876
- let type = entity?.type;
1877
- let avatarUrl = entity?.avatarUrl;
1878
- let color = entity?.color || entity?.hexColor;
1879
- if (!entity) {
1880
- const customResolved = resolveMention?.(rawHandle);
1881
- if (customResolved) {
1882
- id = customResolved.id ?? id;
1883
- label = customResolved.label ?? customResolved.handle ?? label;
1884
- type = customResolved.type ?? type;
1885
- avatarUrl = customResolved.avatarUrl ?? avatarUrl;
1886
- color = customResolved.color ?? customResolved.hexColor ?? color;
1887
- } else {
1888
- const stripped = lowerHandle.replace(/^agent[\s\-_]*/i, "");
1889
- if (["supervisor", "coordinator", "planner", "validator", "executor"].includes(stripped)) {
1890
- type = "agent";
1891
- id = stripped;
1892
- label = stripped;
1893
- } else if (lowerHandle === "agents") {
1894
- type = "team";
1895
- id = "agents";
1896
- label = "agents";
1897
- } else {
1898
- type = defaultType;
1899
- id = rawHandle;
1900
- label = rawHandle;
1901
- }
1902
- }
1903
- }
1904
- const avatarAttr = avatarUrl ? ` data-avatar-url="${escapeHtml(avatarUrl)}"` : "";
1905
- const colorAttr = color ? ` data-color="${escapeHtml(color)}"` : "";
1906
- return `${prefix}<span class="pixerate-mention-node" data-type="mention" data-id="${escapeHtml(
1907
- id || label
1908
- )}" data-label="${escapeHtml(label)}" data-mention-suggestion-char="@" data-type-name="${escapeHtml(
1909
- type || defaultType
1910
- )}"${avatarAttr}${colorAttr}>@${escapeHtml(label)}</span>`;
1911
- });
1912
- }).join("");
1913
- }
1914
-
1915
- // src/serializers/markdown.ts
1916
- import MarkdownIt from "markdown-it";
1917
- import taskLists from "markdown-it-task-lists";
1918
- var md = new MarkdownIt({
1919
- html: true,
1920
- linkify: true,
1921
- breaks: true
1922
- }).use(taskLists);
1923
- var defaultValidateLink = md.validateLink;
1924
- md.validateLink = (url) => {
1925
- if (url.startsWith("file://") || url.startsWith("vscode://") || url.startsWith("cursor://")) {
1926
- return true;
1927
- }
1928
- return defaultValidateLink.call(md, url);
1929
- };
1930
- var ALLOWED_HTML_TAGS = /* @__PURE__ */ new Set([
1931
- "a",
1932
- "abbr",
1933
- "b",
1934
- "blockquote",
1935
- "br",
1936
- "code",
1937
- "del",
1938
- "details",
1939
- "div",
1940
- "em",
1941
- "h1",
1942
- "h2",
1943
- "h3",
1944
- "h4",
1945
- "h5",
1946
- "h6",
1947
- "hr",
1948
- "i",
1949
- "img",
1950
- "input",
1951
- "kbd",
1952
- "li",
1953
- "mark",
1954
- "ol",
1955
- "p",
1956
- "pre",
1957
- "s",
1958
- "span",
1959
- "strong",
1960
- "sub",
1961
- "summary",
1962
- "sup",
1963
- "table",
1964
- "tbody",
1965
- "td",
1966
- "tfoot",
1967
- "th",
1968
- "thead",
1969
- "tr",
1970
- "u",
1971
- "ul"
1972
- ]);
1973
- var ALLOWED_HTML_ATTRS = /* @__PURE__ */ new Set([
1974
- "alt",
1975
- "align",
1976
- "checked",
1977
- "class",
1978
- "colspan",
1979
- "disabled",
1980
- "height",
1981
- "href",
1982
- "rel",
1983
- "rowspan",
1984
- "src",
1985
- "start",
1986
- "target",
1987
- "title",
1988
- "type",
1989
- "width"
1990
- ]);
1991
- var HTML_TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:\s+[^\s"'>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*\/?>/g;
1992
- var HTML_ATTR_REGEX = /([^\s"'>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
1993
- function sanitizeTag(tagText, isClosing, rawTagName, rawAttrs, stripDisallowed) {
1994
- const tagName = rawTagName.toLowerCase();
1995
- const reject = () => stripDisallowed ? "" : escapeHtml(tagText);
1996
- if (!ALLOWED_HTML_TAGS.has(tagName)) return reject();
1997
- if (isClosing) return `</${tagName}>`;
1998
- if (tagName === "input" && !/\stype\s*=\s*["']?checkbox["']?[\s>/]/i.test(tagText)) {
1999
- return reject();
2000
- }
2001
- let attrs = "";
2002
- let attrMatch;
2003
- HTML_ATTR_REGEX.lastIndex = 0;
2004
- while ((attrMatch = HTML_ATTR_REGEX.exec(rawAttrs)) !== null) {
2005
- const name = attrMatch[1].toLowerCase();
2006
- if (!ALLOWED_HTML_ATTRS.has(name) && !/^data-[a-z0-9_.-]+$/.test(name)) {
2007
- continue;
2008
- }
2009
- const rawValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4];
2010
- if (rawValue === void 0) {
2011
- attrs += ` ${name}`;
2012
- continue;
2013
- }
2014
- const value = md.utils.unescapeAll(rawValue);
2015
- if ((name === "href" || name === "src") && // Browsers ignore control characters and whitespace inside schemes.
2016
- !md.validateLink(value.replace(/[\u0000-\u0020\u007f]/g, ""))) {
2017
- continue;
2018
- }
2019
- attrs += ` ${name}="${escapeHtml(value)}"`;
2020
- }
2021
- return `<${tagName}${attrs}>`;
2022
- }
2023
- function sanitizeHtml(html, options = {}) {
2024
- return sanitizeEmbeddedHtml(html, options.disallowedTags === "strip");
2025
- }
2026
- function sanitizeEmbeddedHtml(html, stripDisallowed = false) {
2027
- let result = "";
2028
- let lastIndex = 0;
2029
- let match;
2030
- HTML_TAG_REGEX.lastIndex = 0;
2031
- while ((match = HTML_TAG_REGEX.exec(html)) !== null) {
2032
- result += html.slice(lastIndex, match.index).replace(/</g, "&lt;");
2033
- result += sanitizeTag(match[0], match[1], match[2], match[3], stripDisallowed);
2034
- lastIndex = match.index + match[0].length;
2035
- }
2036
- return result + html.slice(lastIndex).replace(/</g, "&lt;");
2037
- }
2038
- var defaultHtmlInline = md.renderer.rules.html_inline;
2039
- var defaultHtmlBlock = md.renderer.rules.html_block;
2040
- md.renderer.rules.html_inline = (tokens, idx, options, env, self) => env?.allowUnsafeHtml ? defaultHtmlInline(tokens, idx, options, env, self) : sanitizeEmbeddedHtml(tokens[idx].content);
2041
- md.renderer.rules.html_block = (tokens, idx, options, env, self) => env?.allowUnsafeHtml ? defaultHtmlBlock(tokens, idx, options, env, self) : sanitizeEmbeddedHtml(tokens[idx].content);
2042
- function preprocessMarkdownFileLinks(content) {
2043
- if (!content) return "";
2044
- return content.replace(
2045
- /\[([^\]]+)\]\(((?:file|vscode|cursor):\/\/[^)]+)\)/g,
2046
- (_match, rawLabel, href) => {
2047
- const trimmed = rawLabel.trim();
2048
- const codeMatch = trimmed.match(/^`([^`]+)`$/);
2049
- const labelContent = codeMatch ? `<code>${escapeHtml(codeMatch[1])}</code>` : escapeHtml(trimmed);
2050
- return `<a href="${escapeHtml(href)}" data-file-link="true" title="Click to copy file path">${labelContent}</a>`;
2051
- }
2052
- );
2053
- }
2054
- function markdownToTipTapHtml(markdown, options) {
2055
- if (!markdown) return "<p></p>";
2056
- let content = markdown;
2057
- if (options?.preprocessFileLinks) {
2058
- content = preprocessMarkdownFileLinks(content);
2059
- }
2060
- if (options?.preprocessMentions || options?.mentionables || options?.preprocessOptions) {
2061
- const mentionOpts = options.preprocessOptions || {
2062
- mentionables: options.mentionables
2063
- };
2064
- content = preprocessMarkdownMentions(content, mentionOpts);
2065
- }
2066
- return md.render(content, { allowUnsafeHtml: options?.allowUnsafeHtml });
2067
- }
2068
- function getEditorMarkdown(editor, options = {}) {
2069
- if (!editor) return "";
2070
- const markdown = editor.storage?.markdown;
2071
- if (typeof markdown?.getMarkdown === "function") {
2072
- const doc = editor.state?.doc;
2073
- if (doc && typeof markdown.serializer?.serialize === "function") {
2074
- const resolved = resolveTrackedChanges(doc, options.trackedChanges ?? "accepted");
2075
- if (resolved !== doc) return markdown.serializer.serialize(resolved);
2076
- }
2077
- return markdown.getMarkdown();
2078
- }
2079
- return getEditorText(editor, options);
2080
- }
2081
-
2082
1821
  export {
2083
1822
  createReviewId,
2084
1823
  CommentStore,
@@ -2088,35 +1827,24 @@ export {
2088
1827
  DELETION_MARK_NAME,
2089
1828
  FORMAT_CHANGE_MARK_NAME,
2090
1829
  BLOCK_CHANGE_ATTRIBUTE,
1830
+ MOVE_ID_ATTRIBUTE,
2091
1831
  TRACK_CHANGES_SKIP_META,
2092
1832
  TrackChangesStore,
2093
1833
  trackChangesPluginKey,
1834
+ MOVE_TO_CLASS,
1835
+ MOVE_FROM_CLASS,
2094
1836
  TrackedInsertion,
2095
1837
  TrackedDeletion,
2096
1838
  TrackedFormatChange,
2097
1839
  describeFormatChange,
2098
1840
  describeBlockChange,
1841
+ trackedChangeLabel,
2099
1842
  getTrackedChanges,
2100
1843
  getTrackedChangeAt,
2101
1844
  isTrackChangesEnabled,
2102
1845
  getTrackChangesStore,
2103
1846
  resolveTrackedChanges,
2104
- TrackChanges,
2105
- MENTION_REGEX,
2106
- MARKDOWN_MENTION_SPLIT_REGEX,
2107
- stripMarkdownCode,
2108
- extractMentions,
2109
- parseMentionSegments,
2110
- preprocessMarkdownMentions,
2111
- escapeHtml,
2112
- plainTextToTipTapHtml,
2113
- plainTextToSlice,
2114
- htmlToPlainText,
2115
- getEditorText,
2116
- extractMentionsFromDoc,
2117
- sanitizeHtml,
2118
- preprocessMarkdownFileLinks,
2119
- markdownToTipTapHtml,
2120
- getEditorMarkdown
1847
+ isMoveSized,
1848
+ TrackChanges
2121
1849
  };
2122
- //# sourceMappingURL=chunk-MJZBNWG7.js.map
1850
+ //# sourceMappingURL=chunk-UAKDG62Z.js.map