miyuan-editor 0.0.3

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 (58) hide show
  1. package/README.md +525 -0
  2. package/dist/core/index.cjs.js +40 -0
  3. package/dist/core/index.esm.js +4 -0
  4. package/dist/dist-5Q_Z9Ell.js +6390 -0
  5. package/dist/dist-5Q_Z9Ell.js.map +1 -0
  6. package/dist/dist-CMM6n8DO.cjs +4629 -0
  7. package/dist/dist-CMM6n8DO.cjs.map +1 -0
  8. package/dist/dist-CRSJDo2G.cjs +6617 -0
  9. package/dist/dist-CRSJDo2G.cjs.map +1 -0
  10. package/dist/dist-CZw77IJK.js +4612 -0
  11. package/dist/dist-CZw77IJK.js.map +1 -0
  12. package/dist/dist-CnVrDtsI.js +556 -0
  13. package/dist/dist-CnVrDtsI.js.map +1 -0
  14. package/dist/dist-rItBfhNb.cjs +591 -0
  15. package/dist/dist-rItBfhNb.cjs.map +1 -0
  16. package/dist/export-utils-CYaNoyVg.cjs +167 -0
  17. package/dist/export-utils-CYaNoyVg.cjs.map +1 -0
  18. package/dist/export-utils-DN0Gu8Vu.js +144 -0
  19. package/dist/export-utils-DN0Gu8Vu.js.map +1 -0
  20. package/dist/extension-BPFuYyzN.cjs +338 -0
  21. package/dist/extension-BPFuYyzN.cjs.map +1 -0
  22. package/dist/extension-Cl6x5MDR.js +321 -0
  23. package/dist/extension-Cl6x5MDR.js.map +1 -0
  24. package/dist/extensions/index.cjs.js +3462 -0
  25. package/dist/extensions/index.cjs.js.map +1 -0
  26. package/dist/extensions/index.esm.js +3412 -0
  27. package/dist/extensions/index.esm.js.map +1 -0
  28. package/dist/prompt-B4AOP8f_.js +24143 -0
  29. package/dist/prompt-B4AOP8f_.js.map +1 -0
  30. package/dist/prompt-CGLw2O21.cjs +25530 -0
  31. package/dist/prompt-CGLw2O21.cjs.map +1 -0
  32. package/dist/react/index.cjs.js +839 -0
  33. package/dist/react/index.cjs.js.map +1 -0
  34. package/dist/react/index.esm.js +820 -0
  35. package/dist/react/index.esm.js.map +1 -0
  36. package/dist/shortcut-panel-BskGXV8n.js +49468 -0
  37. package/dist/shortcut-panel-BskGXV8n.js.map +1 -0
  38. package/dist/shortcut-panel-yP4RPTFt.cjs +49563 -0
  39. package/dist/shortcut-panel-yP4RPTFt.cjs.map +1 -0
  40. package/dist/toc-extension-BESc0uEW.js +150 -0
  41. package/dist/toc-extension-BESc0uEW.js.map +1 -0
  42. package/dist/toc-extension-SRvSuskn.cjs +173 -0
  43. package/dist/toc-extension-SRvSuskn.cjs.map +1 -0
  44. package/dist/toolbar-config-Cgc9mV2v.js +243 -0
  45. package/dist/toolbar-config-Cgc9mV2v.js.map +1 -0
  46. package/dist/toolbar-config-Cjt_fPMi.cjs +260 -0
  47. package/dist/toolbar-config-Cjt_fPMi.cjs.map +1 -0
  48. package/dist/ui/index.cjs.js +18 -0
  49. package/dist/ui/index.esm.js +3 -0
  50. package/dist/vue/index.cjs.js +323 -0
  51. package/dist/vue/index.cjs.js.map +1 -0
  52. package/dist/vue/index.esm.js +307 -0
  53. package/dist/vue/index.esm.js.map +1 -0
  54. package/dist/vue2/index.cjs.js +323 -0
  55. package/dist/vue2/index.cjs.js.map +1 -0
  56. package/dist/vue2/index.esm.js +307 -0
  57. package/dist/vue2/index.esm.js.map +1 -0
  58. package/package.json +116 -0
@@ -0,0 +1,3412 @@
1
+ import { M as Fragment, P as Node, c as toggleMark, f as NodeSelection, g as TextSelection, l as wrapIn, m as PluginKey, p as Plugin, s as setBlockType } from "../dist-5Q_Z9Ell.js";
2
+ import { n as DecorationSet, t as Decoration } from "../dist-CZw77IJK.js";
3
+ import { n as redo, o as splitListItem, r as undo, t as history } from "../dist-CnVrDtsI.js";
4
+ import { i as getWordCountStats, n as getTableOfContents, r as WordCountExtension, t as TableOfContentsExtension } from "../toc-extension-BESc0uEW.js";
5
+ //#region src/extensions/doc-extension.ts
6
+ /**
7
+ * DocExtension: the root document node.
8
+ */
9
+ function DocExtension() {
10
+ return {
11
+ name: "doc",
12
+ type: "extension",
13
+ addNodes() {
14
+ return { doc: { content: "block+" } };
15
+ }
16
+ };
17
+ }
18
+ //#endregion
19
+ //#region src/extensions/text-paragraph-extension.ts
20
+ var MAX_INDENT = 7;
21
+ var INDENT_STEP = 2;
22
+ /**
23
+ * TextExtension: the inline text leaf node.
24
+ */
25
+ function TextExtension() {
26
+ return {
27
+ name: "text",
28
+ type: "extension",
29
+ addNodes() {
30
+ return { text: { group: "inline" } };
31
+ }
32
+ };
33
+ }
34
+ /** Helper: compute inline style from attrs (textAlign + indent + lineHeight) */
35
+ function blockStyle(attrs) {
36
+ const parts = [];
37
+ const align = attrs.textAlign;
38
+ if (align && align !== "left") parts.push(`text-align: ${align}`);
39
+ const indent = attrs.indent || 0;
40
+ if (indent > 0) parts.push(`margin-left: ${indent * INDENT_STEP}em`);
41
+ const lineHeight = attrs.lineHeight;
42
+ if (lineHeight) parts.push(`line-height: ${lineHeight}`);
43
+ return parts.length ? parts.join("; ") : void 0;
44
+ }
45
+ /** Helper: parse text-align from DOM element */
46
+ function parseAlign(dom) {
47
+ const match = (dom.getAttribute("style") || "").match(/text-align:\s*(left|center|right)/);
48
+ if (match) return match[1];
49
+ return null;
50
+ }
51
+ /** Helper: parse indent level from DOM element */
52
+ function parseIndent(dom) {
53
+ const match = (dom.getAttribute("style") || "").match(/margin-left:\s*([\d.]+)em/);
54
+ if (!match) return 0;
55
+ const em = parseFloat(match[1]);
56
+ return Math.round(em / INDENT_STEP);
57
+ }
58
+ /** Helper: parse line-height from DOM element */
59
+ function parseLineHeight(dom) {
60
+ const match = (dom.getAttribute("style") || "").match(/line-height:\s*([\d.]+)/);
61
+ if (!match) return null;
62
+ return parseFloat(match[1]);
63
+ }
64
+ /**
65
+ * ParagraphExtension: the basic block node for text content.
66
+ * Supports textAlign attribute.
67
+ */
68
+ function ParagraphExtension() {
69
+ return {
70
+ name: "paragraph",
71
+ type: "node",
72
+ addNodes() {
73
+ return { paragraph: {
74
+ group: "block",
75
+ content: "inline*",
76
+ attrs: {
77
+ textAlign: { default: null },
78
+ indent: { default: 0 },
79
+ lineHeight: { default: null }
80
+ },
81
+ toDOM(node) {
82
+ const style = blockStyle(node.attrs);
83
+ return style ? [
84
+ "p",
85
+ { style },
86
+ 0
87
+ ] : ["p", 0];
88
+ },
89
+ parseDOM: [{
90
+ tag: "p",
91
+ getAttrs(dom) {
92
+ const el = dom;
93
+ return {
94
+ textAlign: parseAlign(el),
95
+ indent: parseIndent(el),
96
+ lineHeight: parseLineHeight(el)
97
+ };
98
+ }
99
+ }]
100
+ } };
101
+ },
102
+ addCommands() {
103
+ return {
104
+ setTextAlign: (...args) => {
105
+ const align = args[0];
106
+ return setTextAlignCommand(align, ["paragraph", "heading"]);
107
+ },
108
+ indent: () => indentCommand(1, ["paragraph", "heading"]),
109
+ outdent: () => indentCommand(-1, ["paragraph", "heading"])
110
+ };
111
+ },
112
+ addKeyboardShortcuts() {
113
+ return {
114
+ Tab: indentCommand(1, ["paragraph", "heading"]),
115
+ "Shift-Tab": indentCommand(-1, ["paragraph", "heading"])
116
+ };
117
+ }
118
+ };
119
+ }
120
+ /**
121
+ * Create a command that sets textAlign on the given block types.
122
+ */
123
+ function setTextAlignCommand(align, nodeNames) {
124
+ return (state, dispatch) => {
125
+ const { from, to } = state.selection;
126
+ let applicable = false;
127
+ state.doc.nodesBetween(from, to, (node, pos) => {
128
+ if (!nodeNames.includes(node.type.name)) return;
129
+ applicable = true;
130
+ if (dispatch) {
131
+ const tr = state.tr;
132
+ const newAlign = node.attrs.textAlign === align ? null : align;
133
+ tr.setNodeMarkup(pos, null, {
134
+ ...node.attrs,
135
+ textAlign: newAlign
136
+ });
137
+ dispatch(tr);
138
+ }
139
+ });
140
+ return applicable;
141
+ };
142
+ }
143
+ /**
144
+ * Create a command that changes indent level by delta (+1 or -1).
145
+ */
146
+ function indentCommand(delta, nodeNames) {
147
+ return (state, dispatch) => {
148
+ const { from, to } = state.selection;
149
+ let applicable = false;
150
+ state.doc.nodesBetween(from, to, (node, pos) => {
151
+ if (!nodeNames.includes(node.type.name)) return;
152
+ const current = node.attrs.indent || 0;
153
+ const next = Math.max(0, Math.min(MAX_INDENT, current + delta));
154
+ if (next === current) return;
155
+ applicable = true;
156
+ if (dispatch) {
157
+ const tr = state.tr;
158
+ tr.setNodeMarkup(pos, null, {
159
+ ...node.attrs,
160
+ indent: next
161
+ });
162
+ dispatch(tr);
163
+ }
164
+ });
165
+ return applicable;
166
+ };
167
+ }
168
+ //#endregion
169
+ //#region src/extensions/mark-extensions.ts
170
+ function BoldExtension() {
171
+ return {
172
+ name: "bold",
173
+ type: "mark",
174
+ addMarks() {
175
+ return { bold: {
176
+ toDOM() {
177
+ return ["strong", 0];
178
+ },
179
+ parseDOM: [{ tag: "strong" }, {
180
+ tag: "b",
181
+ getAttrs: (node) => node.style.fontWeight !== "normal" && null
182
+ }]
183
+ } };
184
+ },
185
+ addCommands() {
186
+ return { bold: () => (state, dispatch) => {
187
+ const mt = state.schema.marks["bold"];
188
+ if (!mt) return false;
189
+ return toggleMark(mt)(state, dispatch);
190
+ } };
191
+ },
192
+ addKeyboardShortcuts() {
193
+ return { "Mod-b": (state, dispatch) => {
194
+ const mt = state.schema.marks["bold"];
195
+ if (!mt) return false;
196
+ return toggleMark(mt)(state, dispatch);
197
+ } };
198
+ }
199
+ };
200
+ }
201
+ function ItalicExtension() {
202
+ return {
203
+ name: "italic",
204
+ type: "mark",
205
+ addMarks() {
206
+ return { italic: {
207
+ toDOM() {
208
+ return ["em", 0];
209
+ },
210
+ parseDOM: [{ tag: "em" }, { tag: "i" }]
211
+ } };
212
+ },
213
+ addCommands() {
214
+ return { italic: () => (state, dispatch) => {
215
+ const mt = state.schema.marks["italic"];
216
+ if (!mt) return false;
217
+ return toggleMark(mt)(state, dispatch);
218
+ } };
219
+ },
220
+ addKeyboardShortcuts() {
221
+ return { "Mod-i": (state, dispatch) => {
222
+ const mt = state.schema.marks["italic"];
223
+ if (!mt) return false;
224
+ return toggleMark(mt)(state, dispatch);
225
+ } };
226
+ }
227
+ };
228
+ }
229
+ function CodeExtension() {
230
+ return {
231
+ name: "code",
232
+ type: "mark",
233
+ addMarks() {
234
+ return { code: {
235
+ excludes: "_",
236
+ toDOM() {
237
+ return ["code", 0];
238
+ },
239
+ parseDOM: [{ tag: "code" }]
240
+ } };
241
+ },
242
+ addCommands() {
243
+ return { code: () => (state, dispatch) => {
244
+ const mt = state.schema.marks["code"];
245
+ if (!mt) return false;
246
+ return toggleMark(mt)(state, dispatch);
247
+ } };
248
+ },
249
+ addKeyboardShortcuts() {
250
+ return { "Mod-e": (state, dispatch) => {
251
+ const mt = state.schema.marks["code"];
252
+ if (!mt) return false;
253
+ return toggleMark(mt)(state, dispatch);
254
+ } };
255
+ }
256
+ };
257
+ }
258
+ function StrikeExtension() {
259
+ return {
260
+ name: "strike",
261
+ type: "mark",
262
+ addMarks() {
263
+ return { strike: {
264
+ toDOM() {
265
+ return ["s", 0];
266
+ },
267
+ parseDOM: [
268
+ { tag: "s" },
269
+ { tag: "del" },
270
+ { tag: "strike" }
271
+ ]
272
+ } };
273
+ },
274
+ addCommands() {
275
+ return { strike: () => (state, dispatch) => {
276
+ const mt = state.schema.marks["strike"];
277
+ if (!mt) return false;
278
+ return toggleMark(mt)(state, dispatch);
279
+ } };
280
+ }
281
+ };
282
+ }
283
+ function UnderlineExtension() {
284
+ return {
285
+ name: "underline",
286
+ type: "mark",
287
+ addMarks() {
288
+ return { underline: {
289
+ toDOM() {
290
+ return ["u", 0];
291
+ },
292
+ parseDOM: [{ tag: "u" }]
293
+ } };
294
+ },
295
+ addCommands() {
296
+ return { underline: () => (state, dispatch) => {
297
+ const mt = state.schema.marks["underline"];
298
+ if (!mt) return false;
299
+ return toggleMark(mt)(state, dispatch);
300
+ } };
301
+ },
302
+ addKeyboardShortcuts() {
303
+ return { "Mod-u": (state, dispatch) => {
304
+ const mt = state.schema.marks["underline"];
305
+ if (!mt) return false;
306
+ return toggleMark(mt)(state, dispatch);
307
+ } };
308
+ }
309
+ };
310
+ }
311
+ function LinkExtension() {
312
+ return {
313
+ name: "link",
314
+ type: "mark",
315
+ addMarks() {
316
+ return { link: {
317
+ attrs: {
318
+ href: {},
319
+ target: { default: "_blank" }
320
+ },
321
+ inclusive: false,
322
+ toDOM(mark) {
323
+ const { href, target } = mark.attrs;
324
+ return [
325
+ "a",
326
+ {
327
+ href,
328
+ target
329
+ },
330
+ 0
331
+ ];
332
+ },
333
+ parseDOM: [{
334
+ tag: "a[href]",
335
+ getAttrs(dom) {
336
+ const el = dom;
337
+ return {
338
+ href: el.getAttribute("href"),
339
+ target: el.getAttribute("target")
340
+ };
341
+ }
342
+ }]
343
+ } };
344
+ },
345
+ addCommands() {
346
+ return { setLink: (...args) => {
347
+ const attrs = args[0] ?? {};
348
+ return (state, dispatch) => {
349
+ const mt = state.schema.marks["link"];
350
+ if (!mt) return false;
351
+ return toggleMark(mt, attrs)(state, dispatch);
352
+ };
353
+ } };
354
+ }
355
+ };
356
+ }
357
+ var DEFAULT_COLORS = [
358
+ "#000000",
359
+ "#434343",
360
+ "#666666",
361
+ "#999999",
362
+ "#b7b7b7",
363
+ "#cccccc",
364
+ "#d9d9d9",
365
+ "#ffffff",
366
+ "#980000",
367
+ "#ff0000",
368
+ "#ff9900",
369
+ "#ffff00",
370
+ "#00ff00",
371
+ "#00ffff",
372
+ "#4a86e8",
373
+ "#0000ff",
374
+ "#9900ff",
375
+ "#ff00ff",
376
+ "#f4cccc",
377
+ "#fce5cd",
378
+ "#fff2cc",
379
+ "#d9ead3",
380
+ "#d0e0e3",
381
+ "#c9daf8",
382
+ "#e4d7f5",
383
+ "#ea9999",
384
+ "#f9cb9c",
385
+ "#ffe599",
386
+ "#b6d7a8",
387
+ "#a2c4c9",
388
+ "#a4c2f4",
389
+ "#d5a6bd"
390
+ ];
391
+ function TextColorExtension(config) {
392
+ return {
393
+ name: "textColor",
394
+ type: "mark",
395
+ options: { colors: config?.colors ?? DEFAULT_COLORS },
396
+ addMarks() {
397
+ return { textColor: {
398
+ attrs: { color: {} },
399
+ toDOM(mark) {
400
+ return [
401
+ "span",
402
+ { style: `color: ${mark.attrs.color}` },
403
+ 0
404
+ ];
405
+ },
406
+ parseDOM: [{
407
+ tag: "span[style*=\"color\"]",
408
+ getAttrs(dom) {
409
+ const color = dom.style.color;
410
+ return color ? { color } : false;
411
+ }
412
+ }]
413
+ } };
414
+ },
415
+ addCommands() {
416
+ return { setTextColor: (...args) => {
417
+ const color = args[0];
418
+ return (state, dispatch) => {
419
+ const mt = state.schema.marks["textColor"];
420
+ if (!mt) return false;
421
+ const { from, to } = state.selection;
422
+ if (from === to) return false;
423
+ if (dispatch) {
424
+ let tr = state.tr;
425
+ tr = tr.removeMark(from, to, mt);
426
+ if (color) tr = tr.addMark(from, to, mt.create({ color }));
427
+ dispatch(tr);
428
+ }
429
+ return true;
430
+ };
431
+ } };
432
+ }
433
+ };
434
+ }
435
+ function BackgroundColorExtension(config) {
436
+ return {
437
+ name: "backgroundColor",
438
+ type: "mark",
439
+ options: { colors: config?.colors ?? DEFAULT_COLORS },
440
+ addMarks() {
441
+ return { backgroundColor: {
442
+ attrs: { color: {} },
443
+ toDOM(mark) {
444
+ return [
445
+ "span",
446
+ { style: `background-color: ${mark.attrs.color}` },
447
+ 0
448
+ ];
449
+ },
450
+ parseDOM: [{
451
+ tag: "span[style*=\"background-color\"]",
452
+ getAttrs(dom) {
453
+ const color = dom.style.backgroundColor;
454
+ return color ? { color } : false;
455
+ }
456
+ }]
457
+ } };
458
+ },
459
+ addCommands() {
460
+ return { setBackgroundColor: (...args) => {
461
+ const color = args[0];
462
+ return (state, dispatch) => {
463
+ const mt = state.schema.marks["backgroundColor"];
464
+ if (!mt) return false;
465
+ const { from, to } = state.selection;
466
+ if (from === to) return false;
467
+ if (dispatch) {
468
+ let tr = state.tr;
469
+ tr = tr.removeMark(from, to, mt);
470
+ if (color) tr = tr.addMark(from, to, mt.create({ color }));
471
+ dispatch(tr);
472
+ }
473
+ return true;
474
+ };
475
+ } };
476
+ }
477
+ };
478
+ }
479
+ var DEFAULT_FONT_SIZES = [
480
+ {
481
+ label: "12",
482
+ value: "12px"
483
+ },
484
+ {
485
+ label: "14",
486
+ value: "14px"
487
+ },
488
+ {
489
+ label: "16",
490
+ value: "16px"
491
+ },
492
+ {
493
+ label: "18",
494
+ value: "18px"
495
+ },
496
+ {
497
+ label: "20",
498
+ value: "20px"
499
+ },
500
+ {
501
+ label: "24",
502
+ value: "24px"
503
+ },
504
+ {
505
+ label: "28",
506
+ value: "28px"
507
+ },
508
+ {
509
+ label: "32",
510
+ value: "32px"
511
+ },
512
+ {
513
+ label: "36",
514
+ value: "36px"
515
+ },
516
+ {
517
+ label: "48",
518
+ value: "48px"
519
+ }
520
+ ];
521
+ function normalizeFontSize(value) {
522
+ const num = typeof value === "string" ? parseFloat(value) : value;
523
+ if (isNaN(num) || num <= 0) return "";
524
+ return `${Math.round(num)}px`;
525
+ }
526
+ function FontSizeExtension(config) {
527
+ return {
528
+ name: "fontSize",
529
+ type: "mark",
530
+ options: { sizes: config?.sizes ?? DEFAULT_FONT_SIZES },
531
+ addMarks() {
532
+ return { fontSize: {
533
+ attrs: { size: {} },
534
+ toDOM(mark) {
535
+ return [
536
+ "span",
537
+ { style: `font-size: ${normalizeFontSize(mark.attrs.size)}` },
538
+ 0
539
+ ];
540
+ },
541
+ parseDOM: [{
542
+ tag: "span[style*=\"font-size\"]",
543
+ getAttrs(dom) {
544
+ const raw = dom.style.fontSize;
545
+ const size = normalizeFontSize(raw);
546
+ return size ? { size } : false;
547
+ }
548
+ }]
549
+ } };
550
+ },
551
+ addCommands() {
552
+ return { setFontSize: (...args) => {
553
+ const size = normalizeFontSize(args[0]);
554
+ return (state, dispatch) => {
555
+ const mt = state.schema.marks["fontSize"];
556
+ if (!mt) return false;
557
+ const { from, to } = state.selection;
558
+ if (from === to) return false;
559
+ if (dispatch) {
560
+ let tr = state.tr;
561
+ tr = tr.removeMark(from, to, mt);
562
+ if (size) tr = tr.addMark(from, to, mt.create({ size }));
563
+ dispatch(tr);
564
+ }
565
+ return true;
566
+ };
567
+ } };
568
+ }
569
+ };
570
+ }
571
+ var DEFAULT_FONT_FAMILIES = [
572
+ {
573
+ label: "默认",
574
+ value: ""
575
+ },
576
+ {
577
+ label: "宋体",
578
+ value: "SimSun, serif"
579
+ },
580
+ {
581
+ label: "黑体",
582
+ value: "SimHei, sans-serif"
583
+ },
584
+ {
585
+ label: "微软雅黑",
586
+ value: "Microsoft YaHei, sans-serif"
587
+ },
588
+ {
589
+ label: "楷体",
590
+ value: "KaiTi, serif"
591
+ },
592
+ {
593
+ label: "Arial",
594
+ value: "Arial, sans-serif"
595
+ },
596
+ {
597
+ label: "Times New Roman",
598
+ value: "Times New Roman, serif"
599
+ },
600
+ {
601
+ label: "Courier New",
602
+ value: "Courier New, monospace"
603
+ },
604
+ {
605
+ label: "Georgia",
606
+ value: "Georgia, serif"
607
+ },
608
+ {
609
+ label: "Verdana",
610
+ value: "Verdana, sans-serif"
611
+ }
612
+ ];
613
+ function FontFamilyExtension(config) {
614
+ return {
615
+ name: "fontFamily",
616
+ type: "mark",
617
+ options: { families: config?.families ?? DEFAULT_FONT_FAMILIES },
618
+ addMarks() {
619
+ return { fontFamily: {
620
+ attrs: { family: {} },
621
+ toDOM(mark) {
622
+ return [
623
+ "span",
624
+ { style: `font-family: ${mark.attrs.family}` },
625
+ 0
626
+ ];
627
+ },
628
+ parseDOM: [{
629
+ tag: "span[style*=\"font-family\"]",
630
+ getAttrs(dom) {
631
+ const family = dom.style.fontFamily;
632
+ return family ? { family } : false;
633
+ }
634
+ }]
635
+ } };
636
+ },
637
+ addCommands() {
638
+ return { setFontFamily: (...args) => {
639
+ const family = args[0];
640
+ return (state, dispatch) => {
641
+ const mt = state.schema.marks["fontFamily"];
642
+ if (!mt) return false;
643
+ const { from, to } = state.selection;
644
+ if (from === to) return false;
645
+ if (dispatch) {
646
+ let tr = state.tr;
647
+ tr = tr.removeMark(from, to, mt);
648
+ if (family) tr = tr.addMark(from, to, mt.create({ family }));
649
+ dispatch(tr);
650
+ }
651
+ return true;
652
+ };
653
+ } };
654
+ },
655
+ addKeyboardShortcuts() {
656
+ return {};
657
+ },
658
+ addPlugins() {
659
+ return [];
660
+ }
661
+ };
662
+ }
663
+ //#endregion
664
+ //#region node_modules/.pnpm/prosemirror-inputrules@1.5.1/node_modules/prosemirror-inputrules/dist/index.js
665
+ /**
666
+ Input rules are regular expressions describing a piece of text
667
+ that, when typed, causes something to happen. This might be
668
+ changing two dashes into an emdash, wrapping a paragraph starting
669
+ with `"> "` into a blockquote, or something entirely different.
670
+ */
671
+ var InputRule = class {
672
+ /**
673
+ Create an input rule. The rule applies when the user typed
674
+ something and the text directly in front of the cursor matches
675
+ `match`, which should end with `$`.
676
+
677
+ The `handler` can be a string, in which case the matched text, or
678
+ the first matched group in the regexp, is replaced by that
679
+ string.
680
+
681
+ Or a it can be a function, which will be called with the match
682
+ array produced by
683
+ [`RegExp.exec`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec),
684
+ as well as the start and end of the matched range, and which can
685
+ return a [transaction](https://prosemirror.net/docs/ref/#state.Transaction) that describes the
686
+ rule's effect, or null to indicate the input was not handled.
687
+ */
688
+ constructor(match, handler, options = {}) {
689
+ this.match = match;
690
+ this.match = match;
691
+ this.handler = typeof handler == "string" ? stringHandler(handler) : handler;
692
+ this.undoable = options.undoable !== false;
693
+ this.inCode = options.inCode || false;
694
+ this.inCodeMark = options.inCodeMark !== false;
695
+ }
696
+ };
697
+ function stringHandler(string) {
698
+ return function(state, match, start, end) {
699
+ let insert = string;
700
+ if (match[1]) {
701
+ let offset = match[0].lastIndexOf(match[1]);
702
+ insert += match[0].slice(offset + match[1].length);
703
+ start += offset;
704
+ let cutOff = start - end;
705
+ if (cutOff > 0) {
706
+ insert = match[0].slice(offset - cutOff, offset) + insert;
707
+ start = end;
708
+ }
709
+ }
710
+ return state.tr.insertText(insert, start, end);
711
+ };
712
+ }
713
+ var MAX_MATCH = 500;
714
+ /**
715
+ Create an input rules plugin. When enabled, it will cause text
716
+ input that matches any of the given rules to trigger the rule's
717
+ action.
718
+ */
719
+ function inputRules({ rules }) {
720
+ let plugin = new Plugin({
721
+ state: {
722
+ init() {
723
+ return null;
724
+ },
725
+ apply(tr, prev) {
726
+ let stored = tr.getMeta(this);
727
+ if (stored) return stored;
728
+ return tr.selectionSet || tr.docChanged ? null : prev;
729
+ }
730
+ },
731
+ props: {
732
+ handleTextInput(view, from, to, text) {
733
+ return run(view, from, to, text, rules, plugin);
734
+ },
735
+ handleDOMEvents: { compositionend: (view) => {
736
+ setTimeout(() => {
737
+ let { $cursor } = view.state.selection;
738
+ if ($cursor) run(view, $cursor.pos, $cursor.pos, "", rules, plugin);
739
+ });
740
+ } }
741
+ },
742
+ isInputRules: true
743
+ });
744
+ return plugin;
745
+ }
746
+ function run(view, from, to, text, rules, plugin) {
747
+ if (view.composing) return false;
748
+ let state = view.state, $from = state.doc.resolve(from);
749
+ let textBefore = $from.parent.textBetween(Math.max(0, $from.parentOffset - MAX_MATCH), $from.parentOffset, null, "") + text;
750
+ for (let i = 0; i < rules.length; i++) {
751
+ let rule = rules[i];
752
+ if (!rule.inCodeMark && $from.marks().some((m) => m.type.spec.code)) continue;
753
+ if ($from.parent.type.spec.code) {
754
+ if (!rule.inCode) continue;
755
+ } else if (rule.inCode === "only") continue;
756
+ let match = rule.match.exec(textBefore);
757
+ if (!match || match[0].length < text.length) continue;
758
+ let startPos = from - (match[0].length - text.length);
759
+ if (!rule.inCodeMark) {
760
+ let hasMark = false;
761
+ state.doc.nodesBetween(startPos, $from.pos, (node) => {
762
+ if (node.isInline && node.marks.some((m) => m.type.spec.code)) hasMark = true;
763
+ });
764
+ if (hasMark) continue;
765
+ }
766
+ let tr = rule.handler(state, match, startPos, to);
767
+ if (!tr) continue;
768
+ if (rule.undoable) tr.setMeta(plugin, {
769
+ transform: tr,
770
+ from,
771
+ to,
772
+ text
773
+ });
774
+ view.dispatch(tr);
775
+ return true;
776
+ }
777
+ return false;
778
+ }
779
+ new InputRule(/--$/, "—", { inCodeMark: false });
780
+ new InputRule(/\.\.\.$/, "…", { inCodeMark: false });
781
+ new InputRule(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(")$/, "“", { inCodeMark: false });
782
+ new InputRule(/"$/, "”", { inCodeMark: false });
783
+ new InputRule(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(')$/, "‘", { inCodeMark: false });
784
+ new InputRule(/'$/, "’", { inCodeMark: false });
785
+ //#endregion
786
+ //#region src/extensions/node-extensions.ts
787
+ /** Parse text-align from DOM element style attribute */
788
+ function parseAlignFromDOM(dom) {
789
+ const match = (dom.getAttribute("style") || "").match(/text-align:\s*(left|center|right)/);
790
+ return match ? match[1] : null;
791
+ }
792
+ /** Parse indent level from DOM element (margin-left in em) */
793
+ function parseIndentFromDOM(dom) {
794
+ const match = (dom.getAttribute("style") || "").match(/margin-left:\s*([\d.]+)em/);
795
+ if (!match) return 0;
796
+ return Math.round(parseFloat(match[1]) / 2);
797
+ }
798
+ /** Build inline style string from align + indent + lineHeight attrs */
799
+ function buildBlockStyle(attrs) {
800
+ const parts = [];
801
+ const align = attrs.textAlign;
802
+ if (align && align !== "left") parts.push(`text-align: ${align}`);
803
+ const indent = attrs.indent || 0;
804
+ if (indent > 0) parts.push(`margin-left: ${indent * 2}em`);
805
+ const lineHeight = attrs.lineHeight;
806
+ if (lineHeight) parts.push(`line-height: ${lineHeight}`);
807
+ return parts.length ? parts.join("; ") : void 0;
808
+ }
809
+ /** Helper: parse line-height from DOM element */
810
+ function parseLineHeightFromDOM(dom) {
811
+ const match = (dom.getAttribute("style") || "").match(/line-height:\s*([\d.]+)/);
812
+ if (!match) return null;
813
+ return parseFloat(match[1]);
814
+ }
815
+ function HeadingExtension() {
816
+ return {
817
+ name: "heading",
818
+ type: "node",
819
+ addNodes() {
820
+ return { heading: {
821
+ group: "block",
822
+ content: "inline*",
823
+ attrs: {
824
+ level: { default: 1 },
825
+ textAlign: { default: null },
826
+ indent: { default: 0 },
827
+ lineHeight: { default: null }
828
+ },
829
+ defining: true,
830
+ toDOM(node) {
831
+ const style = buildBlockStyle(node.attrs);
832
+ const tag = `h${node.attrs.level}`;
833
+ return style ? [
834
+ tag,
835
+ { style },
836
+ 0
837
+ ] : [tag, 0];
838
+ },
839
+ parseDOM: [
840
+ {
841
+ tag: "h1",
842
+ getAttrs: (dom) => ({
843
+ level: 1,
844
+ textAlign: parseAlignFromDOM(dom),
845
+ indent: parseIndentFromDOM(dom),
846
+ lineHeight: parseLineHeightFromDOM(dom)
847
+ })
848
+ },
849
+ {
850
+ tag: "h2",
851
+ getAttrs: (dom) => ({
852
+ level: 2,
853
+ textAlign: parseAlignFromDOM(dom),
854
+ indent: parseIndentFromDOM(dom),
855
+ lineHeight: parseLineHeightFromDOM(dom)
856
+ })
857
+ },
858
+ {
859
+ tag: "h3",
860
+ getAttrs: (dom) => ({
861
+ level: 3,
862
+ textAlign: parseAlignFromDOM(dom),
863
+ indent: parseIndentFromDOM(dom),
864
+ lineHeight: parseLineHeightFromDOM(dom)
865
+ })
866
+ },
867
+ {
868
+ tag: "h4",
869
+ getAttrs: (dom) => ({
870
+ level: 4,
871
+ textAlign: parseAlignFromDOM(dom),
872
+ indent: parseIndentFromDOM(dom),
873
+ lineHeight: parseLineHeightFromDOM(dom)
874
+ })
875
+ },
876
+ {
877
+ tag: "h5",
878
+ getAttrs: (dom) => ({
879
+ level: 5,
880
+ textAlign: parseAlignFromDOM(dom),
881
+ indent: parseIndentFromDOM(dom),
882
+ lineHeight: parseLineHeightFromDOM(dom)
883
+ })
884
+ },
885
+ {
886
+ tag: "h6",
887
+ getAttrs: (dom) => ({
888
+ level: 6,
889
+ textAlign: parseAlignFromDOM(dom),
890
+ indent: parseIndentFromDOM(dom),
891
+ lineHeight: parseLineHeightFromDOM(dom)
892
+ })
893
+ }
894
+ ]
895
+ } };
896
+ },
897
+ addCommands() {
898
+ return { heading: (...args) => {
899
+ const level = args[0] ?? 1;
900
+ return (state, dispatch) => {
901
+ const headingType = state.schema.nodes["heading"];
902
+ const paraType = state.schema.nodes["paragraph"];
903
+ if (!headingType || !paraType) return false;
904
+ const { $from } = state.selection;
905
+ const parent = $from.parent;
906
+ if (parent.type === headingType && parent.attrs.level === level) return setBlockType(paraType)(state, dispatch);
907
+ return setBlockType(headingType, { level })(state, dispatch);
908
+ };
909
+ } };
910
+ },
911
+ addKeyboardShortcuts() {
912
+ const shortcuts = {};
913
+ for (let i = 1; i <= 6; i++) {
914
+ const level = i;
915
+ shortcuts[`Mod-Alt-${i}`] = (state, dispatch) => {
916
+ const headingType = state.schema.nodes["heading"];
917
+ const paraType = state.schema.nodes["paragraph"];
918
+ if (!headingType || !paraType) return false;
919
+ const { $from } = state.selection;
920
+ const parent = $from.parent;
921
+ if (parent.type === headingType && parent.attrs.level === level) return setBlockType(paraType)(state, dispatch);
922
+ return setBlockType(headingType, { level })(state, dispatch);
923
+ };
924
+ }
925
+ return shortcuts;
926
+ },
927
+ addPlugins() {
928
+ return [inputRules({ rules: [new InputRule(/^(#{1,6})\s$/, (state, match, start, end) => {
929
+ const level = match[1].length;
930
+ const nt = state.schema.nodes["heading"];
931
+ if (!nt) return null;
932
+ const tr = state.tr;
933
+ tr.delete(start, end);
934
+ const range = tr.doc.resolve(start).blockRange();
935
+ if (!range) return null;
936
+ tr.setBlockType(range.start, range.end, nt, { level });
937
+ return tr;
938
+ })] })];
939
+ }
940
+ };
941
+ }
942
+ function BlockquoteExtension() {
943
+ return {
944
+ name: "blockquote",
945
+ type: "node",
946
+ addNodes() {
947
+ return { blockquote: {
948
+ group: "block",
949
+ content: "block+",
950
+ defining: true,
951
+ toDOM() {
952
+ return ["blockquote", 0];
953
+ },
954
+ parseDOM: [{ tag: "blockquote" }]
955
+ } };
956
+ },
957
+ addCommands() {
958
+ return { blockquote: () => (state, dispatch) => {
959
+ const nt = state.schema.nodes["blockquote"];
960
+ if (!nt) return false;
961
+ return wrapIn(nt)(state, dispatch);
962
+ } };
963
+ },
964
+ addKeyboardShortcuts() {
965
+ return { "Mod-Shift-b": (state, dispatch) => {
966
+ const nt = state.schema.nodes["blockquote"];
967
+ if (!nt) return false;
968
+ return wrapIn(nt)(state, dispatch);
969
+ } };
970
+ }
971
+ };
972
+ }
973
+ function BulletListExtension() {
974
+ return {
975
+ name: "bullet_list",
976
+ type: "node",
977
+ addNodes() {
978
+ return {
979
+ bullet_list: {
980
+ group: "block",
981
+ content: "list_item+",
982
+ toDOM() {
983
+ return ["ul", 0];
984
+ },
985
+ parseDOM: [{ tag: "ul" }]
986
+ },
987
+ list_item: {
988
+ content: "paragraph block*",
989
+ toDOM() {
990
+ return ["li", 0];
991
+ },
992
+ parseDOM: [{ tag: "li" }]
993
+ }
994
+ };
995
+ },
996
+ addCommands() {
997
+ return { bulletList: () => (state, dispatch) => {
998
+ const nt = state.schema.nodes["bullet_list"];
999
+ if (!nt) return false;
1000
+ return wrapIn(nt)(state, dispatch);
1001
+ } };
1002
+ },
1003
+ addKeyboardShortcuts() {
1004
+ return { Enter: (state, dispatch) => {
1005
+ const nt = state.schema.nodes["list_item"];
1006
+ if (!nt) return false;
1007
+ return splitListItem(nt)(state, dispatch);
1008
+ } };
1009
+ }
1010
+ };
1011
+ }
1012
+ function OrderedListExtension() {
1013
+ return {
1014
+ name: "ordered_list",
1015
+ type: "node",
1016
+ addNodes() {
1017
+ return { ordered_list: {
1018
+ group: "block",
1019
+ content: "list_item+",
1020
+ attrs: { order: { default: 1 } },
1021
+ toDOM(node) {
1022
+ return node.attrs.order === 1 ? ["ol", 0] : [
1023
+ "ol",
1024
+ { start: node.attrs.order },
1025
+ 0
1026
+ ];
1027
+ },
1028
+ parseDOM: [{
1029
+ tag: "ol",
1030
+ getAttrs(dom) {
1031
+ return { order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1 };
1032
+ }
1033
+ }]
1034
+ } };
1035
+ },
1036
+ addCommands() {
1037
+ return { orderedList: () => (state, dispatch) => {
1038
+ const nt = state.schema.nodes["ordered_list"];
1039
+ if (!nt) return false;
1040
+ return wrapIn(nt)(state, dispatch);
1041
+ } };
1042
+ },
1043
+ addKeyboardShortcuts() {
1044
+ return { Enter: (state, dispatch) => {
1045
+ const nt = state.schema.nodes["list_item"];
1046
+ if (!nt) return false;
1047
+ return splitListItem(nt)(state, dispatch);
1048
+ } };
1049
+ }
1050
+ };
1051
+ }
1052
+ function TaskListExtension() {
1053
+ return {
1054
+ name: "task_list",
1055
+ type: "node",
1056
+ addNodes() {
1057
+ return {
1058
+ task_list: {
1059
+ group: "block",
1060
+ content: "task_item+",
1061
+ toDOM() {
1062
+ return [
1063
+ "ul",
1064
+ { class: "mi-task-list" },
1065
+ 0
1066
+ ];
1067
+ },
1068
+ parseDOM: [{ tag: "ul.mi-task-list" }, { tag: "ul[data-task-list]" }]
1069
+ },
1070
+ task_item: {
1071
+ content: "paragraph block*",
1072
+ attrs: { checked: { default: false } },
1073
+ toDOM(node) {
1074
+ return [
1075
+ "li",
1076
+ {
1077
+ "data-task-item": "",
1078
+ class: "mi-task-item"
1079
+ },
1080
+ ["input", {
1081
+ type: "checkbox",
1082
+ checked: node.attrs.checked ? "checked" : null,
1083
+ "data-checked": String(node.attrs.checked)
1084
+ }],
1085
+ [
1086
+ "div",
1087
+ { class: "mi-task-content" },
1088
+ 0
1089
+ ]
1090
+ ];
1091
+ },
1092
+ parseDOM: [{
1093
+ tag: "li[data-task-item]",
1094
+ getAttrs(dom) {
1095
+ return { checked: dom.querySelector("input[type=\"checkbox\"]")?.checked ?? false };
1096
+ }
1097
+ }]
1098
+ }
1099
+ };
1100
+ },
1101
+ addCommands() {
1102
+ return {
1103
+ taskList: () => (state, dispatch) => {
1104
+ const nt = state.schema.nodes["task_list"];
1105
+ if (!nt) return false;
1106
+ return wrapIn(nt)(state, dispatch);
1107
+ },
1108
+ toggleTaskItem: () => (state, dispatch) => {
1109
+ const { $from } = state.selection;
1110
+ let taskItemPos = -1;
1111
+ let taskItemNode = null;
1112
+ state.doc.nodesBetween($from.pos, $from.pos, (node, pos) => {
1113
+ if (node.type.name === "task_item") {
1114
+ taskItemPos = pos;
1115
+ taskItemNode = node;
1116
+ return false;
1117
+ }
1118
+ });
1119
+ for (let d = $from.depth; d > 0; d--) {
1120
+ const node = $from.node(d);
1121
+ if (node.type.name === "task_item") {
1122
+ taskItemPos = $from.before(d);
1123
+ taskItemNode = node;
1124
+ break;
1125
+ }
1126
+ }
1127
+ if (taskItemPos < 0 || !taskItemNode) return false;
1128
+ if (dispatch) {
1129
+ const tr = state.tr;
1130
+ tr.setNodeMarkup(taskItemPos, null, {
1131
+ ...taskItemNode.attrs,
1132
+ checked: !taskItemNode.attrs.checked
1133
+ });
1134
+ dispatch(tr);
1135
+ }
1136
+ return true;
1137
+ }
1138
+ };
1139
+ },
1140
+ addKeyboardShortcuts() {
1141
+ return { Enter: (state, dispatch) => {
1142
+ const nt = state.schema.nodes["task_item"];
1143
+ if (!nt) return false;
1144
+ return splitListItem(nt)(state, dispatch);
1145
+ } };
1146
+ },
1147
+ addPlugins() {
1148
+ return [new Plugin({ props: { handleDOMEvents: { click(view, event) {
1149
+ const target = event.target;
1150
+ if (target.tagName === "INPUT" && target.type === "checkbox" && target.closest(".mi-task-item")) {
1151
+ event.preventDefault();
1152
+ const li = target.closest(".mi-task-item");
1153
+ if (!li) return false;
1154
+ const pos = view.posAtDOM(li, 0);
1155
+ const node = view.state.doc.nodeAt(pos);
1156
+ if (!node || node.type.name !== "task_item") return false;
1157
+ const tr = view.state.tr;
1158
+ tr.setNodeMarkup(pos, null, {
1159
+ ...node.attrs,
1160
+ checked: !node.attrs.checked
1161
+ });
1162
+ view.dispatch(tr);
1163
+ return true;
1164
+ }
1165
+ return false;
1166
+ } } } })];
1167
+ }
1168
+ };
1169
+ }
1170
+ function CodeBlockExtension() {
1171
+ return {
1172
+ name: "code_block",
1173
+ type: "node",
1174
+ addNodes() {
1175
+ return { code_block: {
1176
+ group: "block",
1177
+ content: "text*",
1178
+ marks: "",
1179
+ code: true,
1180
+ defining: true,
1181
+ toDOM() {
1182
+ return ["pre", ["code", 0]];
1183
+ },
1184
+ parseDOM: [{
1185
+ tag: "pre",
1186
+ preserveWhitespace: "full"
1187
+ }]
1188
+ } };
1189
+ },
1190
+ addCommands() {
1191
+ return { codeBlock: () => (state, dispatch) => {
1192
+ const nt = state.schema.nodes["code_block"];
1193
+ if (!nt) return false;
1194
+ return setBlockType(nt)(state, dispatch);
1195
+ } };
1196
+ },
1197
+ addPlugins() {
1198
+ return [inputRules({ rules: [new InputRule(/^```$/, (state, _match, start, end) => {
1199
+ const nt = state.schema.nodes["code_block"];
1200
+ if (!nt) return null;
1201
+ const tr = state.tr;
1202
+ tr.delete(start, end);
1203
+ const range = tr.doc.resolve(start).blockRange();
1204
+ if (!range) return null;
1205
+ tr.setBlockType(range.start, range.end, nt);
1206
+ return tr;
1207
+ })] })];
1208
+ }
1209
+ };
1210
+ }
1211
+ function HorizontalRuleExtension() {
1212
+ return {
1213
+ name: "horizontal_rule",
1214
+ type: "node",
1215
+ addNodes() {
1216
+ return { horizontal_rule: {
1217
+ group: "block",
1218
+ toDOM() {
1219
+ return ["hr"];
1220
+ },
1221
+ parseDOM: [{ tag: "hr" }]
1222
+ } };
1223
+ },
1224
+ addCommands() {
1225
+ return { horizontalRule: () => (state, dispatch) => {
1226
+ const nt = state.schema.nodes["horizontal_rule"];
1227
+ if (!nt) return false;
1228
+ const tr = state.tr;
1229
+ tr.replaceSelectionWith(nt.create());
1230
+ if (dispatch) dispatch(tr);
1231
+ return true;
1232
+ } };
1233
+ }
1234
+ };
1235
+ }
1236
+ function HardBreakExtension() {
1237
+ return {
1238
+ name: "hard_break",
1239
+ type: "node",
1240
+ addNodes() {
1241
+ return { hard_break: {
1242
+ group: "inline",
1243
+ inline: true,
1244
+ selectable: false,
1245
+ toDOM() {
1246
+ return ["br"];
1247
+ },
1248
+ parseDOM: [{ tag: "br" }]
1249
+ } };
1250
+ },
1251
+ addKeyboardShortcuts() {
1252
+ const insertHardBreak = (state, dispatch) => {
1253
+ const nt = state.schema.nodes["hard_break"];
1254
+ if (!nt) return false;
1255
+ const tr = state.tr.replaceSelectionWith(nt.create());
1256
+ if (dispatch) dispatch(tr.scrollIntoView());
1257
+ return true;
1258
+ };
1259
+ return {
1260
+ "Mod-Enter": insertHardBreak,
1261
+ "Shift-Enter": insertHardBreak
1262
+ };
1263
+ }
1264
+ };
1265
+ }
1266
+ var MIN_IMG_SIZE = 30;
1267
+ var IMG_PRESET_SIZES = [
1268
+ 25,
1269
+ 50,
1270
+ 75,
1271
+ 100
1272
+ ];
1273
+ var ImageNodeView = class {
1274
+ constructor(node, view, getPos) {
1275
+ this.selected = false;
1276
+ this._onDocMouseDown = null;
1277
+ this.node = node;
1278
+ this.view = view;
1279
+ this.getPos = getPos;
1280
+ const { src, alt, title, width, height, align } = node.attrs;
1281
+ this.wrapper = document.createElement("span");
1282
+ this.wrapper.className = `mi-img-wrap mi-img-align-${align || "center"}`;
1283
+ this.wrapper.style.display = "inline-block";
1284
+ this.wrapper.style.position = "relative";
1285
+ this.wrapper.style.lineHeight = "0";
1286
+ this.wrapper.style.verticalAlign = "middle";
1287
+ if (width) this.wrapper.style.width = typeof width === "number" ? `${width}px` : String(width);
1288
+ this.img = document.createElement("img");
1289
+ this.img.src = src || "";
1290
+ if (alt) this.img.alt = alt;
1291
+ if (title) this.img.title = title;
1292
+ if (width) this.img.style.width = typeof width === "number" ? `${width}px` : String(width);
1293
+ if (height) this.img.style.height = typeof height === "number" ? `${height}px` : String(height);
1294
+ this.img.style.display = "block";
1295
+ this.img.style.maxWidth = "100%";
1296
+ this.img.style.height = height ? typeof height === "number" ? `${height}px` : String(height) : "auto";
1297
+ this.img.style.borderRadius = "6px";
1298
+ this.img.style.cursor = "pointer";
1299
+ this.img.draggable = false;
1300
+ this.handles = document.createElement("div");
1301
+ this.handles.style.cssText = "display:none;position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;";
1302
+ [
1303
+ "nw",
1304
+ "ne",
1305
+ "sw",
1306
+ "se"
1307
+ ].forEach((dir) => {
1308
+ const h = document.createElement("div");
1309
+ const isTop = dir.includes("n");
1310
+ const isLeft = dir.includes("w");
1311
+ h.style.cssText = `
1312
+ position:absolute;width:10px;height:10px;background:#fff;border:2px solid #1a73e8;
1313
+ border-radius:2px;pointer-events:all;cursor:${dir}-resize;z-index:10;
1314
+ top:${isTop ? "-5px" : "auto"};bottom:${!isTop ? "-5px" : "auto"};
1315
+ left:${isLeft ? "-5px" : "auto"};right:${!isLeft ? "-5px" : "auto"};
1316
+ `;
1317
+ h.addEventListener("mousedown", (e) => {
1318
+ e.preventDefault();
1319
+ e.stopPropagation();
1320
+ this.startResize(e, dir);
1321
+ });
1322
+ this.handles.appendChild(h);
1323
+ });
1324
+ this.toolbar = document.createElement("div");
1325
+ this.toolbar.style.cssText = "display:none;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#fff;border:1px solid #e0e0e0;border-radius:8px;padding:3px 6px;box-shadow:0 2px 10px rgba(0,0,0,0.12);z-index:20;white-space:nowrap;line-height:1;";
1326
+ this.buildToolbar();
1327
+ this.wrapper.appendChild(this.img);
1328
+ this.wrapper.appendChild(this.handles);
1329
+ this.wrapper.appendChild(this.toolbar);
1330
+ this.dom = this.wrapper;
1331
+ this.wrapper.addEventListener("mousedown", (e) => {
1332
+ if (e.target === this.img || e.target === this.wrapper) e.preventDefault();
1333
+ });
1334
+ this.wrapper.addEventListener("click", (e) => {
1335
+ e.preventDefault();
1336
+ e.stopPropagation();
1337
+ this.select();
1338
+ });
1339
+ const onDocMouseDown = (e) => {
1340
+ if (!this.wrapper.contains(e.target)) this.deselect();
1341
+ };
1342
+ document.addEventListener("mousedown", onDocMouseDown);
1343
+ this._onDocMouseDown = onDocMouseDown;
1344
+ }
1345
+ buildToolbar() {
1346
+ this.toolbar.innerHTML = "";
1347
+ const sizeGroup = document.createElement("span");
1348
+ sizeGroup.style.cssText = "display:inline-flex;gap:2px;align-items:center;";
1349
+ IMG_PRESET_SIZES.forEach((pct) => {
1350
+ const btn = document.createElement("button");
1351
+ btn.textContent = `${pct}%`;
1352
+ btn.style.cssText = "padding:2px 6px;border:none;border-radius:3px;background:transparent;color:#555;cursor:pointer;font-size:11px;";
1353
+ btn.addEventListener("mousedown", (e) => {
1354
+ e.preventDefault();
1355
+ e.stopPropagation();
1356
+ });
1357
+ btn.addEventListener("click", (e) => {
1358
+ e.preventDefault();
1359
+ e.stopPropagation();
1360
+ this.applyPreset(pct);
1361
+ });
1362
+ btn.addEventListener("mouseenter", () => {
1363
+ btn.style.background = "#f0f0f0";
1364
+ });
1365
+ btn.addEventListener("mouseleave", () => {
1366
+ btn.style.background = "transparent";
1367
+ });
1368
+ sizeGroup.appendChild(btn);
1369
+ });
1370
+ const divider = document.createElement("span");
1371
+ divider.style.cssText = "display:inline-block;width:1px;height:16px;background:#e0e0e0;margin:0 4px;vertical-align:middle;";
1372
+ const alignGroup = document.createElement("span");
1373
+ alignGroup.style.cssText = "display:inline-flex;gap:2px;align-items:center;";
1374
+ const currentAlign = this.node.attrs.align || "center";
1375
+ [
1376
+ ["left", "◀"],
1377
+ ["center", "◆"],
1378
+ ["right", "▶"]
1379
+ ].forEach(([val, icon]) => {
1380
+ const btn = document.createElement("button");
1381
+ btn.textContent = icon;
1382
+ btn.style.cssText = `padding:2px 5px;border:none;border-radius:3px;background:${currentAlign === val ? "#e8f0fe" : "transparent"};color:${currentAlign === val ? "#1a73e8" : "#555"};cursor:pointer;font-size:11px;`;
1383
+ btn.addEventListener("mousedown", (e) => {
1384
+ e.preventDefault();
1385
+ e.stopPropagation();
1386
+ });
1387
+ btn.addEventListener("click", (e) => {
1388
+ e.preventDefault();
1389
+ e.stopPropagation();
1390
+ this.setAlign(val);
1391
+ });
1392
+ alignGroup.appendChild(btn);
1393
+ });
1394
+ this.toolbar.appendChild(sizeGroup);
1395
+ this.toolbar.appendChild(divider);
1396
+ this.toolbar.appendChild(alignGroup);
1397
+ }
1398
+ select() {
1399
+ if (this.selected) return;
1400
+ this.selected = true;
1401
+ this.handles.style.display = "";
1402
+ this.toolbar.style.display = "";
1403
+ this.img.style.outline = "2px solid rgba(26,115,232,0.5)";
1404
+ this.img.style.outlineOffset = "1px";
1405
+ const pos = this.getPos();
1406
+ if (pos === void 0) return;
1407
+ try {
1408
+ const tr = this.view.state.tr.setSelection(NodeSelection.create(this.view.state.doc, pos));
1409
+ this.view.dispatch(tr);
1410
+ } catch {}
1411
+ }
1412
+ deselect() {
1413
+ if (!this.selected) return;
1414
+ this.selected = false;
1415
+ this.handles.style.display = "none";
1416
+ this.toolbar.style.display = "none";
1417
+ this.img.style.outline = "";
1418
+ this.img.style.outlineOffset = "";
1419
+ }
1420
+ startResize(e, dir) {
1421
+ const startX = e.clientX;
1422
+ const startY = e.clientY;
1423
+ const startW = this.img.offsetWidth;
1424
+ const startH = this.img.offsetHeight;
1425
+ const aspect = startW / startH;
1426
+ const onMove = (ev) => {
1427
+ let dx = ev.clientX - startX;
1428
+ let dy = ev.clientY - startY;
1429
+ if (dir.includes("w")) dx = -dx;
1430
+ if (dir.includes("n")) dy = -dy;
1431
+ const scale = Math.max(dx / startW, dy / startH);
1432
+ let newW = Math.max(MIN_IMG_SIZE, Math.round(startW * (1 + scale)));
1433
+ let newH = Math.round(newW / aspect);
1434
+ if (newH < MIN_IMG_SIZE) {
1435
+ newH = MIN_IMG_SIZE;
1436
+ newW = Math.round(newH * aspect);
1437
+ }
1438
+ this.img.style.width = `${newW}px`;
1439
+ this.img.style.height = `${newH}px`;
1440
+ this.wrapper.style.width = `${newW}px`;
1441
+ };
1442
+ const onUp = () => {
1443
+ document.removeEventListener("mousemove", onMove);
1444
+ document.removeEventListener("mouseup", onUp);
1445
+ const pos = this.getPos();
1446
+ if (pos === void 0) return;
1447
+ const cur = this.view.state.doc.nodeAt(pos);
1448
+ if (!cur || cur.type.name !== "image") return;
1449
+ const tr = this.view.state.tr.setNodeMarkup(pos, null, {
1450
+ ...cur.attrs,
1451
+ width: this.img.offsetWidth,
1452
+ height: this.img.offsetHeight
1453
+ });
1454
+ this.view.dispatch(tr);
1455
+ };
1456
+ document.addEventListener("mousemove", onMove);
1457
+ document.addEventListener("mouseup", onUp);
1458
+ }
1459
+ applyPreset(pct) {
1460
+ const pos = this.getPos();
1461
+ if (pos === void 0) return;
1462
+ const cur = this.view.state.doc.nodeAt(pos);
1463
+ if (!cur || cur.type.name !== "image") return;
1464
+ const baseW = this.img.naturalWidth || this.img.offsetWidth;
1465
+ const baseH = this.img.naturalHeight || this.img.offsetHeight;
1466
+ const newW = Math.max(MIN_IMG_SIZE, Math.round(baseW * pct / 100));
1467
+ const newH = Math.round(newW / (baseW / baseH));
1468
+ this.img.style.width = `${newW}px`;
1469
+ this.img.style.height = `${newH}px`;
1470
+ this.wrapper.style.width = `${newW}px`;
1471
+ const tr = this.view.state.tr.setNodeMarkup(pos, null, {
1472
+ ...cur.attrs,
1473
+ width: newW,
1474
+ height: newH
1475
+ });
1476
+ this.view.dispatch(tr);
1477
+ }
1478
+ setAlign(align) {
1479
+ const pos = this.getPos();
1480
+ if (pos === void 0) return;
1481
+ const cur = this.view.state.doc.nodeAt(pos);
1482
+ if (!cur || cur.type.name !== "image") return;
1483
+ this.wrapper.className = `mi-img-wrap mi-img-align-${align}`;
1484
+ this.buildToolbar();
1485
+ const tr = this.view.state.tr.setNodeMarkup(pos, null, {
1486
+ ...cur.attrs,
1487
+ align
1488
+ });
1489
+ this.view.dispatch(tr);
1490
+ }
1491
+ update(node) {
1492
+ if (node.type.name !== "image") return false;
1493
+ this.node = node;
1494
+ const { src, alt, title, width, height, align } = node.attrs;
1495
+ this.wrapper.className = `mi-img-wrap mi-img-align-${align || "center"}`;
1496
+ if (this.img.src !== src) this.img.src = src || "";
1497
+ if (alt) this.img.alt = alt;
1498
+ if (title) this.img.title = title;
1499
+ if (width) {
1500
+ const w = typeof width === "number" ? `${width}px` : String(width);
1501
+ this.img.style.width = w;
1502
+ this.wrapper.style.width = w;
1503
+ } else {
1504
+ this.img.style.width = "";
1505
+ this.wrapper.style.width = "";
1506
+ }
1507
+ if (height) this.img.style.height = typeof height === "number" ? `${height}px` : String(height);
1508
+ else this.img.style.height = "";
1509
+ this.buildToolbar();
1510
+ return true;
1511
+ }
1512
+ stopEvent() {
1513
+ return true;
1514
+ }
1515
+ ignoreMutation() {
1516
+ return true;
1517
+ }
1518
+ destroy() {
1519
+ if (this._onDocMouseDown) {
1520
+ document.removeEventListener("mousedown", this._onDocMouseDown);
1521
+ this._onDocMouseDown = null;
1522
+ }
1523
+ this.deselect();
1524
+ }
1525
+ };
1526
+ function ImageExtension() {
1527
+ return {
1528
+ name: "image",
1529
+ type: "node",
1530
+ addNodes() {
1531
+ return { image: {
1532
+ group: "inline",
1533
+ inline: true,
1534
+ attrs: {
1535
+ src: {},
1536
+ alt: { default: null },
1537
+ title: { default: null },
1538
+ width: { default: null },
1539
+ height: { default: null },
1540
+ align: { default: "center" }
1541
+ },
1542
+ selectable: true,
1543
+ draggable: true,
1544
+ toDOM(node) {
1545
+ const { src, alt, title, width, height } = node.attrs;
1546
+ const attrs = {
1547
+ src: src || "",
1548
+ draggable: "false"
1549
+ };
1550
+ if (alt) attrs.alt = alt;
1551
+ if (title) attrs.title = title;
1552
+ if (width) attrs.style = `width: ${typeof width === "number" ? `${width}px` : String(width)}; height: ${height ? typeof height === "number" ? `${height}px` : String(height) : "auto"};`;
1553
+ return ["img", attrs];
1554
+ },
1555
+ parseDOM: [{
1556
+ tag: "img[src]",
1557
+ getAttrs(dom) {
1558
+ const el = dom;
1559
+ const style = el.getAttribute("style") || "";
1560
+ const wMatch = style.match(/width:\s*(\d+)px/);
1561
+ const hMatch = style.match(/height:\s*(\d+)px/);
1562
+ return {
1563
+ src: el.getAttribute("src"),
1564
+ alt: el.getAttribute("alt"),
1565
+ title: el.getAttribute("title"),
1566
+ width: wMatch ? parseInt(wMatch[1]) : el.getAttribute("width") ? parseInt(el.getAttribute("width")) : null,
1567
+ height: hMatch ? parseInt(hMatch[1]) : el.getAttribute("height") ? parseInt(el.getAttribute("height")) : null,
1568
+ align: "center"
1569
+ };
1570
+ }
1571
+ }]
1572
+ } };
1573
+ },
1574
+ addCommands() {
1575
+ return {
1576
+ setImageSize: (...args) => {
1577
+ const width = args[0];
1578
+ const height = args[1];
1579
+ return (state, dispatch) => {
1580
+ const { $from } = state.selection;
1581
+ let imagePos = -1;
1582
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === "image") {
1583
+ imagePos = $from.before(d);
1584
+ break;
1585
+ }
1586
+ if (imagePos < 0) return false;
1587
+ const node = state.doc.nodeAt(imagePos);
1588
+ if (!node || node.type.name !== "image") return false;
1589
+ const aspect = node.attrs.width && node.attrs.height ? node.attrs.width / node.attrs.height : 1;
1590
+ const finalH = height ?? Math.round(width / aspect);
1591
+ if (dispatch) dispatch(state.tr.setNodeMarkup(imagePos, null, {
1592
+ ...node.attrs,
1593
+ width,
1594
+ height: finalH
1595
+ }));
1596
+ return true;
1597
+ };
1598
+ },
1599
+ setImageAlign: (...args) => {
1600
+ const align = args[0];
1601
+ return (state, dispatch) => {
1602
+ const { $from } = state.selection;
1603
+ let imagePos = -1;
1604
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === "image") {
1605
+ imagePos = $from.before(d);
1606
+ break;
1607
+ }
1608
+ if (imagePos < 0) return false;
1609
+ const node = state.doc.nodeAt(imagePos);
1610
+ if (!node || node.type.name !== "image") return false;
1611
+ if (dispatch) dispatch(state.tr.setNodeMarkup(imagePos, null, {
1612
+ ...node.attrs,
1613
+ align
1614
+ }));
1615
+ return true;
1616
+ };
1617
+ }
1618
+ };
1619
+ },
1620
+ addPlugins() {
1621
+ return [new Plugin({
1622
+ key: new PluginKey("imageNodeView"),
1623
+ props: { nodeViews: { image(node, view, getPos) {
1624
+ return new ImageNodeView(node, view, getPos);
1625
+ } } }
1626
+ })];
1627
+ }
1628
+ };
1629
+ }
1630
+ function CardExtension() {
1631
+ return {
1632
+ name: "card",
1633
+ type: "node",
1634
+ addNodes() {
1635
+ return { card: {
1636
+ group: "block",
1637
+ content: "block+",
1638
+ defining: true,
1639
+ attrs: { title: { default: "" } },
1640
+ toDOM(node) {
1641
+ return [
1642
+ "div",
1643
+ {
1644
+ class: "mi-card",
1645
+ "data-card-title": node.attrs.title || ""
1646
+ },
1647
+ [
1648
+ "div",
1649
+ { class: "mi-card-header" },
1650
+ [
1651
+ "div",
1652
+ {
1653
+ class: "mi-card-title",
1654
+ contenteditable: "false",
1655
+ "data-card-title": ""
1656
+ },
1657
+ node.attrs.title || "卡片标题"
1658
+ ]
1659
+ ],
1660
+ [
1661
+ "div",
1662
+ { class: "mi-card-body" },
1663
+ 0
1664
+ ]
1665
+ ];
1666
+ },
1667
+ parseDOM: [{
1668
+ tag: "div.mi-card",
1669
+ getAttrs(dom) {
1670
+ const el = dom;
1671
+ return { title: el.querySelector(".mi-card-title")?.textContent || el.getAttribute("data-card-title") || "" };
1672
+ }
1673
+ }]
1674
+ } };
1675
+ },
1676
+ addCommands() {
1677
+ return { card: (...args) => {
1678
+ const title = args[0] ?? "";
1679
+ return (state, dispatch) => {
1680
+ const nt = state.schema.nodes["card"];
1681
+ const paraType = state.schema.nodes["paragraph"];
1682
+ if (!nt || !paraType) return false;
1683
+ const { from, to } = state.selection;
1684
+ const para = paraType.createAndFill();
1685
+ const card = nt.create({ title }, para);
1686
+ if (dispatch) dispatch(state.tr.replaceWith(from, to, card).scrollIntoView());
1687
+ return true;
1688
+ };
1689
+ } };
1690
+ },
1691
+ addPlugins() {
1692
+ return [new Plugin({ props: { handleDOMEvents: { click(view, event) {
1693
+ const titleEl = event.target.closest(".mi-card-title");
1694
+ if (!titleEl) return false;
1695
+ const card = titleEl.closest(".mi-card");
1696
+ if (!card) return false;
1697
+ event.preventDefault();
1698
+ const pos = view.posAtDOM(card, 0);
1699
+ const tr = view.state.tr;
1700
+ const resolved = tr.doc.resolve(pos);
1701
+ let nodePos = -1;
1702
+ for (let d = resolved.depth; d >= 0; d--) if (resolved.node(d).type.name === "card") {
1703
+ nodePos = resolved.before(d);
1704
+ break;
1705
+ }
1706
+ if (nodePos < 0) return false;
1707
+ const node = tr.doc.nodeAt(nodePos);
1708
+ if (!node || node.type.name !== "card") return false;
1709
+ const currentTitle = node.attrs.title;
1710
+ const newTitle = prompt("编辑卡片标题:", currentTitle);
1711
+ if (newTitle !== null && newTitle !== currentTitle) {
1712
+ tr.setNodeMarkup(nodePos, null, {
1713
+ ...node.attrs,
1714
+ title: newTitle
1715
+ });
1716
+ view.dispatch(tr);
1717
+ }
1718
+ return true;
1719
+ } } } })];
1720
+ }
1721
+ };
1722
+ }
1723
+ function ProductCardExtension() {
1724
+ return {
1725
+ name: "product_card",
1726
+ type: "node",
1727
+ addNodes() {
1728
+ return { product_card: {
1729
+ group: "block",
1730
+ atom: true,
1731
+ attrs: {
1732
+ productName: { default: "" },
1733
+ productImage: { default: "" },
1734
+ productPrice: { default: "" },
1735
+ productOriginalPrice: { default: "" },
1736
+ productDescription: { default: "" },
1737
+ productLink: { default: "" },
1738
+ productTags: { default: "" }
1739
+ },
1740
+ toDOM(node) {
1741
+ const a = node.attrs;
1742
+ const tags = a.productTags ? a.productTags.split(",").map((t) => t.trim()).filter(Boolean) : [];
1743
+ const imgWrap = a.productImage ? [
1744
+ "div",
1745
+ { class: "mi-product-card-img-wrap" },
1746
+ ["img", {
1747
+ src: a.productImage,
1748
+ alt: a.productName,
1749
+ class: "mi-product-card-img",
1750
+ draggable: "false"
1751
+ }]
1752
+ ] : [
1753
+ "div",
1754
+ { class: "mi-product-card-img-wrap mi-product-card-img-empty" },
1755
+ [
1756
+ "span",
1757
+ { class: "mi-product-card-img-placeholder" },
1758
+ "📦"
1759
+ ]
1760
+ ];
1761
+ const contentChildren = [[
1762
+ "div",
1763
+ { class: "mi-product-card-title" },
1764
+ a.productName || "商品名称"
1765
+ ]];
1766
+ if (a.productDescription) contentChildren.push([
1767
+ "div",
1768
+ { class: "mi-product-card-desc" },
1769
+ a.productDescription
1770
+ ]);
1771
+ if (tags.length > 0) {
1772
+ const tagElements = tags.map((tag) => [
1773
+ "span",
1774
+ { class: "mi-product-card-tag" },
1775
+ tag
1776
+ ]);
1777
+ contentChildren.push([
1778
+ "div",
1779
+ { class: "mi-product-card-tags" },
1780
+ ...tagElements
1781
+ ]);
1782
+ }
1783
+ if (a.productPrice || a.productOriginalPrice) {
1784
+ const priceElements = [];
1785
+ if (a.productPrice) priceElements.push([
1786
+ "span",
1787
+ { class: "mi-product-card-price" },
1788
+ `¥${a.productPrice}`
1789
+ ]);
1790
+ if (a.productOriginalPrice) priceElements.push([
1791
+ "span",
1792
+ { class: "mi-product-card-original-price" },
1793
+ `¥${a.productOriginalPrice}`
1794
+ ]);
1795
+ contentChildren.push([
1796
+ "div",
1797
+ { class: "mi-product-card-price-row" },
1798
+ ...priceElements
1799
+ ]);
1800
+ }
1801
+ if (a.productLink) contentChildren.push([
1802
+ "a",
1803
+ {
1804
+ class: "mi-product-card-link",
1805
+ href: a.productLink,
1806
+ target: "_blank",
1807
+ rel: "noopener noreferrer"
1808
+ },
1809
+ "查看详情 →"
1810
+ ]);
1811
+ return [
1812
+ "div",
1813
+ {
1814
+ class: "mi-product-card",
1815
+ contenteditable: "false"
1816
+ },
1817
+ [
1818
+ "div",
1819
+ { class: "mi-product-card-inner" },
1820
+ imgWrap,
1821
+ [
1822
+ "div",
1823
+ { class: "mi-product-card-content" },
1824
+ ...contentChildren
1825
+ ]
1826
+ ],
1827
+ [
1828
+ "button",
1829
+ {
1830
+ class: "mi-product-card-delete",
1831
+ title: "删除卡片"
1832
+ },
1833
+ "×"
1834
+ ]
1835
+ ];
1836
+ },
1837
+ parseDOM: [{
1838
+ tag: "div.mi-product-card",
1839
+ getAttrs(dom) {
1840
+ const el = dom;
1841
+ const get = (sel) => el.querySelector(sel)?.textContent?.trim() || "";
1842
+ const img = el.querySelector(".mi-product-card-img");
1843
+ const link = el.querySelector(".mi-product-card-link");
1844
+ const tags = Array.from(el.querySelectorAll(".mi-product-card-tag")).map((t) => t.textContent?.trim() || "").filter(Boolean).join(",");
1845
+ return {
1846
+ productName: get(".mi-product-card-title"),
1847
+ productImage: img?.getAttribute("src") || "",
1848
+ productPrice: get(".mi-product-card-price")?.replace(/^¥/, "") || "",
1849
+ productOriginalPrice: get(".mi-product-card-original-price")?.replace(/^¥/, "") || "",
1850
+ productDescription: get(".mi-product-card-desc"),
1851
+ productLink: link?.getAttribute("href") || "",
1852
+ productTags: tags
1853
+ };
1854
+ }
1855
+ }]
1856
+ } };
1857
+ },
1858
+ addCommands() {
1859
+ return { insertProductCard: (...args) => {
1860
+ const attrs = args[0];
1861
+ return (state, dispatch) => {
1862
+ const nt = state.schema.nodes["product_card"];
1863
+ if (!nt) return false;
1864
+ const { from, to } = state.selection;
1865
+ const node = nt.create({
1866
+ productName: attrs?.productName ?? "",
1867
+ productImage: attrs?.productImage ?? "",
1868
+ productPrice: attrs?.productPrice ?? "",
1869
+ productOriginalPrice: attrs?.productOriginalPrice ?? "",
1870
+ productDescription: attrs?.productDescription ?? "",
1871
+ productLink: attrs?.productLink ?? "",
1872
+ productTags: attrs?.productTags ?? ""
1873
+ });
1874
+ if (dispatch) dispatch(state.tr.replaceWith(from, to, node).scrollIntoView());
1875
+ return true;
1876
+ };
1877
+ } };
1878
+ },
1879
+ addPlugins() {
1880
+ return [new Plugin({ props: { handleDOMEvents: {
1881
+ click(view, event) {
1882
+ const target = event.target;
1883
+ if (target.classList.contains("mi-product-card-delete")) {
1884
+ event.preventDefault();
1885
+ event.stopPropagation();
1886
+ const card = target.closest(".mi-product-card");
1887
+ if (!card || !view.dom.contains(card)) return false;
1888
+ const pos = view.posAtDOM(card, 0);
1889
+ const node = view.state.doc.nodeAt(pos);
1890
+ if (!node || node.type.name !== "product_card") return false;
1891
+ const tr = view.state.tr.delete(pos, pos + node.nodeSize);
1892
+ view.dispatch(tr);
1893
+ return true;
1894
+ }
1895
+ return false;
1896
+ },
1897
+ dblclick(view, event) {
1898
+ const target = event.target;
1899
+ if (target.classList.contains("mi-product-card-delete")) return false;
1900
+ const card = target.closest(".mi-product-card");
1901
+ if (!card || !view.dom.contains(card)) return false;
1902
+ event.preventDefault();
1903
+ const pos = view.posAtDOM(card, 0);
1904
+ const node = view.state.doc.nodeAt(pos);
1905
+ if (!node || node.type.name !== "product_card") return false;
1906
+ const a = node.attrs;
1907
+ const name = prompt("商品名称:", a.productName);
1908
+ if (name === null) return true;
1909
+ const price = prompt("商品价格:", a.productPrice);
1910
+ if (price === null) return true;
1911
+ const desc = prompt("商品描述:", a.productDescription);
1912
+ if (desc === null) return true;
1913
+ const tr = view.state.tr;
1914
+ tr.setNodeMarkup(pos, null, {
1915
+ ...node.attrs,
1916
+ productName: name,
1917
+ productPrice: price,
1918
+ productDescription: desc
1919
+ });
1920
+ view.dispatch(tr);
1921
+ return true;
1922
+ }
1923
+ } } })];
1924
+ }
1925
+ };
1926
+ }
1927
+ //#endregion
1928
+ //#region src/extensions/table-extension.ts
1929
+ function cellAttrs(attrs) {
1930
+ return {
1931
+ colspan: attrs.colspan || 1,
1932
+ rowspan: attrs.rowspan || 1,
1933
+ colwidth: attrs.colwidth || null,
1934
+ style: attrs.style || null
1935
+ };
1936
+ }
1937
+ function cellAttrsToDOM(attrs) {
1938
+ const result = {};
1939
+ if (attrs.colspan && attrs.colspan > 1) result.colspan = String(attrs.colspan);
1940
+ if (attrs.rowspan && attrs.rowspan > 1) result.rowspan = String(attrs.rowspan);
1941
+ if (attrs.colwidth && attrs.colwidth.length > 0) result.style = `width: ${attrs.colwidth[0]}px`;
1942
+ if (attrs.style) result.style = result.style ? `${result.style}; ${attrs.style}` : attrs.style;
1943
+ return result;
1944
+ }
1945
+ function parseCellAttrs(dom) {
1946
+ return {
1947
+ colspan: parseInt(dom.getAttribute("colspan") || "1", 10),
1948
+ rowspan: parseInt(dom.getAttribute("rowspan") || "1", 10),
1949
+ colwidth: dom.getAttribute("colwidth") ? dom.getAttribute("colwidth").split(",").map(Number) : null,
1950
+ style: dom.getAttribute("style") || null
1951
+ };
1952
+ }
1953
+ function tableNodes() {
1954
+ return {
1955
+ table: {
1956
+ group: "block",
1957
+ content: "table_row+",
1958
+ tableRole: "table",
1959
+ isolating: true,
1960
+ attrs: {
1961
+ class: { default: null },
1962
+ tableStyle: { default: "default" }
1963
+ },
1964
+ parseDOM: [{
1965
+ tag: "table",
1966
+ getAttrs: (dom) => ({
1967
+ class: dom.getAttribute("class"),
1968
+ tableStyle: dom.getAttribute("data-table-style") || "default"
1969
+ })
1970
+ }],
1971
+ toDOM(node) {
1972
+ const style = node.attrs.tableStyle || "default";
1973
+ const attrs = {
1974
+ class: `miyuan-table miyuan-table-${style}`,
1975
+ "data-table-style": style
1976
+ };
1977
+ if (node.attrs.class) attrs.class += ` ${node.attrs.class}`;
1978
+ return [
1979
+ "div",
1980
+ { class: "miyuan-table-wrapper" },
1981
+ [
1982
+ "table",
1983
+ attrs,
1984
+ ["tbody", 0]
1985
+ ]
1986
+ ];
1987
+ }
1988
+ },
1989
+ table_row: {
1990
+ content: "(table_cell | table_header)+",
1991
+ tableRole: "row",
1992
+ parseDOM: [{ tag: "tr" }],
1993
+ toDOM() {
1994
+ return ["tr", 0];
1995
+ }
1996
+ },
1997
+ table_cell: {
1998
+ content: "block+",
1999
+ tableRole: "cell",
2000
+ isolating: true,
2001
+ attrs: {
2002
+ colspan: { default: 1 },
2003
+ rowspan: { default: 1 },
2004
+ colwidth: { default: null },
2005
+ style: { default: null }
2006
+ },
2007
+ parseDOM: [{
2008
+ tag: "td",
2009
+ getAttrs: (dom) => parseCellAttrs(dom)
2010
+ }],
2011
+ toDOM(node) {
2012
+ return [
2013
+ "td",
2014
+ cellAttrsToDOM(cellAttrs(node.attrs)),
2015
+ 0
2016
+ ];
2017
+ }
2018
+ },
2019
+ table_header: {
2020
+ content: "block+",
2021
+ tableRole: "header_cell",
2022
+ isolating: true,
2023
+ attrs: {
2024
+ colspan: { default: 1 },
2025
+ rowspan: { default: 1 },
2026
+ colwidth: { default: null },
2027
+ style: { default: null }
2028
+ },
2029
+ parseDOM: [{
2030
+ tag: "th",
2031
+ getAttrs: (dom) => parseCellAttrs(dom)
2032
+ }],
2033
+ toDOM(node) {
2034
+ return [
2035
+ "th",
2036
+ cellAttrsToDOM(cellAttrs(node.attrs)),
2037
+ 0
2038
+ ];
2039
+ }
2040
+ }
2041
+ };
2042
+ }
2043
+ function findTable(state) {
2044
+ const { $from } = state.selection;
2045
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type.name === "table") return {
2046
+ pos: $from.before(d),
2047
+ node: $from.node(d),
2048
+ depth: d
2049
+ };
2050
+ return null;
2051
+ }
2052
+ function findCellPosition(state) {
2053
+ const { $from } = state.selection;
2054
+ for (let d = $from.depth; d > 0; d--) {
2055
+ const node = $from.node(d);
2056
+ if (node.type.name === "table_cell" || node.type.name === "table_header") return {
2057
+ cellPos: $from.before(d),
2058
+ cellNode: node,
2059
+ rowPos: $from.before(d - 1),
2060
+ rowNode: $from.node(d - 1),
2061
+ tablePos: $from.before(d - 2),
2062
+ tableNode: $from.node(d - 2)
2063
+ };
2064
+ }
2065
+ return null;
2066
+ }
2067
+ function insertTable(rows = 3, cols = 3) {
2068
+ return (state, dispatch) => {
2069
+ const { schema } = state;
2070
+ const tableNodeType = schema.nodes.table;
2071
+ const rowType = schema.nodes.table_row;
2072
+ const cellType = schema.nodes.table_cell;
2073
+ const headerType = schema.nodes.table_header;
2074
+ const paraType = schema.nodes.paragraph;
2075
+ if (!tableNodeType || !rowType || !cellType || !headerType || !paraType) return false;
2076
+ const tableRows = [];
2077
+ const headerCells = [];
2078
+ for (let c = 0; c < cols; c++) headerCells.push(headerType.createAndFill({}, paraType.createAndFill()));
2079
+ tableRows.push(rowType.create(null, headerCells));
2080
+ for (let r = 1; r < rows; r++) {
2081
+ const cells = [];
2082
+ for (let c = 0; c < cols; c++) cells.push(cellType.createAndFill({}, paraType.createAndFill()));
2083
+ tableRows.push(rowType.create(null, cells));
2084
+ }
2085
+ const table = tableNodeType.create(null, tableRows);
2086
+ if (dispatch) {
2087
+ const { from, to } = state.selection;
2088
+ const tr = state.tr.replaceWith(from, to, table);
2089
+ const newSelection = TextSelection.near(tr.doc.resolve(from + 2));
2090
+ tr.setSelection(newSelection);
2091
+ dispatch(tr.scrollIntoView());
2092
+ }
2093
+ return true;
2094
+ };
2095
+ }
2096
+ function addRowBefore() {
2097
+ return (state, dispatch) => {
2098
+ const cellPos = findCellPosition(state);
2099
+ if (!cellPos) return false;
2100
+ if (dispatch) {
2101
+ const { schema } = state;
2102
+ const rowType = schema.nodes.table_row;
2103
+ const cellType = schema.nodes.table_cell;
2104
+ const paraType = schema.nodes.paragraph;
2105
+ const colCount = cellPos.rowNode.childCount;
2106
+ const cells = [];
2107
+ for (let i = 0; i < colCount; i++) cells.push(cellType.createAndFill({}, paraType.createAndFill()));
2108
+ const newRow = rowType.create(null, cells);
2109
+ dispatch(state.tr.insert(cellPos.rowPos, newRow).scrollIntoView());
2110
+ }
2111
+ return true;
2112
+ };
2113
+ }
2114
+ function addRowAfter() {
2115
+ return (state, dispatch) => {
2116
+ const cellPos = findCellPosition(state);
2117
+ if (!cellPos) return false;
2118
+ if (dispatch) {
2119
+ const { schema } = state;
2120
+ const rowType = schema.nodes.table_row;
2121
+ const cellType = schema.nodes.table_cell;
2122
+ const paraType = schema.nodes.paragraph;
2123
+ const colCount = cellPos.rowNode.childCount;
2124
+ const cells = [];
2125
+ for (let i = 0; i < colCount; i++) cells.push(cellType.createAndFill({}, paraType.createAndFill()));
2126
+ const newRow = rowType.create(null, cells);
2127
+ dispatch(state.tr.insert(cellPos.rowPos + cellPos.rowNode.nodeSize, newRow).scrollIntoView());
2128
+ }
2129
+ return true;
2130
+ };
2131
+ }
2132
+ function addColumnBefore() {
2133
+ return (state, dispatch) => {
2134
+ const cellPos = findCellPosition(state);
2135
+ if (!cellPos) return false;
2136
+ if (dispatch) {
2137
+ const { schema } = state;
2138
+ const table = cellPos.tableNode;
2139
+ const colIndex = getCellColumnIndex(cellPos.rowNode, cellPos.cellPos - cellPos.rowPos - 1);
2140
+ let tr = state.tr;
2141
+ for (let i = table.childCount - 1; i >= 0; i--) {
2142
+ const row = table.child(i);
2143
+ const cellType = i === 0 ? schema.nodes.table_header : schema.nodes.table_cell;
2144
+ const paraType = schema.nodes.paragraph;
2145
+ const newCell = cellType.createAndFill({}, paraType.createAndFill());
2146
+ let insertPos = cellPos.tablePos + 1;
2147
+ for (let r = 0; r < i; r++) insertPos += table.child(r).nodeSize;
2148
+ insertPos += 1;
2149
+ for (let j = 0; j < colIndex && j < row.childCount; j++) insertPos += row.child(j).nodeSize;
2150
+ tr = tr.insert(insertPos, newCell);
2151
+ }
2152
+ dispatch(tr.scrollIntoView());
2153
+ }
2154
+ return true;
2155
+ };
2156
+ }
2157
+ function addColumnAfter() {
2158
+ return (state, dispatch) => {
2159
+ const cellPos = findCellPosition(state);
2160
+ if (!cellPos) return false;
2161
+ if (dispatch) {
2162
+ const { schema } = state;
2163
+ const table = cellPos.tableNode;
2164
+ const colIndex = getCellColumnIndex(cellPos.rowNode, cellPos.cellPos - cellPos.rowPos - 1);
2165
+ let tr = state.tr;
2166
+ for (let i = table.childCount - 1; i >= 0; i--) {
2167
+ const row = table.child(i);
2168
+ const cellType = i === 0 ? schema.nodes.table_header : schema.nodes.table_cell;
2169
+ const paraType = schema.nodes.paragraph;
2170
+ const newCell = cellType.createAndFill({}, paraType.createAndFill());
2171
+ let insertPos = cellPos.tablePos + 1;
2172
+ for (let r = 0; r < i; r++) insertPos += table.child(r).nodeSize;
2173
+ insertPos += 1;
2174
+ for (let j = 0; j <= colIndex && j < row.childCount; j++) insertPos += row.child(j).nodeSize;
2175
+ tr = tr.insert(insertPos, newCell);
2176
+ }
2177
+ dispatch(tr.scrollIntoView());
2178
+ }
2179
+ return true;
2180
+ };
2181
+ }
2182
+ function deleteRow() {
2183
+ return (state, dispatch) => {
2184
+ const cellPos = findCellPosition(state);
2185
+ if (!cellPos) return false;
2186
+ if (cellPos.tableNode.childCount === 1) return deleteTable()(state, dispatch);
2187
+ if (dispatch) dispatch(state.tr.delete(cellPos.rowPos, cellPos.rowPos + cellPos.rowNode.nodeSize).scrollIntoView());
2188
+ return true;
2189
+ };
2190
+ }
2191
+ function deleteColumn() {
2192
+ return (state, dispatch) => {
2193
+ const cellPos = findCellPosition(state);
2194
+ if (!cellPos) return false;
2195
+ if (cellPos.rowNode.childCount === 1) return deleteTable()(state, dispatch);
2196
+ if (dispatch) {
2197
+ const { schema } = state;
2198
+ const table = cellPos.tableNode;
2199
+ const colIndex = getCellColumnIndex(cellPos.rowNode, cellPos.cellPos - cellPos.rowPos - 1);
2200
+ let tr = state.tr;
2201
+ let offset = 0;
2202
+ for (let i = 0; i < table.childCount; i++) {
2203
+ const row = table.child(i);
2204
+ let cellDeletePos = cellPos.tablePos + offset + 1 + 1;
2205
+ for (let j = 0; j < colIndex && j < row.childCount; j++) cellDeletePos += row.child(j).nodeSize;
2206
+ const cellToDelete = row.child(Math.min(colIndex, row.childCount - 1));
2207
+ tr = tr.delete(cellDeletePos, cellDeletePos + cellToDelete.nodeSize);
2208
+ offset -= cellToDelete.nodeSize;
2209
+ }
2210
+ dispatch(tr.scrollIntoView());
2211
+ }
2212
+ return true;
2213
+ };
2214
+ }
2215
+ function deleteTable() {
2216
+ return (state, dispatch) => {
2217
+ const table = findTable(state);
2218
+ if (!table) return false;
2219
+ if (dispatch) dispatch(state.tr.delete(table.pos, table.pos + table.node.nodeSize).scrollIntoView());
2220
+ return true;
2221
+ };
2222
+ }
2223
+ function toggleHeaderRow() {
2224
+ return (state, dispatch) => {
2225
+ const table = findTable(state);
2226
+ if (!table) return false;
2227
+ if (dispatch) {
2228
+ const { schema } = state;
2229
+ const firstRow = table.node.child(0);
2230
+ const tr = state.tr;
2231
+ const allHeaders = Array.from({ length: firstRow.childCount }, (_, i) => firstRow.child(i).type.name).every((type) => type === "table_header");
2232
+ let offset = 0;
2233
+ for (let i = 0; i < firstRow.childCount; i++) {
2234
+ const cell = firstRow.child(i);
2235
+ const cellPos = table.pos + 1 + offset;
2236
+ const newType = allHeaders ? schema.nodes.table_cell : schema.nodes.table_header;
2237
+ tr.setNodeMarkup(cellPos, newType, cell.attrs);
2238
+ offset += cell.nodeSize;
2239
+ }
2240
+ dispatch(tr.scrollIntoView());
2241
+ }
2242
+ return true;
2243
+ };
2244
+ }
2245
+ function setTableStyle(style) {
2246
+ return (state, dispatch) => {
2247
+ const table = findTable(state);
2248
+ if (!table) return false;
2249
+ if (dispatch) dispatch(state.tr.setNodeMarkup(table.pos, null, {
2250
+ ...table.node.attrs,
2251
+ tableStyle: style
2252
+ }));
2253
+ return true;
2254
+ };
2255
+ }
2256
+ function getCellColumnIndex(row, cellOffset) {
2257
+ let offset = 0;
2258
+ for (let i = 0; i < row.childCount; i++) {
2259
+ if (offset === cellOffset) return i;
2260
+ offset += row.child(i).nodeSize;
2261
+ }
2262
+ return row.childCount - 1;
2263
+ }
2264
+ var tablePluginKey = new PluginKey("table");
2265
+ function tablePlugin() {
2266
+ return new Plugin({
2267
+ key: tablePluginKey,
2268
+ props: { handleKeyDown(view, event) {
2269
+ const state = view.state;
2270
+ const cellPos = findCellPosition(state);
2271
+ if (event.key === "Tab") {
2272
+ if (!cellPos) return false;
2273
+ event.preventDefault();
2274
+ const { schema } = state;
2275
+ const table = cellPos.tableNode;
2276
+ const row = cellPos.rowNode;
2277
+ const colIndex = getCellColumnIndex(row, cellPos.cellPos - cellPos.rowPos - 1);
2278
+ const rowIndex = getRowIndex(table, cellPos.rowPos - cellPos.tablePos - 1);
2279
+ let targetRow;
2280
+ let targetCol;
2281
+ if (event.shiftKey) if (colIndex > 0) {
2282
+ targetRow = rowIndex;
2283
+ targetCol = colIndex - 1;
2284
+ } else if (rowIndex > 0) {
2285
+ targetRow = rowIndex - 1;
2286
+ targetCol = table.child(rowIndex - 1).childCount - 1;
2287
+ } else return true;
2288
+ else if (colIndex < row.childCount - 1) {
2289
+ targetRow = rowIndex;
2290
+ targetCol = colIndex + 1;
2291
+ } else if (rowIndex < table.childCount - 1) {
2292
+ targetRow = rowIndex + 1;
2293
+ targetCol = 0;
2294
+ } else return addRowAfter()(state, view.dispatch);
2295
+ const targetCellPos = getCellPos(table, targetRow, targetCol, cellPos.tablePos);
2296
+ if (targetCellPos !== null) {
2297
+ const $pos = state.doc.resolve(targetCellPos + 1);
2298
+ const tr = state.tr.setSelection(TextSelection.near($pos, 1));
2299
+ view.dispatch(tr.scrollIntoView());
2300
+ }
2301
+ return true;
2302
+ }
2303
+ if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey && !event.metaKey) {
2304
+ if (!cellPos) return false;
2305
+ return false;
2306
+ }
2307
+ if (event.key === "Backspace") {
2308
+ if (!cellPos) return false;
2309
+ const { from, empty } = state.selection;
2310
+ if (!empty) return false;
2311
+ if (from === cellPos.cellPos + 2) {
2312
+ event.preventDefault();
2313
+ const table = cellPos.tableNode;
2314
+ table.child(0);
2315
+ const firstCellPos = cellPos.tablePos + 2;
2316
+ if (cellPos.cellPos === firstCellPos - 1) return deleteTable()(state, view.dispatch);
2317
+ const row = cellPos.rowNode;
2318
+ const colIndex = getCellColumnIndex(row, cellPos.cellPos - cellPos.rowPos - 1);
2319
+ const rowIndex = getRowIndex(table, cellPos.rowPos - cellPos.tablePos - 1);
2320
+ let prevRow;
2321
+ let prevCol;
2322
+ if (colIndex > 0) {
2323
+ prevRow = rowIndex;
2324
+ prevCol = colIndex - 1;
2325
+ } else if (rowIndex > 0) {
2326
+ prevRow = rowIndex - 1;
2327
+ prevCol = table.child(rowIndex - 1).childCount - 1;
2328
+ } else return true;
2329
+ const prevCellPos = getCellPos(table, prevRow, prevCol, cellPos.tablePos);
2330
+ if (prevCellPos !== null) {
2331
+ const $pos = state.doc.resolve(prevCellPos + 1);
2332
+ const tr = state.tr.setSelection(TextSelection.near($pos, 1));
2333
+ view.dispatch(tr.scrollIntoView());
2334
+ }
2335
+ return true;
2336
+ }
2337
+ }
2338
+ return false;
2339
+ } }
2340
+ });
2341
+ }
2342
+ function getRowIndex(table, rowOffset) {
2343
+ let offset = 0;
2344
+ for (let i = 0; i < table.childCount; i++) {
2345
+ if (offset === rowOffset) return i;
2346
+ offset += table.child(i).nodeSize;
2347
+ }
2348
+ return table.childCount - 1;
2349
+ }
2350
+ function getCellPos(table, rowIndex, colIndex, tablePos) {
2351
+ if (rowIndex < 0 || rowIndex >= table.childCount) return null;
2352
+ const row = table.child(rowIndex);
2353
+ if (colIndex < 0 || colIndex >= row.childCount) return null;
2354
+ let offset = 1;
2355
+ for (let r = 0; r < rowIndex; r++) offset += table.child(r).nodeSize;
2356
+ let cellOffset = 1;
2357
+ for (let c = 0; c < colIndex; c++) cellOffset += row.child(c).nodeSize;
2358
+ return tablePos + offset + cellOffset;
2359
+ }
2360
+ function TableExtension() {
2361
+ return {
2362
+ name: "table",
2363
+ type: "node",
2364
+ addNodes() {
2365
+ return tableNodes();
2366
+ },
2367
+ addCommands() {
2368
+ return {
2369
+ insertTable: (...args) => (state, dispatch) => {
2370
+ return insertTable(args[0] || 3, args[1] || 3)(state, dispatch);
2371
+ },
2372
+ addRowBefore: () => addRowBefore(),
2373
+ addRowAfter: () => addRowAfter(),
2374
+ addColumnBefore: () => addColumnBefore(),
2375
+ addColumnAfter: () => addColumnAfter(),
2376
+ deleteRow: () => deleteRow(),
2377
+ deleteColumn: () => deleteColumn(),
2378
+ deleteTable: () => deleteTable(),
2379
+ toggleHeaderRow: () => toggleHeaderRow(),
2380
+ setTableStyle: (...args) => setTableStyle(args[0] || "default")
2381
+ };
2382
+ },
2383
+ addKeyboardShortcuts() {
2384
+ return { "Mod-Alt-t": insertTable(3, 3) };
2385
+ },
2386
+ addPlugins() {
2387
+ return [tablePlugin()];
2388
+ }
2389
+ };
2390
+ }
2391
+ //#endregion
2392
+ //#region src/extensions/behavior-extensions.ts
2393
+ function HistoryExtension() {
2394
+ return {
2395
+ name: "history",
2396
+ type: "extension",
2397
+ addPlugins() {
2398
+ return [history()];
2399
+ },
2400
+ addCommands() {
2401
+ return {
2402
+ undo: () => undo,
2403
+ redo: () => redo
2404
+ };
2405
+ },
2406
+ addKeyboardShortcuts() {
2407
+ return {
2408
+ "Mod-z": undo,
2409
+ "Mod-y": redo,
2410
+ "Mod-Shift-z": redo
2411
+ };
2412
+ }
2413
+ };
2414
+ }
2415
+ var placeholderKey = new PluginKey("placeholder");
2416
+ function PlaceholderExtension(config) {
2417
+ const text = config?.placeholder ?? "开始输入...";
2418
+ return {
2419
+ name: "placeholder",
2420
+ type: "extension",
2421
+ addPlugins() {
2422
+ return [new Plugin({
2423
+ key: placeholderKey,
2424
+ state: {
2425
+ init(_, state) {
2426
+ return buildDecorations(state, text);
2427
+ },
2428
+ apply(tr, oldDecorations, _oldState, newState) {
2429
+ if (tr.docChanged) return buildDecorations(newState, text);
2430
+ return oldDecorations;
2431
+ }
2432
+ },
2433
+ props: { decorations(state) {
2434
+ return this.getState(state);
2435
+ } }
2436
+ })];
2437
+ }
2438
+ };
2439
+ }
2440
+ function buildDecorations(state, text) {
2441
+ const doc = state.doc;
2442
+ if (doc.childCount !== 1) return DecorationSet.empty;
2443
+ const first = doc.firstChild;
2444
+ if (!first || first.type.name !== "paragraph" || first.content.size > 0) return DecorationSet.empty;
2445
+ const deco = Decoration.node(0, first.nodeSize, {
2446
+ class: "mi-placeholder",
2447
+ "data-placeholder": text
2448
+ });
2449
+ return DecorationSet.create(doc, [deco]);
2450
+ }
2451
+ var imageUploadKey = new PluginKey("imageUpload");
2452
+ /** Default: convert file to base64 data URL */
2453
+ function fileToDataURL(file) {
2454
+ return new Promise((resolve, reject) => {
2455
+ const reader = new FileReader();
2456
+ reader.onload = () => resolve(reader.result);
2457
+ reader.onerror = reject;
2458
+ reader.readAsDataURL(file);
2459
+ });
2460
+ }
2461
+ function ImageUploadExtension(config) {
2462
+ const upload = config?.upload ?? fileToDataURL;
2463
+ const maxSize = config?.maxSize ?? 5 * 1024 * 1024;
2464
+ function insertImage(view, src) {
2465
+ const imageType = view.state.schema.nodes["image"];
2466
+ if (!imageType) return;
2467
+ const tr = view.state.tr.replaceSelectionWith(imageType.create({ src }));
2468
+ view.dispatch(tr.scrollIntoView());
2469
+ }
2470
+ async function handleFile(file, view) {
2471
+ if (!file.type.startsWith("image/")) return;
2472
+ if (file.size > maxSize) {
2473
+ console.warn(`Image too large: ${(file.size / 1024 / 1024).toFixed(1)}MB > ${(maxSize / 1024 / 1024).toFixed(1)}MB`);
2474
+ return;
2475
+ }
2476
+ try {
2477
+ insertImage(view, await upload(file));
2478
+ } catch (e) {
2479
+ console.error("Image upload failed:", e);
2480
+ }
2481
+ }
2482
+ return {
2483
+ name: "imageUpload",
2484
+ type: "extension",
2485
+ addCommands() {
2486
+ return { insertImage: (...args) => {
2487
+ const src = args[0];
2488
+ return (state, dispatch) => {
2489
+ const imageType = state.schema.nodes["image"];
2490
+ if (!imageType) return false;
2491
+ const tr = state.tr.replaceSelectionWith(imageType.create({ src }));
2492
+ if (dispatch) dispatch(tr.scrollIntoView());
2493
+ return true;
2494
+ };
2495
+ } };
2496
+ },
2497
+ addPlugins() {
2498
+ return [new Plugin({
2499
+ key: imageUploadKey,
2500
+ props: {
2501
+ handlePaste(view, event) {
2502
+ const files = event.clipboardData?.files;
2503
+ if (!files || files.length === 0) return false;
2504
+ const imageFile = Array.from(files).find((f) => f.type.startsWith("image/"));
2505
+ if (!imageFile) return false;
2506
+ event.preventDefault();
2507
+ handleFile(imageFile, view);
2508
+ return true;
2509
+ },
2510
+ handleDrop(view, event) {
2511
+ const files = event.dataTransfer?.files;
2512
+ if (!files || files.length === 0) return false;
2513
+ const imageFile = Array.from(files).find((f) => f.type.startsWith("image/"));
2514
+ if (!imageFile) return false;
2515
+ event.preventDefault();
2516
+ handleFile(imageFile, view);
2517
+ return true;
2518
+ }
2519
+ }
2520
+ })];
2521
+ }
2522
+ };
2523
+ }
2524
+ //#endregion
2525
+ //#region src/extensions/line-height-extension.ts
2526
+ var DEFAULT_LINE_HEIGHTS = [
2527
+ {
2528
+ label: "单倍行距",
2529
+ value: 1
2530
+ },
2531
+ {
2532
+ label: "1.15 倍行距",
2533
+ value: 1.15
2534
+ },
2535
+ {
2536
+ label: "1.5 倍行距",
2537
+ value: 1.5
2538
+ },
2539
+ {
2540
+ label: "双倍行距",
2541
+ value: 2
2542
+ },
2543
+ {
2544
+ label: "2.5 倍行距",
2545
+ value: 2.5
2546
+ },
2547
+ {
2548
+ label: "3 倍行距",
2549
+ value: 3
2550
+ }
2551
+ ];
2552
+ function LineHeightExtension(config) {
2553
+ return {
2554
+ name: "lineHeight",
2555
+ type: "extension",
2556
+ options: { lineHeights: config?.lineHeights ?? DEFAULT_LINE_HEIGHTS },
2557
+ addCommands() {
2558
+ return { setLineHeight: (...args) => {
2559
+ const value = args[0];
2560
+ return setLineHeightCommand(value);
2561
+ } };
2562
+ }
2563
+ };
2564
+ }
2565
+ function setLineHeightCommand(value) {
2566
+ return (state, dispatch) => {
2567
+ const { from, to } = state.selection;
2568
+ let applicable = false;
2569
+ state.doc.nodesBetween(from, to, (node, pos) => {
2570
+ if (node.type.name !== "paragraph" && node.type.name !== "heading") return;
2571
+ applicable = true;
2572
+ if (dispatch) {
2573
+ const tr = state.tr;
2574
+ tr.setNodeMarkup(pos, null, {
2575
+ ...node.attrs,
2576
+ lineHeight: value
2577
+ });
2578
+ dispatch(tr);
2579
+ }
2580
+ });
2581
+ return applicable;
2582
+ };
2583
+ }
2584
+ //#endregion
2585
+ //#region src/extensions/version-history-extension.ts
2586
+ var versionHistoryKey = new PluginKey("versionHistory");
2587
+ function generateId() {
2588
+ return `v_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2589
+ }
2590
+ function createInitialState(versions) {
2591
+ return { versions };
2592
+ }
2593
+ function getVersions(state) {
2594
+ return versionHistoryKey.getState(state)?.versions ?? [];
2595
+ }
2596
+ function saveVersion(name) {
2597
+ return (state, dispatch) => {
2598
+ if (dispatch) dispatch(state.tr.setMeta(versionHistoryKey, {
2599
+ type: "save",
2600
+ name: name ?? `版本 ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`
2601
+ }));
2602
+ return true;
2603
+ };
2604
+ }
2605
+ function restoreVersion(versionId) {
2606
+ return (state, dispatch) => {
2607
+ const pluginState = versionHistoryKey.getState(state);
2608
+ if (!pluginState) return false;
2609
+ const version = pluginState.versions.find((v) => v.id === versionId);
2610
+ if (!version) return false;
2611
+ if (dispatch) {
2612
+ const doc = Node.fromJSON(state.schema, version.docJSON);
2613
+ const tr = state.tr.replaceWith(0, state.doc.content.size, doc.content);
2614
+ tr.setMeta(versionHistoryKey, { type: "restore" });
2615
+ dispatch(tr);
2616
+ }
2617
+ return true;
2618
+ };
2619
+ }
2620
+ function deleteVersion(versionId) {
2621
+ return (state, dispatch) => {
2622
+ if (dispatch) dispatch(state.tr.setMeta(versionHistoryKey, {
2623
+ type: "delete",
2624
+ versionId
2625
+ }));
2626
+ return true;
2627
+ };
2628
+ }
2629
+ function renameVersion(versionId, newName) {
2630
+ return (state, dispatch) => {
2631
+ if (dispatch) dispatch(state.tr.setMeta(versionHistoryKey, {
2632
+ type: "rename",
2633
+ versionId,
2634
+ name: newName
2635
+ }));
2636
+ return true;
2637
+ };
2638
+ }
2639
+ function VersionHistoryExtension(config) {
2640
+ const maxVersions = config?.maxVersions ?? 50;
2641
+ const autoSaveInterval = config?.autoSaveInterval ?? 0;
2642
+ const onVersionsChange = config?.onVersionsChange;
2643
+ const storage = config?.storage;
2644
+ let autoSaveTimer = null;
2645
+ let currentEditorState = null;
2646
+ let currentDispatch = null;
2647
+ return {
2648
+ name: "versionHistory",
2649
+ type: "extension",
2650
+ addCommands() {
2651
+ return {
2652
+ saveVersion: (...args) => {
2653
+ const name = args[0];
2654
+ return saveVersion(name);
2655
+ },
2656
+ restoreVersion: (...args) => {
2657
+ const versionId = args[0];
2658
+ return restoreVersion(versionId);
2659
+ },
2660
+ deleteVersion: (...args) => {
2661
+ const versionId = args[0];
2662
+ return deleteVersion(versionId);
2663
+ },
2664
+ renameVersion: (...args) => {
2665
+ const versionId = args[0];
2666
+ const newName = args[1];
2667
+ return renameVersion(versionId, newName);
2668
+ },
2669
+ listVersions: () => {
2670
+ return (state, dispatch) => {
2671
+ const versions = getVersions(state);
2672
+ if (dispatch && onVersionsChange) onVersionsChange(versions);
2673
+ return true;
2674
+ };
2675
+ }
2676
+ };
2677
+ },
2678
+ addPlugins() {
2679
+ return [new Plugin({
2680
+ key: versionHistoryKey,
2681
+ state: {
2682
+ init() {
2683
+ const loaded = storage?.load();
2684
+ if (loaded && !(typeof loaded.then === "function")) return createInitialState(loaded);
2685
+ return createInitialState([]);
2686
+ },
2687
+ apply(tr, value, _oldState, newState) {
2688
+ const meta = tr.getMeta(versionHistoryKey);
2689
+ if (!meta) return value;
2690
+ let newVersions;
2691
+ switch (meta.type) {
2692
+ case "save":
2693
+ newVersions = [{
2694
+ id: generateId(),
2695
+ name: meta.name ?? `版本 ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`,
2696
+ timestamp: Date.now(),
2697
+ docJSON: newState.doc.toJSON()
2698
+ }, ...value.versions];
2699
+ if (newVersions.length > maxVersions) newVersions = newVersions.slice(0, maxVersions);
2700
+ break;
2701
+ case "delete":
2702
+ newVersions = value.versions.filter((v) => v.id !== meta.versionId);
2703
+ break;
2704
+ case "rename":
2705
+ newVersions = value.versions.map((v) => v.id === meta.versionId ? {
2706
+ ...v,
2707
+ name: meta.name ?? v.name
2708
+ } : v);
2709
+ break;
2710
+ case "restore": return value;
2711
+ case "init": {
2712
+ const versions = meta.versions;
2713
+ return createInitialState(versions);
2714
+ }
2715
+ default: return value;
2716
+ }
2717
+ if (onVersionsChange) onVersionsChange(newVersions);
2718
+ if (storage) {
2719
+ const result = storage.save(newVersions);
2720
+ if (result && typeof result.then === "function") result.catch((err) => {
2721
+ console.error("[VersionHistory] Failed to save versions to storage:", err);
2722
+ });
2723
+ }
2724
+ return createInitialState(newVersions);
2725
+ }
2726
+ },
2727
+ view(editorView) {
2728
+ currentEditorState = editorView.state;
2729
+ currentDispatch = editorView.dispatch.bind(editorView);
2730
+ if (storage) {
2731
+ const loadResult = storage.load();
2732
+ if (loadResult && typeof loadResult.then === "function") loadResult.then((loaded) => {
2733
+ if (loaded && currentDispatch && currentEditorState) {
2734
+ const tr = currentEditorState.tr.setMeta(versionHistoryKey, {
2735
+ type: "init",
2736
+ versions: loaded
2737
+ });
2738
+ currentDispatch(tr);
2739
+ }
2740
+ });
2741
+ else if (loadResult) {
2742
+ const tr = editorView.state.tr.setMeta(versionHistoryKey, {
2743
+ type: "init",
2744
+ versions: loadResult
2745
+ });
2746
+ editorView.dispatch(tr);
2747
+ }
2748
+ }
2749
+ if (autoSaveInterval > 0) autoSaveTimer = setInterval(() => {
2750
+ if (currentEditorState && currentDispatch) {
2751
+ const tr = currentEditorState.tr.setMeta(versionHistoryKey, {
2752
+ type: "save",
2753
+ name: `自动保存 ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}`
2754
+ });
2755
+ currentDispatch(tr);
2756
+ }
2757
+ }, autoSaveInterval);
2758
+ return {
2759
+ update(view) {
2760
+ currentEditorState = view.state;
2761
+ currentDispatch = view.dispatch.bind(view);
2762
+ },
2763
+ destroy() {
2764
+ if (autoSaveTimer) {
2765
+ clearInterval(autoSaveTimer);
2766
+ autoSaveTimer = null;
2767
+ }
2768
+ currentEditorState = null;
2769
+ currentDispatch = null;
2770
+ }
2771
+ };
2772
+ }
2773
+ })];
2774
+ }
2775
+ };
2776
+ }
2777
+ function createLocalStorage(key = "mibao-editor-versions") {
2778
+ return {
2779
+ save(versions) {
2780
+ try {
2781
+ localStorage.setItem(key, JSON.stringify(versions));
2782
+ } catch (err) {
2783
+ console.error("[VersionHistory] localStorage save failed:", err);
2784
+ }
2785
+ },
2786
+ load() {
2787
+ try {
2788
+ const data = localStorage.getItem(key);
2789
+ return data ? JSON.parse(data) : null;
2790
+ } catch {
2791
+ return null;
2792
+ }
2793
+ }
2794
+ };
2795
+ }
2796
+ //#endregion
2797
+ //#region src/extensions/toc-node-extension.ts
2798
+ function TocNodeExtension() {
2799
+ return {
2800
+ name: "tocNode",
2801
+ type: "node",
2802
+ addNodes() {
2803
+ return { toc: {
2804
+ group: "block",
2805
+ atom: true,
2806
+ attrs: {
2807
+ items: { default: "[]" },
2808
+ title: { default: "目录" }
2809
+ },
2810
+ toDOM(node) {
2811
+ const items = JSON.parse(node.attrs.items);
2812
+ const title = node.attrs.title;
2813
+ const container = document.createElement("div");
2814
+ container.className = "mi-toc";
2815
+ container.contentEditable = "false";
2816
+ const header = document.createElement("div");
2817
+ header.className = "mi-toc-header";
2818
+ const titleSpan = document.createElement("span");
2819
+ titleSpan.className = "mi-toc-title";
2820
+ titleSpan.textContent = title;
2821
+ header.appendChild(titleSpan);
2822
+ container.appendChild(header);
2823
+ if (items.length === 0) {
2824
+ const empty = document.createElement("div");
2825
+ empty.className = "mi-toc-empty";
2826
+ empty.textContent = "暂无标题,添加标题后将自动生成目录";
2827
+ container.appendChild(empty);
2828
+ } else {
2829
+ const list = document.createElement("ul");
2830
+ list.className = "mi-toc-list";
2831
+ const minLevel = Math.min(...items.map((i) => i.level));
2832
+ items.forEach((item) => {
2833
+ const li = document.createElement("li");
2834
+ li.className = `mi-toc-item mi-toc-level-${item.level}`;
2835
+ li.style.paddingLeft = `${(item.level - minLevel) * 16}px`;
2836
+ const a = document.createElement("a");
2837
+ a.href = `#${item.id}`;
2838
+ a.className = "mi-toc-link";
2839
+ a.textContent = item.text;
2840
+ li.appendChild(a);
2841
+ list.appendChild(li);
2842
+ });
2843
+ container.appendChild(list);
2844
+ }
2845
+ const deleteBtn = document.createElement("button");
2846
+ deleteBtn.className = "mi-toc-delete";
2847
+ deleteBtn.textContent = "×";
2848
+ deleteBtn.title = "删除目录";
2849
+ container.appendChild(deleteBtn);
2850
+ return container;
2851
+ },
2852
+ parseDOM: [{
2853
+ tag: "div.mi-toc",
2854
+ getAttrs(dom) {
2855
+ const el = dom;
2856
+ const titleEl = el.querySelector(".mi-toc-title");
2857
+ return {
2858
+ items: el.getAttribute("data-items") || "[]",
2859
+ title: titleEl?.textContent || "目录"
2860
+ };
2861
+ }
2862
+ }]
2863
+ } };
2864
+ },
2865
+ addCommands() {
2866
+ return { insertTocNode: (...args) => {
2867
+ const attrs = args[0];
2868
+ return (state, dispatch) => {
2869
+ const tocType = state.schema.nodes["toc"];
2870
+ if (!tocType) return false;
2871
+ const { from, to } = state.selection;
2872
+ const node = tocType.create({
2873
+ items: attrs?.items || "[]",
2874
+ title: attrs?.title || "目录"
2875
+ });
2876
+ if (dispatch) dispatch(state.tr.replaceWith(from, to, node).scrollIntoView());
2877
+ return true;
2878
+ };
2879
+ } };
2880
+ },
2881
+ addPlugins() {
2882
+ return [new Plugin({ props: { handleDOMEvents: { click(view, event) {
2883
+ const target = event.target;
2884
+ if (target.classList.contains("mi-toc-delete")) {
2885
+ event.preventDefault();
2886
+ event.stopPropagation();
2887
+ const toc = target.closest(".mi-toc");
2888
+ if (!toc || !view.dom.contains(toc)) return false;
2889
+ const pos = view.posAtDOM(toc, 0);
2890
+ const node = view.state.doc.nodeAt(pos);
2891
+ if (!node || node.type.name !== "toc") return false;
2892
+ const tr = view.state.tr.delete(pos, pos + node.nodeSize);
2893
+ view.dispatch(tr);
2894
+ return true;
2895
+ }
2896
+ if (target.classList.contains("mi-toc-link")) {
2897
+ event.preventDefault();
2898
+ const href = target.getAttribute("href");
2899
+ if (!href || !href.startsWith("#")) return false;
2900
+ const id = href.slice(1);
2901
+ const targetEl = document.getElementById(id);
2902
+ if (targetEl) targetEl.scrollIntoView({
2903
+ behavior: "smooth",
2904
+ block: "start"
2905
+ });
2906
+ return true;
2907
+ }
2908
+ return false;
2909
+ } } } })];
2910
+ }
2911
+ };
2912
+ }
2913
+ //#endregion
2914
+ //#region src/extensions/code-block-enhanced-extension.ts
2915
+ var codeBlockKey = new PluginKey("codeBlockEnhanced");
2916
+ var DEFAULT_LANGUAGES = [
2917
+ "javascript",
2918
+ "typescript",
2919
+ "python",
2920
+ "java",
2921
+ "cpp",
2922
+ "c",
2923
+ "go",
2924
+ "rust",
2925
+ "html",
2926
+ "css",
2927
+ "scss",
2928
+ "json",
2929
+ "yaml",
2930
+ "markdown",
2931
+ "bash",
2932
+ "sql",
2933
+ "php",
2934
+ "ruby",
2935
+ "swift",
2936
+ "kotlin",
2937
+ "xml",
2938
+ "dockerfile",
2939
+ "plaintext"
2940
+ ];
2941
+ var CodeBlockNodeView = class {
2942
+ constructor(node, view, getPos, _decorations, languages) {
2943
+ this.node = node;
2944
+ this.view = view;
2945
+ this.getPos = getPos;
2946
+ this.languages = languages;
2947
+ const language = node.attrs.language;
2948
+ this.dom = document.createElement("div");
2949
+ this.dom.className = "mi-code-block";
2950
+ this.dom.setAttribute("data-language", language);
2951
+ const header = document.createElement("div");
2952
+ header.className = "mi-code-block-header";
2953
+ const langSelect = document.createElement("select");
2954
+ langSelect.className = "mi-code-block-lang";
2955
+ languages.forEach((lang) => {
2956
+ const option = document.createElement("option");
2957
+ option.value = lang;
2958
+ option.textContent = lang;
2959
+ if (lang === language) option.selected = true;
2960
+ langSelect.appendChild(option);
2961
+ });
2962
+ langSelect.addEventListener("mousedown", (e) => {
2963
+ e.stopPropagation();
2964
+ });
2965
+ langSelect.addEventListener("change", () => {
2966
+ const pos = this.getPos();
2967
+ if (pos === void 0) return;
2968
+ const currentNode = this.view.state.doc.nodeAt(pos);
2969
+ if (!currentNode || currentNode.type.name !== "code_block") return;
2970
+ const tr = this.view.state.tr.setNodeMarkup(pos, null, {
2971
+ ...currentNode.attrs,
2972
+ language: langSelect.value
2973
+ });
2974
+ this.view.dispatch(tr);
2975
+ });
2976
+ const copyBtn = document.createElement("button");
2977
+ copyBtn.className = "mi-code-block-copy";
2978
+ copyBtn.textContent = "复制";
2979
+ copyBtn.title = "复制代码";
2980
+ copyBtn.addEventListener("mousedown", (e) => {
2981
+ e.preventDefault();
2982
+ e.stopPropagation();
2983
+ });
2984
+ copyBtn.addEventListener("click", (e) => {
2985
+ e.preventDefault();
2986
+ e.stopPropagation();
2987
+ const pos = this.getPos();
2988
+ if (pos === void 0) return;
2989
+ const currentNode = this.view.state.doc.nodeAt(pos);
2990
+ if (!currentNode) return;
2991
+ const text = currentNode.textContent || "";
2992
+ navigator.clipboard.writeText(text).then(() => {
2993
+ copyBtn.textContent = "已复制 ✓";
2994
+ setTimeout(() => {
2995
+ copyBtn.textContent = "复制";
2996
+ }, 2e3);
2997
+ }).catch(() => {
2998
+ try {
2999
+ const textarea = document.createElement("textarea");
3000
+ textarea.value = text;
3001
+ textarea.style.cssText = "position:fixed;left:-9999px;top:-9999px;opacity:0";
3002
+ document.body.appendChild(textarea);
3003
+ textarea.select();
3004
+ document.execCommand("copy");
3005
+ document.body.removeChild(textarea);
3006
+ } catch {}
3007
+ copyBtn.textContent = "已复制 ✓";
3008
+ setTimeout(() => {
3009
+ copyBtn.textContent = "复制";
3010
+ }, 2e3);
3011
+ });
3012
+ });
3013
+ header.appendChild(langSelect);
3014
+ header.appendChild(copyBtn);
3015
+ const deleteBtn = document.createElement("button");
3016
+ deleteBtn.className = "mi-code-block-delete";
3017
+ deleteBtn.textContent = "×";
3018
+ deleteBtn.title = "删除代码块";
3019
+ deleteBtn.addEventListener("mousedown", (e) => {
3020
+ e.preventDefault();
3021
+ e.stopPropagation();
3022
+ });
3023
+ deleteBtn.addEventListener("click", (e) => {
3024
+ e.preventDefault();
3025
+ e.stopPropagation();
3026
+ const pos = this.getPos();
3027
+ if (pos === void 0) return;
3028
+ const currentNode = this.view.state.doc.nodeAt(pos);
3029
+ if (!currentNode || currentNode.type.name !== "code_block") return;
3030
+ const tr = this.view.state.tr.delete(pos, pos + currentNode.nodeSize);
3031
+ this.view.dispatch(tr);
3032
+ });
3033
+ header.appendChild(deleteBtn);
3034
+ const pre = document.createElement("pre");
3035
+ pre.className = "mi-code-block-pre";
3036
+ const codeEl = document.createElement("code");
3037
+ codeEl.className = `language-${language}`;
3038
+ codeEl.spellcheck = false;
3039
+ this.contentDOM = codeEl;
3040
+ pre.appendChild(codeEl);
3041
+ this.dom.appendChild(header);
3042
+ this.dom.appendChild(pre);
3043
+ }
3044
+ update(node) {
3045
+ if (node.type !== this.node.type) return false;
3046
+ this.node = node;
3047
+ const language = node.attrs.language;
3048
+ this.dom.setAttribute("data-language", language);
3049
+ const codeEl = this.contentDOM;
3050
+ codeEl.className = `language-${language}`;
3051
+ const select = this.dom.querySelector(".mi-code-block-lang");
3052
+ if (select && select.value !== language) select.value = language;
3053
+ return true;
3054
+ }
3055
+ stopEvent() {
3056
+ return false;
3057
+ }
3058
+ ignoreMutation() {
3059
+ return false;
3060
+ }
3061
+ };
3062
+ function CodeBlockEnhancedExtension(config) {
3063
+ const defaultLanguage = config?.defaultLanguage || "plaintext";
3064
+ const languages = config?.languages || DEFAULT_LANGUAGES;
3065
+ return {
3066
+ name: "codeBlockEnhanced",
3067
+ type: "node",
3068
+ addNodes() {
3069
+ return { code_block: {
3070
+ group: "block",
3071
+ content: "text*",
3072
+ marks: "",
3073
+ code: true,
3074
+ defining: true,
3075
+ attrs: { language: { default: defaultLanguage } },
3076
+ toDOM(node) {
3077
+ return [
3078
+ "div",
3079
+ {
3080
+ class: "mi-code-block",
3081
+ "data-language": node.attrs.language
3082
+ },
3083
+ [
3084
+ "div",
3085
+ { class: "mi-code-block-header" },
3086
+ ["select", { class: "mi-code-block-lang" }],
3087
+ [
3088
+ "button",
3089
+ { class: "mi-code-block-copy" },
3090
+ "复制"
3091
+ ],
3092
+ [
3093
+ "button",
3094
+ { class: "mi-code-block-delete" },
3095
+ "×"
3096
+ ]
3097
+ ],
3098
+ [
3099
+ "pre",
3100
+ { class: "mi-code-block-pre" },
3101
+ [
3102
+ "code",
3103
+ {
3104
+ class: `language-${node.attrs.language}`,
3105
+ spellcheck: "false"
3106
+ },
3107
+ 0
3108
+ ]
3109
+ ]
3110
+ ];
3111
+ },
3112
+ parseDOM: [{
3113
+ tag: "div.mi-code-block",
3114
+ getAttrs(dom) {
3115
+ const el = dom;
3116
+ return { language: el.querySelector(".mi-code-block-lang")?.value || el.getAttribute("data-language") || defaultLanguage };
3117
+ },
3118
+ getContent: (dom, schema) => {
3119
+ const text = dom.querySelector("code")?.textContent || "";
3120
+ return Fragment.from(schema.text(text));
3121
+ },
3122
+ preserveWhitespace: "full"
3123
+ }]
3124
+ } };
3125
+ },
3126
+ addCommands() {
3127
+ return {
3128
+ codeBlock: (...args) => {
3129
+ const language = args[0] || defaultLanguage;
3130
+ return (state, dispatch) => {
3131
+ const nt = state.schema.nodes["code_block"];
3132
+ if (!nt) return false;
3133
+ const { $from } = state.selection;
3134
+ if ($from.parent.type === nt) {
3135
+ const paraType = state.schema.nodes["paragraph"];
3136
+ if (!paraType) return false;
3137
+ return setBlockType(paraType)(state, dispatch);
3138
+ }
3139
+ return setBlockType(nt, { language })(state, dispatch);
3140
+ };
3141
+ },
3142
+ setCodeBlockLanguage: (...args) => {
3143
+ const language = args[0];
3144
+ return (state, dispatch) => {
3145
+ const { $from } = state.selection;
3146
+ const nt = state.schema.nodes["code_block"];
3147
+ if (!nt) return false;
3148
+ let codeBlockPos = -1;
3149
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type === nt) {
3150
+ codeBlockPos = $from.before(d);
3151
+ break;
3152
+ }
3153
+ if (codeBlockPos < 0) return false;
3154
+ if (dispatch) {
3155
+ const node = state.doc.nodeAt(codeBlockPos);
3156
+ if (!node) return false;
3157
+ dispatch(state.tr.setNodeMarkup(codeBlockPos, null, {
3158
+ ...node.attrs,
3159
+ language
3160
+ }));
3161
+ }
3162
+ return true;
3163
+ };
3164
+ },
3165
+ deleteCodeBlock: () => {
3166
+ return (state, dispatch) => {
3167
+ const { $from } = state.selection;
3168
+ const nt = state.schema.nodes["code_block"];
3169
+ if (!nt) return false;
3170
+ let codeBlockPos = -1;
3171
+ for (let d = $from.depth; d > 0; d--) if ($from.node(d).type === nt) {
3172
+ codeBlockPos = $from.before(d);
3173
+ break;
3174
+ }
3175
+ if (codeBlockPos < 0) return false;
3176
+ if (dispatch) {
3177
+ const node = state.doc.nodeAt(codeBlockPos);
3178
+ if (!node) return false;
3179
+ dispatch(state.tr.delete(codeBlockPos, codeBlockPos + node.nodeSize));
3180
+ }
3181
+ return true;
3182
+ };
3183
+ }
3184
+ };
3185
+ },
3186
+ addKeyboardShortcuts() {
3187
+ return {
3188
+ "Mod-Shift-c": (state, dispatch) => {
3189
+ const nt = state.schema.nodes["code_block"];
3190
+ if (!nt) return false;
3191
+ const { $from } = state.selection;
3192
+ if ($from.parent.type === nt) {
3193
+ const paraType = state.schema.nodes["paragraph"];
3194
+ if (!paraType) return false;
3195
+ return setBlockType(paraType)(state, dispatch);
3196
+ }
3197
+ return setBlockType(nt)(state, dispatch);
3198
+ },
3199
+ "Enter": (state, dispatch) => {
3200
+ const nt = state.schema.nodes["code_block"];
3201
+ if (!nt) return false;
3202
+ const { $from } = state.selection;
3203
+ if ($from.parent.type !== nt) return false;
3204
+ if ($from.parent.content.size > 0) return false;
3205
+ const paraType = state.schema.nodes["paragraph"];
3206
+ if (!paraType) return false;
3207
+ return setBlockType(paraType)(state, dispatch);
3208
+ },
3209
+ "Backspace": (state, dispatch) => {
3210
+ const nt = state.schema.nodes["code_block"];
3211
+ if (!nt) return false;
3212
+ const { $from } = state.selection;
3213
+ if ($from.parent.type !== nt) return false;
3214
+ if ($from.parent.content.size > 0) return false;
3215
+ const paraType = state.schema.nodes["paragraph"];
3216
+ if (!paraType) return false;
3217
+ return setBlockType(paraType)(state, dispatch);
3218
+ }
3219
+ };
3220
+ },
3221
+ addPlugins() {
3222
+ return [inputRules({ rules: [new InputRule(/^```(\w*)$/, (state, match, start, end) => {
3223
+ const nt = state.schema.nodes["code_block"];
3224
+ if (!nt) return null;
3225
+ const language = match[1] || defaultLanguage;
3226
+ const tr = state.tr;
3227
+ tr.delete(start, end);
3228
+ const range = tr.doc.resolve(start).blockRange();
3229
+ if (!range) return null;
3230
+ tr.setBlockType(range.start, range.end, nt, { language });
3231
+ return tr;
3232
+ })] }), new Plugin({
3233
+ key: codeBlockKey,
3234
+ props: { nodeViews: { code_block(node, view, getPos, decorations) {
3235
+ return new CodeBlockNodeView(node, view, getPos, decorations, languages);
3236
+ } } }
3237
+ })];
3238
+ }
3239
+ };
3240
+ }
3241
+ //#endregion
3242
+ //#region src/extensions/dark-mode-extension.ts
3243
+ var darkModeKey = new PluginKey("darkMode");
3244
+ function DarkModeExtension(config) {
3245
+ const defaultDark = config?.defaultDark ?? false;
3246
+ return {
3247
+ name: "darkMode",
3248
+ type: "extension",
3249
+ addPlugins() {
3250
+ return [new Plugin({
3251
+ key: darkModeKey,
3252
+ state: {
3253
+ init() {
3254
+ return { isDark: defaultDark };
3255
+ },
3256
+ apply(tr, prev) {
3257
+ const meta = tr.getMeta(darkModeKey);
3258
+ if (meta?.type === "toggle") {
3259
+ const isDark = !prev.isDark;
3260
+ if (config?.onChange) setTimeout(() => config.onChange(isDark), 0);
3261
+ return { isDark };
3262
+ }
3263
+ if (meta?.type === "set") {
3264
+ const isDark = !!meta.isDark;
3265
+ if (config?.onChange) setTimeout(() => config.onChange(isDark), 0);
3266
+ return { isDark };
3267
+ }
3268
+ return prev;
3269
+ }
3270
+ },
3271
+ props: { attributes(state) {
3272
+ if (darkModeKey.getState(state)?.isDark) return {
3273
+ "data-theme": "dark",
3274
+ "class": "mi-dark-mode"
3275
+ };
3276
+ return {};
3277
+ } }
3278
+ })];
3279
+ },
3280
+ addCommands() {
3281
+ return {
3282
+ toggleDarkMode: () => (state, dispatch) => {
3283
+ if (dispatch) dispatch(state.tr.setMeta(darkModeKey, { type: "toggle" }));
3284
+ return true;
3285
+ },
3286
+ setDarkMode: (...args) => {
3287
+ const isDark = args[0];
3288
+ return (state, dispatch) => {
3289
+ if (dispatch) dispatch(state.tr.setMeta(darkModeKey, {
3290
+ type: "set",
3291
+ isDark
3292
+ }));
3293
+ return true;
3294
+ };
3295
+ }
3296
+ };
3297
+ }
3298
+ };
3299
+ }
3300
+ function isDarkMode(state) {
3301
+ return darkModeKey.getState(state)?.isDark ?? false;
3302
+ }
3303
+ //#endregion
3304
+ //#region src/extensions/html-block-extension.ts
3305
+ /**
3306
+ * HTMLBlockExtension: supports parsing and preserving arbitrary HTML block elements
3307
+ * like div, section, etc. This is useful for pasting complex HTML content from
3308
+ * external sources (e.g., 135编辑器, wangEditor) while preserving the structure.
3309
+ */
3310
+ function HTMLBlockExtension() {
3311
+ return {
3312
+ name: "html_block",
3313
+ type: "node",
3314
+ addNodes() {
3315
+ return { html_block: {
3316
+ group: "block",
3317
+ content: "block*",
3318
+ atom: false,
3319
+ defining: true,
3320
+ attrs: {
3321
+ tagName: { default: "div" },
3322
+ className: { default: "" },
3323
+ style: { default: "" },
3324
+ dataAttributes: { default: {} }
3325
+ },
3326
+ toDOM(node) {
3327
+ const { tagName, className, style, dataAttributes } = node.attrs;
3328
+ const attrs = {};
3329
+ if (className) attrs.class = className;
3330
+ if (style) attrs.style = style;
3331
+ if (dataAttributes && typeof dataAttributes === "object") {
3332
+ for (const [key, value] of Object.entries(dataAttributes)) if (value !== null && value !== void 0) attrs[key] = String(value);
3333
+ }
3334
+ return [
3335
+ tagName || "div",
3336
+ attrs,
3337
+ 0
3338
+ ];
3339
+ },
3340
+ parseDOM: [
3341
+ {
3342
+ tag: "div:not(.mi-card):not(.mi-product-card):not(.mi-code-block):not(.mi-toc):not(.mi-task-list):not(.mi-task-item)",
3343
+ getAttrs(dom) {
3344
+ return extractAttributes(dom, "div");
3345
+ }
3346
+ },
3347
+ {
3348
+ tag: "section",
3349
+ getAttrs(dom) {
3350
+ return extractAttributes(dom, "section");
3351
+ }
3352
+ },
3353
+ {
3354
+ tag: "article",
3355
+ getAttrs(dom) {
3356
+ return extractAttributes(dom, "article");
3357
+ }
3358
+ },
3359
+ {
3360
+ tag: "aside",
3361
+ getAttrs(dom) {
3362
+ return extractAttributes(dom, "aside");
3363
+ }
3364
+ },
3365
+ {
3366
+ tag: "main",
3367
+ getAttrs(dom) {
3368
+ return extractAttributes(dom, "main");
3369
+ }
3370
+ },
3371
+ {
3372
+ tag: "header",
3373
+ getAttrs(dom) {
3374
+ return extractAttributes(dom, "header");
3375
+ }
3376
+ },
3377
+ {
3378
+ tag: "footer",
3379
+ getAttrs(dom) {
3380
+ return extractAttributes(dom, "footer");
3381
+ }
3382
+ },
3383
+ {
3384
+ tag: "nav",
3385
+ getAttrs(dom) {
3386
+ return extractAttributes(dom, "nav");
3387
+ }
3388
+ }
3389
+ ]
3390
+ } };
3391
+ }
3392
+ };
3393
+ }
3394
+ /**
3395
+ * Extract attributes from a DOM element for the html_block node
3396
+ */
3397
+ function extractAttributes(el, tagName) {
3398
+ const className = el.className || "";
3399
+ const style = el.getAttribute("style") || "";
3400
+ const dataAttributes = {};
3401
+ for (const attr of Array.from(el.attributes)) if (attr.name.startsWith("data-")) dataAttributes[attr.name] = attr.value;
3402
+ return {
3403
+ tagName,
3404
+ className,
3405
+ style,
3406
+ dataAttributes
3407
+ };
3408
+ }
3409
+ //#endregion
3410
+ export { BackgroundColorExtension, BlockquoteExtension, BoldExtension, BulletListExtension, CardExtension, CodeBlockEnhancedExtension, CodeBlockExtension, CodeExtension, DEFAULT_COLORS, DEFAULT_FONT_FAMILIES, DEFAULT_FONT_SIZES, DEFAULT_LINE_HEIGHTS, DarkModeExtension, DocExtension, FontFamilyExtension, FontSizeExtension, HTMLBlockExtension, HardBreakExtension, HeadingExtension, HistoryExtension, HorizontalRuleExtension, ImageExtension, ImageUploadExtension, ItalicExtension, LineHeightExtension, LinkExtension, OrderedListExtension, ParagraphExtension, PlaceholderExtension, ProductCardExtension, StrikeExtension, TableExtension, TableOfContentsExtension, TaskListExtension, TextColorExtension, TextExtension, TocNodeExtension, UnderlineExtension, VersionHistoryExtension, WordCountExtension, createLocalStorage, deleteVersion, getTableOfContents, getVersions, getWordCountStats, isDarkMode, renameVersion, restoreVersion, saveVersion };
3411
+
3412
+ //# sourceMappingURL=index.esm.js.map