@pixerate/editor 0.24.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 (133) hide show
  1. package/README.md +71 -1
  2. package/THIRD_PARTY_NOTICES.md +29 -0
  3. package/dist/agent/index.cjs +10 -9
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +1 -1
  6. package/dist/agent/index.d.ts +1 -1
  7. package/dist/agent/index.js +4 -3
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +3173 -340
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +617 -4
  12. package/dist/canvas/index.d.ts +617 -4
  13. package/dist/canvas/index.js +3105 -272
  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-XNORY7M6.cjs → chunk-7WISYZFO.cjs} +105 -50
  18. package/dist/chunk-7WISYZFO.cjs.map +1 -0
  19. package/dist/{chunk-OLZJXL4R.cjs → chunk-AL6JMGWW.cjs} +59 -25
  20. package/dist/chunk-AL6JMGWW.cjs.map +1 -0
  21. package/dist/chunk-BVP3TSLH.cjs +69 -0
  22. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  23. package/dist/{chunk-MCSJFWAO.cjs → chunk-C7P22VIP.cjs} +670 -96
  24. package/dist/chunk-C7P22VIP.cjs.map +1 -0
  25. package/dist/{chunk-PGNZKFQS.js → chunk-ECCCYKGL.js} +76 -21
  26. package/dist/chunk-ECCCYKGL.js.map +1 -0
  27. package/dist/{chunk-3CN2WD3X.js → chunk-FH6F67DN.js} +646 -72
  28. package/dist/chunk-FH6F67DN.js.map +1 -0
  29. package/dist/chunk-HRMEDT2L.js +1406 -0
  30. package/dist/chunk-HRMEDT2L.js.map +1 -0
  31. package/dist/{chunk-ITBSKI26.js → chunk-IXHSYOVY.js} +941 -9
  32. package/dist/chunk-IXHSYOVY.js.map +1 -0
  33. package/dist/{chunk-FODNLX5N.js → chunk-NAXVVS5I.js} +38 -4
  34. package/dist/chunk-NAXVVS5I.js.map +1 -0
  35. package/dist/chunk-O5RYH2PG.js +69 -0
  36. package/dist/chunk-O5RYH2PG.js.map +1 -0
  37. package/dist/{chunk-SBCZ6RN5.cjs → chunk-PO6A6QQH.cjs} +1039 -107
  38. package/dist/chunk-PO6A6QQH.cjs.map +1 -0
  39. package/dist/{chunk-3LV6RRPH.js → chunk-QGYDWPRM.js} +2 -2
  40. package/dist/{chunk-U5KWZU4X.js → chunk-RR7F5GUM.js} +2632 -129
  41. package/dist/chunk-RR7F5GUM.js.map +1 -0
  42. package/dist/{chunk-5S3Z4PPF.js → chunk-UAKDG62Z.js} +232 -25
  43. package/dist/chunk-UAKDG62Z.js.map +1 -0
  44. package/dist/{chunk-SML64VJG.cjs → chunk-V3CHHWOE.cjs} +4 -4
  45. package/dist/{chunk-SML64VJG.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
  46. package/dist/chunk-WSQKHXW7.cjs +5726 -0
  47. package/dist/chunk-WSQKHXW7.cjs.map +1 -0
  48. package/dist/{chunk-D27YFC46.cjs → chunk-X4N4WUKB.cjs} +267 -60
  49. package/dist/chunk-X4N4WUKB.cjs.map +1 -0
  50. package/dist/commands/index.cjs +74 -1
  51. package/dist/commands/index.cjs.map +1 -1
  52. package/dist/commands/index.d.cts +34 -1
  53. package/dist/commands/index.d.ts +34 -1
  54. package/dist/commands/index.js +73 -0
  55. package/dist/commands/index.js.map +1 -1
  56. package/dist/compare/index.cjs +3 -3
  57. package/dist/compare/index.d.cts +1 -1
  58. package/dist/compare/index.d.ts +1 -1
  59. package/dist/compare/index.js +2 -2
  60. package/dist/controller-BIJiNoVs.d.cts +1842 -0
  61. package/dist/controller-BIJiNoVs.d.ts +1842 -0
  62. package/dist/{documents-BQjzJNe2.d.ts → documents-CHT1U5Vs.d.ts} +10 -0
  63. package/dist/{documents-KRTPhvst.d.cts → documents-Gp7wb0Me.d.cts} +10 -0
  64. package/dist/extensions/index.cjs +53 -6
  65. package/dist/extensions/index.cjs.map +1 -1
  66. package/dist/extensions/index.d.cts +12 -4
  67. package/dist/extensions/index.d.ts +12 -4
  68. package/dist/extensions/index.js +53 -6
  69. package/dist/generation/index.cjs +1013 -69
  70. package/dist/generation/index.cjs.map +1 -1
  71. package/dist/generation/index.d.cts +474 -13
  72. package/dist/generation/index.d.ts +474 -13
  73. package/dist/generation/index.js +976 -32
  74. package/dist/generation/index.js.map +1 -1
  75. package/dist/image-editor/index.cjs +3385 -1275
  76. package/dist/image-editor/index.cjs.map +1 -1
  77. package/dist/image-editor/index.d.cts +693 -128
  78. package/dist/image-editor/index.d.ts +693 -128
  79. package/dist/image-editor/index.js +3325 -1215
  80. package/dist/image-editor/index.js.map +1 -1
  81. package/dist/{index-fJrlkJCd.d.ts → index-7a6bHTLy.d.ts} +494 -5
  82. package/dist/{index-BhJBSm2d.d.cts → index-Csd5qI8u.d.cts} +494 -5
  83. package/dist/index.cjs +1491 -45
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.d.cts +110 -19
  86. package/dist/index.d.ts +110 -19
  87. package/dist/index.js +1464 -18
  88. package/dist/index.js.map +1 -1
  89. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  90. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  91. package/dist/model-C1zApQM8.d.cts +219 -0
  92. package/dist/model-C1zApQM8.d.ts +219 -0
  93. package/dist/persistence/index.d.cts +3 -120
  94. package/dist/persistence/index.d.ts +3 -120
  95. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.cts} +12 -1
  96. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.ts} +12 -1
  97. package/dist/serializers/index.cjs +12 -4
  98. package/dist/serializers/index.cjs.map +1 -1
  99. package/dist/serializers/index.d.cts +39 -12
  100. package/dist/serializers/index.d.ts +39 -12
  101. package/dist/serializers/index.js +12 -4
  102. package/dist/spreadsheet/index.cjs +127 -3
  103. package/dist/spreadsheet/index.cjs.map +1 -1
  104. package/dist/spreadsheet/index.d.cts +431 -11
  105. package/dist/spreadsheet/index.d.ts +431 -11
  106. package/dist/spreadsheet/index.js +127 -3
  107. package/dist/storage-BpFfspHm.d.cts +120 -0
  108. package/dist/storage-BpFfspHm.d.ts +120 -0
  109. package/dist/tokens/index.cjs +115 -0
  110. package/dist/tokens/index.cjs.map +1 -0
  111. package/dist/tokens/index.d.cts +212 -0
  112. package/dist/tokens/index.d.ts +212 -0
  113. package/dist/tokens/index.js +115 -0
  114. package/dist/tokens/index.js.map +1 -0
  115. package/dist/{track-changes-BK5sVf4_.d.cts → track-changes-D_VAPELi.d.cts} +95 -11
  116. package/dist/{track-changes-CDjCrwKe.d.ts → track-changes-Db_fGEvx.d.ts} +95 -11
  117. package/package.json +6 -1
  118. package/dist/chunk-3CN2WD3X.js.map +0 -1
  119. package/dist/chunk-5S3Z4PPF.js.map +0 -1
  120. package/dist/chunk-D27YFC46.cjs.map +0 -1
  121. package/dist/chunk-FODNLX5N.js.map +0 -1
  122. package/dist/chunk-ITBSKI26.js.map +0 -1
  123. package/dist/chunk-MA2F5KHF.cjs +0 -3223
  124. package/dist/chunk-MA2F5KHF.cjs.map +0 -1
  125. package/dist/chunk-MCSJFWAO.cjs.map +0 -1
  126. package/dist/chunk-OLZJXL4R.cjs.map +0 -1
  127. package/dist/chunk-PGNZKFQS.js.map +0 -1
  128. package/dist/chunk-SBCZ6RN5.cjs.map +0 -1
  129. package/dist/chunk-U5KWZU4X.js.map +0 -1
  130. package/dist/chunk-XNORY7M6.cjs.map +0 -1
  131. package/dist/controller-BhLLAoi8.d.cts +0 -1000
  132. package/dist/controller-BhLLAoi8.d.ts +0 -1000
  133. /package/dist/{chunk-3LV6RRPH.js.map → chunk-QGYDWPRM.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-7WISYZFO.cjs","../src/serializers/plain-text.ts","../src/serializers/html-scan.ts","../src/serializers/mentions.ts","../src/serializers/markdown.ts"],"names":["match"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;ACZA,yCAAqF;ADcrF;AACA;AEcO,SAAS,YAAA,CACd,IAAA,EACA,GAAA,EACA,QAAA,EACA,YAAA,EACW;AACX,EAAA,MAAM,QAAA,EAAU,OAAO,SAAA,IAAa,WAAA,EAAa,SAAA,EAAW,CAAC,KAAA,EAAA,GAAkB,QAAA,CAAS,IAAA,CAAK,KAAK,CAAA;AAClG,EAAA,MAAM,MAAA,EAAmB,CAAC,CAAA;AAC1B,EAAA,MAAM,QAAA,EAAU,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,GAAG,CAAA,GAAA,CAAA,EAAO,IAAI,CAAA;AAC7C,EAAA,MAAM,SAAA,EAAW,IAAI,MAAA,CAAO,CAAA,EAAA,EAAK,GAAG,CAAA,CAAA,CAAA,EAAK,IAAI,CAAA;AAC7C,EAAA,MAAM,YAAA,EAAc,GAAA,CAAI,OAAA,EAAS,CAAA;AACjC,EAAA,IAAI,OAAA,EAAS,CAAA,CAAA;AACb,EAAA,IAAI,UAAA,EAAY,CAAA,CAAA;AAChB,EAAA,IAAI,KAAA,EAAO,CAAA;AACX,EAAA,IAAA,CAAA,EAAA,EAAA,EAAS;AACP,IAAA,OAAA,CAAQ,UAAA,EAAY,IAAA;AACpB,IAAA,MAAM,KAAA,EAAO,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAC9B,IAAA,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA;AACX,IAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,KAAA;AACnB,IAAA,MAAM,WAAA,EAAa,MAAA,EAAQ,IAAA,CAAK,CAAC,CAAA,CAAE,MAAA;AACnC,IAAA,GAAA,CAAI,OAAA,EAAS,UAAA,EAAY;AACvB,MAAA,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,UAAU,CAAA;AACrC,MAAA,GAAA,CAAI,OAAA,IAAW,CAAA,CAAA,EAAI,KAAA;AAAA,IACrB;AACA,IAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,UAAA,EAAY,MAAM,CAAA;AAC3C,IAAA,MAAM,QAAA,EAAU,OAAA,EAAS,CAAA;AACzB,IAAA,GAAA,CAAI,CAAC,OAAA,CAAQ,KAAK,CAAA,EAAG;AACnB,MAAA,KAAA,EAAO,UAAA;AACP,MAAA,QAAA;AAAA,IACF;AACA,IAAA,GAAA,CAAI,CAAC,YAAA,EAAc;AACjB,MAAA,KAAA,CAAM,IAAA,CAAK,EAAE,KAAA,EAAO,OAAA,EAAS,GAAA,EAAK,OAAA,EAAS,MAAM,CAAC,CAAA;AAClD,MAAA,KAAA,EAAO,OAAA;AACP,MAAA,QAAA;AAAA,IACF;AACA,IAAA,GAAA,CAAI,UAAA,EAAY,OAAA,EAAS;AACvB,MAAA,QAAA,CAAS,UAAA,EAAY,OAAA;AACrB,MAAA,MAAM,MAAA,EAAQ,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AAChC,MAAA,GAAA,CAAI,CAAC,KAAA,EAAO,KAAA;AACZ,MAAA,UAAA,EAAY,KAAA,CAAM,KAAA;AAAA,IACpB;AACA,IAAA,MAAM,IAAA,EAAM,UAAA,EAAY,WAAA;AACxB,IAAA,KAAA,CAAM,IAAA,CAAK,EAAE,KAAA,EAAO,OAAA,EAAS,GAAA,EAAK,MAAM,CAAC,CAAA;AACzC,IAAA,KAAA,EAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,eAAA,CACd,IAAA,EACA,GAAA,EACA,QAAA,EACA,OAAA,EACQ;AACR,EAAA,MAAM,MAAA,EAAQ,YAAA,CAAa,IAAA,EAAM,GAAA,EAAK,QAAA,EAAU,IAAI,CAAA;AACpD,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,CAAA,EAAG,OAAO,IAAA;AAC/B,EAAA,IAAI,IAAA,EAAM,EAAA;AACV,EAAA,IAAI,KAAA,EAAO,CAAA;AACX,EAAA,IAAA,CAAA,MAAW,KAAA,GAAQ,KAAA,EAAO;AACxB,IAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,IAAA,CAAK,KAAK,EAAA,EAAI,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,GAAG,CAAA,EAAG,IAAA,CAAK,KAAK,CAAA;AAC1F,IAAA,KAAA,EAAO,IAAA,CAAK,GAAA;AAAA,EACd;AACA,EAAA,OAAO,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B;AAOO,SAAS,SAAA,CAAU,IAAA,EAAc,YAAA,EAAc,EAAA,EAAY;AAChE,EAAA,IAAI,IAAA,EAAM,EAAA;AACV,EAAA,IAAI,KAAA,EAAO,CAAA;AACX,EAAA,IAAA,CAAA,EAAA,EAAA,EAAS;AACP,IAAA,MAAM,GAAA,EAAK,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,IAAI,CAAA;AACjC,IAAA,GAAA,CAAI,GAAA,IAAO,CAAA,CAAA,EAAI,KAAA;AACf,IAAA,MAAM,GAAA,EAAK,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,GAAA,EAAK,CAAC,CAAA;AACnC,IAAA,GAAA,CAAI,GAAA,IAAO,CAAA,CAAA,EAAI,KAAA;AACf,IAAA,GAAA,CAAI,GAAA,IAAO,GAAA,EAAK,CAAA,EAAG;AAEjB,MAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,GAAA,EAAK,CAAC,CAAA;AAAA,IAChC,EAAA,KAAO;AACL,MAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,EAAE,EAAA,EAAI,WAAA;AAAA,IAChC;AACA,IAAA,KAAA,EAAO,GAAA,EAAK,CAAA;AAAA,EACd;AACA,EAAA,OAAO,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B;AASO,SAAS,gBAAA,CACd,IAAA,EACA,IAAA,EACA,KAAA,EACA,QAAA,EAAqC,CAAA,EAAA,GAAM,EAAA,EAC3C,qBAAA,EAAuB,KAAA,EACf;AACR,EAAA,MAAM,SAAA,EAAW,qBAAA,EAAuB,UAAA,CAAW,IAAI,EAAA,EAAI,IAAA;AAC3D,EAAA,MAAM,OAAA,EAAS,qBAAA,EAAuB,KAAA,CAAM,WAAA,CAAY,EAAA,EAAI,KAAA;AAC5D,EAAA,IAAI,IAAA,EAAM,EAAA;AACV,EAAA,IAAI,KAAA,EAAO,CAAA;AACX,EAAA,IAAI,KAAA,EAAO,CAAA;AACX,EAAA,IAAI,UAAA,EAAY,CAAA,CAAA;AAChB,EAAA,IAAA,CAAA,EAAA,EAAA,EAAS;AACP,IAAA,IAAA,CAAK,UAAA,EAAY,IAAA;AACjB,IAAA,MAAM,OAAA,EAAS,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA;AAC7B,IAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,KAAA;AACb,IAAA,MAAM,UAAA,EAAY,MAAA,CAAO,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,CAAE,MAAA,EAAQ,CAAC,CAAA;AAC7D,IAAA,GAAA,CAAI,UAAA,EAAY,SAAA,EAAW;AACzB,MAAA,UAAA,EAAY,QAAA,CAAS,OAAA,CAAQ,MAAA,EAAQ,SAAS,CAAA;AAC9C,MAAA,GAAA,CAAI,UAAA,IAAc,CAAA,CAAA,EAAI,KAAA;AAAA,IACxB;AACA,IAAA,MAAM,IAAA,EAAM,UAAA,EAAY,KAAA,CAAM,MAAA;AAC9B,IAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,MAAA,CAAO,KAAK,EAAA,EAAI,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,KAAA,EAAO,GAAG,CAAC,CAAA;AAC7E,IAAA,KAAA,EAAO,KAAA,EAAO,GAAA;AAAA,EAChB;AACA,EAAA,OAAO,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B;AAGA,SAAS,UAAA,CAAW,IAAA,EAAsB;AACxC,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,WAAA,CAAY,CAAC,CAAA;AACvD;AF/CA;AACA;ACzFA,IAAM,2BAAA,EAA6B,sCAAA;AACnC,IAAM,4BAAA,EAA8B,uCAAA;AAEpC,SAAS,mBAAA,CAAoB,IAAA,EAAc,IAAA,EAAkC;AAC3E,EAAA,GAAA,CAAI,KAAA,IAAS,MAAA,GAAS,CAAC,IAAA,CAAK,QAAA,CAAS,kBAAkB,CAAA,EAAG,OAAO,IAAA;AACjE,EAAA,OAAO,KAAA,IAAS,WAAA,EACZ,eAAA,CAAgB,IAAA,EAAM,KAAA,EAAO,0BAAA,EAA4B,CAAA,EAAA,GAAM,EAAE,EAAA,EACjE,eAAA,CAAgB,IAAA,EAAM,KAAA,EAAO,2BAAA,EAA6B,CAAA,EAAA,GAAM,EAAE,CAAA;AACxE;AAKO,SAAS,UAAA,CAAW,IAAA,EAAsB;AAC/C,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,EAAA;AAClB,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CACrB,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA,CACpB,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA,CACpB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA;AAC3B;AAMA,IAAM,qBAAA,EAAuB,8BAAA;AAOtB,SAAS,qBAAA,CAAsB,IAAA,EAAsB;AAC1D,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,SAAA;AAClB,EAAA,GAAA,CAAI,oBAAA,CAAqB,IAAA,CAAK,IAAI,CAAA,EAAG;AACnC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,OAAA,EAAS,IAAI,CAAA,CACrB,OAAA,CAAQ,KAAA,EAAO,IAAI,CAAA,CACnB,KAAA,CAAM,IAAI,CAAA,CACV,GAAA,CAAI,CAAC,IAAA,EAAA,GAAS,CAAA,GAAA,EAAM,UAAA,CAAW,IAAI,CAAC,CAAA,IAAA,CAAM,CAAA,CAC1C,IAAA,CAAK,EAAE,CAAA;AACZ;AAOO,SAAS,gBAAA,CAAiB,IAAA,EAAc,MAAA,EAAuB;AACpE,EAAA,MAAM,MAAA,EAA2B,CAAC,CAAA;AAClC,EAAA,IAAA,CACG,OAAA,CAAQ,QAAA,EAAU,IAAI,CAAA,CACtB,KAAA,CAAM,IAAI,CAAA,CACV,OAAA,CAAQ,CAAC,IAAA,EAAA,GAAS;AACjB,IAAA,MAAM,SAAA,EAAgB,EAAE,IAAA,EAAM,YAAY,CAAA;AAC1C,IAAA,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,CAAA,EAAG;AACnB,MAAA,QAAA,CAAS,QAAA,EAAU,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,IAClD;AACA,IAAA,IAAI;AACF,MAAA,KAAA,CAAM,IAAA,CAAK,WAAA,CAAgB,QAAA,CAAS,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAAA,IACvD,EAAA,WAAQ;AAAA,IAER;AAAA,EACF,CAAC,CAAA;AACH,EAAA,OAAO,YAAA,CAAM,OAAA,CAAQ,eAAA,CAAS,SAAA,CAAU,KAAK,CAAC,CAAA;AAChD;AAKA,IAAM,WAAA,kBAAa,IAAI,GAAA,CAAI;AAAA,EACzB,GAAA;AAAA,EAAK,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,YAAA;AAAA,EAAc,OAAA;AAAA,EAAS,IAAA;AAAA,EAC5D,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM;AACtC,CAAC,CAAA;AAED,IAAM,uBAAA,EAAyB,8BAAA;AAE/B,SAAS,gBAAA,CAAiB,KAAA,EAAe,IAAA,EAAkC;AACzE,EAAA,MAAM,MAAA,EAAQ,IAAI,MAAA,CAAO,CAAA,GAAA,EAAM,IAAI,CAAA,wBAAA,CAAA,EAA4B,GAAG,CAAA,CAAE,IAAA;AAAA,IAClE;AAAA,EACF,CAAA;AACA,EAAA,OAAO,MAAA,mBAAS,KAAA,CAAM,CAAC,CAAA,UAAK,KAAA,CAAM,CAAC,IAAA,EAAK,KAAA,CAAA;AAC1C;AAMA,SAAS,iBAAA,CAAkB,IAAA,EAAgC;AACzD,EAAA,MAAM,SAAA,EAAW,QAAA,CAAS,aAAA,CAAc,UAAU,CAAA;AAClD,EAAA,QAAA,CAAS,UAAA,EAAY,IAAA;AACrB,EAAA,OAAO,QAAA,CAAS,OAAA;AAClB;AAEA,SAAS,kBAAA,CAAmB,IAAA,EAAsB;AAChD,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA,CACpB,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA,CACpB,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA,CACtB,OAAA,CAAQ,UAAA,EAAY,GAAG,CAAA,CACvB,OAAA,CAAQ,SAAA,EAAW,MAAQ,CAAA,CAC3B,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA;AAC1B;AAKO,SAAS,eAAA,CAAgB,IAAA,EAAc,QAAA,EAA4B,CAAC,CAAA,EAAW;AACpF,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,EAAA;AAGlB,EAAA,MAAM,gBAAA,EAAkB,mBAAA;AAAA,IACtB,IAAA;AAAA,qBACA,OAAA,CAAQ,cAAA,UAAkB;AAAA,EAC5B,CAAA;AACA,EAAA,MAAM,kBAAA,EAAoB,eAAA,CAAgB,eAAA,EAAiB,MAAA,EAAQ,sBAAA,EAAwB,CAAC,KAAA,EAAO,KAAA,EAAA,GAAU;AAC3G,IAAA,MAAM,MAAA,EACJ,gBAAA,CAAiB,KAAA,EAAO,YAAY,EAAA,GAAK,gBAAA,CAAiB,KAAA,EAAO,SAAS,CAAA;AAC5E,IAAA,MAAM,KAAA,EAAO,gBAAA,CAAiB,KAAA,EAAO,WAAW,CAAA;AAEhD,IAAA,MAAM,UAAA,EAAY,KAAA,GAAQ,0CAAA,CAA2C,IAAA,CAAK,IAAI,EAAA,EAAI,KAAA,EAAO,EAAA;AACzF,IAAA,OAAO,MAAA,EAAQ,CAAA,CAAA,EAAI,KAAK,CAAA,EAAA;AACzB,EAAA;AAEuB,EAAA;AACL,IAAA;AAEd,IAAA;AAKsB,IAAA;AACI,IAAA;AACT,IAAA;AACI,MAAA;AACF,QAAA;AACR,QAAA;AACZ,MAAA;AACF,IAAA;AAC8B,IAAA;AACN,MAAA;AACD,QAAA;AAEH,QAAA;AACL,QAAA;AACX,QAAA;AACF,MAAA;AACsB,MAAA;AACQ,MAAA;AACZ,MAAA;AACL,QAAA;AACX,QAAA;AACF,MAAA;AACuB,MAAA;AACL,QAAA;AAChB,QAAA;AACF,MAAA;AACM,MAAA;AACc,MAAA;AACJ,MAAA;AACV,MAAA;AAEe,MAAA;AACN,QAAA;AACf,MAAA;AACF,IAAA;AACoB,IAAA;AACd,IAAA;AACgB,IAAA;AACxB,EAAA;AAGa,EAAA;AAIT,IAAA;AACA,IAAA;AACF,EAAA;AACwB,EAAA;AAC5B;AAQS;AACiD,EAAA;AACnC,EAAA;AACT,EAAA;AACc,EAAA;AACN,IAAA;AACI,IAAA;AACH,MAAA;AACE,MAAA;AACI,QAAA;AACtB,MAAA;AACM,MAAA;AACT,IAAA;AACO,IAAA;AACR,EAAA;AACkB,EAAA;AACF,EAAA;AACU,EAAA;AACL,EAAA;AACL,EAAA;AACnB;AAO8B;AACR,EAAA;AAElB,EAAA;AAEQ,EAAA;AACe,IAAA;AACzB,EAAA;AACkB,EAAA;AACZ,IAAA;AACoB,MAAA;AAChB,IAAA;AACgB,MAAA;AACxB,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAmBgB;AACY,EAAA;AACY,EAAA;AAIvB,EAAA;AAGO,EAAA;AACH,IAAA;AACA,MAAA;AACC,QAAA;AACG,UAAA;AACG,UAAA;AACD,UAAA;AACD,UAAA;AACE,UAAA;AACV,UAAA;AACO,UAAA;AAChB,QAAA;AACH,MAAA;AACD,IAAA;AACM,IAAA;AACT,EAAA;AAG2B,EAAA;AACM,IAAA;AACV,MAAA;AACC,QAAA;AACF,UAAA;AACG,YAAA;AACH,YAAA;AACK,YAAA;AACD,YAAA;AACJ,YAAA;AACJ,YAAA;AACO,YAAA;AAChB,UAAA;AACH,QAAA;AACkB,QAAA;AACC,UAAA;AACnB,QAAA;AACF,MAAA;AACF,IAAA;AACwB,IAAA;AACjB,IAAA;AACT,EAAA;AAG2B,EAAA;AACD,IAAA;AACJ,MAAA;AACF,QAAA;AACL,UAAA;AACG,UAAA;AACD,UAAA;AACK,UAAA;AACJ,UAAA;AACU,UAAA;AACrB,QAAA;AACF,MAAA;AACI,IAAA;AACc,MAAA;AACI,QAAA;AACJ,QAAA;AACD,UAAA;AACI,UAAA;AACpB,QAAA;AACc,QAAA;AACA,UAAA;AACG,UAAA;AACD,UAAA;AACK,UAAA;AACJ,UAAA;AACH,UAAA;AACb,QAAA;AACH,MAAA;AACF,IAAA;AAGwB,IAAA;AACH,MAAA;AACE,MAAA;AACL,QAAA;AACR,UAAA;AACG,UAAA;AACD,UAAA;AACP,QAAA;AACH,MAAA;AACF,IAAA;AACF,EAAA;AAEO,EAAA;AACT;ADjB6B;AACA;AG5UhB;AAqDqB;AACT,EAAA;AAGZ,EAAA;AACa,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACtB;AAGS;AACH,EAAA;AACc,EAAA;AACK,EAAA;AACJ,EAAA;AACI,EAAA;AACG,EAAA;AAC5B;AAcgC;AACP,EAAA;AACC,EAAA;AAOpB,EAAA;AACO,EAAA;AACQ,EAAA;AACE,IAAA;AACH,IAAA;AACM,IAAA;AACJ,IAAA;AACF,IAAA;AACI,MAAA;AACL,MAAA;AACE,MAAA;AACG,MAAA;AACpB,IAAA;AACA,IAAA;AACY,IAAA;AACd,EAAA;AAC0B,EAAA;AAEL,EAAA;AAED,EAAA;AAIC,EAAA;AAEM,EAAA;AAEC,EAAA;AACO,EAAA;AACtB,EAAA;AAEY,EAAA;AACrB,EAAA;AAEuB,EAAA;AACF,IAAA;AACV,IAAA;AACI,MAAA;AACG,MAAA;AACG,MAAA;AACD,MAAA;AACN,QAAA;AACE,QAAA;AAChB,MAAA;AACA,MAAA;AACF,IAAA;AACwB,IAAA;AACP,MAAA;AACK,MAAA;AACtB,IAAA;AACF,EAAA;AAEiB,EAAA;AACnB;AAMgB;AAIQ,EAAA;AAEc,EAAA;AACX,EAAA;AACT,EAAA;AACZ,EAAA;AAEuB,EAAA;AACA,IAAA;AACT,IAAA;AACS,IAAA;AACN,IAAA;AACG,IAAA;AACG,IAAA;AAEf,IAAA;AAGE,IAAA;AACa,MAAA;AACL,MAAA;AACpB,IAAA;AAEiB,IAAA;AACD,MAAA;AACN,QAAA;AACe,QAAA;AACtB,MAAA;AACH,IAAA;AAEc,IAAA;AACN,MAAA;AACC,MAAA;AACP,MAAA;AACqB,MAAA;AACtB,IAAA;AAEwB,IAAA;AAC3B,EAAA;AAEwB,EAAA;AACR,IAAA;AACN,MAAA;AACe,MAAA;AACtB,IAAA;AACH,EAAA;AAEO,EAAA;AACT;AAOgB;AAIS,EAAA;AAEkC,EAAA;AAIjC,EAAA;AACJ,EAAA;AACG,EAAA;AAED,EAAA;AAGL,EAAA;AACK,IAAA;AAKK,IAAA;AASnB,MAAA;AAEF,IAAA;AACO,MAAA;AACT,IAAA;AAEyB,IAAA;AAGvB,IAAA;AAKsB,MAAA;AACP,MAAA;AACI,MAAA;AACIA,MAAAA;AAEH,MAAA;AACL,MAAA;AACA,QAAA;AACC,QAAA;AACF,QAAA;AACF,QAAA;AACK,QAAA;AAEF,QAAA;AAEL,UAAA;AAGN,QAAA;AACO,UAAA;AACT,QAAA;AACO,QAAA;AACR,MAAA;AAEgB,MAAA;AACG,MAAA;AACD,MAAA;AACH,MAAA;AACI,MAAA;AAEP,MAAA;AACL,QAAA;AACc,QAAA;AACb,UAAA;AACG,UAAA;AACD,UAAA;AACK,UAAA;AACJ,UAAA;AACH,QAAA;AACY,UAAA;AAEA,UAAA;AAER,YAAA;AACF,YAAA;AACG,YAAA;AACC,UAAA;AACF,YAAA;AACF,YAAA;AACG,YAAA;AACH,UAAA;AACE,YAAA;AACF,YAAA;AACG,YAAA;AACV,UAAA;AACF,QAAA;AACF,MAAA;AAEmB,MAAA;AACD,MAAA;AAEF,MAAA;AACR,QAAA;AACU,MAAA;AACR,QAAA;AACQ,MAAA;AACpB,IAAA;AACoB,IAAA;AACN,MAAA;AAAQ,QAAA;AAAe,QAAA;AAEnC,MAAA;AACF,IAAA;AAEU,IAAA;AACC,IAAA;AACP,IAAA;AACkB,IAAA;AACL,MAAA;AACE,MAAA;AACH,MAAA;AACI,MAAA;AACL,MAAA;AACf,IAAA;AACwB,IAAA;AAElB,EAAA;AACZ;AHuL6B;AACA;AI3iBN;AACD;AAII;AAClB,EAAA;AACG,EAAA;AACD,EAAA;AACM;AAEe;AACI;AAEhB,EAAA;AAIR,IAAA;AACT,EAAA;AAC2B,EAAA;AAC7B;AAQ0B;AACxB,EAAA;AAAK,EAAA;AAAQ,EAAA;AAAK,EAAA;AAAc,EAAA;AAAM,EAAA;AAAQ,EAAA;AAAO,EAAA;AAAW,EAAA;AAAO,EAAA;AACvE,EAAA;AAAM,EAAA;AAAM,EAAA;AAAM,EAAA;AAAM,EAAA;AAAM,EAAA;AAAM,EAAA;AAAM,EAAA;AAAK,EAAA;AAAO,EAAA;AAAS,EAAA;AAAO,EAAA;AAAM,EAAA;AAC5E,EAAA;AAAM,EAAA;AAAK,EAAA;AAAO,EAAA;AAAK,EAAA;AAAQ,EAAA;AAAU,EAAA;AAAO,EAAA;AAAW,EAAA;AAAO,EAAA;AAClE,EAAA;AAAS,EAAA;AAAM,EAAA;AAAS,EAAA;AAAM,EAAA;AAAS,EAAA;AAAM,EAAA;AAAK,EAAA;AACnD;AAE0B;AACzB,EAAA;AAAO,EAAA;AAAS,EAAA;AAAW,EAAA;AAAS,EAAA;AAAW,EAAA;AAAY,EAAA;AAAU,EAAA;AACrE,EAAA;AAAO,EAAA;AAAW,EAAA;AAAO,EAAA;AAAS,EAAA;AAAU,EAAA;AAAS,EAAA;AAAQ,EAAA;AAC9D;AAGC;AAEA;AAGA;AAM2B,EAAA;AACL,EAAA;AACK,EAAA;AACA,EAAA;AACA,EAAA;AACX,IAAA;AAChB,EAAA;AAEY,EAAA;AACR,EAAA;AACY,EAAA;AACI,EAAA;AACM,IAAA;AACA,IAAA;AACtB,MAAA;AACF,IAAA;AACiB,IAAA;AACW,IAAA;AACT,MAAA;AACjB,MAAA;AACF,IAAA;AACuB,IAAA;AAED,IAAA;AAEG,IAAA;AAEvB,MAAA;AACF,IAAA;AACsB,IAAA;AACxB,EAAA;AAC0B,EAAA;AAC5B;AAgB2C;AAClC,EAAA;AACT;AAES;AACM,EAAA;AACG,EAAA;AACZ,EAAA;AACuB,EAAA;AACX,EAAA;AACO,IAAA;AACC,IAAA;AACJ,IAAA;AACpB,EAAA;AAC2B,EAAA;AAC7B;AAE6B;AACD;AACV;AAIA;AAgBF;AACO,EAAA;AACN,EAAA;AACb,IAAA;AACmB,IAAA;AACD,MAAA;AACE,MAAA;AACG,MAAA;AAGF,MAAA;AACrB,IAAA;AACF,EAAA;AACF;AAgCgB;AAIQ,EAAA;AACR,EAAA;AACD,EAAA;AACD,IAAA;AACZ,EAAA;AAEW,EAAA;AAIW,IAAA;AACI,MAAA;AACxB,IAAA;AACU,IAAA;AACZ,EAAA;AAC0B,EAAA;AAC5B;AASkC;AACZ,EAAA;AACI,EAAA;AACH,EAAA;AACA,IAAA;AACD,IAAA;AACC,MAAA;AACK,MAAA;AACxB,IAAA;AACgB,IAAA;AAClB,EAAA;AACqB,EAAA;AACvB;AJ+e6B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-7WISYZFO.cjs","sourcesContent":[null,"import { DOMSerializer, Slice, Fragment, Node as ProsemirrorNode, type Schema } from \"@tiptap/pm/model\";\nimport {\n BLOCK_CHANGE_ATTRIBUTE,\n MOVE_ID_ATTRIBUTE,\n resolveTrackedChanges,\n type TrackedChangesView,\n} from \"../extensions/track-changes\";\nimport { findTagSpans, replaceTagSpans, stripTags } from \"./html-scan\";\n\nexport interface PlainTextOptions {\n /**\n * How tracked changes (`TrackChanges` extension) appear in the text.\n * Defaults to `\"accepted\"`: deleted text is dropped and inserted text kept,\n * so suggested deletions never leak into exported text. `\"original\"` drops\n * insertions and keeps deletions; `\"all\"` keeps both.\n */\n trackedChanges?: TrackedChangesView;\n}\n\n/**\n * Match tracked deletion / insertion elements. They never nest within\n * themselves, so a lazy match is enough.\n */\nconst TRACKED_DELETION_ATTRIBUTE = /\\bdata-change-type=[\"']deletion[\"']/i;\nconst TRACKED_INSERTION_ATTRIBUTE = /\\bdata-change-type=[\"']insertion[\"']/i;\n\nfunction stripTrackedChanges(html: string, view: TrackedChangesView): string {\n if (view === \"all\" || !html.includes(\"data-change-type\")) return html;\n return view === \"accepted\"\n ? replaceTagSpans(html, \"del\", TRACKED_DELETION_ATTRIBUTE, () => \"\")\n : replaceTagSpans(html, \"ins\", TRACKED_INSERTION_ATTRIBUTE, () => \"\");\n}\n\n/**\n * Escapes characters that have syntactic meaning in HTML text.\n */\nexport function escapeHtml(text: string): string {\n if (!text) return \"\";\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\n}\n\n/**\n * Matches strings that are already TipTap paragraph HTML (start with `<p>` and\n * end with `</p>`), as opposed to plain text that merely mentions a `<p>` tag.\n */\nconst PARAGRAPH_HTML_REGEX = /^\\s*<p[\\s>][\\s\\S]*<\\/p>\\s*$/i;\n\n/**\n * Converts a plain-text string with newlines into HTML paragraphs suitable\n * for TipTap initial content or setContent, preserving line breaks.\n * Input that is already paragraph HTML (`<p>…</p>`) is passed through as-is.\n */\nexport function plainTextToTipTapHtml(text: string): string {\n if (!text) return \"<p></p>\";\n if (PARAGRAPH_HTML_REGEX.test(text)) {\n return text;\n }\n return text\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\r/g, \"\\n\")\n .split(\"\\n\")\n .map((line) => `<p>${escapeHtml(line)}</p>`)\n .join(\"\");\n}\n\n/**\n * Builds an open ProseMirror slice with one paragraph per line, so inserting it\n * splits the surrounding paragraph like typing Enter would. Use it for\n * plain-text paste handling (`clipboardTextParser`) and literal text insertion.\n */\nexport function plainTextToSlice(text: string, schema: Schema): Slice {\n const nodes: ProsemirrorNode[] = [];\n text\n .replace(/\\r\\n?/g, \"\\n\")\n .split(\"\\n\")\n .forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n nodes.push(ProsemirrorNode.fromJSON(schema, nodeJson));\n } catch {\n // Ignore invalid node\n }\n });\n return Slice.maxOpen(Fragment.fromArray(nodes));\n}\n\n/**\n * Block-level elements that start a new line in plain-text output.\n */\nconst BLOCK_TAGS = new Set([\n \"P\", \"DIV\", \"LI\", \"UL\", \"OL\", \"PRE\", \"BLOCKQUOTE\", \"TABLE\", \"TR\",\n \"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\", \"HR\",\n]);\n\nconst MENTION_TYPE_ATTRIBUTE = /\\bdata-type=[\"']mention[\"']/i;\n\nfunction getHtmlAttribute(attrs: string, name: string): string | undefined {\n const match = new RegExp(`\\\\b${name}=(?:\"([^\"]*)\"|'([^']*)')`, \"i\").exec(\n attrs,\n );\n return match ? (match[1] ?? match[2]) : undefined;\n}\n\n/**\n * Parses HTML into an inert fragment. Content of a `<template>` element is not\n * rendered, so `<img onerror>` and similar handlers never run.\n */\nfunction parseHtmlFragment(html: string): DocumentFragment {\n const template = document.createElement(\"template\");\n template.innerHTML = html;\n return template.content;\n}\n\nfunction decodeHtmlEntities(text: string): string {\n return text\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#0*39;/g, \"'\")\n .replace(/&nbsp;/g, \"\\u00a0\")\n .replace(/&amp;/g, \"&\");\n}\n\n/**\n * Extracts plain text from an HTML string or TipTap HTML, preserving newlines between block tags.\n */\nexport function htmlToPlainText(html: string, options: PlainTextOptions = {}): string {\n if (!html) return \"\";\n\n // Normalize mentions so data-label is preserved as @label even if textContent is empty\n const trackedResolved = stripTrackedChanges(\n html,\n options.trackedChanges ?? \"accepted\",\n );\n const mentionNormalized = replaceTagSpans(trackedResolved, \"span\", MENTION_TYPE_ATTRIBUTE, (match, attrs) => {\n const label =\n getHtmlAttribute(attrs, \"data-label\") || getHtmlAttribute(attrs, \"data-id\");\n const role = getHtmlAttribute(attrs, \"data-role\");\n // A role is written as `@label:role`, the text syntax `extractMentions` reads.\n const validRole = role && /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/.test(role) ? role : \"\";\n return label ? `@${label}${validRole ? `:${validRole}` : \"\"}` : match;\n });\n\n if (typeof document !== \"undefined\") {\n const fragment = parseHtmlFragment(mentionNormalized);\n fragment\n .querySelectorAll(\"br.ProseMirror-trailingBreak\")\n .forEach((br) => br.remove());\n\n // Walk the tree, emitting one line per innermost block so nested blocks\n // (e.g. TipTap's <li><p>…</p></li>) are not repeated.\n const lines: string[] = [];\n let current: string | null = null;\n const flush = () => {\n if (current !== null) {\n lines.push(current);\n current = null;\n }\n };\n const visit = (node: Node) => {\n if (node.nodeType === 3) {\n const value = node.nodeValue ?? \"\";\n // Skip formatting whitespace between blocks.\n if (current === null && value.trim() === \"\") return;\n current = (current ?? \"\") + value;\n return;\n }\n if (node.nodeType !== 1) return;\n const tag = (node as Element).tagName.toUpperCase();\n if (tag === \"BR\") {\n current = (current ?? \"\") + \"\\n\";\n return;\n }\n if (!BLOCK_TAGS.has(tag)) {\n node.childNodes.forEach(visit);\n return;\n }\n flush();\n const linesBefore = lines.length;\n node.childNodes.forEach(visit);\n flush();\n // An empty block (e.g. <p></p>) is an empty line.\n if (lines.length === linesBefore && tag !== \"HR\") {\n lines.push(\"\");\n }\n };\n fragment.childNodes.forEach(visit);\n flush();\n return lines.join(\"\\n\");\n }\n\n // Node.js fallback regex\n const text = mentionNormalized\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n // A closing block, plus any closing wrappers right after it, ends one line.\n .replace(\n /<\\/(?:p|div|li|pre|blockquote|h[1-6])>(?:\\s*<\\/(?:li|ul|ol|blockquote|div)>)*/gi,\n \"\\n\",\n );\n return decodeHtmlEntities(stripTags(text)).replace(/\\n$/, \"\");\n}\n\n/**\n * Whole-block insertions/deletions (`blockChange.operation`, from document\n * comparison) have no `<ins>`/`<del>` elements to strip, and resolving a\n * moved paragraph removes its whole block, so documents with either are\n * resolved first and serialized from the resolved document.\n */\nfunction resolvedBlockOperationsHtml(editor: any, view: TrackedChangesView): string | null {\n const doc: ProsemirrorNode | undefined = editor?.state?.doc;\n if (!doc || view === \"all\" || typeof document === \"undefined\") return null;\n let found = false;\n doc.descendants((node) => {\n if (found) return false;\n if (node.isTextblock) {\n if (node.attrs?.[BLOCK_CHANGE_ATTRIBUTE]?.operation) found = true;\n else node.forEach((child) => {\n if (child.marks.some((mark) => mark.attrs?.[MOVE_ID_ATTRIBUTE])) found = true;\n });\n return false;\n }\n return true;\n });\n if (!found) return null;\n const resolved = resolveTrackedChanges(doc, view);\n const container = document.createElement(\"div\");\n container.appendChild(DOMSerializer.fromSchema(doc.type.schema).serializeFragment(resolved.content));\n return container.innerHTML;\n}\n\n/**\n * Extracts plain text from a TipTap editor instance, preserving newline separators\n * between block nodes and converting mention nodes to @label. Tracked changes\n * are rendered as accepted unless `options.trackedChanges` says otherwise.\n */\nexport function getEditorText(editor: any, options: PlainTextOptions = {}): string {\n if (!editor) return \"\";\n const html =\n resolvedBlockOperationsHtml(editor, options.trackedChanges ?? \"accepted\") ??\n (typeof editor.getHTML === \"function\" ? editor.getHTML() : \"\");\n if (html) {\n return htmlToPlainText(html, options);\n }\n if (typeof editor.getText === \"function\") {\n try {\n return editor.getText({ blockSeparator: \"\\n\" });\n } catch {\n return editor.getText();\n }\n }\n return \"\";\n}\n\nexport interface ExtractedMention {\n id: string;\n label: string;\n type?: string;\n /** Asset role (`data-role`), when set. */\n role?: string;\n avatarUrl?: string;\n color?: string;\n [key: string]: any;\n}\n\nimport { extractMentions } from \"./mentions\";\n\n/**\n * Traverses an editor instance, ProseMirror document, JSONContent, or HTML string\n * and extracts all mention attributes without needing regex.\n */\nexport function extractMentionsFromDoc(docOrEditor: any): ExtractedMention[] {\n if (!docOrEditor) return [];\n const mentions: ExtractedMention[] = [];\n\n // TipTap editor or ProseMirror Node with .descendants()\n const doc =\n docOrEditor?.state?.doc ||\n (typeof docOrEditor?.descendants === \"function\" ? docOrEditor : null);\n\n if (doc && typeof doc.descendants === \"function\") {\n doc.descendants((node: any) => {\n if (node.type?.name === \"mention\" && node.attrs) {\n mentions.push({\n id: node.attrs.id || \"\",\n label: node.attrs.label || \"\",\n type: node.attrs.type,\n avatarUrl: node.attrs.avatarUrl,\n color: node.attrs.color,\n ...node.attrs,\n ...(node.attrs.assetRole ? { role: node.attrs.assetRole } : {}),\n });\n }\n });\n return mentions;\n }\n\n // JSONContent tree\n if (typeof docOrEditor === \"object\" && docOrEditor.content) {\n const walk = (nodes: any[]) => {\n for (const node of nodes) {\n if (node.type === \"mention\" && node.attrs) {\n mentions.push({\n id: node.attrs.id || \"\",\n label: node.attrs.label || \"\",\n type: node.attrs.type,\n avatarUrl: node.attrs.avatarUrl,\n color: node.attrs.color,\n ...node.attrs,\n ...(node.attrs.assetRole ? { role: node.attrs.assetRole } : {}),\n });\n }\n if (Array.isArray(node.content)) {\n walk(node.content);\n }\n }\n };\n walk(docOrEditor.content);\n return mentions;\n }\n\n // HTML String\n if (typeof docOrEditor === \"string\") {\n if (typeof document !== \"undefined\") {\n parseHtmlFragment(docOrEditor).querySelectorAll('span[data-type=\"mention\"]').forEach((el) => {\n mentions.push({\n id: el.getAttribute(\"data-id\") || \"\",\n label: el.getAttribute(\"data-label\") || \"\",\n type: el.getAttribute(\"data-type-name\") || undefined,\n avatarUrl: el.getAttribute(\"data-avatar-url\") || undefined,\n color: el.getAttribute(\"data-color\") || undefined,\n ...(el.getAttribute(\"data-role\") ? { role: el.getAttribute(\"data-role\")! } : {}),\n });\n });\n } else {\n for (const span of findTagSpans(docOrEditor, \"span\", MENTION_TYPE_ATTRIBUTE, false)) {\n const attrStr = span.attrs;\n const getAttr = (name: string) => {\n const m = new RegExp(`${name}=[\"']([^\"']*)[\"']`, \"i\").exec(attrStr);\n return m ? m[1] : undefined;\n };\n mentions.push({\n id: getAttr(\"data-id\") || \"\",\n label: getAttr(\"data-label\") || \"\",\n type: getAttr(\"data-type-name\") || getAttr(\"data-entity-type\"),\n avatarUrl: getAttr(\"data-avatar-url\"),\n color: getAttr(\"data-color\"),\n ...(getAttr(\"data-role\") ? { role: getAttr(\"data-role\") } : {}),\n });\n }\n }\n\n // Fallback: If no HTML mention tags were found, extract raw @mentions from text or markdown\n if (mentions.length === 0) {\n const rawHandles = extractMentions(docOrEditor);\n for (const handle of rawHandles) {\n mentions.push({\n id: handle,\n label: handle,\n type: \"user\",\n });\n }\n }\n }\n\n return mentions;\n}\n","/**\n * Linear-time scanners for the HTML-ish strings the serializers handle.\n *\n * Regexes like `/<span\\b[^>]*…[^>]*>/`, `/<[^>]+>/g` or `/<pre[\\s\\S]*?<\\/pre>/g` are\n * quadratic (or worse) on malformed input: every opener without its closer\n * rescans to the end of the string. These helpers find each closer once and\n * reuse it, so untrusted input can't stall the main thread (see GOTCHAS).\n */\n\nexport interface TagSpan {\n /** Index of the opening `<tag`. */\n start: number;\n /** Index just past the opening tag's `>`. */\n openEnd: number;\n /** Index just past the close tag (or `openEnd` when the close tag isn't required). */\n end: number;\n /** Attribute text between `<span` and `>`. */\n attrs: string;\n}\n\n/**\n * Finds `<tag …attrs…>…</tag>` elements whose attributes match `attrTest`, in\n * one linear pass. A regex such as `/<span\\b(?=[^>]*…)([^>]*)>[\\s\\S]*?<\\/span>/g`\n * rescans to the end of the input from every `<span` when a `>` or `</span>` is\n * missing, which is quadratic on malformed input. Here the next `>` and the next\n * close tag are found once and reused until the scan passes them, and the scan\n * stops as soon as either no longer exists further on. The first close tag after\n * the opening tag ends the element (no nesting), as with a lazy regex.\n */\nexport function findTagSpans(\n html: string,\n tag: string,\n attrTest: RegExp | ((attrs: string) => boolean),\n requireClose: boolean,\n): TagSpan[] {\n const matches = typeof attrTest === \"function\" ? attrTest : (attrs: string) => attrTest.test(attrs);\n const spans: TagSpan[] = [];\n const openTag = new RegExp(`<${tag}\\\\b`, \"gi\");\n const closeTag = new RegExp(`</${tag}>`, \"gi\");\n const closeLength = tag.length + 3;\n let nextGt = -1;\n let nextClose = -1;\n let from = 0;\n for (;;) {\n openTag.lastIndex = from;\n const open = openTag.exec(html);\n if (!open) break;\n const start = open.index;\n const attrsStart = start + open[0].length;\n if (nextGt < attrsStart) {\n nextGt = html.indexOf(\">\", attrsStart);\n if (nextGt === -1) break;\n }\n const attrs = html.slice(attrsStart, nextGt);\n const openEnd = nextGt + 1;\n if (!matches(attrs)) {\n from = attrsStart;\n continue;\n }\n if (!requireClose) {\n spans.push({ start, openEnd, end: openEnd, attrs });\n from = openEnd;\n continue;\n }\n if (nextClose < openEnd) {\n closeTag.lastIndex = openEnd;\n const close = closeTag.exec(html);\n if (!close) break;\n nextClose = close.index;\n }\n const end = nextClose + closeLength;\n spans.push({ start, openEnd, end, attrs });\n from = end;\n }\n return spans;\n}\n\n/** Replaces each matching element (opening tag through close tag) using `replace`. */\nexport function replaceTagSpans(\n html: string,\n tag: string,\n attrTest: RegExp | ((attrs: string) => boolean),\n replace: (match: string, attrs: string) => string,\n): string {\n const spans = findTagSpans(html, tag, attrTest, true);\n if (spans.length === 0) return html;\n let out = \"\";\n let last = 0;\n for (const span of spans) {\n out += html.slice(last, span.start) + replace(html.slice(span.start, span.end), span.attrs);\n last = span.end;\n }\n return out + html.slice(last);\n}\n\n/**\n * Replaces `<…>` tags in one pass. Same result as `.replace(/<[^>]+>/g, replacement)`,\n * which is quadratic when many `<` have no closing `>`: each one rescans to the\n * end. Once no `>` is left, the rest of the input is plain text.\n */\nexport function stripTags(html: string, replacement = \"\"): string {\n let out = \"\";\n let from = 0;\n for (;;) {\n const lt = html.indexOf(\"<\", from);\n if (lt === -1) break;\n const gt = html.indexOf(\">\", lt + 1);\n if (gt === -1) break;\n if (gt === lt + 1) {\n // \"<>\" is not a tag (the regex needs at least one character).\n out += html.slice(from, gt + 1);\n } else {\n out += html.slice(from, lt) + replacement;\n }\n from = gt + 1;\n }\n return out + html.slice(from);\n}\n\n/**\n * Removes (or replaces) regions that start with a match of `open` and end at the\n * next `close`, in one pass. Equivalent to `.replace(/<open>[\\s\\S]*?<close>/g, …)`\n * but linear: such a lazy regex rescans to the end of the input from every\n * unclosed opener. The next close position is cached until the scan passes it,\n * and scanning stops once no close is left. `open` must have the `g` flag.\n */\nexport function replaceDelimited(\n text: string,\n open: RegExp,\n close: string,\n replace: (match: string) => string = () => \"\",\n caseInsensitiveClose = false,\n): string {\n const haystack = caseInsensitiveClose ? asciiLower(text) : text;\n const needle = caseInsensitiveClose ? close.toLowerCase() : close;\n let out = \"\";\n let last = 0;\n let from = 0;\n let nextClose = -1;\n for (;;) {\n open.lastIndex = from;\n const opened = open.exec(text);\n if (!opened) break;\n const afterOpen = opened.index + Math.max(opened[0].length, 1);\n if (nextClose < afterOpen) {\n nextClose = haystack.indexOf(needle, afterOpen);\n if (nextClose === -1) break;\n }\n const end = nextClose + close.length;\n out += text.slice(last, opened.index) + replace(text.slice(opened.index, end));\n last = from = end;\n }\n return out + text.slice(last);\n}\n\n/** Lower-cases ASCII letters only, so indices match the original string. */\nfunction asciiLower(text: string): string {\n return text.replace(/[A-Z]+/g, (m) => m.toLowerCase());\n}\n","import { escapeHtml } from \"./plain-text\";\nimport {\n MENTION_REGEX,\n NODE_ROLE_MARK,\n formatMentionHandle,\n readRoleSuffix,\n type MentionRoleSyntax,\n} from \"./mention-text\";\n\nexport {\n MENTION_REGEX,\n findTextMentions,\n formatMentionText,\n setMentionRoleInText,\n type MentionRoleSyntax,\n type TextMention,\n} from \"./mention-text\";\nimport { findTagSpans, replaceDelimited, stripTags } from \"./html-scan\";\nimport type { MentionEntity } from \"../extensions/mention\";\n\n\n/**\n * Split regex identifying markdown code blocks, inline code, URLs, and existing HTML tags.\n * Used by preprocessMarkdownMentions to ensure non-mention regions remain completely untouched.\n */\nexport const MARKDOWN_MENTION_SPLIT_REGEX =\n /(```[\\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;\n\nexport interface MentionSegment {\n type: \"text\" | \"mention\";\n value: string;\n handle?: string;\n /** Role written after the handle (`@fox:style`), when role syntax is enabled. */\n role?: string;\n}\n\n\n/** A mention with its role (asset mentions: `@fox:style`, or `data-role` on a mention node). */\nexport interface MentionDetail {\n handle: string;\n role?: string;\n}\n\nexport interface ExtractMentionsOptions {\n /** Return `{ handle, role }` objects instead of handles. */\n details?: boolean;\n /** Read `@handle:role` suffixes in text (see {@link MentionRoleSyntax}). Mention nodes' `data-role` is always read in `details` mode. */\n roles?: MentionRoleSyntax;\n}\n\nexport interface PreprocessMarkdownMentionsOptions {\n /**\n * Known pool of mentionable entities (users, agents, teams, etc.).\n */\n mentionables?: MentionEntity[];\n /**\n * Default entity type assigned when a handle does not match any entry in mentionables.\n * Defaults to 'user'.\n */\n defaultType?: string;\n /**\n * Custom resolver callback to derive entity metadata for unmatched handles.\n */\n resolveMention?: (handle: string) => Partial<MentionEntity> | null | undefined;\n /**\n * Read `@handle:role` and emit the role as `data-role` (the `:role` text is\n * consumed). Off by default. See {@link MentionRoleSyntax}.\n */\n roles?: MentionRoleSyntax;\n}\n\n/**\n * Strips code blocks and inline code from markdown or HTML content:\n * - Fenced code blocks: ```...``` and ~~~...~~~\n * - Inline backtick code: `...`\n * - HTML code/pre tags: <pre>...</pre>, <code>...</code>\n * Used to avoid false-positive mention parsing and keyword analysis within code snippets, terminal logs, or stack traces.\n */\nexport function stripMarkdownCode(content: string): string {\n if (!content || typeof content !== \"string\") return \"\";\n // Linear scans instead of lazy `[\\s\\S]*?` regexes, which rescan to the end of the\n // input from every unclosed opener (see html-scan.ts).\n let text = replaceDelimited(content, /```/g, \"```\");\n text = replaceDelimited(text, /~~~/g, \"~~~\");\n text = replaceDelimited(text, /<pre/gi, \"</pre>\", undefined, true);\n text = replaceDelimited(text, /<code/gi, \"</code>\", undefined, true);\n return text.replace(/`[^`\\n]*`/g, \"\");\n}\n\n/** True for the attributes of a mention node: `data-type=\"mention\"` or the `pixerate-mention-node` class. */\nfunction isMentionNodeAttributes(attrs: string): boolean {\n if (/\\bdata-type=[\"']mention[\"']/i.test(attrs)) return true;\n const classAttr = /\\bclass=([\"'])/i.exec(attrs);\n if (!classAttr) return false;\n const valueStart = classAttr.index + classAttr[0].length;\n const valueEnd = attrs.indexOf(classAttr[1], valueStart);\n return valueEnd !== -1 && attrs.slice(valueStart, valueEnd).includes(\"pixerate-mention-node\");\n}\n\n/**\n * Extracts unique mention handles from a text, markdown, or HTML string,\n * ignoring trailing punctuation, code blocks, URLs, and pre-existing HTML mention attributes.\n *\n * With `{ details: true }` it returns `{ handle, role }` objects, unique per\n * handle and role: roles come from mention nodes' `data-role` and, when\n * `roles` is set, from `@handle:role` text. Without options the result is\n * unchanged.\n */\nexport function extractMentions(content: string, options?: ExtractMentionsOptions & { details?: false }): string[];\nexport function extractMentions(content: string, options: ExtractMentionsOptions & { details: true }): MentionDetail[];\nexport function extractMentions(content: string, options?: ExtractMentionsOptions): string[] | MentionDetail[];\nexport function extractMentions(content: string, options: ExtractMentionsOptions = {}): string[] | MentionDetail[] {\n if (!content || typeof content !== \"string\") return [];\n const details = options.details === true;\n\n // Normalize existing HTML mention nodes (e.g. from TipTap or rich text serializers)\n // so data-mention-suggestion-char=\"@\" does not trigger spurious quoted matches against attributes,\n // and ensure the actual mentioned label or ID is preserved cleanly.\n // A linear scan: the equivalent regex (`<span\\b[^>]*\\b(?:…)[^>]*>`) backtracks\n // cubically on malformed input without a `>` (see html-scan.ts).\n let normalizedMentionNodes = \"\";\n let last = 0;\n for (const span of findTagSpans(content, \"span\", isMentionNodeAttributes, true)) {\n const labelMatch = /\\bdata-label=[\"']([^\"']+)[\"']/i.exec(span.attrs);\n const idMatch = labelMatch ? null : /\\bdata-id=[\"']([^\"']+)[\"']/i.exec(span.attrs);\n const inner = content.slice(span.openEnd, span.end - \"</span>\".length);\n let replacement = labelMatch ? ` @${labelMatch[1]} ` : idMatch ? ` @${idMatch[1]} ` : ` ${inner} `;\n if (details && (labelMatch || idMatch)) {\n const roleMatch = /\\bdata-role=[\"']([^\"']+)[\"']/i.exec(span.attrs);\n const role = roleMatch && /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/.test(roleMatch[1]) ? roleMatch[1] : null;\n const label = (labelMatch ?? idMatch)![1];\n replacement = ` ${formatMentionHandle(label)}${role ? NODE_ROLE_MARK + role : \"\"} `;\n }\n normalizedMentionNodes += content.slice(last, span.start) + replacement;\n last = span.end;\n }\n normalizedMentionNodes += content.slice(last);\n\n const cleanContent = stripMarkdownCode(normalizedMentionNodes);\n // Strip URLs and markdown link destinations so @package or @handles in URLs are not extracted as mentions\n const withoutUrls = cleanContent\n .replace(/(?:https?|ftp|file|vscode|cursor|mailto):\\/\\/[^\\s<>()]+/gi, \"\");\n // Markdown link destinations: `](…)` → `]` (linear; `/\\]\\([^)]*\\)/g` is quadratic\n // when many `](` have no `)`).\n const withoutLinks = replaceDelimited(withoutUrls, /\\]\\(/g, \")\", () => \"]\");\n // Strip remaining HTML tags so attributes containing '@' or '>' aren't treated as mention triggers\n const textOnly = stripTags(withoutLinks, \" \");\n\n const mentions: string[] = [];\n const detailed: MentionDetail[] = [];\n const seen = new Set<string>();\n\n const regex = new RegExp(MENTION_REGEX.source, \"g\");\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(textOnly)) !== null) {\n const handle = (match[1] ?? match[2] ?? match[3] ?? \"\").trim();\n if (details) {\n const suffix = readRoleSuffix(textOnly, regex.lastIndex, options.roles);\n if (suffix) regex.lastIndex = suffix.end;\n const key = `${handle}\\u0000${suffix?.role ?? \"\"}`;\n if (handle && !seen.has(key)) {\n seen.add(key);\n detailed.push(suffix ? { handle, role: suffix.role } : { handle });\n }\n continue;\n }\n if (handle && !seen.has(handle)) {\n seen.add(handle);\n mentions.push(handle);\n }\n }\n\n return details ? detailed : mentions;\n}\n\n/**\n * Segments a text string into plain text parts and mention tokens.\n * Correctly leaves trailing punctuation (like \".\") as part of the following text segment.\n */\nexport function parseMentionSegments(\n content: string,\n options: { roles?: MentionRoleSyntax } = {},\n): MentionSegment[] {\n if (!content) return [];\n\n const segments: MentionSegment[] = [];\n const regex = new RegExp(MENTION_REGEX.source, \"g\");\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const fullMatch = match[0];\n const atIndex = fullMatch.indexOf(\"@\");\n const prefix = atIndex > 0 ? fullMatch.slice(0, atIndex) : \"\";\n let mentionToken = fullMatch.slice(atIndex);\n const handle = match[1] ?? match[2] ?? match[3];\n const matchStart = match.index + prefix.length;\n const suffix =\n options.roles && content.charAt(regex.lastIndex) === \":\"\n ? readRoleSuffix(content, regex.lastIndex, options.roles)\n : null;\n if (suffix) {\n mentionToken = content.slice(matchStart, suffix.end);\n regex.lastIndex = suffix.end;\n }\n\n if (matchStart > lastIndex) {\n segments.push({\n type: \"text\",\n value: content.slice(lastIndex, matchStart),\n });\n }\n\n segments.push({\n type: \"mention\",\n value: mentionToken,\n handle,\n ...(suffix ? { role: suffix.role } : {}),\n });\n\n lastIndex = matchStart + mentionToken.length;\n }\n\n if (lastIndex < content.length) {\n segments.push({\n type: \"text\",\n value: content.slice(lastIndex),\n });\n }\n\n return segments;\n}\n\n/**\n * Preprocesses markdown text by turning raw @mention handles into TipTap-compatible\n * mention HTML spans (<span class=\"pixerate-mention-node\" data-type=\"mention\" ...>),\n * ensuring code blocks, inline code, existing mention nodes, and URLs remain immune.\n */\nexport function preprocessMarkdownMentions(\n content: string,\n optionsOrMentionables: MentionEntity[] | PreprocessMarkdownMentionsOptions = []\n): string {\n if (!content || typeof content !== \"string\") return \"\";\n\n const options: PreprocessMarkdownMentionsOptions = Array.isArray(optionsOrMentionables)\n ? { mentionables: optionsOrMentionables }\n : optionsOrMentionables;\n\n const allMentionables = options.mentionables || [];\n const defaultType = options.defaultType || \"user\";\n const resolveMention = options.resolveMention;\n\n const parts = content.split(MARKDOWN_MENTION_SPLIT_REGEX);\n\n return parts\n .map((part) => {\n if (!part) return \"\";\n\n // Preserve code fences, code tags, inline backticks, existing mention spans,\n // HTML tags, markdown link targets, and raw URLs untouched.\n if (\n part.startsWith(\"```\") ||\n part.startsWith(\"~~~\") ||\n part.startsWith(\"`\") ||\n part.startsWith(\"](\") ||\n /^(?:https?|file|vscode|cursor|mailto):\\/\\//i.test(part) ||\n /^<pre[\\s>]/i.test(part) ||\n /^<code[\\s>]/i.test(part) ||\n /^<[^>]+>$/i.test(part) ||\n /^<span\\b[^>]*\\b(?:data-type=[\"']mention[\"']|class=[\"'][^\"']*pixerate-mention-node[^\"']*[\"']|data-mention=[\"'][^\"']*[\"'])/i.test(\n part\n )\n ) {\n return part;\n }\n\n const regex = new RegExp(MENTION_REGEX.source, \"g\");\n const render = (\n match: string,\n quotedHandle: string | undefined,\n escapedQuotedHandle: string | undefined,\n standardHandle: string | undefined,\n role?: string,\n ): string => {\n const atIndex = match.indexOf(\"@\");\n const prefix = atIndex > 0 ? match.slice(0, atIndex) : \"\";\n const rawHandle = (quotedHandle || escapedQuotedHandle || standardHandle || \"\").trim();\n if (!rawHandle) return match;\n\n const lowerHandle = rawHandle.toLowerCase();\n const entity = allMentionables.find((e) => {\n if (e.label?.toLowerCase() === lowerHandle) return true;\n if (e.handle?.toLowerCase() === lowerHandle) return true;\n if (e.name?.toLowerCase() === lowerHandle) return true;\n if (e.id?.toLowerCase() === lowerHandle) return true;\n if (e.type === \"agent\" && `agent${(e.handle || e.label || \"\").toLowerCase()}` === lowerHandle) return true;\n if (\n e.aliases?.some(\n (a: string) =>\n a.toLowerCase() === lowerHandle ||\n (e.type === \"agent\" && `agent${a.toLowerCase()}` === lowerHandle)\n )\n ) {\n return true;\n }\n return false;\n });\n\n let id = entity?.id;\n let label = entity?.label || entity?.handle || rawHandle;\n let type = entity?.type;\n let avatarUrl = entity?.avatarUrl;\n let color = entity?.color || entity?.hexColor;\n\n if (!entity) {\n const customResolved = resolveMention?.(rawHandle);\n if (customResolved) {\n id = customResolved.id ?? id;\n label = customResolved.label ?? customResolved.handle ?? label;\n type = customResolved.type ?? type;\n avatarUrl = customResolved.avatarUrl ?? avatarUrl;\n color = customResolved.color ?? customResolved.hexColor ?? color;\n } else {\n const stripped = lowerHandle.replace(/^agent[\\s\\-_]*/i, \"\");\n if (\n [\"supervisor\", \"coordinator\", \"planner\", \"validator\", \"executor\"].includes(stripped)\n ) {\n type = \"agent\";\n id = stripped;\n label = stripped;\n } else if (lowerHandle === \"agents\") {\n type = \"team\";\n id = \"agents\";\n label = \"agents\";\n } else {\n type = defaultType;\n id = rawHandle;\n label = rawHandle;\n }\n }\n }\n\n const avatarAttr = avatarUrl ? ` data-avatar-url=\"${escapeHtml(avatarUrl)}\"` : \"\";\n const colorAttr = color ? ` data-color=\"${escapeHtml(color)}\"` : \"\";\n\n return `${prefix}<span class=\"pixerate-mention-node\" data-type=\"mention\" data-id=\"${escapeHtml(\n id || label\n )}\" data-label=\"${escapeHtml(label)}\" data-mention-suggestion-char=\"@\" data-type-name=\"${escapeHtml(\n type || defaultType\n )}\"${avatarAttr}${colorAttr}${role ? ` data-role=\"${escapeHtml(role)}\"` : \"\"}>@${escapeHtml(label)}</span>`;\n };\n if (!options.roles) {\n return part.replace(regex, (match, quotedHandle, escapedQuotedHandle, standardHandle) =>\n render(match, quotedHandle, escapedQuotedHandle, standardHandle),\n );\n }\n // Role syntax: consume a `:role` suffix after each mention.\n let out = \"\";\n let last = 0;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(part)) !== null) {\n const suffix = readRoleSuffix(part, regex.lastIndex, options.roles);\n const rendered = render(match[0], match[1], match[2], match[3], suffix?.role);\n if (suffix && rendered !== match[0]) regex.lastIndex = suffix.end;\n out += part.slice(last, match.index) + rendered;\n last = regex.lastIndex;\n }\n return out + part.slice(last);\n })\n .join(\"\");\n}\n","import MarkdownIt from \"markdown-it\";\nimport taskLists from \"markdown-it-task-lists\";\nimport { escapeHtml, getEditorText, type PlainTextOptions } from \"./plain-text\";\nimport { resolveTrackedChanges } from \"../extensions/track-changes\";\n\nconst md = new MarkdownIt({\n html: true,\n linkify: true,\n breaks: true,\n}).use(taskLists);\n\nconst defaultValidateLink = md.validateLink;\nmd.validateLink = (url: string) => {\n if (\n url.startsWith(\"file://\") ||\n url.startsWith(\"vscode://\") ||\n url.startsWith(\"cursor://\")\n ) {\n return true;\n }\n return defaultValidateLink.call(md, url);\n};\n\n/**\n * Tags allowed through from raw HTML embedded in markdown (e.g. mention spans\n * and file links that tiptap-markdown serializes as HTML). Anything else is\n * rendered as escaped text. `input` is limited to checkboxes, which\n * markdown-it-task-lists emits as raw HTML.\n */\nconst ALLOWED_HTML_TAGS = new Set([\n \"a\", \"abbr\", \"b\", \"blockquote\", \"br\", \"code\", \"del\", \"details\", \"div\", \"em\",\n \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"hr\", \"i\", \"img\", \"input\", \"kbd\", \"li\", \"mark\",\n \"ol\", \"p\", \"pre\", \"s\", \"span\", \"strong\", \"sub\", \"summary\", \"sup\", \"table\",\n \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\", \"u\", \"ul\",\n]);\n\nconst ALLOWED_HTML_ATTRS = new Set([\n \"alt\", \"align\", \"checked\", \"class\", \"colspan\", \"disabled\", \"height\", \"href\",\n \"rel\", \"rowspan\", \"src\", \"start\", \"target\", \"title\", \"type\", \"width\",\n]);\n\nconst HTML_TAG_REGEX =\n /<(\\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:\\s+[^\\s\"'>\\/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'=<>`]+))?)*)\\s*\\/?>/g;\nconst HTML_ATTR_REGEX =\n /([^\\s\"'>\\/=]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g;\n\nfunction sanitizeTag(\n tagText: string,\n isClosing: string,\n rawTagName: string,\n rawAttrs: string,\n stripDisallowed: boolean,\n): string {\n const tagName = rawTagName.toLowerCase();\n const reject = () => (stripDisallowed ? \"\" : escapeHtml(tagText));\n if (!ALLOWED_HTML_TAGS.has(tagName)) return reject();\n if (isClosing) return `</${tagName}>`;\n if (tagName === \"input\" && !/\\stype\\s*=\\s*[\"']?checkbox[\"']?[\\s>/]/i.test(tagText)) {\n return reject();\n }\n\n let attrs = \"\";\n let attrMatch: RegExpExecArray | null;\n HTML_ATTR_REGEX.lastIndex = 0;\n while ((attrMatch = HTML_ATTR_REGEX.exec(rawAttrs)) !== null) {\n const name = attrMatch[1].toLowerCase();\n if (!ALLOWED_HTML_ATTRS.has(name) && !/^data-[a-z0-9_.-]+$/.test(name)) {\n continue;\n }\n const rawValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4];\n if (rawValue === undefined) {\n attrs += ` ${name}`;\n continue;\n }\n const value = md.utils.unescapeAll(rawValue);\n if (\n (name === \"href\" || name === \"src\") &&\n // Browsers ignore control characters and whitespace inside schemes.\n !md.validateLink(value.replace(/[\\u0000-\\u0020\\u007f]/g, \"\"))\n ) {\n continue;\n }\n attrs += ` ${name}=\"${escapeHtml(value)}\"`;\n }\n return `<${tagName}${attrs}>`;\n}\n\nexport interface SanitizeHtmlOptions {\n /**\n * What to do with tags outside the allowlist: `escape` (default) renders\n * them as literal text, `strip` removes the tag but keeps its text content.\n */\n disallowedTags?: \"escape\" | \"strip\";\n}\n\n/**\n * Sanitizes raw HTML with the same allowlist the markdown renderer applies to\n * embedded HTML: event handlers, `style` and unsafe URL schemes are dropped,\n * and disallowed tags are escaped (or removed with `disallowedTags: \"strip\"`).\n * It is string-based, so it works without a DOM and never runs handlers.\n */\nexport function sanitizeHtml(html: string, options: SanitizeHtmlOptions = {}): string {\n return sanitizeEmbeddedHtml(html, options.disallowedTags === \"strip\");\n}\n\nfunction sanitizeEmbeddedHtml(html: string, stripDisallowed = false): string {\n let result = \"\";\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n HTML_TAG_REGEX.lastIndex = 0;\n while ((match = HTML_TAG_REGEX.exec(html)) !== null) {\n result += html.slice(lastIndex, match.index).replace(/</g, \"&lt;\");\n result += sanitizeTag(match[0], match[1], match[2], match[3], stripDisallowed);\n lastIndex = match.index + match[0].length;\n }\n return result + html.slice(lastIndex).replace(/</g, \"&lt;\");\n}\n\nconst defaultHtmlInline = md.renderer.rules.html_inline;\nconst defaultHtmlBlock = md.renderer.rules.html_block;\nmd.renderer.rules.html_inline = (tokens, idx, options, env, self) =>\n env?.allowUnsafeHtml\n ? defaultHtmlInline!(tokens, idx, options, env, self)\n : sanitizeEmbeddedHtml(tokens[idx].content);\nmd.renderer.rules.html_block = (tokens, idx, options, env, self) =>\n env?.allowUnsafeHtml\n ? defaultHtmlBlock!(tokens, idx, options, env, self)\n : sanitizeEmbeddedHtml(tokens[idx].content);\n\nimport {\n preprocessMarkdownMentions,\n type PreprocessMarkdownMentionsOptions,\n} from \"./mentions\";\nimport type { MentionEntity } from \"../extensions/mention\";\n\n/**\n * Preprocesses file-protocol and editor-protocol links in markdown content:\n * converts [label](file://...), vscode://, and cursor:// links into anchor tags\n * annotated with data-file-link=\"true\", preserving code backticks in labels.\n */\nexport function preprocessMarkdownFileLinks(content: string): string {\n if (!content) return \"\";\n return content.replace(\n /\\[([^\\]]+)\\]\\(((?:file|vscode|cursor):\\/\\/[^)]+)\\)/g,\n (_match, rawLabel, href) => {\n const trimmed = rawLabel.trim();\n const codeMatch = trimmed.match(/^`([^`]+)`$/);\n const labelContent = codeMatch\n ? `<code>${escapeHtml(codeMatch[1])}</code>`\n : escapeHtml(trimmed);\n return `<a href=\"${escapeHtml(href)}\" data-file-link=\"true\" title=\"Click to copy file path\">${labelContent}</a>`;\n }\n );\n}\n\nexport interface MarkdownToHtmlOptions {\n /**\n * Preprocess file, vscode, and cursor links into clickable data-file-link tags.\n */\n preprocessFileLinks?: boolean;\n /**\n * Whether to preprocess @mentions into TipTap mention node HTML spans.\n */\n preprocessMentions?: boolean;\n /**\n * Pool of mentionables to pass to preprocessMarkdownMentions.\n */\n mentionables?: MentionEntity[];\n /**\n * Full options for preprocessMarkdownMentions.\n */\n preprocessOptions?: PreprocessMarkdownMentionsOptions;\n /**\n * Render raw HTML embedded in the markdown without sanitizing it. By default,\n * embedded HTML is restricted to an allowlist of tags and attributes (event\n * handlers, `style` and unsafe URL schemes are removed). Only enable this for\n * fully trusted input.\n */\n allowUnsafeHtml?: boolean;\n}\n\n/**\n * Converts a markdown string into HTML suitable for TipTap initial content or setContent.\n * Raw HTML embedded in the markdown is sanitized unless `allowUnsafeHtml` is set.\n */\nexport function markdownToTipTapHtml(\n markdown: string,\n options?: MarkdownToHtmlOptions\n): string {\n if (!markdown) return \"<p></p>\";\n let content = markdown;\n if (options?.preprocessFileLinks) {\n content = preprocessMarkdownFileLinks(content);\n }\n if (\n options?.preprocessMentions ||\n options?.mentionables ||\n options?.preprocessOptions\n ) {\n const mentionOpts = options.preprocessOptions || {\n mentionables: options.mentionables,\n };\n content = preprocessMarkdownMentions(content, mentionOpts);\n }\n return md.render(content, { allowUnsafeHtml: options?.allowUnsafeHtml });\n}\n\n/**\n * Extracts clean markdown from a TipTap editor instance.\n * If the editor has the Markdown extension configured, it uses editor.storage.markdown.getMarkdown().\n * Otherwise, it falls back to getEditorText(editor).\n * Tracked changes are exported as accepted unless `options.trackedChanges`\n * says otherwise (`\"all\"` renders deletions as `~~strikethrough~~`).\n */\nexport function getEditorMarkdown(editor: any, options: PlainTextOptions = {}): string {\n if (!editor) return \"\";\n const markdown = editor.storage?.markdown;\n if (typeof markdown?.getMarkdown === \"function\") {\n const doc = editor.state?.doc;\n if (doc && typeof markdown.serializer?.serialize === \"function\") {\n const resolved = resolveTrackedChanges(doc, options.trackedChanges ?? \"accepted\");\n if (resolved !== doc) return markdown.serializer.serialize(resolved);\n }\n return markdown.getMarkdown();\n }\n return getEditorText(editor, options);\n}\n"]}
@@ -6,7 +6,8 @@
6
6
 
7
7
 
8
8
 
9
- var _chunkD27YFC46cjs = require('./chunk-D27YFC46.cjs');
9
+
10
+ var _chunkX4N4WUKBcjs = require('./chunk-X4N4WUKB.cjs');
10
11
 
11
12
  // src/compare/diff.ts
12
13
  var EQUAL = 0;
@@ -256,9 +257,9 @@ var _core = require('@tiptap/core');
256
257
 
257
258
  var _model = require('@tiptap/pm/model');
258
259
  var REVIEW_MARK_NAMES = /* @__PURE__ */ new Set([
259
- _chunkD27YFC46cjs.INSERTION_MARK_NAME,
260
- _chunkD27YFC46cjs.DELETION_MARK_NAME,
261
- _chunkD27YFC46cjs.FORMAT_CHANGE_MARK_NAME,
260
+ _chunkX4N4WUKBcjs.INSERTION_MARK_NAME,
261
+ _chunkX4N4WUKBcjs.DELETION_MARK_NAME,
262
+ _chunkX4N4WUKBcjs.FORMAT_CHANGE_MARK_NAME,
262
263
  "comment",
263
264
  "bookmark"
264
265
  ]);
@@ -302,7 +303,7 @@ function lazyAttributes(ctx) {
302
303
  }
303
304
  function stripBlockChange(attrs) {
304
305
  const copy = { ...attrs };
305
- delete copy[_chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE];
306
+ delete copy[_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE];
306
307
  return copy;
307
308
  }
308
309
  function wrapperOf(node) {
@@ -315,7 +316,7 @@ function filteredAttributes(attrs, ctx) {
315
316
  return stableStringify(
316
317
  Object.fromEntries(
317
318
  Object.entries(_nullishCoalesce(attrs, () => ( {}))).filter(
318
- ([key]) => key !== _chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE && !ctx.ignoredAttributes.has(key)
319
+ ([key]) => key !== _chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE && !ctx.ignoredAttributes.has(key)
319
320
  )
320
321
  )
321
322
  );
@@ -326,7 +327,7 @@ function sameBlock(a, b, ctx) {
326
327
  if (a.wrappers.length !== b.wrappers.length) return false;
327
328
  return a.wrappers.every((w, i) => w.type === b.wrappers[i].type);
328
329
  }
329
- var hasBlockChange = (node) => !!node.type.spec.attrs && _chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs;
330
+ var hasBlockChange = (node) => !!node.type.spec.attrs && _chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs;
330
331
  var label = (node) => node.type.name;
331
332
  function normalize(text, ctx) {
332
333
  let result = ctx.ignoreWhitespace ? text.replace(/\s+/g, " ") : text;
@@ -379,12 +380,12 @@ function wholeBlock(node, operation, wrappers, attrs, ctx) {
379
380
  previous: snapshot(node, wrappers),
380
381
  operation
381
382
  };
382
- return [node.type.create({ ...node.attrs, [_chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE]: record }, node.content, node.marks)];
383
+ return [node.type.create({ ...node.attrs, [_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE]: record }, node.content, node.marks)];
383
384
  }
384
385
  const markType = operation === "insert" ? ctx.insertion : ctx.deletion;
385
386
  if (markType && node.type.allowsMarkType(markType) && node.content.size > 0) {
386
387
  ctx.notes.add(
387
- `${verb} ${label(node)} blocks have no "${_chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE}" attribute: only their text is tracked, so an empty block remains when the change is ${operation === "insert" ? "rejected" : "accepted"}.`
388
+ `${verb} ${label(node)} blocks have no "${_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE}" attribute: only their text is tracked, so an empty block remains when the change is ${operation === "insert" ? "rejected" : "accepted"}.`
388
389
  );
389
390
  return [node.type.create(node.attrs, markInline(node.content, markType.create(attrs())), node.marks)];
390
391
  }
@@ -483,7 +484,7 @@ function formattingOf(marks, ctx) {
483
484
  }
484
485
  function withoutReviewMarks(marks) {
485
486
  return marks.filter(
486
- (mark) => mark.type.name !== _chunkD27YFC46cjs.INSERTION_MARK_NAME && mark.type.name !== _chunkD27YFC46cjs.DELETION_MARK_NAME && mark.type.name !== _chunkD27YFC46cjs.FORMAT_CHANGE_MARK_NAME
487
+ (mark) => mark.type.name !== _chunkX4N4WUKBcjs.INSERTION_MARK_NAME && mark.type.name !== _chunkX4N4WUKBcjs.DELETION_MARK_NAME && mark.type.name !== _chunkX4N4WUKBcjs.FORMAT_CHANGE_MARK_NAME
487
488
  );
488
489
  }
489
490
  function mergeInline(original, revised, parentType, ctx) {
@@ -579,12 +580,12 @@ function mergeTextblocks(original, revised, oPath, rPath, ctx) {
579
580
  if (hasBlockChange(revised)) {
580
581
  const onlyWrappers = ctx.wrapperChange && sameBlock(previous, { ...snapshot(revised, rPath), wrappers: previous.wrappers }, ctx);
581
582
  const base = onlyWrappers ? ctx.wrapperChange() : newAttributes(ctx);
582
- attrs = { ...attrs, [_chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE]: { ...base, previous } };
583
+ attrs = { ...attrs, [_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE]: { ...base, previous } };
583
584
  } else {
584
585
  ctx.notes.add(`Block changes to ${label(revised)} blocks cannot be tracked.`);
585
586
  }
586
- } else if (_chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE in attrs) {
587
- attrs[_chunkD27YFC46cjs.BLOCK_CHANGE_ATTRIBUTE] = null;
587
+ } else if (_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE in attrs) {
588
+ attrs[_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE] = null;
588
589
  }
589
590
  const type = revised.type;
590
591
  const inlineTracking = !!ctx.insertion && !!ctx.deletion && type.allowsMarkType(ctx.insertion) && type.allowsMarkType(ctx.deletion);
@@ -674,30 +675,62 @@ function compareChildren(original, revised, oPath, rPath, ctx) {
674
675
  }
675
676
  return out;
676
677
  }
678
+ function detectMovedBlocks(children, ctx) {
679
+ const keyOf = (node) => `${node.type.name}\0${filteredAttributes(node.attrs, ctx)}\0${stableStringify(node.content.toJSON())}`;
680
+ const candidates = /* @__PURE__ */ new Map();
681
+ children.forEach((node, index) => {
682
+ const operation = _optionalChain([node, 'access', _2 => _2.attrs, 'optionalAccess', _3 => _3[_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE], 'optionalAccess', _4 => _4.operation]);
683
+ if (!operation || !node.isTextblock || !node.textContent.trim()) return;
684
+ if (!node.type.allowsMarkType(ctx.insertion) || !node.type.allowsMarkType(ctx.deletion)) return;
685
+ const key = keyOf(node);
686
+ const entry = _nullishCoalesce(candidates.get(key), () => ( { del: [], ins: [] }));
687
+ (operation === "delete" ? entry.del : entry.ins).push(index);
688
+ candidates.set(key, entry);
689
+ });
690
+ const out = [...children];
691
+ candidates.forEach(({ del, ins }) => {
692
+ if (del.length !== 1 || ins.length !== 1) return;
693
+ const attrs = newAttributes(ctx);
694
+ const moved = { ...attrs, [_chunkX4N4WUKBcjs.MOVE_ID_ATTRIBUTE]: attrs.changeId };
695
+ const relink = (index, markType) => {
696
+ const node = out[index];
697
+ out[index] = node.type.create(
698
+ { ...node.attrs, [_chunkX4N4WUKBcjs.BLOCK_CHANGE_ATTRIBUTE]: null },
699
+ markInline(node.content, markType.create(moved)),
700
+ node.marks
701
+ );
702
+ };
703
+ relink(del[0], ctx.deletion);
704
+ relink(ins[0], ctx.insertion);
705
+ });
706
+ return out;
707
+ }
677
708
  function summarize(doc, notes) {
678
709
  const summary = {
679
710
  insertions: 0,
680
711
  deletions: 0,
681
712
  formatChanges: 0,
682
713
  blockChanges: 0,
714
+ moves: 0,
683
715
  notHonoured: [...notes]
684
716
  };
685
- _chunkD27YFC46cjs.getTrackedChanges.call(void 0, doc).forEach((change) => {
686
- if (change.type === "insertion" || _optionalChain([change, 'access', _2 => _2.block, 'optionalAccess', _3 => _3.operation]) === "insert") summary.insertions++;
687
- else if (change.type === "deletion" || _optionalChain([change, 'access', _4 => _4.block, 'optionalAccess', _5 => _5.operation]) === "delete") summary.deletions++;
717
+ _chunkX4N4WUKBcjs.getTrackedChanges.call(void 0, doc).forEach((change) => {
718
+ if (change.type === "move") summary.moves++;
719
+ else if (change.type === "insertion" || _optionalChain([change, 'access', _5 => _5.block, 'optionalAccess', _6 => _6.operation]) === "insert") summary.insertions++;
720
+ else if (change.type === "deletion" || _optionalChain([change, 'access', _7 => _7.block, 'optionalAccess', _8 => _8.operation]) === "delete") summary.deletions++;
688
721
  else if (change.type === "format") summary.formatChanges++;
689
722
  else summary.blockChanges++;
690
723
  });
691
724
  return summary;
692
725
  }
693
726
  function compareDocuments(original, revised, options = {}) {
694
- const schema = _nullishCoalesce(options.schema, () => ( (typeof _optionalChain([original, 'optionalAccess', _6 => _6.descendants]) === "function" ? original.type.schema : typeof _optionalChain([revised, 'optionalAccess', _7 => _7.descendants]) === "function" ? revised.type.schema : null)));
727
+ const schema = _nullishCoalesce(options.schema, () => ( (typeof _optionalChain([original, 'optionalAccess', _9 => _9.descendants]) === "function" ? original.type.schema : typeof _optionalChain([revised, 'optionalAccess', _10 => _10.descendants]) === "function" ? revised.type.schema : null)));
695
728
  if (!schema) throw new Error("compareDocuments: `schema` is required for JSON or HTML sources.");
696
729
  const ctx = {
697
730
  schema,
698
731
  author: _nullishCoalesce(options.author, () => ( { id: "compare", name: "Compare" })),
699
732
  createdAt: toTime(options.date),
700
- generateId: _nullishCoalesce(options.generateId, () => ( (() => _chunkD27YFC46cjs.createReviewId.call(void 0, "change")))),
733
+ generateId: _nullishCoalesce(options.generateId, () => ( (() => _chunkX4N4WUKBcjs.createReviewId.call(void 0, "change")))),
701
734
  granularity: _nullishCoalesce(options.granularity, () => ( "word")),
702
735
  ignoreCase: !!options.ignoreCase,
703
736
  ignoreWhitespace: !!options.ignoreWhitespace,
@@ -705,9 +738,9 @@ function compareDocuments(original, revised, options = {}) {
705
738
  maxCharDiff: _nullishCoalesce(options.maxCharDiff, () => ( 2e3)),
706
739
  ignoredMarks: new Set(_nullishCoalesce(options.ignoredMarks, () => ( []))),
707
740
  ignoredAttributes: new Set(_nullishCoalesce(options.ignoredBlockAttributes, () => ( ["id"]))),
708
- insertion: schema.marks[_chunkD27YFC46cjs.INSERTION_MARK_NAME],
709
- deletion: schema.marks[_chunkD27YFC46cjs.DELETION_MARK_NAME],
710
- format: schema.marks[_chunkD27YFC46cjs.FORMAT_CHANGE_MARK_NAME],
741
+ insertion: schema.marks[_chunkX4N4WUKBcjs.INSERTION_MARK_NAME],
742
+ deletion: schema.marks[_chunkX4N4WUKBcjs.DELETION_MARK_NAME],
743
+ format: schema.marks[_chunkX4N4WUKBcjs.FORMAT_CHANGE_MARK_NAME],
711
744
  notes: /* @__PURE__ */ new Set(),
712
745
  keys: /* @__PURE__ */ new Map(),
713
746
  wrapperChange: null
@@ -720,13 +753,14 @@ function compareDocuments(original, revised, options = {}) {
720
753
  }
721
754
  const prepare = (source) => {
722
755
  const doc2 = toNode(source, schema);
723
- if (_chunkD27YFC46cjs.getTrackedChanges.call(void 0, doc2).length === 0) return doc2;
756
+ if (_chunkX4N4WUKBcjs.getTrackedChanges.call(void 0, doc2).length === 0) return doc2;
724
757
  ctx.notes.add("Tracked changes already in the documents were accepted before comparing.");
725
- return _chunkD27YFC46cjs.resolveTrackedChanges.call(void 0, doc2, "accepted");
758
+ return _chunkX4N4WUKBcjs.resolveTrackedChanges.call(void 0, doc2, "accepted");
726
759
  };
727
760
  const a = prepare(original);
728
761
  const b = prepare(revised);
729
- const children = compareChildren(a, b, [], [], ctx);
762
+ let children = compareChildren(a, b, [], [], ctx);
763
+ if (options.detectMoves) children = detectMovedBlocks(children, ctx);
730
764
  let doc;
731
765
  try {
732
766
  doc = b.type.createChecked(b.attrs, children, b.marks);
@@ -746,4 +780,4 @@ function compareHtml(originalHtml, revisedHtml, options) {
746
780
 
747
781
 
748
782
  exports.diffSequences = diffSequences; exports.diffChanges = diffChanges; exports.compareDocuments = compareDocuments; exports.compareHtml = compareHtml;
749
- //# sourceMappingURL=chunk-OLZJXL4R.cjs.map
783
+ //# sourceMappingURL=chunk-AL6JMGWW.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-AL6JMGWW.cjs","../src/compare/diff.ts","../src/compare/documents.ts"],"names":["x","y","k","i","j"],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-AL6JMGWW.cjs","sourcesContent":[null,"// Portions ported from pdfcraft/crates/compare/src/lib.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Generic sequence diff: common prefix/suffix stripping, Myers' O((N+M)·D)\n * algorithm for small segments and patience anchors (items unique on both\n * sides, longest increasing subsequence) to split long ones. Dependency-free.\n */\n\nexport type DiffOpType = \"equal\" | \"delete\" | \"insert\";\n\n/** A run of consecutive operations of one type. */\nexport interface DiffRun {\n type: DiffOpType;\n /** Start index in `a` (for `insert`, where the items go in `a`). */\n aIndex: number;\n /** Start index in `b` (for `delete`, where the items were in `b`). */\n bIndex: number;\n length: number;\n}\n\n/** A change: a deleted range of `a`, an inserted range of `b`, or both (replaced). */\nexport interface DiffChange {\n kind: \"inserted\" | \"deleted\" | \"replaced\";\n aStart: number;\n aEnd: number;\n bStart: number;\n bEnd: number;\n}\n\nexport interface DiffSequencesOptions<T> {\n /**\n * Key of an item: items with the same key are equal. Defaults to the item\n * itself for strings, numbers and booleans, and `JSON.stringify` otherwise.\n * Keys enable patience anchors for long inputs.\n */\n hash?: (item: T) => string | number | boolean;\n /**\n * Custom equality. Without `hash`, segments longer than `direct` are only\n * diffed while the edit distance stays below `direct`, otherwise replaced.\n */\n equals?: (a: T, b: T) => boolean;\n /**\n * Segments with more items (both sides together) are split at anchors\n * first so Myers' saved frontiers (O(D²) memory) stay small. Default 3000.\n */\n direct?: number;\n /** Maximum anchor recursion depth before a long segment is replaced whole. Default 8. */\n maxDepth?: number;\n}\n\nconst EQUAL = 0;\nconst DELETE = 1;\nconst INSERT = 2;\n\ntype Eq = (i: number, j: number) => boolean;\n\nfunction pushRepeat(out: number[], op: number, count: number): void {\n for (let i = 0; i < count; i++) out.push(op);\n}\n\n/**\n * Myers' diff of a[aLo..aHi) and b[bLo..bHi). Returns false (and emits\n * nothing) when the edit distance exceeds `maxD`.\n */\nfunction myers(\n aLo: number,\n aHi: number,\n bLo: number,\n bHi: number,\n eq: Eq,\n out: number[],\n maxD: number,\n): boolean {\n const n = aHi - aLo;\n const m = bHi - bLo;\n if (n === 0 || m === 0) {\n pushRepeat(out, DELETE, n);\n pushRepeat(out, INSERT, m);\n return true;\n }\n const max = n + m;\n const off = max + 1;\n const v = new Int32Array(2 * max + 3);\n // Frontier d only spans diagonals -d..d: save just that slice.\n const trace: Int32Array[] = [];\n const limit = Math.min(max, maxD);\n let found = false;\n outer: for (let d = 0; d <= limit; d++) {\n trace.push(v.slice(Math.max(off - d - 1, 0), off + d + 2));\n for (let k = -d; k <= d; k += 2) {\n const i = k + off;\n let x = k === -d || (k !== d && v[i - 1] < v[i + 1]) ? v[i + 1] : v[i - 1] + 1;\n let y = x - k;\n while (x < n && y < m && eq(aLo + x, bLo + y)) {\n x++;\n y++;\n }\n v[i] = x;\n if (x >= n && y >= m) {\n found = true;\n break outer;\n }\n }\n }\n if (!found) return false;\n\n // Walk back through the saved frontiers.\n const ops: number[] = [];\n let x = n;\n let y = m;\n for (let d = trace.length - 1; d >= 0; d--) {\n const frontier = trace[d];\n const base = Math.max(off - d - 1, 0);\n const at = (k: number) => frontier[k + off - base];\n const k = x - y;\n const prevK = k === -d || (k !== d && at(k - 1) < at(k + 1)) ? k + 1 : k - 1;\n const prevX = at(prevK);\n const prevY = prevX - prevK;\n while (x > prevX && y > prevY) {\n ops.push(EQUAL);\n x--;\n y--;\n }\n if (d > 0) ops.push(x === prevX ? INSERT : DELETE);\n x = prevX;\n y = prevY;\n }\n for (let i = ops.length - 1; i >= 0; i--) out.push(ops[i]);\n return true;\n}\n\n/**\n * Patience anchors: items that occur once on each side, in an order both\n * sides agree on (the longest increasing subsequence of their positions).\n */\nfunction anchors(\n ids: { a: Int32Array; b: Int32Array },\n aLo: number,\n aHi: number,\n bLo: number,\n bHi: number,\n): [number, number][] {\n const count = new Map<number, [number, number, number, number]>();\n for (let i = aLo; i < aHi; i++) {\n const id = ids.a[i];\n const entry = count.get(id);\n if (entry) {\n entry[0]++;\n } else {\n count.set(id, [1, 0, i, -1]);\n }\n }\n for (let j = bLo; j < bHi; j++) {\n const entry = count.get(ids.b[j]);\n if (entry) {\n entry[1]++;\n entry[3] = j;\n }\n }\n const pairs: [number, number][] = [];\n count.forEach((entry) => {\n if (entry[0] === 1 && entry[1] === 1) pairs.push([entry[2], entry[3]]);\n });\n pairs.sort((p, q) => p[0] - q[0]);\n // LIS on the b positions (patience sorting with back links).\n const tails: number[] = [];\n const prev = new Int32Array(pairs.length).fill(-1);\n for (let k = 0; k < pairs.length; k++) {\n const y = pairs[k][1];\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (pairs[tails[mid]][1] < y) lo = mid + 1;\n else hi = mid;\n }\n if (lo > 0) prev[k] = tails[lo - 1];\n if (lo === tails.length) tails.push(k);\n else tails[lo] = k;\n }\n const result: [number, number][] = [];\n let k = tails.length ? tails[tails.length - 1] : -1;\n while (k !== -1) {\n result.push(pairs[k]);\n k = prev[k];\n }\n return result.reverse();\n}\n\ninterface DiffContext {\n eq: Eq;\n ids: { a: Int32Array; b: Int32Array } | null;\n direct: number;\n maxDepth: number;\n}\n\nfunction diffRange(\n ctx: DiffContext,\n aLo: number,\n aHi: number,\n bLo: number,\n bHi: number,\n out: number[],\n depth: number,\n): void {\n const { eq } = ctx;\n let prefix = 0;\n while (aLo + prefix < aHi && bLo + prefix < bHi && eq(aLo + prefix, bLo + prefix)) prefix++;\n let suffix = 0;\n while (\n aHi - suffix > aLo + prefix &&\n bHi - suffix > bLo + prefix &&\n eq(aHi - suffix - 1, bHi - suffix - 1)\n ) {\n suffix++;\n }\n pushRepeat(out, EQUAL, prefix);\n const ma = aLo + prefix;\n const mb = bLo + prefix;\n const ea = aHi - suffix;\n const eb = bHi - suffix;\n const replace = () => {\n pushRepeat(out, DELETE, ea - ma);\n pushRepeat(out, INSERT, eb - mb);\n };\n\n if (ea - ma + (eb - mb) <= ctx.direct) {\n myers(ma, ea, mb, eb, eq, out, Infinity);\n } else if (!ctx.ids) {\n // No keys, so no anchors: bounded Myers, else one replacement.\n if (!myers(ma, ea, mb, eb, eq, out, ctx.direct)) replace();\n } else {\n const found = depth >= ctx.maxDepth ? [] : anchors(ctx.ids, ma, ea, mb, eb);\n if (found.length === 0) {\n // A long stretch with nothing unique in common: one replacement.\n replace();\n } else {\n let i = ma;\n let j = mb;\n for (const [x, y] of found) {\n diffRange(ctx, i, x, j, y, out, depth + 1);\n out.push(EQUAL);\n i = x + 1;\n j = y + 1;\n }\n diffRange(ctx, i, ea, j, eb, out, depth + 1);\n }\n }\n pushRepeat(out, EQUAL, suffix);\n}\n\nconst defaultHash = (item: unknown): string | number | boolean =>\n typeof item === \"string\" || typeof item === \"number\" || typeof item === \"boolean\"\n ? item\n : JSON.stringify(item);\n\n/**\n * Diffs two sequences and returns the edit script as runs of equal, deleted\n * and inserted items, in order. Within a changed stretch deletions come\n * before insertions. The script is minimal for segments up to `direct`\n * items; longer inputs are split at patience anchors (bounded work).\n */\nexport function diffSequences<T>(\n a: readonly T[],\n b: readonly T[],\n options: DiffSequencesOptions<T> = {},\n): DiffRun[] {\n const direct = Math.max(2, options.direct ?? 3000);\n const maxDepth = options.maxDepth ?? 8;\n let ids: { a: Int32Array; b: Int32Array } | null = null;\n let eq: Eq;\n if (options.equals && !options.hash) {\n const equals = options.equals;\n eq = (i, j) => equals(a[i], b[j]);\n } else {\n const hash = options.hash ?? (defaultHash as (item: T) => string | number | boolean);\n const table = new Map<string | number | boolean, number>();\n const idOf = (item: T) => {\n const key = hash(item);\n let id = table.get(key);\n if (id === undefined) {\n id = table.size;\n table.set(key, id);\n }\n return id;\n };\n ids = { a: Int32Array.from(a, idOf), b: Int32Array.from(b, idOf) };\n const { a: ia, b: ib } = ids;\n eq = (i, j) => ia[i] === ib[j];\n }\n\n const ops: number[] = [];\n diffRange({ eq, ids, direct, maxDepth }, 0, a.length, 0, b.length, ops, 0);\n\n // Group into runs, deletions first within each changed stretch.\n const runs: DiffRun[] = [];\n const push = (type: DiffOpType, aIndex: number, bIndex: number, length: number) => {\n if (length === 0) return;\n const last = runs[runs.length - 1];\n if (last && last.type === type) {\n last.length += length;\n } else {\n runs.push({ type, aIndex, bIndex, length });\n }\n };\n let i = 0;\n let j = 0;\n let k = 0;\n while (k < ops.length) {\n if (ops[k] === EQUAL) {\n let n = 0;\n while (k < ops.length && ops[k] === EQUAL) {\n n++;\n k++;\n }\n push(\"equal\", i, j, n);\n i += n;\n j += n;\n continue;\n }\n let dels = 0;\n let inss = 0;\n while (k < ops.length && ops[k] !== EQUAL) {\n if (ops[k] === DELETE) dels++;\n else inss++;\n k++;\n }\n push(\"delete\", i, j, dels);\n push(\"insert\", i + dels, j, inss);\n i += dels;\n j += inss;\n }\n return runs;\n}\n\n/** Groups an edit script into inserted, deleted and replaced ranges. */\nexport function diffChanges<T>(\n a: readonly T[],\n b: readonly T[],\n options: DiffSequencesOptions<T> = {},\n): DiffChange[] {\n const changes: DiffChange[] = [];\n let current: DiffChange | null = null;\n for (const run of diffSequences(a, b, options)) {\n if (run.type === \"equal\") {\n current = null;\n continue;\n }\n if (!current) {\n current = {\n kind: run.type === \"delete\" ? \"deleted\" : \"inserted\",\n aStart: run.aIndex,\n aEnd: run.aIndex,\n bStart: run.bIndex,\n bEnd: run.bIndex,\n };\n changes.push(current);\n }\n if (run.type === \"delete\") {\n current.aEnd = run.aIndex + run.length;\n } else {\n current.bEnd = run.bIndex + run.length;\n }\n if (current.aEnd > current.aStart && current.bEnd > current.bStart) current.kind = \"replaced\";\n }\n return changes;\n}\n","// Portions ported from wordcraft/crates/engine/src/cmd/tools.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\nimport { createDocument } from \"@tiptap/core\";\nimport {\n Fragment,\n Mark as ProsemirrorMark,\n type MarkType,\n type Node as ProsemirrorNode,\n type Schema,\n} from \"@tiptap/pm/model\";\nimport {\n BLOCK_CHANGE_ATTRIBUTE,\n DELETION_MARK_NAME,\n FORMAT_CHANGE_MARK_NAME,\n INSERTION_MARK_NAME,\n MOVE_ID_ATTRIBUTE,\n getTrackedChanges,\n resolveTrackedChanges,\n type TrackedBlockChangeAttributes,\n type TrackedBlockOperation,\n type TrackedBlockSnapshot,\n type TrackedChangeAttributes,\n type TrackedMark,\n} from \"../extensions/track-changes\";\nimport { createReviewId, type ReviewAuthor } from \"../extensions/comment-store\";\nimport { diffSequences } from \"./diff\";\n\n/** A document to compare: a ProseMirror node, its JSON, or HTML (needs a DOM). */\nexport type CompareSource = ProsemirrorNode | Record<string, any> | string;\n\nexport interface CompareDocumentsOptions {\n /** Schema of the documents. Required unless both sources are ProseMirror nodes. */\n schema?: Schema;\n /** Author recorded on every change. Default `{ id: \"compare\", name: \"Compare\" }`. */\n author?: ReviewAuthor;\n /** Time recorded on every change (`createdAt`). Default now. */\n date?: number | Date | string;\n /** Compare changed paragraphs word by word (default) or character by character. */\n granularity?: \"word\" | \"char\";\n /** Treat text differing only in case as equal (the revised text is kept). */\n ignoreCase?: boolean;\n /** Treat runs of white space as equal (the revised text is kept). */\n ignoreWhitespace?: boolean;\n /** Record formatting differences of equal text as `formatChange` marks. Default `true`. */\n formatting?: boolean;\n /** Change id generator. */\n generateId?: () => string;\n /** Marks not compared as formatting (in addition to review marks, comments and bookmarks). */\n ignoredMarks?: string[];\n /** Node attributes ignored when comparing blocks. Default `[\"id\"]`. */\n ignoredBlockAttributes?: string[];\n /**\n * Paragraphs longer than this (in characters) are compared by words even\n * at `granularity: \"char\"`. Default 2000.\n */\n maxCharDiff?: number;\n /**\n * Record a top-level paragraph that was deleted in one place and inserted,\n * identical, elsewhere as a tracked move (`type: \"move\"`) instead of a\n * whole-block deletion and insertion. Only paragraphs whose content occurs\n * once among the deleted and once among the inserted blocks are paired.\n * Default `false`.\n */\n detectMoves?: boolean;\n}\n\nexport interface CompareSummary {\n /** Inserted text runs and inserted block runs. */\n insertions: number;\n /** Deleted text runs and deleted block runs. */\n deletions: number;\n /** Formatting changes of equal text. */\n formatChanges: number;\n /** Block type, attribute and wrapping changes. */\n blockChanges: number;\n /** Moved paragraphs (with `detectMoves`). */\n moves: number;\n /** Differences the result could not represent as tracked changes. */\n notHonoured: string[];\n}\n\nexport interface CompareDocumentsResult {\n /**\n * The revised document with the differences as tracked changes: accepting\n * all changes yields the revised document, rejecting all the original.\n */\n doc: ProsemirrorNode;\n summary: CompareSummary;\n}\n\n/** Marks that are never compared as formatting. */\nconst REVIEW_MARK_NAMES = new Set([\n INSERTION_MARK_NAME,\n DELETION_MARK_NAME,\n FORMAT_CHANGE_MARK_NAME,\n \"comment\",\n \"bookmark\",\n]);\n\n/** Container types that pair with each other (a list whose type changed). */\nconst LIST_TYPES = new Set([\"bulletList\", \"orderedList\"]);\n\nconst OBJECT_REPLACEMENT = \"\";\n\ntype Wrapper = TrackedBlockSnapshot[\"wrappers\"][number];\n\ninterface Unit {\n /** One code point, or \"\" for an inline leaf node. */\n text: string;\n node: ProsemirrorNode | null;\n marks: readonly ProsemirrorMark[];\n}\n\ninterface Context {\n schema: Schema;\n author: ReviewAuthor;\n createdAt: number;\n generateId: () => string;\n granularity: \"word\" | \"char\";\n ignoreCase: boolean;\n ignoreWhitespace: boolean;\n formatting: boolean;\n maxCharDiff: number;\n ignoredMarks: Set<string>;\n ignoredAttributes: Set<string>;\n insertion?: MarkType;\n deletion?: MarkType;\n format?: MarkType;\n notes: Set<string>;\n keys: Map<ProsemirrorNode, string>;\n /** Shared change of the textblocks inside a container whose type changed. */\n wrapperChange: (() => TrackedChangeAttributes) | null;\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === undefined) return \"null\";\n if (value === null || typeof value !== \"object\") return JSON.stringify(value);\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n const keys = Object.keys(value as object).sort();\n return `{${keys\n .map((key) => `${JSON.stringify(key)}:${stableStringify((value as any)[key])}`)\n .join(\",\")}}`;\n}\n\nfunction toTime(date: CompareDocumentsOptions[\"date\"]): number {\n if (date instanceof Date) return date.getTime();\n if (typeof date === \"string\") {\n const time = Date.parse(date);\n return Number.isNaN(time) ? Date.now() : time;\n }\n return typeof date === \"number\" ? date : Date.now();\n}\n\nfunction toNode(source: CompareSource, schema: Schema): ProsemirrorNode {\n if (typeof source === \"string\") return createDocument(source, schema);\n if (typeof (source as ProsemirrorNode).descendants === \"function\") {\n const node = source as ProsemirrorNode;\n return node.type.schema === schema ? node : schema.nodeFromJSON(node.toJSON());\n }\n return schema.nodeFromJSON(source);\n}\n\nfunction newAttributes(ctx: Context): TrackedChangeAttributes {\n return {\n changeId: ctx.generateId(),\n authorId: ctx.author.id,\n authorName: ctx.author.name,\n authorColor: ctx.author.color ?? null,\n createdAt: ctx.createdAt,\n };\n}\n\n/** Lazily created attributes shared by one run of changes. */\nfunction lazyAttributes(ctx: Context): () => TrackedChangeAttributes {\n let attrs: TrackedChangeAttributes | null = null;\n return () => (attrs = attrs ?? newAttributes(ctx));\n}\n\nfunction stripBlockChange(attrs: Record<string, any>): Record<string, any> {\n const copy = { ...attrs };\n delete copy[BLOCK_CHANGE_ATTRIBUTE];\n return copy;\n}\n\nfunction wrapperOf(node: ProsemirrorNode): Wrapper {\n return { type: node.type.name, attrs: stripBlockChange(node.attrs) };\n}\n\nfunction snapshot(node: ProsemirrorNode, wrappers: Wrapper[]): TrackedBlockSnapshot {\n return { type: node.type.name, attrs: stripBlockChange(node.attrs), wrappers };\n}\n\nfunction filteredAttributes(attrs: Record<string, any>, ctx: Context): string {\n return stableStringify(\n Object.fromEntries(\n Object.entries(attrs ?? {}).filter(\n ([key]) => key !== BLOCK_CHANGE_ATTRIBUTE && !ctx.ignoredAttributes.has(key),\n ),\n ),\n );\n}\n\n/** Mirrors the tracker's block comparison: type, attributes and wrapper types. */\nfunction sameBlock(a: TrackedBlockSnapshot, b: TrackedBlockSnapshot, ctx: Context): boolean {\n if (a.type !== b.type) return false;\n if (filteredAttributes(a.attrs, ctx) !== filteredAttributes(b.attrs, ctx)) return false;\n if (a.wrappers.length !== b.wrappers.length) return false;\n return a.wrappers.every((w, i) => w.type === b.wrappers[i].type);\n}\n\nconst hasBlockChange = (node: ProsemirrorNode) =>\n !!node.type.spec.attrs && BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs;\n\nconst label = (node: ProsemirrorNode) => node.type.name;\n\n// ---------------------------------------------------------------------------\n// Keys\n// ---------------------------------------------------------------------------\n\nfunction normalize(text: string, ctx: Context): string {\n let result = ctx.ignoreWhitespace ? text.replace(/\\s+/g, \" \") : text;\n if (ctx.ignoreCase) result = result.toLowerCase();\n return result;\n}\n\nfunction leafKey(node: ProsemirrorNode): string {\n return `${node.type.name}${stableStringify(stripBlockChange(node.attrs))}`;\n}\n\nfunction group(node: ProsemirrorNode): string {\n return LIST_TYPES.has(node.type.name) ? \"list\" : node.type.name;\n}\n\n/** Block key: textblocks by their (normalized) text only, so type changes pair up. */\nfunction blockKey(node: ProsemirrorNode, ctx: Context): string {\n const cached = ctx.keys.get(node);\n if (cached !== undefined) return cached;\n let key: string;\n if (node.isTextblock) {\n let text = \"\";\n node.forEach((child) => {\n text += child.isText ? child.text : `${OBJECT_REPLACEMENT}${leafKey(child)}${OBJECT_REPLACEMENT}`;\n });\n text = normalize(text, ctx);\n key = `T\\u0000${ctx.ignoreWhitespace ? text.trim() : text}`;\n } else if (node.isLeaf) {\n key = `A\\u0000${leafKey(node)}`;\n } else {\n const parts: string[] = [];\n node.forEach((child) => parts.push(blockKey(child, ctx)));\n key = `C\\u0000${group(node)}\\u0000${parts.join(\"\\u0001\")}\\u0002`;\n }\n ctx.keys.set(node, key);\n return key;\n}\n\nfunction compatible(a: ProsemirrorNode, b: ProsemirrorNode): boolean {\n if (a.isTextblock || b.isTextblock) return a.isTextblock && b.isTextblock;\n if (a.isLeaf || b.isLeaf) return a.isLeaf && b.isLeaf && a.type === b.type;\n return group(a) === group(b);\n}\n\n// ---------------------------------------------------------------------------\n// Whole blocks\n// ---------------------------------------------------------------------------\n\nfunction markInline(\n content: Fragment,\n mark: ProsemirrorMark,\n): Fragment {\n const nodes: ProsemirrorNode[] = [];\n content.forEach((child) => nodes.push(child.mark(mark.addToSet(child.marks))));\n return Fragment.from(nodes);\n}\n\n/**\n * A block deleted or inserted as a whole: every textblock inside gets a\n * `blockChange` record with the operation (one change id per run).\n */\nfunction wholeBlock(\n node: ProsemirrorNode,\n operation: TrackedBlockOperation,\n wrappers: Wrapper[],\n attrs: () => TrackedChangeAttributes,\n ctx: Context,\n): ProsemirrorNode[] {\n const verb = operation === \"insert\" ? \"Inserted\" : \"Deleted\";\n if (node.isTextblock) {\n if (hasBlockChange(node)) {\n const record: TrackedBlockChangeAttributes = {\n ...attrs(),\n previous: snapshot(node, wrappers),\n operation,\n };\n return [node.type.create({ ...node.attrs, [BLOCK_CHANGE_ATTRIBUTE]: record }, node.content, node.marks)];\n }\n // No block record: fall back to marking the text.\n const markType = operation === \"insert\" ? ctx.insertion : ctx.deletion;\n if (markType && node.type.allowsMarkType(markType) && node.content.size > 0) {\n ctx.notes.add(\n `${verb} ${label(node)} blocks have no \"${BLOCK_CHANGE_ATTRIBUTE}\" attribute: only their text is tracked, so an empty block remains when the change is ${operation === \"insert\" ? \"rejected\" : \"accepted\"}.`,\n );\n return [node.type.create(node.attrs, markInline(node.content, markType.create(attrs())), node.marks)];\n }\n ctx.notes.add(`${verb} ${label(node)} blocks cannot be tracked.`);\n return operation === \"insert\" ? [node] : [];\n }\n if (node.isLeaf) {\n ctx.notes.add(`${verb} ${label(node)} blocks cannot be tracked.`);\n return operation === \"insert\" ? [node] : [];\n }\n const inner = [...wrappers, wrapperOf(node)];\n const children: ProsemirrorNode[] = [];\n node.forEach((child) => children.push(...wholeBlock(child, operation, inner, attrs, ctx)));\n try {\n return [node.type.createChecked(node.attrs, children, node.marks)];\n } catch {\n ctx.notes.add(`${verb} ${label(node)} blocks cannot be tracked.`);\n return operation === \"insert\" ? [node] : [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Inline comparison\n// ---------------------------------------------------------------------------\n\nfunction units(node: ProsemirrorNode): Unit[] {\n const result: Unit[] = [];\n node.forEach((child) => {\n if (child.isText) {\n for (const ch of child.text ?? \"\") result.push({ text: ch, node: null, marks: child.marks });\n } else {\n result.push({ text: \"\", node: child, marks: child.marks });\n }\n });\n return result;\n}\n\nconst SPACE = /\\s/u;\nconst WORD = /[\\p{L}\\p{N}\\p{M}_]/u;\n\nconst isSpace = (u: Unit) => !u.node && SPACE.test(u.text);\nconst isWord = (u: Unit) => !u.node && WORD.test(u.text);\n\n/** Token ranges: words, white space runs and single symbols, or characters. */\nfunction tokenize(list: Unit[], chars: boolean): [number, number][] {\n const tokens: [number, number][] = [];\n if (chars) {\n for (let i = 0; i < list.length; i++) tokens.push([i, i + 1]);\n return tokens;\n }\n let i = 0;\n while (i < list.length) {\n const start = i;\n const unit = list[i];\n if (isSpace(unit)) {\n while (i < list.length && isSpace(list[i])) i++;\n } else if (isWord(unit)) {\n while (i < list.length && isWord(list[i])) i++;\n } else {\n i++;\n }\n tokens.push([start, i]);\n }\n return tokens;\n}\n\ntype Stretch =\n | { type: \"equal\"; aIndex: number; bIndex: number; length: number }\n | { type: \"change\"; aFrom: number; aTo: number; bFrom: number; bTo: number };\n\n/**\n * Groups token runs into equal stretches and changes. White space alone\n * between two changes joins them, so \"a b\" → \"x y\" reads as one\n * replacement instead of alternating deletions and insertions.\n */\nfunction stretches(runs: ReturnType<typeof diffSequences>, list: Unit[], tokens: [number, number][]): Stretch[] {\n const spaceOnly = (bIndex: number, length: number) => {\n for (let t = bIndex; t < bIndex + length; t++) {\n const [from, to] = tokens[t];\n for (let k = from; k < to; k++) if (!isSpace(list[k])) return false;\n }\n return true;\n };\n const result: Stretch[] = [];\n runs.forEach((run, index) => {\n const prev = runs[index - 1];\n const next = runs[index + 1];\n const absorbed =\n run.type === \"equal\" &&\n prev &&\n next &&\n prev.type !== \"equal\" &&\n next.type !== \"equal\" &&\n spaceOnly(run.bIndex, run.length);\n if (run.type === \"equal\" && !absorbed) {\n result.push({ type: \"equal\", aIndex: run.aIndex, bIndex: run.bIndex, length: run.length });\n return;\n }\n let last = result[result.length - 1];\n if (!last || last.type !== \"change\") {\n last = { type: \"change\", aFrom: run.aIndex, aTo: run.aIndex, bFrom: run.bIndex, bTo: run.bIndex };\n result.push(last);\n }\n if (run.type !== \"insert\") last.aTo = run.aIndex + run.length;\n if (run.type !== \"delete\") last.bTo = run.bIndex + run.length;\n });\n return result;\n}\n\nfunction tokenKey(list: Unit[], [from, to]: [number, number], ctx: Context): string {\n let text = \"\";\n for (let i = from; i < to; i++) {\n const unit = list[i];\n text += unit.node ? `${OBJECT_REPLACEMENT}${leafKey(unit.node)}${OBJECT_REPLACEMENT}` : unit.text;\n }\n return normalize(text, ctx);\n}\n\nfunction formattingOf(marks: readonly ProsemirrorMark[], ctx: Context): ProsemirrorMark[] {\n return marks.filter(\n (mark) => !REVIEW_MARK_NAMES.has(mark.type.name) && !ctx.ignoredMarks.has(mark.type.name),\n );\n}\n\nfunction withoutReviewMarks(marks: readonly ProsemirrorMark[]): readonly ProsemirrorMark[] {\n return marks.filter(\n (mark) =>\n mark.type.name !== INSERTION_MARK_NAME &&\n mark.type.name !== DELETION_MARK_NAME &&\n mark.type.name !== FORMAT_CHANGE_MARK_NAME,\n );\n}\n\n/**\n * Merges the inline content of two paired textblocks: equal tokens keep the\n * revised text (with `formatChange` where formatting differs), deleted\n * tokens are re-inserted with the `deletion` mark before inserted tokens.\n */\nfunction mergeInline(\n original: ProsemirrorNode,\n revised: ProsemirrorNode,\n parentType: ProsemirrorNode[\"type\"],\n ctx: Context,\n): Fragment {\n const a = units(original);\n const b = units(revised);\n let chars = ctx.granularity === \"char\";\n if (chars && Math.max(a.length, b.length) > ctx.maxCharDiff) {\n chars = false;\n ctx.notes.add(\n `Paragraphs longer than ${ctx.maxCharDiff} characters were compared by words instead of characters.`,\n );\n }\n const ta = tokenize(a, chars);\n const tb = tokenize(b, chars);\n const runs = diffSequences(\n ta.map((t) => tokenKey(a, t, ctx)),\n tb.map((t) => tokenKey(b, t, ctx)),\n );\n\n const format = ctx.formatting && ctx.format && parentType.allowsMarkType(ctx.format) ? ctx.format : null;\n const out: Unit[] = [];\n let formatRun: { attrs: TrackedChangeAttributes; key: string } | null = null;\n\n const emit = (\n source: Unit[],\n tokens: [number, number][],\n from: number,\n to: number,\n markType: MarkType,\n ) => {\n if (to <= from) return;\n const mark = markType.create(newAttributes(ctx));\n for (let k = tokens[from][0]; k < tokens[to - 1][1]; k++) {\n const unit = source[k];\n out.push({ ...unit, marks: mark.addToSet(withoutReviewMarks(unit.marks)) });\n }\n };\n\n for (const run of stretches(runs, b, tb)) {\n if (run.type === \"equal\") {\n for (let t = 0; t < run.length; t++) {\n const [aFrom, aTo] = ta[run.aIndex + t];\n const [bFrom, bTo] = tb[run.bIndex + t];\n for (let k = bFrom; k < bTo; k++) {\n const unit = b[k];\n const before = a[Math.min(aFrom + (k - bFrom), aTo - 1)];\n const marks = withoutReviewMarks(unit.marks);\n if (!format) {\n formatRun = null;\n out.push({ ...unit, marks });\n continue;\n }\n const oldFormat = formattingOf(before.marks, ctx);\n const newFormat = formattingOf(unit.marks, ctx);\n if (ProsemirrorMark.sameSet(oldFormat, newFormat)) {\n formatRun = null;\n out.push({ ...unit, marks });\n continue;\n }\n const beforeJSON = oldFormat.map((m) => m.toJSON() as TrackedMark);\n const afterJSON = newFormat.map((m) => m.toJSON() as TrackedMark);\n const key = stableStringify([beforeJSON, afterJSON]);\n if (!formatRun || formatRun.key !== key) formatRun = { attrs: newAttributes(ctx), key };\n const mark = format.create({ ...formatRun.attrs, before: beforeJSON, after: afterJSON });\n out.push({ ...unit, marks: mark.addToSet(marks) });\n }\n }\n continue;\n }\n formatRun = null;\n emit(a, ta, run.aFrom, run.aTo, ctx.deletion!);\n emit(b, tb, run.bFrom, run.bTo, ctx.insertion!);\n }\n\n const nodes: ProsemirrorNode[] = [];\n let i = 0;\n while (i < out.length) {\n const unit = out[i];\n if (unit.node) {\n nodes.push(unit.node.mark(unit.marks));\n i++;\n continue;\n }\n let text = unit.text;\n let j = i + 1;\n while (j < out.length && !out[j].node && ProsemirrorMark.sameSet(out[j].marks, unit.marks)) {\n text += out[j].text;\n j++;\n }\n nodes.push(ctx.schema.text(text, unit.marks));\n i = j;\n }\n return Fragment.from(nodes);\n}\n\n// ---------------------------------------------------------------------------\n// Blocks\n// ---------------------------------------------------------------------------\n\nfunction mergeTextblocks(\n original: ProsemirrorNode,\n revised: ProsemirrorNode,\n oPath: Wrapper[],\n rPath: Wrapper[],\n ctx: Context,\n): ProsemirrorNode[] {\n const replaceWhole = () => [\n ...wholeBlock(original, \"delete\", rPath, lazyAttributes(ctx), ctx),\n ...wholeBlock(revised, \"insert\", rPath, lazyAttributes(ctx), ctx),\n ];\n\n let attrs: Record<string, any> = { ...revised.attrs };\n const previous = snapshot(original, oPath);\n if (!sameBlock(previous, snapshot(revised, rPath), ctx)) {\n if (hasBlockChange(revised)) {\n // Only the wrappers changed (e.g. bulleted → numbered list): one change per container.\n const onlyWrappers =\n ctx.wrapperChange &&\n sameBlock(previous, { ...snapshot(revised, rPath), wrappers: previous.wrappers }, ctx);\n const base = onlyWrappers ? ctx.wrapperChange!() : newAttributes(ctx);\n attrs = { ...attrs, [BLOCK_CHANGE_ATTRIBUTE]: { ...base, previous } };\n } else {\n ctx.notes.add(`Block changes to ${label(revised)} blocks cannot be tracked.`);\n }\n } else if (BLOCK_CHANGE_ATTRIBUTE in attrs) {\n attrs[BLOCK_CHANGE_ATTRIBUTE] = null;\n }\n\n const type = revised.type;\n const inlineTracking =\n !!ctx.insertion &&\n !!ctx.deletion &&\n type.allowsMarkType(ctx.insertion) &&\n type.allowsMarkType(ctx.deletion);\n if (!inlineTracking) {\n // e.g. code blocks in a headless schema: equal text keeps the block,\n // anything else is replaced whole.\n if (!original.content.eq(revised.content)) return replaceWhole();\n return [type.create(attrs, revised.content, revised.marks)];\n }\n const content = mergeInline(original, revised, type, ctx);\n if (!type.validContent(content)) {\n ctx.notes.add(`Changes between ${label(original)} and ${label(revised)} blocks were tracked as whole-block replacements.`);\n return replaceWhole();\n }\n return [type.create(attrs, content, revised.marks)];\n}\n\nfunction mergePair(\n original: ProsemirrorNode,\n revised: ProsemirrorNode,\n oPath: Wrapper[],\n rPath: Wrapper[],\n ctx: Context,\n): ProsemirrorNode[] {\n if (original.isTextblock) return mergeTextblocks(original, revised, oPath, rPath, ctx);\n const replaceWhole = () => [\n ...wholeBlock(original, \"delete\", rPath, lazyAttributes(ctx), ctx),\n ...wholeBlock(revised, \"insert\", rPath, lazyAttributes(ctx), ctx),\n ];\n if (original.isLeaf) {\n return original.eq(revised) ? [revised] : replaceWhole();\n }\n if (\n original.type === revised.type &&\n filteredAttributes(original.attrs, ctx) !== filteredAttributes(revised.attrs, ctx)\n ) {\n ctx.notes.add(`Attribute changes of ${label(revised)} nodes are not tracked.`);\n }\n const outer = ctx.wrapperChange;\n if (original.type !== revised.type) ctx.wrapperChange = lazyAttributes(ctx);\n let children: ProsemirrorNode[];\n try {\n children = compareChildren(\n original,\n revised,\n [...oPath, wrapperOf(original)],\n [...rPath, wrapperOf(revised)],\n ctx,\n );\n } finally {\n ctx.wrapperChange = outer;\n }\n try {\n return [revised.type.createChecked(revised.attrs, children, revised.marks)];\n } catch {\n ctx.notes.add(`Changes inside ${label(revised)} nodes were tracked as whole-block replacements.`);\n return replaceWhole();\n }\n}\n\nfunction compareChildren(\n original: ProsemirrorNode,\n revised: ProsemirrorNode,\n oPath: Wrapper[],\n rPath: Wrapper[],\n ctx: Context,\n): ProsemirrorNode[] {\n const a: ProsemirrorNode[] = [];\n const b: ProsemirrorNode[] = [];\n original.forEach((child) => a.push(child));\n revised.forEach((child) => b.push(child));\n const runs = diffSequences(a, b, { hash: (node) => blockKey(node, ctx) });\n const out: ProsemirrorNode[] = [];\n\n for (let r = 0; r < runs.length; r++) {\n const run = runs[r];\n if (run.type === \"equal\") {\n for (let t = 0; t < run.length; t++) {\n out.push(...mergePair(a[run.aIndex + t], b[run.bIndex + t], oPath, rPath, ctx));\n }\n continue;\n }\n // A run of deletions and insertions: the i-th deleted block pairs with\n // the i-th inserted one when they are alike; the rest are whole.\n let dels: ProsemirrorNode[] = [];\n let inss: ProsemirrorNode[] = [];\n if (run.type === \"delete\") {\n dels = a.slice(run.aIndex, run.aIndex + run.length);\n const next = runs[r + 1];\n if (next?.type === \"insert\") {\n inss = b.slice(next.bIndex, next.bIndex + next.length);\n r++;\n }\n } else {\n inss = b.slice(run.bIndex, run.bIndex + run.length);\n }\n const deleted = lazyAttributes(ctx);\n const inserted = lazyAttributes(ctx);\n for (let x = 0; x < Math.max(dels.length, inss.length); x++) {\n const del = dels[x];\n const ins = inss[x];\n if (del && ins && compatible(del, ins)) {\n out.push(...mergePair(del, ins, oPath, rPath, ctx));\n continue;\n }\n if (del) out.push(...wholeBlock(del, \"delete\", rPath, deleted, ctx));\n if (ins) out.push(...wholeBlock(ins, \"insert\", rPath, inserted, ctx));\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Moves\n// ---------------------------------------------------------------------------\n\n/**\n * Pairs top-level paragraphs deleted as a whole with identical paragraphs\n * inserted as a whole elsewhere, and records each pair as a move: the\n * source's text gets the deletion mark and the destination's the insertion\n * mark, both with the same move id (accepting or rejecting removes the\n * emptied block). Ambiguous content (several identical candidates) is left\n * as whole-block changes.\n */\nfunction detectMovedBlocks(children: ProsemirrorNode[], ctx: Context): ProsemirrorNode[] {\n const keyOf = (node: ProsemirrorNode) =>\n `${node.type.name}\\u0000${filteredAttributes(node.attrs, ctx)}\\u0000${stableStringify(node.content.toJSON())}`;\n const candidates = new Map<string, { del: number[]; ins: number[] }>();\n children.forEach((node, index) => {\n const operation = (node.attrs?.[BLOCK_CHANGE_ATTRIBUTE] as TrackedBlockChangeAttributes | null)\n ?.operation;\n if (!operation || !node.isTextblock || !node.textContent.trim()) return;\n if (!node.type.allowsMarkType(ctx.insertion!) || !node.type.allowsMarkType(ctx.deletion!)) return;\n const key = keyOf(node);\n const entry = candidates.get(key) ?? { del: [], ins: [] };\n (operation === \"delete\" ? entry.del : entry.ins).push(index);\n candidates.set(key, entry);\n });\n const out = [...children];\n candidates.forEach(({ del, ins }) => {\n if (del.length !== 1 || ins.length !== 1) return;\n const attrs = newAttributes(ctx);\n const moved = { ...attrs, [MOVE_ID_ATTRIBUTE]: attrs.changeId };\n const relink = (index: number, markType: MarkType) => {\n const node = out[index];\n out[index] = node.type.create(\n { ...node.attrs, [BLOCK_CHANGE_ATTRIBUTE]: null },\n markInline(node.content, markType.create(moved)),\n node.marks,\n );\n };\n relink(del[0], ctx.deletion!);\n relink(ins[0], ctx.insertion!);\n });\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nfunction summarize(doc: ProsemirrorNode, notes: Set<string>): CompareSummary {\n const summary: CompareSummary = {\n insertions: 0,\n deletions: 0,\n formatChanges: 0,\n blockChanges: 0,\n moves: 0,\n notHonoured: [...notes],\n };\n getTrackedChanges(doc).forEach((change) => {\n if (change.type === \"move\") summary.moves++;\n else if (change.type === \"insertion\" || change.block?.operation === \"insert\") summary.insertions++;\n else if (change.type === \"deletion\" || change.block?.operation === \"delete\") summary.deletions++;\n else if (change.type === \"format\") summary.formatChanges++;\n else summary.blockChanges++;\n });\n return summary;\n}\n\n/**\n * Compares two documents and returns the revised document with the\n * differences as tracked changes (`insertion`/`deletion`/`formatChange`\n * marks and `blockChange` records), plus a summary.\n *\n * Blocks are matched by their text (Myers diff with patience anchors); in a\n * run of changed blocks the i-th deleted block is paired with the i-th\n * inserted one and compared word by word (or by character), while unpaired\n * blocks are recorded as whole-block insertions/deletions. Equal text is\n * compared for formatting, and type/attribute/wrapping changes become\n * `blockChange` records. Without ignore options, accepting all changes\n * yields the revised document and rejecting all yields the original.\n *\n * The schema needs the `TrackChanges` extension (marks and the\n * `blockChange` attribute). Existing tracked changes in either document are\n * accepted first.\n */\nexport function compareDocuments(\n original: CompareSource,\n revised: CompareSource,\n options: CompareDocumentsOptions = {},\n): CompareDocumentsResult {\n const schema =\n options.schema ??\n (typeof (original as ProsemirrorNode)?.descendants === \"function\"\n ? (original as ProsemirrorNode).type.schema\n : typeof (revised as ProsemirrorNode)?.descendants === \"function\"\n ? (revised as ProsemirrorNode).type.schema\n : null);\n if (!schema) throw new Error(\"compareDocuments: `schema` is required for JSON or HTML sources.\");\n\n const ctx: Context = {\n schema,\n author: options.author ?? { id: \"compare\", name: \"Compare\" },\n createdAt: toTime(options.date),\n generateId: options.generateId ?? (() => createReviewId(\"change\")),\n granularity: options.granularity ?? \"word\",\n ignoreCase: !!options.ignoreCase,\n ignoreWhitespace: !!options.ignoreWhitespace,\n formatting: options.formatting !== false,\n maxCharDiff: options.maxCharDiff ?? 2000,\n ignoredMarks: new Set(options.ignoredMarks ?? []),\n ignoredAttributes: new Set(options.ignoredBlockAttributes ?? [\"id\"]),\n insertion: schema.marks[INSERTION_MARK_NAME],\n deletion: schema.marks[DELETION_MARK_NAME],\n format: schema.marks[FORMAT_CHANGE_MARK_NAME],\n notes: new Set(),\n keys: new Map(),\n wrapperChange: null,\n };\n if (!ctx.insertion || !ctx.deletion) {\n throw new Error(\"compareDocuments: the schema needs the TrackChanges extension (insertion/deletion marks).\");\n }\n if (ctx.formatting && !ctx.format) {\n ctx.notes.add(\"Formatting differences cannot be tracked: the schema has no formatChange mark.\");\n }\n\n const prepare = (source: CompareSource) => {\n const doc = toNode(source, schema);\n if (getTrackedChanges(doc).length === 0) return doc;\n ctx.notes.add(\"Tracked changes already in the documents were accepted before comparing.\");\n return resolveTrackedChanges(doc, \"accepted\");\n };\n const a = prepare(original);\n const b = prepare(revised);\n\n let children = compareChildren(a, b, [], [], ctx);\n if (options.detectMoves) children = detectMovedBlocks(children, ctx);\n let doc: ProsemirrorNode;\n try {\n doc = b.type.createChecked(b.attrs, children, b.marks);\n } catch {\n doc = b.type.createAndFill(b.attrs, children, b.marks) ?? b;\n ctx.notes.add(\"The compared document had to be repaired to fit the schema.\");\n }\n return { doc, summary: summarize(doc, ctx.notes) };\n}\n\n/** `compareDocuments` for two HTML strings (needs a DOM, like `setContent`). */\nexport function compareHtml(\n originalHtml: string,\n revisedHtml: string,\n options: CompareDocumentsOptions & { schema: Schema },\n): CompareDocumentsResult {\n return compareDocuments(originalHtml, revisedHtml, options);\n}\n"]}
@@ -0,0 +1,69 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/serializers/mention-text.ts
2
+ var MENTION_REGEX = /(?:^|[\s>(*_"';\\/[\]{}~`:]|&quot;|&gt;|&lt;|&amp;)@(?:"([^"]+)"|&quot;([^&]+)&quot;|([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*))/g;
3
+ var NODE_ROLE_MARK = "";
4
+ var ROLE_SUFFIX_REGEX = /[:\u0001]([A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?)(?![A-Za-z0-9])/y;
5
+ var PLAIN_HANDLE_REGEX = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
6
+ function readRoleSuffix(text, index, roles) {
7
+ const first = text.charAt(index);
8
+ if (first !== ":" && first !== NODE_ROLE_MARK) return null;
9
+ if (first === ":" && !roles) return null;
10
+ ROLE_SUFFIX_REGEX.lastIndex = index;
11
+ const match = ROLE_SUFFIX_REGEX.exec(text);
12
+ if (!match) return null;
13
+ let role = match[1];
14
+ if (first === ":" && roles !== true) {
15
+ const lower = role.toLowerCase();
16
+ const listed = roles.find((r) => r.toLowerCase() === lower);
17
+ if (listed === void 0) return null;
18
+ role = listed;
19
+ }
20
+ return { role, end: index + match[0].length };
21
+ }
22
+ function formatMentionHandle(label) {
23
+ return PLAIN_HANDLE_REGEX.test(label) || label.includes('"') ? `@${label}` : `@"${label}"`;
24
+ }
25
+ function formatMentionText(handle, role) {
26
+ return role ? `${formatMentionHandle(handle)}:${role}` : formatMentionHandle(handle);
27
+ }
28
+ function findTextMentions(text, options = {}) {
29
+ if (!text || typeof text !== "string") return [];
30
+ const roles = options.roles === false ? void 0 : _nullishCoalesce(options.roles, () => ( true));
31
+ const result = [];
32
+ const regex = new RegExp(MENTION_REGEX.source, "g");
33
+ let match;
34
+ while ((match = regex.exec(text)) !== null) {
35
+ const handle = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(match[1], () => ( match[2])), () => ( match[3])), () => ( ""));
36
+ const start = match.index + match[0].indexOf("@");
37
+ const handleEnd = regex.lastIndex;
38
+ const suffix = text.charAt(handleEnd) === ":" ? readRoleSuffix(text, handleEnd, roles) : null;
39
+ if (suffix) regex.lastIndex = suffix.end;
40
+ result.push({
41
+ handle,
42
+ ...suffix ? { role: suffix.role } : {},
43
+ start,
44
+ end: suffix ? suffix.end : handleEnd,
45
+ handleEnd
46
+ });
47
+ }
48
+ return result;
49
+ }
50
+ function setMentionRoleInText(text, start, role, options = {}) {
51
+ const mention = findTextMentions(text, { roles: _nullishCoalesce(options.roles, () => ( true)) }).find((m) => m.start === start);
52
+ if (!mention) return text;
53
+ if (role !== null && !/^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/.test(role)) {
54
+ throw new Error(`Invalid mention role "${role}".`);
55
+ }
56
+ const suffix = role ? `:${role}` : "";
57
+ return text.slice(0, mention.handleEnd) + suffix + text.slice(mention.end);
58
+ }
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+ exports.MENTION_REGEX = MENTION_REGEX; exports.NODE_ROLE_MARK = NODE_ROLE_MARK; exports.readRoleSuffix = readRoleSuffix; exports.formatMentionHandle = formatMentionHandle; exports.formatMentionText = formatMentionText; exports.findTextMentions = findTextMentions; exports.setMentionRoleInText = setMentionRoleInText;
69
+ //# sourceMappingURL=chunk-BVP3TSLH.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-BVP3TSLH.cjs","../src/serializers/mention-text.ts"],"names":[],"mappings":"AAAA;ACcO,IAAM,cAAA,EACX,8HAAA;AAUK,IAAM,eAAA,EAAiB,GAAA;AAC9B,IAAM,kBAAA,EAAoB,mEAAA;AAC1B,IAAM,mBAAA,EAAqB,uCAAA;AAOpB,SAAS,cAAA,CACd,IAAA,EACA,KAAA,EACA,KAAA,EACsC;AACtC,EAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AAC/B,EAAA,GAAA,CAAI,MAAA,IAAU,IAAA,GAAO,MAAA,IAAU,cAAA,EAAgB,OAAO,IAAA;AACtD,EAAA,GAAA,CAAI,MAAA,IAAU,IAAA,GAAO,CAAC,KAAA,EAAO,OAAO,IAAA;AACpC,EAAA,iBAAA,CAAkB,UAAA,EAAY,KAAA;AAC9B,EAAA,MAAM,MAAA,EAAQ,iBAAA,CAAkB,IAAA,CAAK,IAAI,CAAA;AACzC,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,IAAA;AACnB,EAAA,IAAI,KAAA,EAAO,KAAA,CAAM,CAAC,CAAA;AAClB,EAAA,GAAA,CAAI,MAAA,IAAU,IAAA,GAAO,MAAA,IAAU,IAAA,EAAM;AACnC,IAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,WAAA,CAAY,CAAA;AAC/B,IAAA,MAAM,OAAA,EAAU,KAAA,CAA4B,IAAA,CAAK,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,WAAA,CAAY,EAAA,IAAM,KAAK,CAAA;AACjF,IAAA,GAAA,CAAI,OAAA,IAAW,KAAA,CAAA,EAAW,OAAO,IAAA;AACjC,IAAA,KAAA,EAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,EAAE,IAAA,EAAM,GAAA,EAAK,MAAA,EAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,OAAO,CAAA;AAC9C;AAGO,SAAS,mBAAA,CAAoB,KAAA,EAAuB;AACzD,EAAA,OAAO,kBAAA,CAAmB,IAAA,CAAK,KAAK,EAAA,GAAK,KAAA,CAAM,QAAA,CAAS,GAAG,EAAA,EAAI,CAAA,CAAA,EAAI,KAAK,CAAA,EAAA;AAC1E;AAMgF;AACrB,EAAA;AAC3D;AAmBmH;AAClE,EAAA;AACuB,EAAA;AACvC,EAAA;AACmB,EAAA;AAC9C,EAAA;AACwC,EAAA;AACS,IAAA;AACH,IAAA;AACxB,IAAA;AAC6C,IAAA;AAChC,IAAA;AACzB,IAAA;AACV,MAAA;AACsC,MAAA;AACtC,MAAA;AAC2B,MAAA;AAC3B,MAAA;AACD,IAAA;AACH,EAAA;AACO,EAAA;AACT;AAWU;AACiE,EAAA;AACpD,EAAA;AACiD,EAAA;AACnB,IAAA;AACnD,EAAA;AACmC,EAAA;AACsC,EAAA;AAC3E;ADnE2E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-BVP3TSLH.cjs","sourcesContent":[null,"/**\n * Dependency-free mention text helpers: the `@handle` pattern, the opt-in\n * `@handle:role` suffix used by asset mentions, and offset-based editing of\n * mentions in plain prompt text. Kept apart from `mentions.ts` (which pulls in\n * the HTML serializers) so `@pixerate/editor/generation` can re-export them.\n */\n\n/**\n * Canonical regex pattern for matching @mentions in plain text or markdown.\n * Supports:\n * - Standard unquoted handles: @username, @user.name, @team_alpha, @sa-1\n * - Quoted handles: @\"John Doe\", &quot;Jane Doe&quot;\n * Unquoted handles exclude trailing punctuation (e.g. @violet. matches \"violet\" without the dot).\n */\nexport const MENTION_REGEX =\n /(?:^|[\\s>(*_\"';\\\\/[\\]{}~`:]|&quot;|&gt;|&lt;|&amp;)@(?:\"([^\"]+)\"|&quot;([^&]+)&quot;|([a-zA-Z0-9_-]+(?:\\.[a-zA-Z0-9_-]+)*))/g;\n\n/**\n * Which `@handle:role` suffixes are read as a mention role: `true` for any\n * role name, or a list of accepted roles (matched case-insensitively and\n * returned as listed). Off by default, so `@john:` text is unchanged.\n */\nexport type MentionRoleSyntax = true | readonly string[];\n\n/** Marks a role taken from a mention node's `data-role` while extracting (never in output). */\nexport const NODE_ROLE_MARK = \"\\u0001\";\nconst ROLE_SUFFIX_REGEX = /[:\\u0001]([A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?)(?![A-Za-z0-9])/y;\nconst PLAIN_HANDLE_REGEX = /^[a-zA-Z0-9_-]+(?:\\.[a-zA-Z0-9_-]+)*$/;\n\n/**\n * Reads a `:role` suffix at `index`. Returns the role (as listed in `roles`)\n * and the index after it, or `null`. A suffix taken from a mention node\n * (`NODE_ROLE_MARK`) is accepted whatever `roles` says.\n */\nexport function readRoleSuffix(\n text: string,\n index: number,\n roles: MentionRoleSyntax | undefined,\n): { role: string; end: number } | null {\n const first = text.charAt(index);\n if (first !== \":\" && first !== NODE_ROLE_MARK) return null;\n if (first === \":\" && !roles) return null;\n ROLE_SUFFIX_REGEX.lastIndex = index;\n const match = ROLE_SUFFIX_REGEX.exec(text);\n if (!match) return null;\n let role = match[1];\n if (first === \":\" && roles !== true) {\n const lower = role.toLowerCase();\n const listed = (roles as readonly string[]).find((r) => r.toLowerCase() === lower);\n if (listed === undefined) return null;\n role = listed;\n }\n return { role, end: index + match[0].length };\n}\n\n/** `@label`, quoted when the label is not a plain handle (and has no `\"`). */\nexport function formatMentionHandle(label: string): string {\n return PLAIN_HANDLE_REGEX.test(label) || label.includes('\"') ? `@${label}` : `@\"${label}\"`;\n}\n\n/**\n * Formats a mention as text: `@handle` or `@handle:role` (quoted as\n * `@\"Two words\":role` when the handle is not a plain handle).\n */\nexport function formatMentionText(handle: string, role?: string | null): string {\n return role ? `${formatMentionHandle(handle)}:${role}` : formatMentionHandle(handle);\n}\n\n/** A mention found in plain text, with offsets into that text. */\nexport interface TextMention {\n handle: string;\n role?: string;\n /** Offset of the `@`. */\n start: number;\n /** Offset just past the handle (and role, when present). */\n end: number;\n /** Offset just past the handle (where a `:role` suffix starts). */\n handleEnd: number;\n}\n\n/**\n * Every mention in plain text (prompt text), in order, with offsets and\n * `@handle:role` roles (`roles` defaults to `true` here). Unlike\n * {@link extractMentions} it does not skip code or URLs and keeps duplicates.\n */\nexport function findTextMentions(text: string, options: { roles?: MentionRoleSyntax | false } = {}): TextMention[] {\n if (!text || typeof text !== \"string\") return [];\n const roles = options.roles === false ? undefined : (options.roles ?? true);\n const result: TextMention[] = [];\n const regex = new RegExp(MENTION_REGEX.source, \"g\");\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n const handle = match[1] ?? match[2] ?? match[3] ?? \"\";\n const start = match.index + match[0].indexOf(\"@\");\n const handleEnd = regex.lastIndex;\n const suffix = text.charAt(handleEnd) === \":\" ? readRoleSuffix(text, handleEnd, roles) : null;\n if (suffix) regex.lastIndex = suffix.end;\n result.push({\n handle,\n ...(suffix ? { role: suffix.role } : {}),\n start,\n end: suffix ? suffix.end : handleEnd,\n handleEnd,\n });\n }\n return result;\n}\n\n/**\n * Sets (or with `null` removes) the `:role` of the text mention starting at\n * offset `start` (its `@`). Returns the text unchanged when no mention starts there.\n */\nexport function setMentionRoleInText(\n text: string,\n start: number,\n role: string | null,\n options: { roles?: MentionRoleSyntax } = {},\n): string {\n const mention = findTextMentions(text, { roles: options.roles ?? true }).find((m) => m.start === start);\n if (!mention) return text;\n if (role !== null && !/^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/.test(role)) {\n throw new Error(`Invalid mention role \"${role}\".`);\n }\n const suffix = role ? `:${role}` : \"\";\n return text.slice(0, mention.handleEnd) + suffix + text.slice(mention.end);\n}\n\n"]}