@pixerate/editor 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +167 -2
  2. package/THIRD_PARTY_NOTICES.md +80 -0
  3. package/dist/agent/index.cjs +10 -8
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +4 -3
  6. package/dist/agent/index.d.ts +4 -3
  7. package/dist/agent/index.js +4 -2
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +4437 -411
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +934 -4
  12. package/dist/canvas/index.d.ts +934 -4
  13. package/dist/canvas/index.js +4309 -283
  14. package/dist/canvas/index.js.map +1 -1
  15. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  16. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  17. package/dist/chunk-664LQYFJ.js +115 -0
  18. package/dist/chunk-664LQYFJ.js.map +1 -0
  19. package/dist/chunk-7WISYZFO.cjs +740 -0
  20. package/dist/chunk-7WISYZFO.cjs.map +1 -0
  21. package/dist/chunk-AL6JMGWW.cjs +783 -0
  22. package/dist/chunk-AL6JMGWW.cjs.map +1 -0
  23. package/dist/chunk-BVP3TSLH.cjs +69 -0
  24. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  25. package/dist/{chunk-RIKBJLS2.cjs → chunk-C7P22VIP.cjs} +2001 -279
  26. package/dist/chunk-C7P22VIP.cjs.map +1 -0
  27. package/dist/chunk-D6FH2YAX.cjs +131 -0
  28. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  29. package/dist/chunk-ECCCYKGL.js +740 -0
  30. package/dist/chunk-ECCCYKGL.js.map +1 -0
  31. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  32. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  33. package/dist/{chunk-QBJ22YZR.js → chunk-FH6F67DN.js} +1999 -277
  34. package/dist/chunk-FH6F67DN.js.map +1 -0
  35. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  36. package/dist/chunk-FPENXAEJ.js.map +1 -0
  37. package/dist/chunk-HLIMTC6N.js +1 -0
  38. package/dist/chunk-HLIMTC6N.js.map +1 -0
  39. package/dist/chunk-HRMEDT2L.js +1406 -0
  40. package/dist/chunk-HRMEDT2L.js.map +1 -0
  41. package/dist/{chunk-QGHGJ56O.js → chunk-IXHSYOVY.js} +2518 -122
  42. package/dist/chunk-IXHSYOVY.js.map +1 -0
  43. package/dist/chunk-MKA3AHLV.cjs +256 -0
  44. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  45. package/dist/chunk-NAXVVS5I.js +783 -0
  46. package/dist/chunk-NAXVVS5I.js.map +1 -0
  47. package/dist/chunk-O5RYH2PG.js +69 -0
  48. package/dist/chunk-O5RYH2PG.js.map +1 -0
  49. package/dist/chunk-OXSV64JX.js +689 -0
  50. package/dist/chunk-OXSV64JX.js.map +1 -0
  51. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  52. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  53. package/dist/chunk-P6ODPZ4W.js +256 -0
  54. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  55. package/dist/chunk-PL4JB3BF.cjs +1 -0
  56. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  57. package/dist/{chunk-GVSMRPHB.cjs → chunk-PO6A6QQH.cjs} +2607 -211
  58. package/dist/chunk-PO6A6QQH.cjs.map +1 -0
  59. package/dist/{chunk-JJDLPEWL.js → chunk-QGYDWPRM.js} +2 -2
  60. package/dist/chunk-RR7F5GUM.js +5726 -0
  61. package/dist/chunk-RR7F5GUM.js.map +1 -0
  62. package/dist/chunk-TNNJXF46.cjs +115 -0
  63. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  64. package/dist/chunk-U5HZSFCS.js +131 -0
  65. package/dist/chunk-U5HZSFCS.js.map +1 -0
  66. package/dist/{chunk-MJZBNWG7.js → chunk-UAKDG62Z.js} +309 -581
  67. package/dist/chunk-UAKDG62Z.js.map +1 -0
  68. package/dist/{chunk-CL37JGFW.cjs → chunk-V3CHHWOE.cjs} +4 -4
  69. package/dist/{chunk-CL37JGFW.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
  70. package/dist/chunk-WSQKHXW7.cjs +5726 -0
  71. package/dist/chunk-WSQKHXW7.cjs.map +1 -0
  72. package/dist/{chunk-AA4ZEVCC.cjs → chunk-X4N4WUKB.cjs} +336 -608
  73. package/dist/chunk-X4N4WUKB.cjs.map +1 -0
  74. package/dist/commands/index.cjs +74 -1
  75. package/dist/commands/index.cjs.map +1 -1
  76. package/dist/commands/index.d.cts +38 -353
  77. package/dist/commands/index.d.ts +38 -353
  78. package/dist/commands/index.js +73 -0
  79. package/dist/commands/index.js.map +1 -1
  80. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  81. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  82. package/dist/compare/index.cjs +15 -0
  83. package/dist/compare/index.cjs.map +1 -0
  84. package/dist/compare/index.d.cts +58 -0
  85. package/dist/compare/index.d.ts +58 -0
  86. package/dist/compare/index.js +15 -0
  87. package/dist/compare/index.js.map +1 -0
  88. package/dist/controller-BIJiNoVs.d.cts +1842 -0
  89. package/dist/controller-BIJiNoVs.d.ts +1842 -0
  90. package/dist/documents-CHT1U5Vs.d.ts +86 -0
  91. package/dist/documents-Gp7wb0Me.d.cts +86 -0
  92. package/dist/extensions/index.cjs +100 -5
  93. package/dist/extensions/index.cjs.map +1 -1
  94. package/dist/extensions/index.d.cts +16 -4
  95. package/dist/extensions/index.d.ts +16 -4
  96. package/dist/extensions/index.js +100 -5
  97. package/dist/generation/index.cjs +2317 -0
  98. package/dist/generation/index.cjs.map +1 -0
  99. package/dist/generation/index.d.cts +1015 -0
  100. package/dist/generation/index.d.ts +1015 -0
  101. package/dist/generation/index.js +2317 -0
  102. package/dist/generation/index.js.map +1 -0
  103. package/dist/grammar/index.cjs +48 -3
  104. package/dist/grammar/index.cjs.map +1 -1
  105. package/dist/grammar/index.d.cts +322 -100
  106. package/dist/grammar/index.d.ts +322 -100
  107. package/dist/grammar/index.js +54 -9
  108. package/dist/image-editor/index.cjs +4392 -481
  109. package/dist/image-editor/index.cjs.map +1 -1
  110. package/dist/image-editor/index.d.cts +1249 -97
  111. package/dist/image-editor/index.d.ts +1249 -97
  112. package/dist/image-editor/index.js +4281 -370
  113. package/dist/image-editor/index.js.map +1 -1
  114. package/dist/index-7a6bHTLy.d.ts +1754 -0
  115. package/dist/index-Csd5qI8u.d.cts +1754 -0
  116. package/dist/index.cjs +1682 -99
  117. package/dist/index.cjs.map +1 -1
  118. package/dist/index.d.cts +118 -21
  119. package/dist/index.d.ts +118 -21
  120. package/dist/index.js +1630 -47
  121. package/dist/index.js.map +1 -1
  122. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  123. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  124. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  125. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  126. package/dist/model-C1zApQM8.d.cts +219 -0
  127. package/dist/model-C1zApQM8.d.ts +219 -0
  128. package/dist/persistence/index.cjs +676 -0
  129. package/dist/persistence/index.cjs.map +1 -0
  130. package/dist/persistence/index.d.cts +230 -0
  131. package/dist/persistence/index.d.ts +230 -0
  132. package/dist/persistence/index.js +676 -0
  133. package/dist/persistence/index.js.map +1 -0
  134. package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
  135. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.cts} +12 -1
  136. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.ts} +12 -1
  137. package/dist/registry-D32LpIc4.d.cts +354 -0
  138. package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
  139. package/dist/registry-evKTP4ls.d.ts +354 -0
  140. package/dist/schema-DYDGdYL9.d.cts +41 -0
  141. package/dist/schema-DYDGdYL9.d.ts +41 -0
  142. package/dist/serializers/index.cjs +12 -3
  143. package/dist/serializers/index.cjs.map +1 -1
  144. package/dist/serializers/index.d.cts +40 -12
  145. package/dist/serializers/index.d.ts +40 -12
  146. package/dist/serializers/index.js +12 -3
  147. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  148. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  149. package/dist/spreadsheet/index.cjs +161 -3
  150. package/dist/spreadsheet/index.cjs.map +1 -1
  151. package/dist/spreadsheet/index.d.cts +611 -20
  152. package/dist/spreadsheet/index.d.ts +611 -20
  153. package/dist/spreadsheet/index.js +168 -10
  154. package/dist/storage-BpFfspHm.d.cts +120 -0
  155. package/dist/storage-BpFfspHm.d.ts +120 -0
  156. package/dist/tokens/index.cjs +115 -0
  157. package/dist/tokens/index.cjs.map +1 -0
  158. package/dist/tokens/index.d.cts +212 -0
  159. package/dist/tokens/index.d.ts +212 -0
  160. package/dist/tokens/index.js +115 -0
  161. package/dist/tokens/index.js.map +1 -0
  162. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  163. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  164. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-D_VAPELi.d.cts} +110 -144
  165. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-Db_fGEvx.d.ts} +110 -144
  166. package/package.json +21 -1
  167. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  168. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  169. package/dist/chunk-AXV2ZW34.cjs +0 -118
  170. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  171. package/dist/chunk-B2ZRLN4S.js +0 -118
  172. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  173. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  174. package/dist/chunk-K3R5NG2D.js +0 -3100
  175. package/dist/chunk-K3R5NG2D.js.map +0 -1
  176. package/dist/chunk-MJZBNWG7.js.map +0 -1
  177. package/dist/chunk-OYXSKQFR.js.map +0 -1
  178. package/dist/chunk-QBJ22YZR.js.map +0 -1
  179. package/dist/chunk-QGHGJ56O.js.map +0 -1
  180. package/dist/chunk-RIKBJLS2.cjs.map +0 -1
  181. package/dist/chunk-XDMXXC4Z.cjs +0 -3100
  182. package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
  183. package/dist/controller-pePCJLT1.d.cts +0 -959
  184. package/dist/controller-pePCJLT1.d.ts +0 -959
  185. package/dist/index-CCr_z9kF.d.cts +0 -899
  186. package/dist/index-Ck6ajwXh.d.ts +0 -899
  187. /package/dist/{chunk-JJDLPEWL.js.map → chunk-QGYDWPRM.js.map} +0 -0
@@ -1,10 +1,13 @@
1
+ import {
2
+ compareDocuments
3
+ } from "./chunk-NAXVVS5I.js";
1
4
  import {
2
5
  INSTRUCTION_REGEX,
3
6
  TEMPLATE_REGEX,
4
7
  VARIABLE_REGEX,
5
8
  matchConditionalTags,
6
9
  parseVariableBody
7
- } from "./chunk-B2ZRLN4S.js";
10
+ } from "./chunk-P6ODPZ4W.js";
8
11
  import {
9
12
  PIXERATE_DOCUMENT_MIME,
10
13
  getClipboardData,
@@ -13,16 +16,20 @@ import {
13
16
  rtfToPlainText,
14
17
  sanitizeImportedHtml,
15
18
  serializeRichClipboardPayload
16
- } from "./chunk-JJDLPEWL.js";
19
+ } from "./chunk-QGYDWPRM.js";
17
20
  import {
18
- CommentStore,
19
- TrackChanges,
20
- createReviewId,
21
21
  escapeHtml,
22
22
  htmlToPlainText,
23
23
  markdownToTipTapHtml,
24
24
  plainTextToSlice
25
- } from "./chunk-MJZBNWG7.js";
25
+ } from "./chunk-ECCCYKGL.js";
26
+ import {
27
+ CommentStore,
28
+ TRACK_CHANGES_SKIP_META,
29
+ TrackChanges,
30
+ createReviewId,
31
+ trackChangesPluginKey
32
+ } from "./chunk-UAKDG62Z.js";
26
33
 
27
34
  // src/extensions/gradient-text.ts
28
35
  import { Mark } from "@tiptap/core";
@@ -124,11 +131,12 @@ var createGradientDecorations = (doc, options) => {
124
131
  result.error ? {
125
132
  class: "underline decoration-wavy decoration-red-500 text-red-600 dark:text-red-400",
126
133
  "data-conditional-error": result.error,
127
- title: result.error === "unclosed-section" ? `Section "${tag.name}" is never closed with {/${tag.name}}` : `{/${tag.name}} does not close any open section`
134
+ title: result.error === "invalid-condition" ? `Invalid condition: ${result.comparison?.error ?? tag.name}` : result.error === "unclosed-section" ? `Section "${tag.name}" is never closed with {/${tag.name}}` : `{/${tag.name}} does not close any open section`
128
135
  } : {
129
136
  class: "bg-amber-400/30 text-amber-700 dark:text-amber-300 font-medium px-0.5 rounded-xs",
130
137
  "data-conditional": tag.conditional,
131
- "data-variable-condition": tag.name
138
+ // A comparison (`{?amount>100}`) names its subject variable.
139
+ "data-variable-condition": result.comparison?.subject ?? tag.name
132
140
  }
133
141
  )
134
142
  );
@@ -593,10 +601,47 @@ var Mention = BaseMention.extend({
593
601
  if (!attributes.color) return {};
594
602
  return { "data-color": attributes.color };
595
603
  }
604
+ },
605
+ // Role of an asset mention (`reference`, `style`, `start_frame`, …). The
606
+ // node attribute is `assetRole` so entities that already carry a `role`
607
+ // (e.g. a user's job title) passed as suggestion props do not set it.
608
+ assetRole: {
609
+ default: null,
610
+ parseHTML: (element) => {
611
+ const role = element.getAttribute("data-role");
612
+ return role && MENTION_ROLE_PATTERN.test(role) ? role : null;
613
+ },
614
+ renderHTML: (attributes) => {
615
+ if (!attributes.assetRole) return {};
616
+ return { "data-role": attributes.assetRole };
617
+ }
596
618
  }
597
619
  };
598
620
  }
599
621
  });
622
+ var MENTION_ROLE_PATTERN = /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/;
623
+ function getMentionNodes(editor) {
624
+ const result = [];
625
+ editor.state.doc.descendants((node, pos) => {
626
+ if (node.type?.name !== "mention") return;
627
+ result.push({
628
+ pos,
629
+ id: node.attrs.id ?? "",
630
+ label: node.attrs.label ?? "",
631
+ ...node.attrs.type ? { type: node.attrs.type } : {},
632
+ ...node.attrs.assetRole ? { role: node.attrs.assetRole } : {}
633
+ });
634
+ });
635
+ return result;
636
+ }
637
+ function setMentionRoleAt(editor, pos, role) {
638
+ const node = editor?.state?.doc?.nodeAt(pos);
639
+ if (!node || node.type.name !== "mention") return false;
640
+ if (role !== null && !MENTION_ROLE_PATTERN.test(role)) return false;
641
+ const tr = editor.state.tr.setNodeMarkup(pos, void 0, { ...node.attrs, assetRole: role });
642
+ editor.view.dispatch(tr);
643
+ return true;
644
+ }
600
645
  function createMentionExtension(options = {}) {
601
646
  const { HTMLAttributes = {}, ...rest } = options;
602
647
  return Mention.configure({
@@ -2028,8 +2073,56 @@ function getCommentAuthor(editor) {
2028
2073
  return ext ? resolveAuthor(ext.options.author) : null;
2029
2074
  }
2030
2075
 
2031
- // src/extensions/file-import.ts
2076
+ // src/extensions/document-compare.ts
2032
2077
  import { Extension as Extension7 } from "@tiptap/core";
2078
+ function trackChangesAuthor(state) {
2079
+ return trackChangesPluginKey.getState(state)?.author;
2080
+ }
2081
+ function compareInEditor(editor, original, revised, options = {}) {
2082
+ return compareDocuments(original, revised, {
2083
+ ...options,
2084
+ author: options.author ?? trackChangesAuthor(editor.state),
2085
+ schema: editor.schema
2086
+ });
2087
+ }
2088
+ function applyDocumentComparison(editor, original, revised, options = {}) {
2089
+ const result = compareInEditor(editor, original, revised, options);
2090
+ const { state } = editor;
2091
+ const tr = state.tr.replaceWith(0, state.doc.content.size, result.doc.content);
2092
+ tr.setMeta(TRACK_CHANGES_SKIP_META, true);
2093
+ editor.view.dispatch(tr);
2094
+ return result;
2095
+ }
2096
+ var DocumentCompare = Extension7.create({
2097
+ name: "documentCompare",
2098
+ addOptions() {
2099
+ return { compare: {} };
2100
+ },
2101
+ addStorage() {
2102
+ return { lastResult: null };
2103
+ },
2104
+ addCommands() {
2105
+ return {
2106
+ setContentFromComparison: (original, revised, options = {}) => ({ state, tr, dispatch }) => {
2107
+ const merged = { ...this.options.compare, ...options };
2108
+ const result = compareDocuments(original, revised, {
2109
+ ...merged,
2110
+ author: merged.author ?? trackChangesAuthor(state),
2111
+ schema: state.schema
2112
+ });
2113
+ if (dispatch) {
2114
+ tr.replaceWith(0, tr.doc.content.size, result.doc.content);
2115
+ tr.setMeta(TRACK_CHANGES_SKIP_META, true);
2116
+ this.storage.lastResult = result;
2117
+ }
2118
+ return true;
2119
+ }
2120
+ };
2121
+ }
2122
+ });
2123
+
2124
+ // src/extensions/file-import.ts
2125
+ import { Extension as Extension8 } from "@tiptap/core";
2033
2126
  import { Plugin as Plugin7, PluginKey as PluginKey4 } from "@tiptap/pm/state";
2034
2127
 
2035
2128
  // src/import/registry.ts
@@ -2193,7 +2286,13 @@ var ImportAdapterRegistry = class {
2193
2286
  { fileName }
2194
2287
  );
2195
2288
  }
2196
- const context = createImportContext(input, { fileName, mimeType, target: adapter.target, registry: this });
2289
+ const context = createImportContext(input, {
2290
+ fileName,
2291
+ mimeType,
2292
+ target: adapter.target,
2293
+ registry: this,
2294
+ options: options.adapterOptions?.[adapter.id]
2295
+ });
2197
2296
  try {
2198
2297
  const result = await adapter.import(input, context);
2199
2298
  return { adapterId: adapter.id, target: adapter.target, fileName, mimeType, result };
@@ -3292,7 +3391,10 @@ async function importFilesIntoEditor(target, files, options = {}) {
3292
3391
  let at = options.at;
3293
3392
  for (const file of Array.from(files)) {
3294
3393
  try {
3295
- const outcome = await registry.importFile(file, { target: "document" });
3394
+ const outcome = await registry.importFile(file, {
3395
+ target: "document",
3396
+ adapterOptions: options.adapterOptions
3397
+ });
3296
3398
  const view = toView(target);
3297
3399
  if (view.isDestroyed) break;
3298
3400
  at = insertImportedDocument(view, outcome.result, { at, asPlainText: options.asPlainText });
@@ -3321,11 +3423,12 @@ function dropPosition(view, event) {
3321
3423
  return void 0;
3322
3424
  }
3323
3425
  }
3324
- var FileImport = Extension7.create({
3426
+ var FileImport = Extension8.create({
3325
3427
  name: "fileImport",
3326
3428
  addOptions() {
3327
3429
  return {
3328
3430
  registry: void 0,
3431
+ adapterOptions: void 0,
3329
3432
  asPlainText: false,
3330
3433
  enableDrop: true,
3331
3434
  enablePaste: true,
@@ -3341,6 +3444,7 @@ var FileImport = Extension7.create({
3341
3444
  registry: registry(),
3342
3445
  at,
3343
3446
  asPlainText: options.asPlainText,
3447
+ adapterOptions: options.adapterOptions,
3344
3448
  onImport: options.onImport,
3345
3449
  onError: options.onImportError
3346
3450
  });
@@ -3372,7 +3476,7 @@ var FileImport = Extension7.create({
3372
3476
  });
3373
3477
 
3374
3478
  // src/extensions/rich-clipboard.ts
3375
- import { Extension as Extension8 } from "@tiptap/core";
3479
+ import { Extension as Extension9 } from "@tiptap/core";
3376
3480
  import { Plugin as Plugin8, PluginKey as PluginKey5 } from "@tiptap/pm/state";
3377
3481
  var richClipboardPluginKey = new PluginKey5("richClipboard");
3378
3482
  function writeRichClipboard(view, event, options = {}) {
@@ -3392,7 +3496,7 @@ function writeRichClipboard(view, event, options = {}) {
3392
3496
  }
3393
3497
  return true;
3394
3498
  }
3395
- var RichClipboard = Extension8.create({
3499
+ var RichClipboard = Extension9.create({
3396
3500
  name: "richClipboard",
3397
3501
  addOptions() {
3398
3502
  return {
@@ -3431,7 +3535,7 @@ var RichClipboard = Extension8.create({
3431
3535
  });
3432
3536
 
3433
3537
  // src/extensions/autocorrect.ts
3434
- import { Extension as Extension9 } from "@tiptap/core";
3538
+ import { Extension as Extension10 } from "@tiptap/core";
3435
3539
  import { closeHistory } from "@tiptap/pm/history";
3436
3540
  import { Plugin as Plugin9, PluginKey as PluginKey6 } from "@tiptap/pm/state";
3437
3541
  var quoteOpener = /[\s ([{<—–‘“-]/;
@@ -3590,7 +3694,7 @@ function isInsidePromptToken(textBefore) {
3590
3694
  const underscores = textBefore.match(/__/g);
3591
3695
  return !!underscores && underscores.length % 2 === 1;
3592
3696
  }
3593
- var Autocorrect = Extension9.create({
3697
+ var Autocorrect = Extension10.create({
3594
3698
  name: "autocorrect",
3595
3699
  addOptions() {
3596
3700
  return {
@@ -3738,7 +3842,7 @@ var Autocorrect = Extension9.create({
3738
3842
  });
3739
3843
 
3740
3844
  // src/extensions/document-structure/outline.ts
3741
- import { Extension as Extension10 } from "@tiptap/core";
3845
+ import { Extension as Extension11 } from "@tiptap/core";
3742
3846
  import {
3743
3847
  Plugin as Plugin10,
3744
3848
  PluginKey as PluginKey7,
@@ -3974,7 +4078,7 @@ function assignHeadingIds(state, types, generateId) {
3974
4078
  });
3975
4079
  return tr;
3976
4080
  }
3977
- var Outline = Extension10.create({
4081
+ var Outline = Extension11.create({
3978
4082
  name: "outline",
3979
4083
  priority: STRUCTURE_PRIORITY,
3980
4084
  addOptions() {
@@ -5169,117 +5273,2224 @@ function getDocumentStructureCommandItems(editor, query = "") {
5169
5273
  );
5170
5274
  }
5171
5275
 
5172
- // src/extensions/rich-text-preset.ts
5173
- var resolveExt3 = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
5174
- var StarterKit = resolveExt3(_StarterKit, "StarterKit");
5175
- var Color = resolveExt3(_Color, "Color");
5176
- var Highlight = resolveExt3(_Highlight, "Highlight");
5177
- var Link2 = resolveExt3(_Link, "Link");
5178
- var TaskList2 = resolveExt3(_TaskList, "TaskList");
5179
- var TaskItem2 = resolveExt3(_TaskItem, "TaskItem");
5180
- var TextAlign = resolveExt3(_TextAlign, "TextAlign");
5181
- var TextStyle = resolveExt3(_TextStyle, "TextStyle");
5182
- var Typography = resolveExt3(_Typography, "Typography");
5183
- var Underline = resolveExt3(_Underline, "Underline");
5184
- var Placeholder = resolveExt3(_Placeholder, "Placeholder");
5185
- var CharacterCount = resolveExt3(_CharacterCount, "CharacterCount");
5186
- var optionObject = (value) => typeof value === "object" && value !== null ? value : {};
5187
- function createRichTextPreset(options = {}) {
5188
- const {
5189
- placeholder = "Start typing...",
5190
- characterLimit,
5191
- openLinksOnClick = false,
5192
- enableSmilies = true,
5193
- enableColorHighlighter = true,
5194
- headingLevels = [1, 2, 3, 4]
5195
- } = options;
5196
- const extensions2 = [
5197
- StarterKit.configure({
5198
- orderedList: {
5199
- HTMLAttributes: {
5200
- class: "list-decimal pl-4"
5276
+ // src/extensions/find-replace/extension.ts
5277
+ import { Extension as Extension12 } from "@tiptap/core";
5278
+ import { Plugin as Plugin14, PluginKey as PluginKey11, TextSelection as TextSelection7 } from "@tiptap/pm/state";
5279
+ import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "@tiptap/pm/view";
5280
+ import { closeHistory as closeHistory2 } from "@tiptap/pm/history";
5281
+
5282
+ // src/extensions/find-replace/pattern.ts
5283
+ var FIND_PARAGRAPH_MARK = "\r";
5284
+ var MAX_FIND_PATTERN_LENGTH = 1024;
5285
+ var MAX_COUNT = 1e3;
5286
+ var MAX_PROGRAM_SIZE = 2e4;
5287
+ var now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
5288
+ function createSearchBudget(options = {}) {
5289
+ return {
5290
+ deadline: now() + (options.timeLimitMs ?? 250),
5291
+ steps: options.maxSteps ?? 5e7,
5292
+ exhausted: false
5293
+ };
5294
+ }
5295
+ var PARA = 13;
5296
+ var ANY = (cp) => cp !== PARA;
5297
+ var LETTER = /\p{L}/u;
5298
+ var PUNCT = /\p{P}/u;
5299
+ var WORD = /[\p{L}\p{M}\p{Nd}\p{Pc}]/u;
5300
+ var foldCache = /* @__PURE__ */ new Map();
5301
+ function foldCodePoint(cp) {
5302
+ if (cp < 128) return cp >= 65 && cp <= 90 ? cp + 32 : cp;
5303
+ const cached = foldCache.get(cp);
5304
+ if (cached !== void 0) return cached;
5305
+ const lower = String.fromCodePoint(cp).toLowerCase();
5306
+ const folded = [...lower].length === 1 ? lower.codePointAt(0) : cp;
5307
+ if (foldCache.size < 4096) foldCache.set(cp, folded);
5308
+ return folded;
5309
+ }
5310
+ function isWordChar(cp) {
5311
+ if (cp < 0) return false;
5312
+ if (cp < 128) {
5313
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 95;
5314
+ }
5315
+ return WORD.test(String.fromCodePoint(cp));
5316
+ }
5317
+ var isLetter = (cp) => cp < 128 ? cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 : LETTER.test(String.fromCodePoint(cp));
5318
+ function isPunct(cp) {
5319
+ if (cp < 128) {
5320
+ return cp >= 33 && cp <= 47 || cp >= 58 && cp <= 64 || cp >= 91 && cp <= 96 || cp >= 123 && cp <= 126;
5321
+ }
5322
+ return cp >= 8208 && cp <= 8231 || cp === 161 || cp === 191 || cp === 171 || cp === 187 || PUNCT.test(String.fromCodePoint(cp));
5323
+ }
5324
+ var isSpace = (cp) => cp === 32 || cp === 9 || cp === 160;
5325
+ function cpBefore(hay, pos) {
5326
+ if (pos <= 0) return -1;
5327
+ const low = hay.charCodeAt(pos - 1);
5328
+ if (low >= 56320 && low <= 57343 && pos >= 2) {
5329
+ const high = hay.charCodeAt(pos - 2);
5330
+ if (high >= 55296 && high <= 56319) return hay.codePointAt(pos - 2);
5331
+ }
5332
+ return low;
5333
+ }
5334
+ var cpAt = (hay, pos) => pos < hay.length ? hay.codePointAt(pos) : -1;
5335
+ var PatternError = class extends Error {
5336
+ constructor(code, message) {
5337
+ super(message);
5338
+ this.code = code;
5339
+ }
5340
+ code;
5341
+ };
5342
+ var charNode = (test) => ({ k: "char", test });
5343
+ var exact = (cp) => charNode((c) => c === cp);
5344
+ var literalNode = (cp, caseInsensitive) => {
5345
+ if (!caseInsensitive) return exact(cp);
5346
+ const f = foldCodePoint(cp);
5347
+ return charNode((_c, folded) => folded === f);
5348
+ };
5349
+ var star = (body, lazy) => ({ k: "rep", body, min: 0, max: -1, lazy });
5350
+ function findCode(c) {
5351
+ switch (c) {
5352
+ case "p":
5353
+ return exact(PARA);
5354
+ case "t":
5355
+ return exact(9);
5356
+ case "?":
5357
+ return charNode(ANY);
5358
+ case "#":
5359
+ return charNode((cp) => cp >= 48 && cp <= 57);
5360
+ case "$":
5361
+ return charNode((cp) => isLetter(cp));
5362
+ case "^":
5363
+ return exact(94);
5364
+ case "l":
5365
+ return exact(10);
5366
+ case "m":
5367
+ return exact(12);
5368
+ case "n":
5369
+ return exact(14);
5370
+ case "+":
5371
+ return exact(8212);
5372
+ case "=":
5373
+ return exact(8211);
5374
+ case "s":
5375
+ return exact(160);
5376
+ case "~":
5377
+ return exact(8209);
5378
+ case "-":
5379
+ return exact(173);
5380
+ case "w":
5381
+ return { k: "rep", body: charNode((cp) => isSpace(cp)), min: 1, max: -1, lazy: false };
5382
+ case "g":
5383
+ return exact(65532);
5384
+ case "%":
5385
+ return exact(167);
5386
+ case "v":
5387
+ return exact(182);
5388
+ default:
5389
+ return null;
5390
+ }
5391
+ }
5392
+ function replaceCode(c) {
5393
+ switch (c) {
5394
+ case "p":
5395
+ return FIND_PARAGRAPH_MARK;
5396
+ case "t":
5397
+ return " ";
5398
+ case "^":
5399
+ return "^";
5400
+ case "l":
5401
+ return "\n";
5402
+ case "m":
5403
+ return "\f";
5404
+ case "n":
5405
+ return "";
5406
+ case "+":
5407
+ return "\u2014";
5408
+ case "=":
5409
+ return "\u2013";
5410
+ case "s":
5411
+ return "\xA0";
5412
+ case "~":
5413
+ return "\u2011";
5414
+ case "-":
5415
+ return "\xAD";
5416
+ case "%":
5417
+ return "\xA7";
5418
+ case "v":
5419
+ return "\xB6";
5420
+ default:
5421
+ return null;
5422
+ }
5423
+ }
5424
+ function codeChar(n) {
5425
+ switch (n) {
5426
+ case 13:
5427
+ return PARA;
5428
+ case 11:
5429
+ return 10;
5430
+ case 12:
5431
+ return 12;
5432
+ case 14:
5433
+ return 14;
5434
+ case 30:
5435
+ return 8209;
5436
+ case 31:
5437
+ return 173;
5438
+ }
5439
+ if (n > 1114111 || n >= 55296 && n <= 57343) return null;
5440
+ if (n < 32 && n !== 9 || n >= 127 && n < 160) return null;
5441
+ return n;
5442
+ }
5443
+ function numericCode(chars, i) {
5444
+ const unicode = chars[i] === "u";
5445
+ const start2 = unicode ? i + 1 : i;
5446
+ const max = unicode ? 5 : 4;
5447
+ let digits = "";
5448
+ for (let j = start2; j < chars.length && digits.length < max && /[0-9]/.test(chars[j]); j++) digits += chars[j];
5449
+ if (!digits) return null;
5450
+ const cp = codeChar(parseInt(digits, 10));
5451
+ return cp === null ? null : { cp, used: start2 - i + digits.length };
5452
+ }
5453
+ var toChars = (s) => Array.from(s);
5454
+ var cpOf = (ch) => ch.codePointAt(0);
5455
+ function parseSpecial(query, o, caseInsensitive) {
5456
+ const chars = toChars(query);
5457
+ const atoms = [];
5458
+ let spans = false;
5459
+ let i = 0;
5460
+ while (i < chars.length) {
5461
+ const c = chars[i++];
5462
+ if (c === "^" && !o.literal && i < chars.length) {
5463
+ const n = chars[i];
5464
+ const code = findCode(n);
5465
+ if (code) {
5466
+ if (n === "p") spans = true;
5467
+ atoms.push(code);
5468
+ i++;
5469
+ continue;
5470
+ }
5471
+ const num = numericCode(chars, i);
5472
+ if (num) {
5473
+ if (num.cp === PARA) spans = true;
5474
+ atoms.push(literalNode(num.cp, caseInsensitive));
5475
+ i += num.used;
5476
+ continue;
5477
+ }
5478
+ }
5479
+ const cp = cpOf(c);
5480
+ if (o.ignorePunctuation && isPunct(cp) || o.ignoreWhitespace && isSpace(cp)) continue;
5481
+ atoms.push(literalNode(cp, caseInsensitive));
5482
+ }
5483
+ const filler = o.ignorePunctuation && o.ignoreWhitespace ? (cp) => isPunct(cp) || isSpace(cp) : o.ignorePunctuation ? (cp) => isPunct(cp) : o.ignoreWhitespace ? (cp) => isSpace(cp) : null;
5484
+ const items = [];
5485
+ atoms.forEach((atom, index) => {
5486
+ if (index > 0 && filler) items.push(star(charNode(filler), false));
5487
+ items.push(atom);
5488
+ });
5489
+ return { node: { k: "cat", items }, spans };
5490
+ }
5491
+ function parseCount(body) {
5492
+ const bad = () => new PatternError("invalid_pattern", `{${body}} is not a valid count`);
5493
+ const num = (s) => {
5494
+ const t = s.trim();
5495
+ if (!t) return null;
5496
+ if (!/^[0-9]+$/.test(t)) throw bad();
5497
+ const n = parseInt(t, 10);
5498
+ if (n > MAX_COUNT) throw new PatternError("invalid_pattern", `counts go up to ${MAX_COUNT}`);
5499
+ return n;
5500
+ };
5501
+ const sep = body.search(/[,;]/);
5502
+ if (sep < 0) {
5503
+ const n = num(body);
5504
+ if (n === null) throw bad();
5505
+ return { min: n, max: n };
5506
+ }
5507
+ const a = num(body.slice(0, sep)) ?? 0;
5508
+ const b = num(body.slice(sep + 1));
5509
+ if (b !== null && b < a) throw bad();
5510
+ return { min: a, max: b ?? -1 };
5511
+ }
5512
+ function parseClass(inner) {
5513
+ let negated = false;
5514
+ if (inner[0] === "!") {
5515
+ negated = true;
5516
+ inner = inner.slice(1);
5517
+ }
5518
+ if (inner.length === 0) throw new PatternError("invalid_pattern", "[ ] needs at least one character");
5519
+ const singles = /* @__PURE__ */ new Set();
5520
+ const ranges = [];
5521
+ let i = 0;
5522
+ while (i < inner.length) {
5523
+ let c = inner[i];
5524
+ if (c === "\\" && i + 1 < inner.length) c = inner[++i];
5525
+ i++;
5526
+ if (inner[i] === "-" && i + 1 < inner.length) {
5527
+ const hi = inner[i + 1];
5528
+ if (cpOf(hi) < cpOf(c)) throw new PatternError("invalid_pattern", `[${c}-${hi}] is not in ascending order`);
5529
+ ranges.push([cpOf(c), cpOf(hi)]);
5530
+ i += 2;
5531
+ } else {
5532
+ singles.add(cpOf(c));
5533
+ }
5534
+ }
5535
+ const inSet = (cp) => singles.has(cp) || ranges.some(([lo, hi]) => cp >= lo && cp <= hi);
5536
+ return negated ? (cp) => cp !== PARA && !inSet(cp) : (cp) => inSet(cp);
5537
+ }
5538
+ function parseWildcards(query) {
5539
+ const chars = toChars(query);
5540
+ const stack = [{ items: [], group: 0 }];
5541
+ let groups = 0;
5542
+ let spans = false;
5543
+ const top = () => stack[stack.length - 1].items;
5544
+ const quantify = (min, max, what) => {
5545
+ const items = top();
5546
+ const last = items[items.length - 1];
5547
+ if (!last || last.k === "assert" || last.k === "rep") {
5548
+ throw new PatternError("invalid_pattern", `${what} needs something to repeat before it`);
5549
+ }
5550
+ items[items.length - 1] = { k: "rep", body: last, min, max, lazy: false };
5551
+ };
5552
+ let i = 0;
5553
+ while (i < chars.length) {
5554
+ const c = chars[i++];
5555
+ switch (c) {
5556
+ case "?":
5557
+ top().push(charNode(ANY));
5558
+ break;
5559
+ case "*":
5560
+ top().push(star(charNode(ANY), true));
5561
+ break;
5562
+ case "@":
5563
+ quantify(1, -1, "@");
5564
+ break;
5565
+ case "<":
5566
+ top().push({ k: "assert", kind: "wordStart" });
5567
+ break;
5568
+ case ">":
5569
+ top().push({ k: "assert", kind: "wordEnd" });
5570
+ break;
5571
+ case "(":
5572
+ stack.push({ items: [], group: ++groups });
5573
+ break;
5574
+ case ")": {
5575
+ if (stack.length === 1) throw new PatternError("invalid_pattern", "a ) has no opening (");
5576
+ const frame = stack.pop();
5577
+ top().push({ k: "group", index: frame.group, body: { k: "cat", items: frame.items } });
5578
+ break;
5579
+ }
5580
+ case "{": {
5581
+ const end = chars.indexOf("}", i);
5582
+ if (end < 0) throw new PatternError("invalid_pattern", "a { needs a closing }");
5583
+ const { min, max } = parseCount(chars.slice(i, end).join(""));
5584
+ quantify(min, max, `{${chars.slice(i, end).join("")}}`);
5585
+ i = end + 1;
5586
+ break;
5587
+ }
5588
+ case "[": {
5589
+ const end = chars.indexOf("]", i);
5590
+ if (end < 0) throw new PatternError("invalid_pattern", "a [ needs a closing ]");
5591
+ top().push(charNode(parseClass(chars.slice(i, end))));
5592
+ i = end + 1;
5593
+ break;
5594
+ }
5595
+ case "\\": {
5596
+ if (i >= chars.length) {
5597
+ top().push(exact(92));
5598
+ } else if (/[0-9]/.test(chars[i])) {
5599
+ throw new PatternError("invalid_pattern", "back-references (\\1) work in Replace with, not in Find what");
5600
+ } else {
5601
+ top().push(exact(cpOf(chars[i++])));
5201
5602
  }
5202
- },
5203
- bulletList: {
5204
- HTMLAttributes: {
5205
- class: "list-disc pl-4"
5603
+ break;
5604
+ }
5605
+ case "^": {
5606
+ if (i >= chars.length) {
5607
+ top().push(exact(94));
5608
+ break;
5206
5609
  }
5207
- },
5208
- heading: {
5209
- levels: headingLevels,
5210
- HTMLAttributes: {
5211
- class: "font-bold tracking-tight"
5610
+ const n = chars[i];
5611
+ const code = findCode(n);
5612
+ if (code) {
5613
+ if (n === "p") spans = true;
5614
+ top().push(code);
5615
+ i++;
5616
+ break;
5617
+ }
5618
+ const num = numericCode(chars, i);
5619
+ if (num) {
5620
+ if (num.cp === PARA) spans = true;
5621
+ top().push(exact(num.cp));
5622
+ i += num.used;
5623
+ } else {
5624
+ top().push(exact(94));
5212
5625
  }
5626
+ break;
5213
5627
  }
5214
- }),
5215
- TextStyle,
5216
- Color,
5217
- Highlight.configure({ multicolor: true }),
5218
- Underline,
5219
- ...options.autocorrect ? [] : [Typography],
5220
- TextAlign.configure({
5221
- types: ["heading", "paragraph"]
5222
- }),
5223
- Link2.extend({
5224
- addInputRules() {
5225
- return [
5226
- markInputRule({
5227
- find: /(?:^|\s)\[([^\]]+)\]\(([^)]+)\)$/,
5228
- type: this.type,
5229
- getAttributes: (match) => ({
5230
- href: match[2]
5231
- })
5232
- })
5233
- ];
5628
+ default:
5629
+ top().push(exact(cpOf(c)));
5630
+ }
5631
+ }
5632
+ if (stack.length > 1) throw new PatternError("invalid_pattern", "a ( needs a closing )");
5633
+ return { node: { k: "cat", items: stack[0].items }, spans, groups };
5634
+ }
5635
+ function compileProgram(node) {
5636
+ const prog = [];
5637
+ const push = (inst) => {
5638
+ if (prog.length >= MAX_PROGRAM_SIZE) {
5639
+ throw new PatternError("too_complex", "the pattern is too complex (too many repeats)");
5640
+ }
5641
+ prog.push(inst);
5642
+ return prog.length - 1;
5643
+ };
5644
+ const emit = (n) => {
5645
+ switch (n.k) {
5646
+ case "char":
5647
+ push({ op: 0 /* Char */, test: n.test });
5648
+ return;
5649
+ case "cat":
5650
+ n.items.forEach(emit);
5651
+ return;
5652
+ case "group":
5653
+ push({ op: 3 /* Save */, n: 2 * n.index });
5654
+ emit(n.body);
5655
+ push({ op: 3 /* Save */, n: 2 * n.index + 1 });
5656
+ return;
5657
+ case "assert":
5658
+ push({ op: 4 /* Assert */, kind: n.kind });
5659
+ return;
5660
+ case "rep": {
5661
+ for (let k = 0; k < n.min; k++) emit(n.body);
5662
+ if (n.max < 0) {
5663
+ const split = push({ op: 1 /* Split */, x: 0, y: 0 });
5664
+ emit(n.body);
5665
+ push({ op: 2 /* Jmp */, x: split });
5666
+ const exit2 = prog.length;
5667
+ const inst = prog[split];
5668
+ inst.x = n.lazy ? exit2 : split + 1;
5669
+ inst.y = n.lazy ? split + 1 : exit2;
5670
+ return;
5671
+ }
5672
+ const splits = [];
5673
+ for (let k = n.min; k < n.max; k++) {
5674
+ splits.push(push({ op: 1 /* Split */, x: 0, y: 0 }));
5675
+ emit(n.body);
5676
+ }
5677
+ const exit = prog.length;
5678
+ for (const s of splits) {
5679
+ const inst = prog[s];
5680
+ inst.x = n.lazy ? exit : s + 1;
5681
+ inst.y = n.lazy ? s + 1 : exit;
5682
+ }
5683
+ return;
5234
5684
  }
5235
- }).configure({
5236
- openOnClick: openLinksOnClick,
5237
- autolink: true,
5238
- defaultProtocol: "https",
5239
- protocols: ["file", "vscode", "cursor"],
5240
- HTMLAttributes: {
5241
- target: "_blank",
5242
- rel: "noopener noreferrer",
5243
- class: "text-blue-500 underline underline-offset-2"
5685
+ }
5686
+ };
5687
+ push({ op: 3 /* Save */, n: 0 });
5688
+ emit(node);
5689
+ push({ op: 3 /* Save */, n: 1 });
5690
+ push({ op: 5 /* Match */ });
5691
+ return prog;
5692
+ }
5693
+ function checkAssert(kind, hay, pos) {
5694
+ const before = isWordChar(cpBefore(hay, pos));
5695
+ const after = isWordChar(cpAt(hay, pos));
5696
+ switch (kind) {
5697
+ case "boundary":
5698
+ return before !== after;
5699
+ case "wordStart":
5700
+ return !before && after;
5701
+ case "wordEnd":
5702
+ return before && !after;
5703
+ }
5704
+ }
5705
+ function pikeExec(prog, groupCount, caseInsensitive, hay, start2, end, budget) {
5706
+ const ncap = 2 * (groupCount + 1);
5707
+ const mark = new Int32Array(prog.length);
5708
+ let gen = 1;
5709
+ let clistPc = [];
5710
+ let clistCaps = [];
5711
+ let nlistPc = [];
5712
+ let nlistCaps = [];
5713
+ const stackPc = [];
5714
+ const stackCaps = [];
5715
+ let matched = null;
5716
+ const initial = new Int32Array(ncap).fill(-1);
5717
+ const add = (listPc, listCaps, pc0, caps0, pos) => {
5718
+ stackPc.push(pc0);
5719
+ stackCaps.push(caps0);
5720
+ while (stackPc.length) {
5721
+ const pc = stackPc.pop();
5722
+ const caps = stackCaps.pop();
5723
+ if (mark[pc] === gen) continue;
5724
+ mark[pc] = gen;
5725
+ const inst = prog[pc];
5726
+ switch (inst.op) {
5727
+ case 2 /* Jmp */:
5728
+ stackPc.push(inst.x);
5729
+ stackCaps.push(caps);
5730
+ break;
5731
+ case 1 /* Split */:
5732
+ stackPc.push(inst.y, inst.x);
5733
+ stackCaps.push(caps, caps);
5734
+ break;
5735
+ case 3 /* Save */: {
5736
+ const next = caps.slice();
5737
+ next[inst.n] = pos;
5738
+ stackPc.push(pc + 1);
5739
+ stackCaps.push(next);
5740
+ break;
5741
+ }
5742
+ case 4 /* Assert */:
5743
+ if (checkAssert(inst.kind, hay, pos)) {
5744
+ stackPc.push(pc + 1);
5745
+ stackCaps.push(caps);
5746
+ }
5747
+ break;
5748
+ default:
5749
+ listPc.push(pc);
5750
+ listCaps.push(caps);
5244
5751
  }
5245
- }),
5246
- TaskList2,
5247
- TaskItem2.extend({
5248
- addInputRules() {
5249
- return [
5250
- wrappingInputRule({
5251
- find: /^\s*(\[([ |x])?\])\s$/i,
5252
- type: this.type,
5253
- getAttributes: (match) => ({
5254
- checked: match[2]?.toLowerCase() === "x"
5255
- })
5256
- })
5257
- ];
5752
+ }
5753
+ };
5754
+ let i = start2;
5755
+ if (budget.steps <= 0 || now() > budget.deadline) {
5756
+ budget.exhausted = true;
5757
+ return "budget";
5758
+ }
5759
+ let steps = 0;
5760
+ for (; ; ) {
5761
+ if (!matched) add(clistPc, clistCaps, 0, initial, i);
5762
+ const cp = i < end ? hay.codePointAt(i) : -1;
5763
+ const width = cp > 65535 ? 2 : 1;
5764
+ if (clistPc.length === 0) {
5765
+ if (matched || i >= end) break;
5766
+ gen++;
5767
+ i += width;
5768
+ continue;
5769
+ }
5770
+ const folded = cp >= 0 && caseInsensitive ? foldCodePoint(cp) : cp;
5771
+ gen++;
5772
+ nlistPc = [];
5773
+ nlistCaps = [];
5774
+ for (let t = 0; t < clistPc.length; t++) {
5775
+ if (++steps >= 4096) {
5776
+ budget.steps -= steps;
5777
+ steps = 0;
5778
+ if (budget.steps <= 0 || now() > budget.deadline) {
5779
+ budget.exhausted = true;
5780
+ return "budget";
5781
+ }
5258
5782
  }
5259
- }).configure({
5260
- nested: true
5261
- }),
5262
- FontSize,
5263
- Placeholder.configure({
5264
- emptyEditorClass: "is-empty",
5265
- placeholder: typeof placeholder === "function" ? placeholder : () => placeholder
5266
- })
5267
- ];
5268
- if (characterLimit) {
5269
- extensions2.push(
5270
- CharacterCount.configure({
5271
- limit: characterLimit
5272
- })
5273
- );
5783
+ const inst = prog[clistPc[t]];
5784
+ if (inst.op === 0 /* Char */) {
5785
+ if (cp >= 0 && inst.test(cp, folded)) add(nlistPc, nlistCaps, clistPc[t] + 1, clistCaps[t], i + width);
5786
+ } else if (inst.op === 5 /* Match */) {
5787
+ matched = clistCaps[t];
5788
+ break;
5789
+ }
5790
+ }
5791
+ clistPc = nlistPc;
5792
+ clistCaps = nlistCaps;
5793
+ if (i >= end) break;
5794
+ i += width;
5795
+ }
5796
+ budget.steps -= steps;
5797
+ if (!matched) return null;
5798
+ const groups = [];
5799
+ for (let g = 0; g <= groupCount; g++) {
5800
+ const a = matched[2 * g];
5801
+ const b = matched[2 * g + 1];
5802
+ groups.push(a >= 0 && b >= 0 ? hay.slice(a, b) : void 0);
5803
+ }
5804
+ return { start: matched[0], end: matched[1], groups };
5805
+ }
5806
+ var WORD_CLASS = "[\\p{L}\\p{M}\\p{Nd}\\p{Pc}]";
5807
+ var REGEX_BOUNDARY = `(?:(?<=${WORD_CLASS})(?!${WORD_CLASS})|(?<!${WORD_CLASS})(?=${WORD_CLASS}))`;
5808
+ function hasNestedRepeat(source) {
5809
+ const quantifierAt = (i) => {
5810
+ const c = source[i];
5811
+ if (c === "*" || c === "+") return { repeats: true, many: true };
5812
+ if (c === "{") {
5813
+ const m = /^\{(\d*)(,?)(\d*)\}/.exec(source.slice(i));
5814
+ if (m) {
5815
+ const max = m[2] ? m[3] ? Number(m[3]) : Infinity : Number(m[1]);
5816
+ return { repeats: max > 1, many: max > 1 };
5817
+ }
5818
+ }
5819
+ return { repeats: false, many: false };
5820
+ };
5821
+ const stack = [];
5822
+ let inClass = false;
5823
+ for (let i = 0; i < source.length; i++) {
5824
+ const c = source[i];
5825
+ if (c === "\\") {
5826
+ i++;
5827
+ continue;
5828
+ }
5829
+ if (inClass) {
5830
+ if (c === "]") inClass = false;
5831
+ continue;
5832
+ }
5833
+ if (c === "[") {
5834
+ inClass = true;
5835
+ } else if (c === "(") {
5836
+ stack.push(false);
5837
+ } else if (c === ")") {
5838
+ const inner = stack.pop() ?? false;
5839
+ const q = quantifierAt(i + 1);
5840
+ if (inner && q.many) return true;
5841
+ if (stack.length && (inner || q.repeats)) stack[stack.length - 1] = true;
5842
+ } else if (stack.length && quantifierAt(i).repeats) {
5843
+ stack[stack.length - 1] = true;
5844
+ }
5274
5845
  }
5275
- if (enableSmilies) {
5276
- extensions2.push(SmilieReplacer);
5846
+ return false;
5847
+ }
5848
+ function compileRegex(query, o) {
5849
+ if (hasNestedRepeat(query)) {
5850
+ throw new PatternError(
5851
+ "too_complex",
5852
+ "a repeated group containing a repeat, like (a+)+, can take exponential time; rewrite it without nesting"
5853
+ );
5277
5854
  }
5278
- if (enableColorHighlighter) {
5279
- extensions2.push(ColorHighlighter);
5855
+ let source = query;
5856
+ if (o.wholeWord || o.matchPrefix && o.matchSuffix) source = `${REGEX_BOUNDARY}(?:${query})${REGEX_BOUNDARY}`;
5857
+ else if (o.matchPrefix) source = `${REGEX_BOUNDARY}(?:${query})`;
5858
+ else if (o.matchSuffix) source = `(?:${query})${REGEX_BOUNDARY}`;
5859
+ let re;
5860
+ try {
5861
+ re = new RegExp(source, o.matchCase ? "gu" : "giu");
5862
+ } catch (error) {
5863
+ throw new PatternError("invalid_pattern", `bad pattern: ${error instanceof Error ? error.message : String(error)}`);
5280
5864
  }
5281
- if (options.mention) {
5282
- const mentionConfig = typeof options.mention === "object" ? options.mention : {};
5865
+ let slicedFor = null;
5866
+ return {
5867
+ mode: "regex",
5868
+ spansParagraphs: false,
5869
+ caseInsensitive: !o.matchCase,
5870
+ groupCount: 0,
5871
+ exec(hay, start2, end, budget) {
5872
+ if (now() > budget.deadline) {
5873
+ budget.exhausted = true;
5874
+ return "budget";
5875
+ }
5876
+ let text = hay;
5877
+ if (end < hay.length) {
5878
+ if (!slicedFor || slicedFor.hay !== hay || slicedFor.end !== end) {
5879
+ slicedFor = { hay, end, text: hay.slice(0, end) };
5880
+ }
5881
+ text = slicedFor.text;
5882
+ }
5883
+ re.lastIndex = start2;
5884
+ const m = re.exec(text);
5885
+ if (!m) return null;
5886
+ return { start: m.index, end: m.index + m[0].length, groups: Array.from(m) };
5887
+ }
5888
+ };
5889
+ }
5890
+ function compileFindPattern(query, options = {}) {
5891
+ try {
5892
+ if (Array.from(query).length > MAX_FIND_PATTERN_LENGTH) {
5893
+ throw new PatternError("too_long", `the Find what text is longer than ${MAX_FIND_PATTERN_LENGTH} characters`);
5894
+ }
5895
+ if (options.regex) return { ok: true, pattern: compileRegex(query, options) };
5896
+ const wildcards = !!options.wildcards && !options.literal;
5897
+ const caseInsensitive = !options.matchCase && !wildcards;
5898
+ let node;
5899
+ let spans;
5900
+ let groups = 0;
5901
+ if (wildcards) {
5902
+ ({ node, spans, groups } = parseWildcards(query));
5903
+ } else {
5904
+ ({ node, spans } = parseSpecial(query, options, caseInsensitive));
5905
+ const before = options.wholeWord || options.matchPrefix;
5906
+ const after = options.wholeWord || options.matchSuffix;
5907
+ if (before || after) {
5908
+ const items = [];
5909
+ if (before) items.push({ k: "assert", kind: "boundary" });
5910
+ items.push(node);
5911
+ if (after) items.push({ k: "assert", kind: "boundary" });
5912
+ node = { k: "cat", items };
5913
+ }
5914
+ }
5915
+ const prog = compileProgram(node);
5916
+ return {
5917
+ ok: true,
5918
+ pattern: {
5919
+ mode: wildcards ? "wildcards" : "special",
5920
+ spansParagraphs: spans,
5921
+ caseInsensitive,
5922
+ groupCount: groups,
5923
+ exec: (hay, start2, end, budget) => pikeExec(prog, groups, caseInsensitive, hay, start2, end, budget)
5924
+ }
5925
+ };
5926
+ } catch (error) {
5927
+ if (error instanceof PatternError) return { ok: false, error: { code: error.code, message: error.message } };
5928
+ return { ok: false, error: { code: "invalid_pattern", message: error instanceof Error ? error.message : String(error) } };
5929
+ }
5930
+ }
5931
+ function findAllMatches(pattern, hay, options = {}) {
5932
+ const budget = options.budget ?? createSearchBudget();
5933
+ const maxMatches = options.maxMatches ?? 1e4;
5934
+ const cuts = [...options.cuts ?? [], hay.length].sort((a, b) => a - b);
5935
+ const matches = [];
5936
+ let from = 0;
5937
+ for (const cut of cuts) {
5938
+ if (cut <= from) continue;
5939
+ let pos = from;
5940
+ from = cut;
5941
+ for (; ; ) {
5942
+ const m = pattern.exec(hay, pos, cut, budget);
5943
+ if (m === "budget") return { matches, truncated: true };
5944
+ if (!m) break;
5945
+ const empty = m.end === m.start;
5946
+ const next = empty ? m.end + (cpAt(hay, m.end) > 65535 ? 2 : 1) : m.end;
5947
+ if (next > cut) break;
5948
+ pos = next;
5949
+ if (empty) continue;
5950
+ matches.push(m);
5951
+ if (matches.length >= maxMatches) return { matches, truncated: true };
5952
+ }
5953
+ }
5954
+ return { matches, truncated: false };
5955
+ }
5956
+ function parseReplacement(replacement, options = {}) {
5957
+ if (options.literal || options.regex) return replacement ? [{ type: "text", text: replacement }] : [];
5958
+ const chars = toChars(replacement);
5959
+ const out = [];
5960
+ let cur = "";
5961
+ const flush = () => {
5962
+ if (cur) out.push({ type: "text", text: cur });
5963
+ cur = "";
5964
+ };
5965
+ let i = 0;
5966
+ while (i < chars.length) {
5967
+ const c = chars[i++];
5968
+ if (c === "\\" && options.wildcards && i < chars.length) {
5969
+ const d = chars[i];
5970
+ if (/[1-9]/.test(d)) {
5971
+ flush();
5972
+ out.push({ type: "group", index: Number(d) });
5973
+ } else {
5974
+ cur += d;
5975
+ }
5976
+ i++;
5977
+ continue;
5978
+ }
5979
+ if (c === "^" && i < chars.length) {
5980
+ const n = chars[i];
5981
+ if (n === "&" || n === "c") {
5982
+ flush();
5983
+ out.push(n === "&" ? { type: "found" } : { type: "clipboard" });
5984
+ i++;
5985
+ continue;
5986
+ }
5987
+ const code = replaceCode(n);
5988
+ if (code !== null) {
5989
+ cur += code;
5990
+ i++;
5991
+ continue;
5992
+ }
5993
+ const num = numericCode(chars, i);
5994
+ if (num) {
5995
+ cur += String.fromCodePoint(num.cp);
5996
+ i += num.used;
5997
+ continue;
5998
+ }
5999
+ }
6000
+ cur += c;
6001
+ }
6002
+ flush();
6003
+ return out;
6004
+ }
6005
+ function expandReplacement(pieces, match, clipboardText = "") {
6006
+ let out = "";
6007
+ for (const piece of pieces) {
6008
+ switch (piece.type) {
6009
+ case "text":
6010
+ out += piece.text;
6011
+ break;
6012
+ case "found":
6013
+ out += match.groups[0] ?? "";
6014
+ break;
6015
+ case "group":
6016
+ out += match.groups[piece.index] ?? "";
6017
+ break;
6018
+ case "clipboard":
6019
+ out += clipboardText.replace(/\r\n/g, "\r").replace(/\n/g, "\r");
6020
+ break;
6021
+ }
6022
+ }
6023
+ return out;
6024
+ }
6025
+
6026
+ // src/extensions/find-replace/search.ts
6027
+ var DELETION_MARK = "deletion";
6028
+ var DROPPED_REPLACEMENT_MARKS = /* @__PURE__ */ new Set([DELETION_MARK, "formatChange"]);
6029
+ var OBJECT_CHAR = "\uFFFC";
6030
+ function searchDocument(doc, query, options = {}, searchOptions = {}) {
6031
+ if (!query) return { matches: [], truncated: false, error: null };
6032
+ const compiled = compileFindPattern(query, options);
6033
+ if (!compiled.ok) return { matches: [], truncated: false, error: compiled.error };
6034
+ const pattern = compiled.pattern;
6035
+ const pieces = parseReplacement(searchOptions.replacement ?? "", options);
6036
+ const budget = createSearchBudget({ timeLimitMs: searchOptions.timeLimitMs });
6037
+ const maxMatches = searchOptions.maxMatches ?? 1e4;
6038
+ const blocks = [];
6039
+ doc.descendants((node, pos, parent, index) => {
6040
+ if (!node.isTextblock) return true;
6041
+ blocks.push({ node, pos, parent, index });
6042
+ return false;
6043
+ });
6044
+ const chunks = [];
6045
+ blocks.forEach((block, i) => {
6046
+ const prev = blocks[i - 1];
6047
+ const joins = pattern.spansParagraphs && prev && prev.parent === block.parent && prev.index + 1 === block.index;
6048
+ if (joins) chunks[chunks.length - 1].push(block);
6049
+ else chunks.push([block]);
6050
+ });
6051
+ const matches = [];
6052
+ let truncated = false;
6053
+ for (const chunk of chunks) {
6054
+ let hay = "";
6055
+ const posOf = [];
6056
+ const endOf = [];
6057
+ const cuts = [];
6058
+ chunk.forEach((block, k) => {
6059
+ const contentStart = block.pos + 1;
6060
+ block.node.forEach((child, offset) => {
6061
+ const at = contentStart + offset;
6062
+ if (child.marks.some((m) => m.type.name === DELETION_MARK)) {
6063
+ if (cuts[cuts.length - 1] !== hay.length) cuts.push(hay.length);
6064
+ return;
6065
+ }
6066
+ if (child.isText) {
6067
+ const text = child.text ?? "";
6068
+ for (let j = 0; j < text.length; j++) {
6069
+ posOf.push(at + j);
6070
+ endOf.push(at + j + 1);
6071
+ }
6072
+ hay += text;
6073
+ } else {
6074
+ posOf.push(at);
6075
+ endOf.push(at + child.nodeSize);
6076
+ hay += child.type.name === "hardBreak" ? "\n" : OBJECT_CHAR;
6077
+ }
6078
+ });
6079
+ const next = chunk[k + 1];
6080
+ if (next) {
6081
+ posOf.push(contentStart + block.node.content.size);
6082
+ endOf.push(next.pos + 1);
6083
+ hay += FIND_PARAGRAPH_MARK;
6084
+ }
6085
+ });
6086
+ if (!hay) continue;
6087
+ const result = findAllMatches(pattern, hay, {
6088
+ cuts,
6089
+ budget,
6090
+ maxMatches: maxMatches - matches.length
6091
+ });
6092
+ for (const m of result.matches) {
6093
+ matches.push({
6094
+ from: posOf[m.start],
6095
+ to: endOf[m.end - 1],
6096
+ text: m.groups[0] ?? hay.slice(m.start, m.end),
6097
+ replacement: expandReplacement(pieces, m, searchOptions.clipboardText ?? "")
6098
+ });
6099
+ }
6100
+ if (result.truncated) {
6101
+ truncated = true;
6102
+ break;
6103
+ }
6104
+ }
6105
+ return { matches, truncated, error: null };
6106
+ }
6107
+ function replacementMarks(doc, from, to) {
6108
+ const $from = doc.resolve(from);
6109
+ const node = from < to ? $from.nodeAfter : null;
6110
+ const marks = node ? node.marks : $from.marks();
6111
+ return marks.filter((m) => !DROPPED_REPLACEMENT_MARKS.has(m.type.name));
6112
+ }
6113
+ function inlineContent(schema, text, marks) {
6114
+ const nodes = [];
6115
+ const hardBreak = schema.nodes.hardBreak;
6116
+ const parts = hardBreak ? text.split("\n") : [text];
6117
+ parts.forEach((part, i) => {
6118
+ if (i > 0) nodes.push(hardBreak.create(null, null, marks));
6119
+ if (part) nodes.push(schema.text(part, marks));
6120
+ });
6121
+ return nodes;
6122
+ }
6123
+ function applyReplacements(tr, matches) {
6124
+ const ends = new Array(matches.length);
6125
+ const schema = tr.doc.type.schema;
6126
+ for (let i = matches.length - 1; i >= 0; i--) {
6127
+ const { from, to, replacement } = matches[i];
6128
+ const marks = replacementMarks(tr.doc, from, to);
6129
+ const parts = replacement.split(/[\r\u2029]/);
6130
+ const stepsBefore = tr.steps.length;
6131
+ const first = inlineContent(schema, parts[0], marks);
6132
+ if (first.length) tr.replaceWith(from, to, first);
6133
+ else if (to > from) tr.delete(from, to);
6134
+ let pos = tr.mapping.slice(stepsBefore).map(to, 1);
6135
+ for (let k = 1; k < parts.length; k++) {
6136
+ tr.split(pos);
6137
+ pos += 2;
6138
+ const content = inlineContent(schema, parts[k], marks);
6139
+ if (content.length) {
6140
+ tr.insert(pos, content);
6141
+ pos += content.reduce((size, node) => size + node.nodeSize, 0);
6142
+ }
6143
+ }
6144
+ const map = tr.mapping.slice(stepsBefore);
6145
+ for (let j = i + 1; j < matches.length; j++) ends[j] = map.map(ends[j], 1);
6146
+ ends[i] = pos;
6147
+ }
6148
+ return ends;
6149
+ }
6150
+
6151
+ // src/extensions/find-replace/extension.ts
6152
+ var EMPTY_OPTIONS = Object.freeze({});
6153
+ var EMPTY_FIND_REPLACE_SNAPSHOT = Object.freeze({
6154
+ open: false,
6155
+ mode: "find",
6156
+ query: "",
6157
+ replacement: "",
6158
+ options: EMPTY_OPTIONS,
6159
+ matches: Object.freeze([]),
6160
+ currentIndex: -1,
6161
+ error: null,
6162
+ truncated: false,
6163
+ lastReplaceCount: null,
6164
+ focusRequest: 0
6165
+ });
6166
+ var FindReplaceStore = class {
6167
+ snapshot = EMPTY_FIND_REPLACE_SNAPSHOT;
6168
+ listeners = /* @__PURE__ */ new Set();
6169
+ subscribe = (listener) => {
6170
+ this.listeners.add(listener);
6171
+ listener(this.snapshot);
6172
+ return () => {
6173
+ this.listeners.delete(listener);
6174
+ };
6175
+ };
6176
+ getSnapshot = () => this.snapshot;
6177
+ /** @internal Called by the extension's plugin view. */
6178
+ set(snapshot) {
6179
+ if (snapshot === this.snapshot) return;
6180
+ this.snapshot = snapshot;
6181
+ this.listeners.forEach((listener) => {
6182
+ try {
6183
+ listener(snapshot);
6184
+ } catch (err) {
6185
+ console.error("[FindReplaceStore] Error in listener:", err);
6186
+ }
6187
+ });
6188
+ }
6189
+ };
6190
+ var findReplacePluginKey = new PluginKey11("findReplace");
6191
+ function getFindReplaceState(source) {
6192
+ const state = source?.state ?? source;
6193
+ if (!state || typeof state.plugins === "undefined") return null;
6194
+ const plugin = findReplacePluginKey.getState(state);
6195
+ return plugin ? toSnapshot(plugin) : null;
6196
+ }
6197
+ function getFindReplaceStore(editor) {
6198
+ return editor?.storage?.findReplace?.store ?? null;
6199
+ }
6200
+ var snapshotCache = /* @__PURE__ */ new WeakMap();
6201
+ function toSnapshot(s) {
6202
+ let snapshot = snapshotCache.get(s);
6203
+ if (!snapshot) {
6204
+ snapshot = Object.freeze({
6205
+ open: s.open,
6206
+ mode: s.mode,
6207
+ query: s.query,
6208
+ replacement: s.replacement,
6209
+ options: s.options,
6210
+ matches: s.active ? s.matches : EMPTY_FIND_REPLACE_SNAPSHOT.matches,
6211
+ currentIndex: s.active ? s.currentIndex : -1,
6212
+ error: s.error,
6213
+ truncated: s.truncated,
6214
+ lastReplaceCount: s.lastReplaceCount,
6215
+ focusRequest: s.focusRequest
6216
+ });
6217
+ snapshotCache.set(s, snapshot);
6218
+ }
6219
+ return snapshot;
6220
+ }
6221
+ var FIND_OPTION_TOGGLES = Object.freeze([
6222
+ { key: "matchCase", label: "Match case", short: "Aa", advanced: false },
6223
+ { key: "wholeWord", label: "Whole words only", short: "ab", advanced: false },
6224
+ { key: "wildcards", label: "Use wildcards", short: "*?", advanced: false },
6225
+ { key: "regex", label: "Regular expression", short: ".*", advanced: false },
6226
+ { key: "matchPrefix", label: "Match prefix", short: "ab-", advanced: true },
6227
+ { key: "matchSuffix", label: "Match suffix", short: "-ab", advanced: true },
6228
+ { key: "ignorePunctuation", label: "Ignore punctuation", short: ".,", advanced: true },
6229
+ { key: "ignoreWhitespace", label: "Ignore white space", short: "_", advanced: true },
6230
+ { key: "literal", label: "No special characters (^)", short: "^", advanced: true }
6231
+ ]);
6232
+ function formatFindStatus(s) {
6233
+ if (!s.query) return "";
6234
+ if (s.error) return s.error.message;
6235
+ const n = s.matches.length;
6236
+ if (n === 0) return "No results";
6237
+ const total = s.truncated ? `${n}+` : `${n}`;
6238
+ return s.currentIndex >= 0 ? `${s.currentIndex + 1} of ${total}` : `${total} results`;
6239
+ }
6240
+ function indexFrom(matches, pos) {
6241
+ if (!matches.length) return -1;
6242
+ const i = matches.findIndex((m) => m.from >= pos);
6243
+ return i < 0 ? 0 : i;
6244
+ }
6245
+ function selectedQuery(state, maxLength) {
6246
+ const { from, to, $from, $to } = state.selection;
6247
+ if (from === to || !$from.sameParent($to)) return void 0;
6248
+ const text = state.doc.textBetween(from, to, "", "");
6249
+ return text && text.length <= maxLength && !/[\r\n]/.test(text) ? text : void 0;
6250
+ }
6251
+ var FindReplace = Extension12.create({
6252
+ name: "findReplace",
6253
+ addOptions() {
6254
+ return {
6255
+ shortcuts: true,
6256
+ matchClass: "pixerate-find-match",
6257
+ currentMatchClass: "pixerate-find-match-current",
6258
+ timeLimitMs: 250,
6259
+ maxMatches: 1e4,
6260
+ getClipboardText: () => "",
6261
+ maxPrefillLength: 256
6262
+ };
6263
+ },
6264
+ addStorage() {
6265
+ return { store: new FindReplaceStore() };
6266
+ },
6267
+ addCommands() {
6268
+ const meta = (tr, value) => tr.setMeta(findReplacePluginKey, value);
6269
+ const current = (state) => findReplacePluginKey.getState(state);
6270
+ const maxPrefill = () => this.options.maxPrefillLength;
6271
+ const select = (tr, match) => {
6272
+ tr.setSelection(TextSelection7.create(tr.doc, match.from, match.to)).scrollIntoView();
6273
+ };
6274
+ return {
6275
+ setSearch: (params) => ({ tr, state, dispatch }) => {
6276
+ if (!current(state)) return false;
6277
+ if (dispatch) meta(tr, { type: "set", params, anchor: state.selection.from });
6278
+ return true;
6279
+ },
6280
+ openFindReplace: (params = {}) => ({ tr, state, dispatch }) => {
6281
+ if (!current(state)) return false;
6282
+ if (dispatch) {
6283
+ meta(tr, {
6284
+ type: "open",
6285
+ mode: params.mode ?? "find",
6286
+ query: params.query ?? selectedQuery(state, maxPrefill()),
6287
+ anchor: state.selection.from
6288
+ });
6289
+ }
6290
+ return true;
6291
+ },
6292
+ closeFindReplace: () => ({ tr, state, dispatch }) => {
6293
+ if (!current(state)) return false;
6294
+ if (dispatch) meta(tr, { type: "close" });
6295
+ return true;
6296
+ },
6297
+ clearSearch: () => ({ tr, state, dispatch }) => {
6298
+ if (!current(state)) return false;
6299
+ if (dispatch) meta(tr, { type: "clear" });
6300
+ return true;
6301
+ },
6302
+ findNext: () => ({ tr, state, dispatch }) => {
6303
+ const s = current(state);
6304
+ if (!s || !s.active || !s.matches.length) return false;
6305
+ const { from, to } = state.selection;
6306
+ const cur = s.matches[s.currentIndex];
6307
+ const onCurrent = cur && cur.from === from && cur.to === to;
6308
+ const index = onCurrent ? (s.currentIndex + 1) % s.matches.length : indexFrom(s.matches, to);
6309
+ if (dispatch) {
6310
+ meta(tr, { type: "current", index });
6311
+ select(tr, s.matches[index]);
6312
+ }
6313
+ return true;
6314
+ },
6315
+ findPrevious: () => ({ tr, state, dispatch }) => {
6316
+ const s = current(state);
6317
+ if (!s || !s.active || !s.matches.length) return false;
6318
+ const { from, to } = state.selection;
6319
+ const n = s.matches.length;
6320
+ const cur = s.matches[s.currentIndex];
6321
+ const onCurrent = cur && cur.from === from && cur.to === to;
6322
+ let index;
6323
+ if (onCurrent) {
6324
+ index = (s.currentIndex - 1 + n) % n;
6325
+ } else {
6326
+ index = -1;
6327
+ for (let i = n - 1; i >= 0; i--) {
6328
+ if (s.matches[i].to <= from) {
6329
+ index = i;
6330
+ break;
6331
+ }
6332
+ }
6333
+ if (index < 0) index = n - 1;
6334
+ }
6335
+ if (dispatch) {
6336
+ meta(tr, { type: "current", index });
6337
+ select(tr, s.matches[index]);
6338
+ }
6339
+ return true;
6340
+ },
6341
+ replaceCurrent: () => ({ tr, state, dispatch }) => {
6342
+ const s = current(state);
6343
+ if (!s || !s.active || s.currentIndex < 0 || !s.matches[s.currentIndex]) return false;
6344
+ if (dispatch) {
6345
+ closeHistory2(tr);
6346
+ const [end] = applyReplacements(tr, [s.matches[s.currentIndex]]);
6347
+ tr.setSelection(TextSelection7.create(tr.doc, end)).scrollIntoView();
6348
+ meta(tr, { type: "replaced", anchor: end, count: 1 });
6349
+ }
6350
+ return true;
6351
+ },
6352
+ replaceAll: () => ({ tr, state, dispatch }) => {
6353
+ const s = current(state);
6354
+ if (!s || !s.active || !s.matches.length) return false;
6355
+ if (dispatch) {
6356
+ closeHistory2(tr);
6357
+ const ends = applyReplacements(tr, s.matches);
6358
+ meta(tr, { type: "replaced", anchor: ends[ends.length - 1], count: s.matches.length });
6359
+ }
6360
+ return true;
6361
+ }
6362
+ };
6363
+ },
6364
+ addKeyboardShortcuts() {
6365
+ if (!this.options.shortcuts) return {};
6366
+ const open = (mode) => () => this.editor.commands.openFindReplace({ mode });
6367
+ const step = (forward) => () => {
6368
+ const s = findReplacePluginKey.getState(this.editor.state);
6369
+ if (!s?.active || !s.matches.length) return false;
6370
+ return forward ? this.editor.commands.findNext() : this.editor.commands.findPrevious();
6371
+ };
6372
+ return {
6373
+ "Mod-f": open("find"),
6374
+ "Mod-h": open("replace"),
6375
+ "Mod-Shift-h": open("replace"),
6376
+ F3: step(true),
6377
+ "Shift-F3": step(false),
6378
+ "Mod-g": step(true),
6379
+ "Mod-Shift-g": step(false)
6380
+ };
6381
+ },
6382
+ addProseMirrorPlugins() {
6383
+ const options = this.options;
6384
+ const store = this.storage.store;
6385
+ const search = (s, doc) => {
6386
+ if (!s.active || !s.query) {
6387
+ return { ...s, matches: [], currentIndex: -1, error: null, truncated: false };
6388
+ }
6389
+ const result = searchDocument(doc, s.query, s.options, {
6390
+ replacement: s.replacement,
6391
+ clipboardText: safeClipboard(options.getClipboardText),
6392
+ timeLimitMs: options.timeLimitMs,
6393
+ maxMatches: options.maxMatches
6394
+ });
6395
+ return {
6396
+ ...s,
6397
+ matches: result.matches,
6398
+ currentIndex: indexFrom(result.matches, s.anchor),
6399
+ error: result.error,
6400
+ truncated: result.truncated
6401
+ };
6402
+ };
6403
+ const decorate = (s, doc) => {
6404
+ if (!s.active || !s.matches.length) return { ...s, decorations: DecorationSet4.empty };
6405
+ const decorations = s.matches.map(
6406
+ (m, i) => Decoration4.inline(m.from, m.to, {
6407
+ class: i === s.currentIndex ? `${options.matchClass} ${options.currentMatchClass}` : options.matchClass
6408
+ })
6409
+ );
6410
+ return { ...s, decorations: DecorationSet4.create(doc, decorations) };
6411
+ };
6412
+ return [
6413
+ new Plugin14({
6414
+ key: findReplacePluginKey,
6415
+ state: {
6416
+ init: () => ({
6417
+ active: false,
6418
+ open: false,
6419
+ mode: "find",
6420
+ query: "",
6421
+ replacement: "",
6422
+ options: EMPTY_OPTIONS,
6423
+ matches: [],
6424
+ currentIndex: -1,
6425
+ anchor: 0,
6426
+ error: null,
6427
+ truncated: false,
6428
+ lastReplaceCount: null,
6429
+ focusRequest: 0,
6430
+ decorations: DecorationSet4.empty
6431
+ }),
6432
+ apply: (tr, prev, _oldState, newState) => {
6433
+ const m = tr.getMeta(findReplacePluginKey);
6434
+ if (!m && !tr.docChanged) return prev;
6435
+ let s = prev;
6436
+ let research = false;
6437
+ let redecorate = false;
6438
+ if (tr.docChanged) {
6439
+ s = { ...s, anchor: tr.mapping.map(s.anchor) };
6440
+ research = s.active;
6441
+ }
6442
+ if (m) {
6443
+ switch (m.type) {
6444
+ case "set": {
6445
+ const p = m.params;
6446
+ s = {
6447
+ ...s,
6448
+ active: true,
6449
+ query: p.query ?? s.query,
6450
+ replacement: p.replacement ?? s.replacement,
6451
+ options: p.options ? Object.freeze({ ...s.options, ...p.options }) : s.options,
6452
+ anchor: m.anchor,
6453
+ lastReplaceCount: null
6454
+ };
6455
+ research = true;
6456
+ break;
6457
+ }
6458
+ case "open":
6459
+ s = {
6460
+ ...s,
6461
+ active: true,
6462
+ open: true,
6463
+ mode: m.mode,
6464
+ query: m.query ?? s.query,
6465
+ anchor: m.anchor,
6466
+ focusRequest: s.focusRequest + 1
6467
+ };
6468
+ research = true;
6469
+ break;
6470
+ case "close":
6471
+ s = { ...s, open: false, active: false };
6472
+ research = true;
6473
+ break;
6474
+ case "clear":
6475
+ s = { ...s, active: false, query: "", lastReplaceCount: null };
6476
+ research = true;
6477
+ break;
6478
+ case "current":
6479
+ s = {
6480
+ ...s,
6481
+ currentIndex: m.index,
6482
+ anchor: s.matches[m.index]?.from ?? s.anchor
6483
+ };
6484
+ redecorate = true;
6485
+ break;
6486
+ case "replaced":
6487
+ s = { ...s, anchor: m.anchor, lastReplaceCount: m.count };
6488
+ research = true;
6489
+ break;
6490
+ }
6491
+ }
6492
+ if (research) s = search(s, newState.doc);
6493
+ if (research || redecorate) s = decorate(s, newState.doc);
6494
+ return s;
6495
+ }
6496
+ },
6497
+ props: {
6498
+ decorations: (state) => findReplacePluginKey.getState(state)?.decorations ?? DecorationSet4.empty
6499
+ },
6500
+ view: (view) => {
6501
+ const sync = () => {
6502
+ const s = findReplacePluginKey.getState(view.state);
6503
+ if (s) store.set(toSnapshot(s));
6504
+ };
6505
+ sync();
6506
+ return { update: sync };
6507
+ }
6508
+ })
6509
+ ];
6510
+ }
6511
+ });
6512
+ function safeClipboard(get) {
6513
+ try {
6514
+ return String(get() ?? "");
6515
+ } catch {
6516
+ return "";
6517
+ }
6518
+ }
6519
+
6520
+ // src/extensions/paragraph-styles/extension.ts
6521
+ import { Extension as Extension13, Mark as Mark5, mergeAttributes as mergeAttributes7 } from "@tiptap/core";
6522
+ import { Plugin as Plugin15, PluginKey as PluginKey12 } from "@tiptap/pm/state";
6523
+
6524
+ // src/extensions/paragraph-styles/registry.ts
6525
+ var MAX_CHAIN = 32;
6526
+ var STYLE_ID = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
6527
+ var UNSAFE_CSS = /[;{}<>\\]|\/\*|url\s*\(|expression\s*\(|@import|javascript:/i;
6528
+ var NORMAL_STYLE_ID = "Normal";
6529
+ var PREVIEW_PROPERTIES = /* @__PURE__ */ new Set([
6530
+ "font-family",
6531
+ "font-weight",
6532
+ "font-style",
6533
+ "color",
6534
+ "text-decoration-line",
6535
+ "text-transform",
6536
+ "letter-spacing",
6537
+ "font-variant-caps"
6538
+ ]);
6539
+ function isValidStyleId(id) {
6540
+ return typeof id === "string" && STYLE_ID.test(id);
6541
+ }
6542
+ var MUTED = "var(--pixerate-style-muted, #6b7280)";
6543
+ var HEADING_SIZES = ["2em", "1.5em", "1.25em", "1.1em", "1em", "0.9em"];
6544
+ function builtinStyles() {
6545
+ const styles = [
6546
+ {
6547
+ id: NORMAL_STYLE_ID,
6548
+ name: "Normal",
6549
+ kind: "paragraph",
6550
+ block: { type: "paragraph" },
6551
+ priority: 0,
6552
+ quick: true,
6553
+ builtin: true
6554
+ },
6555
+ {
6556
+ id: "Title",
6557
+ name: "Title",
6558
+ kind: "paragraph",
6559
+ basedOn: NORMAL_STYLE_ID,
6560
+ next: NORMAL_STYLE_ID,
6561
+ linked: "TitleChar",
6562
+ paragraph: { spaceAfter: "0.25em", lineHeight: "1.15" },
6563
+ character: { fontSize: "2.5em", bold: true, letterSpacing: "-0.01em" },
6564
+ priority: 10,
6565
+ quick: true,
6566
+ builtin: true
6567
+ },
6568
+ {
6569
+ id: "Subtitle",
6570
+ name: "Subtitle",
6571
+ kind: "paragraph",
6572
+ basedOn: NORMAL_STYLE_ID,
6573
+ next: NORMAL_STYLE_ID,
6574
+ linked: "SubtitleChar",
6575
+ paragraph: { spaceAfter: "0.75em" },
6576
+ character: { fontSize: "1.25em", color: MUTED },
6577
+ priority: 11,
6578
+ quick: true,
6579
+ builtin: true
6580
+ }
6581
+ ];
6582
+ HEADING_SIZES.forEach((fontSize, i) => {
6583
+ const level = i + 1;
6584
+ styles.push({
6585
+ id: `Heading${level}`,
6586
+ name: `Heading ${level}`,
6587
+ kind: "paragraph",
6588
+ basedOn: NORMAL_STYLE_ID,
6589
+ next: NORMAL_STYLE_ID,
6590
+ linked: `Heading${level}Char`,
6591
+ block: { type: "heading", attrs: { level } },
6592
+ paragraph: { spaceBefore: level <= 2 ? "1em" : "0.75em", spaceAfter: "0.25em" },
6593
+ character: { fontSize, bold: true },
6594
+ priority: 11 + level,
6595
+ quick: level <= 3,
6596
+ builtin: true
6597
+ });
6598
+ });
6599
+ styles.push(
6600
+ {
6601
+ id: "Quote",
6602
+ name: "Quote",
6603
+ kind: "paragraph",
6604
+ basedOn: NORMAL_STYLE_ID,
6605
+ next: NORMAL_STYLE_ID,
6606
+ linked: "QuoteChar",
6607
+ paragraph: { indentLeft: "1em", paddingLeft: "0.75em", borderLeft: "3px solid currentColor" },
6608
+ character: { italic: true, color: MUTED },
6609
+ priority: 29,
6610
+ quick: true,
6611
+ builtin: true
6612
+ },
6613
+ {
6614
+ id: "Code",
6615
+ name: "Code",
6616
+ kind: "paragraph",
6617
+ basedOn: NORMAL_STYLE_ID,
6618
+ next: NORMAL_STYLE_ID,
6619
+ block: { type: "codeBlock" },
6620
+ character: {
6621
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
6622
+ fontSize: "0.9em"
6623
+ },
6624
+ priority: 30,
6625
+ quick: true,
6626
+ builtin: true
6627
+ },
6628
+ {
6629
+ id: "ListParagraph",
6630
+ name: "List Paragraph",
6631
+ kind: "paragraph",
6632
+ basedOn: NORMAL_STYLE_ID,
6633
+ paragraph: { indentLeft: "2em" },
6634
+ priority: 34,
6635
+ quick: true,
6636
+ builtin: true
6637
+ }
6638
+ );
6639
+ const linked = ["Title", "Subtitle", "Quote", ...HEADING_SIZES.map((_, i) => `Heading${i + 1}`)];
6640
+ linked.forEach((paragraphId) => {
6641
+ const name = styles.find((s) => s.id === paragraphId).name;
6642
+ styles.push({
6643
+ id: `${paragraphId}Char`,
6644
+ name: `${name} Char`,
6645
+ kind: "character",
6646
+ linked: paragraphId,
6647
+ priority: 99,
6648
+ hidden: true,
6649
+ builtin: true
6650
+ });
6651
+ });
6652
+ styles.push(
6653
+ {
6654
+ id: "Emphasis",
6655
+ name: "Emphasis",
6656
+ kind: "character",
6657
+ character: { italic: true },
6658
+ priority: 20,
6659
+ quick: true,
6660
+ builtin: true
6661
+ },
6662
+ {
6663
+ id: "Strong",
6664
+ name: "Strong",
6665
+ kind: "character",
6666
+ character: { bold: true },
6667
+ priority: 22,
6668
+ quick: true,
6669
+ builtin: true
6670
+ }
6671
+ );
6672
+ return styles;
6673
+ }
6674
+ var DEFAULT_STYLES = Object.freeze(builtinStyles());
6675
+ var clone = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
6676
+ function overlay(base, patch) {
6677
+ if (!patch) return base;
6678
+ const out = { ...base };
6679
+ Object.entries(patch).forEach(([key, value]) => {
6680
+ if (value !== void 0 && value !== null) out[key] = value;
6681
+ });
6682
+ return out;
6683
+ }
6684
+ function sameBlock(a, type, attrs) {
6685
+ if (a.type !== type) return false;
6686
+ return Object.entries(a.attrs ?? {}).every(([key, value]) => attrs[key] === value);
6687
+ }
6688
+ var StyleRegistry = class {
6689
+ styles = /* @__PURE__ */ new Map();
6690
+ defaults;
6691
+ version = 0;
6692
+ snapshot = null;
6693
+ listeners = /* @__PURE__ */ new Set();
6694
+ constructor(options = {}) {
6695
+ this.defaults = clone(options.defaults ?? {});
6696
+ if (options.builtins !== false) DEFAULT_STYLES.forEach((style) => this.styles.set(style.id, clone(style)));
6697
+ (options.styles ?? []).forEach((style) => this.put(style));
6698
+ }
6699
+ // -- Reading --------------------------------------------------------------
6700
+ get(id) {
6701
+ return id ? this.styles.get(id) : void 0;
6702
+ }
6703
+ /** Finds a style by id or display name (case-insensitive). */
6704
+ find(nameOrId) {
6705
+ const direct = this.styles.get(nameOrId);
6706
+ if (direct) return direct;
6707
+ const lower = nameOrId.toLowerCase();
6708
+ return this.list().find((style) => style.name.toLowerCase() === lower);
6709
+ }
6710
+ /** All styles (optionally of one kind) in gallery order. */
6711
+ list(kind) {
6712
+ return Array.from(this.styles.values()).filter((style) => !kind || style.kind === kind).sort((a, b) => (a.priority ?? 99) - (b.priority ?? 99) || a.name.localeCompare(b.name));
6713
+ }
6714
+ /** Styles shown in the gallery (`quick`, not hidden), paragraph styles by default. */
6715
+ gallery(kind = "paragraph") {
6716
+ return this.list(kind).filter((style) => style.quick && !style.hidden);
6717
+ }
6718
+ /** The `basedOn` chain from the root style down to `id` (cycle-safe, bounded). */
6719
+ chain(id) {
6720
+ const out = [];
6721
+ let current = this.styles.get(id);
6722
+ while (current && out.length < MAX_CHAIN && !out.includes(current)) {
6723
+ out.push(current);
6724
+ current = current.basedOn ? this.styles.get(current.basedOn) : void 0;
6725
+ }
6726
+ return out.reverse();
6727
+ }
6728
+ /**
6729
+ * Resolves a paragraph style: defaults, then each style of the chain (base
6730
+ * first). Unknown ids resolve to the defaults in a `paragraph` block.
6731
+ */
6732
+ resolve(id) {
6733
+ let paragraph = overlay({}, this.defaults.paragraph);
6734
+ let character = overlay({}, this.defaults.character);
6735
+ let block = { type: "paragraph" };
6736
+ this.chain(id).forEach((style) => {
6737
+ if (style.kind !== "paragraph") return;
6738
+ paragraph = overlay(paragraph, style.paragraph);
6739
+ character = overlay(character, style.character);
6740
+ if (style.block) block = clone(style.block);
6741
+ });
6742
+ return { id, paragraph, character, block, next: this.nextStyleId(id) };
6743
+ }
6744
+ /** Character formatting a paragraph style's chain sets (without defaults). */
6745
+ paragraphCharacter(id) {
6746
+ let character = {};
6747
+ this.chain(id).forEach((style) => {
6748
+ if (style.kind === "paragraph") character = overlay(character, style.character);
6749
+ });
6750
+ return character;
6751
+ }
6752
+ /**
6753
+ * Character formatting of a character style's chain. A style linked to a
6754
+ * paragraph style takes that style's character formatting first.
6755
+ */
6756
+ resolveCharacterStyle(id) {
6757
+ let character = {};
6758
+ this.chain(id).forEach((style) => {
6759
+ if (style.kind === "character" && style.linked && this.get(style.linked)?.kind === "paragraph") {
6760
+ character = overlay(character, this.paragraphCharacter(style.linked));
6761
+ }
6762
+ character = overlay(character, style.character);
6763
+ });
6764
+ return character;
6765
+ }
6766
+ /**
6767
+ * Character formatting of text: defaults → paragraph style chain →
6768
+ * character style chain → direct formatting.
6769
+ */
6770
+ resolveText(input) {
6771
+ let character = this.resolve(input.paragraphStyle || NORMAL_STYLE_ID).character;
6772
+ if (input.characterStyle) character = overlay(character, this.resolveCharacterStyle(input.characterStyle));
6773
+ return overlay(character, input.direct);
6774
+ }
6775
+ /** The style after Enter at the end of a paragraph of `id` (itself when unset or unknown). */
6776
+ nextStyleId(id) {
6777
+ const next = this.styles.get(id)?.next;
6778
+ return next && this.styles.get(next)?.kind === "paragraph" ? next : id;
6779
+ }
6780
+ /** The linked character style of a paragraph style, if any. */
6781
+ linkedCharacterStyle(id) {
6782
+ const style = this.styles.get(id);
6783
+ const linked = style?.linked ? this.styles.get(style.linked) : void 0;
6784
+ return style?.kind === "paragraph" && linked?.kind === "character" ? linked : void 0;
6785
+ }
6786
+ /**
6787
+ * The style a block uses when it names none: Normal for paragraphs,
6788
+ * Heading N for headings of level N, Code for code blocks (the built-in
6789
+ * style whose own `block` matches).
6790
+ */
6791
+ defaultStyleFor(type, attrs = {}) {
6792
+ if (type === "paragraph" && this.styles.get(NORMAL_STYLE_ID)) return NORMAL_STYLE_ID;
6793
+ const candidates = this.list("paragraph").filter(
6794
+ (style) => style.block && sameBlock(style.block, type, attrs)
6795
+ );
6796
+ return (candidates.find((s) => s.builtin) ?? candidates[0])?.id ?? null;
6797
+ }
6798
+ /** The style of a block: its `styleId` when known, otherwise the block's default style. */
6799
+ styleOfBlock(type, attrs = {}) {
6800
+ const id = attrs.styleId;
6801
+ if (id && this.styles.get(id)?.kind === "paragraph") return id;
6802
+ return this.defaultStyleFor(type, attrs);
6803
+ }
6804
+ getSnapshot = () => {
6805
+ if (!this.snapshot) {
6806
+ this.snapshot = Object.freeze({
6807
+ styles: Object.freeze(this.list()),
6808
+ defaults: this.defaults,
6809
+ version: this.version
6810
+ });
6811
+ }
6812
+ return this.snapshot;
6813
+ };
6814
+ subscribe = (listener) => {
6815
+ this.listeners.add(listener);
6816
+ listener(this.getSnapshot());
6817
+ return () => {
6818
+ this.listeners.delete(listener);
6819
+ };
6820
+ };
6821
+ toJSON() {
6822
+ return { styles: clone(Array.from(this.styles.values())), defaults: clone(this.defaults) };
6823
+ }
6824
+ // -- Writing --------------------------------------------------------------
6825
+ /** Adds or replaces a style. Throws on an invalid id. */
6826
+ upsert(style) {
6827
+ const stored = this.put(style);
6828
+ this.changed();
6829
+ return stored;
6830
+ }
6831
+ /** Merges `patch` into a style (formats are merged key by key). Returns `undefined` for unknown ids. */
6832
+ update(id, patch) {
6833
+ const current = this.styles.get(id);
6834
+ if (!current) return void 0;
6835
+ const next = {
6836
+ ...current,
6837
+ ...patch,
6838
+ id,
6839
+ paragraph: patch.paragraph ? { ...current.paragraph, ...patch.paragraph } : current.paragraph,
6840
+ character: patch.character ? { ...current.character, ...patch.character } : current.character
6841
+ };
6842
+ return this.upsert(next);
6843
+ }
6844
+ /**
6845
+ * Removes a style and returns it. Built-in styles cannot be removed. Styles
6846
+ * based on it are rebased onto its own base, and `next`/`linked`
6847
+ * references to it are dropped.
6848
+ */
6849
+ remove(id) {
6850
+ const gone = this.styles.get(id);
6851
+ if (!gone || gone.builtin) return void 0;
6852
+ this.styles.delete(id);
6853
+ this.styles.forEach((style, key) => {
6854
+ let changed = style;
6855
+ if (style.basedOn === id) {
6856
+ changed = { ...changed, basedOn: gone.basedOn && gone.basedOn !== key ? gone.basedOn : null };
6857
+ }
6858
+ if (style.next === id) changed = { ...changed, next: null };
6859
+ if (style.linked === id) changed = { ...changed, linked: null };
6860
+ if (changed !== style) this.styles.set(key, changed);
6861
+ });
6862
+ this.changed();
6863
+ return gone;
6864
+ }
6865
+ /** Replaces the document defaults. */
6866
+ setDefaults(defaults) {
6867
+ this.defaults = clone(defaults);
6868
+ this.changed();
6869
+ }
6870
+ /** An unused id derived from a display name (`My Style` → `MyStyle`, then `MyStyle1`, …). */
6871
+ newId(name) {
6872
+ let base = name.replace(/[^A-Za-z0-9_-]+/g, "").replace(/^[^A-Za-z]+/, "").slice(0, 56);
6873
+ if (!base) base = "Style";
6874
+ if (!this.styles.has(base)) return base;
6875
+ for (let i = 1; i < 1e5; i++) {
6876
+ const id = `${base}${i}`;
6877
+ if (!this.styles.has(id)) return id;
6878
+ }
6879
+ return `${base}${Date.now()}`;
6880
+ }
6881
+ // -- CSS --------------------------------------------------------------------
6882
+ /** CSS declarations (`property: value`) of a resolved paragraph or character style. */
6883
+ cssDeclarations(id) {
6884
+ const style = this.styles.get(id);
6885
+ if (style?.kind === "character") return characterCSS(this.resolveCharacterStyle(id));
6886
+ const resolved = this.resolve(id);
6887
+ return { ...paragraphCSS(resolved.paragraph), ...characterCSS(resolved.character) };
6888
+ }
6889
+ /**
6890
+ * Declarations for previewing a style in a menu or gallery: font family,
6891
+ * weight, style, color, decoration, case and letter spacing (no sizes,
6892
+ * margins or borders).
6893
+ */
6894
+ previewDeclarations(id) {
6895
+ return Object.fromEntries(
6896
+ Object.entries(this.cssDeclarations(id)).filter(([property]) => PREVIEW_PROPERTIES.has(property))
6897
+ );
6898
+ }
6899
+ /**
6900
+ * A stylesheet for the styles: `[data-style="Id"]` rules for paragraph
6901
+ * styles (plus the plain element of blocks that use their default style,
6902
+ * e.g. `h2:not([data-style])`), `[data-char-style="Id"]` for character
6903
+ * styles and the defaults on the scope element.
6904
+ */
6905
+ toCSS(options = {}) {
6906
+ const scope = options.scope ?? ".ProseMirror";
6907
+ const tagFor = options.tagFor ?? defaultTagFor;
6908
+ const rules = [];
6909
+ const rule = (selectors, declarations) => {
6910
+ const body = Object.entries(declarations).map(([property, value]) => `${property}: ${value};`).join(" ");
6911
+ if (body) rules.push(`${selectors.join(", ")} { ${body} }`);
6912
+ };
6913
+ rule([scope], {
6914
+ ...paragraphCSS(this.defaults.paragraph ?? {}),
6915
+ ...characterCSS(this.defaults.character ?? {})
6916
+ });
6917
+ this.list().forEach((style) => {
6918
+ if (style.kind === "character") {
6919
+ rule([`${scope} [data-char-style="${style.id}"]`], this.cssDeclarations(style.id));
6920
+ return;
6921
+ }
6922
+ const selectors = [`${scope} [data-style="${style.id}"]`];
6923
+ const block = this.resolve(style.id).block;
6924
+ if (this.defaultStyleFor(block.type, block.attrs ?? {}) === style.id) {
6925
+ const tag = tagFor(block);
6926
+ if (tag) selectors.push(`${scope} ${tag}:not([data-style])`);
6927
+ }
6928
+ const resolved = this.resolve(style.id);
6929
+ const own = {
6930
+ ...paragraphCSS(withoutDefaults(resolved.paragraph, this.defaults.paragraph)),
6931
+ ...characterCSS(withoutDefaults(resolved.character, this.defaults.character))
6932
+ };
6933
+ rule(selectors, own);
6934
+ });
6935
+ return rules.join("\n");
6936
+ }
6937
+ // -- Internals --------------------------------------------------------------
6938
+ put(style) {
6939
+ if (!isValidStyleId(style.id)) throw new Error(`Invalid style id: ${String(style.id)}`);
6940
+ if (style.kind !== "paragraph" && style.kind !== "character") {
6941
+ throw new Error(`Invalid style kind: ${String(style.kind)}`);
6942
+ }
6943
+ const stored = clone({ ...style, name: style.name || style.id });
6944
+ this.styles.set(style.id, stored);
6945
+ return stored;
6946
+ }
6947
+ changed() {
6948
+ this.version++;
6949
+ this.snapshot = null;
6950
+ const snapshot = this.getSnapshot();
6951
+ this.listeners.forEach((listener) => {
6952
+ try {
6953
+ listener(snapshot);
6954
+ } catch (err) {
6955
+ console.error("[StyleRegistry] Error in listener:", err);
6956
+ }
6957
+ });
6958
+ }
6959
+ };
6960
+ function withoutDefaults(format, defaults) {
6961
+ if (!defaults) return format;
6962
+ const out = {};
6963
+ Object.entries(format).forEach(([key, value]) => {
6964
+ if (defaults[key] !== value) out[key] = value;
6965
+ });
6966
+ return out;
6967
+ }
6968
+ function defaultTagFor(block) {
6969
+ if (block.type === "paragraph") return "p";
6970
+ if (block.type === "heading") {
6971
+ const level = Number(block.attrs?.level);
6972
+ return level >= 1 && level <= 6 ? `h${level}` : null;
6973
+ }
6974
+ if (block.type === "codeBlock") return "pre";
6975
+ return null;
6976
+ }
6977
+ function safeCSSValue(value) {
6978
+ if (typeof value !== "string") return void 0;
6979
+ const trimmed = value.trim();
6980
+ if (!trimmed || trimmed.length > 200 || UNSAFE_CSS.test(trimmed)) return void 0;
6981
+ return trimmed;
6982
+ }
6983
+ function paragraphCSS(format) {
6984
+ const out = {};
6985
+ const set = (property, value) => {
6986
+ const safe = safeCSSValue(value);
6987
+ if (safe) out[property] = safe;
6988
+ };
6989
+ set("text-align", format.textAlign);
6990
+ set("line-height", format.lineHeight);
6991
+ set("margin-top", format.spaceBefore);
6992
+ set("margin-bottom", format.spaceAfter);
6993
+ set("margin-left", format.indentLeft);
6994
+ set("margin-right", format.indentRight);
6995
+ set("text-indent", format.indentFirstLine);
6996
+ set("border-left", format.borderLeft);
6997
+ set("padding-left", format.paddingLeft);
6998
+ set("background-color", format.backgroundColor);
6999
+ return out;
7000
+ }
7001
+ function characterCSS(format) {
7002
+ const out = {};
7003
+ const set = (property, value) => {
7004
+ const safe = safeCSSValue(value);
7005
+ if (safe) out[property] = safe;
7006
+ };
7007
+ set("font-family", format.fontFamily);
7008
+ set("font-size", format.fontSize);
7009
+ if (format.bold !== void 0) out["font-weight"] = format.bold ? "700" : "400";
7010
+ if (format.italic !== void 0) out["font-style"] = format.italic ? "italic" : "normal";
7011
+ const lines = [format.underline && "underline", format.strike && "line-through"].filter(Boolean);
7012
+ if (format.underline !== void 0 || format.strike !== void 0) {
7013
+ out["text-decoration-line"] = lines.length ? lines.join(" ") : "none";
7014
+ }
7015
+ set("color", format.color);
7016
+ set("background-color", format.backgroundColor);
7017
+ set("letter-spacing", format.letterSpacing);
7018
+ set("text-transform", format.textTransform);
7019
+ if (format.smallCaps !== void 0) out["font-variant-caps"] = format.smallCaps ? "small-caps" : "normal";
7020
+ return out;
7021
+ }
7022
+ function createStyleRegistry(options = {}) {
7023
+ return new StyleRegistry(options);
7024
+ }
7025
+
7026
+ // src/extensions/paragraph-styles/extension.ts
7027
+ var STYLE_ID_ATTRIBUTE = "styleId";
7028
+ var CHARACTER_STYLE_MARK_NAME = "characterStyle";
7029
+ var paragraphStylesPluginKey = new PluginKey12("paragraphStyles");
7030
+ function getStyleRegistry(editor) {
7031
+ return editor?.storage?.paragraphStyles?.registry ?? null;
7032
+ }
7033
+ function headingLevels(editor) {
7034
+ const heading = editor?.extensionManager?.extensions.find((e) => e.name === "heading");
7035
+ const levels = heading?.options?.levels;
7036
+ return Array.isArray(levels) ? levels : null;
7037
+ }
7038
+ function isParagraphStyleAvailable(editor, registry, id) {
7039
+ const style = registry.get(id);
7040
+ if (!style || style.kind !== "paragraph") return false;
7041
+ const block = registry.resolve(id).block;
7042
+ if (!editor.schema.nodes[block.type]) return false;
7043
+ if (block.type === "heading") {
7044
+ const levels = headingLevels(editor);
7045
+ if (levels && !levels.includes(Number(block.attrs?.level))) return false;
7046
+ }
7047
+ return true;
7048
+ }
7049
+ function getParagraphStyleAt(state, registry, pos = state.selection.from) {
7050
+ const $pos = state.doc.resolve(Math.max(0, Math.min(pos, state.doc.content.size)));
7051
+ const block = $pos.parent;
7052
+ if (!block.isTextblock) return null;
7053
+ return registry.styleOfBlock(block.type.name, block.attrs);
7054
+ }
7055
+ var MARK_FORMAT = {
7056
+ bold: "bold",
7057
+ italic: "italic",
7058
+ underline: "underline",
7059
+ strike: "strike"
7060
+ };
7061
+ function selectionMarks(state) {
7062
+ const { from, to, empty, $from } = state.selection;
7063
+ if (empty) return state.storedMarks ?? $from.marks();
7064
+ let common = null;
7065
+ state.doc.nodesBetween(from, to, (node) => {
7066
+ if (!node.isText) return true;
7067
+ common = common === null ? node.marks : common.filter((m) => m.isInSet(node.marks));
7068
+ return false;
7069
+ });
7070
+ return common ?? $from.marks();
7071
+ }
7072
+ function captureSelectionFormat(state) {
7073
+ const character = {};
7074
+ selectionMarks(state).forEach((mark) => {
7075
+ const name = mark.type.name;
7076
+ if (MARK_FORMAT[name]) character[MARK_FORMAT[name]] = true;
7077
+ if (name === "textStyle") {
7078
+ if (mark.attrs.fontFamily) character.fontFamily = mark.attrs.fontFamily;
7079
+ if (mark.attrs.fontSize) character.fontSize = mark.attrs.fontSize;
7080
+ if (mark.attrs.color) character.color = mark.attrs.color;
7081
+ }
7082
+ if (name === "highlight") character.backgroundColor = mark.attrs.color || "#fef08a";
7083
+ });
7084
+ const paragraph = {};
7085
+ const align = state.selection.$from.parent.attrs?.textAlign;
7086
+ if (align && align !== "left") paragraph.textAlign = align;
7087
+ return { paragraph, character };
7088
+ }
7089
+ function carriedAttributes(node, type) {
7090
+ const spec = type.spec.attrs ?? {};
7091
+ return Object.fromEntries(Object.entries(node.attrs).filter(([key]) => key in spec));
7092
+ }
7093
+ function applyStyleToBlock(tr, pos, id, registry) {
7094
+ const node = tr.doc.nodeAt(pos);
7095
+ if (!node || !node.isTextblock) return false;
7096
+ const resolved = registry.resolve(id);
7097
+ const type = tr.doc.type.schema.nodes[resolved.block.type];
7098
+ if (!type) return false;
7099
+ const blockAttrs = resolved.block.attrs ?? {};
7100
+ const isDefault = registry.defaultStyleFor(resolved.block.type, blockAttrs) === id;
7101
+ const attrs = { ...carriedAttributes(node, type), ...blockAttrs };
7102
+ if (STYLE_ID_ATTRIBUTE in (type.spec.attrs ?? {})) attrs[STYLE_ID_ATTRIBUTE] = isDefault ? null : id;
7103
+ if ("textAlign" in (type.spec.attrs ?? {})) {
7104
+ const fallback = type.spec.attrs.textAlign.default ?? null;
7105
+ attrs.textAlign = fallback === null ? null : resolved.paragraph.textAlign ?? fallback;
7106
+ }
7107
+ if (node.type === type && Object.entries(attrs).every(([k, v]) => node.attrs[k] === v)) return false;
7108
+ try {
7109
+ tr.setNodeMarkup(pos, type, attrs);
7110
+ } catch {
7111
+ try {
7112
+ tr.setBlockType(pos + 1, pos + 1, type, attrs);
7113
+ } catch {
7114
+ return false;
7115
+ }
7116
+ }
7117
+ return true;
7118
+ }
7119
+ function selectedBlocks(state) {
7120
+ const { from, to } = state.selection;
7121
+ const positions = [];
7122
+ state.doc.nodesBetween(from, to, (node, pos) => {
7123
+ if (node.isTextblock) {
7124
+ positions.push(pos);
7125
+ return false;
7126
+ }
7127
+ return true;
7128
+ });
7129
+ if (positions.length === 0 && state.selection.$from.parent.isTextblock) {
7130
+ positions.push(state.selection.$from.before());
7131
+ }
7132
+ return positions;
7133
+ }
7134
+ function setParagraphStyle(tr, state, id, registry) {
7135
+ const start2 = tr.steps.length;
7136
+ selectedBlocks(state).forEach((pos) => {
7137
+ applyStyleToBlock(tr, tr.mapping.slice(start2).map(pos), id, registry);
7138
+ });
7139
+ }
7140
+ function nextStyleTransaction(transactions, oldState, newState, registry) {
7141
+ if (transactions.length !== 1) return null;
7142
+ const [tr] = transactions;
7143
+ if (!tr.docChanged || tr.getMeta("uiEvent") || tr.getMeta("history$") || tr.getMeta("y-sync$")) {
7144
+ return null;
7145
+ }
7146
+ const old = oldState.selection;
7147
+ const $old = old.$from;
7148
+ if (!old.empty || !$old.parent.isTextblock || $old.parent.type.spec.code) return null;
7149
+ if ($old.parentOffset !== $old.parent.content.size) return null;
7150
+ if (newState.doc.content.size !== oldState.doc.content.size + 2) return null;
7151
+ const $new = newState.selection.$from;
7152
+ if (!newState.selection.empty || !$new.parent.isTextblock || $new.parent.content.size !== 0) return null;
7153
+ const depth = $new.depth;
7154
+ const index = $new.index(depth - 1);
7155
+ if (index === 0 || $new.depth !== $old.depth) return null;
7156
+ const previous = $new.node(depth - 1).child(index - 1);
7157
+ if (!previous.isTextblock || !previous.eq($old.parent)) return null;
7158
+ const style = registry.styleOfBlock($old.parent.type.name, $old.parent.attrs);
7159
+ if (!style) return null;
7160
+ const next = registry.nextStyleId(style);
7161
+ const current = registry.styleOfBlock($new.parent.type.name, $new.parent.attrs);
7162
+ const target = registry.resolve(next).block;
7163
+ const typeMatches = $new.parent.type.name === target.type && Object.entries(target.attrs ?? {}).every(([k, v]) => $new.parent.attrs[k] === v);
7164
+ if (current === next && typeMatches) return null;
7165
+ const out = newState.tr;
7166
+ if (!applyStyleToBlock(out, $new.before(), next, registry)) return null;
7167
+ return out;
7168
+ }
7169
+ var CharacterStyle = Mark5.create({
7170
+ name: CHARACTER_STYLE_MARK_NAME,
7171
+ addAttributes() {
7172
+ return {
7173
+ [STYLE_ID_ATTRIBUTE]: {
7174
+ default: null,
7175
+ parseHTML: (el) => {
7176
+ const id = el.getAttribute("data-char-style");
7177
+ return isValidStyleId(id) ? id : null;
7178
+ },
7179
+ renderHTML: (attrs) => attrs[STYLE_ID_ATTRIBUTE] ? { "data-char-style": attrs[STYLE_ID_ATTRIBUTE] } : {}
7180
+ }
7181
+ };
7182
+ },
7183
+ parseHTML() {
7184
+ return [{ tag: "span[data-char-style]" }];
7185
+ },
7186
+ renderHTML({ HTMLAttributes }) {
7187
+ return ["span", mergeAttributes7(HTMLAttributes), 0];
7188
+ }
7189
+ });
7190
+ var ParagraphStyles = Extension13.create({
7191
+ name: "paragraphStyles",
7192
+ addOptions() {
7193
+ return {
7194
+ registry: null,
7195
+ types: ["paragraph", "heading", "codeBlock"],
7196
+ injectCSS: true,
7197
+ cssScope: ".ProseMirror",
7198
+ cssNonce: void 0,
7199
+ linkedCharacterStyles: true
7200
+ };
7201
+ },
7202
+ addStorage() {
7203
+ return { registry: this.options.registry ?? createStyleRegistry() };
7204
+ },
7205
+ addExtensions() {
7206
+ return [CharacterStyle];
7207
+ },
7208
+ addGlobalAttributes() {
7209
+ return [
7210
+ {
7211
+ types: this.options.types,
7212
+ attributes: {
7213
+ [STYLE_ID_ATTRIBUTE]: {
7214
+ default: null,
7215
+ keepOnSplit: true,
7216
+ parseHTML: (el) => {
7217
+ const id = el.getAttribute("data-style");
7218
+ return isValidStyleId(id) ? id : null;
7219
+ },
7220
+ renderHTML: (attrs) => {
7221
+ const id = attrs[STYLE_ID_ATTRIBUTE];
7222
+ return isValidStyleId(id) ? { "data-style": id, class: `pixerate-style-${id}` } : {};
7223
+ }
7224
+ }
7225
+ }
7226
+ }
7227
+ ];
7228
+ },
7229
+ addCommands() {
7230
+ const registry = () => this.storage.registry;
7231
+ const editor = () => this.editor;
7232
+ return {
7233
+ setParagraphStyle: (id) => ({ state, tr, dispatch }) => {
7234
+ const reg = registry();
7235
+ if (!isParagraphStyleAvailable(editor(), reg, id)) return false;
7236
+ const { $from, $to, empty } = state.selection;
7237
+ const linked = this.options.linkedCharacterStyles ? reg.linkedCharacterStyle(id) : void 0;
7238
+ const partial = !empty && $from.sameParent($to) && $from.parent.isTextblock && ($from.parentOffset > 0 || $to.parentOffset < $from.parent.content.size);
7239
+ const markType = state.schema.marks[CHARACTER_STYLE_MARK_NAME];
7240
+ if (partial && linked && markType) {
7241
+ if (dispatch) {
7242
+ tr.addMark($from.pos, $to.pos, markType.create({ [STYLE_ID_ATTRIBUTE]: linked.id }));
7243
+ }
7244
+ return true;
7245
+ }
7246
+ if (dispatch) setParagraphStyle(tr, state, id, reg);
7247
+ return true;
7248
+ },
7249
+ clearParagraphStyle: () => ({ commands }) => commands.setParagraphStyle(NORMAL_STYLE_ID),
7250
+ setCharacterStyle: (id) => ({ commands }) => {
7251
+ if (registry().get(id)?.kind !== "character") return false;
7252
+ return commands.setMark(CHARACTER_STYLE_MARK_NAME, { [STYLE_ID_ATTRIBUTE]: id });
7253
+ },
7254
+ unsetCharacterStyle: () => ({ commands }) => commands.unsetMark(CHARACTER_STYLE_MARK_NAME),
7255
+ updateParagraphStyleFromSelection: (id) => ({ state, dispatch }) => {
7256
+ const reg = registry();
7257
+ const target = id ?? getParagraphStyleAt(state, reg);
7258
+ const style = target ? reg.get(target) : void 0;
7259
+ if (!style || style.kind !== "paragraph") return false;
7260
+ if (dispatch) {
7261
+ const captured = captureSelectionFormat(state);
7262
+ reg.update(style.id, captured);
7263
+ }
7264
+ return true;
7265
+ },
7266
+ createParagraphStyleFromSelection: (name, options = {}) => ({ state, tr, dispatch }) => {
7267
+ const reg = registry();
7268
+ const trimmed = name.trim();
7269
+ if (!trimmed) return false;
7270
+ const id = options.id ?? reg.newId(trimmed);
7271
+ if (!isValidStyleId(id) || reg.get(id)) return false;
7272
+ const basedOn = options.basedOn === void 0 ? getParagraphStyleAt(state, reg) : options.basedOn;
7273
+ if (dispatch) {
7274
+ const captured = captureSelectionFormat(state);
7275
+ const definition = {
7276
+ id,
7277
+ name: trimmed,
7278
+ kind: "paragraph",
7279
+ basedOn: basedOn ?? NORMAL_STYLE_ID,
7280
+ paragraph: captured.paragraph,
7281
+ character: captured.character,
7282
+ priority: 50,
7283
+ quick: true
7284
+ };
7285
+ reg.upsert(definition);
7286
+ if (options.apply !== false) setParagraphStyle(tr, state, id, reg);
7287
+ }
7288
+ return true;
7289
+ }
7290
+ };
7291
+ },
7292
+ addProseMirrorPlugins() {
7293
+ const registry = this.storage.registry;
7294
+ return [
7295
+ new Plugin15({
7296
+ key: paragraphStylesPluginKey,
7297
+ appendTransaction: (transactions, oldState, newState) => nextStyleTransaction(transactions, oldState, newState, registry)
7298
+ })
7299
+ ];
7300
+ },
7301
+ onBeforeCreate() {
7302
+ if (!this.options.injectCSS || typeof document === "undefined") return;
7303
+ const element = document.createElement("style");
7304
+ element.setAttribute("data-pixerate-paragraph-styles", "");
7305
+ if (this.options.cssNonce) element.nonce = this.options.cssNonce;
7306
+ document.head.appendChild(element);
7307
+ const scope = this.options.cssScope;
7308
+ const unsubscribe = this.storage.registry.subscribe(() => {
7309
+ element.textContent = this.storage.registry.toCSS({ scope });
7310
+ });
7311
+ this.storage.cleanup = () => {
7312
+ unsubscribe();
7313
+ element.remove();
7314
+ };
7315
+ },
7316
+ onDestroy() {
7317
+ this.storage.cleanup?.();
7318
+ }
7319
+ });
7320
+ var EMPTY_STYLES = Object.freeze({
7321
+ gallery: [],
7322
+ styles: [],
7323
+ characterStyles: [],
7324
+ currentStyleId: null,
7325
+ version: -1
7326
+ });
7327
+ function createParagraphStylesStore(editor) {
7328
+ const registry = getStyleRegistry(editor);
7329
+ const listeners = /* @__PURE__ */ new Set();
7330
+ let snapshot = EMPTY_STYLES;
7331
+ let unsubscribeRegistry = null;
7332
+ const compute = () => {
7333
+ if (!editor || editor.isDestroyed || !registry) return EMPTY_STYLES;
7334
+ const { version } = registry.getSnapshot();
7335
+ const currentStyleId = getParagraphStyleAt(editor.state, registry);
7336
+ if (snapshot.version === version && snapshot.currentStyleId === currentStyleId) return snapshot;
7337
+ if (snapshot.version === version) return { ...snapshot, currentStyleId };
7338
+ const styles = registry.list("paragraph").filter((style) => !style.hidden && isParagraphStyleAvailable(editor, registry, style.id));
7339
+ return {
7340
+ gallery: styles.filter((style) => style.quick),
7341
+ styles,
7342
+ characterStyles: registry.list("character").filter((style) => !style.hidden),
7343
+ currentStyleId,
7344
+ version
7345
+ };
7346
+ };
7347
+ const update = () => {
7348
+ const next = compute();
7349
+ if (next !== snapshot) {
7350
+ snapshot = next;
7351
+ listeners.forEach((listener) => listener());
7352
+ }
7353
+ };
7354
+ const detach = () => {
7355
+ editor?.off("transaction", update);
7356
+ unsubscribeRegistry?.();
7357
+ unsubscribeRegistry = null;
7358
+ };
7359
+ return {
7360
+ getSnapshot: () => {
7361
+ if (!listeners.size) snapshot = compute();
7362
+ return snapshot;
7363
+ },
7364
+ subscribe(listener) {
7365
+ if (!listeners.size) {
7366
+ snapshot = compute();
7367
+ editor?.on("transaction", update);
7368
+ unsubscribeRegistry = registry ? registry.subscribe(update) : null;
7369
+ }
7370
+ listeners.add(listener);
7371
+ return () => {
7372
+ listeners.delete(listener);
7373
+ if (!listeners.size) detach();
7374
+ };
7375
+ },
7376
+ destroy() {
7377
+ detach();
7378
+ listeners.clear();
7379
+ }
7380
+ };
7381
+ }
7382
+
7383
+ // src/extensions/rich-text-preset.ts
7384
+ var resolveExt3 = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
7385
+ var StarterKit = resolveExt3(_StarterKit, "StarterKit");
7386
+ var Color = resolveExt3(_Color, "Color");
7387
+ var Highlight = resolveExt3(_Highlight, "Highlight");
7388
+ var Link2 = resolveExt3(_Link, "Link");
7389
+ var TaskList2 = resolveExt3(_TaskList, "TaskList");
7390
+ var TaskItem2 = resolveExt3(_TaskItem, "TaskItem");
7391
+ var TextAlign = resolveExt3(_TextAlign, "TextAlign");
7392
+ var TextStyle = resolveExt3(_TextStyle, "TextStyle");
7393
+ var Typography = resolveExt3(_Typography, "Typography");
7394
+ var Underline = resolveExt3(_Underline, "Underline");
7395
+ var Placeholder = resolveExt3(_Placeholder, "Placeholder");
7396
+ var CharacterCount = resolveExt3(_CharacterCount, "CharacterCount");
7397
+ var optionObject = (value) => typeof value === "object" && value !== null ? value : {};
7398
+ function createRichTextPreset(options = {}) {
7399
+ const {
7400
+ placeholder = "Start typing...",
7401
+ characterLimit,
7402
+ openLinksOnClick = false,
7403
+ enableSmilies = true,
7404
+ enableColorHighlighter = true,
7405
+ headingLevels: headingLevels2 = [1, 2, 3, 4]
7406
+ } = options;
7407
+ const extensions2 = [
7408
+ StarterKit.configure({
7409
+ orderedList: {
7410
+ HTMLAttributes: {
7411
+ class: "list-decimal pl-4"
7412
+ }
7413
+ },
7414
+ bulletList: {
7415
+ HTMLAttributes: {
7416
+ class: "list-disc pl-4"
7417
+ }
7418
+ },
7419
+ heading: {
7420
+ levels: headingLevels2,
7421
+ HTMLAttributes: {
7422
+ class: "font-bold tracking-tight"
7423
+ }
7424
+ }
7425
+ }),
7426
+ TextStyle,
7427
+ Color,
7428
+ Highlight.configure({ multicolor: true }),
7429
+ Underline,
7430
+ ...options.autocorrect ? [] : [Typography],
7431
+ TextAlign.configure({
7432
+ types: ["heading", "paragraph"]
7433
+ }),
7434
+ Link2.extend({
7435
+ addInputRules() {
7436
+ return [
7437
+ markInputRule({
7438
+ find: /(?:^|\s)\[([^\]]+)\]\(([^)]+)\)$/,
7439
+ type: this.type,
7440
+ getAttributes: (match) => ({
7441
+ href: match[2]
7442
+ })
7443
+ })
7444
+ ];
7445
+ }
7446
+ }).configure({
7447
+ openOnClick: openLinksOnClick,
7448
+ autolink: true,
7449
+ defaultProtocol: "https",
7450
+ protocols: ["file", "vscode", "cursor"],
7451
+ HTMLAttributes: {
7452
+ target: "_blank",
7453
+ rel: "noopener noreferrer",
7454
+ class: "text-blue-500 underline underline-offset-2"
7455
+ }
7456
+ }),
7457
+ TaskList2,
7458
+ TaskItem2.extend({
7459
+ addInputRules() {
7460
+ return [
7461
+ wrappingInputRule({
7462
+ find: /^\s*(\[([ |x])?\])\s$/i,
7463
+ type: this.type,
7464
+ getAttributes: (match) => ({
7465
+ checked: match[2]?.toLowerCase() === "x"
7466
+ })
7467
+ })
7468
+ ];
7469
+ }
7470
+ }).configure({
7471
+ nested: true
7472
+ }),
7473
+ FontSize,
7474
+ Placeholder.configure({
7475
+ emptyEditorClass: "is-empty",
7476
+ placeholder: typeof placeholder === "function" ? placeholder : () => placeholder
7477
+ })
7478
+ ];
7479
+ if (characterLimit) {
7480
+ extensions2.push(
7481
+ CharacterCount.configure({
7482
+ limit: characterLimit
7483
+ })
7484
+ );
7485
+ }
7486
+ if (enableSmilies) {
7487
+ extensions2.push(SmilieReplacer);
7488
+ }
7489
+ if (enableColorHighlighter) {
7490
+ extensions2.push(ColorHighlighter);
7491
+ }
7492
+ if (options.mention) {
7493
+ const mentionConfig = typeof options.mention === "object" ? options.mention : {};
5283
7494
  extensions2.push(
5284
7495
  Mention.configure({
5285
7496
  HTMLAttributes: {
@@ -5325,7 +7536,8 @@ function createRichTextPreset(options = {}) {
5325
7536
  }
5326
7537
  if (options.trackChanges) {
5327
7538
  extensions2.push(
5328
- typeof options.trackChanges === "object" ? TrackChanges.configure(options.trackChanges) : TrackChanges
7539
+ typeof options.trackChanges === "object" ? TrackChanges.configure(options.trackChanges) : TrackChanges,
7540
+ DocumentCompare
5329
7541
  );
5330
7542
  }
5331
7543
  if (options.fileImport) {
@@ -5364,9 +7576,152 @@ function createRichTextPreset(options = {}) {
5364
7576
  if (options.crossReferences) {
5365
7577
  extensions2.push(CrossReference.configure(optionObject(options.crossReferences)));
5366
7578
  }
7579
+ if (options.findReplace) {
7580
+ extensions2.push(FindReplace.configure(optionObject(options.findReplace)));
7581
+ }
7582
+ if (options.paragraphStyles) {
7583
+ extensions2.push(ParagraphStyles.configure(optionObject(options.paragraphStyles)));
7584
+ }
5367
7585
  return extensions2;
5368
7586
  }
5369
7587
 
7588
+ // src/extensions/find-replace/commands.ts
7589
+ var OPTION_KEYS = [
7590
+ "matchCase",
7591
+ "wholeWord",
7592
+ "matchPrefix",
7593
+ "matchSuffix",
7594
+ "ignorePunctuation",
7595
+ "ignoreWhitespace",
7596
+ "wildcards",
7597
+ "regex",
7598
+ "literal"
7599
+ ];
7600
+ var searchParams = (withReplacement) => ({
7601
+ type: "object",
7602
+ properties: {
7603
+ query: { type: "string", description: "Find what. Word ^ codes (^p, ^t, ^#) unless literal." },
7604
+ ...withReplacement ? {
7605
+ replacement: {
7606
+ type: "string",
7607
+ description: "Replace with. ^& is the found text; with wildcards, \\1-\\9 are groups."
7608
+ }
7609
+ } : {},
7610
+ ...Object.fromEntries(OPTION_KEYS.map((key) => [key, { type: "boolean" }]))
7611
+ },
7612
+ additionalProperties: false
7613
+ });
7614
+ function splitParams(params) {
7615
+ if (!params) return null;
7616
+ const options = {};
7617
+ for (const key of OPTION_KEYS) if (params[key] !== void 0) options[key] = params[key];
7618
+ return {
7619
+ ...params.query !== void 0 ? { query: params.query } : {},
7620
+ ...params.replacement !== void 0 ? { replacement: params.replacement } : {},
7621
+ options
7622
+ };
7623
+ }
7624
+ var usable = (editor) => !editor.isDestroyed && findReplacePluginKey.getState(editor.state) ? true : "Find and Replace is not available.";
7625
+ function createFindReplaceCommands(editor, options = {}) {
7626
+ const prefix = options.prefix ?? "findReplace.";
7627
+ const group = options.group ?? "Edit";
7628
+ const scope = options.scope ? { scope: options.scope } : {};
7629
+ const state = () => getFindReplaceState(editor);
7630
+ const hasMatches = () => {
7631
+ const ok = usable(editor);
7632
+ if (ok !== true) return ok;
7633
+ return state()?.matches.length ? true : "No matches.";
7634
+ };
7635
+ const apply = (params) => {
7636
+ const split = splitParams(params);
7637
+ if (split) editor.commands.setSearch(split);
7638
+ };
7639
+ const failIfInvalid = () => {
7640
+ const s = state();
7641
+ if (s?.error) throw new Error(s.error.message);
7642
+ };
7643
+ return [
7644
+ {
7645
+ id: `${prefix}open`,
7646
+ label: "Find",
7647
+ group,
7648
+ keywords: ["search", "find"],
7649
+ description: "Shows the find bar.",
7650
+ ...options.shortcuts ? { shortcut: "Mod+F" } : {},
7651
+ ...scope,
7652
+ enabled: () => usable(editor),
7653
+ run: () => editor.commands.openFindReplace({ mode: "find" })
7654
+ },
7655
+ {
7656
+ id: `${prefix}openReplace`,
7657
+ label: "Replace",
7658
+ group,
7659
+ keywords: ["search", "substitute"],
7660
+ description: "Shows the find bar with the replace field.",
7661
+ ...options.shortcuts ? { shortcut: "Mod+H" } : {},
7662
+ ...scope,
7663
+ enabled: () => usable(editor),
7664
+ run: () => editor.commands.openFindReplace({ mode: "replace" })
7665
+ },
7666
+ {
7667
+ id: `${prefix}find`,
7668
+ label: "Find text",
7669
+ group,
7670
+ description: "Searches the document and returns the number of matches.",
7671
+ params: searchParams(false),
7672
+ readOnly: true,
7673
+ ...scope,
7674
+ enabled: () => usable(editor),
7675
+ run: (_ctx, params) => {
7676
+ apply(params);
7677
+ failIfInvalid();
7678
+ const s = state();
7679
+ return {
7680
+ count: s?.matches.length ?? 0,
7681
+ truncated: s?.truncated ?? false,
7682
+ matches: (s?.matches ?? []).slice(0, 200).map(({ from, to, text }) => ({ from, to, text }))
7683
+ };
7684
+ }
7685
+ },
7686
+ {
7687
+ id: `${prefix}next`,
7688
+ label: "Find next",
7689
+ group,
7690
+ ...scope,
7691
+ enabled: hasMatches,
7692
+ run: () => editor.commands.findNext()
7693
+ },
7694
+ {
7695
+ id: `${prefix}previous`,
7696
+ label: "Find previous",
7697
+ group,
7698
+ ...scope,
7699
+ enabled: hasMatches,
7700
+ run: () => editor.commands.findPrevious()
7701
+ },
7702
+ {
7703
+ id: `${prefix}replaceAll`,
7704
+ label: "Replace all",
7705
+ group,
7706
+ description: "Replaces every match (one undo step). Tracked when Track Changes is on.",
7707
+ params: searchParams(true),
7708
+ ...scope,
7709
+ enabled: (_ctx, params) => params?.query !== void 0 ? usable(editor) : hasMatches(),
7710
+ run: (_ctx, params) => {
7711
+ apply(params);
7712
+ failIfInvalid();
7713
+ const count = state()?.matches.length ?? 0;
7714
+ if (count) editor.commands.replaceAll();
7715
+ return { replaced: count };
7716
+ }
7717
+ }
7718
+ ];
7719
+ }
7720
+ function validateFindPattern(query, options = {}) {
7721
+ const result = compileFindPattern(query, options);
7722
+ return result.ok ? null : result.error;
7723
+ }
7724
+
5370
7725
  export {
5371
7726
  GradientText,
5372
7727
  SlashCommands,
@@ -5379,6 +7734,8 @@ export {
5379
7734
  ColorHighlighter,
5380
7735
  FontSize,
5381
7736
  Mention,
7737
+ getMentionNodes,
7738
+ setMentionRoleAt,
5382
7739
  createMentionExtension,
5383
7740
  Markdown2 as Markdown,
5384
7741
  imagePastePluginKey,
@@ -5395,6 +7752,9 @@ export {
5395
7752
  getCommentStore,
5396
7753
  Comments,
5397
7754
  getCommentAuthor,
7755
+ compareInEditor,
7756
+ applyDocumentComparison,
7757
+ DocumentCompare,
5398
7758
  ImportError,
5399
7759
  createImportContext,
5400
7760
  ImportAdapterRegistry,
@@ -5467,6 +7827,42 @@ export {
5467
7827
  CrossReference,
5468
7828
  documentStructureCommandItems,
5469
7829
  getDocumentStructureCommandItems,
5470
- createRichTextPreset
7830
+ FIND_PARAGRAPH_MARK,
7831
+ MAX_FIND_PATTERN_LENGTH,
7832
+ createSearchBudget,
7833
+ foldCodePoint,
7834
+ compileFindPattern,
7835
+ findAllMatches,
7836
+ parseReplacement,
7837
+ expandReplacement,
7838
+ searchDocument,
7839
+ applyReplacements,
7840
+ EMPTY_FIND_REPLACE_SNAPSHOT,
7841
+ FindReplaceStore,
7842
+ findReplacePluginKey,
7843
+ getFindReplaceState,
7844
+ getFindReplaceStore,
7845
+ FIND_OPTION_TOGGLES,
7846
+ formatFindStatus,
7847
+ FindReplace,
7848
+ NORMAL_STYLE_ID,
7849
+ isValidStyleId,
7850
+ DEFAULT_STYLES,
7851
+ StyleRegistry,
7852
+ safeCSSValue,
7853
+ createStyleRegistry,
7854
+ STYLE_ID_ATTRIBUTE,
7855
+ CHARACTER_STYLE_MARK_NAME,
7856
+ paragraphStylesPluginKey,
7857
+ getStyleRegistry,
7858
+ isParagraphStyleAvailable,
7859
+ getParagraphStyleAt,
7860
+ captureSelectionFormat,
7861
+ CharacterStyle,
7862
+ ParagraphStyles,
7863
+ createParagraphStylesStore,
7864
+ createRichTextPreset,
7865
+ createFindReplaceCommands,
7866
+ validateFindPattern
5471
7867
  };
5472
- //# sourceMappingURL=chunk-QGHGJ56O.js.map
7868
+ //# sourceMappingURL=chunk-IXHSYOVY.js.map