@pixerate/editor 0.19.0 → 0.20.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 (112) hide show
  1. package/README.md +91 -2
  2. package/dist/agent/index.cjs +625 -2
  3. package/dist/agent/index.cjs.map +1 -1
  4. package/dist/agent/index.d.cts +286 -2
  5. package/dist/agent/index.d.ts +286 -2
  6. package/dist/agent/index.js +624 -1
  7. package/dist/agent/index.js.map +1 -1
  8. package/dist/canvas/index.cjs +1240 -22
  9. package/dist/canvas/index.cjs.map +1 -1
  10. package/dist/canvas/index.d.cts +515 -3
  11. package/dist/canvas/index.d.ts +515 -3
  12. package/dist/canvas/index.js +1219 -1
  13. package/dist/canvas/index.js.map +1 -1
  14. package/dist/chunk-3TYMNULT.js +1367 -0
  15. package/dist/chunk-3TYMNULT.js.map +1 -0
  16. package/dist/chunk-4QPLEUL2.cjs +4480 -0
  17. package/dist/chunk-4QPLEUL2.cjs.map +1 -0
  18. package/dist/chunk-4SUNJZWB.cjs +553 -0
  19. package/dist/chunk-4SUNJZWB.cjs.map +1 -0
  20. package/dist/chunk-A7DKIA72.js +2657 -0
  21. package/dist/chunk-A7DKIA72.js.map +1 -0
  22. package/dist/chunk-AXV2ZW34.cjs +118 -0
  23. package/dist/chunk-AXV2ZW34.cjs.map +1 -0
  24. package/dist/chunk-B2ZRLN4S.js +118 -0
  25. package/dist/chunk-B2ZRLN4S.js.map +1 -0
  26. package/dist/chunk-CYFJCKC4.js +4480 -0
  27. package/dist/chunk-CYFJCKC4.js.map +1 -0
  28. package/dist/chunk-GVW4P7BG.js +2706 -0
  29. package/dist/chunk-GVW4P7BG.js.map +1 -0
  30. package/dist/chunk-JACER6TE.cjs +1 -0
  31. package/dist/chunk-JACER6TE.cjs.map +1 -0
  32. package/dist/chunk-JYRIOFEN.cjs +2657 -0
  33. package/dist/chunk-JYRIOFEN.cjs.map +1 -0
  34. package/dist/chunk-KYRZDWM4.js +299 -0
  35. package/dist/chunk-KYRZDWM4.js.map +1 -0
  36. package/dist/chunk-LA7PEGU3.cjs +299 -0
  37. package/dist/chunk-LA7PEGU3.cjs.map +1 -0
  38. package/dist/chunk-N7FG6CBO.js +1 -0
  39. package/dist/chunk-N7FG6CBO.js.map +1 -0
  40. package/dist/chunk-OYXSKQFR.js +553 -0
  41. package/dist/chunk-OYXSKQFR.js.map +1 -0
  42. package/dist/chunk-TVRC4KVT.cjs +1367 -0
  43. package/dist/chunk-TVRC4KVT.cjs.map +1 -0
  44. package/dist/chunk-U6NDQFOK.cjs +2706 -0
  45. package/dist/chunk-U6NDQFOK.cjs.map +1 -0
  46. package/dist/controller-OUdxPY-X.d.cts +754 -0
  47. package/dist/controller-OUdxPY-X.d.ts +754 -0
  48. package/dist/extensions/index.cjs +144 -3
  49. package/dist/extensions/index.cjs.map +1 -1
  50. package/dist/extensions/index.d.cts +9 -144
  51. package/dist/extensions/index.d.ts +9 -144
  52. package/dist/extensions/index.js +145 -4
  53. package/dist/grammar/index.cjs +31 -3
  54. package/dist/grammar/index.cjs.map +1 -1
  55. package/dist/grammar/index.d.cts +239 -19
  56. package/dist/grammar/index.d.ts +239 -19
  57. package/dist/grammar/index.js +31 -3
  58. package/dist/history/index.d.cts +2 -41
  59. package/dist/history/index.d.ts +2 -41
  60. package/dist/index-Ng1pG7JE.d.ts +899 -0
  61. package/dist/index-k3mFbPx_.d.cts +899 -0
  62. package/dist/index.cjs +457 -20
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +99 -12
  65. package/dist/index.d.ts +99 -12
  66. package/dist/index.js +457 -20
  67. package/dist/index.js.map +1 -1
  68. package/dist/registry-iAHbqkYt.d.cts +128 -0
  69. package/dist/registry-iAHbqkYt.d.ts +128 -0
  70. package/dist/serializers/index.cjs +26 -3
  71. package/dist/serializers/index.cjs.map +1 -1
  72. package/dist/serializers/index.d.cts +119 -6
  73. package/dist/serializers/index.d.ts +119 -6
  74. package/dist/serializers/index.js +24 -1
  75. package/dist/spreadsheet/index.cjs +122 -2
  76. package/dist/spreadsheet/index.cjs.map +1 -1
  77. package/dist/spreadsheet/index.d.cts +371 -313
  78. package/dist/spreadsheet/index.d.ts +371 -313
  79. package/dist/spreadsheet/index.js +127 -7
  80. package/dist/tokens-CyLZmIei.d.cts +76 -0
  81. package/dist/tokens-CyLZmIei.d.ts +76 -0
  82. package/dist/track-changes-G8JcWB8b.d.cts +306 -0
  83. package/dist/track-changes-G8JcWB8b.d.ts +306 -0
  84. package/dist/types-BZgcDsC5.d.cts +42 -0
  85. package/dist/types-BZgcDsC5.d.ts +42 -0
  86. package/dist/{types-JV3S8VeR.d.cts → types-DMe0vFB1.d.cts} +1 -1
  87. package/dist/{types-JV3S8VeR.d.ts → types-DMe0vFB1.d.ts} +1 -1
  88. package/package.json +1 -1
  89. package/dist/chunk-7FWEWE6T.js +0 -1888
  90. package/dist/chunk-7FWEWE6T.js.map +0 -1
  91. package/dist/chunk-AIZUKWTL.js +0 -1715
  92. package/dist/chunk-AIZUKWTL.js.map +0 -1
  93. package/dist/chunk-GLSIZKOX.js +0 -17
  94. package/dist/chunk-GLSIZKOX.js.map +0 -1
  95. package/dist/chunk-LDES3EOS.cjs +0 -1888
  96. package/dist/chunk-LDES3EOS.cjs.map +0 -1
  97. package/dist/chunk-LLUAKTLW.cjs +0 -532
  98. package/dist/chunk-LLUAKTLW.cjs.map +0 -1
  99. package/dist/chunk-MPPXH7U5.cjs +0 -336
  100. package/dist/chunk-MPPXH7U5.cjs.map +0 -1
  101. package/dist/chunk-O7LOPQMH.js +0 -336
  102. package/dist/chunk-O7LOPQMH.js.map +0 -1
  103. package/dist/chunk-QGVMBCT5.js +0 -532
  104. package/dist/chunk-QGVMBCT5.js.map +0 -1
  105. package/dist/chunk-SEDR75VH.cjs +0 -17
  106. package/dist/chunk-SEDR75VH.cjs.map +0 -1
  107. package/dist/chunk-YQCI4CCN.cjs +0 -1715
  108. package/dist/chunk-YQCI4CCN.cjs.map +0 -1
  109. package/dist/mention-Dcevh3ff.d.cts +0 -32
  110. package/dist/mention-Dcevh3ff.d.ts +0 -32
  111. package/dist/tokens-7kRTEtPb.d.cts +0 -39
  112. package/dist/tokens-7kRTEtPb.d.ts +0 -39
@@ -0,0 +1,4480 @@
1
+ import {
2
+ INSTRUCTION_REGEX,
3
+ TEMPLATE_REGEX,
4
+ VARIABLE_REGEX,
5
+ matchConditionalTags,
6
+ parseVariableBody
7
+ } from "./chunk-B2ZRLN4S.js";
8
+ import {
9
+ PIXERATE_DOCUMENT_MIME,
10
+ getClipboardData,
11
+ plainTextToParagraphHtml,
12
+ readRichClipboard,
13
+ rtfToPlainText,
14
+ sanitizeImportedHtml,
15
+ serializeRichClipboardPayload
16
+ } from "./chunk-KYRZDWM4.js";
17
+ import {
18
+ CommentStore,
19
+ TrackChanges,
20
+ createReviewId,
21
+ htmlToPlainText,
22
+ markdownToTipTapHtml,
23
+ plainTextToSlice
24
+ } from "./chunk-3TYMNULT.js";
25
+
26
+ // src/extensions/gradient-text.ts
27
+ import { Mark } from "@tiptap/core";
28
+ import { Plugin } from "@tiptap/pm/state";
29
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
30
+ var createGradientDecorations = (doc, options) => {
31
+ const decorations = [];
32
+ const speed = options.speed ?? 1;
33
+ const jsonVarSet = options.jsonVariables ? options.jsonVariables instanceof Set ? options.jsonVariables : new Set(options.jsonVariables) : null;
34
+ const conditionalTags = [];
35
+ doc.descendants((node, pos) => {
36
+ if (node.isText) {
37
+ const text = node.text || "";
38
+ let match;
39
+ const tmplRegex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
40
+ while ((match = tmplRegex.exec(text)) !== null) {
41
+ const templateName = match[1].trim();
42
+ const colors = options.templateColorMap?.get(templateName);
43
+ if (colors) {
44
+ const start2 = pos + match.index;
45
+ const end = start2 + match[0].length;
46
+ const { from: colorFrom, to: colorTo } = colors;
47
+ const style = `--bg-size: ${speed * 300}%; --color-from: ${colorFrom}; --color-to: ${colorTo}; caret-color: #111 !important; background-image: linear-gradient(to right, ${colorFrom}, ${colorTo}, ${colorFrom}); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; color: transparent;`;
48
+ decorations.push(
49
+ Decoration.inline(start2, end, {
50
+ class: "inline animate-gradient bg-gradient-to-r from-[var(--color-from)] via-[var(--color-to)] to-[var(--color-from)] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent border border-(--color-to)/20 px-1 py-0.5 rounded-sm cursor-pointer",
51
+ style,
52
+ "data-template": templateName
53
+ })
54
+ );
55
+ }
56
+ }
57
+ const instRegex = new RegExp(
58
+ INSTRUCTION_REGEX.source,
59
+ INSTRUCTION_REGEX.flags
60
+ );
61
+ while ((match = instRegex.exec(text)) !== null) {
62
+ const content = match[1].trim();
63
+ const colors = options.templateColorMap?.get(content) || {
64
+ from: "#ffaa40",
65
+ to: "#9c40ff"
66
+ };
67
+ const start2 = pos + match.index;
68
+ const end = start2 + match[0].length;
69
+ const { from: colorFrom, to: colorTo } = colors;
70
+ const style = `--bg-size: ${speed * 300}%; --color-from: ${colorFrom}; --color-to: ${colorTo}; caret-color: #111 !important; background-image: linear-gradient(to right, #fb7185, #8b5cf6, #fb7185); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; color: transparent;`;
71
+ decorations.push(
72
+ Decoration.inline(start2, end, {
73
+ class: "italic animate-gradient bg-gradient-to-r from-rose-400 from-50% to-violet-500 to-50% bg-size-[200%_100%] bg-clip-text text-transparent inline-flex items-baseline gap-0.5",
74
+ style
75
+ })
76
+ );
77
+ }
78
+ const varRegex = new RegExp(
79
+ VARIABLE_REGEX.source,
80
+ VARIABLE_REGEX.flags
81
+ );
82
+ while ((match = varRegex.exec(text)) !== null) {
83
+ const parsed = parseVariableBody(match[1]);
84
+ const start2 = pos + match.index;
85
+ const end = start2 + match[0].length;
86
+ if (parsed.kind === "conditional") {
87
+ conditionalTags.push({
88
+ conditional: parsed.conditional,
89
+ name: parsed.name,
90
+ start: start2,
91
+ end
92
+ });
93
+ continue;
94
+ }
95
+ const variableName = parsed.name;
96
+ const isJsonVar = jsonVarSet?.has(variableName);
97
+ const attrs = {
98
+ class: isJsonVar ? "bg-violet-400/40 border-b-4 border-rose-300 dark:border-rose-800 inline-flex items-baseline px-1 py-0 text-foreground/80 gap-0.5 rounded-xs" : "bg-rose-400/40 border-b-4 border-violet-300 dark:border-violet-800 inline-flex items-baseline px-1 py-0 text-foreground/80 gap-0.5 rounded-xs",
99
+ "data-variable": variableName
100
+ };
101
+ if (parsed.defaultValue !== void 0) {
102
+ attrs.class += " border-dashed";
103
+ attrs["data-default"] = parsed.defaultValue;
104
+ }
105
+ if (parsed.description !== void 0) {
106
+ attrs["data-description"] = parsed.description;
107
+ }
108
+ const title = [
109
+ parsed.description,
110
+ parsed.defaultValue !== void 0 ? `Default: ${parsed.defaultValue || "(empty)"}` : void 0
111
+ ].filter(Boolean).join(" \xB7 ");
112
+ if (title) attrs.title = title;
113
+ decorations.push(Decoration.inline(start2, end, attrs));
114
+ }
115
+ }
116
+ });
117
+ matchConditionalTags(conditionalTags).forEach((result, i) => {
118
+ const tag = conditionalTags[i];
119
+ decorations.push(
120
+ Decoration.inline(
121
+ tag.start,
122
+ tag.end,
123
+ result.error ? {
124
+ class: "underline decoration-wavy decoration-red-500 text-red-600 dark:text-red-400",
125
+ "data-conditional-error": result.error,
126
+ title: result.error === "unclosed-section" ? `Section "${tag.name}" is never closed with {/${tag.name}}` : `{/${tag.name}} does not close any open section`
127
+ } : {
128
+ class: "bg-amber-400/30 text-amber-700 dark:text-amber-300 font-medium px-0.5 rounded-xs",
129
+ "data-conditional": tag.conditional,
130
+ "data-variable-condition": tag.name
131
+ }
132
+ )
133
+ );
134
+ });
135
+ return decorations;
136
+ };
137
+ var GradientText = Mark.create({
138
+ name: "gradientText",
139
+ addOptions() {
140
+ return {
141
+ templateColorMap: /* @__PURE__ */ new Map(),
142
+ speed: 1,
143
+ jsonVariables: void 0,
144
+ onTemplateClick: void 0
145
+ };
146
+ },
147
+ renderHTML({ HTMLAttributes }) {
148
+ const defaultColors = {
149
+ from: "#ffaa40",
150
+ to: "#9c40ff"
151
+ };
152
+ const speed = this.options.speed ?? 1;
153
+ const style = `--bg-size: ${speed * 300}%; --color-from: ${defaultColors.from}; --color-to: ${defaultColors.to}; caret-color: #111 !important;`;
154
+ return [
155
+ "span",
156
+ {
157
+ class: "inline animate-gradient bg-gradient-to-r from-[var(--color-from)] via-[var(--color-to)] to-[var(--color-from)] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent",
158
+ style,
159
+ ...HTMLAttributes
160
+ },
161
+ 0
162
+ ];
163
+ },
164
+ parseHTML() {
165
+ return [
166
+ {
167
+ tag: "span",
168
+ getAttrs: (node) => node.classList.contains("animate-gradient") && node.classList.contains("bg-clip-text") && null
169
+ }
170
+ ];
171
+ },
172
+ addProseMirrorPlugins() {
173
+ const markOptions = this.options;
174
+ return [
175
+ new Plugin({
176
+ state: {
177
+ init(_, { doc }) {
178
+ return DecorationSet.create(
179
+ doc,
180
+ createGradientDecorations(doc, markOptions)
181
+ );
182
+ },
183
+ apply(tr, value) {
184
+ const newDoc = tr.doc;
185
+ if (tr.docChanged) {
186
+ return DecorationSet.create(
187
+ newDoc,
188
+ createGradientDecorations(newDoc, markOptions)
189
+ );
190
+ }
191
+ return value.map(tr.mapping, newDoc);
192
+ }
193
+ },
194
+ props: {
195
+ decorations(state) {
196
+ return this.getState(state);
197
+ },
198
+ handleClickOn(view, pos, _node, _nodePos, event) {
199
+ const decorationSet = this.getState(
200
+ view.state
201
+ );
202
+ const decorations = decorationSet.find(pos, pos + 1);
203
+ for (const decoration of decorations) {
204
+ const decorationWithAttrs = decoration;
205
+ const templateName = decorationWithAttrs.type?.attrs?.["data-template"];
206
+ if (templateName) {
207
+ if (markOptions.onTemplateClick) {
208
+ markOptions.onTemplateClick(templateName, event);
209
+ }
210
+ if (typeof window !== "undefined") {
211
+ window.dispatchEvent(
212
+ new CustomEvent("show-gradient-popover", {
213
+ detail: {
214
+ templateName,
215
+ target: event.target
216
+ }
217
+ })
218
+ );
219
+ }
220
+ return true;
221
+ }
222
+ }
223
+ return false;
224
+ }
225
+ }
226
+ })
227
+ ];
228
+ }
229
+ });
230
+
231
+ // src/extensions/slash-commands.ts
232
+ import { Extension } from "@tiptap/core";
233
+ import { Plugin as Plugin2 } from "@tiptap/pm/state";
234
+ var SlashCommands = Extension.create({
235
+ name: "slashCommands",
236
+ addOptions() {
237
+ return {
238
+ trigger: "/",
239
+ onMatch: void 0,
240
+ onClear: void 0,
241
+ containerId: void 0
242
+ };
243
+ },
244
+ addProseMirrorPlugins() {
245
+ const opts = this.options;
246
+ return [
247
+ new Plugin2({
248
+ view() {
249
+ return {
250
+ update(view) {
251
+ const { state } = view;
252
+ const { selection } = state;
253
+ const { $from } = selection;
254
+ if (!$from.parent.isTextblock) {
255
+ opts.onClear?.();
256
+ return;
257
+ }
258
+ const textBeforeCursor = $from.parent.textBetween(
259
+ 0,
260
+ $from.parentOffset
261
+ );
262
+ const slashMatch = textBeforeCursor.match(
263
+ /(?:^|\s)\/([a-zA-Z0-9_-]*)$/
264
+ );
265
+ if (slashMatch) {
266
+ const matchIndex = slashMatch.index ?? 0;
267
+ const offsetInParent = matchIndex + (slashMatch[0].startsWith(" ") ? 1 : 0);
268
+ const matchFrom = $from.start() + offsetInParent;
269
+ const matchTo = $from.pos;
270
+ const coords = view.coordsAtPos($from.pos);
271
+ let relativeCoords = {
272
+ top: coords.top,
273
+ left: coords.left,
274
+ bottom: coords.bottom,
275
+ right: coords.right
276
+ };
277
+ if (opts.containerId && typeof document !== "undefined") {
278
+ const container = document.getElementById(opts.containerId);
279
+ if (container) {
280
+ const rect = container.getBoundingClientRect();
281
+ relativeCoords = {
282
+ top: coords.bottom - rect.top + 5,
283
+ left: coords.left - rect.left,
284
+ bottom: coords.bottom - rect.top,
285
+ right: coords.right - rect.left
286
+ };
287
+ }
288
+ }
289
+ opts.onMatch?.({
290
+ match: slashMatch,
291
+ query: slashMatch[1] || "",
292
+ from: matchFrom,
293
+ to: matchTo,
294
+ coords: relativeCoords
295
+ });
296
+ } else {
297
+ opts.onClear?.();
298
+ }
299
+ }
300
+ };
301
+ }
302
+ })
303
+ ];
304
+ }
305
+ });
306
+
307
+ // src/extensions/template-suggestions.ts
308
+ import { Extension as Extension2 } from "@tiptap/core";
309
+ import Suggestion from "@tiptap/suggestion";
310
+ var TemplateSuggestions = Extension2.create({
311
+ name: "templateSuggestions",
312
+ addOptions() {
313
+ return {
314
+ templates: [],
315
+ char: "{{",
316
+ onSuggestion: void 0
317
+ };
318
+ },
319
+ addProseMirrorPlugins() {
320
+ const opts = this.options;
321
+ const suggestionConfig = {
322
+ char: opts.char || "{{",
323
+ items: ({ query }) => {
324
+ const lower = query.toLowerCase();
325
+ return (opts.templates || []).filter(
326
+ (t) => t.name.toLowerCase().includes(lower)
327
+ );
328
+ },
329
+ command: ({ editor, range, props }) => {
330
+ editor.chain().focus().deleteRange(range).insertContent(`{{${props.name}}}`).run();
331
+ },
332
+ render: () => {
333
+ return {
334
+ onStart: (props) => {
335
+ opts.onSuggestion?.(props);
336
+ },
337
+ onUpdate: (props) => {
338
+ opts.onSuggestion?.(props);
339
+ },
340
+ onExit: () => {
341
+ opts.onSuggestion?.({
342
+ query: "",
343
+ range: { from: 0, to: 0 },
344
+ items: [],
345
+ command: () => {
346
+ }
347
+ });
348
+ }
349
+ };
350
+ }
351
+ };
352
+ const SuggestionFn = typeof Suggestion === "function" ? Suggestion : Suggestion?.default || Suggestion?.Suggestion;
353
+ return [
354
+ SuggestionFn({
355
+ editor: this.editor,
356
+ ...suggestionConfig
357
+ })
358
+ ];
359
+ }
360
+ });
361
+
362
+ // src/extensions/loading-node.ts
363
+ import { Node as Node2, mergeAttributes } from "@tiptap/core";
364
+ var LoadingNode = Node2.create({
365
+ name: "loadingNode",
366
+ group: "inline",
367
+ inline: true,
368
+ selectable: true,
369
+ atom: true,
370
+ addAttributes() {
371
+ return {
372
+ id: {
373
+ default: null,
374
+ parseHTML: (element) => element.getAttribute("data-id"),
375
+ renderHTML: (attributes) => {
376
+ if (!attributes.id) return {};
377
+ return { "data-id": attributes.id };
378
+ }
379
+ }
380
+ };
381
+ },
382
+ parseHTML() {
383
+ return [
384
+ {
385
+ tag: 'span[data-type="loading"]'
386
+ }
387
+ ];
388
+ },
389
+ renderHTML({ HTMLAttributes }) {
390
+ return [
391
+ "span",
392
+ mergeAttributes(HTMLAttributes, {
393
+ "data-type": "loading",
394
+ class: "inline-flex items-center px-1.5 py-0.5 text-xs text-muted-foreground animate-pulse"
395
+ }),
396
+ "\u23F3"
397
+ ];
398
+ }
399
+ });
400
+
401
+ // src/extensions/SmilieReplacer.ts
402
+ import { Extension as Extension3, textInputRule } from "@tiptap/core";
403
+ var SMILIE_REPLACEMENTS = [
404
+ [/-___- $/, "\u{1F611} "],
405
+ [/:'-\) $/, "\u{1F602} "],
406
+ [/':-\) $/, "\u{1F605} "],
407
+ [/':-D $/, "\u{1F605} "],
408
+ [/>:-\) $/, "\u{1F606} "],
409
+ [/-__- $/, "\u{1F611} "],
410
+ [/':-\( $/, "\u{1F613} "],
411
+ [/:'-\( $/, "\u{1F622} "],
412
+ [/>:-\( $/, "\u{1F620} "],
413
+ [/O:-\) $/, "\u{1F607} "],
414
+ [/:'\) $/, "\u{1F602} "],
415
+ [/:-D $/, "\u{1F603} "],
416
+ [/':\) $/, "\u{1F605} "],
417
+ [/;-\) $/, "\u{1F609} "],
418
+ [/B-\) $/, "\u{1F60E} "],
419
+ [/:-\* $/, "\u{1F618} "],
420
+ [/:-\) $/, "\u{1F642} "],
421
+ [/-_- $/, "\u{1F611} "],
422
+ [/:-O $/, "\u{1F62E} "],
423
+ [/:-P $/, "\u{1F61B} "],
424
+ [/:-p $/, "\u{1F61B} "],
425
+ [/:-\/ $/, "\u{1F615} "],
426
+ [/:-\( $/, "\u{1F61E} "],
427
+ [/:'\( $/, "\u{1F622} "],
428
+ [/:D $/, "\u{1F603} "],
429
+ [/=D $/, "\u{1F603} "],
430
+ [/;\) $/, "\u{1F609} "],
431
+ [/B\) $/, "\u{1F60E} "],
432
+ [/:\) $/, "\u{1F642} "],
433
+ [/=\) $/, "\u{1F642} "],
434
+ [/:O $/, "\u{1F62E} "],
435
+ [/:P $/, "\u{1F61B} "],
436
+ [/:p $/, "\u{1F61B} "],
437
+ [/:\/ $/, "\u{1F615} "],
438
+ [/:\( $/, "\u{1F61E} "],
439
+ [/<3 $/, "\u2764\uFE0F "],
440
+ [/<\/3 $/, "\u{1F494} "],
441
+ [/\/shrug $/, "\xAF\\_(\u30C4)_/\xAF "]
442
+ ];
443
+ var smilieAutocorrectRules = SMILIE_REPLACEMENTS.map(
444
+ ([find, replace], index) => ({
445
+ id: `smilie:${index}`,
446
+ find: new RegExp(find.source.replace(/'/g, "['\u2018\u2019]"), find.flags),
447
+ replace
448
+ })
449
+ );
450
+ var SmilieReplacer = Extension3.create({
451
+ name: "smilieReplacer",
452
+ addInputRules() {
453
+ return SMILIE_REPLACEMENTS.map(
454
+ ([find, replace]) => textInputRule({ find, replace })
455
+ );
456
+ }
457
+ });
458
+
459
+ // src/extensions/ColorHighlighter.ts
460
+ import { Extension as Extension4 } from "@tiptap/core";
461
+ import { Plugin as Plugin3 } from "@tiptap/pm/state";
462
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
463
+ function findHexColors(doc) {
464
+ const hexColorRegex = /(#[0-9a-fA-F]{3,8})\b/g;
465
+ const decorations = [];
466
+ doc.descendants((node, position) => {
467
+ if (!node.text) return;
468
+ let match;
469
+ while ((match = hexColorRegex.exec(node.text)) !== null) {
470
+ const color = match[0];
471
+ const from = position + match.index;
472
+ const to = from + color.length;
473
+ decorations.push(
474
+ Decoration2.inline(from, to, {
475
+ class: "color-highlight px-1 py-0.5 rounded text-xs font-mono border",
476
+ style: `--color: ${color}; background-color: ${color}20; border-color: ${color}; color: ${color};`
477
+ })
478
+ );
479
+ }
480
+ });
481
+ return DecorationSet2.create(doc, decorations);
482
+ }
483
+ var ColorHighlighter = Extension4.create({
484
+ name: "colorHighlighter",
485
+ addProseMirrorPlugins() {
486
+ return [
487
+ new Plugin3({
488
+ state: {
489
+ init(_, { doc }) {
490
+ return findHexColors(doc);
491
+ },
492
+ apply(transaction, oldState) {
493
+ return transaction.docChanged ? findHexColors(transaction.doc) : oldState;
494
+ }
495
+ },
496
+ props: {
497
+ decorations(state) {
498
+ return this.getState(state);
499
+ }
500
+ }
501
+ })
502
+ ];
503
+ }
504
+ });
505
+
506
+ // src/extensions/FontSize.ts
507
+ import { Extension as Extension5 } from "@tiptap/core";
508
+ import "@tiptap/extension-text-style";
509
+ var FontSize = Extension5.create({
510
+ name: "fontSize",
511
+ addOptions() {
512
+ return {
513
+ types: ["textStyle"]
514
+ };
515
+ },
516
+ addGlobalAttributes() {
517
+ return [
518
+ {
519
+ types: this.options.types,
520
+ attributes: {
521
+ fontSize: {
522
+ default: null,
523
+ parseHTML: (element) => element.style.fontSize.replace(/['"]+/g, ""),
524
+ renderHTML: (attributes) => {
525
+ if (!attributes.fontSize) {
526
+ return {};
527
+ }
528
+ return {
529
+ style: `font-size: ${attributes.fontSize}`
530
+ };
531
+ }
532
+ }
533
+ }
534
+ }
535
+ ];
536
+ },
537
+ addCommands() {
538
+ return {
539
+ setFontSize: (fontSize) => ({ chain }) => chain().setMark("textStyle", { fontSize }).run(),
540
+ unsetFontSize: () => ({ chain }) => chain().setMark("textStyle", { fontSize: null }).removeEmptyTextStyle().run()
541
+ };
542
+ }
543
+ });
544
+
545
+ // src/extensions/rich-text-preset.ts
546
+ import {
547
+ wrappingInputRule,
548
+ markInputRule
549
+ } from "@tiptap/core";
550
+ import * as _StarterKit from "@tiptap/starter-kit";
551
+ import * as _Color from "@tiptap/extension-color";
552
+ import * as _Highlight from "@tiptap/extension-highlight";
553
+ import * as _Link from "@tiptap/extension-link";
554
+ import * as _TaskList from "@tiptap/extension-task-list";
555
+ import * as _TaskItem from "@tiptap/extension-task-item";
556
+ import * as _TextAlign from "@tiptap/extension-text-align";
557
+ import * as _TextStyle from "@tiptap/extension-text-style";
558
+ import * as _Typography from "@tiptap/extension-typography";
559
+ import * as _Underline from "@tiptap/extension-underline";
560
+ import * as _Placeholder from "@tiptap/extension-placeholder";
561
+ import * as _CharacterCount from "@tiptap/extension-character-count";
562
+
563
+ // src/extensions/mention.ts
564
+ import {
565
+ Mention as TipTapMention
566
+ } from "@tiptap/extension-mention";
567
+ var BaseMention = TipTapMention || TipTapMention?.default?.default || TipTapMention?.default || TipTapMention;
568
+ var Mention = BaseMention.extend({
569
+ addAttributes() {
570
+ return {
571
+ ...this.parent?.(),
572
+ type: {
573
+ default: null,
574
+ parseHTML: (element) => element.getAttribute("data-type-name") || element.getAttribute("data-entity-type"),
575
+ renderHTML: (attributes) => {
576
+ if (!attributes.type) return {};
577
+ return { "data-type-name": attributes.type };
578
+ }
579
+ },
580
+ avatarUrl: {
581
+ default: null,
582
+ parseHTML: (element) => element.getAttribute("data-avatar-url"),
583
+ renderHTML: (attributes) => {
584
+ if (!attributes.avatarUrl) return {};
585
+ return { "data-avatar-url": attributes.avatarUrl };
586
+ }
587
+ },
588
+ color: {
589
+ default: null,
590
+ parseHTML: (element) => element.getAttribute("data-color"),
591
+ renderHTML: (attributes) => {
592
+ if (!attributes.color) return {};
593
+ return { "data-color": attributes.color };
594
+ }
595
+ }
596
+ };
597
+ }
598
+ });
599
+ function createMentionExtension(options = {}) {
600
+ const { HTMLAttributes = {}, ...rest } = options;
601
+ return Mention.configure({
602
+ HTMLAttributes: {
603
+ class: "pixerate-mention-node",
604
+ ...HTMLAttributes
605
+ },
606
+ ...rest
607
+ });
608
+ }
609
+
610
+ // ../../node_modules/.pnpm/tiptap-markdown@0.8.10_@tiptap+core@2.27.3_@tiptap+pm@2.27.3_/node_modules/tiptap-markdown/dist/tiptap-markdown.es.js
611
+ import { Extension as Extension6, Mark as Mark2, getHTMLFromFragment, Node as Node$1, extensions } from "@tiptap/core";
612
+ import { MarkdownSerializerState as MarkdownSerializerState$1, defaultMarkdownSerializer } from "prosemirror-markdown";
613
+ import markdownit from "markdown-it";
614
+ import { Fragment, DOMParser } from "@tiptap/pm/model";
615
+ import taskListPlugin from "markdown-it-task-lists";
616
+ import { Plugin as Plugin4, PluginKey } from "@tiptap/pm/state";
617
+ var __defProp = Object.defineProperty;
618
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
619
+ var __publicField = (obj, key, value) => {
620
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
621
+ return value;
622
+ };
623
+ var MarkdownTightLists = Extension6.create({
624
+ name: "markdownTightLists",
625
+ addOptions: () => ({
626
+ tight: true,
627
+ tightClass: "tight",
628
+ listTypes: ["bulletList", "orderedList"]
629
+ }),
630
+ addGlobalAttributes() {
631
+ return [{
632
+ types: this.options.listTypes,
633
+ attributes: {
634
+ tight: {
635
+ default: this.options.tight,
636
+ parseHTML: (element) => element.getAttribute("data-tight") === "true" || !element.querySelector("p"),
637
+ renderHTML: (attributes) => ({
638
+ class: attributes.tight ? this.options.tightClass : null,
639
+ "data-tight": attributes.tight ? "true" : null
640
+ })
641
+ }
642
+ }
643
+ }];
644
+ },
645
+ addCommands() {
646
+ var _this = this;
647
+ return {
648
+ toggleTight: function() {
649
+ let tight = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : null;
650
+ return (_ref) => {
651
+ let {
652
+ editor,
653
+ commands
654
+ } = _ref;
655
+ function toggleTight(name) {
656
+ if (!editor.isActive(name)) {
657
+ return false;
658
+ }
659
+ const attrs = editor.getAttributes(name);
660
+ return commands.updateAttributes(name, {
661
+ tight: tight !== null && tight !== void 0 ? tight : !(attrs !== null && attrs !== void 0 && attrs.tight)
662
+ });
663
+ }
664
+ return _this.options.listTypes.some((name) => toggleTight(name));
665
+ };
666
+ }
667
+ };
668
+ }
669
+ });
670
+ var md = markdownit();
671
+ function scanDelims(text, pos) {
672
+ md.inline.State.prototype.scanDelims.call({
673
+ src: text,
674
+ posMax: text.length
675
+ });
676
+ const state = new md.inline.State(text, null, null, []);
677
+ return state.scanDelims(pos, true);
678
+ }
679
+ function shiftDelim(text, delim, start2, offset) {
680
+ let res = text.substring(0, start2) + text.substring(start2 + delim.length);
681
+ res = res.substring(0, start2 + offset) + delim + res.substring(start2 + offset);
682
+ return res;
683
+ }
684
+ function trimStart(text, delim, from, to) {
685
+ let pos = from, res = text;
686
+ while (pos < to) {
687
+ if (scanDelims(res, pos).can_open) {
688
+ break;
689
+ }
690
+ res = shiftDelim(res, delim, pos, 1);
691
+ pos++;
692
+ }
693
+ return {
694
+ text: res,
695
+ from: pos,
696
+ to
697
+ };
698
+ }
699
+ function trimEnd(text, delim, from, to) {
700
+ let pos = to, res = text;
701
+ while (pos > from) {
702
+ if (scanDelims(res, pos).can_close) {
703
+ break;
704
+ }
705
+ res = shiftDelim(res, delim, pos, -1);
706
+ pos--;
707
+ }
708
+ return {
709
+ text: res,
710
+ from,
711
+ to: pos
712
+ };
713
+ }
714
+ function trimInline(text, delim, from, to) {
715
+ let state = {
716
+ text,
717
+ from,
718
+ to
719
+ };
720
+ state = trimStart(state.text, delim, state.from, state.to);
721
+ state = trimEnd(state.text, delim, state.from, state.to);
722
+ if (state.to - state.from < delim.length + 1) {
723
+ state.text = state.text.substring(0, state.from) + state.text.substring(state.to + delim.length);
724
+ }
725
+ return state.text;
726
+ }
727
+ var MarkdownSerializerState = class extends MarkdownSerializerState$1 {
728
+ constructor(nodes, marks, options) {
729
+ super(nodes, marks, options !== null && options !== void 0 ? options : {});
730
+ __publicField(this, "inTable", false);
731
+ this.inlines = [];
732
+ }
733
+ render(node, parent, index) {
734
+ super.render(node, parent, index);
735
+ const top = this.inlines[this.inlines.length - 1];
736
+ if (top !== null && top !== void 0 && top.start && top !== null && top !== void 0 && top.end) {
737
+ const {
738
+ delimiter,
739
+ start: start2,
740
+ end
741
+ } = this.normalizeInline(top);
742
+ this.out = trimInline(this.out, delimiter, start2, end);
743
+ this.inlines.pop();
744
+ }
745
+ }
746
+ markString(mark, open, parent, index) {
747
+ const info = this.marks[mark.type.name];
748
+ if (info.expelEnclosingWhitespace) {
749
+ if (open) {
750
+ this.inlines.push({
751
+ start: this.out.length,
752
+ delimiter: info.open
753
+ });
754
+ } else {
755
+ const top = this.inlines.pop();
756
+ this.inlines.push({
757
+ ...top,
758
+ end: this.out.length
759
+ });
760
+ }
761
+ }
762
+ return super.markString(mark, open, parent, index);
763
+ }
764
+ normalizeInline(inline) {
765
+ let {
766
+ start: start2,
767
+ end
768
+ } = inline;
769
+ while (this.out.charAt(start2).match(/\s/)) {
770
+ start2++;
771
+ }
772
+ return {
773
+ ...inline,
774
+ start: start2
775
+ };
776
+ }
777
+ };
778
+ var HTMLMark = Mark2.create({
779
+ name: "markdownHTMLMark",
780
+ /**
781
+ * @return {{markdown: MarkdownMarkSpec}}
782
+ */
783
+ addStorage() {
784
+ return {
785
+ markdown: {
786
+ serialize: {
787
+ open(state, mark) {
788
+ var _getMarkTags$, _getMarkTags;
789
+ if (!this.editor.storage.markdown.options.html) {
790
+ console.warn(`Tiptap Markdown: "${mark.type.name}" mark is only available in html mode`);
791
+ return "";
792
+ }
793
+ return (_getMarkTags$ = (_getMarkTags = getMarkTags(mark)) === null || _getMarkTags === void 0 ? void 0 : _getMarkTags[0]) !== null && _getMarkTags$ !== void 0 ? _getMarkTags$ : "";
794
+ },
795
+ close(state, mark) {
796
+ var _getMarkTags$2, _getMarkTags2;
797
+ if (!this.editor.storage.markdown.options.html) {
798
+ return "";
799
+ }
800
+ return (_getMarkTags$2 = (_getMarkTags2 = getMarkTags(mark)) === null || _getMarkTags2 === void 0 ? void 0 : _getMarkTags2[1]) !== null && _getMarkTags$2 !== void 0 ? _getMarkTags$2 : "";
801
+ }
802
+ },
803
+ parse: {
804
+ // handled by markdown-it
805
+ }
806
+ }
807
+ };
808
+ }
809
+ });
810
+ function getMarkTags(mark) {
811
+ const schema = mark.type.schema;
812
+ const node = schema.text(" ", [mark]);
813
+ const html = getHTMLFromFragment(Fragment.from(node), schema);
814
+ const match = html.match(/^(<.*?>) (<\/.*?>)$/);
815
+ return match ? [match[1], match[2]] : null;
816
+ }
817
+ function elementFromString(value) {
818
+ const wrappedValue = `<body>${value}</body>`;
819
+ return new window.DOMParser().parseFromString(wrappedValue, "text/html").body;
820
+ }
821
+ function escapeHTML(value) {
822
+ return value === null || value === void 0 ? void 0 : value.replace(/</g, "&lt;").replace(/>/g, "&gt;");
823
+ }
824
+ function extractElement(node) {
825
+ const parent = node.parentElement;
826
+ const prepend = parent.cloneNode();
827
+ while (parent.firstChild && parent.firstChild !== node) {
828
+ prepend.appendChild(parent.firstChild);
829
+ }
830
+ if (prepend.childNodes.length > 0) {
831
+ parent.parentElement.insertBefore(prepend, parent);
832
+ }
833
+ parent.parentElement.insertBefore(node, parent);
834
+ if (parent.childNodes.length === 0) {
835
+ parent.remove();
836
+ }
837
+ }
838
+ function unwrapElement(node) {
839
+ const parent = node.parentNode;
840
+ while (node.firstChild)
841
+ parent.insertBefore(node.firstChild, node);
842
+ parent.removeChild(node);
843
+ }
844
+ var HTMLNode = Node$1.create({
845
+ name: "markdownHTMLNode",
846
+ addStorage() {
847
+ return {
848
+ markdown: {
849
+ serialize(state, node, parent) {
850
+ if (this.editor.storage.markdown.options.html) {
851
+ state.write(serializeHTML(node, parent));
852
+ } else {
853
+ console.warn(`Tiptap Markdown: "${node.type.name}" node is only available in html mode`);
854
+ state.write(`[${node.type.name}]`);
855
+ }
856
+ if (node.isBlock) {
857
+ state.closeBlock(node);
858
+ }
859
+ },
860
+ parse: {
861
+ // handled by markdown-it
862
+ }
863
+ }
864
+ };
865
+ }
866
+ });
867
+ function serializeHTML(node, parent) {
868
+ const schema = node.type.schema;
869
+ const html = getHTMLFromFragment(Fragment.from(node), schema);
870
+ if (node.isBlock && (parent instanceof Fragment || parent.type.name === schema.topNodeType.name)) {
871
+ return formatBlock(html);
872
+ }
873
+ return html;
874
+ }
875
+ function formatBlock(html) {
876
+ const dom = elementFromString(html);
877
+ const element = dom.firstElementChild;
878
+ element.innerHTML = element.innerHTML.trim() ? `
879
+ ${element.innerHTML}
880
+ ` : `
881
+ `;
882
+ return element.outerHTML;
883
+ }
884
+ var Blockquote = Node$1.create({
885
+ name: "blockquote"
886
+ });
887
+ var Blockquote$1 = Blockquote.extend({
888
+ /**
889
+ * @return {{markdown: MarkdownNodeSpec}}
890
+ */
891
+ addStorage() {
892
+ return {
893
+ markdown: {
894
+ serialize: defaultMarkdownSerializer.nodes.blockquote,
895
+ parse: {
896
+ // handled by markdown-it
897
+ }
898
+ }
899
+ };
900
+ }
901
+ });
902
+ var BulletList = Node$1.create({
903
+ name: "bulletList"
904
+ });
905
+ var BulletList$1 = BulletList.extend({
906
+ /**
907
+ * @return {{markdown: MarkdownNodeSpec}}
908
+ */
909
+ addStorage() {
910
+ return {
911
+ markdown: {
912
+ serialize(state, node) {
913
+ return state.renderList(node, " ", () => (this.editor.storage.markdown.options.bulletListMarker || "-") + " ");
914
+ },
915
+ parse: {
916
+ // handled by markdown-it
917
+ }
918
+ }
919
+ };
920
+ }
921
+ });
922
+ var CodeBlock = Node$1.create({
923
+ name: "codeBlock"
924
+ });
925
+ var CodeBlock$1 = CodeBlock.extend({
926
+ /**
927
+ * @return {{markdown: MarkdownNodeSpec}}
928
+ */
929
+ addStorage() {
930
+ return {
931
+ markdown: {
932
+ serialize(state, node) {
933
+ state.write("```" + (node.attrs.language || "") + "\n");
934
+ state.text(node.textContent, false);
935
+ state.ensureNewLine();
936
+ state.write("```");
937
+ state.closeBlock(node);
938
+ },
939
+ parse: {
940
+ setup(markdownit2) {
941
+ var _this$options$languag;
942
+ markdownit2.set({
943
+ langPrefix: (_this$options$languag = this.options.languageClassPrefix) !== null && _this$options$languag !== void 0 ? _this$options$languag : "language-"
944
+ });
945
+ },
946
+ updateDOM(element) {
947
+ element.innerHTML = element.innerHTML.replace(/\n<\/code><\/pre>/g, "</code></pre>");
948
+ }
949
+ }
950
+ }
951
+ };
952
+ }
953
+ });
954
+ var HardBreak = Node$1.create({
955
+ name: "hardBreak"
956
+ });
957
+ var HardBreak$1 = HardBreak.extend({
958
+ /**
959
+ * @return {{markdown: MarkdownNodeSpec}}
960
+ */
961
+ addStorage() {
962
+ return {
963
+ markdown: {
964
+ serialize(state, node, parent, index) {
965
+ for (let i = index + 1; i < parent.childCount; i++)
966
+ if (parent.child(i).type != node.type) {
967
+ state.write(state.inTable ? HTMLNode.storage.markdown.serialize.call(this, state, node, parent) : "\\\n");
968
+ return;
969
+ }
970
+ },
971
+ parse: {
972
+ // handled by markdown-it
973
+ }
974
+ }
975
+ };
976
+ }
977
+ });
978
+ var Heading = Node$1.create({
979
+ name: "heading"
980
+ });
981
+ var Heading$1 = Heading.extend({
982
+ /**
983
+ * @return {{markdown: MarkdownNodeSpec}}
984
+ */
985
+ addStorage() {
986
+ return {
987
+ markdown: {
988
+ serialize: defaultMarkdownSerializer.nodes.heading,
989
+ parse: {
990
+ // handled by markdown-it
991
+ }
992
+ }
993
+ };
994
+ }
995
+ });
996
+ var HorizontalRule = Node$1.create({
997
+ name: "horizontalRule"
998
+ });
999
+ var HorizontalRule$1 = HorizontalRule.extend({
1000
+ /**
1001
+ * @return {{markdown: MarkdownNodeSpec}}
1002
+ */
1003
+ addStorage() {
1004
+ return {
1005
+ markdown: {
1006
+ serialize: defaultMarkdownSerializer.nodes.horizontal_rule,
1007
+ parse: {
1008
+ // handled by markdown-it
1009
+ }
1010
+ }
1011
+ };
1012
+ }
1013
+ });
1014
+ var Image = Node$1.create({
1015
+ name: "image"
1016
+ });
1017
+ var Image$1 = Image.extend({
1018
+ /**
1019
+ * @return {{markdown: MarkdownNodeSpec}}
1020
+ */
1021
+ addStorage() {
1022
+ return {
1023
+ markdown: {
1024
+ serialize: defaultMarkdownSerializer.nodes.image,
1025
+ parse: {
1026
+ // handled by markdown-it
1027
+ }
1028
+ }
1029
+ };
1030
+ }
1031
+ });
1032
+ var ListItem = Node$1.create({
1033
+ name: "listItem"
1034
+ });
1035
+ var ListItem$1 = ListItem.extend({
1036
+ /**
1037
+ * @return {{markdown: MarkdownNodeSpec}}
1038
+ */
1039
+ addStorage() {
1040
+ return {
1041
+ markdown: {
1042
+ serialize: defaultMarkdownSerializer.nodes.list_item,
1043
+ parse: {
1044
+ // handled by markdown-it
1045
+ }
1046
+ }
1047
+ };
1048
+ }
1049
+ });
1050
+ var OrderedList = Node$1.create({
1051
+ name: "orderedList"
1052
+ });
1053
+ function findIndexOfAdjacentNode(node, parent, index) {
1054
+ let i = 0;
1055
+ for (; index - i > 0; i++) {
1056
+ if (parent.child(index - i - 1).type.name !== node.type.name) {
1057
+ break;
1058
+ }
1059
+ }
1060
+ return i;
1061
+ }
1062
+ var OrderedList$1 = OrderedList.extend({
1063
+ /**
1064
+ * @return {{markdown: MarkdownNodeSpec}}
1065
+ */
1066
+ addStorage() {
1067
+ return {
1068
+ markdown: {
1069
+ serialize(state, node, parent, index) {
1070
+ const start2 = node.attrs.start || 1;
1071
+ const maxW = String(start2 + node.childCount - 1).length;
1072
+ const space = state.repeat(" ", maxW + 2);
1073
+ const adjacentIndex = findIndexOfAdjacentNode(node, parent, index);
1074
+ const separator = adjacentIndex % 2 ? ") " : ". ";
1075
+ state.renderList(node, space, (i) => {
1076
+ const nStr = String(start2 + i);
1077
+ return state.repeat(" ", maxW - nStr.length) + nStr + separator;
1078
+ });
1079
+ },
1080
+ parse: {
1081
+ // handled by markdown-it
1082
+ }
1083
+ }
1084
+ };
1085
+ }
1086
+ });
1087
+ var Paragraph = Node$1.create({
1088
+ name: "paragraph"
1089
+ });
1090
+ var Paragraph$1 = Paragraph.extend({
1091
+ /**
1092
+ * @return {{markdown: MarkdownNodeSpec}}
1093
+ */
1094
+ addStorage() {
1095
+ return {
1096
+ markdown: {
1097
+ serialize: defaultMarkdownSerializer.nodes.paragraph,
1098
+ parse: {
1099
+ // handled by markdown-it
1100
+ }
1101
+ }
1102
+ };
1103
+ }
1104
+ });
1105
+ function childNodes(node) {
1106
+ var _node$content$content, _node$content;
1107
+ return (_node$content$content = node === null || node === void 0 || (_node$content = node.content) === null || _node$content === void 0 ? void 0 : _node$content.content) !== null && _node$content$content !== void 0 ? _node$content$content : [];
1108
+ }
1109
+ var Table = Node$1.create({
1110
+ name: "table"
1111
+ });
1112
+ var Table$1 = Table.extend({
1113
+ /**
1114
+ * @return {{markdown: MarkdownNodeSpec}}
1115
+ */
1116
+ addStorage() {
1117
+ return {
1118
+ markdown: {
1119
+ serialize(state, node, parent) {
1120
+ if (!isMarkdownSerializable(node)) {
1121
+ HTMLNode.storage.markdown.serialize.call(this, state, node, parent);
1122
+ return;
1123
+ }
1124
+ state.inTable = true;
1125
+ node.forEach((row, p, i) => {
1126
+ state.write("| ");
1127
+ row.forEach((col, p2, j) => {
1128
+ if (j) {
1129
+ state.write(" | ");
1130
+ }
1131
+ const cellContent = col.firstChild;
1132
+ if (cellContent.textContent.trim()) {
1133
+ state.renderInline(cellContent);
1134
+ }
1135
+ });
1136
+ state.write(" |");
1137
+ state.ensureNewLine();
1138
+ if (!i) {
1139
+ const delimiterRow = Array.from({
1140
+ length: row.childCount
1141
+ }).map(() => "---").join(" | ");
1142
+ state.write(`| ${delimiterRow} |`);
1143
+ state.ensureNewLine();
1144
+ }
1145
+ });
1146
+ state.closeBlock(node);
1147
+ state.inTable = false;
1148
+ },
1149
+ parse: {
1150
+ // handled by markdown-it
1151
+ }
1152
+ }
1153
+ };
1154
+ }
1155
+ });
1156
+ function hasSpan(node) {
1157
+ return node.attrs.colspan > 1 || node.attrs.rowspan > 1;
1158
+ }
1159
+ function isMarkdownSerializable(node) {
1160
+ const rows = childNodes(node);
1161
+ const firstRow = rows[0];
1162
+ const bodyRows = rows.slice(1);
1163
+ if (childNodes(firstRow).some((cell) => cell.type.name !== "tableHeader" || hasSpan(cell) || cell.childCount > 1)) {
1164
+ return false;
1165
+ }
1166
+ if (bodyRows.some((row) => childNodes(row).some((cell) => cell.type.name === "tableHeader" || hasSpan(cell) || cell.childCount > 1))) {
1167
+ return false;
1168
+ }
1169
+ return true;
1170
+ }
1171
+ var TaskItem = Node$1.create({
1172
+ name: "taskItem"
1173
+ });
1174
+ var TaskItem$1 = TaskItem.extend({
1175
+ /**
1176
+ * @return {{markdown: MarkdownNodeSpec}}
1177
+ */
1178
+ addStorage() {
1179
+ return {
1180
+ markdown: {
1181
+ serialize(state, node) {
1182
+ const check = node.attrs.checked ? "[x]" : "[ ]";
1183
+ state.write(`${check} `);
1184
+ state.renderContent(node);
1185
+ },
1186
+ parse: {
1187
+ updateDOM(element) {
1188
+ [...element.querySelectorAll(".task-list-item")].forEach((item) => {
1189
+ const input = item.querySelector("input");
1190
+ item.setAttribute("data-type", "taskItem");
1191
+ if (input) {
1192
+ item.setAttribute("data-checked", input.checked);
1193
+ input.remove();
1194
+ }
1195
+ });
1196
+ }
1197
+ }
1198
+ }
1199
+ };
1200
+ }
1201
+ });
1202
+ var TaskList = Node$1.create({
1203
+ name: "taskList"
1204
+ });
1205
+ var TaskList$1 = TaskList.extend({
1206
+ /**
1207
+ * @return {{markdown: MarkdownNodeSpec}}
1208
+ */
1209
+ addStorage() {
1210
+ return {
1211
+ markdown: {
1212
+ serialize: BulletList$1.storage.markdown.serialize,
1213
+ parse: {
1214
+ setup(markdownit2) {
1215
+ markdownit2.use(taskListPlugin);
1216
+ },
1217
+ updateDOM(element) {
1218
+ [...element.querySelectorAll(".contains-task-list")].forEach((list) => {
1219
+ list.setAttribute("data-type", "taskList");
1220
+ });
1221
+ }
1222
+ }
1223
+ }
1224
+ };
1225
+ }
1226
+ });
1227
+ var Text = Node$1.create({
1228
+ name: "text"
1229
+ });
1230
+ var Text$1 = Text.extend({
1231
+ /**
1232
+ * @return {{markdown: MarkdownNodeSpec}}
1233
+ */
1234
+ addStorage() {
1235
+ return {
1236
+ markdown: {
1237
+ serialize(state, node) {
1238
+ state.text(escapeHTML(node.text));
1239
+ },
1240
+ parse: {
1241
+ // handled by markdown-it
1242
+ }
1243
+ }
1244
+ };
1245
+ }
1246
+ });
1247
+ var Bold = Mark2.create({
1248
+ name: "bold"
1249
+ });
1250
+ var Bold$1 = Bold.extend({
1251
+ /**
1252
+ * @return {{markdown: MarkdownMarkSpec}}
1253
+ */
1254
+ addStorage() {
1255
+ return {
1256
+ markdown: {
1257
+ serialize: defaultMarkdownSerializer.marks.strong,
1258
+ parse: {
1259
+ // handled by markdown-it
1260
+ }
1261
+ }
1262
+ };
1263
+ }
1264
+ });
1265
+ var Code = Mark2.create({
1266
+ name: "code"
1267
+ });
1268
+ var Code$1 = Code.extend({
1269
+ /**
1270
+ * @return {{markdown: MarkdownMarkSpec}}
1271
+ */
1272
+ addStorage() {
1273
+ return {
1274
+ markdown: {
1275
+ serialize: defaultMarkdownSerializer.marks.code,
1276
+ parse: {
1277
+ // handled by markdown-it
1278
+ }
1279
+ }
1280
+ };
1281
+ }
1282
+ });
1283
+ var Italic = Mark2.create({
1284
+ name: "italic"
1285
+ });
1286
+ var Italic$1 = Italic.extend({
1287
+ /**
1288
+ * @return {{markdown: MarkdownMarkSpec}}
1289
+ */
1290
+ addStorage() {
1291
+ return {
1292
+ markdown: {
1293
+ serialize: defaultMarkdownSerializer.marks.em,
1294
+ parse: {
1295
+ // handled by markdown-it
1296
+ }
1297
+ }
1298
+ };
1299
+ }
1300
+ });
1301
+ var Link = Mark2.create({
1302
+ name: "link"
1303
+ });
1304
+ var Link$1 = Link.extend({
1305
+ /**
1306
+ * @return {{markdown: MarkdownMarkSpec}}
1307
+ */
1308
+ addStorage() {
1309
+ return {
1310
+ markdown: {
1311
+ serialize: defaultMarkdownSerializer.marks.link,
1312
+ parse: {
1313
+ // handled by markdown-it
1314
+ }
1315
+ }
1316
+ };
1317
+ }
1318
+ });
1319
+ var Strike = Mark2.create({
1320
+ name: "strike"
1321
+ });
1322
+ var Strike$1 = Strike.extend({
1323
+ /**
1324
+ * @return {{markdown: MarkdownMarkSpec}}
1325
+ */
1326
+ addStorage() {
1327
+ return {
1328
+ markdown: {
1329
+ serialize: {
1330
+ open: "~~",
1331
+ close: "~~",
1332
+ expelEnclosingWhitespace: true
1333
+ },
1334
+ parse: {
1335
+ // handled by markdown-it
1336
+ }
1337
+ }
1338
+ };
1339
+ }
1340
+ });
1341
+ var markdownExtensions = [Blockquote$1, BulletList$1, CodeBlock$1, HardBreak$1, Heading$1, HorizontalRule$1, HTMLNode, Image$1, ListItem$1, OrderedList$1, Paragraph$1, Table$1, TaskItem$1, TaskList$1, Text$1, Bold$1, Code$1, HTMLMark, Italic$1, Link$1, Strike$1];
1342
+ function getMarkdownSpec(extension) {
1343
+ var _extension$storage, _markdownExtensions$f;
1344
+ const markdownSpec = (_extension$storage = extension.storage) === null || _extension$storage === void 0 ? void 0 : _extension$storage.markdown;
1345
+ const defaultMarkdownSpec = (_markdownExtensions$f = markdownExtensions.find((e) => e.name === extension.name)) === null || _markdownExtensions$f === void 0 ? void 0 : _markdownExtensions$f.storage.markdown;
1346
+ if (markdownSpec || defaultMarkdownSpec) {
1347
+ return {
1348
+ ...defaultMarkdownSpec,
1349
+ ...markdownSpec
1350
+ };
1351
+ }
1352
+ return null;
1353
+ }
1354
+ var MarkdownSerializer = class {
1355
+ constructor(editor) {
1356
+ __publicField(this, "editor", null);
1357
+ this.editor = editor;
1358
+ }
1359
+ serialize(content) {
1360
+ const state = new MarkdownSerializerState(this.nodes, this.marks, {
1361
+ hardBreakNodeName: HardBreak$1.name
1362
+ });
1363
+ state.renderContent(content);
1364
+ return state.out;
1365
+ }
1366
+ get nodes() {
1367
+ var _this$editor$extensio;
1368
+ return {
1369
+ ...Object.fromEntries(Object.keys(this.editor.schema.nodes).map((name) => [name, this.serializeNode(HTMLNode)])),
1370
+ ...Object.fromEntries((_this$editor$extensio = this.editor.extensionManager.extensions.filter((extension) => extension.type === "node" && this.serializeNode(extension)).map((extension) => [extension.name, this.serializeNode(extension)])) !== null && _this$editor$extensio !== void 0 ? _this$editor$extensio : [])
1371
+ };
1372
+ }
1373
+ get marks() {
1374
+ var _this$editor$extensio2;
1375
+ return {
1376
+ ...Object.fromEntries(Object.keys(this.editor.schema.marks).map((name) => [name, this.serializeMark(HTMLMark)])),
1377
+ ...Object.fromEntries((_this$editor$extensio2 = this.editor.extensionManager.extensions.filter((extension) => extension.type === "mark" && this.serializeMark(extension)).map((extension) => [extension.name, this.serializeMark(extension)])) !== null && _this$editor$extensio2 !== void 0 ? _this$editor$extensio2 : [])
1378
+ };
1379
+ }
1380
+ serializeNode(node) {
1381
+ var _getMarkdownSpec;
1382
+ return (_getMarkdownSpec = getMarkdownSpec(node)) === null || _getMarkdownSpec === void 0 || (_getMarkdownSpec = _getMarkdownSpec.serialize) === null || _getMarkdownSpec === void 0 ? void 0 : _getMarkdownSpec.bind({
1383
+ editor: this.editor,
1384
+ options: node.options
1385
+ });
1386
+ }
1387
+ serializeMark(mark) {
1388
+ var _getMarkdownSpec2;
1389
+ const serialize = (_getMarkdownSpec2 = getMarkdownSpec(mark)) === null || _getMarkdownSpec2 === void 0 ? void 0 : _getMarkdownSpec2.serialize;
1390
+ return serialize ? {
1391
+ ...serialize,
1392
+ open: typeof serialize.open === "function" ? serialize.open.bind({
1393
+ editor: this.editor,
1394
+ options: mark.options
1395
+ }) : serialize.open,
1396
+ close: typeof serialize.close === "function" ? serialize.close.bind({
1397
+ editor: this.editor,
1398
+ options: mark.options
1399
+ }) : serialize.close
1400
+ } : null;
1401
+ }
1402
+ };
1403
+ var MarkdownParser = class {
1404
+ constructor(editor, _ref) {
1405
+ __publicField(this, "editor", null);
1406
+ __publicField(this, "md", null);
1407
+ let {
1408
+ html,
1409
+ linkify,
1410
+ breaks
1411
+ } = _ref;
1412
+ this.editor = editor;
1413
+ this.md = this.withPatchedRenderer(markdownit({
1414
+ html,
1415
+ linkify,
1416
+ breaks
1417
+ }));
1418
+ }
1419
+ parse(content) {
1420
+ let {
1421
+ inline
1422
+ } = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
1423
+ if (typeof content === "string") {
1424
+ this.editor.extensionManager.extensions.forEach((extension) => {
1425
+ var _getMarkdownSpec;
1426
+ return (_getMarkdownSpec = getMarkdownSpec(extension)) === null || _getMarkdownSpec === void 0 || (_getMarkdownSpec = _getMarkdownSpec.parse) === null || _getMarkdownSpec === void 0 || (_getMarkdownSpec = _getMarkdownSpec.setup) === null || _getMarkdownSpec === void 0 ? void 0 : _getMarkdownSpec.call({
1427
+ editor: this.editor,
1428
+ options: extension.options
1429
+ }, this.md);
1430
+ });
1431
+ const renderedHTML = this.md.render(content);
1432
+ const element = elementFromString(renderedHTML);
1433
+ this.editor.extensionManager.extensions.forEach((extension) => {
1434
+ var _getMarkdownSpec2;
1435
+ return (_getMarkdownSpec2 = getMarkdownSpec(extension)) === null || _getMarkdownSpec2 === void 0 || (_getMarkdownSpec2 = _getMarkdownSpec2.parse) === null || _getMarkdownSpec2 === void 0 || (_getMarkdownSpec2 = _getMarkdownSpec2.updateDOM) === null || _getMarkdownSpec2 === void 0 ? void 0 : _getMarkdownSpec2.call({
1436
+ editor: this.editor,
1437
+ options: extension.options
1438
+ }, element);
1439
+ });
1440
+ this.normalizeDOM(element, {
1441
+ inline,
1442
+ content
1443
+ });
1444
+ return element.innerHTML;
1445
+ }
1446
+ return content;
1447
+ }
1448
+ normalizeDOM(node, _ref2) {
1449
+ let {
1450
+ inline,
1451
+ content
1452
+ } = _ref2;
1453
+ this.normalizeBlocks(node);
1454
+ node.querySelectorAll("*").forEach((el) => {
1455
+ var _el$nextSibling;
1456
+ if (((_el$nextSibling = el.nextSibling) === null || _el$nextSibling === void 0 ? void 0 : _el$nextSibling.nodeType) === Node.TEXT_NODE && !el.closest("pre")) {
1457
+ el.nextSibling.textContent = el.nextSibling.textContent.replace(/^\n/, "");
1458
+ }
1459
+ });
1460
+ if (inline) {
1461
+ this.normalizeInline(node, content);
1462
+ }
1463
+ return node;
1464
+ }
1465
+ normalizeBlocks(node) {
1466
+ const blocks = Object.values(this.editor.schema.nodes).filter((node2) => node2.isBlock);
1467
+ const selector = blocks.map((block) => {
1468
+ var _block$spec$parseDOM;
1469
+ return (_block$spec$parseDOM = block.spec.parseDOM) === null || _block$spec$parseDOM === void 0 ? void 0 : _block$spec$parseDOM.map((spec) => spec.tag);
1470
+ }).flat().filter(Boolean).join(",");
1471
+ if (!selector) {
1472
+ return;
1473
+ }
1474
+ [...node.querySelectorAll(selector)].forEach((el) => {
1475
+ if (el.parentElement.matches("p")) {
1476
+ extractElement(el);
1477
+ }
1478
+ });
1479
+ }
1480
+ normalizeInline(node, content) {
1481
+ var _node$firstElementChi;
1482
+ if ((_node$firstElementChi = node.firstElementChild) !== null && _node$firstElementChi !== void 0 && _node$firstElementChi.matches("p")) {
1483
+ var _content$match$, _content$match, _content$match$2, _content$match2;
1484
+ const firstParagraph = node.firstElementChild;
1485
+ const {
1486
+ nextElementSibling
1487
+ } = firstParagraph;
1488
+ const startSpaces = (_content$match$ = (_content$match = content.match(/^\s+/)) === null || _content$match === void 0 ? void 0 : _content$match[0]) !== null && _content$match$ !== void 0 ? _content$match$ : "";
1489
+ const endSpaces = !nextElementSibling ? (_content$match$2 = (_content$match2 = content.match(/\s+$/)) === null || _content$match2 === void 0 ? void 0 : _content$match2[0]) !== null && _content$match$2 !== void 0 ? _content$match$2 : "" : "";
1490
+ if (content.match(/^\n\n/)) {
1491
+ firstParagraph.innerHTML = `${firstParagraph.innerHTML}${endSpaces}`;
1492
+ return;
1493
+ }
1494
+ unwrapElement(firstParagraph);
1495
+ node.innerHTML = `${startSpaces}${node.innerHTML}${endSpaces}`;
1496
+ }
1497
+ }
1498
+ /**
1499
+ * @param {markdownit} md
1500
+ */
1501
+ withPatchedRenderer(md2) {
1502
+ const withoutNewLine = (renderer) => function() {
1503
+ const rendered = renderer(...arguments);
1504
+ if (rendered === "\n") {
1505
+ return rendered;
1506
+ }
1507
+ if (rendered[rendered.length - 1] === "\n") {
1508
+ return rendered.slice(0, -1);
1509
+ }
1510
+ return rendered;
1511
+ };
1512
+ md2.renderer.rules.hardbreak = withoutNewLine(md2.renderer.rules.hardbreak);
1513
+ md2.renderer.rules.softbreak = withoutNewLine(md2.renderer.rules.softbreak);
1514
+ md2.renderer.rules.fence = withoutNewLine(md2.renderer.rules.fence);
1515
+ md2.renderer.rules.code_block = withoutNewLine(md2.renderer.rules.code_block);
1516
+ md2.renderer.renderToken = withoutNewLine(md2.renderer.renderToken.bind(md2.renderer));
1517
+ return md2;
1518
+ }
1519
+ };
1520
+ var MarkdownClipboard = Extension6.create({
1521
+ name: "markdownClipboard",
1522
+ addOptions() {
1523
+ return {
1524
+ transformPastedText: false,
1525
+ transformCopiedText: false
1526
+ };
1527
+ },
1528
+ addProseMirrorPlugins() {
1529
+ return [new Plugin4({
1530
+ key: new PluginKey("markdownClipboard"),
1531
+ props: {
1532
+ clipboardTextParser: (text, context, plainText) => {
1533
+ if (plainText || !this.options.transformPastedText) {
1534
+ return null;
1535
+ }
1536
+ const parsed = this.editor.storage.markdown.parser.parse(text, {
1537
+ inline: true
1538
+ });
1539
+ return DOMParser.fromSchema(this.editor.schema).parseSlice(elementFromString(parsed), {
1540
+ preserveWhitespace: true,
1541
+ context
1542
+ });
1543
+ },
1544
+ /**
1545
+ * @param {import('prosemirror-model').Slice} slice
1546
+ */
1547
+ clipboardTextSerializer: (slice) => {
1548
+ if (!this.options.transformCopiedText) {
1549
+ return null;
1550
+ }
1551
+ return this.editor.storage.markdown.serializer.serialize(slice.content);
1552
+ }
1553
+ }
1554
+ })];
1555
+ }
1556
+ });
1557
+ var Markdown = Extension6.create({
1558
+ name: "markdown",
1559
+ priority: 50,
1560
+ addOptions() {
1561
+ return {
1562
+ html: true,
1563
+ tightLists: true,
1564
+ tightListClass: "tight",
1565
+ bulletListMarker: "-",
1566
+ linkify: false,
1567
+ breaks: false,
1568
+ transformPastedText: false,
1569
+ transformCopiedText: false
1570
+ };
1571
+ },
1572
+ addCommands() {
1573
+ const commands = extensions.Commands.config.addCommands();
1574
+ return {
1575
+ setContent: (content, emitUpdate, parseOptions) => (props) => {
1576
+ return commands.setContent(props.editor.storage.markdown.parser.parse(content), emitUpdate, parseOptions)(props);
1577
+ },
1578
+ insertContentAt: (range, content, options) => (props) => {
1579
+ return commands.insertContentAt(range, props.editor.storage.markdown.parser.parse(content, {
1580
+ inline: true
1581
+ }), options)(props);
1582
+ }
1583
+ };
1584
+ },
1585
+ onBeforeCreate() {
1586
+ this.editor.storage.markdown = {
1587
+ options: {
1588
+ ...this.options
1589
+ },
1590
+ parser: new MarkdownParser(this.editor, this.options),
1591
+ serializer: new MarkdownSerializer(this.editor),
1592
+ getMarkdown: () => {
1593
+ return this.editor.storage.markdown.serializer.serialize(this.editor.state.doc);
1594
+ }
1595
+ };
1596
+ this.editor.options.initialContent = this.editor.options.content;
1597
+ this.editor.options.content = this.editor.storage.markdown.parser.parse(this.editor.options.content);
1598
+ },
1599
+ onCreate() {
1600
+ this.editor.options.content = this.editor.options.initialContent;
1601
+ delete this.editor.options.initialContent;
1602
+ },
1603
+ addStorage() {
1604
+ return {
1605
+ /// storage will be defined in onBeforeCreate() to prevent initial object overriding
1606
+ };
1607
+ },
1608
+ addExtensions() {
1609
+ return [MarkdownTightLists.configure({
1610
+ tight: this.options.tightLists,
1611
+ tightClass: this.options.tightListClass
1612
+ }), MarkdownClipboard.configure({
1613
+ transformPastedText: this.options.transformPastedText,
1614
+ transformCopiedText: this.options.transformCopiedText
1615
+ })];
1616
+ }
1617
+ });
1618
+
1619
+ // src/extensions/markdown.ts
1620
+ var resolveExt = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
1621
+ var Markdown2 = resolveExt(Markdown, "Markdown");
1622
+
1623
+ // src/extensions/image.ts
1624
+ import * as _Image from "@tiptap/extension-image";
1625
+ import { Plugin as Plugin5, PluginKey as PluginKey2 } from "@tiptap/pm/state";
1626
+ var resolveExt2 = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
1627
+ var BaseImage = resolveExt2(_Image, "Image");
1628
+ var imagePastePluginKey = new PluginKey2("imagePasteHandler");
1629
+ function insertImageNode(view, src, alt) {
1630
+ const { schema } = view.state;
1631
+ const imageType = schema.nodes.image;
1632
+ if (!imageType) return;
1633
+ const node = imageType.create({ src, alt: alt || "" });
1634
+ const tr = view.state.tr.replaceSelectionWith(node);
1635
+ view.dispatch(tr);
1636
+ }
1637
+ function updateImageSrc(view, oldSrc, newSrc) {
1638
+ const { doc } = view.state;
1639
+ let foundPos = null;
1640
+ let foundAttrs = null;
1641
+ doc.descendants((node, pos) => {
1642
+ if (node.type.name === "image" && node.attrs.src === oldSrc) {
1643
+ foundPos = pos;
1644
+ foundAttrs = node.attrs;
1645
+ return false;
1646
+ }
1647
+ });
1648
+ if (foundPos !== null && foundAttrs) {
1649
+ const tr = view.state.tr.setNodeMarkup(foundPos, void 0, {
1650
+ ...foundAttrs,
1651
+ src: newSrc
1652
+ });
1653
+ view.dispatch(tr);
1654
+ }
1655
+ }
1656
+ function removeImageNode(view, src) {
1657
+ const { doc } = view.state;
1658
+ let foundPos = null;
1659
+ let nodeSize = 0;
1660
+ doc.descendants((node, pos) => {
1661
+ if (node.type.name === "image" && node.attrs.src === src) {
1662
+ foundPos = pos;
1663
+ nodeSize = node.nodeSize;
1664
+ return false;
1665
+ }
1666
+ });
1667
+ if (foundPos !== null) {
1668
+ const tr = view.state.tr.delete(foundPos, foundPos + nodeSize);
1669
+ view.dispatch(tr);
1670
+ }
1671
+ }
1672
+ function handleImageInsertion(view, file, options) {
1673
+ if (typeof options.upload === "function") {
1674
+ const hasObjectUrl = typeof URL !== "undefined" && typeof URL.createObjectURL === "function";
1675
+ const tempUrl = hasObjectUrl ? URL.createObjectURL(file) : null;
1676
+ if (tempUrl) {
1677
+ insertImageNode(view, tempUrl, file.name);
1678
+ }
1679
+ Promise.resolve(options.upload(file)).then((uploadedUrl) => {
1680
+ if (!uploadedUrl) {
1681
+ throw new Error("Upload handler returned empty URL");
1682
+ }
1683
+ if (tempUrl) {
1684
+ updateImageSrc(view, tempUrl, uploadedUrl);
1685
+ if (typeof URL.revokeObjectURL === "function") {
1686
+ URL.revokeObjectURL(tempUrl);
1687
+ }
1688
+ } else {
1689
+ insertImageNode(view, uploadedUrl, file.name);
1690
+ }
1691
+ }).catch((err) => {
1692
+ const error = err instanceof Error ? err : new Error(String(err));
1693
+ console.error("Failed to upload pasted image:", error);
1694
+ if (tempUrl) {
1695
+ removeImageNode(view, tempUrl);
1696
+ if (typeof URL.revokeObjectURL === "function") {
1697
+ URL.revokeObjectURL(tempUrl);
1698
+ }
1699
+ }
1700
+ if (typeof options.onUploadError === "function") {
1701
+ options.onUploadError(error, file);
1702
+ }
1703
+ });
1704
+ } else if (options.allowBase64 !== false) {
1705
+ const maxBase64Size = options.maxBase64Size ?? 65536;
1706
+ if (file.size > maxBase64Size) {
1707
+ const err = new Error(
1708
+ `Image size (${Math.round(file.size / 1024)}KB) exceeds maxBase64Size (${Math.round(
1709
+ maxBase64Size / 1024
1710
+ )}KB). Provide an upload handler to support large images.`
1711
+ );
1712
+ console.warn(`[Editor] ${err.message}`);
1713
+ if (typeof options.onUploadError === "function") {
1714
+ options.onUploadError(err, file);
1715
+ }
1716
+ return;
1717
+ }
1718
+ if (typeof FileReader !== "undefined") {
1719
+ const reader = new FileReader();
1720
+ reader.onload = () => {
1721
+ const src = reader.result;
1722
+ if (src) {
1723
+ insertImageNode(view, src, file.name);
1724
+ }
1725
+ };
1726
+ reader.readAsDataURL(file);
1727
+ }
1728
+ }
1729
+ }
1730
+ var Image2 = BaseImage.extend({
1731
+ addOptions() {
1732
+ return {
1733
+ ...this.parent?.(),
1734
+ allowBase64: true,
1735
+ maxBase64Size: 65536,
1736
+ enablePaste: true,
1737
+ enableDrop: true,
1738
+ upload: void 0,
1739
+ onUploadError: void 0,
1740
+ HTMLAttributes: {
1741
+ class: "rounded-md max-w-full h-auto my-2"
1742
+ }
1743
+ };
1744
+ },
1745
+ addProseMirrorPlugins() {
1746
+ const parentPlugins = this.parent?.() || [];
1747
+ const options = this.options;
1748
+ if (!options.enablePaste && !options.enableDrop) {
1749
+ return parentPlugins;
1750
+ }
1751
+ const pastePlugin = new Plugin5({
1752
+ key: imagePastePluginKey,
1753
+ props: {
1754
+ handlePaste(view, event) {
1755
+ if (!options.enablePaste) return false;
1756
+ const items = Array.from(event.clipboardData?.items || []);
1757
+ const imageItems = items.filter(
1758
+ (item) => item.kind === "file" && item.type.startsWith("image/")
1759
+ );
1760
+ if (imageItems.length === 0) return false;
1761
+ event.preventDefault();
1762
+ for (const item of imageItems) {
1763
+ const file = item.getAsFile();
1764
+ if (!file) continue;
1765
+ handleImageInsertion(view, file, options);
1766
+ }
1767
+ return true;
1768
+ },
1769
+ handleDrop(view, event) {
1770
+ if (!options.enableDrop) return false;
1771
+ const files = Array.from(event.dataTransfer?.files || []);
1772
+ const imageFiles = files.filter((f) => f.type.startsWith("image/"));
1773
+ if (imageFiles.length === 0) return false;
1774
+ event.preventDefault();
1775
+ for (const file of imageFiles) {
1776
+ handleImageInsertion(view, file, options);
1777
+ }
1778
+ return true;
1779
+ }
1780
+ }
1781
+ });
1782
+ return [...parentPlugins, pastePlugin];
1783
+ }
1784
+ });
1785
+
1786
+ // src/extensions/comments.ts
1787
+ import { Mark as Mark3, mergeAttributes as mergeAttributes2 } from "@tiptap/core";
1788
+ import { Plugin as Plugin6, PluginKey as PluginKey3, TextSelection } from "@tiptap/pm/state";
1789
+ import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "@tiptap/pm/view";
1790
+ var commentsPluginKey = new PluginKey3("comments");
1791
+ var COMMENT_MARK_NAME = "comment";
1792
+ function findCommentRanges(doc) {
1793
+ const result = /* @__PURE__ */ new Map();
1794
+ doc.descendants((node, pos) => {
1795
+ if (!node.isInline) return;
1796
+ node.marks.forEach((mark) => {
1797
+ if (mark.type.name !== COMMENT_MARK_NAME) return;
1798
+ const id = mark.attrs.commentId;
1799
+ if (!id) return;
1800
+ const ranges = result.get(id) ?? [];
1801
+ const last = ranges[ranges.length - 1];
1802
+ const end = pos + node.nodeSize;
1803
+ if (last && last.to === pos) {
1804
+ last.to = end;
1805
+ } else {
1806
+ ranges.push({ from: pos, to: end });
1807
+ }
1808
+ result.set(id, ranges);
1809
+ });
1810
+ });
1811
+ return result;
1812
+ }
1813
+ function getCommentAnchors(doc) {
1814
+ const anchors = /* @__PURE__ */ new Map();
1815
+ findCommentRanges(doc).forEach((ranges, id) => {
1816
+ const from = ranges[0].from;
1817
+ const to = ranges[ranges.length - 1].to;
1818
+ anchors.set(id, {
1819
+ from,
1820
+ to,
1821
+ quote: ranges.map((r) => doc.textBetween(r.from, r.to, " ", " ")).join(" ")
1822
+ });
1823
+ });
1824
+ return anchors;
1825
+ }
1826
+ function getCommentIdsAt(state, pos = state.selection.from) {
1827
+ const $pos = state.doc.resolve(pos);
1828
+ const marks = [
1829
+ ...$pos.nodeAfter?.marks ?? [],
1830
+ ...$pos.nodeBefore?.marks ?? []
1831
+ ];
1832
+ const ids = [];
1833
+ marks.forEach((m) => {
1834
+ if (m.type.name === COMMENT_MARK_NAME && m.attrs.commentId && !ids.includes(m.attrs.commentId)) {
1835
+ ids.push(m.attrs.commentId);
1836
+ }
1837
+ });
1838
+ return ids;
1839
+ }
1840
+ function getCommentStore(editor) {
1841
+ return editor?.storage?.[COMMENT_MARK_NAME]?.store ?? null;
1842
+ }
1843
+ function resolveAuthor(author) {
1844
+ return typeof author === "function" ? author() : author;
1845
+ }
1846
+ var Comments = Mark3.create({
1847
+ name: COMMENT_MARK_NAME,
1848
+ // Typing at the edge of a comment does not extend it.
1849
+ inclusive: false,
1850
+ // Allow several comment marks (different threads) on the same text.
1851
+ excludes: "",
1852
+ addOptions() {
1853
+ return {
1854
+ store: null,
1855
+ author: { id: "anonymous", name: "Anonymous" },
1856
+ HTMLAttributes: {},
1857
+ commentClass: "pixerate-comment bg-amber-100 border-b-2 border-amber-400 dark:bg-amber-500/20 cursor-pointer",
1858
+ activeClass: "pixerate-comment-active bg-amber-200 dark:bg-amber-500/40",
1859
+ resolvedClass: "",
1860
+ activateOnSelection: true
1861
+ };
1862
+ },
1863
+ addStorage() {
1864
+ return {
1865
+ store: this.options.store ?? new CommentStore(),
1866
+ // tiptap-markdown: comments are not part of the exported markdown.
1867
+ markdown: { serialize: { open: "", close: "" }, parse: {} }
1868
+ };
1869
+ },
1870
+ addAttributes() {
1871
+ return {
1872
+ commentId: {
1873
+ default: null,
1874
+ parseHTML: (el) => el.getAttribute("data-comment-id"),
1875
+ renderHTML: (attrs) => attrs.commentId ? { "data-comment-id": attrs.commentId } : {}
1876
+ }
1877
+ };
1878
+ },
1879
+ parseHTML() {
1880
+ return [{ tag: "span[data-comment-id]" }];
1881
+ },
1882
+ renderHTML({ HTMLAttributes }) {
1883
+ return ["span", mergeAttributes2(this.options.HTMLAttributes, HTMLAttributes), 0];
1884
+ },
1885
+ addCommands() {
1886
+ const store = () => this.storage.store;
1887
+ return {
1888
+ addComment: (options = {}) => ({ state, tr, dispatch }) => {
1889
+ const { from, to, empty } = state.selection;
1890
+ if (empty) return false;
1891
+ if (dispatch) {
1892
+ const id = options.id ?? createReviewId("comment");
1893
+ tr.addMark(from, to, this.type.create({ commentId: id }));
1894
+ tr.setMeta(commentsPluginKey, "refresh");
1895
+ store().createThread({
1896
+ id,
1897
+ body: options.body ?? "",
1898
+ author: options.author ?? resolveAuthor(this.options.author),
1899
+ anchor: { from, to, quote: state.doc.textBetween(from, to, " ", " ") }
1900
+ });
1901
+ store().setActiveThread(id);
1902
+ }
1903
+ return true;
1904
+ },
1905
+ resolveComment: (id, resolved = true) => ({ tr, dispatch }) => {
1906
+ if (!store().getThread(id)) return false;
1907
+ if (dispatch) {
1908
+ store().resolveThread(id, resolved, resolveAuthor(this.options.author));
1909
+ tr.setMeta(commentsPluginKey, "refresh");
1910
+ }
1911
+ return true;
1912
+ },
1913
+ deleteComment: (id) => ({ state, tr, dispatch }) => {
1914
+ const ranges = findCommentRanges(state.doc).get(id) ?? [];
1915
+ if (ranges.length === 0 && !store().getThread(id)) return false;
1916
+ if (dispatch) {
1917
+ ranges.forEach(({ from, to }) => {
1918
+ state.doc.nodesBetween(from, to, (node, pos) => {
1919
+ const mark = node.marks.find(
1920
+ (m) => m.type === this.type && m.attrs.commentId === id
1921
+ );
1922
+ if (mark) {
1923
+ tr.removeMark(Math.max(pos, from), Math.min(pos + node.nodeSize, to), mark);
1924
+ }
1925
+ });
1926
+ });
1927
+ tr.setMeta(commentsPluginKey, "refresh");
1928
+ store().deleteThread(id);
1929
+ }
1930
+ return true;
1931
+ },
1932
+ setActiveComment: (id) => ({ tr, dispatch }) => {
1933
+ if (id !== null && !store().getThread(id)) return false;
1934
+ if (dispatch) {
1935
+ store().setActiveThread(id);
1936
+ tr.setMeta(commentsPluginKey, "refresh");
1937
+ }
1938
+ return true;
1939
+ },
1940
+ focusComment: (id) => ({ state, tr, dispatch }) => {
1941
+ const ranges = findCommentRanges(state.doc).get(id);
1942
+ if (!ranges || ranges.length === 0) return false;
1943
+ if (dispatch) {
1944
+ const from = ranges[0].from;
1945
+ const to = ranges[ranges.length - 1].to;
1946
+ tr.setSelection(TextSelection.create(tr.doc, from, to)).scrollIntoView();
1947
+ store().setActiveThread(id);
1948
+ tr.setMeta(commentsPluginKey, "refresh");
1949
+ }
1950
+ return true;
1951
+ }
1952
+ };
1953
+ },
1954
+ addProseMirrorPlugins() {
1955
+ const store = this.storage.store;
1956
+ const options = this.options;
1957
+ let cache = null;
1958
+ const buildDecorations = (doc) => {
1959
+ const snapshot = store.getSnapshot();
1960
+ if (cache && cache.doc === doc && cache.snapshot === snapshot) return cache.set;
1961
+ const decorations = [];
1962
+ findCommentRanges(doc).forEach((ranges, id) => {
1963
+ const thread = store.getThread(id);
1964
+ const resolved = thread?.resolved ?? false;
1965
+ const active = snapshot.activeThreadId === id;
1966
+ const cls = [
1967
+ resolved ? options.resolvedClass : options.commentClass,
1968
+ active ? options.activeClass : ""
1969
+ ].filter(Boolean).join(" ");
1970
+ if (!cls) return;
1971
+ ranges.forEach(({ from, to }) => {
1972
+ decorations.push(Decoration3.inline(from, to, { class: cls }));
1973
+ });
1974
+ });
1975
+ const set = DecorationSet3.create(doc, decorations);
1976
+ cache = { doc, snapshot, set };
1977
+ return set;
1978
+ };
1979
+ return [
1980
+ new Plugin6({
1981
+ key: commentsPluginKey,
1982
+ props: {
1983
+ decorations: (state) => buildDecorations(state.doc)
1984
+ },
1985
+ view: (view) => {
1986
+ let destroyed = false;
1987
+ let scheduled = false;
1988
+ const sync = () => store.syncAnchors(getCommentAnchors(view.state.doc));
1989
+ sync();
1990
+ const unsubscribe = store.subscribe(() => {
1991
+ if (scheduled) return;
1992
+ scheduled = true;
1993
+ queueMicrotask(() => {
1994
+ scheduled = false;
1995
+ if (destroyed) return;
1996
+ sync();
1997
+ if (cache?.snapshot !== store.getSnapshot()) {
1998
+ view.dispatch(
1999
+ view.state.tr.setMeta(commentsPluginKey, "refresh").setMeta("addToHistory", false)
2000
+ );
2001
+ }
2002
+ });
2003
+ });
2004
+ return {
2005
+ update(view2, prevState) {
2006
+ if (!prevState.doc.eq(view2.state.doc)) sync();
2007
+ if (options.activateOnSelection && !prevState.selection.eq(view2.state.selection)) {
2008
+ const ids = getCommentIdsAt(view2.state);
2009
+ const active = store.getSnapshot().activeThreadId;
2010
+ if (!active || !ids.includes(active)) {
2011
+ store.setActiveThread(ids[0] ?? null);
2012
+ }
2013
+ }
2014
+ },
2015
+ destroy() {
2016
+ destroyed = true;
2017
+ unsubscribe();
2018
+ }
2019
+ };
2020
+ }
2021
+ })
2022
+ ];
2023
+ }
2024
+ });
2025
+ function getCommentAuthor(editor) {
2026
+ const ext = editor?.extensionManager?.extensions.find((e) => e.name === COMMENT_MARK_NAME);
2027
+ return ext ? resolveAuthor(ext.options.author) : null;
2028
+ }
2029
+
2030
+ // src/extensions/file-import.ts
2031
+ import { Extension as Extension7 } from "@tiptap/core";
2032
+ import { Plugin as Plugin7, PluginKey as PluginKey4 } from "@tiptap/pm/state";
2033
+
2034
+ // src/import/registry.ts
2035
+ var ImportError = class extends Error {
2036
+ code;
2037
+ fileName;
2038
+ constructor(code, message, details = {}) {
2039
+ super(message, details.cause !== void 0 ? { cause: details.cause } : void 0);
2040
+ this.name = "ImportError";
2041
+ this.code = code;
2042
+ this.fileName = details.fileName;
2043
+ }
2044
+ };
2045
+ var normalizeExtension = (ext) => {
2046
+ const lower = ext.trim().toLowerCase();
2047
+ return lower.startsWith(".") ? lower : `.${lower}`;
2048
+ };
2049
+ var normalizeMimeType = (mime) => (mime ?? "").split(";")[0].trim().toLowerCase();
2050
+ function isFileLike(input) {
2051
+ return typeof input === "object" && input !== null && !(input instanceof ArrayBuffer) && !ArrayBuffer.isView(input);
2052
+ }
2053
+ function stripBom(text) {
2054
+ return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
2055
+ }
2056
+ function createImportContext(input, details) {
2057
+ const readArrayBuffer = async () => {
2058
+ if (typeof input === "string") {
2059
+ return new TextEncoder().encode(input).buffer;
2060
+ }
2061
+ if (input instanceof ArrayBuffer) return input;
2062
+ if (ArrayBuffer.isView(input)) {
2063
+ return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
2064
+ }
2065
+ if (typeof input.arrayBuffer === "function") return input.arrayBuffer();
2066
+ if (typeof input.text === "function") {
2067
+ return new TextEncoder().encode(await input.text()).buffer;
2068
+ }
2069
+ throw new TypeError("Import input cannot be read as binary data.");
2070
+ };
2071
+ const readText = async () => {
2072
+ if (typeof input === "string") return stripBom(input);
2073
+ if (isFileLike(input) && typeof input.text === "function") {
2074
+ return stripBom(await input.text());
2075
+ }
2076
+ return stripBom(new TextDecoder("utf-8").decode(await readArrayBuffer()));
2077
+ };
2078
+ return { ...details, readText, readArrayBuffer };
2079
+ }
2080
+ var ImportAdapterRegistry = class {
2081
+ adapters = [];
2082
+ /** Registers an adapter and returns a function that unregisters it. */
2083
+ register(adapter, options = {}) {
2084
+ const existing = this.adapters.findIndex((a) => a.id === adapter.id);
2085
+ if (existing !== -1) {
2086
+ if (!options.replace) {
2087
+ throw new Error(`Import adapter "${adapter.id}" is already registered.`);
2088
+ }
2089
+ this.adapters.splice(existing, 1);
2090
+ }
2091
+ this.adapters.push(adapter);
2092
+ return () => {
2093
+ this.unregister(adapter);
2094
+ };
2095
+ }
2096
+ /** Removes an adapter by id or instance. Returns whether it was registered. */
2097
+ unregister(adapterOrId) {
2098
+ const index = this.adapters.findIndex(
2099
+ (a) => typeof adapterOrId === "string" ? a.id === adapterOrId : a === adapterOrId
2100
+ );
2101
+ if (index === -1) return false;
2102
+ this.adapters.splice(index, 1);
2103
+ return true;
2104
+ }
2105
+ get(id) {
2106
+ return this.adapters.find((a) => a.id === id);
2107
+ }
2108
+ /** Registered adapters in registration order, optionally for one target. */
2109
+ list(target) {
2110
+ return this.adapters.filter((a) => target === void 0 || a.target === target);
2111
+ }
2112
+ /**
2113
+ * Finds the best adapter for a file. Extensions win over MIME types (browsers
2114
+ * often report `""` or `text/plain` for markdown), the longest matching
2115
+ * extension wins (`.md.txt` beats `.txt`), exact MIME types beat `type/*`
2116
+ * wildcards, then higher `priority`, then the most recently registered.
2117
+ */
2118
+ find(query) {
2119
+ const fileName = query.fileName?.toLowerCase() ?? "";
2120
+ const mime = normalizeMimeType(query.mimeType);
2121
+ let best;
2122
+ let bestScore = 0;
2123
+ let bestPriority = -Infinity;
2124
+ this.adapters.forEach((adapter) => {
2125
+ if (query.target !== void 0 && adapter.target !== query.target) return;
2126
+ let score = 0;
2127
+ if (fileName) {
2128
+ for (const raw of adapter.extensions) {
2129
+ const ext = normalizeExtension(raw);
2130
+ if (fileName.endsWith(ext) && fileName.length > ext.length) {
2131
+ score = Math.max(score, 1e3 + ext.length);
2132
+ }
2133
+ }
2134
+ }
2135
+ if (score === 0 && mime) {
2136
+ for (const raw of adapter.mimeTypes) {
2137
+ const accepted = normalizeMimeType(raw);
2138
+ if (accepted === mime) score = Math.max(score, 2);
2139
+ else if (accepted.endsWith("/*") && mime.startsWith(accepted.slice(0, -1))) {
2140
+ score = Math.max(score, 1);
2141
+ }
2142
+ }
2143
+ }
2144
+ if (score === 0) return;
2145
+ const priority = adapter.priority ?? 0;
2146
+ if (score > bestScore || score === bestScore && priority >= bestPriority) {
2147
+ best = adapter;
2148
+ bestScore = score;
2149
+ bestPriority = priority;
2150
+ }
2151
+ });
2152
+ return best;
2153
+ }
2154
+ /**
2155
+ * Returns a value for `<input type="file" accept="…">` covering every
2156
+ * registered extension and MIME type (optionally for one target).
2157
+ */
2158
+ accept(target) {
2159
+ const values = /* @__PURE__ */ new Set();
2160
+ for (const adapter of this.list(target)) {
2161
+ adapter.extensions.forEach((ext) => values.add(normalizeExtension(ext)));
2162
+ adapter.mimeTypes.forEach((mime) => values.add(normalizeMimeType(mime)));
2163
+ }
2164
+ return Array.from(values).join(",");
2165
+ }
2166
+ /** Whether some adapter (optionally for `target`) accepts this file. */
2167
+ canImport(file, target) {
2168
+ return !!this.find({ fileName: file.name, mimeType: file.type, target });
2169
+ }
2170
+ /**
2171
+ * Imports a file (or string / binary input) with the matching adapter.
2172
+ * Rejects with an {@link ImportError} when no adapter matches or the
2173
+ * adapter fails.
2174
+ */
2175
+ async importFile(input, options = {}) {
2176
+ const fileName = options.fileName ?? (isFileLike(input) ? input.name : void 0);
2177
+ const mimeType = options.mimeType ?? (isFileLike(input) ? input.type : void 0);
2178
+ let adapter;
2179
+ if (options.adapterId) {
2180
+ adapter = this.get(options.adapterId);
2181
+ if (!adapter) {
2182
+ throw new ImportError("unknown_adapter", `No import adapter with id "${options.adapterId}".`, { fileName });
2183
+ }
2184
+ } else {
2185
+ adapter = this.find({ fileName, mimeType, target: options.target });
2186
+ }
2187
+ if (!adapter) {
2188
+ const what = fileName || mimeType || "input";
2189
+ throw new ImportError(
2190
+ "unsupported_type",
2191
+ `No import adapter accepts ${what}${options.target ? ` for target "${options.target}"` : ""}.`,
2192
+ { fileName }
2193
+ );
2194
+ }
2195
+ const context = createImportContext(input, { fileName, mimeType, target: adapter.target });
2196
+ try {
2197
+ const result = await adapter.import(input, context);
2198
+ return { adapterId: adapter.id, target: adapter.target, fileName, mimeType, result };
2199
+ } catch (error) {
2200
+ throw new ImportError(
2201
+ "adapter_failed",
2202
+ `Import adapter "${adapter.id}" failed: ${error instanceof Error ? error.message : String(error)}`,
2203
+ { fileName, cause: error }
2204
+ );
2205
+ }
2206
+ }
2207
+ };
2208
+
2209
+ // src/import/adapters.ts
2210
+ var markdownImportAdapter = {
2211
+ id: "markdown",
2212
+ label: "Markdown",
2213
+ mimeTypes: ["text/markdown", "text/x-markdown"],
2214
+ extensions: [".md", ".mdown", ".markdown", ".mkdn", ".mkd", ".rmd", ".md.txt"],
2215
+ target: "document",
2216
+ async import(_input, context) {
2217
+ const text = await context.readText();
2218
+ return { html: markdownToTipTapHtml(text), text };
2219
+ }
2220
+ };
2221
+ var htmlImportAdapter = {
2222
+ id: "html",
2223
+ label: "HTML",
2224
+ mimeTypes: ["text/html", "application/xhtml+xml"],
2225
+ extensions: [".html", ".htm", ".xhtml"],
2226
+ target: "document",
2227
+ async import(_input, context) {
2228
+ const html = sanitizeImportedHtml(await context.readText());
2229
+ return { html, text: htmlToPlainText(html) };
2230
+ }
2231
+ };
2232
+ var plainTextImportAdapter = {
2233
+ id: "text",
2234
+ label: "Plain text",
2235
+ mimeTypes: ["text/plain"],
2236
+ extensions: [".txt", ".text"],
2237
+ target: "document",
2238
+ async import(_input, context) {
2239
+ const text = (await context.readText()).replace(/\r\n?/g, "\n");
2240
+ return { html: plainTextToParagraphHtml(text), text };
2241
+ }
2242
+ };
2243
+ var rtfImportAdapter = {
2244
+ id: "rtf",
2245
+ label: "Rich Text Format",
2246
+ mimeTypes: ["application/rtf", "text/rtf"],
2247
+ extensions: [".rtf"],
2248
+ target: "document",
2249
+ async import(_input, context) {
2250
+ const text = rtfToPlainText(await context.readText());
2251
+ return { html: plainTextToParagraphHtml(text), text };
2252
+ }
2253
+ };
2254
+ var BUILT_IN_IMPORT_ADAPTERS = [
2255
+ markdownImportAdapter,
2256
+ htmlImportAdapter,
2257
+ plainTextImportAdapter,
2258
+ rtfImportAdapter
2259
+ ];
2260
+ function createImportAdapterRegistry(options = {}) {
2261
+ const registry = new ImportAdapterRegistry();
2262
+ if (options.builtIns !== false) {
2263
+ BUILT_IN_IMPORT_ADAPTERS.forEach((adapter) => registry.register(adapter));
2264
+ }
2265
+ return registry;
2266
+ }
2267
+ var defaultImportRegistry = createImportAdapterRegistry();
2268
+ function registerImportAdapter(adapter, options) {
2269
+ return defaultImportRegistry.register(adapter, options);
2270
+ }
2271
+ function unregisterImportAdapter(adapterOrId) {
2272
+ return defaultImportRegistry.unregister(adapterOrId);
2273
+ }
2274
+ function findImportAdapter(query) {
2275
+ return defaultImportRegistry.find(query);
2276
+ }
2277
+ function importFile(input, options) {
2278
+ return defaultImportRegistry.importFile(input, options);
2279
+ }
2280
+ function getImportAccept(target) {
2281
+ return defaultImportRegistry.accept(target);
2282
+ }
2283
+
2284
+ // src/import/editor.ts
2285
+ import { DOMParser as PMDOMParser, Slice } from "@tiptap/pm/model";
2286
+ import { Selection } from "@tiptap/pm/state";
2287
+ var toView = (target) => "state" in target && "dispatch" in target ? target : target.view;
2288
+ function htmlToSlice(html, schema) {
2289
+ const Parser = globalThis.DOMParser ?? globalThis.window?.DOMParser;
2290
+ if (!Parser) return null;
2291
+ const body = new Parser().parseFromString(`<body>${html}</body>`, "text/html").body;
2292
+ return new Slice(PMDOMParser.fromSchema(schema).parse(body).content, 0, 0);
2293
+ }
2294
+ function insertImportedDocument(target, content, options = {}) {
2295
+ const view = toView(target);
2296
+ const { state } = view;
2297
+ const htmlSlice = options.asPlainText ? null : htmlToSlice(content.html, state.schema);
2298
+ const size = state.doc.content.size;
2299
+ let from = options.at !== void 0 ? Math.max(0, Math.min(options.at, size)) : state.selection.from;
2300
+ let to = options.at !== void 0 ? from : state.selection.to;
2301
+ if (!htmlSlice && from === to && !state.doc.resolve(from).parent.inlineContent) {
2302
+ from = to = Selection.near(state.doc.resolve(from)).from;
2303
+ }
2304
+ const tr = htmlSlice ? state.tr.replaceRange(from, to, htmlSlice) : state.tr.replace(from, to, plainTextToSlice(content.text, state.schema));
2305
+ const end = tr.mapping.map(to, 1);
2306
+ view.dispatch(tr.scrollIntoView().setMeta("uiEvent", "drop"));
2307
+ return end;
2308
+ }
2309
+ async function importFilesIntoEditor(target, files, options = {}) {
2310
+ const registry = options.registry ?? defaultImportRegistry;
2311
+ const outcomes = [];
2312
+ let at = options.at;
2313
+ for (const file of Array.from(files)) {
2314
+ try {
2315
+ const outcome = await registry.importFile(file, { target: "document" });
2316
+ const view = toView(target);
2317
+ if (view.isDestroyed) break;
2318
+ at = insertImportedDocument(view, outcome.result, { at, asPlainText: options.asPlainText });
2319
+ outcomes.push(outcome);
2320
+ options.onImport?.(outcome, file);
2321
+ } catch (error) {
2322
+ const err = error instanceof Error ? error : new Error(String(error));
2323
+ if (options.onError) options.onError(err, file);
2324
+ else console.error("[Editor] File import failed:", err);
2325
+ }
2326
+ }
2327
+ return outcomes;
2328
+ }
2329
+
2330
+ // src/extensions/file-import.ts
2331
+ var fileImportPluginKey = new PluginKey4("fileImport");
2332
+ function getImportableFiles(files, registry) {
2333
+ const list = Array.from(files ?? []);
2334
+ if (list.length === 0) return null;
2335
+ return list.every((file) => registry.canImport(file, "document")) ? list : null;
2336
+ }
2337
+ function dropPosition(view, event) {
2338
+ try {
2339
+ return view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos;
2340
+ } catch {
2341
+ return void 0;
2342
+ }
2343
+ }
2344
+ var FileImport = Extension7.create({
2345
+ name: "fileImport",
2346
+ addOptions() {
2347
+ return {
2348
+ registry: void 0,
2349
+ asPlainText: false,
2350
+ enableDrop: true,
2351
+ enablePaste: true,
2352
+ onImport: void 0,
2353
+ onImportError: void 0
2354
+ };
2355
+ },
2356
+ addProseMirrorPlugins() {
2357
+ const options = this.options;
2358
+ const registry = () => options.registry ?? defaultImportRegistry;
2359
+ const run = (view, files, at) => {
2360
+ void importFilesIntoEditor(view, files, {
2361
+ registry: registry(),
2362
+ at,
2363
+ asPlainText: options.asPlainText,
2364
+ onImport: options.onImport,
2365
+ onError: options.onImportError
2366
+ });
2367
+ };
2368
+ return [
2369
+ new Plugin7({
2370
+ key: fileImportPluginKey,
2371
+ props: {
2372
+ handleDrop(view, event, _slice, moved) {
2373
+ if (!options.enableDrop || moved) return false;
2374
+ const files = getImportableFiles(event.dataTransfer?.files, registry());
2375
+ if (!files) return false;
2376
+ event.preventDefault();
2377
+ run(view, files, dropPosition(view, event));
2378
+ return true;
2379
+ },
2380
+ handlePaste(view, event) {
2381
+ if (!options.enablePaste) return false;
2382
+ const files = getImportableFiles(event.clipboardData?.files, registry());
2383
+ if (!files) return false;
2384
+ event.preventDefault();
2385
+ run(view, files);
2386
+ return true;
2387
+ }
2388
+ }
2389
+ })
2390
+ ];
2391
+ }
2392
+ });
2393
+
2394
+ // src/extensions/rich-clipboard.ts
2395
+ import { Extension as Extension8 } from "@tiptap/core";
2396
+ import { Plugin as Plugin8, PluginKey as PluginKey5 } from "@tiptap/pm/state";
2397
+ var richClipboardPluginKey = new PluginKey5("richClipboard");
2398
+ function writeRichClipboard(view, event, options = {}) {
2399
+ const { state } = view;
2400
+ if (state.selection.empty) return false;
2401
+ const data = getClipboardData(event);
2402
+ if (!data || typeof data.setData !== "function") return false;
2403
+ const slice = state.selection.content();
2404
+ const { dom, text } = view.serializeForClipboard(slice);
2405
+ event.preventDefault();
2406
+ data.clearData?.();
2407
+ data.setData("text/html", dom.innerHTML);
2408
+ data.setData("text/plain", text);
2409
+ data.setData(options.mimeType ?? PIXERATE_DOCUMENT_MIME, serializeRichClipboardPayload(slice, state.schema));
2410
+ if (options.cut && view.editable) {
2411
+ view.dispatch(state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent", "cut"));
2412
+ }
2413
+ return true;
2414
+ }
2415
+ var RichClipboard = Extension8.create({
2416
+ name: "richClipboard",
2417
+ addOptions() {
2418
+ return {
2419
+ mimeType: PIXERATE_DOCUMENT_MIME,
2420
+ plainTextMode: false
2421
+ };
2422
+ },
2423
+ addStorage() {
2424
+ return { plainTextMode: !!this.options.plainTextMode };
2425
+ },
2426
+ addProseMirrorPlugins() {
2427
+ const mimeType = this.options.mimeType ?? PIXERATE_DOCUMENT_MIME;
2428
+ const storage = this.storage;
2429
+ return [
2430
+ new Plugin8({
2431
+ key: richClipboardPluginKey,
2432
+ props: {
2433
+ handleDOMEvents: {
2434
+ copy: (view, event) => writeRichClipboard(view, event, { mimeType }),
2435
+ cut: (view, event) => writeRichClipboard(view, event, { cut: true, mimeType })
2436
+ },
2437
+ handlePaste(view, event) {
2438
+ if (storage.plainTextMode) return false;
2439
+ const slice = readRichClipboard(event, view.state.schema, mimeType);
2440
+ if (!slice) return false;
2441
+ event.preventDefault();
2442
+ view.dispatch(
2443
+ view.state.tr.replaceSelection(slice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")
2444
+ );
2445
+ return true;
2446
+ }
2447
+ }
2448
+ })
2449
+ ];
2450
+ }
2451
+ });
2452
+
2453
+ // src/extensions/autocorrect.ts
2454
+ import { Extension as Extension9 } from "@tiptap/core";
2455
+ import { closeHistory } from "@tiptap/pm/history";
2456
+ import { Plugin as Plugin9, PluginKey as PluginKey6 } from "@tiptap/pm/state";
2457
+ var quoteOpener = /[\s ([{<—–‘“-]/;
2458
+ var smartQuoteRules = [
2459
+ {
2460
+ id: "smart-quotes:double",
2461
+ find: /(^|.)"$/su,
2462
+ replace: (m) => m[1] + (!m[1] || quoteOpener.test(m[1]) ? "\u201C" : "\u201D")
2463
+ },
2464
+ {
2465
+ id: "smart-quotes:single",
2466
+ find: /(^|.)'$/su,
2467
+ replace: (m) => m[1] + (!m[1] || quoteOpener.test(m[1]) ? "\u2018" : "\u2019")
2468
+ }
2469
+ ];
2470
+ var symbolRules = [
2471
+ { id: "symbols:em-dash", find: "--", replace: "\u2014" },
2472
+ { id: "symbols:ellipsis", find: "...", replace: "\u2026" },
2473
+ { id: "symbols:right-arrow", find: "->", replace: "\u2192" },
2474
+ { id: "symbols:left-arrow", find: "<-", replace: "\u2190" },
2475
+ { id: "symbols:copyright", find: /\([cC]\)$/, replace: "\xA9" },
2476
+ { id: "symbols:registered", find: /\([rR]\)$/, replace: "\xAE" },
2477
+ { id: "symbols:trademark", find: /\((?:tm|TM)\)$/, replace: "\u2122" }
2478
+ ];
2479
+ var FRACTIONS = { "1/2": "\xBD", "1/4": "\xBC", "3/4": "\xBE" };
2480
+ var fractionRules = [
2481
+ {
2482
+ id: "fractions",
2483
+ find: /(^|[^\d/.,])([13]\/[24])([\s.,;:!?)])$/u,
2484
+ replace: (m) => FRACTIONS[m[2]] ? m[1] + FRACTIONS[m[2]] + m[3] : null
2485
+ }
2486
+ ];
2487
+ var capitalizeSentenceRules = [
2488
+ {
2489
+ id: "capitalize-sentence",
2490
+ find: /(?:^|(\S+)(\s+))(\p{Ll}[^\s]*)(\s)$/u,
2491
+ replace: (m, ctx) => {
2492
+ const [, previous, space = "", word, trailing] = m;
2493
+ if (/[./@:\\]/.test(word.slice(0, -1))) return null;
2494
+ if (previous !== void 0) {
2495
+ if (!/[.!?]["'”’)]*$/.test(previous)) return null;
2496
+ const token = previous.replace(/["'”’)]+$/, "");
2497
+ if (ctx.isException(token)) return null;
2498
+ if (/^\p{L}\.$/u.test(token)) return null;
2499
+ if (/\.\./.test(token) || token.endsWith("\u2026")) return null;
2500
+ }
2501
+ return (previous ?? "") + space + word.charAt(0).toUpperCase() + word.slice(1) + trailing;
2502
+ }
2503
+ }
2504
+ ];
2505
+ var twoInitialCapitalsRules = [
2506
+ {
2507
+ id: "two-initial-capitals",
2508
+ find: /(^|[^\p{L}\p{N}'’])(\p{Lu})(\p{Lu})(\p{Ll}{1,})([\s.,;:!?])$/u,
2509
+ replace: (m, ctx) => {
2510
+ const word = m[2] + m[3] + m[4];
2511
+ if (ctx.isException(word)) return null;
2512
+ return m[1] + m[2] + m[3].toLowerCase() + m[4] + m[5];
2513
+ }
2514
+ }
2515
+ ];
2516
+ var defaultAutocorrectRules = [
2517
+ ...smartQuoteRules,
2518
+ ...symbolRules,
2519
+ ...fractionRules,
2520
+ ...capitalizeSentenceRules,
2521
+ ...twoInitialCapitalsRules
2522
+ ];
2523
+ var DEFAULT_AUTOCORRECT_EXCEPTIONS = [
2524
+ "a.m.",
2525
+ "abbr.",
2526
+ "approx.",
2527
+ "apt.",
2528
+ "assn.",
2529
+ "ave.",
2530
+ "ca.",
2531
+ "cf.",
2532
+ "co.",
2533
+ "corp.",
2534
+ "dept.",
2535
+ "dr.",
2536
+ "e.g.",
2537
+ "est.",
2538
+ "etc.",
2539
+ "fig.",
2540
+ "i.e.",
2541
+ "inc.",
2542
+ "jr.",
2543
+ "ltd.",
2544
+ "mr.",
2545
+ "mrs.",
2546
+ "ms.",
2547
+ "mt.",
2548
+ "no.",
2549
+ "p.",
2550
+ "p.m.",
2551
+ "pp.",
2552
+ "prof.",
2553
+ "sr.",
2554
+ "st.",
2555
+ "vol.",
2556
+ "vs.",
2557
+ "CDs",
2558
+ "IDs",
2559
+ "PCs",
2560
+ "GHz",
2561
+ "MHz",
2562
+ "KHz"
2563
+ ];
2564
+ var autocorrectPluginKey = new PluginKey6("autocorrect");
2565
+ var WHOLE_WORD_DELIMITER = /[\s.,;:!?]/;
2566
+ function findAutocorrection(textBefore, rules, context) {
2567
+ const ctx = { ...context, textBefore };
2568
+ for (const rule of rules) {
2569
+ let match = null;
2570
+ if (typeof rule.find === "string") {
2571
+ const find = rule.find;
2572
+ if (rule.wholeWord) {
2573
+ const delimiter = textBefore.slice(-1);
2574
+ if (!WHOLE_WORD_DELIMITER.test(delimiter)) continue;
2575
+ const body = textBefore.slice(0, -1);
2576
+ if (!body.endsWith(find)) continue;
2577
+ const before = body.charAt(body.length - find.length - 1);
2578
+ if (before && /[\p{L}\p{N}_]/u.test(before)) continue;
2579
+ match = Object.assign([find + delimiter], {
2580
+ index: textBefore.length - find.length - 1,
2581
+ input: textBefore
2582
+ });
2583
+ } else {
2584
+ if (!textBefore.endsWith(find)) continue;
2585
+ match = Object.assign([find], {
2586
+ index: textBefore.length - find.length,
2587
+ input: textBefore
2588
+ });
2589
+ }
2590
+ } else {
2591
+ const regex = new RegExp(rule.find.source, rule.find.flags.replace("g", ""));
2592
+ match = textBefore.match(regex);
2593
+ if (!match || match.index + match[0].length !== textBefore.length) continue;
2594
+ }
2595
+ const original = match[0];
2596
+ if (ctx.isException(original.trim())) continue;
2597
+ let replacement;
2598
+ if (typeof rule.replace === "function") {
2599
+ replacement = rule.replace(match, ctx);
2600
+ } else {
2601
+ replacement = rule.wholeWord && typeof rule.find === "string" ? rule.replace + original.slice(-1) : rule.replace;
2602
+ }
2603
+ if (replacement == null || replacement === original) continue;
2604
+ return { ruleId: rule.id, start: match.index, original, replacement };
2605
+ }
2606
+ return null;
2607
+ }
2608
+ function isInsidePromptToken(textBefore) {
2609
+ if (textBefore.lastIndexOf("{") > textBefore.lastIndexOf("}")) return true;
2610
+ const underscores = textBefore.match(/__/g);
2611
+ return !!underscores && underscores.length % 2 === 1;
2612
+ }
2613
+ var Autocorrect = Extension9.create({
2614
+ name: "autocorrect",
2615
+ addOptions() {
2616
+ return {
2617
+ rules: defaultAutocorrectRules,
2618
+ exceptions: DEFAULT_AUTOCORRECT_EXCEPTIONS,
2619
+ ignoreNodes: ["codeBlock"],
2620
+ ignoreMarks: ["code"],
2621
+ ignorePromptTokens: true,
2622
+ onCorrection: void 0
2623
+ };
2624
+ },
2625
+ addStorage() {
2626
+ return {
2627
+ enabled: true,
2628
+ exceptions: new Set(this.options.exceptions.map((e) => e.toLowerCase()))
2629
+ };
2630
+ },
2631
+ addCommands() {
2632
+ return {
2633
+ undoAutocorrect: () => ({ state, tr, dispatch }) => {
2634
+ const last = autocorrectPluginKey.getState(state)?.last;
2635
+ if (!last) return false;
2636
+ if (state.doc.textBetween(last.from, last.to) !== last.replacement) return false;
2637
+ if (dispatch) {
2638
+ tr.insertText(last.original, last.from, last.to);
2639
+ tr.setMeta(autocorrectPluginKey, { undo: true });
2640
+ }
2641
+ return true;
2642
+ },
2643
+ setAutocorrectEnabled: (enabled) => () => {
2644
+ this.storage.enabled = enabled;
2645
+ return true;
2646
+ },
2647
+ addAutocorrectException: (text) => () => {
2648
+ this.storage.exceptions.add(text.toLowerCase());
2649
+ return true;
2650
+ },
2651
+ removeAutocorrectException: (text) => () => this.storage.exceptions.delete(text.toLowerCase())
2652
+ };
2653
+ },
2654
+ addProseMirrorPlugins() {
2655
+ const extension = this;
2656
+ const isIgnored = (state, from, to) => {
2657
+ const { ignoreNodes, ignoreMarks } = extension.options;
2658
+ const $from = state.doc.resolve(from);
2659
+ for (let d = $from.depth; d > 0; d--) {
2660
+ const node = $from.node(d);
2661
+ if (node.type.spec.code || ignoreNodes.includes(node.type.name)) return true;
2662
+ }
2663
+ const isIgnoredMark = (mark) => mark.type.spec.code || ignoreMarks.includes(mark.type.name);
2664
+ const marks = state.storedMarks ?? $from.marks();
2665
+ if (marks.some(isIgnoredMark)) return true;
2666
+ let ignored = false;
2667
+ if (to > from) {
2668
+ state.doc.nodesBetween(from, to, (node) => {
2669
+ if (node.marks.some(isIgnoredMark)) ignored = true;
2670
+ });
2671
+ }
2672
+ return ignored;
2673
+ };
2674
+ const handleTextInput = (view, from, to, text) => {
2675
+ if (!extension.storage.enabled || view.composing) return false;
2676
+ const { state } = view;
2677
+ const pluginState = autocorrectPluginKey.getState(state);
2678
+ const $from = state.doc.resolve(from);
2679
+ if (!$from.parent.isTextblock || !$from.sameParent(state.doc.resolve(to))) {
2680
+ return false;
2681
+ }
2682
+ const insertTyped = () => {
2683
+ const tr2 = state.tr.insertText(text, from, to);
2684
+ if (pluginState?.closePending) closeHistory(tr2);
2685
+ view.dispatch(tr2);
2686
+ };
2687
+ if (isIgnored(state, from, to)) return false;
2688
+ const textBefore = $from.parent.textBetween(0, $from.parentOffset, void 0, "\uFFFC") + text;
2689
+ if (extension.options.ignorePromptTokens && isInsidePromptToken(textBefore)) {
2690
+ return false;
2691
+ }
2692
+ const currentWord = /\S*$/.exec(textBefore)?.[0] ?? "";
2693
+ if (/:\/\/|^www\./i.test(currentWord)) return false;
2694
+ const exceptions = extension.storage.exceptions;
2695
+ const found = findAutocorrection(textBefore, extension.options.rules, {
2696
+ state,
2697
+ isException: (value) => exceptions.has(value.toLowerCase())
2698
+ });
2699
+ if (!found) {
2700
+ if (!pluginState?.closePending) return false;
2701
+ insertTyped();
2702
+ return true;
2703
+ }
2704
+ let { original, replacement } = found;
2705
+ let start2 = found.start;
2706
+ let prefix = 0;
2707
+ while (prefix < original.length && prefix < replacement.length && original[prefix] === replacement[prefix]) {
2708
+ prefix++;
2709
+ }
2710
+ let suffix = 0;
2711
+ while (suffix < original.length - prefix && suffix < replacement.length - prefix && original[original.length - 1 - suffix] === replacement[replacement.length - 1 - suffix]) {
2712
+ suffix++;
2713
+ }
2714
+ start2 += prefix;
2715
+ original = original.slice(prefix, original.length - suffix);
2716
+ replacement = replacement.slice(prefix, replacement.length - suffix);
2717
+ const textEnd = textBefore.length - suffix;
2718
+ insertTyped();
2719
+ const next = view.state;
2720
+ const blockStart = next.doc.resolve(from + text.length).start();
2721
+ const correctionFrom = blockStart + start2;
2722
+ const correctionTo = blockStart + textEnd;
2723
+ if (next.doc.textBetween(correctionFrom, correctionTo, void 0, "\uFFFC") !== original || isIgnored(next, correctionFrom, correctionTo)) {
2724
+ return true;
2725
+ }
2726
+ const correction = {
2727
+ ruleId: found.ruleId,
2728
+ from: correctionFrom,
2729
+ to: correctionFrom + replacement.length,
2730
+ original,
2731
+ replacement
2732
+ };
2733
+ const tr = next.tr.insertText(replacement, correctionFrom, correctionTo);
2734
+ closeHistory(tr);
2735
+ tr.setMeta(autocorrectPluginKey, { correction });
2736
+ view.dispatch(tr);
2737
+ extension.options.onCorrection?.(correction);
2738
+ return true;
2739
+ };
2740
+ return [
2741
+ new Plugin9({
2742
+ key: autocorrectPluginKey,
2743
+ state: {
2744
+ init: () => ({ last: null, closePending: false }),
2745
+ apply: (tr, prev) => {
2746
+ const meta = tr.getMeta(autocorrectPluginKey);
2747
+ if (meta?.correction) return { last: meta.correction, closePending: true };
2748
+ if (meta?.undo) return { last: null, closePending: true };
2749
+ if (!tr.docChanged) return prev;
2750
+ if (!prev.last && !prev.closePending) return prev;
2751
+ return { last: null, closePending: false };
2752
+ }
2753
+ },
2754
+ props: { handleTextInput }
2755
+ })
2756
+ ];
2757
+ }
2758
+ });
2759
+
2760
+ // src/extensions/document-structure/outline.ts
2761
+ import { Extension as Extension10 } from "@tiptap/core";
2762
+ import {
2763
+ Plugin as Plugin10,
2764
+ PluginKey as PluginKey7,
2765
+ TextSelection as TextSelection2
2766
+ } from "@tiptap/pm/state";
2767
+
2768
+ // src/extensions/document-structure/utils.ts
2769
+ import { createDocument } from "@tiptap/core";
2770
+ import { EditorState } from "@tiptap/pm/state";
2771
+ import { ReplaceAroundStep, ReplaceStep } from "@tiptap/pm/transform";
2772
+ var idCounter = 0;
2773
+ function generateStructureId(prefix) {
2774
+ idCounter = (idCounter + 1) % 1296;
2775
+ const random = Math.random().toString(36).slice(2, 8).padEnd(6, "0");
2776
+ return `${prefix}-${random}${idCounter.toString(36).padStart(2, "0")}`;
2777
+ }
2778
+ function transactionsInsert(transactions, typeNames) {
2779
+ for (const tr of transactions) {
2780
+ for (const step of tr.steps) {
2781
+ if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) {
2782
+ const mark = step.mark;
2783
+ if (mark && typeNames.includes(mark.type.name)) return true;
2784
+ continue;
2785
+ }
2786
+ let found = false;
2787
+ step.slice.content.descendants((node) => {
2788
+ if (found) return false;
2789
+ if (typeNames.includes(node.type.name) || node.marks.some((m) => typeNames.includes(m.type.name))) {
2790
+ found = true;
2791
+ }
2792
+ return !found;
2793
+ });
2794
+ if (found) return true;
2795
+ }
2796
+ }
2797
+ return false;
2798
+ }
2799
+ function docContains(doc, typeNames) {
2800
+ let found = false;
2801
+ doc.descendants((node) => {
2802
+ if (found) return false;
2803
+ if (typeNames.includes(node.type.name) || node.marks.some((m) => typeNames.includes(m.type.name))) {
2804
+ found = true;
2805
+ }
2806
+ return !found;
2807
+ });
2808
+ return found;
2809
+ }
2810
+ function presenceStateSpec(typeNames) {
2811
+ return {
2812
+ init: (_config, state) => docContains(state.doc, typeNames),
2813
+ apply: (tr, present, _oldState, newState) => {
2814
+ if (!tr.docChanged) return present;
2815
+ if (!present && !transactionsInsert([tr], typeNames)) return false;
2816
+ return docContains(newState.doc, typeNames);
2817
+ }
2818
+ };
2819
+ }
2820
+ function arraysEqual(a, b, eq) {
2821
+ if (a.length !== b.length) return false;
2822
+ for (let i = 0; i < a.length; i++) {
2823
+ if (!eq(a[i], b[i])) return false;
2824
+ }
2825
+ return true;
2826
+ }
2827
+ function scrollDomAtPos(view, pos, behavior = "smooth") {
2828
+ try {
2829
+ const dom = view?.nodeDOM?.(pos);
2830
+ if (dom && typeof dom.scrollIntoView === "function") {
2831
+ dom.scrollIntoView({ block: "start", behavior });
2832
+ }
2833
+ } catch {
2834
+ }
2835
+ }
2836
+ function syncInitialContent(editor, sync) {
2837
+ const { content } = editor.options;
2838
+ if (content == null || content === "") return;
2839
+ try {
2840
+ const doc = createDocument(content, editor.schema, editor.options.parseOptions, {
2841
+ errorOnInvalidContent: false
2842
+ });
2843
+ const tr = sync(EditorState.create({ doc }));
2844
+ if (tr?.docChanged) editor.options.content = tr.doc;
2845
+ } catch {
2846
+ }
2847
+ }
2848
+ var STRUCTURE_PRIORITY = 45;
2849
+
2850
+ // src/extensions/document-structure/outline.ts
2851
+ var outlinePluginKey = new PluginKey7("outline");
2852
+ var outlineIdPluginKey = new PluginKey7("outlineIds");
2853
+ var sameHeading = (a, b) => a.id === b.id && a.level === b.level && a.text === b.text && a.pos === b.pos && a.depth === b.depth;
2854
+ function computeOutline(doc, types = ["heading"]) {
2855
+ const headings = [];
2856
+ const stack = [];
2857
+ doc.descendants((node, pos) => {
2858
+ if (types.includes(node.type.name)) {
2859
+ const level = Number(node.attrs.level) || 1;
2860
+ while (stack.length && stack[stack.length - 1] >= level) stack.pop();
2861
+ headings.push({
2862
+ id: node.attrs.id ?? `outline-${headings.length}`,
2863
+ level,
2864
+ text: node.textContent,
2865
+ pos,
2866
+ index: headings.length,
2867
+ depth: stack.length
2868
+ });
2869
+ stack.push(level);
2870
+ return false;
2871
+ }
2872
+ return !node.isTextblock;
2873
+ });
2874
+ return headings;
2875
+ }
2876
+ function buildOutlineTree(headings) {
2877
+ const root = [];
2878
+ const stack = [];
2879
+ for (const heading of headings) {
2880
+ const item = { ...heading, children: [] };
2881
+ while (stack.length && stack[stack.length - 1].level >= item.level) {
2882
+ stack.pop();
2883
+ }
2884
+ if (stack.length) stack[stack.length - 1].children.push(item);
2885
+ else root.push(item);
2886
+ stack.push(item);
2887
+ }
2888
+ return root;
2889
+ }
2890
+ function getOutline(editorOrState) {
2891
+ const state = "state" in editorOrState ? editorOrState.state : editorOrState;
2892
+ return outlinePluginKey.getState(state) ?? computeOutline(state.doc);
2893
+ }
2894
+ function getActiveHeading(editorOrState, headings = getOutline(editorOrState)) {
2895
+ const state = "state" in editorOrState ? editorOrState.state : editorOrState;
2896
+ const head = state.selection.from;
2897
+ let lo = 0;
2898
+ let hi = headings.length - 1;
2899
+ let found = null;
2900
+ while (lo <= hi) {
2901
+ const mid = lo + hi >> 1;
2902
+ if (headings[mid].pos <= head) {
2903
+ found = headings[mid];
2904
+ lo = mid + 1;
2905
+ } else {
2906
+ hi = mid - 1;
2907
+ }
2908
+ }
2909
+ return found;
2910
+ }
2911
+ function findHeading(state, id) {
2912
+ return getOutline(state).find((h) => h.id === id) ?? null;
2913
+ }
2914
+ var EMPTY_SNAPSHOT = Object.freeze({
2915
+ headings: [],
2916
+ tree: [],
2917
+ activeId: null
2918
+ });
2919
+ function createOutlineStore(editor) {
2920
+ const listeners = /* @__PURE__ */ new Set();
2921
+ let snapshot = EMPTY_SNAPSHOT;
2922
+ const compute = () => {
2923
+ if (!editor || editor.isDestroyed) return EMPTY_SNAPSHOT;
2924
+ const headings = getOutline(editor);
2925
+ const activeId = getActiveHeading(editor, headings)?.id ?? null;
2926
+ const sameStructure = arraysEqual(snapshot.headings, headings, sameHeading);
2927
+ if (sameStructure && snapshot.activeId === activeId) return snapshot;
2928
+ return {
2929
+ headings: sameStructure ? snapshot.headings : headings,
2930
+ tree: sameStructure ? snapshot.tree : buildOutlineTree(headings),
2931
+ activeId
2932
+ };
2933
+ };
2934
+ snapshot = compute();
2935
+ const onTransaction = () => {
2936
+ const next = compute();
2937
+ if (next !== snapshot) {
2938
+ snapshot = next;
2939
+ listeners.forEach((l) => l());
2940
+ }
2941
+ };
2942
+ return {
2943
+ getSnapshot: () => {
2944
+ if (!listeners.size) snapshot = compute();
2945
+ return snapshot;
2946
+ },
2947
+ subscribe(listener) {
2948
+ if (!listeners.size) {
2949
+ snapshot = compute();
2950
+ editor?.on("transaction", onTransaction);
2951
+ }
2952
+ listeners.add(listener);
2953
+ return () => {
2954
+ listeners.delete(listener);
2955
+ if (!listeners.size) editor?.off("transaction", onTransaction);
2956
+ };
2957
+ },
2958
+ destroy() {
2959
+ editor?.off("transaction", onTransaction);
2960
+ listeners.clear();
2961
+ }
2962
+ };
2963
+ }
2964
+ function navigateToHeading(editor, heading, options = {}) {
2965
+ if (!editor || editor.isDestroyed) return false;
2966
+ const current = findHeading(editor.state, heading.id) ?? getOutline(editor).find((h) => h.pos === heading.pos);
2967
+ if (!current) return false;
2968
+ const { focus = true, behavior = "smooth" } = options;
2969
+ if (focus) {
2970
+ const tr = editor.state.tr.setSelection(TextSelection2.create(editor.state.doc, current.pos + 1)).scrollIntoView();
2971
+ editor.view.dispatch(tr);
2972
+ editor.view.focus();
2973
+ }
2974
+ scrollDomAtPos(editor.view, current.pos, behavior);
2975
+ return true;
2976
+ }
2977
+ function assignHeadingIds(state, types, generateId) {
2978
+ const seen = /* @__PURE__ */ new Set();
2979
+ let tr = null;
2980
+ state.doc.descendants((node, pos) => {
2981
+ if (types.includes(node.type.name)) {
2982
+ let id = node.attrs.id;
2983
+ if (!id || seen.has(id)) {
2984
+ do {
2985
+ id = generateId();
2986
+ } while (seen.has(id));
2987
+ tr = tr ?? state.tr;
2988
+ tr.setNodeMarkup(pos, void 0, { ...node.attrs, id });
2989
+ }
2990
+ seen.add(id);
2991
+ return false;
2992
+ }
2993
+ return !node.isTextblock;
2994
+ });
2995
+ return tr;
2996
+ }
2997
+ var Outline = Extension10.create({
2998
+ name: "outline",
2999
+ priority: STRUCTURE_PRIORITY,
3000
+ addOptions() {
3001
+ return {
3002
+ types: ["heading"],
3003
+ generateId: () => generateStructureId("h"),
3004
+ onUpdate: void 0
3005
+ };
3006
+ },
3007
+ addStorage() {
3008
+ return { headings: [] };
3009
+ },
3010
+ addGlobalAttributes() {
3011
+ return [
3012
+ {
3013
+ types: this.options.types,
3014
+ attributes: {
3015
+ id: {
3016
+ default: null,
3017
+ keepOnSplit: false,
3018
+ parseHTML: (element) => element.getAttribute("id") || element.getAttribute("data-id"),
3019
+ renderHTML: (attributes) => attributes.id ? { id: attributes.id } : {}
3020
+ }
3021
+ }
3022
+ }
3023
+ ];
3024
+ },
3025
+ addCommands() {
3026
+ return {
3027
+ focusHeading: (id, options = {}) => ({ state, tr, dispatch, view }) => {
3028
+ const heading = findHeading(state, id);
3029
+ if (!heading) return false;
3030
+ if (dispatch) {
3031
+ if (options.focus !== false) {
3032
+ tr.setSelection(TextSelection2.create(tr.doc, heading.pos + 1));
3033
+ tr.scrollIntoView();
3034
+ view?.focus?.();
3035
+ }
3036
+ scrollDomAtPos(view, heading.pos, options.behavior);
3037
+ }
3038
+ return true;
3039
+ },
3040
+ scrollToHeading: (id, options = {}) => ({ state, dispatch, view }) => {
3041
+ const heading = findHeading(state, id);
3042
+ if (!heading) return false;
3043
+ if (dispatch) scrollDomAtPos(view, heading.pos, options.behavior);
3044
+ return true;
3045
+ }
3046
+ };
3047
+ },
3048
+ addProseMirrorPlugins() {
3049
+ const { types, generateId } = this.options;
3050
+ const storage = this.storage;
3051
+ const onUpdate = () => this.options.onUpdate;
3052
+ return [
3053
+ new Plugin10({
3054
+ key: outlinePluginKey,
3055
+ state: {
3056
+ init: (_config, state) => {
3057
+ const headings = computeOutline(state.doc, types);
3058
+ storage.headings = headings;
3059
+ return headings;
3060
+ },
3061
+ apply: (tr, prev, _oldState, newState) => {
3062
+ if (!tr.docChanged) return prev;
3063
+ const next = computeOutline(newState.doc, types);
3064
+ if (arraysEqual(prev, next, sameHeading)) return prev;
3065
+ storage.headings = next;
3066
+ onUpdate()?.(next);
3067
+ return next;
3068
+ }
3069
+ }
3070
+ }),
3071
+ new Plugin10({
3072
+ key: outlineIdPluginKey,
3073
+ appendTransaction: (transactions, _oldState, newState) => {
3074
+ if (!transactions.some((tr) => tr.docChanged)) return null;
3075
+ return assignHeadingIds(newState, types, generateId);
3076
+ }
3077
+ })
3078
+ ];
3079
+ },
3080
+ onBeforeCreate() {
3081
+ const { types, generateId } = this.options;
3082
+ syncInitialContent(this.editor, (state) => assignHeadingIds(state, types, generateId));
3083
+ }
3084
+ });
3085
+
3086
+ // src/extensions/document-structure/table-of-contents.ts
3087
+ import { Node as Node3, mergeAttributes as mergeAttributes3 } from "@tiptap/core";
3088
+ import {
3089
+ Plugin as Plugin11,
3090
+ PluginKey as PluginKey8,
3091
+ TextSelection as TextSelection3
3092
+ } from "@tiptap/pm/state";
3093
+
3094
+ // src/extensions/document-structure/markdown-it.ts
3095
+ var installed = /* @__PURE__ */ new WeakMap();
3096
+ function once(md2, name) {
3097
+ let names = installed.get(md2);
3098
+ if (!names) {
3099
+ names = /* @__PURE__ */ new Set();
3100
+ installed.set(md2, names);
3101
+ }
3102
+ if (names.has(name)) return false;
3103
+ names.add(name);
3104
+ return true;
3105
+ }
3106
+ var escapeAttr = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3107
+ var TOC_LINE = /^(?:\[\[toc\]\]|\[toc\])\s*$/i;
3108
+ function tableOfContentsMarkdownIt(md2) {
3109
+ if (!once(md2, "toc")) return;
3110
+ md2.block.ruler.before(
3111
+ "reference",
3112
+ "pixerate_toc",
3113
+ (state, startLine, _endLine, silent) => {
3114
+ if (state.sCount[startLine] - state.blkIndent >= 4) return false;
3115
+ const start2 = state.bMarks[startLine] + state.tShift[startLine];
3116
+ const line = state.src.slice(start2, state.eMarks[startLine]);
3117
+ if (!TOC_LINE.test(line)) return false;
3118
+ if (silent) return true;
3119
+ const token = state.push("pixerate_toc", "nav", 0);
3120
+ token.block = true;
3121
+ token.map = [startLine, startLine + 1];
3122
+ state.line = startLine + 1;
3123
+ return true;
3124
+ },
3125
+ { alt: ["paragraph", "reference"] }
3126
+ );
3127
+ md2.renderer.rules.pixerate_toc = () => '<nav data-type="table-of-contents"></nav>\n';
3128
+ }
3129
+ var FOOTNOTE_DEF = /^\[\^([^\]\s]+)\]:[ \t]?(.*)$/;
3130
+ function footnotesMarkdownIt(md2) {
3131
+ if (!once(md2, "footnotes")) return;
3132
+ md2.block.ruler.before(
3133
+ "reference",
3134
+ "pixerate_footnote_def",
3135
+ (state, startLine, endLine, silent) => {
3136
+ if (state.sCount[startLine] - state.blkIndent >= 4) return false;
3137
+ const start2 = state.bMarks[startLine] + state.tShift[startLine];
3138
+ const match = FOOTNOTE_DEF.exec(
3139
+ state.src.slice(start2, state.eMarks[startLine])
3140
+ );
3141
+ if (!match) return false;
3142
+ if (silent) return true;
3143
+ const paragraphs = [[match[2]]];
3144
+ let lastContentLine = startLine;
3145
+ let pendingBlank = false;
3146
+ for (let line = startLine + 1; line < endLine; line++) {
3147
+ const lineStart = state.bMarks[line] + state.tShift[line];
3148
+ const lineEnd = state.eMarks[line];
3149
+ if (lineStart >= lineEnd) {
3150
+ pendingBlank = true;
3151
+ continue;
3152
+ }
3153
+ if (state.sCount[line] - state.blkIndent < 4) break;
3154
+ const text = state.src.slice(lineStart, lineEnd);
3155
+ if (pendingBlank) paragraphs.push([text]);
3156
+ else paragraphs[paragraphs.length - 1].push(text);
3157
+ pendingBlank = false;
3158
+ lastContentLine = line;
3159
+ }
3160
+ const open = state.push("pixerate_footnote_open", "li", 1);
3161
+ open.meta = { label: match[1] };
3162
+ open.block = true;
3163
+ open.map = [startLine, lastContentLine + 1];
3164
+ for (const paragraph of paragraphs) {
3165
+ state.push("paragraph_open", "p", 1).block = true;
3166
+ const inline = state.push("inline", "", 0);
3167
+ inline.content = paragraph.join("\n").trim();
3168
+ inline.map = [startLine, lastContentLine + 1];
3169
+ inline.children = [];
3170
+ state.push("paragraph_close", "p", -1).block = true;
3171
+ }
3172
+ state.push("pixerate_footnote_close", "li", -1).block = true;
3173
+ state.line = lastContentLine + 1;
3174
+ return true;
3175
+ },
3176
+ { alt: ["paragraph", "reference"] }
3177
+ );
3178
+ md2.inline.ruler.before("link", "pixerate_footnote_ref", (state, silent) => {
3179
+ const { src, pos, posMax } = state;
3180
+ if (src.charCodeAt(pos) !== 91 || src.charCodeAt(pos + 1) !== 94) {
3181
+ return false;
3182
+ }
3183
+ let end = pos + 2;
3184
+ while (end < posMax && src.charCodeAt(end) !== 93) {
3185
+ if (/\s/.test(src[end])) return false;
3186
+ end++;
3187
+ }
3188
+ if (end >= posMax || end === pos + 2) return false;
3189
+ if (!silent) {
3190
+ const token = state.push("pixerate_footnote_ref", "sup", 0);
3191
+ token.meta = { label: src.slice(pos + 2, end) };
3192
+ }
3193
+ state.pos = end + 1;
3194
+ return true;
3195
+ });
3196
+ md2.renderer.rules.pixerate_footnote_ref = (tokens, idx) => {
3197
+ const label = escapeAttr(tokens[idx].meta.label);
3198
+ return `<sup data-type="footnote-reference" data-id="${label}">${label}</sup>`;
3199
+ };
3200
+ md2.renderer.rules.pixerate_footnote_open = (tokens, idx) => {
3201
+ const label = escapeAttr(tokens[idx].meta.label);
3202
+ return `<section data-type="footnotes"><ol><li data-type="footnote" data-id="${label}">`;
3203
+ };
3204
+ md2.renderer.rules.pixerate_footnote_close = () => "</li></ol></section>\n";
3205
+ }
3206
+ function mergeFootnoteSections(element) {
3207
+ const sections = Array.from(
3208
+ element.querySelectorAll('section[data-type="footnotes"]')
3209
+ );
3210
+ if (!sections.length) return;
3211
+ const [first, ...rest] = sections;
3212
+ const list = first.querySelector("ol");
3213
+ if (!list) return;
3214
+ for (const section of rest) {
3215
+ section.querySelectorAll('li[data-type="footnote"]').forEach((li) => list.appendChild(li));
3216
+ section.remove();
3217
+ }
3218
+ element.appendChild(first);
3219
+ }
3220
+
3221
+ // src/extensions/document-structure/table-of-contents.ts
3222
+ var tableOfContentsPluginKey = new PluginKey8("tableOfContents");
3223
+ var sameEntry = (a, b) => a.id === b.id && a.level === b.level && a.text === b.text && a.depth === b.depth;
3224
+ function getTableOfContentsEntries(headings, minLevel = 1, maxLevel = 6) {
3225
+ const entries = [];
3226
+ const stack = [];
3227
+ for (const heading of headings) {
3228
+ if (heading.level < minLevel || heading.level > maxLevel) continue;
3229
+ while (stack.length && stack[stack.length - 1] >= heading.level) stack.pop();
3230
+ entries.push({
3231
+ // Fallback ids (`outline-n`) are not stored on the heading, so they
3232
+ // cannot be linked to.
3233
+ id: heading.id.startsWith("outline-") ? null : heading.id,
3234
+ level: heading.level,
3235
+ text: heading.text,
3236
+ depth: stack.length
3237
+ });
3238
+ stack.push(heading.level);
3239
+ }
3240
+ return entries;
3241
+ }
3242
+ function syncTableOfContents(state, headingTypes) {
3243
+ const headings = outlinePluginKey.getState(state) ?? computeOutline(state.doc, headingTypes);
3244
+ let tr = null;
3245
+ state.doc.descendants((node, pos) => {
3246
+ if (node.type.name === "tableOfContents") {
3247
+ const entries = getTableOfContentsEntries(
3248
+ headings,
3249
+ node.attrs.minLevel,
3250
+ node.attrs.maxLevel
3251
+ );
3252
+ if (!arraysEqual(node.attrs.entries ?? [], entries, sameEntry)) {
3253
+ tr = tr ?? state.tr;
3254
+ tr.setNodeMarkup(pos, void 0, { ...node.attrs, entries });
3255
+ }
3256
+ return false;
3257
+ }
3258
+ return !node.isTextblock;
3259
+ });
3260
+ return tr;
3261
+ }
3262
+ var TableOfContents = Node3.create({
3263
+ name: "tableOfContents",
3264
+ priority: STRUCTURE_PRIORITY - 5,
3265
+ group: "block",
3266
+ atom: true,
3267
+ selectable: true,
3268
+ draggable: true,
3269
+ addOptions() {
3270
+ return {
3271
+ HTMLAttributes: {},
3272
+ headingTypes: ["heading"],
3273
+ defaultTitle: "Contents",
3274
+ emptyText: "Add headings to build the table of contents."
3275
+ };
3276
+ },
3277
+ addAttributes() {
3278
+ return {
3279
+ title: {
3280
+ default: this.options.defaultTitle,
3281
+ parseHTML: (element) => element.getAttribute("data-title") ?? this.options.defaultTitle,
3282
+ renderHTML: (attributes) => ({
3283
+ "data-title": attributes.title
3284
+ })
3285
+ },
3286
+ minLevel: {
3287
+ default: 1,
3288
+ parseHTML: (element) => Number(element.getAttribute("data-min-level")) || 1,
3289
+ renderHTML: (attributes) => ({
3290
+ "data-min-level": attributes.minLevel
3291
+ })
3292
+ },
3293
+ maxLevel: {
3294
+ default: 3,
3295
+ parseHTML: (element) => Number(element.getAttribute("data-max-level")) || 3,
3296
+ renderHTML: (attributes) => ({
3297
+ "data-max-level": attributes.maxLevel
3298
+ })
3299
+ },
3300
+ entries: {
3301
+ default: [],
3302
+ rendered: false,
3303
+ // Entries are derived from the headings and recomputed after parsing.
3304
+ parseHTML: () => []
3305
+ }
3306
+ };
3307
+ },
3308
+ parseHTML() {
3309
+ return [{ tag: 'nav[data-type="table-of-contents"]', priority: 60 }];
3310
+ },
3311
+ renderHTML({ node, HTMLAttributes }) {
3312
+ const entries = node.attrs.entries ?? [];
3313
+ const list = entries.length ? [
3314
+ "ol",
3315
+ { class: "toc-list" },
3316
+ ...entries.map((entry) => [
3317
+ "li",
3318
+ {
3319
+ class: "toc-entry",
3320
+ "data-level": entry.level,
3321
+ "data-depth": entry.depth,
3322
+ style: `margin-left: ${entry.depth * 1.25}em`
3323
+ },
3324
+ entry.id ? ["a", { href: `#${entry.id}`, "data-heading-id": entry.id }, entry.text] : ["span", {}, entry.text]
3325
+ ])
3326
+ ] : ["p", { class: "toc-empty" }, this.options.emptyText];
3327
+ return [
3328
+ "nav",
3329
+ mergeAttributes3(this.options.HTMLAttributes, HTMLAttributes, {
3330
+ "data-type": "table-of-contents",
3331
+ "aria-label": node.attrs.title || "Table of contents",
3332
+ class: "pixerate-toc"
3333
+ }),
3334
+ ["p", { class: "toc-title" }, node.attrs.title ?? ""],
3335
+ list
3336
+ ];
3337
+ },
3338
+ renderText({ node }) {
3339
+ const entries = node.attrs.entries ?? [];
3340
+ return [node.attrs.title, ...entries.map((e) => `${" ".repeat(e.depth)}${e.text}`)].filter(Boolean).join("\n");
3341
+ },
3342
+ addStorage() {
3343
+ return {
3344
+ markdown: {
3345
+ serialize(state, node) {
3346
+ state.write("[[toc]]");
3347
+ state.closeBlock(node);
3348
+ },
3349
+ parse: {
3350
+ setup(markdownit2) {
3351
+ tableOfContentsMarkdownIt(markdownit2);
3352
+ }
3353
+ }
3354
+ }
3355
+ };
3356
+ },
3357
+ addCommands() {
3358
+ return {
3359
+ insertTableOfContents: (options = {}) => ({ commands }) => commands.insertContent({
3360
+ type: this.name,
3361
+ attrs: {
3362
+ title: options.title ?? this.options.defaultTitle,
3363
+ minLevel: options.minLevel ?? 1,
3364
+ maxLevel: options.maxLevel ?? 3
3365
+ }
3366
+ })
3367
+ };
3368
+ },
3369
+ addProseMirrorPlugins() {
3370
+ const { headingTypes } = this.options;
3371
+ const editor = this.editor;
3372
+ return [
3373
+ new Plugin11({
3374
+ key: tableOfContentsPluginKey,
3375
+ state: presenceStateSpec([this.name]),
3376
+ appendTransaction: (transactions, _oldState, newState) => {
3377
+ if (!transactions.some((tr) => tr.docChanged)) return null;
3378
+ if (!tableOfContentsPluginKey.getState(newState)) return null;
3379
+ return syncTableOfContents(newState, headingTypes);
3380
+ },
3381
+ props: {
3382
+ handleDOMEvents: {
3383
+ click: (view, event) => {
3384
+ const target = event.target;
3385
+ const link = target?.closest?.(
3386
+ 'nav[data-type="table-of-contents"] a[data-heading-id]'
3387
+ );
3388
+ if (!link) return false;
3389
+ event.preventDefault();
3390
+ const id = link.getAttribute("data-heading-id");
3391
+ const heading = (outlinePluginKey.getState(view.state) ?? computeOutline(view.state.doc, headingTypes)).find((h) => h.id === id);
3392
+ if (!heading) return true;
3393
+ if (editor.isEditable) {
3394
+ view.dispatch(
3395
+ view.state.tr.setSelection(TextSelection3.create(view.state.doc, heading.pos + 1)).scrollIntoView()
3396
+ );
3397
+ view.focus();
3398
+ }
3399
+ scrollDomAtPos(view, heading.pos);
3400
+ return true;
3401
+ }
3402
+ }
3403
+ }
3404
+ })
3405
+ ];
3406
+ },
3407
+ onBeforeCreate() {
3408
+ const { headingTypes } = this.options;
3409
+ syncInitialContent(this.editor, (state) => syncTableOfContents(state, headingTypes));
3410
+ }
3411
+ });
3412
+
3413
+ // src/extensions/document-structure/footnotes.ts
3414
+ import { Node as Node4, mergeAttributes as mergeAttributes4 } from "@tiptap/core";
3415
+ import {
3416
+ NodeSelection,
3417
+ Plugin as Plugin12,
3418
+ PluginKey as PluginKey9,
3419
+ TextSelection as TextSelection4
3420
+ } from "@tiptap/pm/state";
3421
+ var footnotesPluginKey = new PluginKey9("footnotes");
3422
+ var REFERENCE = "footnoteReference";
3423
+ var LIST = "footnotes";
3424
+ var ITEM = "footnote";
3425
+ function paragraphsFromText(schema, text) {
3426
+ const parts = text.split(/\n\s*\n/);
3427
+ return parts.map(
3428
+ (part) => schema.nodes.paragraph.create(null, part ? schema.text(part) : void 0)
3429
+ );
3430
+ }
3431
+ function footnoteText(node) {
3432
+ const parts = [];
3433
+ node.forEach((child) => parts.push(child.textContent));
3434
+ return parts.join("\n\n");
3435
+ }
3436
+ function getFootnotes(doc) {
3437
+ const footnotes = /* @__PURE__ */ new Map();
3438
+ const refs = [];
3439
+ doc.descendants((node, pos) => {
3440
+ if (node.type.name === LIST) {
3441
+ node.forEach((child, offset) => {
3442
+ if (child.type.name === ITEM && !footnotes.has(child.attrs.id)) {
3443
+ footnotes.set(child.attrs.id, { node: child, pos: pos + 1 + offset });
3444
+ }
3445
+ });
3446
+ return false;
3447
+ }
3448
+ if (node.type.name === REFERENCE) refs.push({ id: node.attrs.id, pos });
3449
+ return true;
3450
+ });
3451
+ return refs.map((ref, index) => {
3452
+ const footnote = footnotes.get(ref.id);
3453
+ return {
3454
+ id: ref.id,
3455
+ number: index + 1,
3456
+ text: footnote ? footnoteText(footnote.node) : "",
3457
+ referencePos: ref.pos,
3458
+ footnotePos: footnote?.pos ?? null
3459
+ };
3460
+ });
3461
+ }
3462
+ function syncFootnotes(tr, options = {}) {
3463
+ const schema = tr.doc.type.schema;
3464
+ const listType = schema.nodes[LIST];
3465
+ const itemType = schema.nodes[ITEM];
3466
+ if (!listType || !itemType) return false;
3467
+ const generateId = options.generateId ?? (() => generateStructureId("fn"));
3468
+ const refs = [];
3469
+ const lists = [];
3470
+ const existing = /* @__PURE__ */ new Map();
3471
+ tr.doc.descendants((node, pos) => {
3472
+ if (node.type === listType) {
3473
+ lists.push({ node, pos });
3474
+ node.forEach((child) => {
3475
+ if (child.type === itemType && !existing.has(child.attrs.id)) {
3476
+ existing.set(child.attrs.id, child);
3477
+ }
3478
+ });
3479
+ return false;
3480
+ }
3481
+ if (node.type.name === REFERENCE) refs.push({ node, pos });
3482
+ return true;
3483
+ });
3484
+ const seen = /* @__PURE__ */ new Set();
3485
+ const desiredRefs = [];
3486
+ refs.forEach((ref, index) => {
3487
+ let id = ref.node.attrs.id;
3488
+ let copyOf;
3489
+ if (!id || seen.has(id)) {
3490
+ copyOf = id || void 0;
3491
+ do {
3492
+ id = generateId();
3493
+ } while (seen.has(id) || existing.has(id));
3494
+ }
3495
+ seen.add(id);
3496
+ desiredRefs.push({ pos: ref.pos, id, number: index + 1, copyOf });
3497
+ });
3498
+ const desiredItems = desiredRefs.map(({ id, number, copyOf }) => {
3499
+ const source = existing.get(id) ?? (copyOf ? existing.get(copyOf) : void 0);
3500
+ const content = source?.content ?? paragraphsFromText(schema, options.contents?.[id] ?? "");
3501
+ if (source && source.attrs.id === id && source.attrs.number === number) {
3502
+ return source;
3503
+ }
3504
+ return itemType.create({ id, number }, content);
3505
+ });
3506
+ const refsInSync = desiredRefs.every(
3507
+ (d, i) => refs[i].node.attrs.id === d.id && refs[i].node.attrs.number === d.number
3508
+ );
3509
+ const lastChild = tr.doc.lastChild;
3510
+ const listInSync = desiredItems.length ? lists.length === 1 && lastChild === lists[0].node && lists[0].node.childCount === desiredItems.length && desiredItems.every((item, i) => lists[0].node.child(i) === item) : lists.length === 0;
3511
+ if (refsInSync && listInSync) return false;
3512
+ desiredRefs.forEach((d, i) => {
3513
+ const node = refs[i].node;
3514
+ if (node.attrs.id !== d.id || node.attrs.number !== d.number) {
3515
+ tr.setNodeMarkup(d.pos, void 0, { ...node.attrs, id: d.id, number: d.number });
3516
+ }
3517
+ });
3518
+ if (!listInSync) {
3519
+ for (let i = lists.length - 1; i >= 0; i--) {
3520
+ tr.delete(lists[i].pos, lists[i].pos + lists[i].node.nodeSize);
3521
+ }
3522
+ if (desiredItems.length) {
3523
+ tr.insert(tr.doc.content.size, listType.create(null, desiredItems));
3524
+ }
3525
+ }
3526
+ return true;
3527
+ }
3528
+ function findFootnote(doc, id) {
3529
+ let found = null;
3530
+ doc.descendants((node, pos) => {
3531
+ if (found) return false;
3532
+ if (node.type.name === ITEM && node.attrs.id === id) {
3533
+ found = { node, pos };
3534
+ return false;
3535
+ }
3536
+ return true;
3537
+ });
3538
+ return found;
3539
+ }
3540
+ function findReferences(doc, id) {
3541
+ const refs = [];
3542
+ doc.descendants((node, pos) => {
3543
+ if (node.type.name === LIST) return false;
3544
+ if (node.type.name === REFERENCE && node.attrs.id === id) refs.push({ node, pos });
3545
+ return true;
3546
+ });
3547
+ return refs;
3548
+ }
3549
+ var FootnoteReference = Node4.create({
3550
+ name: REFERENCE,
3551
+ priority: STRUCTURE_PRIORITY - 5,
3552
+ group: "inline",
3553
+ inline: true,
3554
+ atom: true,
3555
+ selectable: true,
3556
+ addOptions() {
3557
+ return {
3558
+ HTMLAttributes: {},
3559
+ generateId: () => generateStructureId("fn")
3560
+ };
3561
+ },
3562
+ addAttributes() {
3563
+ return {
3564
+ id: {
3565
+ default: null,
3566
+ parseHTML: (element) => element.getAttribute("data-id"),
3567
+ renderHTML: (attributes) => attributes.id ? { "data-id": attributes.id } : {}
3568
+ },
3569
+ number: {
3570
+ default: null,
3571
+ parseHTML: (element) => Number(element.getAttribute("data-number")) || null,
3572
+ renderHTML: (attributes) => attributes.number ? { "data-number": attributes.number } : {}
3573
+ }
3574
+ };
3575
+ },
3576
+ parseHTML() {
3577
+ return [{ tag: 'sup[data-type="footnote-reference"]', priority: 60 }];
3578
+ },
3579
+ renderHTML({ node, HTMLAttributes }) {
3580
+ const id = node.attrs.id;
3581
+ return [
3582
+ "sup",
3583
+ mergeAttributes4(this.options.HTMLAttributes, HTMLAttributes, {
3584
+ "data-type": "footnote-reference",
3585
+ class: "pixerate-footnote-ref",
3586
+ ...id ? { id: `fnref-${id}` } : {}
3587
+ }),
3588
+ ["a", id ? { href: `#fn-${id}` } : {}, String(node.attrs.number ?? "")]
3589
+ ];
3590
+ },
3591
+ renderText({ node }) {
3592
+ return `[${node.attrs.number ?? ""}]`;
3593
+ },
3594
+ addStorage() {
3595
+ return {
3596
+ markdown: {
3597
+ serialize(state, node) {
3598
+ state.write(`[^${node.attrs.number ?? node.attrs.id}]`);
3599
+ },
3600
+ parse: {
3601
+ setup(markdownit2) {
3602
+ footnotesMarkdownIt(markdownit2);
3603
+ },
3604
+ updateDOM(element) {
3605
+ mergeFootnoteSections(element);
3606
+ }
3607
+ }
3608
+ }
3609
+ };
3610
+ },
3611
+ addCommands() {
3612
+ return {
3613
+ insertFootnote: (options = {}) => ({ state, tr, dispatch }) => {
3614
+ const type = state.schema.nodes[REFERENCE];
3615
+ if (!type || !state.schema.nodes[ITEM]) return false;
3616
+ const { $from } = state.selection;
3617
+ for (let d = $from.depth; d > 0; d--) {
3618
+ if ($from.node(d).type.name === LIST) return false;
3619
+ }
3620
+ if (!$from.parent.inlineContent) return false;
3621
+ if (!dispatch) return true;
3622
+ const id = options.id ?? this.options.generateId();
3623
+ tr.replaceSelectionWith(type.create({ id }), false);
3624
+ syncFootnotes(tr, {
3625
+ generateId: this.options.generateId,
3626
+ contents: options.content ? { [id]: options.content } : void 0
3627
+ });
3628
+ const focusContent = options.focusContent ?? !options.content;
3629
+ if (focusContent) {
3630
+ const footnote = findFootnote(tr.doc, id);
3631
+ if (footnote) {
3632
+ tr.setSelection(TextSelection4.create(tr.doc, footnote.pos + 2));
3633
+ }
3634
+ }
3635
+ tr.scrollIntoView();
3636
+ return true;
3637
+ },
3638
+ updateFootnote: (id, content) => ({ state, tr, dispatch }) => {
3639
+ const footnote = findFootnote(state.doc, id);
3640
+ if (!footnote) return false;
3641
+ if (dispatch) {
3642
+ tr.replaceWith(
3643
+ footnote.pos + 1,
3644
+ footnote.pos + footnote.node.nodeSize - 1,
3645
+ paragraphsFromText(state.schema, content)
3646
+ );
3647
+ }
3648
+ return true;
3649
+ },
3650
+ deleteFootnote: (id) => ({ state, tr, dispatch }) => {
3651
+ const refs = findReferences(state.doc, id);
3652
+ const footnote = findFootnote(state.doc, id);
3653
+ if (!refs.length && !footnote) return false;
3654
+ if (dispatch) {
3655
+ for (let i = refs.length - 1; i >= 0; i--) {
3656
+ tr.delete(refs[i].pos, refs[i].pos + refs[i].node.nodeSize);
3657
+ }
3658
+ syncFootnotes(tr, { generateId: this.options.generateId });
3659
+ }
3660
+ return true;
3661
+ },
3662
+ goToFootnote: (id) => ({ state, tr, dispatch, view }) => {
3663
+ const footnote = findFootnote(state.doc, id);
3664
+ if (!footnote) return false;
3665
+ if (dispatch) {
3666
+ tr.setSelection(
3667
+ TextSelection4.create(tr.doc, footnote.pos + footnote.node.nodeSize - 2)
3668
+ );
3669
+ tr.scrollIntoView();
3670
+ scrollDomAtPos(view, footnote.pos);
3671
+ }
3672
+ return true;
3673
+ },
3674
+ goToFootnoteReference: (id) => ({ state, tr, dispatch, view }) => {
3675
+ const [ref] = findReferences(state.doc, id);
3676
+ if (!ref) return false;
3677
+ if (dispatch) {
3678
+ tr.setSelection(NodeSelection.create(tr.doc, ref.pos));
3679
+ tr.scrollIntoView();
3680
+ scrollDomAtPos(view, ref.pos);
3681
+ }
3682
+ return true;
3683
+ }
3684
+ };
3685
+ },
3686
+ addProseMirrorPlugins() {
3687
+ const generateId = this.options.generateId;
3688
+ const editor = this.editor;
3689
+ return [
3690
+ new Plugin12({
3691
+ key: footnotesPluginKey,
3692
+ state: presenceStateSpec([REFERENCE, LIST]),
3693
+ appendTransaction: (transactions, _oldState, newState) => {
3694
+ if (!transactions.some((tr2) => tr2.docChanged)) return null;
3695
+ if (!footnotesPluginKey.getState(newState)) return null;
3696
+ const tr = newState.tr;
3697
+ return syncFootnotes(tr, { generateId }) ? tr : null;
3698
+ },
3699
+ props: {
3700
+ handleClickOn: (_view, _pos, node, _nodePos, event) => {
3701
+ if (node.type.name !== REFERENCE || !node.attrs.id) return false;
3702
+ const target = event.target;
3703
+ if (!target?.closest?.("a")) return false;
3704
+ event.preventDefault();
3705
+ return editor.commands.goToFootnote(node.attrs.id);
3706
+ }
3707
+ }
3708
+ })
3709
+ ];
3710
+ },
3711
+ onBeforeCreate() {
3712
+ const { generateId } = this.options;
3713
+ syncInitialContent(this.editor, (state) => {
3714
+ const tr = state.tr;
3715
+ return syncFootnotes(tr, { generateId }) ? tr : null;
3716
+ });
3717
+ }
3718
+ });
3719
+ var Footnotes = Node4.create({
3720
+ name: LIST,
3721
+ group: "block",
3722
+ content: `${ITEM}+`,
3723
+ isolating: true,
3724
+ selectable: false,
3725
+ draggable: false,
3726
+ parseHTML() {
3727
+ return [{ tag: 'section[data-type="footnotes"]', contentElement: "ol", priority: 60 }];
3728
+ },
3729
+ renderHTML({ HTMLAttributes }) {
3730
+ return [
3731
+ "section",
3732
+ mergeAttributes4(HTMLAttributes, {
3733
+ "data-type": "footnotes",
3734
+ class: "pixerate-footnotes",
3735
+ role: "doc-endnotes"
3736
+ }),
3737
+ ["ol", 0]
3738
+ ];
3739
+ },
3740
+ addStorage() {
3741
+ return {
3742
+ markdown: {
3743
+ serialize(state, node) {
3744
+ node.forEach((footnote) => {
3745
+ const label = footnote.attrs.number ?? footnote.attrs.id;
3746
+ state.wrapBlock(
3747
+ " ",
3748
+ `[^${label}]: `,
3749
+ footnote,
3750
+ () => state.renderContent(footnote)
3751
+ );
3752
+ });
3753
+ },
3754
+ parse: {}
3755
+ }
3756
+ };
3757
+ }
3758
+ });
3759
+ var Footnote = Node4.create({
3760
+ name: ITEM,
3761
+ content: "paragraph+",
3762
+ isolating: true,
3763
+ defining: true,
3764
+ addAttributes() {
3765
+ return {
3766
+ id: {
3767
+ default: null,
3768
+ parseHTML: (element) => element.getAttribute("data-id"),
3769
+ renderHTML: (attributes) => attributes.id ? { "data-id": attributes.id, id: `fn-${attributes.id}` } : {}
3770
+ },
3771
+ number: {
3772
+ default: null,
3773
+ parseHTML: (element) => Number(element.getAttribute("data-number")) || null,
3774
+ renderHTML: (attributes) => attributes.number ? { "data-number": attributes.number } : {}
3775
+ }
3776
+ };
3777
+ },
3778
+ parseHTML() {
3779
+ return [{ tag: 'li[data-type="footnote"]', priority: 60 }];
3780
+ },
3781
+ renderHTML({ HTMLAttributes }) {
3782
+ return [
3783
+ "li",
3784
+ mergeAttributes4(HTMLAttributes, {
3785
+ "data-type": "footnote",
3786
+ class: "pixerate-footnote"
3787
+ }),
3788
+ 0
3789
+ ];
3790
+ },
3791
+ addKeyboardShortcuts() {
3792
+ const atFootnoteStart = () => {
3793
+ const { selection } = this.editor.state;
3794
+ if (!selection.empty) return false;
3795
+ const { $from } = selection;
3796
+ if ($from.parentOffset !== 0 || $from.depth < 2) return false;
3797
+ const footnote = $from.node($from.depth - 1);
3798
+ return footnote.type.name === ITEM && $from.index($from.depth - 1) === 0;
3799
+ };
3800
+ return {
3801
+ Backspace: () => atFootnoteStart()
3802
+ };
3803
+ }
3804
+ });
3805
+
3806
+ // src/extensions/document-structure/bookmarks.ts
3807
+ import { Mark as Mark4, mergeAttributes as mergeAttributes5 } from "@tiptap/core";
3808
+ import { TextSelection as TextSelection5 } from "@tiptap/pm/state";
3809
+ var BOOKMARK = "bookmark";
3810
+ function getBookmarks(doc) {
3811
+ const byId = /* @__PURE__ */ new Map();
3812
+ doc.descendants((node, pos) => {
3813
+ if (!node.isText) return true;
3814
+ for (const mark of node.marks) {
3815
+ if (mark.type.name !== BOOKMARK || !mark.attrs.id) continue;
3816
+ const end = pos + node.nodeSize;
3817
+ const existing = byId.get(mark.attrs.id);
3818
+ if (existing) {
3819
+ existing.text += (existing.to === pos ? "" : " ") + node.text;
3820
+ existing.to = end;
3821
+ } else {
3822
+ byId.set(mark.attrs.id, {
3823
+ id: mark.attrs.id,
3824
+ name: mark.attrs.name ?? mark.attrs.id,
3825
+ text: node.text ?? "",
3826
+ from: pos,
3827
+ to: end
3828
+ });
3829
+ }
3830
+ }
3831
+ return false;
3832
+ });
3833
+ return Array.from(byId.values());
3834
+ }
3835
+ function forEachBookmarkRange(doc, id, fn) {
3836
+ doc.descendants((node, pos) => {
3837
+ if (!node.isText) return true;
3838
+ const mark = node.marks.find(
3839
+ (m) => m.type.name === BOOKMARK && m.attrs.id === id
3840
+ );
3841
+ if (mark) fn(pos, pos + node.nodeSize, mark);
3842
+ return false;
3843
+ });
3844
+ }
3845
+ var Bookmark = Mark4.create({
3846
+ name: BOOKMARK,
3847
+ inclusive: false,
3848
+ excludes: "",
3849
+ spanning: true,
3850
+ addOptions() {
3851
+ return {
3852
+ HTMLAttributes: {},
3853
+ generateId: () => generateStructureId("bm")
3854
+ };
3855
+ },
3856
+ addAttributes() {
3857
+ return {
3858
+ id: {
3859
+ default: null,
3860
+ parseHTML: (element) => element.getAttribute("data-bookmark-id"),
3861
+ renderHTML: (attributes) => attributes.id ? { "data-bookmark-id": attributes.id } : {}
3862
+ },
3863
+ name: {
3864
+ default: null,
3865
+ parseHTML: (element) => element.getAttribute("data-bookmark-name"),
3866
+ renderHTML: (attributes) => attributes.name ? { "data-bookmark-name": attributes.name } : {}
3867
+ }
3868
+ };
3869
+ },
3870
+ parseHTML() {
3871
+ return [{ tag: "span[data-bookmark-id]", priority: 60 }];
3872
+ },
3873
+ renderHTML({ HTMLAttributes }) {
3874
+ return [
3875
+ "span",
3876
+ mergeAttributes5(this.options.HTMLAttributes, HTMLAttributes, {
3877
+ class: "pixerate-bookmark"
3878
+ }),
3879
+ 0
3880
+ ];
3881
+ },
3882
+ addCommands() {
3883
+ return {
3884
+ setBookmark: ({ name, id }) => ({ state, tr, dispatch }) => {
3885
+ const type = state.schema.marks[BOOKMARK];
3886
+ if (!type || !name) return false;
3887
+ let { from, to } = state.selection;
3888
+ if (from === to) {
3889
+ const { $from } = state.selection;
3890
+ const text = $from.parent.textBetween(0, $from.parent.content.size, void 0, "\uFFFC");
3891
+ let start2 = $from.parentOffset;
3892
+ let end = $from.parentOffset;
3893
+ while (start2 > 0 && /[\p{L}\p{N}_'-]/u.test(text[start2 - 1])) start2--;
3894
+ while (end < text.length && /[\p{L}\p{N}_'-]/u.test(text[end])) end++;
3895
+ if (start2 === end) return false;
3896
+ from = $from.start() + start2;
3897
+ to = $from.start() + end;
3898
+ }
3899
+ if (!dispatch) return true;
3900
+ const sameName = getBookmarks(state.doc).find((b) => b.name === name);
3901
+ const bookmarkId = id ?? sameName?.id ?? this.options.generateId();
3902
+ if (sameName) {
3903
+ forEachBookmarkRange(
3904
+ tr.doc,
3905
+ sameName.id,
3906
+ (f, t, mark) => tr.removeMark(f, t, mark)
3907
+ );
3908
+ }
3909
+ tr.addMark(from, to, type.create({ id: bookmarkId, name }));
3910
+ return true;
3911
+ },
3912
+ removeBookmark: (id) => ({ state, tr, dispatch }) => {
3913
+ let found = false;
3914
+ forEachBookmarkRange(state.doc, id, (from, to, mark) => {
3915
+ found = true;
3916
+ if (dispatch) tr.removeMark(from, to, mark);
3917
+ });
3918
+ return found;
3919
+ },
3920
+ renameBookmark: (id, name) => ({ state, tr, dispatch }) => {
3921
+ let found = false;
3922
+ forEachBookmarkRange(state.doc, id, (from, to, mark) => {
3923
+ found = true;
3924
+ if (dispatch) {
3925
+ tr.removeMark(from, to, mark);
3926
+ tr.addMark(from, to, mark.type.create({ ...mark.attrs, name }));
3927
+ }
3928
+ });
3929
+ return found;
3930
+ },
3931
+ goToBookmark: (id) => ({ state, tr, dispatch, view }) => {
3932
+ const bookmark = getBookmarks(state.doc).find((b) => b.id === id);
3933
+ if (!bookmark) return false;
3934
+ if (dispatch) {
3935
+ tr.setSelection(TextSelection5.create(tr.doc, bookmark.from, bookmark.to));
3936
+ tr.scrollIntoView();
3937
+ const domPos = state.doc.resolve(bookmark.from);
3938
+ scrollDomAtPos(view, domPos.before(domPos.depth));
3939
+ }
3940
+ return true;
3941
+ }
3942
+ };
3943
+ }
3944
+ });
3945
+
3946
+ // src/extensions/document-structure/cross-reference.ts
3947
+ import { Node as Node5, mergeAttributes as mergeAttributes6 } from "@tiptap/core";
3948
+ import {
3949
+ Plugin as Plugin13,
3950
+ PluginKey as PluginKey10,
3951
+ TextSelection as TextSelection6
3952
+ } from "@tiptap/pm/state";
3953
+ var crossReferencePluginKey = new PluginKey10("crossReference");
3954
+ var NAME = "crossReference";
3955
+ function getCrossReferenceTargets(doc, headingTypes = ["heading"]) {
3956
+ const headings = computeOutline(doc, headingTypes).filter((h) => !h.id.startsWith("outline-")).map((h) => ({ type: "heading", id: h.id, text: h.text, name: h.text, level: h.level }));
3957
+ const bookmarks = getBookmarks(doc).map((b) => ({
3958
+ type: "bookmark",
3959
+ id: b.id,
3960
+ text: b.text,
3961
+ name: b.name
3962
+ }));
3963
+ return [...headings, ...bookmarks];
3964
+ }
3965
+ function syncCrossReferences(state, headingTypes) {
3966
+ let lookup = null;
3967
+ const resolve = () => {
3968
+ if (!lookup) {
3969
+ const next = { targets: /* @__PURE__ */ new Map(), headingsByText: /* @__PURE__ */ new Map() };
3970
+ for (const t of getCrossReferenceTargets(state.doc, headingTypes)) {
3971
+ next.targets.set(`${t.type}:${t.id}`, t);
3972
+ if (t.type === "heading" && !next.headingsByText.has(t.text)) {
3973
+ next.headingsByText.set(t.text, t);
3974
+ }
3975
+ }
3976
+ lookup = next;
3977
+ }
3978
+ return lookup;
3979
+ };
3980
+ let tr = null;
3981
+ state.doc.descendants((node, pos) => {
3982
+ if (node.type.name !== NAME) return true;
3983
+ const { targets, headingsByText } = resolve();
3984
+ const { targetType } = node.attrs;
3985
+ let { targetId } = node.attrs;
3986
+ let target = targets.get(`${targetType}:${targetId}`);
3987
+ if (!target && targetType === "heading" && node.attrs.label) {
3988
+ target = headingsByText.get(node.attrs.label);
3989
+ if (target) targetId = target.id;
3990
+ }
3991
+ const label = target ? target.text : node.attrs.label;
3992
+ const broken = !target;
3993
+ if (label !== node.attrs.label || broken !== node.attrs.broken || targetId !== node.attrs.targetId) {
3994
+ tr = tr ?? state.tr;
3995
+ tr.setNodeMarkup(pos, void 0, { ...node.attrs, targetId, label, broken });
3996
+ }
3997
+ return false;
3998
+ });
3999
+ return tr;
4000
+ }
4001
+ var CrossReference = Node5.create({
4002
+ name: NAME,
4003
+ priority: STRUCTURE_PRIORITY - 5,
4004
+ group: "inline",
4005
+ inline: true,
4006
+ atom: true,
4007
+ selectable: true,
4008
+ addOptions() {
4009
+ return {
4010
+ HTMLAttributes: {},
4011
+ headingTypes: ["heading"],
4012
+ brokenText: "Error! Reference source not found."
4013
+ };
4014
+ },
4015
+ addAttributes() {
4016
+ return {
4017
+ targetType: {
4018
+ default: "heading",
4019
+ parseHTML: (element) => element.getAttribute("data-target-type") ?? "heading",
4020
+ renderHTML: (attributes) => ({
4021
+ "data-target-type": attributes.targetType
4022
+ })
4023
+ },
4024
+ targetId: {
4025
+ default: null,
4026
+ parseHTML: (element) => element.getAttribute("data-target-id"),
4027
+ renderHTML: (attributes) => attributes.targetId ? { "data-target-id": attributes.targetId } : {}
4028
+ },
4029
+ label: {
4030
+ default: "",
4031
+ parseHTML: (element) => element.getAttribute("data-label") ?? element.textContent ?? "",
4032
+ renderHTML: (attributes) => ({
4033
+ "data-label": attributes.label
4034
+ })
4035
+ },
4036
+ broken: {
4037
+ default: false,
4038
+ parseHTML: (element) => element.hasAttribute("data-broken"),
4039
+ renderHTML: (attributes) => attributes.broken ? { "data-broken": "true" } : {}
4040
+ }
4041
+ };
4042
+ },
4043
+ parseHTML() {
4044
+ return [{ tag: 'a[data-type="cross-reference"]', priority: 60 }];
4045
+ },
4046
+ renderHTML({ node, HTMLAttributes }) {
4047
+ const { targetType, targetId, broken, label } = node.attrs;
4048
+ const href = !broken && targetType === "heading" && targetId ? { href: `#${targetId}` } : {};
4049
+ return [
4050
+ "a",
4051
+ mergeAttributes6(this.options.HTMLAttributes, HTMLAttributes, href, {
4052
+ "data-type": "cross-reference",
4053
+ class: broken ? "pixerate-cross-reference is-broken" : "pixerate-cross-reference"
4054
+ }),
4055
+ broken ? this.options.brokenText : label
4056
+ ];
4057
+ },
4058
+ renderText({ node }) {
4059
+ return node.attrs.broken ? this.options.brokenText : node.attrs.label;
4060
+ },
4061
+ addCommands() {
4062
+ return {
4063
+ insertCrossReference: ({ targetType, targetId }) => ({ state, tr, dispatch }) => {
4064
+ const type = state.schema.nodes[NAME];
4065
+ if (!type || !state.selection.$from.parent.inlineContent) return false;
4066
+ const target = getCrossReferenceTargets(state.doc, this.options.headingTypes).find(
4067
+ (t) => t.type === targetType && t.id === targetId
4068
+ );
4069
+ if (!target) return false;
4070
+ if (dispatch) {
4071
+ tr.replaceSelectionWith(
4072
+ type.create({ targetType, targetId, label: target.text, broken: false }),
4073
+ false
4074
+ );
4075
+ }
4076
+ return true;
4077
+ },
4078
+ followCrossReference: (pos) => ({ state, tr, dispatch, view }) => {
4079
+ const node = pos != null ? state.doc.nodeAt(pos) : state.selection.node ?? null;
4080
+ if (!node || node.type.name !== NAME || node.attrs.broken) return false;
4081
+ const { targetType, targetId } = node.attrs;
4082
+ let range = null;
4083
+ if (targetType === "bookmark") {
4084
+ const bookmark = getBookmarks(state.doc).find((b) => b.id === targetId);
4085
+ if (bookmark) {
4086
+ const $from = state.doc.resolve(bookmark.from);
4087
+ range = {
4088
+ from: bookmark.from,
4089
+ to: bookmark.to,
4090
+ blockPos: $from.before($from.depth)
4091
+ };
4092
+ }
4093
+ } else {
4094
+ const heading = computeOutline(state.doc, this.options.headingTypes).find(
4095
+ (h) => h.id === targetId
4096
+ );
4097
+ if (heading) {
4098
+ range = { from: heading.pos + 1, to: heading.pos + 1, blockPos: heading.pos };
4099
+ }
4100
+ }
4101
+ if (!range) return false;
4102
+ if (dispatch) {
4103
+ tr.setSelection(TextSelection6.create(tr.doc, range.from, range.to));
4104
+ tr.scrollIntoView();
4105
+ scrollDomAtPos(view, range.blockPos);
4106
+ }
4107
+ return true;
4108
+ }
4109
+ };
4110
+ },
4111
+ addProseMirrorPlugins() {
4112
+ const { headingTypes } = this.options;
4113
+ const editor = this.editor;
4114
+ return [
4115
+ new Plugin13({
4116
+ key: crossReferencePluginKey,
4117
+ state: presenceStateSpec([NAME]),
4118
+ appendTransaction: (transactions, _oldState, newState) => {
4119
+ if (!transactions.some((tr) => tr.docChanged)) return null;
4120
+ if (!crossReferencePluginKey.getState(newState)) return null;
4121
+ return syncCrossReferences(newState, headingTypes);
4122
+ },
4123
+ props: {
4124
+ handleClickOn: (_view, _pos, node, nodePos, event) => {
4125
+ if (node.type.name !== NAME) return false;
4126
+ if (editor.isEditable && !(event.metaKey || event.ctrlKey)) return false;
4127
+ event.preventDefault();
4128
+ return editor.commands.followCrossReference(nodePos);
4129
+ }
4130
+ }
4131
+ })
4132
+ ];
4133
+ },
4134
+ onBeforeCreate() {
4135
+ const { headingTypes } = this.options;
4136
+ syncInitialContent(this.editor, (state) => syncCrossReferences(state, headingTypes));
4137
+ }
4138
+ });
4139
+
4140
+ // src/extensions/document-structure/command-items.ts
4141
+ var start = (editor, args) => {
4142
+ const chain = editor.chain().focus();
4143
+ return args?.range ? chain.deleteRange(args.range) : chain;
4144
+ };
4145
+ var documentStructureCommandItems = [
4146
+ {
4147
+ id: "table-of-contents",
4148
+ title: "Table of contents",
4149
+ description: "Insert a linked list of the document's headings",
4150
+ keywords: ["toc", "contents", "outline", "index"],
4151
+ extension: "tableOfContents",
4152
+ run: (editor, args) => start(editor, args).insertTableOfContents().run()
4153
+ },
4154
+ {
4155
+ id: "footnote",
4156
+ title: "Footnote",
4157
+ description: "Insert a numbered footnote",
4158
+ keywords: ["footnote", "note", "fn", "citation"],
4159
+ extension: "footnoteReference",
4160
+ run: (editor, args) => start(editor, args).insertFootnote({ content: args?.content }).run()
4161
+ },
4162
+ {
4163
+ id: "bookmark",
4164
+ title: "Bookmark",
4165
+ description: "Name the selected text or word so it can be referenced",
4166
+ keywords: ["bookmark", "anchor", "name"],
4167
+ extension: "bookmark",
4168
+ requires: "name",
4169
+ run: (editor, args) => !!args?.name && start(editor, args).setBookmark({ name: args.name }).run()
4170
+ },
4171
+ {
4172
+ id: "cross-reference",
4173
+ title: "Cross-reference",
4174
+ description: "Insert a live reference to a heading or bookmark",
4175
+ keywords: ["cross", "reference", "xref", "link", "ref"],
4176
+ extension: "crossReference",
4177
+ requires: "target",
4178
+ run: (editor, args) => !!args?.targetType && !!args.targetId && start(editor, args).insertCrossReference({ targetType: args.targetType, targetId: args.targetId }).run()
4179
+ }
4180
+ ];
4181
+ function getDocumentStructureCommandItems(editor, query = "") {
4182
+ if (!editor) return [];
4183
+ const installed2 = new Set(
4184
+ editor.extensionManager.extensions.map((extension) => extension.name)
4185
+ );
4186
+ const q = query.trim().toLowerCase();
4187
+ return documentStructureCommandItems.filter(
4188
+ (item) => installed2.has(item.extension) && (!q || item.title.toLowerCase().includes(q) || item.keywords.some((k) => k.startsWith(q)))
4189
+ );
4190
+ }
4191
+
4192
+ // src/extensions/rich-text-preset.ts
4193
+ var resolveExt3 = (mod, name) => mod?.[name] || mod?.default?.[name] || mod?.default?.default || mod?.default || mod;
4194
+ var StarterKit = resolveExt3(_StarterKit, "StarterKit");
4195
+ var Color = resolveExt3(_Color, "Color");
4196
+ var Highlight = resolveExt3(_Highlight, "Highlight");
4197
+ var Link2 = resolveExt3(_Link, "Link");
4198
+ var TaskList2 = resolveExt3(_TaskList, "TaskList");
4199
+ var TaskItem2 = resolveExt3(_TaskItem, "TaskItem");
4200
+ var TextAlign = resolveExt3(_TextAlign, "TextAlign");
4201
+ var TextStyle = resolveExt3(_TextStyle, "TextStyle");
4202
+ var Typography = resolveExt3(_Typography, "Typography");
4203
+ var Underline = resolveExt3(_Underline, "Underline");
4204
+ var Placeholder = resolveExt3(_Placeholder, "Placeholder");
4205
+ var CharacterCount = resolveExt3(_CharacterCount, "CharacterCount");
4206
+ var optionObject = (value) => typeof value === "object" && value !== null ? value : {};
4207
+ function createRichTextPreset(options = {}) {
4208
+ const {
4209
+ placeholder = "Start typing...",
4210
+ characterLimit,
4211
+ openLinksOnClick = false,
4212
+ enableSmilies = true,
4213
+ enableColorHighlighter = true,
4214
+ headingLevels = [1, 2, 3, 4]
4215
+ } = options;
4216
+ const extensions2 = [
4217
+ StarterKit.configure({
4218
+ orderedList: {
4219
+ HTMLAttributes: {
4220
+ class: "list-decimal pl-4"
4221
+ }
4222
+ },
4223
+ bulletList: {
4224
+ HTMLAttributes: {
4225
+ class: "list-disc pl-4"
4226
+ }
4227
+ },
4228
+ heading: {
4229
+ levels: headingLevels,
4230
+ HTMLAttributes: {
4231
+ class: "font-bold tracking-tight"
4232
+ }
4233
+ }
4234
+ }),
4235
+ TextStyle,
4236
+ Color,
4237
+ Highlight.configure({ multicolor: true }),
4238
+ Underline,
4239
+ ...options.autocorrect ? [] : [Typography],
4240
+ TextAlign.configure({
4241
+ types: ["heading", "paragraph"]
4242
+ }),
4243
+ Link2.extend({
4244
+ addInputRules() {
4245
+ return [
4246
+ markInputRule({
4247
+ find: /(?:^|\s)\[([^\]]+)\]\(([^)]+)\)$/,
4248
+ type: this.type,
4249
+ getAttributes: (match) => ({
4250
+ href: match[2]
4251
+ })
4252
+ })
4253
+ ];
4254
+ }
4255
+ }).configure({
4256
+ openOnClick: openLinksOnClick,
4257
+ autolink: true,
4258
+ defaultProtocol: "https",
4259
+ protocols: ["file", "vscode", "cursor"],
4260
+ HTMLAttributes: {
4261
+ target: "_blank",
4262
+ rel: "noopener noreferrer",
4263
+ class: "text-blue-500 underline underline-offset-2"
4264
+ }
4265
+ }),
4266
+ TaskList2,
4267
+ TaskItem2.extend({
4268
+ addInputRules() {
4269
+ return [
4270
+ wrappingInputRule({
4271
+ find: /^\s*(\[([ |x])?\])\s$/i,
4272
+ type: this.type,
4273
+ getAttributes: (match) => ({
4274
+ checked: match[2]?.toLowerCase() === "x"
4275
+ })
4276
+ })
4277
+ ];
4278
+ }
4279
+ }).configure({
4280
+ nested: true
4281
+ }),
4282
+ FontSize,
4283
+ Placeholder.configure({
4284
+ emptyEditorClass: "is-empty",
4285
+ placeholder: typeof placeholder === "function" ? placeholder : () => placeholder
4286
+ })
4287
+ ];
4288
+ if (characterLimit) {
4289
+ extensions2.push(
4290
+ CharacterCount.configure({
4291
+ limit: characterLimit
4292
+ })
4293
+ );
4294
+ }
4295
+ if (enableSmilies) {
4296
+ extensions2.push(SmilieReplacer);
4297
+ }
4298
+ if (enableColorHighlighter) {
4299
+ extensions2.push(ColorHighlighter);
4300
+ }
4301
+ if (options.mention) {
4302
+ const mentionConfig = typeof options.mention === "object" ? options.mention : {};
4303
+ extensions2.push(
4304
+ Mention.configure({
4305
+ HTMLAttributes: {
4306
+ class: "pixerate-mention-node",
4307
+ ...mentionConfig.HTMLAttributes || {}
4308
+ },
4309
+ ...mentionConfig
4310
+ })
4311
+ );
4312
+ }
4313
+ if (options.markdown) {
4314
+ const markdownConfig = typeof options.markdown === "object" ? options.markdown : {};
4315
+ extensions2.push(
4316
+ Markdown2.configure({
4317
+ html: true,
4318
+ tightLists: true,
4319
+ tightListClass: "tight",
4320
+ bulletListMarker: "-",
4321
+ linkify: true,
4322
+ breaks: true,
4323
+ transformPastedText: true,
4324
+ transformCopiedText: true,
4325
+ ...markdownConfig
4326
+ })
4327
+ );
4328
+ }
4329
+ if (options.image !== false) {
4330
+ const imageConfig = typeof options.image === "object" ? options.image : {};
4331
+ extensions2.push(
4332
+ Image2.configure({
4333
+ HTMLAttributes: {
4334
+ class: "rounded-md max-w-full h-auto my-2",
4335
+ ...imageConfig.HTMLAttributes || {}
4336
+ },
4337
+ ...imageConfig
4338
+ })
4339
+ );
4340
+ }
4341
+ if (options.comments) {
4342
+ extensions2.push(
4343
+ typeof options.comments === "object" ? Comments.configure(options.comments) : Comments
4344
+ );
4345
+ }
4346
+ if (options.trackChanges) {
4347
+ extensions2.push(
4348
+ typeof options.trackChanges === "object" ? TrackChanges.configure(options.trackChanges) : TrackChanges
4349
+ );
4350
+ }
4351
+ if (options.fileImport) {
4352
+ extensions2.push(
4353
+ FileImport.configure(
4354
+ typeof options.fileImport === "object" ? options.fileImport : {}
4355
+ )
4356
+ );
4357
+ }
4358
+ if (options.richClipboard) {
4359
+ extensions2.push(
4360
+ RichClipboard.configure(
4361
+ typeof options.richClipboard === "object" ? options.richClipboard : {}
4362
+ )
4363
+ );
4364
+ }
4365
+ if (options.autocorrect) {
4366
+ extensions2.push(Autocorrect.configure(optionObject(options.autocorrect)));
4367
+ }
4368
+ if (options.outline || options.tableOfContents || options.crossReferences) {
4369
+ extensions2.push(Outline.configure(optionObject(options.outline)));
4370
+ }
4371
+ if (options.tableOfContents) {
4372
+ extensions2.push(TableOfContents.configure(optionObject(options.tableOfContents)));
4373
+ }
4374
+ if (options.footnotes) {
4375
+ extensions2.push(
4376
+ FootnoteReference.configure(optionObject(options.footnotes)),
4377
+ Footnotes,
4378
+ Footnote
4379
+ );
4380
+ }
4381
+ if (options.bookmarks) {
4382
+ extensions2.push(Bookmark.configure(optionObject(options.bookmarks)));
4383
+ }
4384
+ if (options.crossReferences) {
4385
+ extensions2.push(CrossReference.configure(optionObject(options.crossReferences)));
4386
+ }
4387
+ return extensions2;
4388
+ }
4389
+
4390
+ export {
4391
+ GradientText,
4392
+ SlashCommands,
4393
+ TemplateSuggestions,
4394
+ LoadingNode,
4395
+ SMILIE_REPLACEMENTS,
4396
+ smilieAutocorrectRules,
4397
+ SmilieReplacer,
4398
+ findHexColors,
4399
+ ColorHighlighter,
4400
+ FontSize,
4401
+ Mention,
4402
+ createMentionExtension,
4403
+ Markdown2 as Markdown,
4404
+ imagePastePluginKey,
4405
+ insertImageNode,
4406
+ updateImageSrc,
4407
+ removeImageNode,
4408
+ handleImageInsertion,
4409
+ Image2 as Image,
4410
+ commentsPluginKey,
4411
+ COMMENT_MARK_NAME,
4412
+ findCommentRanges,
4413
+ getCommentAnchors,
4414
+ getCommentIdsAt,
4415
+ getCommentStore,
4416
+ Comments,
4417
+ getCommentAuthor,
4418
+ ImportError,
4419
+ createImportContext,
4420
+ ImportAdapterRegistry,
4421
+ markdownImportAdapter,
4422
+ htmlImportAdapter,
4423
+ plainTextImportAdapter,
4424
+ rtfImportAdapter,
4425
+ BUILT_IN_IMPORT_ADAPTERS,
4426
+ createImportAdapterRegistry,
4427
+ defaultImportRegistry,
4428
+ registerImportAdapter,
4429
+ unregisterImportAdapter,
4430
+ findImportAdapter,
4431
+ importFile,
4432
+ getImportAccept,
4433
+ htmlToSlice,
4434
+ insertImportedDocument,
4435
+ importFilesIntoEditor,
4436
+ fileImportPluginKey,
4437
+ FileImport,
4438
+ richClipboardPluginKey,
4439
+ writeRichClipboard,
4440
+ RichClipboard,
4441
+ smartQuoteRules,
4442
+ symbolRules,
4443
+ fractionRules,
4444
+ capitalizeSentenceRules,
4445
+ twoInitialCapitalsRules,
4446
+ defaultAutocorrectRules,
4447
+ DEFAULT_AUTOCORRECT_EXCEPTIONS,
4448
+ autocorrectPluginKey,
4449
+ findAutocorrection,
4450
+ isInsidePromptToken,
4451
+ Autocorrect,
4452
+ outlinePluginKey,
4453
+ computeOutline,
4454
+ buildOutlineTree,
4455
+ getOutline,
4456
+ getActiveHeading,
4457
+ createOutlineStore,
4458
+ navigateToHeading,
4459
+ Outline,
4460
+ tableOfContentsMarkdownIt,
4461
+ footnotesMarkdownIt,
4462
+ tableOfContentsPluginKey,
4463
+ getTableOfContentsEntries,
4464
+ TableOfContents,
4465
+ footnotesPluginKey,
4466
+ getFootnotes,
4467
+ syncFootnotes,
4468
+ FootnoteReference,
4469
+ Footnotes,
4470
+ Footnote,
4471
+ getBookmarks,
4472
+ Bookmark,
4473
+ crossReferencePluginKey,
4474
+ getCrossReferenceTargets,
4475
+ CrossReference,
4476
+ documentStructureCommandItems,
4477
+ getDocumentStructureCommandItems,
4478
+ createRichTextPreset
4479
+ };
4480
+ //# sourceMappingURL=chunk-CYFJCKC4.js.map