quoin-editor 0.4.2 → 1.0.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 (231) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/MIGRATION.md +233 -0
  3. package/README.md +78 -716
  4. package/dist/ai.cjs +1107 -0
  5. package/dist/ai.cjs.map +1 -0
  6. package/dist/ai.d.cts +68 -0
  7. package/dist/ai.d.ts +68 -0
  8. package/dist/ai.js +1107 -0
  9. package/dist/ai.js.map +1 -0
  10. package/dist/catalogue.cjs +59 -0
  11. package/dist/catalogue.cjs.map +1 -0
  12. package/dist/catalogue.d.cts +39 -0
  13. package/dist/catalogue.d.ts +39 -0
  14. package/dist/catalogue.js +59 -0
  15. package/dist/catalogue.js.map +1 -0
  16. package/dist/chunk-2QCRZ7OL.cjs +319 -0
  17. package/dist/chunk-2QCRZ7OL.cjs.map +1 -0
  18. package/dist/chunk-2VDK27IF.cjs +6666 -0
  19. package/dist/chunk-2VDK27IF.cjs.map +1 -0
  20. package/dist/chunk-47MLKSI2.js +326 -0
  21. package/dist/chunk-47MLKSI2.js.map +1 -0
  22. package/dist/chunk-5DNCCN6X.cjs +326 -0
  23. package/dist/chunk-5DNCCN6X.cjs.map +1 -0
  24. package/dist/chunk-AEJXZBVR.cjs +1378 -0
  25. package/dist/chunk-AEJXZBVR.cjs.map +1 -0
  26. package/dist/chunk-CDELOHDY.cjs +239 -0
  27. package/dist/chunk-CDELOHDY.cjs.map +1 -0
  28. package/dist/chunk-E75EHRPA.cjs +175 -0
  29. package/dist/chunk-E75EHRPA.cjs.map +1 -0
  30. package/dist/chunk-EL2OEJQC.cjs +562 -0
  31. package/dist/chunk-EL2OEJQC.cjs.map +1 -0
  32. package/dist/chunk-GLLXHMH2.cjs +25 -0
  33. package/dist/chunk-GLLXHMH2.cjs.map +1 -0
  34. package/dist/chunk-GTTPVJSD.cjs +3286 -0
  35. package/dist/chunk-GTTPVJSD.cjs.map +1 -0
  36. package/dist/chunk-HELJTLNC.cjs +100 -0
  37. package/dist/chunk-HELJTLNC.cjs.map +1 -0
  38. package/dist/chunk-HJIQT3S5.js +4047 -0
  39. package/dist/chunk-HJIQT3S5.js.map +1 -0
  40. package/dist/chunk-HZSSEYJS.cjs +504 -0
  41. package/dist/chunk-HZSSEYJS.cjs.map +1 -0
  42. package/dist/chunk-JDPTZUBW.js +184 -0
  43. package/dist/chunk-JDPTZUBW.js.map +1 -0
  44. package/dist/chunk-JEIFJHEI.js +14 -0
  45. package/dist/chunk-JEIFJHEI.js.map +1 -0
  46. package/dist/chunk-K574XHMM.js +25 -0
  47. package/dist/chunk-K574XHMM.js.map +1 -0
  48. package/dist/chunk-KAVU6T6J.js +249 -0
  49. package/dist/chunk-KAVU6T6J.js.map +1 -0
  50. package/dist/chunk-KXOBZGQO.js +1378 -0
  51. package/dist/chunk-KXOBZGQO.js.map +1 -0
  52. package/dist/chunk-LCQSG4M6.js +3335 -0
  53. package/dist/chunk-LCQSG4M6.js.map +1 -0
  54. package/dist/chunk-MQ2XQ4MX.js +100 -0
  55. package/dist/chunk-MQ2XQ4MX.js.map +1 -0
  56. package/dist/chunk-ND5XJVK2.cjs +3335 -0
  57. package/dist/chunk-ND5XJVK2.cjs.map +1 -0
  58. package/dist/chunk-NNDYYEW5.js +504 -0
  59. package/dist/chunk-NNDYYEW5.js.map +1 -0
  60. package/dist/chunk-NOKWNK2O.js +319 -0
  61. package/dist/chunk-NOKWNK2O.js.map +1 -0
  62. package/dist/chunk-OMOBMXD5.cjs +57 -0
  63. package/dist/chunk-OMOBMXD5.cjs.map +1 -0
  64. package/dist/chunk-PMWCQSQX.js +239 -0
  65. package/dist/chunk-PMWCQSQX.js.map +1 -0
  66. package/dist/chunk-POBM7MGR.js +30 -0
  67. package/dist/chunk-POBM7MGR.js.map +1 -0
  68. package/dist/chunk-PWUEPDA3.cjs +598 -0
  69. package/dist/chunk-PWUEPDA3.cjs.map +1 -0
  70. package/dist/chunk-PZ4GT5JO.cjs +4047 -0
  71. package/dist/chunk-PZ4GT5JO.cjs.map +1 -0
  72. package/dist/chunk-QAW5NH6S.cjs +14 -0
  73. package/dist/chunk-QAW5NH6S.cjs.map +1 -0
  74. package/dist/chunk-RDG4VI6E.cjs +706 -0
  75. package/dist/chunk-RDG4VI6E.cjs.map +1 -0
  76. package/dist/chunk-RUDWMKYX.js +562 -0
  77. package/dist/chunk-RUDWMKYX.js.map +1 -0
  78. package/dist/chunk-SG37UBDO.js +706 -0
  79. package/dist/chunk-SG37UBDO.js.map +1 -0
  80. package/dist/chunk-SLVDAHHK.cjs +30 -0
  81. package/dist/chunk-SLVDAHHK.cjs.map +1 -0
  82. package/dist/chunk-T7SW6OWV.js +598 -0
  83. package/dist/chunk-T7SW6OWV.js.map +1 -0
  84. package/dist/chunk-TC4DARU2.js +175 -0
  85. package/dist/chunk-TC4DARU2.js.map +1 -0
  86. package/dist/chunk-WQHV3MRE.cjs +184 -0
  87. package/dist/chunk-WQHV3MRE.cjs.map +1 -0
  88. package/dist/chunk-WXMYDXPN.cjs +33 -0
  89. package/dist/chunk-WXMYDXPN.cjs.map +1 -0
  90. package/dist/chunk-XEBWC7ZL.js +57 -0
  91. package/dist/chunk-XEBWC7ZL.js.map +1 -0
  92. package/dist/chunk-ZC24WB7Y.cjs +249 -0
  93. package/dist/chunk-ZC24WB7Y.cjs.map +1 -0
  94. package/dist/chunk-ZGZEJCRV.js +6666 -0
  95. package/dist/chunk-ZGZEJCRV.js.map +1 -0
  96. package/dist/chunk-ZHOSSWUM.js +33 -0
  97. package/dist/chunk-ZHOSSWUM.js.map +1 -0
  98. package/dist/chunk-ZS2WA357.js +3286 -0
  99. package/dist/chunk-ZS2WA357.js.map +1 -0
  100. package/dist/comments.cjs +993 -0
  101. package/dist/comments.cjs.map +1 -0
  102. package/dist/comments.d.cts +88 -0
  103. package/dist/comments.d.ts +88 -0
  104. package/dist/comments.js +993 -0
  105. package/dist/comments.js.map +1 -0
  106. package/dist/diff.cjs +343 -0
  107. package/dist/diff.cjs.map +1 -0
  108. package/dist/diff.d.cts +67 -0
  109. package/dist/diff.d.ts +67 -0
  110. package/dist/diff.js +343 -0
  111. package/dist/diff.js.map +1 -0
  112. package/dist/docx.cjs +854 -0
  113. package/dist/docx.cjs.map +1 -0
  114. package/dist/docx.d.cts +27 -0
  115. package/dist/docx.d.ts +27 -0
  116. package/dist/docx.js +854 -0
  117. package/dist/docx.js.map +1 -0
  118. package/dist/embed-provider-jXwlRmnp.d.cts +19 -0
  119. package/dist/embed-provider-jXwlRmnp.d.ts +19 -0
  120. package/dist/embed.cjs +17 -0
  121. package/dist/embed.cjs.map +1 -0
  122. package/dist/embed.d.cts +56 -0
  123. package/dist/embed.d.ts +56 -0
  124. package/dist/embed.js +17 -0
  125. package/dist/embed.js.map +1 -0
  126. package/dist/emoji-data.cjs +1968 -0
  127. package/dist/emoji-data.cjs.map +1 -0
  128. package/dist/emoji-data.js +1968 -0
  129. package/dist/emoji-data.js.map +1 -0
  130. package/dist/emoji-extension-C12gDPm6.d.cts +46 -0
  131. package/dist/emoji-extension-DpQ43HjE.d.ts +46 -0
  132. package/dist/emoji.cjs +18 -0
  133. package/dist/emoji.cjs.map +1 -0
  134. package/dist/emoji.d.cts +31 -0
  135. package/dist/emoji.d.ts +31 -0
  136. package/dist/emoji.js +18 -0
  137. package/dist/emoji.js.map +1 -0
  138. package/dist/engine.cjs +119 -0
  139. package/dist/engine.cjs.map +1 -0
  140. package/dist/engine.d.cts +144 -0
  141. package/dist/engine.d.ts +144 -0
  142. package/dist/engine.js +119 -0
  143. package/dist/engine.js.map +1 -0
  144. package/dist/find-extension-DKqLLSuP.d.ts +41 -0
  145. package/dist/find-extension-_sbZEt3R.d.cts +41 -0
  146. package/dist/find.cjs +20 -0
  147. package/dist/find.cjs.map +1 -0
  148. package/dist/find.d.cts +44 -0
  149. package/dist/find.d.ts +44 -0
  150. package/dist/find.js +20 -0
  151. package/dist/find.js.map +1 -0
  152. package/dist/headless.cjs +151 -518
  153. package/dist/headless.cjs.map +1 -1
  154. package/dist/headless.d.cts +102 -69
  155. package/dist/headless.d.ts +102 -69
  156. package/dist/headless.js +149 -467
  157. package/dist/headless.js.map +1 -1
  158. package/dist/index-CgD72kHB.d.cts +65 -0
  159. package/dist/index-DNYbTK3w.d.ts +65 -0
  160. package/dist/index.cjs +183 -2753
  161. package/dist/index.cjs.map +1 -1
  162. package/dist/index.d.cts +345 -253
  163. package/dist/index.d.ts +345 -253
  164. package/dist/index.js +168 -2696
  165. package/dist/index.js.map +1 -1
  166. package/dist/math.cjs +18 -0
  167. package/dist/math.cjs.map +1 -0
  168. package/dist/math.d.cts +49 -0
  169. package/dist/math.d.ts +49 -0
  170. package/dist/math.js +18 -0
  171. package/dist/math.js.map +1 -0
  172. package/dist/mention.cjs +18 -0
  173. package/dist/mention.cjs.map +1 -0
  174. package/dist/mention.d.cts +50 -0
  175. package/dist/mention.d.ts +50 -0
  176. package/dist/mention.js +18 -0
  177. package/dist/mention.js.map +1 -0
  178. package/dist/presence.cjs +565 -0
  179. package/dist/presence.cjs.map +1 -0
  180. package/dist/presence.d.cts +30 -0
  181. package/dist/presence.d.ts +30 -0
  182. package/dist/presence.js +565 -0
  183. package/dist/presence.js.map +1 -0
  184. package/dist/registry-CWBOSAHm.d.cts +121 -0
  185. package/dist/registry-CWBOSAHm.d.ts +121 -0
  186. package/dist/stats-extension-Bly88uio.d.ts +40 -0
  187. package/dist/stats-extension-CJPijvUb.d.cts +40 -0
  188. package/dist/stats.cjs +19 -0
  189. package/dist/stats.cjs.map +1 -0
  190. package/dist/stats.d.cts +19 -0
  191. package/dist/stats.d.ts +19 -0
  192. package/dist/stats.js +19 -0
  193. package/dist/stats.js.map +1 -0
  194. package/dist/styles.css +1 -1
  195. package/dist/table-extension-BTLxFy2S.d.cts +57 -0
  196. package/dist/table-extension-Dzf4k6hI.d.ts +57 -0
  197. package/dist/table.cjs +50 -0
  198. package/dist/table.cjs.map +1 -0
  199. package/dist/table.d.cts +57 -0
  200. package/dist/table.d.ts +57 -0
  201. package/dist/table.js +50 -0
  202. package/dist/table.js.map +1 -0
  203. package/dist/to-html-C5t7xyaM.d.ts +21 -0
  204. package/dist/to-html-CNCITCln.d.cts +21 -0
  205. package/dist/toc-extension-B3-s6h2x.d.cts +25 -0
  206. package/dist/toc-extension-DWm5alVj.d.ts +25 -0
  207. package/dist/toc.cjs +18 -0
  208. package/dist/toc.cjs.map +1 -0
  209. package/dist/toc.d.cts +24 -0
  210. package/dist/toc.d.ts +24 -0
  211. package/dist/toc.js +18 -0
  212. package/dist/toc.js.map +1 -0
  213. package/dist/types-Cz7XJLaK.d.cts +366 -0
  214. package/dist/types-DULc8wwg.d.cts +120 -0
  215. package/dist/types-DULc8wwg.d.ts +120 -0
  216. package/dist/types-a1CRBlEh.d.ts +366 -0
  217. package/dist/view-controller-DSHw0ltQ.d.cts +118 -0
  218. package/dist/view-controller-UbpzblGl.d.ts +118 -0
  219. package/dist/view.cjs +23 -0
  220. package/dist/view.cjs.map +1 -0
  221. package/dist/view.d.cts +40 -0
  222. package/dist/view.d.ts +40 -0
  223. package/dist/view.js +23 -0
  224. package/dist/view.js.map +1 -0
  225. package/dist/yjs.cjs +1026 -0
  226. package/dist/yjs.cjs.map +1 -0
  227. package/dist/yjs.d.cts +22 -0
  228. package/dist/yjs.d.ts +22 -0
  229. package/dist/yjs.js +1026 -0
  230. package/dist/yjs.js.map +1 -0
  231. package/package.json +217 -18
@@ -0,0 +1,4047 @@
1
+ import {
2
+ sameValue
3
+ } from "./chunk-NOKWNK2O.js";
4
+ import {
5
+ ALERT_TONES,
6
+ MAX_MARKER_NUMBER,
7
+ PALETTE,
8
+ addMark,
9
+ addMarkRange,
10
+ atom,
11
+ blockById,
12
+ blockPlainText,
13
+ cellKey,
14
+ compareRegionNames,
15
+ coreAtomSpecs,
16
+ coreBlockSpecs,
17
+ coreMarkSpecs,
18
+ createBlockCodec,
19
+ createDoc,
20
+ createSchema,
21
+ documentNodes,
22
+ engineBlocksOf,
23
+ equalInline,
24
+ fromMarkdown,
25
+ hasMark,
26
+ indexOfBlock,
27
+ inlineLength,
28
+ inlinePlainText,
29
+ inlineText,
30
+ isPaletteColor,
31
+ isQuoinType,
32
+ isRecord,
33
+ linkClose,
34
+ marksAt,
35
+ newId,
36
+ nodeLength,
37
+ normalizeInline,
38
+ orderedStart,
39
+ parseInline,
40
+ parseLine,
41
+ removeMark,
42
+ removeMarkRange,
43
+ replaceBlocks,
44
+ replaceInline,
45
+ requireBlockIndex,
46
+ sameItems,
47
+ sameMark,
48
+ sameMarkSet,
49
+ sanitizeHref,
50
+ serializeInline,
51
+ sliceInline,
52
+ specFor,
53
+ startsHtmlBlock,
54
+ str,
55
+ text,
56
+ updateBlock,
57
+ writtenAttrs
58
+ } from "./chunk-LCQSG4M6.js";
59
+
60
+ // src/engine/transform/step.ts
61
+ var StepError = class extends Error {
62
+ };
63
+ var identityMap = (pos) => pos;
64
+
65
+ // src/engine/transform/step-access.ts
66
+ function requireBlock(doc, id) {
67
+ const block = blockById(doc, id);
68
+ if (block === void 0) throw new StepError(`no block "${id}"`);
69
+ return block;
70
+ }
71
+ function requireRegion(block, region) {
72
+ if (!Object.hasOwn(block.regions, region)) throw new StepError(`block "${block.id}" has no region "${region}"`);
73
+ return block.regions[region];
74
+ }
75
+ function requireRange(block, region, from, to) {
76
+ const inline = requireRegion(block, region);
77
+ const length = inlineLength(inline);
78
+ if (!(Number.isInteger(from) && Number.isInteger(to) && from >= 0 && from <= to && to <= length)) {
79
+ throw new StepError(`range ${from} to ${to} is outside 0 to ${length} in "${block.id}" ${region}`);
80
+ }
81
+ return inline;
82
+ }
83
+ function requireSpan(start, count2, length) {
84
+ if (!(Number.isInteger(start) && Number.isInteger(count2) && start >= 0 && count2 >= 0 && start + count2 <= length)) {
85
+ throw new StepError(`blocks ${start} to ${start + count2} are outside 0 to ${length}`);
86
+ }
87
+ }
88
+ function withRegion(block, region, inline) {
89
+ return { ...block, regions: { ...block.regions, [region]: inline } };
90
+ }
91
+
92
+ // src/engine/transform/attrs-steps.ts
93
+ function patchAttrs(attrs, patch) {
94
+ const keys = /* @__PURE__ */ new Set([...Object.keys(attrs), ...Object.keys(patch)]);
95
+ return Object.fromEntries(
96
+ [...keys].filter((key) => !(Object.hasOwn(patch, key) && patch[key] === void 0)).map((key) => [key, Object.hasOwn(patch, key) ? patch[key] : attrs[key]])
97
+ );
98
+ }
99
+ function patchChangesAttrs(attrs, patch) {
100
+ return Object.keys(patch).some(
101
+ (key) => patch[key] === void 0 ? Object.hasOwn(attrs, key) : !sameValue(attrs[key], patch[key])
102
+ );
103
+ }
104
+ function previousValues(attrs, patch) {
105
+ return Object.fromEntries(
106
+ Object.keys(patch).map((key) => [key, Object.hasOwn(attrs, key) ? attrs[key] : void 0])
107
+ );
108
+ }
109
+ function patchToJSON(patch) {
110
+ return Object.fromEntries(
111
+ Object.entries(patch).map(([key, value]) => [key, value === void 0 ? { $delete: true } : value])
112
+ );
113
+ }
114
+ function patchFromJSON(json) {
115
+ const isMarker = (value) => typeof value === "object" && value !== null && value.$delete === true;
116
+ return Object.fromEntries(Object.entries(json).map(([key, value]) => [key, isMarker(value) ? void 0 : value]));
117
+ }
118
+ function setAttrsStep(block, patch) {
119
+ return Object.freeze({
120
+ stepType: "setAttrs",
121
+ apply(doc) {
122
+ requireBlock(doc, block);
123
+ return { doc: updateBlock(doc, block, (b) => ({ ...b, attrs: patchAttrs(b.attrs, patch) })), map: identityMap };
124
+ },
125
+ invert: (before) => setAttrsStep(block, previousValues(requireBlock(before, block).attrs, patch)),
126
+ toJSON: () => ({ stepType: "setAttrs", block, patch: patchToJSON(patch) })
127
+ });
128
+ }
129
+ function setIndentStep(block, indent2) {
130
+ return Object.freeze({
131
+ stepType: "setIndent",
132
+ apply(doc) {
133
+ requireBlock(doc, block);
134
+ if (!Number.isInteger(indent2) || indent2 < 0) {
135
+ throw new StepError(`indent ${indent2} is not a non-negative integer`);
136
+ }
137
+ return { doc: updateBlock(doc, block, (b) => ({ ...b, indent: indent2 })), map: identityMap };
138
+ },
139
+ invert: (before) => setIndentStep(block, requireBlock(before, block).indent),
140
+ toJSON: () => ({ stepType: "setIndent", block, indent: indent2 })
141
+ });
142
+ }
143
+
144
+ // src/engine/transform/block-edges.ts
145
+ function orderedRegions(block) {
146
+ return Object.keys(block.regions).sort(compareRegionNames);
147
+ }
148
+ function firstRegion(block) {
149
+ return orderedRegions(block)[0] ?? null;
150
+ }
151
+ function blockStart(block) {
152
+ return { block: block.id, region: firstRegion(block) ?? "", offset: 0 };
153
+ }
154
+ function blockEnd(block) {
155
+ const region = orderedRegions(block).at(-1);
156
+ return {
157
+ block: block.id,
158
+ region: region ?? "",
159
+ offset: region === void 0 ? 0 : inlineLength(block.regions[region])
160
+ };
161
+ }
162
+ function clampPos(block, pos) {
163
+ if (!Object.hasOwn(block.regions, pos.region)) return blockStart(block);
164
+ const length = inlineLength(block.regions[pos.region]);
165
+ return pos.offset > length ? { ...pos, offset: length } : pos;
166
+ }
167
+
168
+ // src/engine/transform/block-steps.ts
169
+ function insertBlocksStep(index, blocks) {
170
+ return Object.freeze({
171
+ stepType: "insertBlocks",
172
+ apply(doc) {
173
+ requireSpan(index, 0, doc.blocks.length);
174
+ const ids = /* @__PURE__ */ new Set();
175
+ for (const { id } of blocks) {
176
+ if (doc.index.has(id) || ids.has(id)) throw new StepError(`block "${id}" is already in the document`);
177
+ ids.add(id);
178
+ }
179
+ return { doc: replaceBlocks(doc, index, 0, blocks), map: identityMap };
180
+ },
181
+ invert: () => removeBlocksStep(index, blocks.length),
182
+ toJSON: () => ({ stepType: "insertBlocks", index, blocks })
183
+ });
184
+ }
185
+ function removeBlocksStep(index, count2) {
186
+ return Object.freeze({
187
+ stepType: "removeBlocks",
188
+ apply(doc) {
189
+ requireSpan(index, count2, doc.blocks.length);
190
+ const removed = new Set(doc.blocks.slice(index, index + count2).map((block) => block.id));
191
+ const preceding = doc.blocks[index - 1];
192
+ const following = doc.blocks[index + count2];
193
+ const map = (pos, assoc = 1) => {
194
+ if (!removed.has(pos.block)) return pos;
195
+ const before = preceding && blockEnd(preceding);
196
+ const after = following && blockStart(following);
197
+ return (assoc === -1 ? before ?? after : after ?? before) ?? pos;
198
+ };
199
+ return { doc: replaceBlocks(doc, index, count2, []), map };
200
+ },
201
+ invert: (before) => insertBlocksStep(index, before.blocks.slice(index, index + count2)),
202
+ toJSON: () => ({ stepType: "removeBlocks", index, count: count2 })
203
+ });
204
+ }
205
+ function moveBlocksStep(from, count2, to) {
206
+ return Object.freeze({
207
+ stepType: "moveBlocks",
208
+ apply(doc) {
209
+ requireSpan(from, count2, doc.blocks.length);
210
+ requireSpan(to, count2, doc.blocks.length);
211
+ const moved2 = doc.blocks.slice(from, from + count2);
212
+ const rest = [...doc.blocks.slice(0, from), ...doc.blocks.slice(from + count2)];
213
+ return { doc: createDoc([...rest.slice(0, to), ...moved2, ...rest.slice(to)]), map: identityMap };
214
+ },
215
+ invert: () => moveBlocksStep(to, count2, from),
216
+ toJSON: () => ({ stepType: "moveBlocks", from, count: count2, to })
217
+ });
218
+ }
219
+ var keepsRegions = (before, after) => Object.entries(before.regions).every(
220
+ ([name, inline]) => Object.hasOwn(after.regions, name) && equalInline(inline, after.regions[name])
221
+ );
222
+ function replaceBlockStep(block) {
223
+ const clamp = (pos) => pos.block === block.id ? clampPos(block, pos) : pos;
224
+ return Object.freeze({
225
+ stepType: "replaceBlock",
226
+ apply(doc) {
227
+ const map = keepsRegions(requireBlock(doc, block.id), block) ? identityMap : clamp;
228
+ return { doc: updateBlock(doc, block.id, () => block), map };
229
+ },
230
+ invert: (before) => replaceBlockStep(requireBlock(before, block.id)),
231
+ toJSON: () => ({ stepType: "replaceBlock", block })
232
+ });
233
+ }
234
+
235
+ // src/engine/transform/replace-inline-step.ts
236
+ function replaceInlineStep(block, region, from, to, slice) {
237
+ const inserted = inlineLength(slice);
238
+ const map = (pos, assoc = 1) => {
239
+ if (pos.block !== block || pos.region !== region || pos.offset < from) return pos;
240
+ if (pos.offset > to) return { ...pos, offset: pos.offset + inserted - (to - from) };
241
+ return { ...pos, offset: assoc === -1 ? from : from + inserted };
242
+ };
243
+ return Object.freeze({
244
+ stepType: "replaceInline",
245
+ apply(doc) {
246
+ const inline = requireRange(requireBlock(doc, block), region, from, to);
247
+ const next = updateBlock(doc, block, (b) => withRegion(b, region, replaceInline(inline, from, to, slice)));
248
+ return { doc: next, map };
249
+ },
250
+ invert(before) {
251
+ const inline = requireRange(requireBlock(before, block), region, from, to);
252
+ return replaceInlineStep(block, region, from, from + inserted, sliceInline(inline, from, to));
253
+ },
254
+ toJSON: () => ({ stepType: "replaceInline", block, region, from, to, slice })
255
+ });
256
+ }
257
+
258
+ // src/engine/transform/mark-steps.ts
259
+ function markStep(stepType, target, mark, change) {
260
+ const { block, region, from, to } = target;
261
+ return Object.freeze({
262
+ stepType,
263
+ apply(doc) {
264
+ const inline = requireRange(requireBlock(doc, block), region, from, to);
265
+ return { doc: updateBlock(doc, block, (b) => withRegion(b, region, change(inline))), map: identityMap };
266
+ },
267
+ invert(before) {
268
+ const inline = requireRange(requireBlock(before, block), region, from, to);
269
+ return replaceInlineStep(block, region, from, to, sliceInline(inline, from, to));
270
+ },
271
+ toJSON: () => ({ stepType, ...target, mark })
272
+ });
273
+ }
274
+ function addMarkStep(block, region, from, to, mark) {
275
+ return markStep("addMark", { block, region, from, to }, mark, (inline) => addMarkRange(inline, from, to, mark));
276
+ }
277
+ function removeMarkStep(block, region, from, to, mark) {
278
+ return markStep(
279
+ "removeMark",
280
+ { block, region, from, to },
281
+ mark,
282
+ (inline) => removeMarkRange(inline, from, to, mark)
283
+ );
284
+ }
285
+
286
+ // src/engine/model/common-ends.ts
287
+ var isHighSurrogate = (code) => code >= 55296 && code <= 56319;
288
+ var isLowSurrogate = (code) => code >= 56320 && code <= 57343;
289
+ function commonPrefix(a, b) {
290
+ const limit = Math.min(a.length, b.length);
291
+ let length = 0;
292
+ while (length < limit && a.charCodeAt(length) === b.charCodeAt(length)) length++;
293
+ return length > 0 && isHighSurrogate(a.charCodeAt(length - 1)) ? length - 1 : length;
294
+ }
295
+ function commonSuffix(a, b) {
296
+ const limit = Math.min(a.length, b.length);
297
+ let length = 0;
298
+ while (length < limit && a.charCodeAt(a.length - 1 - length) === b.charCodeAt(b.length - 1 - length)) length++;
299
+ return length > 0 && isLowSurrogate(a.charCodeAt(a.length - length)) ? length - 1 : length;
300
+ }
301
+
302
+ // src/engine/transform/map-text-change.ts
303
+ function mapTextChange(before, after) {
304
+ const from = commonPrefix(before, after);
305
+ const inserted = from + commonSuffix(before, after) >= before.length;
306
+ return (offset, assoc) => offset > from || offset === from && inserted && assoc > 0 ? Math.max(offset + after.length - before.length, from) : offset;
307
+ }
308
+
309
+ // src/engine/transform/replace-doc-step.ts
310
+ function replaceDocStep(next) {
311
+ return Object.freeze({
312
+ stepType: "replaceDoc",
313
+ apply(doc) {
314
+ const regionMaps = /* @__PURE__ */ new Map();
315
+ const map = (pos, assoc = 1) => {
316
+ const { block, region } = pos;
317
+ const survivor = blockById(next, block);
318
+ if (survivor !== void 0) {
319
+ const key = JSON.stringify([block, region]);
320
+ let regionMap = regionMaps.get(key);
321
+ if (regionMap === void 0) {
322
+ const before = inlineText(blockById(doc, block)?.regions[region] ?? []);
323
+ regionMap = mapTextChange(before, inlineText(survivor.regions[region] ?? []));
324
+ regionMaps.set(key, regionMap);
325
+ }
326
+ const offset = regionMap(pos.offset, assoc);
327
+ return clampPos(survivor, offset === pos.offset ? pos : { ...pos, offset });
328
+ }
329
+ const nearest = next.blocks[Math.min(doc.index.get(block) ?? 0, next.blocks.length - 1)];
330
+ return nearest === void 0 ? pos : blockStart(nearest);
331
+ };
332
+ return { doc: next, map };
333
+ },
334
+ invert: (before) => replaceDocStep(before),
335
+ toJSON: () => ({ stepType: "replaceDoc", blocks: next.blocks })
336
+ });
337
+ }
338
+
339
+ // src/engine/transform/split-join-steps.ts
340
+ function splitBlockStep(block, region, offset, created) {
341
+ const map = (pos, assoc = 1) => {
342
+ if (pos.block !== block || pos.region !== region) return pos;
343
+ if (pos.offset < offset || pos.offset === offset && assoc === -1) return pos;
344
+ return { block: created.id, region: created.region, offset: pos.offset - offset };
345
+ };
346
+ return Object.freeze({
347
+ stepType: "splitBlock",
348
+ apply(doc) {
349
+ const target = requireBlock(doc, block);
350
+ const inline = requireRange(target, region, offset, offset);
351
+ if (doc.index.has(created.id)) throw new StepError(`block "${created.id}" is already in the document`);
352
+ const tail = {
353
+ id: created.id,
354
+ type: created.type,
355
+ indent: created.indent,
356
+ attrs: created.attrs,
357
+ regions: { ...created.regions, [created.region]: sliceInline(inline, offset, inlineLength(inline)) }
358
+ };
359
+ const head = withRegion(target, region, sliceInline(inline, 0, offset));
360
+ return { doc: replaceBlocks(doc, indexOfBlock(doc, block), 1, [head, tail]), map };
361
+ },
362
+ invert: () => joinBlocksStep(block, region, created.id, created.region),
363
+ toJSON: () => ({ stepType: "splitBlock", block, region, offset, created })
364
+ });
365
+ }
366
+ function joinBlocksStep(target, targetRegion, source, sourceRegion) {
367
+ return Object.freeze({
368
+ stepType: "joinBlocks",
369
+ apply(doc) {
370
+ const first = requireBlock(doc, target);
371
+ const second = requireBlock(doc, source);
372
+ const index = indexOfBlock(doc, target);
373
+ if (indexOfBlock(doc, source) !== index + 1) throw new StepError(`block "${source}" does not follow "${target}"`);
374
+ const head = requireRegion(first, targetRegion);
375
+ const length = inlineLength(head);
376
+ const merged = replaceInline(head, length, length, requireRegion(second, sourceRegion));
377
+ const map = (pos) => pos.block === source ? { block: target, region: targetRegion, offset: length + (pos.region === sourceRegion ? pos.offset : 0) } : pos;
378
+ return { doc: replaceBlocks(doc, index, 2, [withRegion(first, targetRegion, merged)]), map };
379
+ },
380
+ invert(before) {
381
+ const { id, type, attrs, indent: indent2, regions } = requireBlock(before, source);
382
+ const length = inlineLength(requireRegion(requireBlock(before, target), targetRegion));
383
+ const created = { id, type, attrs, indent: indent2, region: sourceRegion, regions };
384
+ return splitBlockStep(target, targetRegion, length, created);
385
+ },
386
+ toJSON: () => ({ stepType: "joinBlocks", target, targetRegion, source, sourceRegion })
387
+ });
388
+ }
389
+
390
+ // src/engine/transform/step-json.ts
391
+ var isRegions = (value) => isRecord(value) && Object.values(value).every(Array.isArray);
392
+ var isMark = (value) => isRecord(value) && typeof value.type === "string" && (value.attrs === void 0 || isRecord(value.attrs));
393
+ var isBlockShape = (value) => isRecord(value) && typeof value.id === "string" && typeof value.type === "string" && Number.isInteger(value.indent) && isRecord(value.attrs);
394
+ var isBlock = (value) => isBlockShape(value) && isRegions(value.regions);
395
+ var isCreated = (value) => isBlockShape(value) && typeof value.region === "string" && (value.regions === void 0 || isRegions(value.regions));
396
+ var isString = (value) => typeof value === "string";
397
+ var isInteger = (value) => Number.isInteger(value);
398
+ var isInline = (value) => Array.isArray(value);
399
+ var isMarkOrType = (value) => isString(value) || isMark(value);
400
+ var areBlocks = (value) => Array.isArray(value) && value.every(isBlock);
401
+ function docOf(blocks) {
402
+ if (new Set(blocks.map((block) => block.id)).size !== blocks.length) {
403
+ throw new StepError("a replaceDoc step repeats a block id");
404
+ }
405
+ return createDoc(blocks);
406
+ }
407
+ function reader(json) {
408
+ const read = (key, accept) => {
409
+ const value = json[key];
410
+ if (!accept(value)) throw new StepError(`invalid "${key}" in a ${json.stepType} step`);
411
+ return value;
412
+ };
413
+ return {
414
+ string: (key) => read(key, isString),
415
+ integer: (key) => read(key, isInteger),
416
+ inline: (key) => read(key, isInline),
417
+ record: (key) => read(key, isRecord),
418
+ mark: (key) => read(key, isMark),
419
+ markOrType: (key) => read(key, isMarkOrType),
420
+ block: (key) => read(key, isBlock),
421
+ blocks: (key) => read(key, areBlocks),
422
+ created: (key) => read(key, isCreated)
423
+ };
424
+ }
425
+ function stepFromJSON(json) {
426
+ const get = reader(json);
427
+ switch (json.stepType) {
428
+ case "replaceInline":
429
+ return replaceInlineStep(
430
+ get.string("block"),
431
+ get.string("region"),
432
+ get.integer("from"),
433
+ get.integer("to"),
434
+ get.inline("slice")
435
+ );
436
+ case "addMark":
437
+ return addMarkStep(
438
+ get.string("block"),
439
+ get.string("region"),
440
+ get.integer("from"),
441
+ get.integer("to"),
442
+ get.mark("mark")
443
+ );
444
+ case "removeMark":
445
+ return removeMarkStep(
446
+ get.string("block"),
447
+ get.string("region"),
448
+ get.integer("from"),
449
+ get.integer("to"),
450
+ get.markOrType("mark")
451
+ );
452
+ case "setAttrs":
453
+ return setAttrsStep(get.string("block"), patchFromJSON(get.record("patch")));
454
+ case "setIndent":
455
+ return setIndentStep(get.string("block"), get.integer("indent"));
456
+ case "insertBlocks":
457
+ return insertBlocksStep(get.integer("index"), get.blocks("blocks"));
458
+ case "removeBlocks":
459
+ return removeBlocksStep(get.integer("index"), get.integer("count"));
460
+ case "moveBlocks":
461
+ return moveBlocksStep(get.integer("from"), get.integer("count"), get.integer("to"));
462
+ case "replaceBlock":
463
+ return replaceBlockStep(get.block("block"));
464
+ case "replaceDoc":
465
+ return replaceDocStep(docOf(get.blocks("blocks")));
466
+ case "splitBlock":
467
+ return splitBlockStep(get.string("block"), get.string("region"), get.integer("offset"), get.created("created"));
468
+ case "joinBlocks":
469
+ return joinBlocksStep(
470
+ get.string("target"),
471
+ get.string("targetRegion"),
472
+ get.string("source"),
473
+ get.string("sourceRegion")
474
+ );
475
+ default:
476
+ throw new StepError(`unknown step type "${json.stepType}"`);
477
+ }
478
+ }
479
+
480
+ // src/engine/model/position.ts
481
+ function comparePos(doc, a, b) {
482
+ return requireBlockIndex(doc, a.block) - requireBlockIndex(doc, b.block) || compareRegionNames(a.region, b.region) || a.offset - b.offset;
483
+ }
484
+ function orderedRange(doc, anchor, head) {
485
+ return comparePos(doc, anchor, head) <= 0 ? { from: anchor, to: head } : { from: head, to: anchor };
486
+ }
487
+
488
+ // src/engine/state/selection.ts
489
+ function textSelection(anchor, head = anchor) {
490
+ return { kind: "text", anchor, head };
491
+ }
492
+ function nodeSelection(block) {
493
+ return { kind: "node", block };
494
+ }
495
+ function blockRangeSelection(anchor, head = anchor) {
496
+ return { kind: "blocks", anchor, head };
497
+ }
498
+ var samePos = (a, b) => a.block === b.block && a.region === b.region && a.offset === b.offset;
499
+ function isCollapsed(selection) {
500
+ return selection?.kind === "text" && samePos(selection.anchor, selection.head);
501
+ }
502
+ function sameSelection(a, b) {
503
+ if (a === b) return true;
504
+ if (a === null || b === null) return false;
505
+ switch (a.kind) {
506
+ case "text":
507
+ return b.kind === "text" && samePos(a.anchor, b.anchor) && samePos(a.head, b.head);
508
+ case "node":
509
+ return b.kind === "node" && a.block === b.block;
510
+ case "blocks":
511
+ return b.kind === "blocks" && a.anchor === b.anchor && a.head === b.head;
512
+ }
513
+ }
514
+ function selectionRange(doc, selection) {
515
+ return selection?.kind === "text" ? orderedRange(doc, selection.anchor, selection.head) : null;
516
+ }
517
+ function selectedBlockIds(doc, selection) {
518
+ if (selection === null) return [];
519
+ if (selection.kind === "node") return [selection.block];
520
+ const ends = selection.kind === "text" ? [selection.anchor.block, selection.head.block] : [selection.anchor, selection.head];
521
+ const [first, second] = ends.map((id) => requireBlockIndex(doc, id));
522
+ return doc.blocks.slice(Math.min(first, second), Math.max(first, second) + 1).map((block) => block.id);
523
+ }
524
+ function isBackward(doc, selection) {
525
+ if (selection.kind === "text") {
526
+ const { anchor, head } = selection;
527
+ return doc.index.has(anchor.block) && doc.index.has(head.block) && comparePos(doc, anchor, head) > 0;
528
+ }
529
+ return doc.index.has(selection.anchor) && doc.index.has(selection.head) && indexOfBlock(doc, selection.anchor) > indexOfBlock(doc, selection.head);
530
+ }
531
+ var blockProbe = (block) => ({ block, region: "", offset: 0 });
532
+ function mapSelection(selection, map, doc) {
533
+ if (selection.kind === "node") {
534
+ const block = map(blockProbe(selection.block), 1).block;
535
+ return block === selection.block ? selection : nodeSelection(block);
536
+ }
537
+ const anchorAssoc = doc !== void 0 && isBackward(doc, selection) ? -1 : 1;
538
+ if (selection.kind === "blocks") {
539
+ const anchor2 = map(blockProbe(selection.anchor), anchorAssoc).block;
540
+ const head2 = map(blockProbe(selection.head), 1).block;
541
+ return anchor2 === selection.anchor && head2 === selection.head ? selection : blockRangeSelection(anchor2, head2);
542
+ }
543
+ const anchor = map(selection.anchor, anchorAssoc);
544
+ const head = map(selection.head, 1);
545
+ return anchor === selection.anchor && head === selection.head ? selection : textSelection(anchor, head);
546
+ }
547
+ function primaryRegion(schema, block) {
548
+ const spec = specFor(schema, block.type);
549
+ if (spec.flow === "text" && spec.primaryRegion !== void 0 && Object.hasOwn(block.regions, spec.primaryRegion)) {
550
+ return spec.primaryRegion;
551
+ }
552
+ return firstRegion(block);
553
+ }
554
+ function flowEdge(schema, block, atEnd) {
555
+ const region = specFor(schema, block.type).flow === "text" ? primaryRegion(schema, block) : null;
556
+ return region === null ? null : { block: block.id, region, offset: atEnd ? inlineLength(block.regions[region]) : 0 };
557
+ }
558
+ function edge(doc, schema, blockId, atEnd) {
559
+ const block = doc.blocks[requireBlockIndex(doc, blockId)];
560
+ const flow = flowEdge(schema, block, atEnd);
561
+ if (flow !== null) return textSelection(flow);
562
+ if (Object.keys(block.regions).length === 0) return nodeSelection(blockId);
563
+ return textSelection(atEnd ? blockEnd(block) : blockStart(block));
564
+ }
565
+ function startOf(doc, schema, blockId) {
566
+ return edge(doc, schema, blockId, false);
567
+ }
568
+ function endOf(doc, schema, blockId) {
569
+ return edge(doc, schema, blockId, true);
570
+ }
571
+ function defaultSelection(doc, schema) {
572
+ return doc.blocks.length === 0 ? null : startOf(doc, schema, doc.blocks[0].id);
573
+ }
574
+ function validatePos(doc, schema, pos) {
575
+ const block = blockById(doc, pos.block);
576
+ if (block === void 0) return null;
577
+ if (!Object.hasOwn(block.regions, pos.region)) {
578
+ const region = primaryRegion(schema, block);
579
+ return region === null ? nodeSelection(block.id) : { block: block.id, region, offset: 0 };
580
+ }
581
+ const length = inlineLength(block.regions[pos.region]);
582
+ return pos.offset >= 0 && pos.offset <= length ? pos : { ...pos, offset: Math.min(Math.max(pos.offset, 0), length) };
583
+ }
584
+ function nearestFlow(doc, schema, from, to, atEnd) {
585
+ const step = atEnd ? -1 : 1;
586
+ for (let index = from; index !== to + step; index += step) {
587
+ const pos = flowEdge(schema, doc.blocks[index], atEnd);
588
+ if (pos !== null) return pos;
589
+ }
590
+ return null;
591
+ }
592
+ var inFlow = (doc, schema, pos) => flowEdge(schema, doc.blocks[requireBlockIndex(doc, pos.block)], false)?.region === pos.region;
593
+ function clampToFlow(doc, schema, selection) {
594
+ const { anchor, head } = selection;
595
+ if (anchor.block === head.block) return selection;
596
+ const backward = comparePos(doc, anchor, head) > 0;
597
+ const [from, to] = backward ? [head, anchor] : [anchor, head];
598
+ const first = requireBlockIndex(doc, from.block);
599
+ const last = requireBlockIndex(doc, to.block);
600
+ const start = inFlow(doc, schema, from) ? from : nearestFlow(doc, schema, first + 1, last, false);
601
+ const end = inFlow(doc, schema, to) ? to : nearestFlow(doc, schema, last - 1, first, true);
602
+ if (start === from && end === to) return selection;
603
+ if (start === null || end === null || comparePos(doc, start, end) > 0) {
604
+ return blockRangeSelection(anchor.block, head.block);
605
+ }
606
+ return backward ? textSelection(end, start) : textSelection(start, end);
607
+ }
608
+ function validateText(doc, schema, selection) {
609
+ const head = validatePos(doc, schema, selection.head);
610
+ const anchor = validatePos(doc, schema, selection.anchor);
611
+ if (head === null || anchor === null) return defaultSelection(doc, schema);
612
+ if ("kind" in head) return head;
613
+ if ("kind" in anchor) return anchor;
614
+ const valid = anchor === selection.anchor && head === selection.head ? selection : textSelection(anchor, head);
615
+ return clampToFlow(doc, schema, valid);
616
+ }
617
+ function validateNode(doc, schema, selection) {
618
+ const block = blockById(doc, selection.block);
619
+ if (block === void 0) return defaultSelection(doc, schema);
620
+ return specFor(schema, block.type).flow === "text" ? startOf(doc, schema, block.id) : selection;
621
+ }
622
+ function validateSelection(doc, schema, selection) {
623
+ if (doc.blocks.length === 0) return null;
624
+ if (selection === null) return defaultSelection(doc, schema);
625
+ switch (selection.kind) {
626
+ case "node":
627
+ return validateNode(doc, schema, selection);
628
+ case "blocks":
629
+ return doc.index.has(selection.anchor) && doc.index.has(selection.head) ? selection : defaultSelection(doc, schema);
630
+ case "text":
631
+ return validateText(doc, schema, selection);
632
+ }
633
+ }
634
+
635
+ // src/engine/commands/collapsed-toggle.ts
636
+ var isClosedToggle = ({ type, attrs }) => type === "TOGGLE" && attrs.open !== true;
637
+ function hidingToggle(doc, index, isClosed = isClosedToggle) {
638
+ if (index < 2) return null;
639
+ let limit = doc.blocks[index - 1].indent;
640
+ let hiding = null;
641
+ for (let at = index - 2; at >= 0 && limit > 0; at--) {
642
+ const { indent: indent2 } = doc.blocks[at];
643
+ if (indent2 >= limit) continue;
644
+ limit = indent2;
645
+ if (isClosed(doc.blocks[at])) hiding = at;
646
+ }
647
+ return hiding;
648
+ }
649
+
650
+ // src/engine/state/visible-selection.ts
651
+ var hiderOf = (doc, id) => hidingToggle(doc, requireBlockIndex(doc, id) + 1);
652
+ function toggleEnd(doc, schema, toggle) {
653
+ const end = endOf(doc, schema, doc.blocks[toggle].id);
654
+ return end.kind === "text" ? end.head : blockEnd(doc.blocks[toggle]);
655
+ }
656
+ function visibleText(doc, schema, selection) {
657
+ const [anchor, head] = [selection.anchor, selection.head].map((pos) => {
658
+ const hider = hiderOf(doc, pos.block);
659
+ return hider === null ? pos : toggleEnd(doc, schema, hider);
660
+ });
661
+ return anchor === selection.anchor && head === selection.head ? selection : textSelection(anchor, head);
662
+ }
663
+ function visibleBlocks(doc, schema, selection) {
664
+ const ids = [selection.anchor, selection.head];
665
+ const indexes = ids.map((id) => requireBlockIndex(doc, id));
666
+ const from = Math.min(...indexes);
667
+ const to = Math.max(...indexes);
668
+ const hiders = indexes.map((index) => hidingToggle(doc, index + 1));
669
+ const [anchor, head] = ids.map((id, i) => {
670
+ const hider = hiders[i];
671
+ return hider === null || hider >= from && hider <= to ? id : doc.blocks[hider].id;
672
+ });
673
+ if (anchor === selection.anchor && head === selection.head) return selection;
674
+ const [anchorHider, headHider] = hiders;
675
+ if (anchorHider !== null && anchorHider === headHider) return textSelection(toggleEnd(doc, schema, anchorHider));
676
+ return blockRangeSelection(anchor, head);
677
+ }
678
+ function visibleSelection(doc, schema, selection) {
679
+ if (selection === null) return null;
680
+ switch (selection.kind) {
681
+ case "text":
682
+ return visibleText(doc, schema, selection);
683
+ case "node": {
684
+ const hider = hiderOf(doc, selection.block);
685
+ return hider === null ? selection : textSelection(toggleEnd(doc, schema, hider));
686
+ }
687
+ case "blocks":
688
+ return visibleBlocks(doc, schema, selection);
689
+ }
690
+ }
691
+
692
+ // src/engine/state/editor-state.ts
693
+ function createState(config) {
694
+ const { doc, schema, plugins = [], clock = Date.now } = config;
695
+ const requested = config.selection === void 0 ? defaultSelection(doc, schema) : config.selection;
696
+ const pluginState = /* @__PURE__ */ new Map();
697
+ const state = {
698
+ doc,
699
+ schema,
700
+ selection: validateSelection(doc, schema, requested),
701
+ storedMarks: null,
702
+ plugins,
703
+ pluginState,
704
+ clock
705
+ };
706
+ for (const plugin of plugins) {
707
+ if (pluginState.has(plugin.key.name)) throw new Error(`duplicate plugin key "${plugin.key.name}"`);
708
+ pluginState.set(plugin.key.name, plugin.init(state));
709
+ }
710
+ return state;
711
+ }
712
+ var mismatchedTransaction = () => new RangeError("Applying a mismatched transaction: it was built on another state");
713
+ function applyTransaction(state, tr) {
714
+ if (tr.before !== state) throw mismatchedTransaction();
715
+ const selection = visibleSelection(tr.doc, state.schema, validateSelection(tr.doc, state.schema, tr.selection));
716
+ const unchanged = !tr.docChanged && sameSelection(state.selection, selection);
717
+ const storedMarks = tr.storedMarksSet ? tr.storedMarks : unchanged ? state.storedMarks : null;
718
+ const next = { ...state, doc: tr.doc, selection, storedMarks };
719
+ if (state.plugins.length === 0) return next;
720
+ const pluginState = new Map(state.pluginState);
721
+ const updated = { ...next, pluginState };
722
+ for (const plugin of state.plugins) {
723
+ const { name } = plugin.key;
724
+ pluginState.set(name, plugin.apply(tr, state.pluginState.get(name), state, updated));
725
+ }
726
+ return updated;
727
+ }
728
+ function getPluginState(state, key) {
729
+ if (!state.pluginState.has(key.name)) throw new Error(`no plugin registered for "${key.name}"`);
730
+ return state.pluginState.get(key.name);
731
+ }
732
+
733
+ // src/engine/state/plugin.ts
734
+ function createPluginKey(name) {
735
+ return { name };
736
+ }
737
+
738
+ // src/engine/state/transaction.ts
739
+ var foreignOrigins = ["remote", "reconcile", "history"];
740
+ var UNFILTERED = "unfiltered";
741
+ var Transaction = class {
742
+ before;
743
+ time;
744
+ currentDoc;
745
+ currentSelection;
746
+ currentStoredMarks;
747
+ selectionWasSet = false;
748
+ storedMarksWereSet = false;
749
+ stepList = [];
750
+ docList = [];
751
+ mapList = [];
752
+ metaValues = /* @__PURE__ */ new Map();
753
+ constructor(state, time = state.clock()) {
754
+ this.before = state;
755
+ this.time = time;
756
+ this.currentDoc = state.doc;
757
+ this.currentSelection = state.selection;
758
+ this.currentStoredMarks = state.storedMarks;
759
+ }
760
+ get doc() {
761
+ return this.currentDoc;
762
+ }
763
+ get docs() {
764
+ return this.docList;
765
+ }
766
+ get steps() {
767
+ return this.stepList;
768
+ }
769
+ get maps() {
770
+ return this.mapList;
771
+ }
772
+ get docChanged() {
773
+ return this.stepList.length > 0;
774
+ }
775
+ get selection() {
776
+ return this.currentSelection;
777
+ }
778
+ get selectionSet() {
779
+ return this.selectionWasSet;
780
+ }
781
+ get storedMarks() {
782
+ return this.currentStoredMarks;
783
+ }
784
+ get storedMarksSet() {
785
+ return this.storedMarksWereSet;
786
+ }
787
+ get meta() {
788
+ return this.metaValues;
789
+ }
790
+ get origin() {
791
+ return this.getMeta("origin") ?? "command";
792
+ }
793
+ get addToHistory() {
794
+ return this.getMeta("addToHistory") !== false;
795
+ }
796
+ step(step) {
797
+ const { doc, map } = step.apply(this.currentDoc);
798
+ if (this.currentSelection !== null) {
799
+ this.currentSelection = mapSelection(this.currentSelection, map, this.currentDoc);
800
+ }
801
+ this.docList.push(this.currentDoc);
802
+ this.stepList.push(step);
803
+ this.mapList.push(map);
804
+ this.currentDoc = doc;
805
+ return this;
806
+ }
807
+ maybeStep(step) {
808
+ try {
809
+ this.step(step);
810
+ return true;
811
+ } catch (error) {
812
+ if (error instanceof StepError) return false;
813
+ throw error;
814
+ }
815
+ }
816
+ setSelection(selection) {
817
+ this.currentSelection = selection;
818
+ this.selectionWasSet = true;
819
+ return this;
820
+ }
821
+ setStoredMarks(marks) {
822
+ this.currentStoredMarks = marks;
823
+ this.storedMarksWereSet = true;
824
+ return this;
825
+ }
826
+ setOrigin(origin) {
827
+ return this.setMeta("origin", origin);
828
+ }
829
+ setMeta(key, value) {
830
+ this.metaValues.set(key, value);
831
+ return this;
832
+ }
833
+ getMeta(key) {
834
+ return this.metaValues.get(key);
835
+ }
836
+ mapPos(pos, assoc = 1, fromStep = 0) {
837
+ let mapped = pos;
838
+ for (let i = fromStep; i < this.mapList.length; i++) mapped = this.mapList[i](mapped, assoc);
839
+ return mapped;
840
+ }
841
+ };
842
+
843
+ // src/engine/commands/caret-marks.ts
844
+ function caretMarks(doc, schema, storedMarks, pos) {
845
+ if (storedMarks !== null) return storedMarks;
846
+ const inline = blockById(doc, pos.block)?.regions[pos.region] ?? [];
847
+ return marksAt(inline, pos.offset, (mark) => schema.marks.get(mark.type)?.inclusive ?? true);
848
+ }
849
+
850
+ // src/engine/commands/run-commands.ts
851
+ var chainDispatches = /* @__PURE__ */ new WeakSet();
852
+ var movesHistory = (tr) => tr.getMeta("history") !== void 0;
853
+ function mergeable(parts) {
854
+ const moving = parts.filter(movesHistory).length;
855
+ return moving === 0 || moving === 1 && parts.every((part) => movesHistory(part) || !part.docChanged);
856
+ }
857
+ function combine(initial, parts, final) {
858
+ if (parts.length === 1) return parts[0];
859
+ const tr = new Transaction(initial, parts[0].time);
860
+ for (const part of parts) {
861
+ part.steps.forEach((step) => tr.step(step));
862
+ part.meta.forEach((value, key) => key !== UNFILTERED && tr.setMeta(key, value));
863
+ }
864
+ const moving = parts.find(movesHistory);
865
+ const origins = new Set(parts.map((part) => part.origin));
866
+ if (moving !== void 0) tr.setOrigin(moving.origin);
867
+ else if (origins.size === 1) tr.setOrigin(parts[0].origin);
868
+ else tr.setOrigin("command");
869
+ if (parts.every((part) => part.getMeta(UNFILTERED) === true)) tr.setMeta(UNFILTERED, true);
870
+ if (parts.some((part) => !part.addToHistory)) tr.setMeta("addToHistory", false);
871
+ if (parts.some((part) => part.getMeta("closeHistory") === true)) tr.setMeta("closeHistory", true);
872
+ if (!sameSelection(tr.selection, final.selection)) tr.setSelection(final.selection);
873
+ if (final.storedMarks !== initial.storedMarks) tr.setStoredMarks(final.storedMarks);
874
+ return tr;
875
+ }
876
+ function runCommands(state, commands) {
877
+ let current = state;
878
+ const parts = [];
879
+ const collect = (tr) => {
880
+ parts.push(tr);
881
+ current = applyTransaction(current, tr);
882
+ };
883
+ chainDispatches.add(collect);
884
+ for (const command of commands) {
885
+ if (!command(current, collect)) return { ok: false, tr: null };
886
+ }
887
+ if (!mergeable(parts)) return { ok: false, tr: null };
888
+ return { ok: true, tr: parts.length === 0 ? null : combine(state, parts, current) };
889
+ }
890
+
891
+ // src/engine/transform/position-neutral.ts
892
+ function sameLayout(before, after) {
893
+ return before.blocks.length === after.blocks.length && before.blocks.every(({ id, indent: indent2 }, index) => id === after.blocks[index].id && indent2 === after.blocks[index].indent);
894
+ }
895
+ function isPositionNeutral(before, { doc, map }) {
896
+ return map === identityMap && sameLayout(before, doc);
897
+ }
898
+
899
+ // src/engine/state/history.ts
900
+ var historyKey = createPluginKey("history");
901
+ var HISTORY_PROVIDER = "historyProvider";
902
+ var HISTORY_GROUP = "historyGroup";
903
+ var emptyHistory = { done: [], undone: [], lastTime: 0, lastGroup: null, lastInputRule: null };
904
+ var historyOf = (state) => state.pluginState.get(historyKey.name);
905
+ function invertTransaction(tr) {
906
+ return tr.steps.map((step, i) => step.invert(tr.docs[i])).reverse();
907
+ }
908
+ var newest = (items, depth) => items.slice(Math.max(0, items.length - depth));
909
+ function groupKey(selection) {
910
+ return selection?.kind === "text" ? JSON.stringify([selection.head.block, selection.head.region]) : null;
911
+ }
912
+ function groupOf(tr) {
913
+ if (tr.getMeta("closeHistory") === true) return null;
914
+ return tr.getMeta(HISTORY_GROUP) ?? (tr.origin === "input" ? groupKey(tr.before.selection) : null);
915
+ }
916
+ function inputRuleRecord(tr, steps) {
917
+ const rule = tr.getMeta("inputRule");
918
+ const before = tr.before.selection;
919
+ if (rule === void 0 || before?.kind !== "text" || !isCollapsed(before)) return null;
920
+ const { doc, schema, storedMarks } = tr.before;
921
+ return { steps, undoText: rule.undoText, at: before.head, marks: caretMarks(doc, schema, storedMarks, before.head) };
922
+ }
923
+ function recorded(tr, value, { depth, groupDelay }) {
924
+ const steps = invertTransaction(tr);
925
+ const group = groupOf(tr);
926
+ const last = value.done.at(-1);
927
+ const merges = last !== void 0 && group !== null && group === value.lastGroup && tr.time - value.lastTime < groupDelay;
928
+ const done = merges ? [...value.done.slice(0, -1), { ...last, steps: [...steps, ...last.steps] }] : newest([...value.done, { steps, selection: tr.before.selection, origin: tr.origin }], depth);
929
+ return { done, undone: [], lastTime: tr.time, lastGroup: group, lastInputRule: inputRuleRecord(tr, steps) };
930
+ }
931
+ function moved(tr, value, direction, depth) {
932
+ const undoing = direction === "undo";
933
+ const source = undoing ? value.done : value.undone;
934
+ const applied = source.length - 1 - (tr.getMeta("historySkipped") ?? 0);
935
+ const inverse = {
936
+ steps: invertTransaction(tr),
937
+ selection: tr.before.selection,
938
+ origin: source[applied]?.origin ?? tr.origin
939
+ };
940
+ const rest = source.slice(0, applied);
941
+ const target = newest([...undoing ? value.undone : value.done, inverse], depth);
942
+ const stacks = undoing ? { done: rest, undone: target } : { done: target, undone: rest };
943
+ return { ...value, ...stacks, lastGroup: null, lastInputRule: null };
944
+ }
945
+ var movesNoPosition = (tr) => tr.steps.every((_, i) => isPositionNeutral(tr.docs[i], { doc: tr.docs[i + 1] ?? tr.doc, map: tr.maps[i] }));
946
+ function advance(tr, value, config) {
947
+ if (tr.docChanged) {
948
+ const direction = tr.getMeta("history");
949
+ if (direction !== void 0) return moved(tr, value, direction, config.depth);
950
+ if (tr.addToHistory && !foreignOrigins.includes(tr.origin)) return recorded(tr, value, config);
951
+ if (!movesNoPosition(tr)) return emptyHistory;
952
+ }
953
+ const closed = tr.getMeta("closeHistory") === true;
954
+ const relocated = tr.selectionSet && !sameSelection(tr.before.selection, tr.selection);
955
+ return {
956
+ ...value,
957
+ lastGroup: closed ? null : value.lastGroup,
958
+ lastInputRule: relocated || tr.storedMarksSet ? null : value.lastInputRule
959
+ };
960
+ }
961
+ function historyPlugin(options = {}) {
962
+ const config = { depth: options.depth ?? 200, groupDelay: options.groupDelay ?? 500 };
963
+ return { key: historyKey, init: () => emptyHistory, apply: (tr, value) => advance(tr, value, config) };
964
+ }
965
+ function travel(direction) {
966
+ return (state, dispatch) => {
967
+ const provider = state.pluginState.get(HISTORY_PROVIDER);
968
+ if (provider) return !(dispatch && chainDispatches.has(dispatch)) && provider[direction](!dispatch);
969
+ const history = historyOf(state);
970
+ const items = (direction === "undo" ? history?.done : history?.undone) ?? [];
971
+ for (let i = items.length - 1; i >= 0; i--) {
972
+ const tr = new Transaction(state).setOrigin("history").setMeta("history", direction);
973
+ if (!items[i].steps.every((step) => tr.maybeStep(step))) continue;
974
+ const { selection } = items[i];
975
+ if (selection !== null) tr.setSelection(selection);
976
+ dispatch?.(tr.setMeta("historySkipped", items.length - 1 - i));
977
+ return true;
978
+ }
979
+ return false;
980
+ };
981
+ }
982
+ var undo = travel("undo");
983
+ var redo = travel("redo");
984
+ var undoInputRule = (state, dispatch) => {
985
+ const record = historyOf(state)?.lastInputRule;
986
+ if (record === null || record === void 0) return false;
987
+ if (dispatch !== void 0) {
988
+ const { steps, undoText, at, marks } = record;
989
+ const tr = new Transaction(state).setMeta(UNFILTERED, true);
990
+ steps.forEach((step) => tr.step(step));
991
+ tr.step(replaceInlineStep(at.block, at.region, at.offset, at.offset, [text(undoText, marks)]));
992
+ dispatch(tr.setSelection(textSelection({ ...at, offset: at.offset + undoText.length })));
993
+ }
994
+ return true;
995
+ };
996
+
997
+ // src/engine/commands/block-helpers.ts
998
+ var ITEM_KINDS = /* @__PURE__ */ new Set(["bullet", "ordered", "task", "quote"]);
999
+ function specOf(schema, block) {
1000
+ return specFor(schema, block.type);
1001
+ }
1002
+ function isTextFlow(schema, block) {
1003
+ return specOf(schema, block).flow === "text";
1004
+ }
1005
+ function isParagraph(block) {
1006
+ return block.type === "MARKDOWN" && block.attrs.kind === "paragraph";
1007
+ }
1008
+ function isItem(block) {
1009
+ return block.type === "MARKDOWN" && typeof block.attrs.kind === "string" && ITEM_KINDS.has(block.attrs.kind);
1010
+ }
1011
+ function regionLength(block, region) {
1012
+ return Object.hasOwn(block.regions, region) ? inlineLength(block.regions[region]) : 0;
1013
+ }
1014
+ function descendantsEnd(doc, index) {
1015
+ const { indent: indent2 } = doc.blocks[index];
1016
+ let end = index + 1;
1017
+ while (end < doc.blocks.length && doc.blocks[end].indent > indent2) end++;
1018
+ return end;
1019
+ }
1020
+ function createBlock(schema, type, attrs = {}, indent2 = 0, id = newId()) {
1021
+ const initial = specFor(schema, type).initial();
1022
+ return { id, type, indent: indent2, attrs: { ...initial.attrs, ...attrs }, regions: initial.regions };
1023
+ }
1024
+ function withContent(schema, block, content) {
1025
+ const region = primaryRegion(schema, block) ?? "";
1026
+ return { ...block, regions: { ...block.regions, [region]: content } };
1027
+ }
1028
+ function startPos(schema, block) {
1029
+ return { block: block.id, region: primaryRegion(schema, block) ?? "", offset: 0 };
1030
+ }
1031
+ function headBlockId(selection) {
1032
+ if (selection === null) return null;
1033
+ if (selection.kind === "text") return selection.head.block;
1034
+ return selection.kind === "node" ? selection.block : selection.head;
1035
+ }
1036
+ function selectedBlockRange(doc, selection) {
1037
+ const ids = selectedBlockIds(doc, selection);
1038
+ if (ids.length === 0) return null;
1039
+ const start = indexOfBlock(doc, ids[0]);
1040
+ let end = start + ids.length;
1041
+ for (let index = start; index < start + ids.length; index++) end = Math.max(end, descendantsEnd(doc, index));
1042
+ return { start, end };
1043
+ }
1044
+
1045
+ // src/engine/commands/cap-indents.ts
1046
+ function capIndents(tr, start, end = start) {
1047
+ const { blocks } = tr.doc;
1048
+ const base = start > 0 ? blocks[start - 1].indent : -1;
1049
+ const levels = [];
1050
+ for (let at = start; at < blocks.length; at++) {
1051
+ const { id, indent: indent2 } = blocks[at];
1052
+ let sibling;
1053
+ while (levels.length > 0 && levels[levels.length - 1].from >= indent2) sibling = levels.pop();
1054
+ const parent = levels.length > 0 ? levels[levels.length - 1].to : base;
1055
+ const next = sibling?.from === indent2 ? sibling.to : Math.min(indent2, parent + 1);
1056
+ if (next === indent2 && at >= end) return;
1057
+ if (next !== indent2) tr.step(setIndentStep(id, next));
1058
+ levels.push({ from: indent2, to: next });
1059
+ }
1060
+ }
1061
+
1062
+ // src/engine/commands/remove-blocks.ts
1063
+ function removeBlocks(tr, schema, index, count2) {
1064
+ const before = tr.doc.blocks[index - 1];
1065
+ const after = tr.doc.blocks[index + count2];
1066
+ tr.step(removeBlocksStep(index, count2));
1067
+ capIndents(tr, index);
1068
+ if (before !== void 0) tr.setSelection(endOf(tr.doc, schema, before.id));
1069
+ else tr.setSelection(after === void 0 ? null : startOf(tr.doc, schema, after.id));
1070
+ }
1071
+
1072
+ // src/engine/commands/shift-subtree.ts
1073
+ function shiftSubtree(tr, index, delta) {
1074
+ const { doc } = tr;
1075
+ for (const block of doc.blocks.slice(index, descendantsEnd(doc, index))) {
1076
+ tr.step(setIndentStep(block.id, block.indent + delta));
1077
+ }
1078
+ }
1079
+
1080
+ // src/engine/commands/block-structure.ts
1081
+ var toggleTask = (state, dispatch) => {
1082
+ const { doc, selection } = state;
1083
+ const tasks = selectedBlockIds(doc, selection).map((id) => doc.blocks[requireBlockIndex(doc, id)]).filter((block) => block.type === "MARKDOWN" && block.attrs.kind === "task");
1084
+ if (tasks.length === 0) return false;
1085
+ const checked = !tasks.every((block) => block.attrs.checked === true);
1086
+ const tr = new Transaction(state);
1087
+ for (const block of tasks) tr.step(setAttrsStep(block.id, { checked }));
1088
+ dispatch?.(tr);
1089
+ return true;
1090
+ };
1091
+ function reindentCommand(delta) {
1092
+ return (state, dispatch) => {
1093
+ const { doc } = state;
1094
+ const range = selectedBlockRange(doc, state.selection);
1095
+ if (range === null) return false;
1096
+ const before = range.start > 0 ? doc.blocks[range.start - 1].indent : -1;
1097
+ const tr = new Transaction(state);
1098
+ for (let root = range.start; root < range.end; root = descendantsEnd(doc, root)) {
1099
+ const shift = delta(doc.blocks[root].indent, before);
1100
+ if (shift !== 0) shiftSubtree(tr, root, shift);
1101
+ }
1102
+ if (!tr.docChanged) return false;
1103
+ dispatch?.(tr);
1104
+ return true;
1105
+ };
1106
+ }
1107
+ var indent = reindentCommand((root, before) => root <= before ? 1 : 0);
1108
+ var outdent = reindentCommand((root) => root > 0 ? -1 : 0);
1109
+ function moveTarget(doc, range, direction) {
1110
+ if (direction === "up") {
1111
+ if (range.start === 0) return null;
1112
+ let root = range.start - 1;
1113
+ while (root > 0 && doc.blocks[root].indent > doc.blocks[range.start].indent) root--;
1114
+ return root;
1115
+ }
1116
+ if (range.end >= doc.blocks.length) return null;
1117
+ return descendantsEnd(doc, range.end) - (range.end - range.start);
1118
+ }
1119
+ function moveCommand(direction) {
1120
+ return (state, dispatch) => {
1121
+ const { doc } = state;
1122
+ const range = selectedBlockRange(doc, state.selection);
1123
+ const to = range === null ? null : moveTarget(doc, range, direction);
1124
+ if (range === null || to === null) return false;
1125
+ const count2 = range.end - range.start;
1126
+ const tr = new Transaction(state).step(moveBlocksStep(range.start, count2, to));
1127
+ capIndents(tr, to);
1128
+ capIndents(tr, to + count2);
1129
+ dispatch?.(tr);
1130
+ return true;
1131
+ };
1132
+ }
1133
+ var moveBlocksUp = moveCommand("up");
1134
+ var moveBlocksDown = moveCommand("down");
1135
+ function copiedSelection(selection, copies) {
1136
+ const copy = (id) => copies.get(id) ?? id;
1137
+ const copyPos = (pos) => ({ ...pos, block: copy(pos.block) });
1138
+ switch (selection.kind) {
1139
+ case "text":
1140
+ return textSelection(copyPos(selection.anchor), copyPos(selection.head));
1141
+ case "node":
1142
+ return nodeSelection(copy(selection.block));
1143
+ case "blocks":
1144
+ return blockRangeSelection(copy(selection.anchor), copy(selection.head));
1145
+ }
1146
+ }
1147
+ function duplicateBlocks(options = {}) {
1148
+ const makeId = options.newId ?? newId;
1149
+ return (state, dispatch) => {
1150
+ const { doc, selection } = state;
1151
+ const range = selectedBlockRange(doc, selection);
1152
+ if (range === null || selection === null) return false;
1153
+ const blocks = doc.blocks.slice(range.start, range.end).map((block) => ({ ...block, id: makeId() }));
1154
+ const copies = new Map(blocks.map((copy, i) => [doc.blocks[range.start + i].id, copy.id]));
1155
+ const tr = new Transaction(state).step(insertBlocksStep(range.end, blocks));
1156
+ capIndents(tr, range.end);
1157
+ dispatch?.(tr.setSelection(copiedSelection(selection, copies)));
1158
+ return true;
1159
+ };
1160
+ }
1161
+ var deleteBlocks = (state, dispatch) => {
1162
+ const range = selectedBlockRange(state.doc, state.selection);
1163
+ if (range === null) return false;
1164
+ const tr = new Transaction(state);
1165
+ removeBlocks(tr, state.schema, range.start, range.end - range.start);
1166
+ dispatch?.(tr);
1167
+ return true;
1168
+ };
1169
+ function insertBlockAfter(type, attrs, options = {}) {
1170
+ const makeId = options.newId ?? newId;
1171
+ return (state, dispatch) => {
1172
+ const { doc, schema } = state;
1173
+ const id = headBlockId(state.selection);
1174
+ if (id === null) return false;
1175
+ const index = requireBlockIndex(doc, id);
1176
+ const current = doc.blocks[index];
1177
+ const fillsCurrent = isParagraph(current) && regionLength(current, primaryRegion(schema, current) ?? "") === 0;
1178
+ const block = createBlock(schema, type, attrs, current.indent, fillsCurrent ? current.id : makeId());
1179
+ const step = fillsCurrent ? replaceBlockStep(block) : insertBlocksStep(descendantsEnd(doc, index), [block]);
1180
+ const tr = new Transaction(state).step(step);
1181
+ dispatch?.(tr.setSelection(startOf(tr.doc, schema, block.id)));
1182
+ return true;
1183
+ };
1184
+ }
1185
+
1186
+ // src/engine/commands/delete-range.ts
1187
+ function clear(tr, pos, start, end) {
1188
+ if (start < end) tr.step(replaceInlineStep(pos.block, pos.region, start, end, []));
1189
+ }
1190
+ function deleteWithinBlock(tr, from, to) {
1191
+ if (from.region === to.region) {
1192
+ clear(tr, from, from.offset, to.offset);
1193
+ return from;
1194
+ }
1195
+ const block = tr.doc.blocks[requireBlockIndex(tr.doc, from.block)];
1196
+ clear(tr, from, from.offset, regionLength(block, from.region));
1197
+ for (const region of Object.keys(block.regions)) {
1198
+ const between = compareRegionNames(from.region, region) < 0 && compareRegionNames(region, to.region) < 0;
1199
+ if (between) clear(tr, { ...from, region }, 0, regionLength(block, region));
1200
+ }
1201
+ clear(tr, to, 0, to.offset);
1202
+ return from;
1203
+ }
1204
+ function deleteRange(tr, schema, from, to, newId2) {
1205
+ if (from.block === to.block) return deleteWithinBlock(tr, from, to);
1206
+ const { doc } = tr;
1207
+ const start = requireBlockIndex(doc, from.block);
1208
+ const end = requireBlockIndex(doc, to.block);
1209
+ const first = doc.blocks[start];
1210
+ const last = doc.blocks[end];
1211
+ const keepFirst = isTextFlow(schema, first);
1212
+ const keepLast = isTextFlow(schema, last);
1213
+ if (keepFirst) clear(tr, from, from.offset, regionLength(first, from.region));
1214
+ if (keepLast) clear(tr, to, 0, to.offset);
1215
+ const removeFrom = keepFirst ? start + 1 : start;
1216
+ const removeTo = keepLast ? end : end + 1;
1217
+ if (removeTo > removeFrom) tr.step(removeBlocksStep(removeFrom, removeTo - removeFrom));
1218
+ if (keepFirst) {
1219
+ if (keepLast) tr.step(joinBlocksStep(first.id, from.region, last.id, to.region));
1220
+ capIndents(tr, start + 1);
1221
+ return from;
1222
+ }
1223
+ if (keepLast) {
1224
+ capIndents(tr, start);
1225
+ return { block: last.id, region: to.region, offset: 0 };
1226
+ }
1227
+ const filler = createBlock(schema, schema.defaultTextBlock, {}, first.indent, newId2?.());
1228
+ tr.step(insertBlocksStep(removeFrom, [filler]));
1229
+ capIndents(tr, start + 1);
1230
+ return startPos(schema, filler);
1231
+ }
1232
+
1233
+ // src/engine/commands/insert-text-at.ts
1234
+ function insertTextAt(tr, schema, pos, value) {
1235
+ const slice = [text(value, caretMarks(tr.doc, schema, tr.storedMarks, pos))];
1236
+ tr.step(replaceInlineStep(pos.block, pos.region, pos.offset, pos.offset, slice));
1237
+ return { ...pos, offset: pos.offset + value.length };
1238
+ }
1239
+
1240
+ // src/engine/commands/insert-text.ts
1241
+ function replaceBlocksWithText(tr, schema, range, value, makeId) {
1242
+ const { doc } = tr;
1243
+ const index = range === null ? doc.blocks.length : range.start;
1244
+ const indent2 = range === null ? 0 : doc.blocks[index].indent;
1245
+ if (range !== null) tr.step(removeBlocksStep(index, range.end - range.start));
1246
+ const empty = createBlock(schema, schema.defaultTextBlock, {}, indent2, makeId());
1247
+ const paragraph = withContent(schema, empty, [text(value)]);
1248
+ tr.step(insertBlocksStep(index, [paragraph]));
1249
+ tr.setSelection(endOf(tr.doc, schema, paragraph.id));
1250
+ }
1251
+ function insertText(value, options = {}) {
1252
+ const makeId = options.newId ?? newId;
1253
+ return (state, dispatch) => {
1254
+ if (value === "") return false;
1255
+ const { doc, schema, selection } = state;
1256
+ const tr = new Transaction(state).setOrigin(options.origin ?? "command");
1257
+ if (selection?.kind === "text") {
1258
+ const { from, to } = orderedRange(doc, selection.anchor, selection.head);
1259
+ const caret = deleteRange(tr, schema, from, to, makeId);
1260
+ tr.setSelection(textSelection(insertTextAt(tr, schema, caret, value)));
1261
+ } else {
1262
+ replaceBlocksWithText(tr, schema, selectedBlockRange(doc, selection), value, makeId);
1263
+ }
1264
+ dispatch?.(tr);
1265
+ return true;
1266
+ };
1267
+ }
1268
+
1269
+ // src/engine/commands/mark-extent.ts
1270
+ function markExtent(inline, offset, type) {
1271
+ const starts = [];
1272
+ let position = 0;
1273
+ for (const node of inline) {
1274
+ starts.push(position);
1275
+ position += nodeLength(node);
1276
+ }
1277
+ const containing = (at) => inline.findIndex((node, i) => starts[i] <= at && at < starts[i] + nodeLength(node));
1278
+ const markOf = (index) => inline[index]?.marks.find((mark2) => mark2.type === type);
1279
+ let seed = containing(offset);
1280
+ let mark = markOf(seed);
1281
+ if (mark === void 0) {
1282
+ seed = containing(offset - 1);
1283
+ mark = markOf(seed);
1284
+ }
1285
+ if (mark === void 0) return null;
1286
+ const shares = (index) => inline[index].marks.some((other) => sameMark(other, mark));
1287
+ let first = seed;
1288
+ while (first > 0 && shares(first - 1)) first--;
1289
+ let last = seed;
1290
+ while (last < inline.length - 1 && shares(last + 1)) last++;
1291
+ return { from: starts[first], to: starts[last] + nodeLength(inline[last]) };
1292
+ }
1293
+
1294
+ // src/engine/commands/storable-mark.ts
1295
+ function readsBack(inline) {
1296
+ return equalInline(parseInline(serializeInline(inline)), normalizeInline(inline));
1297
+ }
1298
+ function safeLink(mark) {
1299
+ const href = mark.attrs?.href;
1300
+ const safe = typeof href === "string" ? sanitizeHref(href) : null;
1301
+ return safe ? { type: "link", attrs: { ...mark.attrs, href: safe } } : null;
1302
+ }
1303
+ function storableMark(mark) {
1304
+ const candidate = mark.type === "link" ? safeLink(mark) : mark;
1305
+ return candidate !== null && readsBack([text("a", [candidate])]) ? candidate : null;
1306
+ }
1307
+ function storableAtom(type, attrs) {
1308
+ return readsBack([atom(type, attrs)]);
1309
+ }
1310
+
1311
+ // src/engine/commands/selection-marks.ts
1312
+ function matchesMark(mark, type, attrs) {
1313
+ return mark.type === type && Object.entries(attrs ?? {}).every(([key, value]) => mark.attrs?.[key] === value);
1314
+ }
1315
+ function wholeRegion(schema, block) {
1316
+ const region = primaryRegion(schema, block);
1317
+ if (!isTextFlow(schema, block) || region === null) return null;
1318
+ return { block: block.id, region, from: 0, to: regionLength(block, region) };
1319
+ }
1320
+ function textRanges(state, selection) {
1321
+ const { doc, schema } = state;
1322
+ const { from, to } = orderedRange(doc, selection.anchor, selection.head);
1323
+ if (from.block === to.block) {
1324
+ if (from.region !== to.region) return [];
1325
+ return [{ block: from.block, region: from.region, from: from.offset, to: to.offset }];
1326
+ }
1327
+ const first = requireBlockIndex(doc, from.block);
1328
+ const last = requireBlockIndex(doc, to.block);
1329
+ return doc.blocks.slice(first, last + 1).flatMap((block, i) => {
1330
+ const whole = wholeRegion(schema, block);
1331
+ if (whole === null) return [];
1332
+ const startsHere = i === 0 && from.region === whole.region;
1333
+ const endsHere = first + i === last && to.region === whole.region;
1334
+ return [{ ...whole, from: startsHere ? from.offset : 0, to: endsHere ? to.offset : whole.to }];
1335
+ });
1336
+ }
1337
+ function selectionRanges(state, selection) {
1338
+ if (selection.kind === "text") return textRanges(state, selection);
1339
+ return selectedBlockIds(state.doc, selection).flatMap((id) => {
1340
+ const whole = wholeRegion(state.schema, state.doc.blocks[requireBlockIndex(state.doc, id)]);
1341
+ return whole === null ? [] : [whole];
1342
+ });
1343
+ }
1344
+ function markRanges(state) {
1345
+ const { selection } = state;
1346
+ return selection === null ? [] : selectionRanges(state, selection).filter((range) => range.from < range.to);
1347
+ }
1348
+ function rangeContent(state, range) {
1349
+ const inline = blockById(state.doc, range.block)?.regions[range.region] ?? [];
1350
+ return sliceInline(inline, range.from, range.to);
1351
+ }
1352
+ function markCoverage(content, type, attrs) {
1353
+ const carries = (node) => node.marks.some((mark) => matchesMark(mark, type, attrs));
1354
+ return { some: content.some(carries), every: content.every(carries) };
1355
+ }
1356
+ function selectionHasMark(state, type, attrs) {
1357
+ const { selection } = state;
1358
+ if (selection?.kind === "text" && isCollapsed(selection)) {
1359
+ const marks = caretMarks(state.doc, state.schema, state.storedMarks, selection.head);
1360
+ return marks.some((mark) => matchesMark(mark, type, attrs));
1361
+ }
1362
+ const ranges = markRanges(state);
1363
+ return ranges.length > 0 && ranges.every((range) => markCoverage(rangeContent(state, range), type, attrs).every);
1364
+ }
1365
+
1366
+ // src/engine/commands/marks.ts
1367
+ var KEPT_ON_CLEAR = /* @__PURE__ */ new Set(["link", "comment"]);
1368
+ function makeMark(type, attrs) {
1369
+ return attrs !== void 0 && Object.keys(attrs).length > 0 ? { type, attrs } : { type };
1370
+ }
1371
+ function collapsedCaret(state) {
1372
+ const { selection } = state;
1373
+ return selection?.kind === "text" && isCollapsed(selection) ? selection.head : null;
1374
+ }
1375
+ function editRanges(state, dispatch, ranges, edit) {
1376
+ const tr = new Transaction(state);
1377
+ for (const range of ranges) edit(tr, range, rangeContent(state, range));
1378
+ if (!tr.docChanged) return false;
1379
+ dispatch?.(tr);
1380
+ return true;
1381
+ }
1382
+ function applyMark(type, attrs, mode) {
1383
+ return (state, dispatch) => {
1384
+ const requested = makeMark(type, attrs);
1385
+ const mark = mode === "remove" ? requested : storableMark(requested);
1386
+ if (mark === null) return false;
1387
+ const target = mark.attrs === void 0 ? type : mark;
1388
+ const caret = collapsedCaret(state);
1389
+ if (caret !== null) {
1390
+ const current = caretMarks(state.doc, state.schema, state.storedMarks, caret);
1391
+ const active = current.some((existing) => matchesMark(existing, type, mark.attrs));
1392
+ const remove2 = mode === "remove" || mode === "toggle" && active;
1393
+ const next = remove2 ? removeMark(current, target) : addMark(current, mark);
1394
+ if (sameMarkSet(next, current)) return false;
1395
+ dispatch?.(new Transaction(state).setStoredMarks(next));
1396
+ return true;
1397
+ }
1398
+ const remove = mode === "remove" || mode === "toggle" && selectionHasMark(state, type, mark.attrs);
1399
+ return editRanges(state, dispatch, markRanges(state), (tr, { block, region, from, to }, content) => {
1400
+ const coverage = markCoverage(content, type, mark.attrs);
1401
+ if (remove && coverage.some) tr.step(removeMarkStep(block, region, from, to, target));
1402
+ if (!remove && !coverage.every) tr.step(addMarkStep(block, region, from, to, mark));
1403
+ });
1404
+ };
1405
+ }
1406
+ function toggleMark(type, attrs) {
1407
+ return applyMark(type, attrs, "toggle");
1408
+ }
1409
+ function setMark(type, attrs) {
1410
+ return applyMark(type, attrs, "add");
1411
+ }
1412
+ function unsetMark(type) {
1413
+ return applyMark(type, void 0, "remove");
1414
+ }
1415
+ function linkRanges(state) {
1416
+ const caret = collapsedCaret(state);
1417
+ if (caret === null) return markRanges(state);
1418
+ const inline = blockById(state.doc, caret.block)?.regions[caret.region] ?? [];
1419
+ const extent = markExtent(inline, caret.offset, "link");
1420
+ return extent === null ? [] : [{ block: caret.block, region: caret.region, ...extent }];
1421
+ }
1422
+ function setLink(href, title) {
1423
+ const safe = sanitizeHref(href);
1424
+ return (state, dispatch) => {
1425
+ if (safe === null || safe === "") return false;
1426
+ const link = { type: "link", attrs: title ? { href: safe, title } : { href: safe } };
1427
+ return editRanges(state, dispatch, linkRanges(state), (tr, { block, region, from, to }, content) => {
1428
+ const linked2 = content.every((node) => node.marks.some((mark) => sameMark(mark, link)));
1429
+ if (!linked2) tr.step(addMarkStep(block, region, from, to, link));
1430
+ });
1431
+ };
1432
+ }
1433
+ var unsetLink = (state, dispatch) => editRanges(state, dispatch, linkRanges(state), (tr, { block, region, from, to }, content) => {
1434
+ if (markCoverage(content, "link").some) tr.step(removeMarkStep(block, region, from, to, "link"));
1435
+ });
1436
+ var clearFormatting = (state, dispatch) => {
1437
+ const clearable = (mark) => !KEPT_ON_CLEAR.has(mark.type);
1438
+ const caret = collapsedCaret(state);
1439
+ if (caret !== null) {
1440
+ const current = caretMarks(state.doc, state.schema, state.storedMarks, caret);
1441
+ const kept = current.filter((mark) => !clearable(mark));
1442
+ if (kept.length === current.length) return false;
1443
+ dispatch?.(new Transaction(state).setStoredMarks(kept));
1444
+ return true;
1445
+ }
1446
+ return editRanges(state, dispatch, markRanges(state), (tr, { block, region, from, to }, content) => {
1447
+ const types = new Set(content.flatMap((node) => node.marks.filter(clearable).map((mark) => mark.type)));
1448
+ for (const type of types) tr.step(removeMarkStep(block, region, from, to, type));
1449
+ });
1450
+ };
1451
+ function insertAtom(type, attrs) {
1452
+ return (state, dispatch) => {
1453
+ const { doc, schema, selection } = state;
1454
+ if (selection?.kind !== "text" || !storableAtom(type, attrs)) return false;
1455
+ const tr = new Transaction(state);
1456
+ const { from, to } = orderedRange(doc, selection.anchor, selection.head);
1457
+ const caret = deleteRange(tr, schema, from, to);
1458
+ const node = atom(type, attrs, caretMarks(tr.doc, schema, tr.storedMarks, caret));
1459
+ tr.step(replaceInlineStep(caret.block, caret.region, caret.offset, caret.offset, [node]));
1460
+ tr.setSelection(textSelection({ ...caret, offset: caret.offset + 1 }));
1461
+ dispatch?.(tr);
1462
+ return true;
1463
+ };
1464
+ }
1465
+ function insertMention(mention) {
1466
+ const { id, label, kind } = mention;
1467
+ return insertAtom("mention", kind === void 0 ? { id, label } : { id, label, kind });
1468
+ }
1469
+
1470
+ // src/engine/commands/turn-into.ts
1471
+ var TEXT_ATTRIBUTE = /* @__PURE__ */ new Map([
1472
+ ["CODE", "code"],
1473
+ ["MATH", "latex"]
1474
+ ]);
1475
+ function holdsText(schema, type) {
1476
+ return TEXT_ATTRIBUTE.has(type) || specFor(schema, type).flow === "text";
1477
+ }
1478
+ function sourceContent(schema, block) {
1479
+ const field = TEXT_ATTRIBUTE.get(block.type);
1480
+ if (field !== void 0) {
1481
+ const value = block.attrs[field];
1482
+ return typeof value === "string" && value !== "" ? [text(value)] : [];
1483
+ }
1484
+ const region = primaryRegion(schema, block);
1485
+ return region === null ? [] : block.regions[region];
1486
+ }
1487
+ function plainText(schema, content) {
1488
+ return inlineText(content, (node) => schema.atoms.get(node.type)?.plainText(node.attrs) ?? "");
1489
+ }
1490
+ function convert(schema, block, type, attrs) {
1491
+ const content = sourceContent(schema, block);
1492
+ const field = TEXT_ATTRIBUTE.get(type);
1493
+ const withText = field === void 0 ? attrs : { ...attrs, [field]: plainText(schema, content) };
1494
+ const created = createBlock(schema, type, withText, block.indent, block.id);
1495
+ return field === void 0 ? withContent(schema, created, content) : created;
1496
+ }
1497
+ function sameTypePatch(type, attrs) {
1498
+ if (type !== "MARKDOWN") return attrs;
1499
+ return { kind: "paragraph", level: void 0, checked: void 0, start: void 0, ...attrs };
1500
+ }
1501
+ function setBlockType(type, attrs = {}) {
1502
+ return (state, dispatch) => {
1503
+ const { doc, schema, selection } = state;
1504
+ if (!holdsText(schema, type)) return false;
1505
+ const ids = selectedBlockIds(doc, selection);
1506
+ const convertible = (block) => isTextFlow(schema, block) || selection?.kind === "node" && TEXT_ATTRIBUTE.has(block.type);
1507
+ const blocks = ids.flatMap((id) => blockById(doc, id) ?? []).filter(convertible);
1508
+ if (blocks.length === 0) return false;
1509
+ const tr = new Transaction(state);
1510
+ const moved2 = /* @__PURE__ */ new Map();
1511
+ for (const block of blocks) {
1512
+ if (block.type === type) {
1513
+ const patch = sameTypePatch(type, attrs);
1514
+ if (patchChangesAttrs(block.attrs, patch)) tr.step(setAttrsStep(block.id, patch));
1515
+ continue;
1516
+ }
1517
+ const next = convert(schema, block, type, attrs);
1518
+ tr.step(replaceBlockStep(next));
1519
+ const from = primaryRegion(schema, block);
1520
+ const to = primaryRegion(schema, next);
1521
+ if (from !== null && to !== null) moved2.set(block.id, { from, to, length: inlineLength(next.regions[to]) });
1522
+ }
1523
+ if (!tr.docChanged) return false;
1524
+ if (selection?.kind === "text" && moved2.size > 0) {
1525
+ const carry = (pos) => {
1526
+ const entry = moved2.get(pos.block);
1527
+ if (entry === void 0 || pos.region !== entry.from) return pos;
1528
+ return { block: pos.block, region: entry.to, offset: Math.min(pos.offset, entry.length) };
1529
+ };
1530
+ tr.setSelection(textSelection(carry(selection.anchor), carry(selection.head)));
1531
+ }
1532
+ const converted = selection?.kind === "node" ? blockById(tr.doc, selection.block) : void 0;
1533
+ if (converted !== void 0 && isTextFlow(schema, converted)) tr.setSelection(endOf(tr.doc, schema, converted.id));
1534
+ dispatch?.(tr);
1535
+ return true;
1536
+ };
1537
+ }
1538
+
1539
+ // src/engine/rules/input-rule.ts
1540
+ function appliesTo(rule, block) {
1541
+ const { blockTypes, kinds } = rule;
1542
+ const { kind } = block.attrs;
1543
+ if (blockTypes !== void 0 && !blockTypes.includes(block.type)) return false;
1544
+ return kinds === void 0 || typeof kind === "string" && kinds.includes(kind);
1545
+ }
1546
+ function ruleTransaction(state, rules, context) {
1547
+ const { selection, doc } = state;
1548
+ if (selection?.kind !== "text") return null;
1549
+ const { block: id, region, offset } = selection.head;
1550
+ const block = doc.blocks[requireBlockIndex(doc, id)];
1551
+ const inline = block.regions[region];
1552
+ if (hasMark(marksAt(inline, offset, () => true), "code")) return null;
1553
+ const before = inlineText(sliceInline(inline, 0, offset));
1554
+ for (const rule of rules) {
1555
+ const match = appliesTo(rule, block) ? rule.match.exec(before) : null;
1556
+ if (match === null) continue;
1557
+ const tr = rule.handler(state, match, { block: id, region, from: match.index, to: offset }, context);
1558
+ if (tr !== null) return tr;
1559
+ }
1560
+ return null;
1561
+ }
1562
+ function insertTextWithRules(value, rules, options = {}) {
1563
+ const context = { newId: options.newId ?? newId };
1564
+ const insert = insertText(value, { origin: "input", newId: context.newId });
1565
+ return (state, dispatch) => {
1566
+ let fired = false;
1567
+ const applyRules = (current, emit2) => {
1568
+ const tr2 = ruleTransaction(current, rules, context);
1569
+ if (tr2 !== null) {
1570
+ fired = true;
1571
+ emit2?.(tr2.setOrigin("input"));
1572
+ }
1573
+ return true;
1574
+ };
1575
+ const { ok, tr } = runCommands(state, [insert, applyRules]);
1576
+ if (!ok || tr === null) return false;
1577
+ if (fired) tr.setMeta("inputRule", { undoText: value });
1578
+ dispatch?.(tr);
1579
+ return true;
1580
+ };
1581
+ }
1582
+
1583
+ // src/engine/rules/convert-prefix.ts
1584
+ function convertPrefix(state, range, convert2) {
1585
+ const { block, region, from, to } = range;
1586
+ const removePrefix = (current, dispatch) => {
1587
+ dispatch?.(new Transaction(current).step(replaceInlineStep(block, region, from, to, [])));
1588
+ return true;
1589
+ };
1590
+ return runCommands(state, [removePrefix, convert2]).tr;
1591
+ }
1592
+
1593
+ // src/engine/rules/block-rules.ts
1594
+ var PARAGRAPH = ["paragraph"];
1595
+ function lineRule(name, match, attrs, kinds = PARAGRAPH) {
1596
+ return {
1597
+ name,
1598
+ match,
1599
+ blockTypes: ["MARKDOWN"],
1600
+ kinds,
1601
+ handler: (state, found, range) => convertPrefix(state, range, setBlockType("MARKDOWN", attrs(found)))
1602
+ };
1603
+ }
1604
+ var divider = {
1605
+ name: "divider",
1606
+ match: /^(?:---|[\u2013\u2014]-|\*\*\*|___)$/,
1607
+ blockTypes: ["MARKDOWN"],
1608
+ kinds: PARAGRAPH,
1609
+ handler(state, _match, range, context) {
1610
+ const { doc, schema } = state;
1611
+ const index = requireBlockIndex(doc, range.block);
1612
+ const block = doc.blocks[index];
1613
+ const content = block.regions[range.region];
1614
+ const tail = sliceInline(content, range.to, inlineLength(content));
1615
+ const empty = createBlock(schema, schema.defaultTextBlock, {}, block.indent, (context?.newId ?? newId)());
1616
+ const next = withContent(schema, empty, tail);
1617
+ const tr = new Transaction(state);
1618
+ tr.step(replaceBlockStep(createBlock(schema, "DIVIDER", {}, block.indent, block.id)));
1619
+ tr.step(insertBlocksStep(index + 1, [next]));
1620
+ return tr.setSelection(startOf(tr.doc, schema, next.id));
1621
+ }
1622
+ };
1623
+ var blockInputRules = [
1624
+ lineRule("heading", /^(#{1,6}) $/, (m) => ({ kind: "heading", level: m[1].length })),
1625
+ lineRule("bullet", /^[-*+] $/, () => ({ kind: "bullet" })),
1626
+ lineRule("ordered", /^(\d{1,9})[.)] $/, (m) => ({ kind: "ordered", start: Number(m[1]) })),
1627
+ lineRule("task", /^\[( |x|X)?\] $/, (m) => ({ kind: "task", checked: m[1] === "x" || m[1] === "X" }), [
1628
+ "paragraph",
1629
+ "bullet"
1630
+ ]),
1631
+ lineRule("quote", /^> $/, () => ({ kind: "quote" })),
1632
+ divider,
1633
+ {
1634
+ name: "math",
1635
+ match: /^\$\$ $/,
1636
+ blockTypes: ["MARKDOWN"],
1637
+ kinds: PARAGRAPH,
1638
+ handler: (state, _match, range) => convertPrefix(state, range, setBlockType("MATH"))
1639
+ },
1640
+ {
1641
+ name: "code",
1642
+ match: /^```([\w+#.-]*) $/,
1643
+ blockTypes: ["MARKDOWN"],
1644
+ kinds: PARAGRAPH,
1645
+ handler: (state, found, range) => convertPrefix(state, range, setBlockType("CODE", { language: found[1] }))
1646
+ }
1647
+ ];
1648
+
1649
+ // src/engine/markdown/autolink-href.ts
1650
+ function autolinkHref(url) {
1651
+ return sanitizeHref(/^www\./i.test(url) ? `https://${url}` : url);
1652
+ }
1653
+
1654
+ // src/engine/markdown/trim-url-end.ts
1655
+ var TRAILING_PUNCTUATION = `?!.,:;*_~'"`;
1656
+ function count(value, char) {
1657
+ return value.split(char).length - 1;
1658
+ }
1659
+ function trimUrlEnd(url) {
1660
+ let end = url.length;
1661
+ while (end > 0) {
1662
+ const last = url[end - 1];
1663
+ const kept = url.slice(0, end);
1664
+ const unbalanced = last === ")" && count(kept, ")") > count(kept, "(");
1665
+ if (!TRAILING_PUNCTUATION.includes(last) && !unbalanced) break;
1666
+ end -= 1;
1667
+ }
1668
+ return url.slice(0, end);
1669
+ }
1670
+
1671
+ // src/engine/rules/region-inline.ts
1672
+ function regionInline(state, range) {
1673
+ return state.doc.blocks[requireBlockIndex(state.doc, range.block)].regions[range.region];
1674
+ }
1675
+
1676
+ // src/engine/rules/touches-mark.ts
1677
+ function touchesMark(inline, from, to, type) {
1678
+ return sliceInline(inline, from, to).some((node) => hasMark(node.marks, type));
1679
+ }
1680
+
1681
+ // src/engine/rules/autolink.ts
1682
+ var autolinkRule = {
1683
+ name: "autolink",
1684
+ match: /(?:^|[\s(])((https?:\/\/|www\.)[^\s\uFFFC]+) $/i,
1685
+ handler(state, match, range) {
1686
+ const url = trimUrlEnd(match[1]);
1687
+ const href = url.length > match[2].length ? autolinkHref(url) : null;
1688
+ const inline = regionInline(state, range);
1689
+ const from = range.to - 1 - match[1].length;
1690
+ const to = from + url.length;
1691
+ if (href === null) return null;
1692
+ if (touchesMark(inline, from, to, "link") || touchesMark(inline, from, to, "code")) return null;
1693
+ const link = { type: "link", attrs: { href } };
1694
+ return new Transaction(state).step(addMarkStep(range.block, range.region, from, to, link));
1695
+ }
1696
+ };
1697
+
1698
+ // src/engine/rules/mark-rule.ts
1699
+ var WORD_CHARACTER = /[\p{L}\p{N}]/u;
1700
+ function markRule({ name, match, open, close, wordBounded = false, mark }) {
1701
+ return {
1702
+ name,
1703
+ match,
1704
+ handler(state, found, range) {
1705
+ const added = mark(found);
1706
+ if (added === null) return null;
1707
+ const { block, region, to } = range;
1708
+ const inline = regionInline(state, range);
1709
+ const contentTo = to - (close?.(found) ?? open);
1710
+ const contentFrom = contentTo - found[1].length;
1711
+ const from = contentFrom - open;
1712
+ if (wordBounded && WORD_CHARACTER.test(inlineText(sliceInline(inline, to, to + 1)))) return null;
1713
+ if (touchesMark(inline, from, contentFrom, "code") || touchesMark(inline, contentTo, to, "code")) return null;
1714
+ const tr = new Transaction(state);
1715
+ tr.step(addMarkStep(block, region, contentFrom, contentTo, added));
1716
+ tr.step(replaceInlineStep(block, region, contentTo, to, []));
1717
+ tr.step(replaceInlineStep(block, region, from, contentFrom, []));
1718
+ const caret = { block, region, offset: from + found[1].length };
1719
+ const carried = caretMarks(tr.doc, state.schema, null, caret);
1720
+ return tr.setSelection(textSelection(caret)).setStoredMarks(removeMark(carried, added.type));
1721
+ }
1722
+ };
1723
+ }
1724
+
1725
+ // src/engine/rules/mark-rules.ts
1726
+ function typed(name, match, open, type, wordBounded = false) {
1727
+ return markRule({ name, match, open, wordBounded, mark: () => ({ type }) });
1728
+ }
1729
+ var markInputRules = [
1730
+ typed("bold-asterisks", /\*\*([^\s*](?:[^*]*[^\s*])?)\*\*$/, 2, "bold"),
1731
+ typed("bold-underscores", /(?:^|[^\p{L}\p{N}_])__([^\s_](?:[^_]*[^\s_])?)__$/u, 2, "bold", true),
1732
+ typed("strike", /~~([^\s~](?:[^~]*[^\s~])?)~~$/, 2, "strike"),
1733
+ typed("code", /`([^\s`](?:[^`]*[^\s`])?)`$/, 1, "code"),
1734
+ typed("highlight", /==([^\s=](?:[^=]*[^\s=])?)==$/, 2, "highlight"),
1735
+ markRule({
1736
+ name: "link",
1737
+ match: /\[([^\s[\]](?:[^[\]]*[^\s[\]])?)\]\(((?:[^\s()]|\((?:[^\s()]|\([^\s()]*\))*\))+)\)$/,
1738
+ open: 1,
1739
+ close: (match) => match[2].length + 3,
1740
+ mark(match) {
1741
+ const href = sanitizeHref(match[2]);
1742
+ return href === null ? null : { type: "link", attrs: { href } };
1743
+ }
1744
+ }),
1745
+ typed("italic-asterisk", /(?:^|[^*])\*([^\s*](?:[^*]*[^\s*])?)\*$/, 1, "italic"),
1746
+ typed("italic-underscore", /(?:^|[^\p{L}\p{N}_])_([^\s_](?:[^_]*[^\s_])?)_$/u, 1, "italic", true),
1747
+ autolinkRule
1748
+ ];
1749
+
1750
+ // src/engine/rules/typography.ts
1751
+ var OPENING_CONTEXT = /[\s([{\u2018\u201C\u2014\u2013]/;
1752
+ function replacing(name, match, replacement) {
1753
+ return {
1754
+ name,
1755
+ match,
1756
+ handler(state, found, range) {
1757
+ const value = typeof replacement === "string" ? replacement : replacement(found);
1758
+ const from = range.to - (found[1] ?? found[0]).length;
1759
+ const node = text(value, marksAt(regionInline(state, range), range.to, () => true));
1760
+ return new Transaction(state).step(replaceInlineStep(range.block, range.region, from, range.to, [node]));
1761
+ }
1762
+ };
1763
+ }
1764
+ function quote(opening, closing2) {
1765
+ return (match) => {
1766
+ const before = match.input[match.index - 1];
1767
+ return before === void 0 || OPENING_CONTEXT.test(before) ? opening : closing2;
1768
+ };
1769
+ }
1770
+ function typographyRules(options = {}) {
1771
+ const { dashes = "none" } = options;
1772
+ const dash = dashes === "em" ? "\u2014" : "\u2013";
1773
+ return [
1774
+ replacing("double-quote", /"$/, quote("\u201C", "\u201D")),
1775
+ replacing("single-quote", /'$/, quote("\u2018", "\u2019")),
1776
+ replacing("ellipsis", /\.\.\.$/, "\u2026"),
1777
+ replacing("arrow-right", /->$/, "\u2192"),
1778
+ replacing("arrow-left", /<-$/, "\u2190"),
1779
+ replacing("double-arrow", /=>$/, "\u21D2"),
1780
+ replacing("copyright", /\(c\)$/, "\xA9"),
1781
+ replacing("registered", /\(r\)$/, "\xAE"),
1782
+ replacing("trademark", /\(tm\)$/, "\u2122"),
1783
+ replacing("one-half", /(?:^|[^\d/])(1\/2)$/, "\xBD"),
1784
+ replacing("one-quarter", /(?:^|[^\d/])(1\/4)$/, "\xBC"),
1785
+ replacing("three-quarters", /(?:^|[^\d/])(3\/4)$/, "\xBE"),
1786
+ ...dashes === "none" ? [] : [replacing("dash", /--$/, dash)]
1787
+ ];
1788
+ }
1789
+
1790
+ // src/engine/clipboard/html-context.ts
1791
+ var PARAGRAPH2 = { kind: "paragraph" };
1792
+ var child = (ctx, patch = {}) => ({
1793
+ ...ctx,
1794
+ ...patch,
1795
+ depth: ctx.depth + 1
1796
+ });
1797
+ function emit(sink, type, indent2, attrs, regions) {
1798
+ sink.out.push({ id: sink.newId(), type, indent: indent2, attrs, regions });
1799
+ }
1800
+ var emitText = (sink, indent2, attrs, content) => emit(sink, "MARKDOWN", indent2, attrs, { text: content });
1801
+
1802
+ // src/engine/clipboard/html-dom.ts
1803
+ var ELEMENT_NODE = 1;
1804
+ var TEXT_NODE = 3;
1805
+ var MAX_DEPTH = 100;
1806
+ var DROPPED = /* @__PURE__ */ new Set([
1807
+ "script",
1808
+ "style",
1809
+ "head",
1810
+ "meta",
1811
+ "link",
1812
+ "title",
1813
+ "template",
1814
+ "noscript",
1815
+ "svg",
1816
+ "iframe",
1817
+ "object",
1818
+ "embed",
1819
+ "canvas",
1820
+ "audio",
1821
+ "video",
1822
+ "select",
1823
+ "textarea",
1824
+ "button",
1825
+ "o:p"
1826
+ ]);
1827
+ var BLOCK_TAGS = /* @__PURE__ */ new Set([
1828
+ "address",
1829
+ "article",
1830
+ "aside",
1831
+ "blockquote",
1832
+ "body",
1833
+ "caption",
1834
+ "center",
1835
+ "dd",
1836
+ "details",
1837
+ "div",
1838
+ "dl",
1839
+ "dt",
1840
+ "fieldset",
1841
+ "figcaption",
1842
+ "figure",
1843
+ "footer",
1844
+ "form",
1845
+ "h1",
1846
+ "h2",
1847
+ "h3",
1848
+ "h4",
1849
+ "h5",
1850
+ "h6",
1851
+ "header",
1852
+ "hgroup",
1853
+ "hr",
1854
+ "li",
1855
+ "main",
1856
+ "nav",
1857
+ "ol",
1858
+ "p",
1859
+ "pre",
1860
+ "section",
1861
+ "summary",
1862
+ "table",
1863
+ "tbody",
1864
+ "td",
1865
+ "tfoot",
1866
+ "th",
1867
+ "thead",
1868
+ "tr",
1869
+ "ul"
1870
+ ]);
1871
+ var isElement = (node) => node.nodeType === ELEMENT_NODE;
1872
+ var tagName = (element) => element.localName.toLowerCase();
1873
+ var isFragmentRoot = ({ parentElement }) => parentElement !== null && tagName(parentElement) === "body";
1874
+ var isDropped = (element) => DROPPED.has(tagName(element));
1875
+ var isBlockTag = (element) => BLOCK_TAGS.has(tagName(element));
1876
+ var isBlockLevel = (element) => isBlockTag(element) || tagName(element) === "img";
1877
+ function hasClass(element, name) {
1878
+ return (element.getAttribute("class") ?? "").split(/\s+/).includes(name);
1879
+ }
1880
+ function hasBlockDescendant(node, depth = 0) {
1881
+ if (depth > MAX_DEPTH) return false;
1882
+ for (const child2 of Array.from(node.childNodes)) {
1883
+ if (!isElement(child2) || isDropped(child2)) continue;
1884
+ if (isBlockLevel(child2) || hasBlockDescendant(child2, depth + 1)) return true;
1885
+ }
1886
+ return false;
1887
+ }
1888
+ var breaksRun = (node) => isElement(node) && !isDropped(node) && (isBlockLevel(node) || hasBlockDescendant(node));
1889
+ function flatText(root) {
1890
+ const parts = [];
1891
+ const pending = [root];
1892
+ for (let node = pending.pop(); node !== void 0; node = pending.pop()) {
1893
+ if (node.nodeType === TEXT_NODE) parts.push(node.nodeValue ?? "");
1894
+ else if (!isElement(node) || !isDropped(node)) {
1895
+ for (let part = node.lastChild; part !== null; part = part.previousSibling) pending.push(part);
1896
+ }
1897
+ }
1898
+ return parts.join("");
1899
+ }
1900
+
1901
+ // src/engine/clipboard/html-code.ts
1902
+ var LANGUAGE = /(?:^|\s)(?:language|lang)-(\S+)/;
1903
+ function preText(node, depth = 0) {
1904
+ if (node.nodeType === TEXT_NODE) return node.nodeValue ?? "";
1905
+ if (!isElement(node)) return "";
1906
+ if (tagName(node) === "br") return "\n";
1907
+ if (depth > MAX_DEPTH) return flatText(node);
1908
+ const text2 = Array.from(node.childNodes).map((part) => preText(part, depth + 1)).join("");
1909
+ return depth > 0 && isBlockTag(node) && !text2.endsWith("\n") ? `${text2}
1910
+ ` : text2;
1911
+ }
1912
+ var languageOf = (element) => element === null ? void 0 : LANGUAGE.exec(element.getAttribute("class") ?? "")?.[1];
1913
+ function codeBlock(pre, ctx, sink) {
1914
+ const code = preText(pre).replace(/\r\n?/g, "\n").replace(/\n$/, "");
1915
+ const language = languageOf(pre) ?? languageOf(pre.querySelector("code")) ?? "";
1916
+ emit(sink, "CODE", ctx.indent, { language, code }, {});
1917
+ }
1918
+
1919
+ // src/engine/markdown/palette-rgb.ts
1920
+ var PALETTE_RGB = {
1921
+ text: {
1922
+ default: [[55, 53, 47]],
1923
+ gray: [[120, 119, 116], [155, 154, 151], [190, 190, 188]],
1924
+ brown: [[159, 107, 83], [100, 71, 58]],
1925
+ orange: [[217, 115, 13]],
1926
+ yellow: [[203, 145, 47], [223, 171, 1]],
1927
+ green: [[68, 131, 97], [15, 123, 108]],
1928
+ blue: [[51, 126, 169], [11, 110, 153]],
1929
+ purple: [[144, 101, 176], [105, 64, 165]],
1930
+ pink: [[193, 76, 138], [173, 26, 114]],
1931
+ red: [[212, 76, 71], [224, 62, 62]]
1932
+ },
1933
+ background: {
1934
+ default: [[255, 255, 255]],
1935
+ gray: [[241, 241, 239], [235, 236, 237]],
1936
+ brown: [[244, 238, 238], [233, 229, 227]],
1937
+ orange: [[251, 236, 221]],
1938
+ yellow: [[251, 243, 219]],
1939
+ green: [[237, 243, 236], [221, 237, 234]],
1940
+ blue: [[231, 243, 248], [221, 235, 241]],
1941
+ purple: [[244, 240, 247], [234, 228, 242]],
1942
+ pink: [[249, 238, 243], [244, 223, 235]],
1943
+ red: [[253, 235, 236], [251, 228, 228]]
1944
+ }
1945
+ };
1946
+
1947
+ // src/engine/clipboard/html-styles.ts
1948
+ var HEX = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
1949
+ var FUNCTION = /^rgba?\(([^)]*)\)$/i;
1950
+ var SEPARATORS = /[\s,/]+/;
1951
+ function parseStyle(style) {
1952
+ const declarations = /* @__PURE__ */ new Map();
1953
+ for (const declaration of (style ?? "").split(";")) {
1954
+ const colon = declaration.indexOf(":");
1955
+ if (colon < 0) continue;
1956
+ const value = declaration.slice(colon + 1).replace(/!important\s*$/i, "").trim().toLowerCase();
1957
+ declarations.set(declaration.slice(0, colon).trim().toLowerCase(), value);
1958
+ }
1959
+ return declarations;
1960
+ }
1961
+ function fontWeight(styles) {
1962
+ const value = styles.get("font-weight");
1963
+ if (value === void 0) return null;
1964
+ if (value === "bold" || value === "bolder") return 700;
1965
+ if (value === "normal") return 400;
1966
+ if (value === "lighter") return 300;
1967
+ const weight = Number(value);
1968
+ return Number.isFinite(weight) ? weight : null;
1969
+ }
1970
+ function isItalicStyle(styles) {
1971
+ const value = styles.get("font-style");
1972
+ if (value === void 0) return null;
1973
+ if (value === "normal") return false;
1974
+ return value === "italic" || value.startsWith("oblique") ? true : null;
1975
+ }
1976
+ var PRESERVING = /* @__PURE__ */ new Set(["pre", "pre-wrap", "break-spaces"]);
1977
+ var COLLAPSING = /* @__PURE__ */ new Set(["normal", "nowrap", "pre-line"]);
1978
+ var MONOSPACE = /\b(?:monospace|menlo|monaco|consolas|courier)\b/;
1979
+ function keepsWhitespace(styles) {
1980
+ const value = styles.get("white-space");
1981
+ if (value === void 0) return null;
1982
+ if (PRESERVING.has(value)) return true;
1983
+ return COLLAPSING.has(value) ? false : null;
1984
+ }
1985
+ var isMonospace = (styles) => MONOSPACE.test(styles.get("font-family") ?? "");
1986
+ function hasDecoration(styles, line2) {
1987
+ return (styles.get("text-decoration") ?? "").includes(line2) || (styles.get("text-decoration-line") ?? "").includes(line2);
1988
+ }
1989
+ function verticalAlign(styles) {
1990
+ const value = styles.get("vertical-align");
1991
+ return value === "sub" || value === "super" ? value : null;
1992
+ }
1993
+ function channel(value) {
1994
+ return value.endsWith("%") ? parseFloat(value) * 2.55 : parseFloat(value);
1995
+ }
1996
+ function fromHex(digits) {
1997
+ const full = digits.length <= 4 ? [...digits].map((digit) => digit + digit).join("") : digits;
1998
+ const byte = (at) => parseInt(full.slice(at, at + 2), 16);
1999
+ return { rgb: [byte(0), byte(2), byte(4)], alpha: full.length === 8 ? byte(6) / 255 : 1 };
2000
+ }
2001
+ function fromFunction(args) {
2002
+ const parts = args.trim().split(SEPARATORS).filter(Boolean);
2003
+ if (parts.length < 3) return null;
2004
+ const [r, g, b] = parts.slice(0, 3).map(channel);
2005
+ const alpha = parts[3] === void 0 ? 1 : parts[3].endsWith("%") ? parseFloat(parts[3]) / 100 : parseFloat(parts[3]);
2006
+ if ([r, g, b, alpha].some(Number.isNaN)) return null;
2007
+ return { rgb: [r, g, b], alpha };
2008
+ }
2009
+ function parseColor(css) {
2010
+ const value = css.trim();
2011
+ const hex = HEX.exec(value);
2012
+ const functional = FUNCTION.exec(value);
2013
+ const parsed = hex ? fromHex(hex[1]) : functional ? fromFunction(functional[1]) : null;
2014
+ return parsed === null || parsed.alpha === 0 ? null : parsed.rgb;
2015
+ }
2016
+ function distance(a, b) {
2017
+ return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2;
2018
+ }
2019
+ var MAX_PALETTE_DISTANCE = 330;
2020
+ var NEUTRAL_CHROMA = 24;
2021
+ var LIGHT_TEXT = 200;
2022
+ var WHITE_SURFACE = 244;
2023
+ var DARK_SURFACE = 128;
2024
+ var PASTEL_FLOOR = 233;
2025
+ function isSurface([r, g, b], kind) {
2026
+ const high = Math.max(r, g, b);
2027
+ const low = Math.min(r, g, b);
2028
+ const neutral = high - low <= NEUTRAL_CHROMA;
2029
+ if (kind === "text") return neutral && (high + low) / 2 > LIGHT_TEXT;
2030
+ return low >= WHITE_SURFACE || neutral && (high + low) / 2 < DARK_SURFACE;
2031
+ }
2032
+ function isGreyBackground([r, g, b]) {
2033
+ const high = Math.max(r, g, b);
2034
+ return high - Math.min(r, g, b) <= NEUTRAL_CHROMA && high < PASTEL_FLOOR;
2035
+ }
2036
+ function nearestPaletteColor(css, kind) {
2037
+ const rgb = parseColor(css);
2038
+ if (rgb === null || isSurface(rgb, kind)) return null;
2039
+ if (kind === "background" && isGreyBackground(rgb)) return "gray";
2040
+ const table = PALETTE_RGB[kind];
2041
+ let best = "default";
2042
+ let least = Infinity;
2043
+ for (const name of PALETTE) {
2044
+ for (const reference of table[name]) {
2045
+ const gap = distance(rgb, reference);
2046
+ if (gap < least) {
2047
+ least = gap;
2048
+ best = name;
2049
+ }
2050
+ }
2051
+ }
2052
+ return best === "default" || least > MAX_PALETTE_DISTANCE ** 2 ? null : best;
2053
+ }
2054
+
2055
+ // src/engine/clipboard/html-marks.ts
2056
+ function linkMark(element) {
2057
+ const href = sanitizeHref(element.getAttribute("href") ?? "");
2058
+ if (href === null || href === "") return null;
2059
+ const title = element.getAttribute("title");
2060
+ return { type: "link", attrs: title ? { href, title } : { href } };
2061
+ }
2062
+ function highlightMark(element) {
2063
+ const color = element.getAttribute("data-color") ?? "";
2064
+ return isPaletteColor(color) ? { type: "highlight", attrs: { color } } : { type: "highlight" };
2065
+ }
2066
+ function elementMarks(element, inherited) {
2067
+ const tag = tagName(element);
2068
+ const styles = parseStyle(element.getAttribute("style"));
2069
+ let marks = inherited;
2070
+ const add = (mark) => {
2071
+ marks = addMark(marks, mark);
2072
+ };
2073
+ const link = tag === "a" ? linkMark(element) : null;
2074
+ if (link !== null) add(link);
2075
+ const linked2 = hasMark(marks, "link");
2076
+ const weight = fontWeight(styles);
2077
+ if (weight !== null ? weight >= 600 : tag === "b" || tag === "strong") add({ type: "bold" });
2078
+ else if (weight !== null) marks = removeMark(marks, "bold");
2079
+ const slanted = isItalicStyle(styles);
2080
+ if (slanted ?? (tag === "i" || tag === "em")) add({ type: "italic" });
2081
+ else if (slanted === false) marks = removeMark(marks, "italic");
2082
+ if (tag === "u" || !linked2 && hasDecoration(styles, "underline")) add({ type: "underline" });
2083
+ if (tag === "s" || tag === "del" || tag === "strike" || hasDecoration(styles, "line-through")) {
2084
+ add({ type: "strike" });
2085
+ }
2086
+ if (tag === "code" || tag === "kbd") add({ type: "code" });
2087
+ const align = verticalAlign(styles);
2088
+ if (tag === "sub" || align === "sub") add({ type: "sub" });
2089
+ if (tag === "sup" || align === "super") add({ type: "sup" });
2090
+ if (tag === "mark") add(highlightMark(element));
2091
+ const pageColors = isFragmentRoot(element);
2092
+ const color = linked2 || pageColors ? null : nearestPaletteColor(styles.get("color") ?? "", "text");
2093
+ if (color !== null) add({ type: "color", attrs: { color } });
2094
+ const background = pageColors ? null : nearestPaletteColor(styles.get("background-color") ?? "", "background");
2095
+ if (background !== null) add({ type: "background", attrs: { color: background } });
2096
+ return marks;
2097
+ }
2098
+
2099
+ // src/engine/clipboard/hoist-spaces.ts
2100
+ var shared = (a, b) => a.filter((mark) => b.some((other) => sameMark(mark, other)));
2101
+ function hoistEdgeSpaces(pieces) {
2102
+ const result = [];
2103
+ pieces.forEach((piece, at) => {
2104
+ const before = pieces[at - 1];
2105
+ const after = pieces[at + 1];
2106
+ if (piece.text === " " && before !== void 0 && after !== void 0) {
2107
+ result.push({ text: " ", marks: shared(before.marks, after.marks) });
2108
+ return;
2109
+ }
2110
+ const leading = before !== void 0 && piece.text.length > 1 && piece.text.startsWith(" ");
2111
+ const trailing = after !== void 0 && piece.text.length > 1 && piece.text.endsWith(" ");
2112
+ if (leading) result.push({ text: " ", marks: shared(before.marks, piece.marks) });
2113
+ result.push({ ...piece, text: piece.text.slice(leading ? 1 : 0, trailing ? -1 : void 0) });
2114
+ if (trailing) result.push({ text: " ", marks: shared(piece.marks, after.marks) });
2115
+ });
2116
+ return result;
2117
+ }
2118
+
2119
+ // src/engine/clipboard/html-inline.ts
2120
+ var COLLAPSIBLE = /[ \t\n\r\f]+/g;
2121
+ var MSO_IGNORED = "mso-list:ignore";
2122
+ function isMsoIgnored(element) {
2123
+ return (element.getAttribute("style") ?? "").toLowerCase().replace(/\s+/g, "").includes(MSO_IGNORED);
2124
+ }
2125
+ function lastChar({ pieces }) {
2126
+ const last = pieces[pieces.length - 1];
2127
+ return last === void 0 ? "" : last.text[last.text.length - 1];
2128
+ }
2129
+ function trimEndSpace({ pieces }) {
2130
+ const last = pieces[pieces.length - 1];
2131
+ if (last === void 0 || !last.text.endsWith(" ")) return;
2132
+ const trimmed2 = last.text.slice(0, -1);
2133
+ if (trimmed2 === "") pieces.pop();
2134
+ else pieces[pieces.length - 1] = { ...last, text: trimmed2 };
2135
+ }
2136
+ function addBreak(acc, marks, preformatted) {
2137
+ acc.pendingBreak = false;
2138
+ if (acc.pieces.length === 0) return;
2139
+ if (!preformatted) trimEndSpace(acc);
2140
+ acc.pieces.push({ text: "\n", marks });
2141
+ }
2142
+ function append(acc, raw, marks, preformatted) {
2143
+ let value = preformatted ? raw.replace(/\r\n?/g, "\n") : raw.replace(COLLAPSIBLE, " ");
2144
+ if (value === "") return;
2145
+ if (acc.pendingBreak) {
2146
+ if (lastChar(acc) !== "\n") addBreak(acc, marks, preformatted);
2147
+ acc.pendingBreak = false;
2148
+ }
2149
+ const before = lastChar(acc);
2150
+ if (!preformatted && (before === "" || before === "\n" || before === " ")) value = value.replace(/^ /, "");
2151
+ if (value !== "") acc.pieces.push({ text: value, marks });
2152
+ }
2153
+ function finish({ pieces }) {
2154
+ while (pieces.length > 0) {
2155
+ const last = pieces[pieces.length - 1];
2156
+ const trimmed2 = last.text.replace(/[ \n]+$/, "");
2157
+ if (trimmed2 === "") pieces.pop();
2158
+ else {
2159
+ pieces[pieces.length - 1] = { ...last, text: trimmed2 };
2160
+ break;
2161
+ }
2162
+ }
2163
+ const inline = normalizeInline(hoistEdgeSpaces(pieces).map((piece) => text(piece.text, piece.marks)));
2164
+ return inline.every((node) => node.kind === "text" && node.text.trim() === "") ? [] : inline;
2165
+ }
2166
+ function visit(acc, node, style, depth) {
2167
+ const { marks, preformatted } = style;
2168
+ if (node.nodeType === TEXT_NODE) return append(acc, node.nodeValue ?? "", marks, preformatted);
2169
+ if (node.nodeType !== ELEMENT_NODE || !isElement(node) || isDropped(node) || isMsoIgnored(node)) return;
2170
+ const tag = tagName(node);
2171
+ if (tag === "br") return addBreak(acc, marks, preformatted);
2172
+ if (tag === "img") return;
2173
+ const next = {
2174
+ marks: elementMarks(node, marks),
2175
+ preformatted: keepsWhitespace(parseStyle(node.getAttribute("style"))) ?? preformatted
2176
+ };
2177
+ if (depth > MAX_DEPTH) return append(acc, flatText(node), next.marks, next.preformatted);
2178
+ const block = isBlockTag(node);
2179
+ if (block) acc.pendingBreak = true;
2180
+ for (const child2 of Array.from(node.childNodes)) visit(acc, child2, next, depth + 1);
2181
+ if (block) acc.pendingBreak = true;
2182
+ }
2183
+ function inlineFromNodes(nodes, style) {
2184
+ const acc = { pieces: [], pendingBreak: false };
2185
+ for (const node of nodes) visit(acc, node, style, 0);
2186
+ return finish(acc);
2187
+ }
2188
+
2189
+ // src/engine/clipboard/html-lists.ts
2190
+ var LIST_TAGS = /* @__PURE__ */ new Set(["ul", "ol"]);
2191
+ var MARKER_NUMBER = /^(\d+)[.)]$/;
2192
+ var MARKER_LETTERS = /^[a-z]{1,4}[.)]$/i;
2193
+ function checkboxState(node) {
2194
+ if (!isElement(node)) return null;
2195
+ if (tagName(node) === "input" && node.getAttribute("type")?.toLowerCase() === "checkbox") {
2196
+ return node.hasAttribute("checked");
2197
+ }
2198
+ if (hasClass(node, "checkbox-on")) return true;
2199
+ return hasClass(node, "checkbox-off") ? false : null;
2200
+ }
2201
+ function takeCheckbox(nodes) {
2202
+ const at = nodes.findIndex((node) => checkboxState(node) !== null);
2203
+ if (at < 0) return { checked: null, rest: [...nodes] };
2204
+ return { checked: checkboxState(nodes[at]), rest: nodes.filter((_, index) => index !== at) };
2205
+ }
2206
+ var isPlainWrapper = (node) => isElement(node) && ["p", "div"].includes(tagName(node)) && !hasBlockDescendant(node);
2207
+ function listItem(item, base, ctx, sink) {
2208
+ const own = takeCheckbox(Array.from(item.childNodes));
2209
+ const split = own.rest.findIndex(breaksRun);
2210
+ let content = inlineFromNodes(split < 0 ? own.rest : own.rest.slice(0, split), ctx);
2211
+ let rest = split < 0 ? [] : own.rest.slice(split);
2212
+ let checked = own.checked;
2213
+ if (content.length === 0 && rest.length > 0 && isPlainWrapper(rest[0])) {
2214
+ const inner = takeCheckbox(Array.from(rest[0].childNodes));
2215
+ checked = checked ?? inner.checked;
2216
+ content = inlineFromNodes(inner.rest, ctx);
2217
+ rest = rest.slice(1);
2218
+ }
2219
+ emitText(sink, ctx.indent, checked === null ? base : { kind: "task", checked }, content);
2220
+ sink.walk(rest, child(ctx, { indent: ctx.indent + 1, run: PARAGRAPH2 }));
2221
+ }
2222
+ function firstNumber(list2) {
2223
+ const start = Number.parseInt(list2.getAttribute("start") ?? "", 10);
2224
+ return Number.isInteger(start) && start >= 0 ? start : 1;
2225
+ }
2226
+ function list(element, ctx, sink) {
2227
+ const ordered = tagName(element) === "ol";
2228
+ const first = firstNumber(element);
2229
+ let position = 0;
2230
+ for (const node of Array.from(element.childNodes)) {
2231
+ if (isElement(node) && tagName(node) === "li") {
2232
+ listItem(node, ordered ? { kind: "ordered", start: first + position } : { kind: "bullet" }, ctx, sink);
2233
+ position++;
2234
+ } else if (isElement(node) && LIST_TAGS.has(tagName(node)) && ctx.depth <= MAX_DEPTH) {
2235
+ list(node, child(ctx, { indent: ctx.indent + 1 }), sink);
2236
+ } else {
2237
+ sink.walk([node], child(ctx));
2238
+ }
2239
+ }
2240
+ }
2241
+ function wordMarker(paragraph) {
2242
+ const ignored = Array.from(paragraph.querySelectorAll("[style]")).find(isMsoIgnored);
2243
+ const marker = (ignored?.textContent ?? "").replace(/\s+/g, "");
2244
+ const number = MARKER_NUMBER.exec(marker);
2245
+ if (number) return { kind: "ordered", start: Number(number[1]) };
2246
+ return MARKER_LETTERS.test(marker) ? { kind: "ordered", start: 1 } : { kind: "bullet" };
2247
+ }
2248
+ function wordListItem(paragraph, ctx, sink) {
2249
+ const style = (paragraph.getAttribute("style") ?? "").toLowerCase();
2250
+ const level = /mso-list:[^;]*\blevel(\d+)/.exec(style);
2251
+ const listClass = (paragraph.getAttribute("class") ?? "").toLowerCase().includes("msolistparagraph");
2252
+ if (level === null && !listClass) return false;
2253
+ const depth = level === null ? 0 : Math.max(0, Number(level[1]) - 1);
2254
+ emitText(sink, ctx.indent + depth, wordMarker(paragraph), inlineFromNodes(paragraph.childNodes, ctx));
2255
+ return true;
2256
+ }
2257
+
2258
+ // src/engine/clipboard/html-media.ts
2259
+ function imageBlock(img, caption, ctx, sink) {
2260
+ const url = sanitizeHref(img.getAttribute("src") ?? "");
2261
+ if (url === null || url === "") return;
2262
+ emit(sink, "IMAGE", ctx.indent, { storageKey: "", alt: img.getAttribute("alt") ?? "", url }, { caption });
2263
+ }
2264
+ function figure(element, ctx, sink) {
2265
+ const img = element.querySelector("img");
2266
+ if (img === null) {
2267
+ sink.walk(element.childNodes, child(ctx));
2268
+ return;
2269
+ }
2270
+ const caption = element.querySelector("figcaption");
2271
+ imageBlock(img, caption === null ? [] : inlineFromNodes(caption.childNodes, ctx), ctx, sink);
2272
+ }
2273
+
2274
+ // src/engine/clipboard/html-table.ts
2275
+ var SECTIONS = /* @__PURE__ */ new Set(["thead", "tbody", "tfoot"]);
2276
+ function rowOf(tr, head) {
2277
+ return { cells: Array.from(tr.children).filter((cell) => ["td", "th"].includes(tagName(cell))), head };
2278
+ }
2279
+ function tableRows(table) {
2280
+ const rows = [];
2281
+ for (const part of Array.from(table.children)) {
2282
+ const tag = tagName(part);
2283
+ if (tag === "tr") rows.push(rowOf(part, false));
2284
+ else if (SECTIONS.has(tag)) {
2285
+ for (const tr of Array.from(part.children)) if (tagName(tr) === "tr") rows.push(rowOf(tr, tag === "thead"));
2286
+ }
2287
+ }
2288
+ return rows;
2289
+ }
2290
+ function tableBlock(table, ctx, sink) {
2291
+ const rows = tableRows(table);
2292
+ const cols = rows.reduce((widest, row) => Math.max(widest, row.cells.length), 0);
2293
+ if (rows.length === 0 || cols === 0) return;
2294
+ const regions = {};
2295
+ rows.forEach((row, r) => {
2296
+ for (let c = 0; c < cols; c++) {
2297
+ const cell = row.cells[c];
2298
+ regions[cellKey(r, c)] = cell === void 0 ? [] : inlineFromNodes(cell.childNodes, ctx);
2299
+ }
2300
+ });
2301
+ const header = rows[0].head || rows[0].cells.some((cell) => tagName(cell) === "th");
2302
+ emit(sink, "TABLE", ctx.indent, { header, align: Array(cols).fill(null), rows: rows.length, cols }, regions);
2303
+ }
2304
+
2305
+ // src/engine/clipboard/html-blocks.ts
2306
+ var QUOTE = { kind: "quote" };
2307
+ var HEADING = /^h([1-6])$/;
2308
+ function emitRun(run, ctx, sink) {
2309
+ const inline = inlineFromNodes(run, ctx);
2310
+ if (inline.length === 0) return;
2311
+ const plain = ctx.run.kind === "heading" ? removeMarkRange(inline, 0, inlineLength(inline), "bold") : inline;
2312
+ emitText(sink, ctx.indent, ctx.run, plain);
2313
+ }
2314
+ function details(element, ctx, sink) {
2315
+ const summary = Array.from(element.children).find((part) => tagName(part) === "summary");
2316
+ const title = summary === void 0 ? [] : inlineFromNodes(summary.childNodes, ctx);
2317
+ emit(sink, "TOGGLE", ctx.indent, { open: element.hasAttribute("open") }, { text: title });
2318
+ const body = Array.from(element.childNodes).filter((part) => part !== summary);
2319
+ sink.walk(body, child(ctx, { indent: ctx.indent + 1, run: PARAGRAPH2 }));
2320
+ }
2321
+ function styledContext(element, outer) {
2322
+ const styles = parseStyle(element.getAttribute("style"));
2323
+ const keeps = keepsWhitespace(styles);
2324
+ const marks = elementMarks(element, outer.marks);
2325
+ const ctx = { ...outer, marks, preformatted: keeps ?? outer.preformatted };
2326
+ return { ctx, code: keeps === true && isMonospace(styles) };
2327
+ }
2328
+ function handleBlock(element, outer, sink) {
2329
+ const { ctx, code } = styledContext(element, outer);
2330
+ const tag = tagName(element);
2331
+ if (code && tag === "div") return codeBlock(element, ctx, sink);
2332
+ const heading = HEADING.exec(tag);
2333
+ if (heading) return sink.walk(element.childNodes, child(ctx, { run: { kind: "heading", level: Number(heading[1]) } }));
2334
+ switch (tag) {
2335
+ case "ul":
2336
+ case "ol":
2337
+ return list(element, ctx, sink);
2338
+ case "li":
2339
+ return listItem(element, { kind: "bullet" }, ctx, sink);
2340
+ case "blockquote": {
2341
+ const nested = ctx.run.kind === "quote";
2342
+ return sink.walk(element.childNodes, child(ctx, { indent: ctx.indent + (nested ? 1 : 0), run: QUOTE }));
2343
+ }
2344
+ case "pre":
2345
+ return codeBlock(element, ctx, sink);
2346
+ case "hr":
2347
+ return emit(sink, "DIVIDER", ctx.indent, {}, {});
2348
+ case "table":
2349
+ return tableBlock(element, ctx, sink);
2350
+ case "img":
2351
+ return imageBlock(element, [], ctx, sink);
2352
+ case "figure":
2353
+ return figure(element, ctx, sink);
2354
+ case "details":
2355
+ return details(element, ctx, sink);
2356
+ case "p":
2357
+ if (wordListItem(element, ctx, sink)) return;
2358
+ }
2359
+ sink.walk(element.childNodes, child(ctx));
2360
+ }
2361
+ function walkNodes(nodes, ctx, sink) {
2362
+ const all = Array.from(nodes);
2363
+ if (ctx.depth > MAX_DEPTH) return emitRun(all, ctx, sink);
2364
+ let run = [];
2365
+ const flush = () => {
2366
+ emitRun(run, ctx, sink);
2367
+ run = [];
2368
+ };
2369
+ for (const node of all) {
2370
+ if (node.nodeType === TEXT_NODE) run.push(node);
2371
+ else if (!isElement(node) || isDropped(node)) continue;
2372
+ else if (isBlockLevel(node)) {
2373
+ flush();
2374
+ handleBlock(node, ctx, sink);
2375
+ } else if (hasBlockDescendant(node)) {
2376
+ flush();
2377
+ sink.walk(node.childNodes, child(styledContext(node, ctx).ctx));
2378
+ } else run.push(node);
2379
+ }
2380
+ flush();
2381
+ }
2382
+ function blocksFromBody(body, newId2) {
2383
+ const sink = { out: [], newId: newId2, walk: (nodes, ctx) => walkNodes(nodes, ctx, sink) };
2384
+ sink.walk(body.childNodes, { indent: 0, marks: [], run: PARAGRAPH2, preformatted: false, depth: 0 });
2385
+ return sink.out;
2386
+ }
2387
+
2388
+ // src/engine/clipboard/from-html.ts
2389
+ function availableParser(parser) {
2390
+ if (parser !== void 0) return parser;
2391
+ return typeof globalThis.DOMParser === "function" ? new globalThis.DOMParser() : null;
2392
+ }
2393
+ function fromHTML(html, options = {}) {
2394
+ const parser = availableParser(options.parser);
2395
+ if (parser === null) throw new Error("fromHTML needs a DOMParser");
2396
+ const makeId = options.newId ?? newId;
2397
+ const codec = createBlockCodec(createSchema(), { newId: makeId });
2398
+ const { body } = parser.parseFromString(html, "text/html");
2399
+ return blocksFromBody(body, makeId).map((block) => codec.toQuoin(block));
2400
+ }
2401
+
2402
+ // src/engine/markdown/document/line-format.ts
2403
+ function indentLines(text2, prefix) {
2404
+ if (prefix === "") return text2;
2405
+ return text2.split("\n").map((line2) => line2 === "" ? line2 : prefix + line2).join("\n");
2406
+ }
2407
+ function hangLines(text2, width) {
2408
+ if (width === 0) return text2;
2409
+ const [first, ...rest] = text2.split("\n");
2410
+ return [first, ...rest.map((line2) => line2 === "" ? line2 : " ".repeat(width) + line2)].join("\n");
2411
+ }
2412
+ function quoteLines(text2) {
2413
+ return text2.split("\n").map((line2) => line2 === "" ? ">" : `> ${line2}`).join("\n");
2414
+ }
2415
+
2416
+ // src/engine/markdown/document/block-forms.ts
2417
+ var ALERT_NAMES = new Map(
2418
+ Object.entries(ALERT_TONES).map(([name, tone]) => [tone, name])
2419
+ );
2420
+ var DELIMITERS = /* @__PURE__ */ new Map([
2421
+ ["left", ":--"],
2422
+ ["center", ":-:"],
2423
+ ["right", "--:"]
2424
+ ]);
2425
+ var MIN_FENCE = 3;
2426
+ var FENCE_LIKE_MATH_LINE = /^ {0,3}\${2,}[ \t]*$/m;
2427
+ function calloutMarkdown(data) {
2428
+ const name = ALERT_NAMES.get(str(data.tone)) ?? "NOTE";
2429
+ const body = str(data.body);
2430
+ return quoteLines(body === "" ? `[!${name}]` : `[!${name}]
2431
+ ${body}`);
2432
+ }
2433
+ function longestRun(value, char) {
2434
+ let longest = 0;
2435
+ let run = 0;
2436
+ for (const current of value) {
2437
+ run = current === char ? run + 1 : 0;
2438
+ longest = Math.max(longest, run);
2439
+ }
2440
+ return longest;
2441
+ }
2442
+ function codeMarkdown(data) {
2443
+ const code = str(data.code);
2444
+ const info = str(data.language);
2445
+ const char = info.includes("`") ? "~" : "`";
2446
+ const fence = char.repeat(Math.max(MIN_FENCE, longestRun(code, char) + 1));
2447
+ return [fence + info.replace(/[\\&]/g, "\\$&"), ...code === "" ? [] : code.split("\n"), fence].join("\n");
2448
+ }
2449
+ function mathMarkdown(data) {
2450
+ const latex = str(data.latex);
2451
+ if (FENCE_LIKE_MATH_LINE.test(latex)) return null;
2452
+ return ["$$", ...latex === "" ? [] : latex.split("\n"), "$$"].join("\n");
2453
+ }
2454
+ var cellsOf = (row) => Array.isArray(row) ? row.map(str) : [];
2455
+ var rowMarkdown = (cells) => `| ${cells.join(" | ")} |`;
2456
+ function tableMarkdown(data) {
2457
+ const rows = Array.isArray(data.rows) ? data.rows.map(cellsOf) : [];
2458
+ const cols = rows.reduce((widest, row) => Math.max(widest, row.length), 0);
2459
+ if (cols === 0) return null;
2460
+ const aligns = Array.isArray(data.align) ? data.align : [];
2461
+ const headed = data.header !== false;
2462
+ const header = headed ? rows[0] : [];
2463
+ const body = headed ? rows.slice(1) : rows;
2464
+ const pad = (cells) => Array.from({ length: cols }, (_, col) => cells[col] ?? "");
2465
+ const delimiter = Array.from({ length: cols }, (_, col) => DELIMITERS.get(aligns[col]) ?? "---");
2466
+ return [header, delimiter, ...body].map((row) => rowMarkdown(pad(row))).join("\n");
2467
+ }
2468
+
2469
+ // src/engine/markdown/document/comment-form.ts
2470
+ var NATIVE_KEYS = /* @__PURE__ */ new Map([
2471
+ ["MARKDOWN", ["markdown"]],
2472
+ ["TOGGLE", ["markdown", "open"]],
2473
+ ["DIVIDER", []],
2474
+ ["TOC", []]
2475
+ ]);
2476
+ var hasNativeForm = (type) => NATIVE_KEYS.has(type);
2477
+ function needsComment(block, residual, schema, ctx) {
2478
+ const native = NATIVE_KEYS.get(block.type);
2479
+ if (!native) return false;
2480
+ if (residual > 0) return true;
2481
+ return Object.keys(specFor(schema, block.type).toData(block, ctx)).some((key) => !native.includes(key));
2482
+ }
2483
+
2484
+ // src/engine/markdown/document/comment-markdown.ts
2485
+ var DOUBLE_HYPHEN = /--/g;
2486
+ var LINE_SEPARATOR = /[\u2028\u2029]/g;
2487
+ function quoinComment(type, data, indent2) {
2488
+ const payload = JSON.stringify(indent2 > 0 ? { data, indent: indent2 } : { data }).replace(DOUBLE_HYPHEN, "-\\u002d").replace(LINE_SEPARATOR, (char) => `\\u${char.charCodeAt(0).toString(16)}`);
2489
+ return `<!-- quoin:${type} ${payload} -->`;
2490
+ }
2491
+
2492
+ // src/engine/markdown/document/link-forms.ts
2493
+ var oneLine = (value) => value.replace(/[\r\n]+/g, " ");
2494
+ function linked(label, href, ctx) {
2495
+ return ctx.inline.serialize([text(label || href, [{ type: "link", attrs: { href } }])]);
2496
+ }
2497
+ var assetUrl = (storageKey, ctx) => storageKey === "" ? "" : ctx.resolveAssetUrl(storageKey);
2498
+ function imageMarkdown(data, ctx) {
2499
+ const href = str(data.url) || assetUrl(str(data.storageKey), ctx);
2500
+ if (href === "") return null;
2501
+ const caption = str(data.caption);
2502
+ const alt = ctx.inline.serialize([text(oneLine(str(data.alt)))]);
2503
+ return `![${alt}${linkClose({ type: "link", attrs: caption ? { href, title: caption } : { href } }, false)}`;
2504
+ }
2505
+ function fileMarkdown(data, ctx) {
2506
+ const href = assetUrl(str(data.storageKey), ctx);
2507
+ return href === "" ? null : linked(oneLine(str(data.filename)), href, ctx);
2508
+ }
2509
+ function embedMarkdown(data, ctx) {
2510
+ const url = str(data.url);
2511
+ return url === "" ? null : linked(url, url, ctx);
2512
+ }
2513
+ function bookmarkMarkdown(data, ctx) {
2514
+ const url = str(data.url);
2515
+ return url === "" ? null : linked(oneLine(str(data.title)), url, ctx);
2516
+ }
2517
+
2518
+ // src/engine/markdown/document/block-markdown.ts
2519
+ var QUOTE_PREFIX = "> ";
2520
+ function markdownLine(block, data) {
2521
+ const kind = block.attrs.kind ?? "paragraph";
2522
+ const markdown = str(data.markdown);
2523
+ const empty = inlineLength(block.regions.text ?? []) === 0;
2524
+ if (empty && (kind === "paragraph" || kind === "quote")) return null;
2525
+ return kind === "quote" ? quoteLines(markdown.slice(QUOTE_PREFIX.length)) : markdown;
2526
+ }
2527
+ function toggleMarkdown(data) {
2528
+ return `<details${data.open === true ? " open" : ""}>
2529
+ <summary>${str(data.markdown)}</summary>`;
2530
+ }
2531
+ function coreMarkdown(block, data, env, residual) {
2532
+ switch (block.type) {
2533
+ case "MARKDOWN":
2534
+ return markdownLine(block, data);
2535
+ case "CALLOUT":
2536
+ return calloutMarkdown(data);
2537
+ case "CODE":
2538
+ return codeMarkdown(data);
2539
+ case "MATH":
2540
+ return mathMarkdown(data);
2541
+ case "DIVIDER":
2542
+ return "---";
2543
+ case "TABLE":
2544
+ return tableMarkdown(data);
2545
+ case "IMAGE":
2546
+ return imageMarkdown(data, env.ctx);
2547
+ case "FILE":
2548
+ return fileMarkdown(data, env.ctx);
2549
+ case "EMBED":
2550
+ return embedMarkdown(data, env.ctx);
2551
+ case "BOOKMARK":
2552
+ return bookmarkMarkdown(data, env.ctx);
2553
+ case "TOGGLE":
2554
+ return toggleMarkdown(data);
2555
+ case "TOC":
2556
+ return Object.keys(data).length === 0 && residual === 0 ? "<!-- quoin:toc -->" : null;
2557
+ default:
2558
+ return null;
2559
+ }
2560
+ }
2561
+ function blockMarkdown(block, env, { residual, asComment }) {
2562
+ const spec = specFor(env.schema, block.type);
2563
+ const data = spec.toData(block, env.ctx);
2564
+ const comment = () => isQuoinType(block.type) ? quoinComment(block.type, data, residual) : null;
2565
+ if (asComment || env.lossless && !hasNativeForm(block.type)) return comment();
2566
+ const written = spec.toMarkdown ? spec.toMarkdown(block, env.ctx) : coreMarkdown(block, data, env, residual);
2567
+ if (written !== null) return written;
2568
+ return env.lossless || block.type !== "MARKDOWN" ? comment() : null;
2569
+ }
2570
+
2571
+ // src/engine/markdown/document/list-context.ts
2572
+ var ITEM_KINDS2 = ["bullet", "ordered", "task"];
2573
+ var lineKindOf = (block) => block.type === "MARKDOWN" ? block.attrs.kind ?? "paragraph" : void 0;
2574
+ function openedBy(block, prefix, hang) {
2575
+ if (hang > 0) return { kind: "item", indent: block.indent, prefix, childPrefix: prefix + " ".repeat(hang) };
2576
+ if (block.type === "TOGGLE") return { kind: "toggle", indent: block.indent, prefix, childPrefix: prefix };
2577
+ return void 0;
2578
+ }
2579
+ function createListContext() {
2580
+ const stack = [];
2581
+ const runs = /* @__PURE__ */ new Map();
2582
+ function place(block, asComment) {
2583
+ const closed = [];
2584
+ while (stack.length > 0 && stack[stack.length - 1].indent >= block.indent) closed.push(stack.pop());
2585
+ const parent = stack[stack.length - 1];
2586
+ const prefix = parent?.childPrefix ?? "";
2587
+ const line2 = lineKindOf(block);
2588
+ const residual = block.indent - stack.length;
2589
+ const comment = asComment?.(residual) === true;
2590
+ for (const indent2 of [...runs.keys()]) if (indent2 > block.indent) runs.delete(indent2);
2591
+ let placed = block;
2592
+ let number = 1;
2593
+ if (line2 === "ordered") {
2594
+ number = Math.min(runs.get(block.indent) ?? orderedStart(block.attrs.start), MAX_MARKER_NUMBER);
2595
+ runs.set(block.indent, number + 1);
2596
+ if (number !== block.attrs.start) placed = { ...block, attrs: { ...block.attrs, start: number } };
2597
+ } else {
2598
+ runs.delete(block.indent);
2599
+ }
2600
+ const isItem2 = !comment && line2 !== void 0 && ITEM_KINDS2.includes(line2);
2601
+ const hang = isItem2 ? (line2 === "ordered" ? String(number).length : 0) + 2 : 0;
2602
+ const container = comment ? void 0 : openedBy(block, prefix, hang);
2603
+ if (container) stack.push(container);
2604
+ const bare = !comment && (line2 === "bullet" || line2 === "ordered") && inlineLength(block.regions.text ?? []) === 0;
2605
+ const fragile = isItem2 && (bare || line2 === "ordered" && number !== 1);
2606
+ const shown = comment ? void 0 : line2;
2607
+ return { block: placed, line: shown, prefix, hang, residual, parent, container, closed, bare, fragile, comment };
2608
+ }
2609
+ return { place, finish: () => stack.splice(0).reverse() };
2610
+ }
2611
+
2612
+ // src/engine/markdown/document/strip-comments.ts
2613
+ var hasCommentMark = (inline) => inline.some((node) => node.marks.some((mark) => mark.type === "comment"));
2614
+ function stripComments(block) {
2615
+ const regions = Object.entries(block.regions);
2616
+ if (!regions.some(([, inline]) => hasCommentMark(inline))) return block;
2617
+ return {
2618
+ ...block,
2619
+ regions: Object.fromEntries(
2620
+ regions.map(([name, inline]) => [
2621
+ name,
2622
+ hasCommentMark(inline) ? removeMarkRange(inline, 0, inlineLength(inline), "comment") : inline
2623
+ ])
2624
+ )
2625
+ };
2626
+ }
2627
+
2628
+ // src/engine/markdown/document/to-markdown.ts
2629
+ function roleOf({ line: line2, hang }) {
2630
+ if (hang > 0) return "item";
2631
+ return line2 === "quote" ? "quote" : "other";
2632
+ }
2633
+ var closing = ({ prefix }) => ({
2634
+ text: indentLines("</details>", prefix),
2635
+ role: "other",
2636
+ prefix,
2637
+ bare: false,
2638
+ fragile: false,
2639
+ leadsHtml: false
2640
+ });
2641
+ function pieceOf(text2, placement) {
2642
+ const { hang, prefix } = placement;
2643
+ return {
2644
+ text: indentLines(hangLines(text2, hang), prefix),
2645
+ role: roleOf(placement),
2646
+ prefix,
2647
+ parent: placement.parent,
2648
+ opened: placement.container,
2649
+ bare: placement.bare,
2650
+ fragile: placement.fragile,
2651
+ leadsHtml: hang > 0 && startsHtmlBlock(text2.split("\n")[0].slice(hang))
2652
+ };
2653
+ }
2654
+ function takenAsItemText(previous, { parent, line: line2 }) {
2655
+ return line2 === "paragraph" && parent !== void 0 && previous?.opened === parent && previous.bare;
2656
+ }
2657
+ function separator(previous, next) {
2658
+ const nested = next.parent !== void 0 && next.parent === previous.opened;
2659
+ if (nested && previous.bare) return "\n";
2660
+ if (previous.role === "item" && next.role === "item") {
2661
+ return nested && (next.fragile || previous.leadsHtml) ? "\n\n" : "\n";
2662
+ }
2663
+ if (previous.role === "quote" && next.role === "quote" && previous.parent === next.parent) {
2664
+ return `
2665
+ ${next.prefix}>
2666
+ `;
2667
+ }
2668
+ return "\n\n";
2669
+ }
2670
+ function join(pieces) {
2671
+ const markdown = pieces.map((piece, at) => at === 0 ? piece.text : separator(pieces[at - 1], piece) + piece.text);
2672
+ return markdown.length === 0 ? "" : `${markdown.join("")}
2673
+ `;
2674
+ }
2675
+ function toMarkdown(blocks, options = {}) {
2676
+ const schema = options.schema ?? createSchema();
2677
+ const codec = createBlockCodec(schema);
2678
+ const env = {
2679
+ schema,
2680
+ lossless: options.lossless === true,
2681
+ ctx: {
2682
+ ...codec.context,
2683
+ resolveAssetUrl: options.resolveAssetUrl ?? ((storageKey) => storageKey),
2684
+ comments: options.comments !== false
2685
+ }
2686
+ };
2687
+ const context = createListContext();
2688
+ const pieces = [];
2689
+ const closeToggles = (containers) => {
2690
+ for (const container of containers) if (container.kind === "toggle") pieces.push(closing(container));
2691
+ };
2692
+ for (const engineBlock of blocks.flatMap((block) => codec.toEngineBlocks(block))) {
2693
+ const block = env.ctx.comments ? engineBlock : stripComments(engineBlock);
2694
+ const placement = context.place(
2695
+ block,
2696
+ env.lossless ? (residual) => needsComment(block, residual, schema, env.ctx) : void 0
2697
+ );
2698
+ closeToggles(placement.closed);
2699
+ const text2 = blockMarkdown(placement.block, env, {
2700
+ residual: placement.residual,
2701
+ asComment: placement.comment || env.lossless && takenAsItemText(pieces[pieces.length - 1], placement)
2702
+ });
2703
+ if (text2) pieces.push(pieceOf(text2, placement));
2704
+ }
2705
+ closeToggles(context.finish());
2706
+ return join(pieces);
2707
+ }
2708
+
2709
+ // src/engine/render/escape-html.ts
2710
+ function escapeHtml(value) {
2711
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2712
+ }
2713
+ function escapeAttr(value) {
2714
+ return escapeHtml(value).replace(/"/g, "&quot;");
2715
+ }
2716
+
2717
+ // src/engine/render/serialize-html.ts
2718
+ var VOID = /* @__PURE__ */ new Set(["br", "hr", "img", "input"]);
2719
+ var attrsHtml = (node) => writtenAttrs(node.attrs).map(([name, value]) => value === true ? ` ${name}` : ` ${name}="${escapeAttr(value)}"`).join("");
2720
+ var serializeHtml = (nodes) => nodes.map(
2721
+ (node) => typeof node === "string" ? escapeHtml(node) : `<${node.tag}${attrsHtml(node)}>${VOID.has(node.tag) ? "" : `${node.html ?? serializeHtml(node.children)}</${node.tag}>`}`
2722
+ ).join("");
2723
+
2724
+ // src/engine/render/to-html.ts
2725
+ var toHTML = (blocks, options = {}) => serializeHtml(documentNodes(blocks, options));
2726
+
2727
+ // src/engine/render/to-plain-text.ts
2728
+ function plainTextBlocks(blocks, schema) {
2729
+ return engineBlocksOf(blocks, schema).map((block) => blockPlainText(block, schema));
2730
+ }
2731
+ function toPlainText(blocks, options = {}) {
2732
+ return plainTextBlocks(blocks, options.schema ?? createSchema()).join("\n");
2733
+ }
2734
+
2735
+ // src/engine/model/graphemes.ts
2736
+ var IntlWithSegmenter = Intl;
2737
+ var graphemeSegmenter = IntlWithSegmenter.Segmenter ? new IntlWithSegmenter.Segmenter(void 0, { granularity: "grapheme" }) : null;
2738
+ var wordSegmenter = IntlWithSegmenter.Segmenter ? new IntlWithSegmenter.Segmenter(void 0, { granularity: "word" }) : null;
2739
+ var WORD = /[\p{L}\p{N}_]+/gu;
2740
+ function boundaries(text2) {
2741
+ if (!graphemeSegmenter) {
2742
+ const result = [];
2743
+ let i = 0;
2744
+ for (const ch of text2) {
2745
+ result.push(i);
2746
+ i += ch.length;
2747
+ }
2748
+ return [...result, text2.length];
2749
+ }
2750
+ return [...[...graphemeSegmenter.segment(text2)].map((s) => s.index), text2.length];
2751
+ }
2752
+ function words(text2) {
2753
+ if (!wordSegmenter) {
2754
+ return [...text2.matchAll(WORD)].map((m) => ({ start: m.index, end: m.index + m[0].length }));
2755
+ }
2756
+ return [...wordSegmenter.segment(text2)].filter((s) => s.isWordLike).map((s) => ({ start: s.index, end: s.index + s.segment.length }));
2757
+ }
2758
+ function graphemeCount(text2) {
2759
+ return boundaries(text2).length - 1;
2760
+ }
2761
+ function wordCount(text2) {
2762
+ return words(text2).length;
2763
+ }
2764
+ function nextGraphemeOffset(text2, offset) {
2765
+ return boundaries(text2).find((b) => b > offset) ?? text2.length;
2766
+ }
2767
+ function prevGraphemeOffset(text2, offset) {
2768
+ return boundaries(text2).reverse().find((b) => b < offset) ?? 0;
2769
+ }
2770
+ function nextWordOffset(text2, offset) {
2771
+ return words(text2).find((w) => w.end > offset)?.end ?? text2.length;
2772
+ }
2773
+ function prevWordOffset(text2, offset) {
2774
+ return words(text2).reverse().find((w) => w.start < offset)?.start ?? 0;
2775
+ }
2776
+
2777
+ // src/engine/render/count-block.ts
2778
+ var counted = /* @__PURE__ */ new WeakMap();
2779
+ function countBlock(block, schema) {
2780
+ let blocks = counted.get(schema);
2781
+ if (!blocks) counted.set(schema, blocks = /* @__PURE__ */ new WeakMap());
2782
+ const known = blocks.get(block);
2783
+ if (known) return known;
2784
+ const text2 = blockPlainText(block, schema);
2785
+ const stats = { characters: graphemeCount(text2), words: wordCount(text2) };
2786
+ blocks.set(block, stats);
2787
+ return stats;
2788
+ }
2789
+
2790
+ // src/engine/render/count-text.ts
2791
+ var defaultSchema;
2792
+ function sum(blocks, schema) {
2793
+ let characters = 0;
2794
+ let words2 = 0;
2795
+ for (const block of blocks) {
2796
+ const stats = countBlock(block, schema);
2797
+ characters += stats.characters;
2798
+ words2 += stats.words;
2799
+ }
2800
+ return { characters, words: words2 };
2801
+ }
2802
+ function countText(blocks, options = {}) {
2803
+ const schema = options.schema ?? (defaultSchema ??= createSchema());
2804
+ return sum(engineBlocksOf(blocks, schema), schema);
2805
+ }
2806
+ function countDoc(doc, options = {}) {
2807
+ return sum(doc.blocks, options.schema ?? (defaultSchema ??= createSchema()));
2808
+ }
2809
+
2810
+ // src/engine/commands/block-style.ts
2811
+ function patchBlocks(state, patch, only) {
2812
+ const changing = selectedBlockIds(state.doc, state.selection).filter((id) => {
2813
+ const block = blockById(state.doc, id);
2814
+ return block !== void 0 && (only === void 0 || block.type === only) && patchChangesAttrs(block.attrs, patch);
2815
+ });
2816
+ if (changing.length === 0) return null;
2817
+ const tr = new Transaction(state);
2818
+ for (const id of changing) tr.step(setAttrsStep(id, patch));
2819
+ return tr;
2820
+ }
2821
+ function patchCommand(patch, only) {
2822
+ return (state, dispatch) => {
2823
+ const tr = patchBlocks(state, patch, only);
2824
+ if (tr === null) return false;
2825
+ dispatch?.(tr);
2826
+ return true;
2827
+ };
2828
+ }
2829
+ function setAttrs(patch) {
2830
+ return patchCommand(patch);
2831
+ }
2832
+ function setTextAlign(align) {
2833
+ return patchCommand({ align: align ?? void 0 }, "MARKDOWN");
2834
+ }
2835
+ function setBlockColor(colors) {
2836
+ const patch = {};
2837
+ for (const key of ["color", "background"]) {
2838
+ const value = colors[key];
2839
+ if (value !== void 0) patch[key] = value ?? void 0;
2840
+ }
2841
+ const valid = Object.values(patch).every((value) => value === void 0 || isPaletteColor(value));
2842
+ return valid ? patchCommand(patch, "MARKDOWN") : () => false;
2843
+ }
2844
+
2845
+ // src/engine/commands/line-join.ts
2846
+ var LINE_JOIN = "lineJoin";
2847
+
2848
+ // src/engine/commands/paragraph-step.ts
2849
+ var PARAGRAPH3 = { kind: "paragraph", level: void 0, checked: void 0, start: void 0 };
2850
+ function paragraphStep(schema, block, region) {
2851
+ if (block.type === "MARKDOWN") return setAttrsStep(block.id, PARAGRAPH3);
2852
+ const paragraph = createBlock(schema, schema.defaultTextBlock, {}, block.indent, block.id);
2853
+ return replaceBlockStep(withContent(schema, paragraph, block.regions[region]));
2854
+ }
2855
+
2856
+ // src/engine/commands/delete-at-boundary.ts
2857
+ function textCaret(state) {
2858
+ const { doc, schema, selection } = state;
2859
+ if (selection?.kind !== "text" || !isCollapsed(selection)) return null;
2860
+ const pos = selection.head;
2861
+ const index = requireBlockIndex(doc, pos.block);
2862
+ const block = doc.blocks[index];
2863
+ const inFlow2 = isTextFlow(schema, block) && pos.region === primaryRegion(schema, block);
2864
+ return inFlow2 ? { block, index, pos } : null;
2865
+ }
2866
+ function joinInto(tr, schema, target, source) {
2867
+ const targetRegion = primaryRegion(schema, target) ?? "";
2868
+ tr.step(joinBlocksStep(target.id, targetRegion, source.id, primaryRegion(schema, source) ?? ""));
2869
+ capIndents(tr, requireBlockIndex(tr.doc, target.id) + 1);
2870
+ const offset = regionLength(target, targetRegion);
2871
+ tr.setSelection(textSelection({ block: target.id, region: targetRegion, offset }));
2872
+ }
2873
+ function joinIntoSummary(tr, schema, summary, source) {
2874
+ const region = primaryRegion(schema, summary) ?? "";
2875
+ const offset = regionLength(summary, region);
2876
+ const index = requireBlockIndex(tr.doc, source.id);
2877
+ const content = source.regions[primaryRegion(schema, source) ?? ""];
2878
+ if (content.length > 0) tr.step(replaceInlineStep(summary.id, region, offset, offset, content));
2879
+ tr.step(removeBlocksStep(index, 1));
2880
+ capIndents(tr, index);
2881
+ tr.setSelection(textSelection({ block: summary.id, region, offset }));
2882
+ }
2883
+ var deleteAtStart = (state, dispatch) => {
2884
+ const caret = textCaret(state);
2885
+ if (caret === null || caret.pos.offset !== 0) return false;
2886
+ const { doc, schema } = state;
2887
+ const { block, index, pos } = caret;
2888
+ const previous = doc.blocks[index - 1];
2889
+ const hidden = hidingToggle(doc, index);
2890
+ const tr = new Transaction(state).setMeta(LINE_JOIN, true);
2891
+ if (!isParagraph(block)) {
2892
+ tr.step(paragraphStep(schema, block, pos.region));
2893
+ tr.setSelection(startOf(tr.doc, schema, block.id));
2894
+ } else if (block.indent > 0) {
2895
+ shiftSubtree(tr, index, -1);
2896
+ } else if (previous === void 0) {
2897
+ return false;
2898
+ } else if (hidden !== null) {
2899
+ joinIntoSummary(tr, schema, doc.blocks[hidden], block);
2900
+ } else if (isTextFlow(schema, previous)) {
2901
+ joinInto(tr, schema, previous, block);
2902
+ } else {
2903
+ if (regionLength(block, pos.region) === 0) tr.step(removeBlocksStep(index, 1));
2904
+ tr.setSelection(nodeSelection(previous.id));
2905
+ }
2906
+ dispatch?.(tr);
2907
+ return true;
2908
+ };
2909
+ var deleteAtEnd = (state, dispatch) => {
2910
+ const caret = textCaret(state);
2911
+ if (caret === null || caret.pos.offset !== regionLength(caret.block, caret.pos.region)) return false;
2912
+ const { doc, schema } = state;
2913
+ const { block, index, pos } = caret;
2914
+ const nextIndex = isClosedToggle(block) ? descendantsEnd(doc, index) : index + 1;
2915
+ const next = doc.blocks[nextIndex];
2916
+ if (next === void 0) return false;
2917
+ const tr = new Transaction(state).setMeta(LINE_JOIN, true);
2918
+ if (isTextFlow(schema, next) && nextIndex > index + 1) {
2919
+ joinIntoSummary(tr, schema, block, next);
2920
+ } else if (isTextFlow(schema, next)) {
2921
+ joinInto(tr, schema, block, next);
2922
+ } else {
2923
+ if (isParagraph(block) && pos.offset === 0) {
2924
+ tr.step(removeBlocksStep(index, 1));
2925
+ capIndents(tr, index);
2926
+ }
2927
+ tr.setSelection(nodeSelection(next.id));
2928
+ }
2929
+ dispatch?.(tr);
2930
+ return true;
2931
+ };
2932
+
2933
+ // src/engine/commands/delete-commands.ts
2934
+ var deleteSelection = (state, dispatch) => {
2935
+ const { doc, schema, selection } = state;
2936
+ if (selection?.kind !== "text") return deleteBlocks(state, dispatch);
2937
+ if (isCollapsed(selection)) return false;
2938
+ const tr = new Transaction(state);
2939
+ const { from, to } = orderedRange(doc, selection.anchor, selection.head);
2940
+ dispatch?.(tr.setSelection(textSelection(deleteRange(tr, schema, from, to))));
2941
+ return true;
2942
+ };
2943
+ function deleteReaching(forward, reach) {
2944
+ return (state, dispatch) => {
2945
+ const { selection } = state;
2946
+ if (selection?.kind !== "text" || !isCollapsed(selection)) return deleteSelection(state, dispatch);
2947
+ const { head } = selection;
2948
+ const value = inlineText(state.doc.blocks[requireBlockIndex(state.doc, head.block)].regions[head.region]);
2949
+ if (forward ? head.offset >= value.length : head.offset <= 0) {
2950
+ return (forward ? deleteAtEnd : deleteAtStart)(state, dispatch);
2951
+ }
2952
+ const other = reach(value, head.offset);
2953
+ const from = Math.min(head.offset, other);
2954
+ const to = Math.max(head.offset, other);
2955
+ const tr = new Transaction(state).setOrigin("input");
2956
+ tr.step(replaceInlineStep(head.block, head.region, from, to, []));
2957
+ dispatch?.(tr.setSelection(textSelection({ ...head, offset: from })));
2958
+ return true;
2959
+ };
2960
+ }
2961
+ var lineStart = (value, offset) => {
2962
+ const start = value.lastIndexOf("\n", offset - 1) + 1;
2963
+ return start === offset ? offset - 1 : start;
2964
+ };
2965
+ var lineEnd = (value, offset) => {
2966
+ const found = value.indexOf("\n", offset);
2967
+ const end = found === -1 ? value.length : found;
2968
+ return end === offset ? offset + 1 : end;
2969
+ };
2970
+ var deleteBackward = deleteReaching(false, prevGraphemeOffset);
2971
+ var deleteForward = deleteReaching(true, nextGraphemeOffset);
2972
+ var deleteWordBackward = deleteReaching(false, prevWordOffset);
2973
+ var deleteWordForward = deleteReaching(true, nextWordOffset);
2974
+ var deleteToLineStart = deleteReaching(false, lineStart);
2975
+ var deleteToLineEnd = deleteReaching(true, lineEnd);
2976
+
2977
+ // src/engine/commands/hard-break.ts
2978
+ var insertHardBreak = insertText("\n", { origin: "command" });
2979
+
2980
+ // src/engine/commands/move-blocks-to.ts
2981
+ function movedRuns(doc, ids) {
2982
+ const moved2 = new Array(doc.blocks.length).fill(false);
2983
+ for (const id of ids) {
2984
+ const index = doc.index.get(id);
2985
+ if (index === void 0) return null;
2986
+ moved2.fill(true, index, descendantsEnd(doc, index));
2987
+ }
2988
+ const runs = [];
2989
+ doc.blocks.forEach((block, index) => {
2990
+ if (!moved2[index]) return;
2991
+ if (index > 0 && moved2[index - 1]) runs[runs.length - 1].push(block.id);
2992
+ else runs.push([block.id]);
2993
+ });
2994
+ return runs;
2995
+ }
2996
+ function movedBlockIds(doc, ids) {
2997
+ return movedRuns(doc, ids)?.flat() ?? null;
2998
+ }
2999
+ function gather(tr, runs, above) {
3000
+ let anchor = above;
3001
+ for (const run of runs) {
3002
+ const from = requireBlockIndex(tr.doc, run[0]);
3003
+ const at = anchor === null ? -1 : requireBlockIndex(tr.doc, anchor);
3004
+ const to = at < from ? at + 1 : at + 1 - run.length;
3005
+ if (to !== from) tr.step(moveBlocksStep(from, run.length, to));
3006
+ anchor = run[run.length - 1];
3007
+ }
3008
+ }
3009
+ function reindent(tr, start, count2, indent2) {
3010
+ const delta = indent2 - tr.doc.blocks[start].indent;
3011
+ for (const block of tr.doc.blocks.slice(start, start + count2)) {
3012
+ const next = Math.max(0, block.indent + delta);
3013
+ if (next !== block.indent) tr.step(setIndentStep(block.id, next));
3014
+ }
3015
+ capIndents(tr, start, start + count2);
3016
+ }
3017
+ function moveBlocksTo(ids, toIndex, indent2) {
3018
+ return (state, dispatch) => {
3019
+ const { doc } = state;
3020
+ const runs = movedRuns(doc, ids);
3021
+ if (runs === null || runs.length === 0) return false;
3022
+ const moved2 = new Set(runs.flat());
3023
+ const rest = doc.blocks.filter((block) => !moved2.has(block.id));
3024
+ if (toIndex < 0 || toIndex > rest.length) return false;
3025
+ const above = toIndex > 0 ? rest[toIndex - 1] : null;
3026
+ const tr = new Transaction(state);
3027
+ gather(tr, runs, above?.id ?? null);
3028
+ const first = runs[0][0];
3029
+ const last = runs[runs.length - 1];
3030
+ const clamped = Math.min(Math.max(indent2, 0), above ? above.indent + 1 : 0);
3031
+ reindent(tr, requireBlockIndex(tr.doc, first), moved2.size, clamped);
3032
+ if (!tr.docChanged) return false;
3033
+ dispatch?.(tr.setSelection(blockRangeSelection(first, last[last.length - 1])));
3034
+ return true;
3035
+ };
3036
+ }
3037
+
3038
+ // src/engine/commands/selection-commands.ts
3039
+ var selectAll = (state, dispatch) => {
3040
+ const { doc, selection } = state;
3041
+ if (doc.blocks.length === 0) return false;
3042
+ let next = blockRangeSelection(doc.blocks[0].id, doc.blocks[doc.blocks.length - 1].id);
3043
+ if (selection?.kind === "text") {
3044
+ const { anchor, head } = selection;
3045
+ const length = inlineLength(doc.blocks[requireBlockIndex(doc, head.block)].regions[head.region] ?? []);
3046
+ const withinRegion = anchor.block === head.block && anchor.region === head.region;
3047
+ const covers = Math.min(anchor.offset, head.offset) === 0 && Math.max(anchor.offset, head.offset) === length;
3048
+ if (withinRegion && !covers) next = textSelection({ ...head, offset: 0 }, { ...head, offset: length });
3049
+ }
3050
+ dispatch?.(new Transaction(state).setSelection(next));
3051
+ return true;
3052
+ };
3053
+ var selectBlock = (state, dispatch) => {
3054
+ const id = headBlockId(state.selection);
3055
+ if (id === null) return false;
3056
+ dispatch?.(new Transaction(state).setSelection(blockRangeSelection(id)));
3057
+ return true;
3058
+ };
3059
+ function setSelection(selection) {
3060
+ return (state, dispatch) => {
3061
+ dispatch?.(new Transaction(state).setSelection(selection));
3062
+ return true;
3063
+ };
3064
+ }
3065
+
3066
+ // src/engine/commands/line-split.ts
3067
+ var LINE_SPLIT = "lineSplit";
3068
+
3069
+ // src/engine/commands/split-step.ts
3070
+ function splitStep(schema, block, region, offset, id) {
3071
+ const next = specOf(schema, block).splitAttrs?.(block.attrs) ?? { type: block.type, attrs: block.attrs };
3072
+ const created = createBlock(schema, next.type, next.attrs, block.indent, id);
3073
+ const tailRegion = primaryRegion(schema, created) ?? "";
3074
+ const { type, attrs, indent: indent2, regions } = created;
3075
+ const step = splitBlockStep(block.id, region, offset, { id, type, attrs, indent: indent2, region: tailRegion, regions });
3076
+ return { step, tail: { id, region: tailRegion } };
3077
+ }
3078
+
3079
+ // src/engine/commands/split-at.ts
3080
+ function enterInHardBreakBlock({ tr, schema, block, index, pos, makeId }) {
3081
+ const length = regionLength(block, pos.region);
3082
+ if (pos.offset === length && inlineText(block.regions[pos.region]).endsWith("\n")) {
3083
+ tr.step(replaceInlineStep(block.id, pos.region, length - 1, length, []));
3084
+ const paragraph = createBlock(schema, schema.defaultTextBlock, {}, block.indent, makeId());
3085
+ tr.step(insertBlocksStep(descendantsEnd(tr.doc, index), [paragraph]));
3086
+ tr.setSelection(startOf(tr.doc, schema, paragraph.id));
3087
+ } else {
3088
+ tr.setSelection(textSelection(insertTextAt(tr, schema, pos, "\n")));
3089
+ }
3090
+ }
3091
+ function leaveItem({ tr, schema, block, index, pos }) {
3092
+ if (block.indent > 0) shiftSubtree(tr, index, -1);
3093
+ else tr.step(paragraphStep(schema, block, pos.region));
3094
+ tr.setSelection(startOf(tr.doc, schema, block.id));
3095
+ }
3096
+ function insertEmptyBefore({ tr, schema, block, index, pos, makeId }) {
3097
+ const next = specOf(schema, block).splitAttrs?.(block.attrs);
3098
+ const empty = createBlock(schema, next?.type ?? schema.defaultTextBlock, next?.attrs, block.indent, makeId());
3099
+ tr.step(insertBlocksStep(index, [empty]));
3100
+ tr.setSelection(textSelection(pos));
3101
+ }
3102
+ function splitToggle({ tr, schema, block, index, pos, makeId }) {
3103
+ const length = regionLength(block, pos.region);
3104
+ const open = block.attrs.open === true;
3105
+ const tail = sliceInline(block.regions[pos.region], pos.offset, length);
3106
+ const empty = createBlock(schema, schema.defaultTextBlock, {}, open ? block.indent + 1 : block.indent, makeId());
3107
+ if (pos.offset < length) tr.step(replaceInlineStep(block.id, pos.region, pos.offset, length, []));
3108
+ tr.step(insertBlocksStep(open ? index + 1 : descendantsEnd(tr.doc, index), [withContent(schema, empty, tail)]));
3109
+ tr.setSelection(startOf(tr.doc, schema, empty.id));
3110
+ }
3111
+ function splitInTwo({ tr, schema, block, pos, makeId }) {
3112
+ const { step, tail } = splitStep(schema, block, pos.region, pos.offset, makeId());
3113
+ tr.step(step);
3114
+ tr.setSelection(textSelection({ block: tail.id, region: tail.region, offset: 0 }));
3115
+ }
3116
+ function splitAt(tr, schema, pos, collapsed, makeId) {
3117
+ const index = requireBlockIndex(tr.doc, pos.block);
3118
+ const block = tr.doc.blocks[index];
3119
+ if (!isTextFlow(schema, block) || pos.region !== primaryRegion(schema, block)) return false;
3120
+ const target = { tr, schema, block, index, pos, makeId };
3121
+ const length = regionLength(block, pos.region);
3122
+ if (specOf(schema, block).enter === "hardBreak") enterInHardBreakBlock(target);
3123
+ else if (collapsed && length === 0 && isItem(block)) leaveItem(target);
3124
+ else if (pos.offset === 0 && length > 0) insertEmptyBefore(target);
3125
+ else if (block.type === "TOGGLE") splitToggle(target);
3126
+ else splitInTwo(target);
3127
+ return true;
3128
+ }
3129
+
3130
+ // src/engine/commands/split-block.ts
3131
+ function splitBlock(options = {}) {
3132
+ const makeId = options.newId ?? newId;
3133
+ return (state, dispatch) => {
3134
+ const { doc, schema, selection } = state;
3135
+ if (selection === null || selection.kind === "blocks") return false;
3136
+ const tr = new Transaction(state).setMeta(LINE_SPLIT, true);
3137
+ if (selection.kind === "node") {
3138
+ const index = requireBlockIndex(doc, selection.block);
3139
+ const paragraph = createBlock(schema, schema.defaultTextBlock, {}, doc.blocks[index].indent, makeId());
3140
+ tr.step(insertBlocksStep(descendantsEnd(doc, index), [paragraph]));
3141
+ tr.setSelection(startOf(tr.doc, schema, paragraph.id));
3142
+ } else {
3143
+ const { from, to } = orderedRange(doc, selection.anchor, selection.head);
3144
+ const caret = deleteRange(tr, schema, from, to, makeId);
3145
+ if (!splitAt(tr, schema, caret, isCollapsed(selection), makeId)) return false;
3146
+ }
3147
+ dispatch?.(tr);
3148
+ return true;
3149
+ };
3150
+ }
3151
+
3152
+ // src/engine/commands/chain.ts
3153
+ var coreCommandFactories = {
3154
+ insertText,
3155
+ insertHardBreak: () => insertHardBreak,
3156
+ deleteSelection: () => deleteSelection,
3157
+ deleteBackward: () => deleteBackward,
3158
+ deleteForward: () => deleteForward,
3159
+ deleteWordBackward: () => deleteWordBackward,
3160
+ deleteWordForward: () => deleteWordForward,
3161
+ deleteToLineStart: () => deleteToLineStart,
3162
+ deleteToLineEnd: () => deleteToLineEnd,
3163
+ splitBlock,
3164
+ undo: () => undo,
3165
+ redo: () => redo,
3166
+ undoInputRule: () => undoInputRule,
3167
+ toggleMark,
3168
+ setMark,
3169
+ unsetMark,
3170
+ setLink,
3171
+ unsetLink: () => unsetLink,
3172
+ clearFormatting: () => clearFormatting,
3173
+ insertAtom,
3174
+ insertMention,
3175
+ setBlockType,
3176
+ setAttrs,
3177
+ toggleTask: () => toggleTask,
3178
+ indent: () => indent,
3179
+ outdent: () => outdent,
3180
+ moveBlocksUp: () => moveBlocksUp,
3181
+ moveBlocksDown: () => moveBlocksDown,
3182
+ moveBlocksTo,
3183
+ duplicateBlocks,
3184
+ deleteBlocks: () => deleteBlocks,
3185
+ insertBlockAfter,
3186
+ setTextAlign,
3187
+ setBlockColor,
3188
+ selectAll: () => selectAll,
3189
+ selectBlock: () => selectBlock,
3190
+ setSelection
3191
+ };
3192
+ function createCommandHelpers(getState, dispatch, factories, live = () => true) {
3193
+ const names = Object.keys(factories);
3194
+ const bind = (send) => Object.fromEntries(
3195
+ names.map((name) => [name, (...args) => live() && factories[name](...args)(getState(), send)])
3196
+ );
3197
+ const commands = bind(dispatch);
3198
+ const dryRun = bind();
3199
+ function chain() {
3200
+ const queue = [];
3201
+ const built = {
3202
+ run() {
3203
+ if (!live()) return false;
3204
+ const { ok, tr } = runCommands(getState(), queue);
3205
+ if (ok && tr !== null) dispatch(tr);
3206
+ return ok;
3207
+ }
3208
+ };
3209
+ for (const name of names) {
3210
+ built[name] = (...args) => {
3211
+ queue.push(factories[name](...args));
3212
+ return built;
3213
+ };
3214
+ }
3215
+ return built;
3216
+ }
3217
+ return { commands, chain, can: () => dryRun };
3218
+ }
3219
+
3220
+ // src/engine/clipboard/slice-blocks.ts
3221
+ function freshBlocks(pieces, baseIndent, makeId) {
3222
+ const least = pieces.reduce((smallest, block) => Math.min(smallest, block.indent), Infinity);
3223
+ return pieces.map((block) => ({ ...block, id: makeId(), indent: block.indent - least + baseIndent }));
3224
+ }
3225
+
3226
+ // src/engine/clipboard/paste-at-caret.ts
3227
+ function contentOf(schema, block) {
3228
+ return block.regions[primaryRegion(schema, block) ?? ""] ?? [];
3229
+ }
3230
+ function insertAt(tr, pos, content) {
3231
+ if (content.length > 0) tr.step(replaceInlineStep(pos.block, pos.region, pos.offset, pos.offset, content));
3232
+ }
3233
+ function insertCapped(tr, at, blocks) {
3234
+ tr.step(insertBlocksStep(at, blocks));
3235
+ capIndents(tr, at, at + blocks.length);
3236
+ }
3237
+ function insertClosed(tr, schema, at, blocks) {
3238
+ insertCapped(tr, at, blocks);
3239
+ tr.setSelection(endOf(tr.doc, schema, blocks[blocks.length - 1].id));
3240
+ }
3241
+ function replaceEmpty(tr, schema, index, blocks) {
3242
+ tr.step(insertBlocksStep(index, blocks));
3243
+ tr.step(removeBlocksStep(index + blocks.length, 1));
3244
+ capIndents(tr, index, index + blocks.length);
3245
+ tr.setSelection(endOf(tr.doc, schema, blocks[blocks.length - 1].id));
3246
+ }
3247
+ function pasteAtCaret(tr, schema, caret, ends, pieces, makeId) {
3248
+ const index = requireBlockIndex(tr.doc, caret.block);
3249
+ const current = tr.doc.blocks[index];
3250
+ const { region } = caret;
3251
+ const first = pieces[0];
3252
+ const last = pieces[pieces.length - 1];
3253
+ const textual = (block) => isTextFlow(schema, block);
3254
+ if (pieces.length === 1 && ends.openStart && ends.openEnd && textual(first)) {
3255
+ const content = contentOf(schema, first);
3256
+ insertAt(tr, caret, content);
3257
+ tr.setSelection(textSelection({ ...caret, offset: caret.offset + inlineLength(content) }));
3258
+ return;
3259
+ }
3260
+ const fresh = freshBlocks(pieces, current.indent, makeId);
3261
+ if (!textual(current) || region !== primaryRegion(schema, current)) {
3262
+ insertClosed(tr, schema, descendantsEnd(tr.doc, index), fresh);
3263
+ return;
3264
+ }
3265
+ const keepsTargetType = ends.plainText === true && ends.openStart && textual(first);
3266
+ if (regionLength(current, region) === 0 && !keepsTargetType) {
3267
+ replaceEmpty(tr, schema, index, fresh);
3268
+ return;
3269
+ }
3270
+ const atEnd = caret.offset === regionLength(current, region);
3271
+ const atStart = caret.offset === 0;
3272
+ const mergeFirst = ends.openStart && textual(first) && (atEnd || !atStart);
3273
+ const mergeLast = ends.openEnd && textual(last) && !atEnd && !(mergeFirst && pieces.length === 1);
3274
+ const middle = fresh.slice(mergeFirst ? 1 : 0, mergeLast ? fresh.length - 1 : fresh.length);
3275
+ const lastContent = mergeLast ? contentOf(schema, fresh[fresh.length - 1]) : [];
3276
+ if (atStart && !atEnd) {
3277
+ if (middle.length > 0) insertCapped(tr, index, middle);
3278
+ insertAt(tr, { ...caret, offset: 0 }, lastContent);
3279
+ tr.setSelection(textSelection({ ...caret, offset: inlineLength(lastContent) }));
3280
+ return;
3281
+ }
3282
+ const firstContent = mergeFirst ? contentOf(schema, fresh[0]) : [];
3283
+ insertAt(tr, caret, firstContent);
3284
+ const offset = caret.offset + inlineLength(firstContent);
3285
+ const tail = !atEnd && (mergeLast || middle.length > 0) ? splitStep(schema, current, region, offset, makeId()) : null;
3286
+ if (tail !== null) tr.step(tail.step);
3287
+ if (middle.length > 0) insertCapped(tr, index + 1, middle);
3288
+ if (tail !== null && mergeLast) {
3289
+ insertAt(tr, { block: tail.tail.id, region: tail.tail.region, offset: 0 }, lastContent);
3290
+ tr.setSelection(textSelection({ block: tail.tail.id, region: tail.tail.region, offset: inlineLength(lastContent) }));
3291
+ } else if (middle.length > 0) {
3292
+ tr.setSelection(endOf(tr.doc, schema, middle[middle.length - 1].id));
3293
+ } else {
3294
+ tr.setSelection(textSelection({ ...caret, offset }));
3295
+ }
3296
+ }
3297
+
3298
+ // src/engine/clipboard/insert-slice.ts
3299
+ function replaceSelected(tr, schema, selection, pieces, makeId) {
3300
+ const range = selectedBlockRange(tr.doc, selection);
3301
+ if (range === null) return;
3302
+ const fresh = freshBlocks(pieces, tr.doc.blocks[range.start].indent, makeId);
3303
+ tr.step(insertBlocksStep(range.end, fresh));
3304
+ tr.step(removeBlocksStep(range.start, range.end - range.start));
3305
+ capIndents(tr, range.start, range.start + fresh.length);
3306
+ tr.setSelection(endOf(tr.doc, schema, fresh[fresh.length - 1].id));
3307
+ }
3308
+ function appendAtEnd(tr, schema, pieces, makeId) {
3309
+ const at = tr.doc.blocks.length;
3310
+ const fresh = freshBlocks(pieces, 0, makeId);
3311
+ tr.step(insertBlocksStep(at, fresh));
3312
+ capIndents(tr, at, at + fresh.length);
3313
+ tr.setSelection(endOf(tr.doc, schema, fresh[fresh.length - 1].id));
3314
+ }
3315
+ function insertSlice(slice, options = {}) {
3316
+ const makeId = options.newId ?? newId;
3317
+ return (state, dispatch) => {
3318
+ const { doc, schema, selection } = state;
3319
+ const codec = createBlockCodec(schema, { newId: makeId });
3320
+ const pieces = slice.blocks.flatMap((block) => codec.toEngineBlocks(block));
3321
+ if (pieces.length === 0) return false;
3322
+ const tr = new Transaction(state).setOrigin("paste");
3323
+ if (selection === null) {
3324
+ appendAtEnd(tr, schema, pieces, makeId);
3325
+ } else if (selection.kind === "text") {
3326
+ const { from, to } = orderedRange(doc, selection.anchor, selection.head);
3327
+ pasteAtCaret(tr, schema, deleteRange(tr, schema, from, to, makeId), slice, pieces, makeId);
3328
+ } else {
3329
+ replaceSelected(tr, schema, selection, pieces, makeId);
3330
+ }
3331
+ dispatch?.(tr);
3332
+ return true;
3333
+ };
3334
+ }
3335
+
3336
+ // src/engine/clipboard/paste-url.ts
3337
+ var HTTP_URL = /^(?:https?:\/\/\S|www\.[^\s.])\S*$/i;
3338
+ var LINKABLE_URL = /^(?:https?:\/\/\S|mailto:\S|www\.[^\s.])\S*$/i;
3339
+ function loneUrl(text2, schemes = "http") {
3340
+ const url = text2.trim();
3341
+ return (schemes === "http" ? HTTP_URL : LINKABLE_URL).test(url) ? url : null;
3342
+ }
3343
+ function pastedLink(text2, schemes = "http") {
3344
+ const token = loneUrl(text2, schemes);
3345
+ if (token === null) return null;
3346
+ const url = trimUrlEnd(token);
3347
+ const href = loneUrl(url, schemes) === null ? null : autolinkHref(url);
3348
+ return href === null ? null : { url, href, rest: token.slice(url.length) };
3349
+ }
3350
+
3351
+ // src/engine/clipboard/paste-link.ts
3352
+ function pasteLinkOverSelection(text2) {
3353
+ const href = pastedLink(text2, "linkable")?.href ?? null;
3354
+ const link = setLink(href ?? "");
3355
+ return (state, dispatch) => {
3356
+ const { selection } = state;
3357
+ if (href === null || selection?.kind !== "text" || isCollapsed(selection)) return false;
3358
+ return link(state, dispatch && ((tr) => dispatch(tr.setOrigin("paste"))));
3359
+ };
3360
+ }
3361
+
3362
+ // src/engine/rules/enter-rules.ts
3363
+ var FENCE = /^```([\w+#.-]*)$/;
3364
+ function fenceTransaction(state) {
3365
+ const { selection, doc } = state;
3366
+ if (selection?.kind !== "text" || !isCollapsed(selection)) return null;
3367
+ const { block: id, region, offset } = selection.head;
3368
+ const block = blockById(doc, id);
3369
+ if (block === void 0 || !isParagraph(block) || offset !== regionLength(block, region)) return null;
3370
+ const fence = FENCE.exec(inlineText(block.regions[region]));
3371
+ if (fence === null) return null;
3372
+ return convertPrefix(state, { block: id, region, from: 0, to: offset }, setBlockType("CODE", { language: fence[1] }));
3373
+ }
3374
+ function splitBlockWithRules(options = {}) {
3375
+ const split = splitBlock(options);
3376
+ return (state, dispatch) => {
3377
+ const tr = fenceTransaction(state);
3378
+ if (tr === null) return split(state, dispatch);
3379
+ dispatch?.(tr);
3380
+ return true;
3381
+ };
3382
+ }
3383
+
3384
+ // src/engine/extension/core-keymap.ts
3385
+ var HEADINGS = [1, 2, 3, 4, 5, 6];
3386
+ var line = (attrs) => setBlockType("MARKDOWN", attrs);
3387
+ var firstThatApplies = (...commands) => (state, dispatch) => commands.some((command) => command(state, dispatch));
3388
+ function coreKeymap(options = {}) {
3389
+ return {
3390
+ Enter: splitBlockWithRules(options),
3391
+ "Shift-Enter": insertHardBreak,
3392
+ Backspace: firstThatApplies(undoInputRule, deleteBackward),
3393
+ Delete: deleteForward,
3394
+ Tab: indent,
3395
+ "Shift-Tab": outdent,
3396
+ "Mod-a": selectAll,
3397
+ Escape: selectBlock,
3398
+ "Mod-Shift-ArrowUp": moveBlocksUp,
3399
+ "Mod-Shift-ArrowDown": moveBlocksDown,
3400
+ "Mod-d": duplicateBlocks(options),
3401
+ "Mod-Alt-0": line({ kind: "paragraph" }),
3402
+ ...Object.fromEntries(HEADINGS.map((level) => [`Mod-Alt-${level}`, line({ kind: "heading", level })])),
3403
+ "Mod-Shift-7": line({ kind: "ordered" }),
3404
+ "Mod-Shift-8": line({ kind: "bullet" }),
3405
+ "Mod-Shift-9": line({ kind: "task" }),
3406
+ "Mod-b": toggleMark("bold"),
3407
+ "Mod-i": toggleMark("italic"),
3408
+ "Mod-u": toggleMark("underline"),
3409
+ "Mod-Shift-s": toggleMark("strike"),
3410
+ "Mod-e": toggleMark("code"),
3411
+ "Mod-Shift-h": toggleMark("highlight")
3412
+ };
3413
+ }
3414
+
3415
+ // src/engine/extension/define-extension.ts
3416
+ function defineExtension(extension) {
3417
+ if (typeof extension.name !== "string" || extension.name === "") throw new Error("an extension needs a name");
3418
+ return extension;
3419
+ }
3420
+
3421
+ // src/engine/extension/core-extensions.ts
3422
+ var coreCommands = {
3423
+ ...coreCommandFactories,
3424
+ insertSlice,
3425
+ pasteLinkOverSelection,
3426
+ insertTextWithRules
3427
+ };
3428
+ var linkOverSelection = {
3429
+ name: "link-over-selection",
3430
+ match: (text2) => loneUrl(text2, "linkable") === null ? null : pasteLinkOverSelection(text2)
3431
+ };
3432
+ function coreExtensions(options = {}) {
3433
+ const typography = options.typography ? [defineExtension({ name: "typography", inputRules: typographyRules(options.typography) })] : [];
3434
+ return [
3435
+ defineExtension({
3436
+ name: "core",
3437
+ blocks: coreBlockSpecs,
3438
+ marks: coreMarkSpecs,
3439
+ atoms: coreAtomSpecs,
3440
+ commands: coreCommands,
3441
+ pasteRules: [linkOverSelection]
3442
+ }),
3443
+ defineExtension({
3444
+ name: "history",
3445
+ plugins: [historyPlugin()],
3446
+ keymap: { "Mod-z": undo, "Mod-Shift-z": redo, "Mod-y": redo }
3447
+ }),
3448
+ defineExtension({ name: "editing-keys", keymap: coreKeymap(options) }),
3449
+ defineExtension({ name: "markdown-shortcuts", inputRules: [...blockInputRules, ...markInputRules] }),
3450
+ ...typography
3451
+ ];
3452
+ }
3453
+
3454
+ // src/engine/clipboard/serialize-slice.ts
3455
+ function sliceText(slice, options) {
3456
+ const schema = options.schema ?? createSchema();
3457
+ if (slice.blocks.length === 1 && slice.openStart && slice.openEnd) {
3458
+ const pieces = createBlockCodec(schema).toEngineBlocks(slice.blocks[0]);
3459
+ const block = pieces.length === 1 ? stripComments(pieces[0]) : null;
3460
+ const region = block === null || !isTextFlow(schema, block) ? null : primaryRegion(schema, block);
3461
+ if (block !== null && region !== null) return inlinePlainText(block.regions[region] ?? [], schema);
3462
+ }
3463
+ const markdown = toMarkdown(slice.blocks, { schema, resolveAssetUrl: options.resolveAssetUrl, comments: false });
3464
+ return markdown.replace(/\n$/, "");
3465
+ }
3466
+ function sliceToClipboard(slice, options = {}) {
3467
+ return {
3468
+ json: JSON.stringify({ quoin: 1, slice }),
3469
+ html: toHTML(slice.blocks, options),
3470
+ text: sliceText(slice, options)
3471
+ };
3472
+ }
3473
+
3474
+ // src/engine/clipboard/extract-slice.ts
3475
+ function isFlowRegion(schema, block, region) {
3476
+ return isTextFlow(schema, block) && region === primaryRegion(schema, block);
3477
+ }
3478
+ function trimmed(block, region, from, to) {
3479
+ return { ...block, regions: { ...block.regions, [region]: sliceInline(block.regions[region], from, to) } };
3480
+ }
3481
+ function insideOneRegion(schema, block, region, from, to) {
3482
+ if (isFlowRegion(schema, block, region)) return trimmed(block, region, from, to);
3483
+ const paragraph = createBlock(schema, schema.defaultTextBlock, {}, block.indent, block.id);
3484
+ return withContent(schema, paragraph, sliceInline(block.regions[region], from, to));
3485
+ }
3486
+ function textBlocks({ doc, schema }, selection) {
3487
+ const { from, to } = orderedRange(doc, selection.anchor, selection.head);
3488
+ const start = requireBlockIndex(doc, from.block);
3489
+ const end = requireBlockIndex(doc, to.block);
3490
+ const first = doc.blocks[start];
3491
+ if (start === end) {
3492
+ if (from.region !== to.region) return [first];
3493
+ return [insideOneRegion(schema, first, from.region, from.offset, to.offset)];
3494
+ }
3495
+ const last = doc.blocks[end];
3496
+ return [
3497
+ isFlowRegion(schema, first, from.region) ? trimmed(first, from.region, from.offset, regionLength(first, from.region)) : first,
3498
+ ...doc.blocks.slice(start + 1, end),
3499
+ isFlowRegion(schema, last, to.region) ? trimmed(last, to.region, 0, to.offset) : last
3500
+ ];
3501
+ }
3502
+ function selectedBlocks(state) {
3503
+ const { doc, selection } = state;
3504
+ if (selection?.kind === "text") return textBlocks(state, selection);
3505
+ const range = selectedBlockRange(doc, selection);
3506
+ return range === null ? [] : doc.blocks.slice(range.start, range.end);
3507
+ }
3508
+ function sliceFromSelection(state, codec) {
3509
+ const { selection } = state;
3510
+ if (selection === null || isCollapsed(selection)) return null;
3511
+ const blocks = selectedBlocks(state);
3512
+ if (blocks.length === 0) return null;
3513
+ const base = blocks.reduce((least, block) => Math.min(least, block.indent), Infinity);
3514
+ const open = selection.kind === "text";
3515
+ return {
3516
+ blocks: blocks.map((block) => codec.toQuoin(base > 0 ? { ...block, indent: block.indent - base } : block)),
3517
+ openStart: open,
3518
+ openEnd: open
3519
+ };
3520
+ }
3521
+
3522
+ // src/engine/clipboard/from-text.ts
3523
+ var BLOCK_MARKERS = [
3524
+ /^ {0,3}#{1,6}[ \t]+\S/,
3525
+ /^[ \t]*[-*+][ \t]+\S/,
3526
+ /^[ \t]*\d{1,9}[.)][ \t]+\S/,
3527
+ /^ {0,3}>[ \t]?\S/,
3528
+ /^ {0,3}(?:`{3,}|~{3,})/,
3529
+ /^[ \t]*\|.+\|[ \t]*$/
3530
+ ];
3531
+ var INLINE_SYNTAX = [/\*\*[^*\n]+\*\*/, /__[^_\n]+__/, /\[[^\]\n]+\]\([^)\s]+[^)\n]*\)/, /`[^`\n]+`/, /~~[^~\n]+~~/];
3532
+ function looksLikeMarkdown(value) {
3533
+ return value.split(/\r\n|\r|\n/).some((line2) => BLOCK_MARKERS.some((marker) => marker.test(line2))) || INLINE_SYNTAX.some((syntax) => syntax.test(value));
3534
+ }
3535
+ function plainTextToBlocks(value, makeId = newId) {
3536
+ return value.split(/\r\n|\r|\n/).filter((line2) => line2.trim() !== "").map((line2) => ({
3537
+ id: makeId(),
3538
+ type: "MARKDOWN",
3539
+ data: { markdown: serializeInline([text(line2)], { escapeLineStart: true }) }
3540
+ }));
3541
+ }
3542
+
3543
+ // src/engine/clipboard/parse-slice-json.ts
3544
+ function isBlock2(value) {
3545
+ if (!isRecord(value) || typeof value.id !== "string" || typeof value.type !== "string") return false;
3546
+ if (!isRecord(value.data)) return false;
3547
+ return value.indent === void 0 || Number.isInteger(value.indent) && value.indent >= 0;
3548
+ }
3549
+ function parseSliceJSON(json) {
3550
+ let parsed;
3551
+ try {
3552
+ parsed = JSON.parse(json);
3553
+ } catch {
3554
+ return null;
3555
+ }
3556
+ if (!isRecord(parsed) || parsed.quoin !== 1 || !isRecord(parsed.slice)) return null;
3557
+ const { blocks, openStart, openEnd } = parsed.slice;
3558
+ if (!Array.isArray(blocks) || blocks.length === 0 || !blocks.every(isBlock2)) return null;
3559
+ if (typeof openStart !== "boolean" || typeof openEnd !== "boolean") return null;
3560
+ return { blocks, openStart, openEnd };
3561
+ }
3562
+
3563
+ // src/engine/clipboard/slice-from-clipboard.ts
3564
+ var isParagraph2 = (block) => block.type === "MARKDOWN" && parseLine(String(block.data.markdown ?? "")).kind === "paragraph";
3565
+ function openSlice(blocks, plainText2 = false) {
3566
+ if (blocks.length === 0) return null;
3567
+ const slice = { blocks, openStart: isParagraph2(blocks[0]), openEnd: isParagraph2(blocks[blocks.length - 1]) };
3568
+ return plainText2 ? { ...slice, plainText: plainText2 } : slice;
3569
+ }
3570
+ function linkBlocks(text2, makeId) {
3571
+ const link = pastedLink(text2);
3572
+ if (link === null) return null;
3573
+ const { url, href, rest } = link;
3574
+ const linked2 = text(url, [{ type: "link", attrs: { href } }]);
3575
+ const markdown = serializeInline(rest === "" ? [linked2] : [linked2, text(rest)]);
3576
+ return [{ id: makeId(), type: "MARKDOWN", data: { markdown } }];
3577
+ }
3578
+ function textBlocks2(text2, makeId) {
3579
+ const link = linkBlocks(text2, makeId);
3580
+ if (link !== null) return link;
3581
+ return looksLikeMarkdown(text2) ? fromMarkdown(text2, { newId: makeId }) : plainTextToBlocks(text2, makeId);
3582
+ }
3583
+ function htmlSlice(html, options) {
3584
+ if (html === void 0) return null;
3585
+ const parser = availableParser(options.parser);
3586
+ return parser === null ? null : openSlice(fromHTML(html, { ...options, parser }));
3587
+ }
3588
+ function sliceFromClipboard(data, options = {}) {
3589
+ const fromJson = data.json === void 0 ? null : parseSliceJSON(data.json);
3590
+ if (fromJson !== null) return fromJson;
3591
+ const fromHtml = htmlSlice(data.html, options);
3592
+ if (fromHtml !== null) return fromHtml;
3593
+ return data.text === void 0 ? null : openSlice(textBlocks2(data.text, options.newId ?? newId), true);
3594
+ }
3595
+
3596
+ // src/engine/commands/is-active.ts
3597
+ function hasAttrs(block, attrs) {
3598
+ return Object.entries(attrs ?? {}).every(([key, value]) => block.attrs[key] === value);
3599
+ }
3600
+ function isActive(state, name, attrs) {
3601
+ if (state.schema.marks.has(name)) return selectionHasMark(state, name, attrs);
3602
+ const blocks = selectedBlockIds(state.doc, state.selection).flatMap((id) => blockById(state.doc, id) ?? []);
3603
+ const isBlockType = state.schema.blocks.has(name);
3604
+ const matches = (block) => isBlockType ? block.type === name : block.type === "MARKDOWN" && block.attrs.kind === name;
3605
+ return blocks.length > 0 && blocks.every((block) => matches(block) && hasAttrs(block, attrs));
3606
+ }
3607
+
3608
+ // src/engine/model/same-doc.ts
3609
+ function sameDoc(a, b) {
3610
+ if (a === b) return true;
3611
+ return a.blocks.length === b.blocks.length && a.blocks.every((block, i) => sameValue(block, b.blocks[i]));
3612
+ }
3613
+
3614
+ // src/engine/extension/same-blocks.ts
3615
+ function sameBlock(a, b) {
3616
+ return a === b || a.id === b.id && a.type === b.type && (a.indent ?? 0) === (b.indent ?? 0) && sameValue(a.data, b.data);
3617
+ }
3618
+ function sameBlocks(a, b) {
3619
+ return a === b || sameItems(a, b, sameBlock);
3620
+ }
3621
+
3622
+ // src/engine/extension/structural-echo.ts
3623
+ var sameIds = (a, b) => sameItems(a, b, (x, y) => x.id === y.id);
3624
+ function copiedSequence(emissions, incoming) {
3625
+ const verdicts = /* @__PURE__ */ new Map();
3626
+ const sameAt = (block, index) => {
3627
+ const known = verdicts.get(block);
3628
+ if (known !== void 0) return known;
3629
+ const same = sameBlock(block, incoming[index]);
3630
+ verdicts.set(block, same);
3631
+ return same;
3632
+ };
3633
+ return emissions.find(({ blocks }) => sameIds(blocks, incoming) && blocks.every(sameAt))?.sequence;
3634
+ }
3635
+
3636
+ // src/engine/extension/echo-tracker.ts
3637
+ var STRUCTURAL_ECHO_WINDOW = 256;
3638
+ function createEchoTracker() {
3639
+ const sequences = /* @__PURE__ */ new WeakMap();
3640
+ let emitted = 0;
3641
+ let acknowledged = 0;
3642
+ let outstanding = [];
3643
+ return {
3644
+ emitted(blocks) {
3645
+ sequences.set(blocks, ++emitted);
3646
+ outstanding.push({ blocks, sequence: emitted });
3647
+ if (outstanding.length > STRUCTURAL_ECHO_WINDOW) outstanding.shift();
3648
+ },
3649
+ classify(blocks) {
3650
+ const sequence = sequences.get(blocks) ?? copiedSequence(outstanding, blocks);
3651
+ if (sequence === void 0 || sequence <= acknowledged) return "foreign";
3652
+ acknowledged = sequence;
3653
+ outstanding = outstanding.filter((emission) => emission.sequence > sequence);
3654
+ return "echo";
3655
+ },
3656
+ forgetAll() {
3657
+ acknowledged = emitted;
3658
+ outstanding = [];
3659
+ }
3660
+ };
3661
+ }
3662
+
3663
+ // src/engine/extension/controlled-blocks.ts
3664
+ function createControlledBlocks(config) {
3665
+ const { codec, state, dispatch, onChange, accepts } = config;
3666
+ const tracker = createEchoTracker();
3667
+ const quiet = /* @__PURE__ */ new WeakSet();
3668
+ let supplied = config.initial;
3669
+ let converted = null;
3670
+ function getBlocks() {
3671
+ const { doc } = state();
3672
+ if (converted?.doc !== doc) converted = { doc, blocks: codec.blocksFromDoc(doc) };
3673
+ return converted.blocks;
3674
+ }
3675
+ function reconcile(incoming) {
3676
+ const doc = codec.docFromBlocks(incoming);
3677
+ if (sameDoc(doc, state().doc) || !accepts(incoming)) return false;
3678
+ tracker.forgetAll();
3679
+ const tr = new Transaction(state()).setOrigin("reconcile").setMeta("addToHistory", false);
3680
+ if (sameBlocks(codec.blocksFromDoc(doc), incoming)) quiet.add(tr);
3681
+ dispatch(tr.step(replaceDocStep(doc)));
3682
+ return true;
3683
+ }
3684
+ return {
3685
+ getBlocks,
3686
+ setBlocks(incoming) {
3687
+ const previous = supplied;
3688
+ supplied = incoming;
3689
+ if (incoming === previous || tracker.classify(incoming) === "echo") return false;
3690
+ if (sameBlocks(incoming, previous) || sameBlocks(incoming, getBlocks())) return false;
3691
+ return reconcile(incoming);
3692
+ },
3693
+ committed(tr) {
3694
+ if (!tr.docChanged || quiet.has(tr)) return;
3695
+ const blocks = getBlocks();
3696
+ tracker.emitted(blocks);
3697
+ onChange?.(blocks);
3698
+ }
3699
+ };
3700
+ }
3701
+
3702
+ // src/engine/extension/detect-mac.ts
3703
+ function detectMac() {
3704
+ if (typeof navigator === "undefined") return false;
3705
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);
3706
+ }
3707
+
3708
+ // src/engine/extension/dispatch-queue.ts
3709
+ function createDispatchQueue(current, commit) {
3710
+ const accepted = /* @__PURE__ */ new WeakSet();
3711
+ let waiting = null;
3712
+ let committing = false;
3713
+ function dispatch(tr) {
3714
+ if (accepted.has(tr)) throw new RangeError("This transaction was already dispatched");
3715
+ if (tr.before !== current() || waiting !== null) throw mismatchedTransaction();
3716
+ accepted.add(tr);
3717
+ if (committing) {
3718
+ waiting = tr;
3719
+ return;
3720
+ }
3721
+ committing = true;
3722
+ try {
3723
+ for (let next = tr; next !== null; next = waiting) {
3724
+ waiting = null;
3725
+ commit(next);
3726
+ }
3727
+ } finally {
3728
+ committing = false;
3729
+ waiting = null;
3730
+ }
3731
+ }
3732
+ return {
3733
+ dispatch,
3734
+ clear() {
3735
+ waiting = null;
3736
+ }
3737
+ };
3738
+ }
3739
+
3740
+ // src/engine/extension/keymap.ts
3741
+ var MODIFIERS = ["Alt", "Ctrl", "Meta", "Shift"];
3742
+ var PHYSICAL_KEY = /^(?:Key([A-Z])|Digit([0-9]))$/;
3743
+ var PLAIN_KEY = /^[a-z0-9]$/;
3744
+ var lowerSingle = (key) => key.length === 1 ? key.toLowerCase() : key;
3745
+ var join2 = (held, key) => [...MODIFIERS.filter((m) => held.has(m)), key].join("-");
3746
+ function modifierOf(part, isMac, name) {
3747
+ if (part === "Mod") return isMac ? "Meta" : "Ctrl";
3748
+ if (MODIFIERS.includes(part)) return part;
3749
+ throw new Error(`unknown modifier "${part}" in key "${name}"`);
3750
+ }
3751
+ function normalizeKey(name, isMac) {
3752
+ const split = name.lastIndexOf("-", name.length - 2);
3753
+ if (split < 1) return lowerSingle(name);
3754
+ const held = new Set(
3755
+ name.slice(0, split).split("-").map((part) => modifierOf(part, isMac, name))
3756
+ );
3757
+ return join2(held, lowerSingle(name.slice(split + 1)));
3758
+ }
3759
+ function eventKey({ key, code, shiftKey, altKey }, isMac) {
3760
+ const typed2 = lowerSingle(key ?? "Unidentified");
3761
+ if (!(shiftKey || isMac && altKey) || PLAIN_KEY.test(typed2)) return typed2;
3762
+ const physical = code === void 0 ? null : PHYSICAL_KEY.exec(code);
3763
+ return physical === null ? typed2 : (physical[1] ?? physical[2]).toLowerCase();
3764
+ }
3765
+ function keyName(event, isMac) {
3766
+ const held = /* @__PURE__ */ new Set();
3767
+ if (event.altKey) held.add("Alt");
3768
+ if (event.ctrlKey) held.add("Ctrl");
3769
+ if (event.metaKey) held.add("Meta");
3770
+ if (event.shiftKey) held.add("Shift");
3771
+ return join2(held, eventKey(event, isMac));
3772
+ }
3773
+ function runKeymap(keymap, state, dispatch, event, isMac) {
3774
+ return (keymap.get(keyName(event, isMac)) ?? []).some((command) => command(state, dispatch));
3775
+ }
3776
+
3777
+ // src/engine/extension/resolve-extensions.ts
3778
+ var DEFAULT_PRIORITY = 100;
3779
+ function keymapOf(extensions, isMac) {
3780
+ const keymap = /* @__PURE__ */ new Map();
3781
+ for (const { keymap: bindings } of extensions) {
3782
+ for (const [name, command] of Object.entries(bindings ?? {})) {
3783
+ const key = normalizeKey(name, isMac);
3784
+ keymap.set(key, [...keymap.get(key) ?? [], command]);
3785
+ }
3786
+ }
3787
+ return keymap;
3788
+ }
3789
+ function commandsOf(extensions) {
3790
+ const commands = /* @__PURE__ */ new Map();
3791
+ for (const { commands: factories } of extensions) {
3792
+ for (const [name, factory] of Object.entries(factories ?? {})) {
3793
+ if (!commands.has(name)) commands.set(name, factory);
3794
+ }
3795
+ }
3796
+ return Object.fromEntries(commands);
3797
+ }
3798
+ function resolveExtensions(extensions, isMac) {
3799
+ const sorted = [...extensions].sort(
3800
+ (a, b) => (b.priority ?? DEFAULT_PRIORITY) - (a.priority ?? DEFAULT_PRIORITY)
3801
+ );
3802
+ return {
3803
+ extensions: sorted,
3804
+ schema: createSchema({
3805
+ blocks: sorted.flatMap((extension) => extension.blocks ?? []),
3806
+ marks: sorted.flatMap((extension) => extension.marks ?? []),
3807
+ atoms: sorted.flatMap((extension) => extension.atoms ?? [])
3808
+ }),
3809
+ inputRules: sorted.flatMap((extension) => extension.inputRules ?? []),
3810
+ pasteRules: sorted.flatMap((extension) => extension.pasteRules ?? []),
3811
+ keymap: keymapOf(sorted, isMac),
3812
+ commands: commandsOf(sorted),
3813
+ plugins: sorted.flatMap((extension) => extension.plugins ?? []),
3814
+ decorations: (state) => sorted.flatMap((extension) => extension.decorations?.(state) ?? []),
3815
+ filterTransaction: (tr, state) => sorted.every((extension) => extension.filterTransaction?.(tr, state) !== false)
3816
+ };
3817
+ }
3818
+
3819
+ // src/engine/extension/editor.ts
3820
+ function createEditor(options) {
3821
+ return createInternalEditor(options);
3822
+ }
3823
+ function createInternalEditor(options) {
3824
+ const { newId: newId2 } = options;
3825
+ const isMac = options.isMac ?? detectMac();
3826
+ const resolved = resolveExtensions(options.extensions ?? coreExtensions({ newId: newId2 }), isMac);
3827
+ const { schema, plugins } = resolved;
3828
+ const codec = createBlockCodec(schema, { newId: newId2 });
3829
+ const listeners = /* @__PURE__ */ new Set();
3830
+ const mounts = /* @__PURE__ */ new Set();
3831
+ let state = createState({ doc: codec.docFromBlocks(options.blocks), schema, plugins, clock: options.now });
3832
+ let destroyed = false;
3833
+ const controlled = createControlledBlocks({
3834
+ codec,
3835
+ initial: options.blocks,
3836
+ state: () => state,
3837
+ dispatch,
3838
+ onChange: options.onChange,
3839
+ accepts: (incoming) => resolved.extensions.every((extension) => extension.filterHostBlocks?.(incoming, state) !== false)
3840
+ });
3841
+ const { getBlocks } = controlled;
3842
+ function commit(tr) {
3843
+ const exempt = foreignOrigins.includes(tr.origin) || tr.getMeta(UNFILTERED) === true;
3844
+ if (!exempt && !resolved.filterTransaction(tr, state)) return;
3845
+ state = applyTransaction(state, tr);
3846
+ for (const listener of [...listeners]) listener(state, tr);
3847
+ for (const extension of resolved.extensions) extension.onTransaction?.(tr, state);
3848
+ options.onTransaction?.(tr, state);
3849
+ controlled.committed(tr);
3850
+ }
3851
+ const queue = createDispatchQueue(() => state, commit);
3852
+ function dispatch(tr) {
3853
+ if (destroyed) throw new Error("Cannot dispatch to a destroyed editor");
3854
+ queue.dispatch(tr);
3855
+ }
3856
+ function paste(data, parser) {
3857
+ if (destroyed) return false;
3858
+ const { text: text2 } = data;
3859
+ const ruled = text2 !== void 0 && resolved.pasteRules.some((rule) => rule.match(text2, state)?.(state, dispatch));
3860
+ if (ruled) return true;
3861
+ const slice = sliceFromClipboard(data, { parser, newId: newId2 });
3862
+ return slice !== null && insertSlice(slice, { newId: newId2 })(state, dispatch);
3863
+ }
3864
+ function copy() {
3865
+ const slice = sliceFromSelection(state, codec);
3866
+ return slice === null ? null : sliceToClipboard(slice, { schema, resolveAssetUrl: options.resolveAssetUrl });
3867
+ }
3868
+ function exportMarkdown(markdown = {}) {
3869
+ const resolveAssetUrl = markdown.resolveAssetUrl ?? options.resolveAssetUrl;
3870
+ return toMarkdown(getBlocks(), { schema, ...markdown, resolveAssetUrl });
3871
+ }
3872
+ const helpers = createCommandHelpers(() => state, dispatch, resolved.commands, () => !destroyed);
3873
+ const editor = {
3874
+ get state() {
3875
+ return state;
3876
+ },
3877
+ schema,
3878
+ resolved,
3879
+ codec,
3880
+ dispatch,
3881
+ createTransaction: () => new Transaction(state),
3882
+ subscribe(listener) {
3883
+ listeners.add(listener);
3884
+ return () => void listeners.delete(listener);
3885
+ },
3886
+ commands: helpers.commands,
3887
+ chain: helpers.chain,
3888
+ can: helpers.can,
3889
+ isActive: (name, attrs) => isActive(state, name, attrs),
3890
+ insertText: (text2) => !destroyed && insertTextWithRules(text2, resolved.inputRules, { newId: newId2 })(state, dispatch),
3891
+ handleKey: (event) => !destroyed && runKeymap(resolved.keymap, state, dispatch, event, isMac),
3892
+ paste,
3893
+ copy,
3894
+ getBlocks,
3895
+ setBlocks: (incoming) => !destroyed && controlled.setBlocks(incoming),
3896
+ mount() {
3897
+ const stops = [];
3898
+ const unmount = () => {
3899
+ if (mounts.delete(unmount)) for (const stop of stops) stop?.();
3900
+ };
3901
+ mounts.add(unmount);
3902
+ try {
3903
+ if (!destroyed) for (const extension of resolved.extensions) stops.push(extension.onMount?.(editor));
3904
+ } catch (error) {
3905
+ unmount();
3906
+ throw error;
3907
+ }
3908
+ return unmount;
3909
+ },
3910
+ decorations: () => resolved.decorations(state),
3911
+ toMarkdown: exportMarkdown,
3912
+ stats: () => countDoc(state.doc, { schema }),
3913
+ destroy() {
3914
+ destroyed = true;
3915
+ listeners.clear();
3916
+ queue.clear();
3917
+ mounts.forEach((unmount) => unmount());
3918
+ }
3919
+ };
3920
+ return editor;
3921
+ }
3922
+
3923
+ export {
3924
+ setAttrsStep,
3925
+ setIndentStep,
3926
+ insertBlocksStep,
3927
+ removeBlocksStep,
3928
+ moveBlocksStep,
3929
+ replaceBlockStep,
3930
+ replaceInlineStep,
3931
+ addMarkStep,
3932
+ removeMarkStep,
3933
+ commonPrefix,
3934
+ commonSuffix,
3935
+ replaceDocStep,
3936
+ splitBlockStep,
3937
+ joinBlocksStep,
3938
+ stepFromJSON,
3939
+ comparePos,
3940
+ orderedRange,
3941
+ textSelection,
3942
+ nodeSelection,
3943
+ blockRangeSelection,
3944
+ isCollapsed,
3945
+ sameSelection,
3946
+ selectionRange,
3947
+ selectedBlockIds,
3948
+ mapSelection,
3949
+ primaryRegion,
3950
+ startOf,
3951
+ endOf,
3952
+ validateSelection,
3953
+ isClosedToggle,
3954
+ hidingToggle,
3955
+ createState,
3956
+ applyTransaction,
3957
+ getPluginState,
3958
+ createPluginKey,
3959
+ foreignOrigins,
3960
+ Transaction,
3961
+ caretMarks,
3962
+ runCommands,
3963
+ HISTORY_PROVIDER,
3964
+ HISTORY_GROUP,
3965
+ undo,
3966
+ redo,
3967
+ undoInputRule,
3968
+ isTextFlow,
3969
+ isParagraph,
3970
+ regionLength,
3971
+ descendantsEnd,
3972
+ createBlock,
3973
+ withContent,
3974
+ headBlockId,
3975
+ selectedBlockRange,
3976
+ capIndents,
3977
+ toggleTask,
3978
+ indent,
3979
+ outdent,
3980
+ moveBlocksUp,
3981
+ moveBlocksDown,
3982
+ duplicateBlocks,
3983
+ deleteBlocks,
3984
+ insertBlockAfter,
3985
+ deleteRange,
3986
+ insertTextAt,
3987
+ insertText,
3988
+ markExtent,
3989
+ markRanges,
3990
+ rangeContent,
3991
+ markCoverage,
3992
+ toggleMark,
3993
+ setMark,
3994
+ unsetMark,
3995
+ setLink,
3996
+ unsetLink,
3997
+ insertMention,
3998
+ setBlockType,
3999
+ insertTextWithRules,
4000
+ blockInputRules,
4001
+ regionInline,
4002
+ markInputRules,
4003
+ typographyRules,
4004
+ fromHTML,
4005
+ toMarkdown,
4006
+ toHTML,
4007
+ toPlainText,
4008
+ graphemeCount,
4009
+ wordCount,
4010
+ nextGraphemeOffset,
4011
+ prevGraphemeOffset,
4012
+ countBlock,
4013
+ countText,
4014
+ countDoc,
4015
+ setAttrs,
4016
+ setTextAlign,
4017
+ setBlockColor,
4018
+ LINE_JOIN,
4019
+ deleteSelection,
4020
+ deleteBackward,
4021
+ deleteForward,
4022
+ deleteWordBackward,
4023
+ deleteWordForward,
4024
+ deleteToLineStart,
4025
+ deleteToLineEnd,
4026
+ insertHardBreak,
4027
+ movedBlockIds,
4028
+ moveBlocksTo,
4029
+ selectAll,
4030
+ setSelection,
4031
+ LINE_SPLIT,
4032
+ freshBlocks,
4033
+ pasteAtCaret,
4034
+ insertSlice,
4035
+ pastedLink,
4036
+ defineExtension,
4037
+ coreExtensions,
4038
+ sliceFromSelection,
4039
+ sliceFromClipboard,
4040
+ isActive,
4041
+ sameDoc,
4042
+ detectMac,
4043
+ normalizeKey,
4044
+ keyName,
4045
+ createEditor
4046
+ };
4047
+ //# sourceMappingURL=chunk-HJIQT3S5.js.map