dn-react-text-editor 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +14 -214
  2. package/dist/attach_file.d.mts +20 -18
  3. package/dist/attach_file.d.ts +20 -18
  4. package/dist/attach_file.js +18 -9
  5. package/dist/attach_file.mjs +18 -9
  6. package/dist/base64_file_uploader.d.mts +6 -0
  7. package/dist/base64_file_uploader.d.ts +6 -0
  8. package/dist/{plugins/trailing_paragraph.js → base64_file_uploader.js} +19 -22
  9. package/dist/base64_file_uploader.mjs +18 -0
  10. package/dist/commands.d.mts +23 -0
  11. package/dist/commands.d.ts +23 -0
  12. package/dist/commands.js +110 -0
  13. package/dist/commands.mjs +75 -0
  14. package/dist/create_text_editor.d.mts +28 -0
  15. package/dist/create_text_editor.d.ts +28 -0
  16. package/dist/create_text_editor.js +1082 -0
  17. package/dist/create_text_editor.mjs +1053 -0
  18. package/dist/html.d.mts +8 -0
  19. package/dist/html.d.ts +8 -0
  20. package/dist/html.js +136 -0
  21. package/dist/html.mjs +98 -0
  22. package/dist/index.d.mts +7 -4
  23. package/dist/index.d.ts +7 -4
  24. package/dist/index.js +790 -380
  25. package/dist/index.mjs +789 -377
  26. package/dist/input.d.mts +21 -0
  27. package/dist/input.d.ts +21 -0
  28. package/dist/input.js +70 -0
  29. package/dist/input.mjs +37 -0
  30. package/dist/plugins/drag_and_drop.d.mts +1 -1
  31. package/dist/plugins/drag_and_drop.d.ts +1 -1
  32. package/dist/plugins/drag_and_drop.js +3 -0
  33. package/dist/plugins/drag_and_drop.mjs +3 -0
  34. package/dist/plugins/highlighter.d.mts +6 -0
  35. package/dist/plugins/highlighter.d.ts +6 -0
  36. package/dist/plugins/highlighter.js +105 -0
  37. package/dist/plugins/highlighter.mjs +69 -0
  38. package/dist/plugins/keymap.js +17 -0
  39. package/dist/plugins/keymap.mjs +17 -0
  40. package/dist/schema.d.mts +2 -2
  41. package/dist/schema.d.ts +2 -2
  42. package/dist/schema.js +255 -14
  43. package/dist/schema.mjs +245 -14
  44. package/dist/text_editor_controller.d.mts +46 -0
  45. package/dist/text_editor_controller.d.ts +46 -0
  46. package/dist/text_editor_controller.js +503 -0
  47. package/dist/text_editor_controller.mjs +470 -0
  48. package/package.json +3 -1
  49. package/dist/plugins/trailing_paragraph.d.mts +0 -5
  50. package/dist/plugins/trailing_paragraph.d.ts +0 -5
  51. package/dist/plugins/trailing_paragraph.mjs +0 -21
  52. package/dist/text_editor.d.mts +0 -37
  53. package/dist/text_editor.d.ts +0 -37
  54. package/dist/text_editor.js +0 -720
  55. package/dist/text_editor.mjs +0 -687
@@ -0,0 +1,1053 @@
1
+ // src/create_text_editor.tsx
2
+ import React2, {
3
+ useImperativeHandle
4
+ } from "react";
5
+ import "prosemirror-state";
6
+ import "prosemirror-view";
7
+ import { useEffect as useEffect2, useRef } from "react";
8
+
9
+ // src/schema.tsx
10
+ import { Schema } from "prosemirror-model";
11
+ import { addListNodes } from "prosemirror-schema-list";
12
+
13
+ // src/plugins/highlighter.ts
14
+ import hljs from "highlight.js";
15
+ import bash from "highlight.js/lib/languages/bash";
16
+ import c from "highlight.js/lib/languages/c";
17
+ import cpp from "highlight.js/lib/languages/cpp";
18
+ import css from "highlight.js/lib/languages/css";
19
+ import java from "highlight.js/lib/languages/java";
20
+ import ts from "highlight.js/lib/languages/typescript";
21
+ import js from "highlight.js/lib/languages/javascript";
22
+ import json from "highlight.js/lib/languages/json";
23
+ import xml from "highlight.js/lib/languages/xml";
24
+ import python from "highlight.js/lib/languages/python";
25
+ import dart from "highlight.js/lib/languages/dart";
26
+ import csharp from "highlight.js/lib/languages/csharp";
27
+ import markdown from "highlight.js/lib/languages/markdown";
28
+ import nginx from "highlight.js/lib/languages/nginx";
29
+ import php from "highlight.js/lib/languages/php";
30
+ import ruby from "highlight.js/lib/languages/ruby";
31
+ import sql from "highlight.js/lib/languages/sql";
32
+ import swift from "highlight.js/lib/languages/swift";
33
+ import yaml from "highlight.js/lib/languages/yaml";
34
+ import rust from "highlight.js/lib/languages/rust";
35
+ hljs.registerLanguage("bash", bash);
36
+ hljs.registerLanguage("c", c);
37
+ hljs.registerLanguage("cpp", cpp);
38
+ hljs.registerLanguage("css", css);
39
+ hljs.registerLanguage("java", java);
40
+ hljs.registerLanguage("markdown", markdown);
41
+ hljs.registerLanguage("nginx", nginx);
42
+ hljs.registerLanguage("php", php);
43
+ hljs.registerLanguage("ruby", ruby);
44
+ hljs.registerLanguage("sql", sql);
45
+ hljs.registerLanguage("swift", swift);
46
+ hljs.registerLanguage("yaml", yaml);
47
+ hljs.registerLanguage("rust", rust);
48
+ hljs.registerLanguage("json", json);
49
+ hljs.registerLanguage("javascript", js);
50
+ hljs.registerLanguage("typescript", ts);
51
+ hljs.registerLanguage("xml", xml);
52
+ hljs.registerLanguage("python", python);
53
+ hljs.registerLanguage("dart", dart);
54
+ hljs.registerLanguage("csharp", csharp);
55
+ var supportedLanguages = [
56
+ "bash",
57
+ "c",
58
+ "cpp",
59
+ "css",
60
+ "java",
61
+ "markdown",
62
+ "nginx",
63
+ "php",
64
+ "ruby",
65
+ "sql",
66
+ "swift",
67
+ "yaml",
68
+ "rust",
69
+ "json",
70
+ "javascript",
71
+ "typescript",
72
+ "xml",
73
+ "python",
74
+ "dart",
75
+ "csharp"
76
+ ];
77
+ var highlighter = hljs;
78
+
79
+ // src/schema.tsx
80
+ function createSchema(spec = { nodes: {}, marks: {} }) {
81
+ const customSchema = new Schema({
82
+ nodes: {
83
+ doc: { content: "block+" },
84
+ paragraph: {
85
+ attrs: { align: { default: null } },
86
+ content: "inline*",
87
+ group: "block",
88
+ parseDOM: [
89
+ {
90
+ tag: "p",
91
+ getAttrs(dom) {
92
+ return {
93
+ align: dom.style.textAlign || null
94
+ };
95
+ }
96
+ }
97
+ ],
98
+ toDOM(node) {
99
+ return [
100
+ "p",
101
+ {
102
+ style: node.attrs.align ? `text-align: ${node.attrs.align}` : null
103
+ },
104
+ 0
105
+ ];
106
+ }
107
+ },
108
+ text: {
109
+ group: "inline"
110
+ },
111
+ hard_break: {
112
+ inline: true,
113
+ group: "inline",
114
+ selectable: false,
115
+ parseDOM: [{ tag: "br" }],
116
+ toDOM() {
117
+ return ["br"];
118
+ }
119
+ },
120
+ heading: {
121
+ attrs: { level: { default: 1 }, align: { default: null } },
122
+ content: "inline*",
123
+ group: "block",
124
+ defining: true,
125
+ parseDOM: [
126
+ {
127
+ tag: "h1",
128
+ getAttrs(node) {
129
+ return {
130
+ level: 1,
131
+ algin: node.style.textAlign || null
132
+ };
133
+ }
134
+ },
135
+ {
136
+ tag: "h2",
137
+ getAttrs(node) {
138
+ return {
139
+ level: 2,
140
+ algin: node.style.textAlign || null
141
+ };
142
+ }
143
+ },
144
+ {
145
+ tag: "h3",
146
+ getAttrs(node) {
147
+ return {
148
+ level: 3,
149
+ algin: node.style.textAlign || null
150
+ };
151
+ }
152
+ },
153
+ {
154
+ tag: "h4",
155
+ getAttrs(node) {
156
+ return {
157
+ level: 4,
158
+ algin: node.style.textAlign || null
159
+ };
160
+ }
161
+ },
162
+ {
163
+ tag: "h5",
164
+ getAttrs(node) {
165
+ return {
166
+ level: 5,
167
+ algin: node.style.textAlign || null
168
+ };
169
+ }
170
+ },
171
+ {
172
+ tag: "h6",
173
+ getAttrs(node) {
174
+ return {
175
+ level: 6,
176
+ algin: node.style.textAlign || null
177
+ };
178
+ }
179
+ }
180
+ ],
181
+ toDOM(node) {
182
+ return [
183
+ "h" + node.attrs.level,
184
+ {
185
+ id: node.textContent.toLowerCase().replace(/\s+/g, "-"),
186
+ style: node.attrs.align ? `text-align: ${node.attrs.align};` : null
187
+ },
188
+ 0
189
+ ];
190
+ }
191
+ },
192
+ horizontal_rule: {
193
+ group: "block",
194
+ parseDOM: [{ tag: "hr" }],
195
+ toDOM() {
196
+ return ["hr"];
197
+ }
198
+ },
199
+ image: {
200
+ attrs: {
201
+ src: { validate: "string" },
202
+ alt: { default: null, validate: "string|null" },
203
+ title: {
204
+ default: null,
205
+ validate: "string|null"
206
+ },
207
+ width: {
208
+ default: null,
209
+ validate: "number|null"
210
+ },
211
+ height: {
212
+ default: null,
213
+ validate: "number|null"
214
+ }
215
+ },
216
+ inline: false,
217
+ group: "block",
218
+ draggable: true,
219
+ parseDOM: [
220
+ {
221
+ tag: "img",
222
+ getAttrs(dom) {
223
+ return {
224
+ src: dom.getAttribute("src"),
225
+ alt: dom.getAttribute("alt"),
226
+ width: dom.getAttribute("width"),
227
+ height: dom.getAttribute("height")
228
+ };
229
+ }
230
+ }
231
+ ],
232
+ toDOM(node) {
233
+ const { src, alt, srcSet, sizes, width, height } = node.attrs;
234
+ return [
235
+ "img",
236
+ {
237
+ src,
238
+ alt,
239
+ srcSet,
240
+ sizes,
241
+ width,
242
+ height
243
+ }
244
+ ];
245
+ }
246
+ },
247
+ video: {
248
+ inline: false,
249
+ group: "block",
250
+ draggable: true,
251
+ attrs: {
252
+ src: { validate: "string" },
253
+ title: {
254
+ default: null,
255
+ validate: "string|null"
256
+ },
257
+ width: {
258
+ default: null,
259
+ validate: "number|null"
260
+ },
261
+ height: {
262
+ default: null,
263
+ validate: "number|null"
264
+ },
265
+ poster: {
266
+ default: null,
267
+ validate: "string|null"
268
+ }
269
+ },
270
+ parseDOM: [
271
+ {
272
+ tag: "video",
273
+ getAttrs(dom) {
274
+ return {
275
+ src: dom.getAttribute("src"),
276
+ title: dom.getAttribute("title"),
277
+ width: dom.getAttribute("width"),
278
+ height: dom.getAttribute("height"),
279
+ poster: dom.getAttribute("poster")
280
+ };
281
+ }
282
+ }
283
+ ],
284
+ toDOM(node) {
285
+ const { src, title, width, height, poster } = node.attrs;
286
+ return [
287
+ "video",
288
+ {
289
+ src,
290
+ title,
291
+ poster,
292
+ width,
293
+ height,
294
+ playsinline: "true",
295
+ controls: "controls",
296
+ style: `aspect-ratio: ${width} / ${height}`
297
+ }
298
+ ];
299
+ }
300
+ },
301
+ iframe: {
302
+ group: "block",
303
+ draggable: true,
304
+ attrs: {
305
+ src: { validate: "string" },
306
+ title: {
307
+ default: null,
308
+ validate: "string|null"
309
+ },
310
+ width: {
311
+ default: null,
312
+ validate: "number|null"
313
+ },
314
+ height: {
315
+ default: null,
316
+ validate: "number|null"
317
+ },
318
+ allow: {
319
+ default: null,
320
+ validate: "string|null"
321
+ },
322
+ allowfullscreen: {
323
+ default: null,
324
+ validate: "string|null"
325
+ },
326
+ referrerPolicy: {
327
+ default: null,
328
+ validate: "string|null"
329
+ },
330
+ style: {
331
+ default: null,
332
+ validate: "string|null"
333
+ }
334
+ },
335
+ parseDOM: [
336
+ {
337
+ tag: "iframe[src]",
338
+ getAttrs(dom) {
339
+ return {
340
+ src: dom.getAttribute("src"),
341
+ title: dom.getAttribute("title"),
342
+ width: dom.getAttribute("width"),
343
+ height: dom.getAttribute("height"),
344
+ style: dom.getAttribute("style"),
345
+ allow: dom.getAttribute("allow"),
346
+ allowfullscreen: dom.getAttribute("allowfullscreen"),
347
+ referrerpolicy: dom.getAttribute("referrerpolicy")
348
+ };
349
+ }
350
+ }
351
+ ],
352
+ toDOM(node) {
353
+ const {
354
+ src,
355
+ title,
356
+ width,
357
+ height,
358
+ allow,
359
+ allowfullscreen,
360
+ referrerpolicy,
361
+ style
362
+ } = node.attrs;
363
+ return [
364
+ "iframe",
365
+ {
366
+ src,
367
+ title,
368
+ width,
369
+ height,
370
+ style,
371
+ allow,
372
+ allowfullscreen,
373
+ referrerpolicy,
374
+ frameborder: "0"
375
+ }
376
+ ];
377
+ }
378
+ },
379
+ blockquote: {
380
+ content: "block+",
381
+ group: "block",
382
+ defining: true,
383
+ parseDOM: [{ tag: "blockquote" }],
384
+ toDOM() {
385
+ return ["blockquote", 0];
386
+ }
387
+ },
388
+ code_block: {
389
+ content: "text*",
390
+ marks: "",
391
+ group: "block",
392
+ code: true,
393
+ defining: true,
394
+ parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
395
+ toDOM(node) {
396
+ const auto = highlighter.highlightAuto(
397
+ node.textContent,
398
+ supportedLanguages
399
+ );
400
+ return [
401
+ "pre",
402
+ {
403
+ class: "hljs"
404
+ },
405
+ [
406
+ "code",
407
+ {
408
+ class: `language-${auto.language}`
409
+ },
410
+ 0
411
+ ]
412
+ ];
413
+ }
414
+ },
415
+ ...spec.nodes
416
+ },
417
+ marks: {
418
+ link: {
419
+ attrs: {
420
+ href: { default: "" },
421
+ title: { default: null }
422
+ },
423
+ inclusive: false,
424
+ parseDOM: [
425
+ {
426
+ tag: "a[href]",
427
+ getAttrs(dom) {
428
+ return {
429
+ href: dom.getAttribute("href"),
430
+ title: dom.getAttribute("title")
431
+ };
432
+ }
433
+ }
434
+ ],
435
+ toDOM(node) {
436
+ const { href, title } = node.attrs;
437
+ const target = "_blank";
438
+ const rel = "noopener noreferrer";
439
+ return ["a", { href, title: title || href, target, rel }, 0];
440
+ }
441
+ },
442
+ bold: {
443
+ parseDOM: [
444
+ { tag: "strong" },
445
+ {
446
+ tag: "b",
447
+ getAttrs: (node) => node.style.fontWeight != "normal" && null
448
+ },
449
+ {
450
+ style: "font-weight=400",
451
+ clearMark: (m) => m.type.name == "strong"
452
+ },
453
+ {
454
+ style: "font-weight",
455
+ getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null
456
+ }
457
+ ],
458
+ toDOM() {
459
+ return ["strong", 0];
460
+ }
461
+ },
462
+ italic: {
463
+ parseDOM: [
464
+ { tag: "em" },
465
+ { tag: "i" },
466
+ { style: "font-style=italic" },
467
+ {
468
+ style: "font-style=normal",
469
+ clearMark: (m) => m.type.name == "em"
470
+ }
471
+ ],
472
+ toDOM() {
473
+ return ["em", 0];
474
+ }
475
+ },
476
+ underline: {
477
+ parseDOM: [
478
+ { tag: "u" },
479
+ {
480
+ style: "text-decoration",
481
+ getAttrs: (value) => value === "underline" && null
482
+ }
483
+ ],
484
+ toDOM() {
485
+ return ["u", 0];
486
+ }
487
+ },
488
+ ...spec.marks
489
+ },
490
+ topNode: spec.topNode
491
+ });
492
+ const prosemirrorSchema = new Schema({
493
+ nodes: addListNodes(customSchema.spec.nodes, "paragraph block*", "block"),
494
+ marks: customSchema.spec.marks
495
+ });
496
+ return prosemirrorSchema;
497
+ }
498
+
499
+ // src/create_text_editor.tsx
500
+ import "rxjs";
501
+
502
+ // src/commands.tsx
503
+ import * as commands from "prosemirror-commands";
504
+ import * as schemaList from "prosemirror-schema-list";
505
+ var createCommands = (schema, view, options = {}) => {
506
+ {
507
+ const isBlockTypeActive = (node, attrs, excludes = []) => {
508
+ const state = view.state;
509
+ const ranges = state.selection.ranges;
510
+ let active = false;
511
+ for (const range of ranges) {
512
+ const { $from, $to } = range;
513
+ state.doc.nodesBetween($from.pos, $to.pos, (n) => {
514
+ if (active) {
515
+ return true;
516
+ }
517
+ if (n.type !== node || excludes.includes(n.type)) {
518
+ return;
519
+ }
520
+ if (!attrs || Object.keys(attrs).every((key) => n.attrs[key] === attrs[key])) {
521
+ active = true;
522
+ }
523
+ });
524
+ return active;
525
+ }
526
+ };
527
+ const setBlockType2 = (node, attrs) => {
528
+ view.focus();
529
+ const nodeType = schema.nodes[node];
530
+ const command = commands.setBlockType(nodeType, attrs);
531
+ command(view.state, view.dispatch);
532
+ };
533
+ const toggleBlockType = (node, attrs) => {
534
+ view.focus();
535
+ const nodeType = schema.nodes[node];
536
+ const command = commands.setBlockType(nodeType, attrs);
537
+ if (isBlockTypeActive(nodeType, attrs)) {
538
+ command(view.state, view.dispatch);
539
+ }
540
+ };
541
+ const toggleMark2 = (mark, attrs, options2) => {
542
+ view.focus();
543
+ const markType = schema.marks[mark];
544
+ const command = commands.toggleMark(markType, attrs, options2);
545
+ command(view.state, view.dispatch);
546
+ };
547
+ const wrapInList2 = (listType, attrs) => {
548
+ view.focus();
549
+ const nodeType = schema.nodes[listType];
550
+ const command = schemaList.wrapInList(nodeType, attrs);
551
+ command(view.state, view.dispatch);
552
+ };
553
+ const clear = () => {
554
+ const tr = view.state.tr.replaceWith(
555
+ 0,
556
+ view.state.doc.content.size,
557
+ schema.nodes.doc.createAndFill()
558
+ );
559
+ view.dispatch(tr);
560
+ };
561
+ return {
562
+ isBlockTypeActive,
563
+ setBlockType: setBlockType2,
564
+ toggleBlockType,
565
+ toggleMark: toggleMark2,
566
+ wrapInList: wrapInList2,
567
+ clear,
568
+ attachFile: (files) => {
569
+ options.attachFile?.(view, files);
570
+ }
571
+ };
572
+ }
573
+ };
574
+
575
+ // src/input.tsx
576
+ import React, {
577
+ useEffect
578
+ } from "react";
579
+ import { debounceTime, filter } from "rxjs";
580
+ function TextEditorInput({
581
+ ref,
582
+ onChange,
583
+ updateDelay = 0,
584
+ ...props
585
+ }) {
586
+ const inputRef = React.useRef(null);
587
+ useEffect(() => {
588
+ const controller = ref.current;
589
+ if (!controller) {
590
+ return;
591
+ }
592
+ const sub = controller.subject.pipe(
593
+ filter((tr) => tr.docChanged),
594
+ debounceTime(updateDelay)
595
+ ).subscribe(() => {
596
+ if (inputRef.current) {
597
+ inputRef.current.value = controller.value;
598
+ const event = new Event("input", { bubbles: true });
599
+ inputRef.current.dispatchEvent(event);
600
+ }
601
+ });
602
+ return () => {
603
+ sub.unsubscribe();
604
+ controller.view.destroy();
605
+ };
606
+ }, []);
607
+ return /* @__PURE__ */ React.createElement("input", { ...props, ref: inputRef, type: "hidden", onInput: onChange });
608
+ }
609
+
610
+ // src/text_editor_controller.tsx
611
+ import {
612
+ EditorState as EditorState2
613
+ } from "prosemirror-state";
614
+ import { EditorView as EditorView3 } from "prosemirror-view";
615
+ import * as commands2 from "prosemirror-commands";
616
+ import { keymap } from "prosemirror-keymap";
617
+
618
+ // src/plugins/drag_and_drop.tsx
619
+ import { Plugin } from "prosemirror-state";
620
+ function dragAndDropPlugin({ attachFile }) {
621
+ return new Plugin({
622
+ props: {
623
+ handleDOMEvents: {
624
+ drop(view, event) {
625
+ if (!attachFile) {
626
+ return;
627
+ }
628
+ const files = event.dataTransfer?.files;
629
+ if (!files || files.length === 0) {
630
+ return;
631
+ }
632
+ event.preventDefault();
633
+ const pos = view.state.selection.$from.pos || view.posAtCoords({
634
+ left: event.clientX,
635
+ top: event.clientY
636
+ })?.pos || null;
637
+ if (pos === null) {
638
+ return;
639
+ }
640
+ const medias = Array.from(files).filter(
641
+ (file) => file.type.startsWith("image/") || file.type.startsWith("video/")
642
+ );
643
+ attachFile(view, medias);
644
+ return true;
645
+ }
646
+ }
647
+ }
648
+ });
649
+ }
650
+
651
+ // src/plugins/upload_placeholder.tsx
652
+ import { Plugin as Plugin2 } from "prosemirror-state";
653
+ import { Decoration, DecorationSet } from "prosemirror-view";
654
+ var uploadPlaceholderPlugin = new Plugin2({
655
+ state: {
656
+ init() {
657
+ return DecorationSet.empty;
658
+ },
659
+ apply(tr, set) {
660
+ set = set.map(tr.mapping, tr.doc);
661
+ const action = tr.getMeta(this);
662
+ if (action && action.add) {
663
+ const { type, width, height } = action.add;
664
+ const widget = document.createElement("div");
665
+ widget.className = "upload-placeholder";
666
+ widget.style.width = `100%`;
667
+ if (type.startsWith("image/") || type.startsWith("video/")) {
668
+ widget.style.aspectRatio = `${width} / ${height}`;
669
+ widget.style.maxWidth = `${width}px`;
670
+ } else {
671
+ widget.style.height = "80px";
672
+ }
673
+ const progress = document.createElement("div");
674
+ progress.className = "upload-progress";
675
+ widget.appendChild(progress);
676
+ const deco = Decoration.widget(action.add.pos, widget, {
677
+ id: action.add.id
678
+ });
679
+ set = set.add(tr.doc, [deco]);
680
+ } else if (action && action.progress) {
681
+ const found = set.find(
682
+ void 0,
683
+ void 0,
684
+ (spec) => spec.id === action.progress.id
685
+ );
686
+ if (found.length) {
687
+ const widget = found[0].type.toDOM;
688
+ const progress = widget.querySelector(".upload-progress");
689
+ if (progress) {
690
+ progress.innerHTML = `${Math.round(action.progress.progress)}%`;
691
+ }
692
+ }
693
+ } else if (action && action.remove) {
694
+ set = set.remove(
695
+ set.find(void 0, void 0, (spec) => spec.id === action.remove.id)
696
+ );
697
+ }
698
+ return set;
699
+ }
700
+ },
701
+ props: {
702
+ decorations(state) {
703
+ return this.getState(state);
704
+ }
705
+ }
706
+ });
707
+ var findPlaceholder = (state, id) => {
708
+ const decos = uploadPlaceholderPlugin.getState(state);
709
+ if (!decos) {
710
+ return null;
711
+ }
712
+ const found = decos.find(void 0, void 0, (spec) => spec.id === id);
713
+ return found.length ? found[0].from : null;
714
+ };
715
+
716
+ // src/plugins/placehoder.tsx
717
+ import "prosemirror-model";
718
+ import { Plugin as Plugin3 } from "prosemirror-state";
719
+ import "prosemirror-view";
720
+ var getFirstChildDescendants = (view) => {
721
+ const nodes = [];
722
+ view.state.doc?.descendants((n) => {
723
+ nodes.push(n);
724
+ });
725
+ return nodes;
726
+ };
727
+ function placeholderPlugin(text) {
728
+ const update = (view) => {
729
+ const decos = uploadPlaceholderPlugin.getState(view.state);
730
+ if (decos && decos.find().length > 0 || view.state.doc.content.content.some((e) => e.type.name !== "paragraph") || view.state.doc.childCount > 1 || getFirstChildDescendants(view).length > 1 || view.state.doc.textContent) {
731
+ view.dom.removeAttribute("data-placeholder");
732
+ } else {
733
+ view.dom.setAttribute("data-placeholder", text);
734
+ }
735
+ };
736
+ return new Plugin3({
737
+ view(view) {
738
+ update(view);
739
+ return { update };
740
+ }
741
+ });
742
+ }
743
+
744
+ // src/text_editor_controller.tsx
745
+ import { history } from "prosemirror-history";
746
+
747
+ // src/plugins/keymap.tsx
748
+ import { TextSelection } from "prosemirror-state";
749
+ import { undo, redo } from "prosemirror-history";
750
+ import { chainCommands, splitBlockAs } from "prosemirror-commands";
751
+ import { splitListItem } from "prosemirror-schema-list";
752
+ function buildKeymap(schema) {
753
+ const keys = {};
754
+ function bind(key, cmd) {
755
+ keys[key] = cmd;
756
+ }
757
+ bind("Mod-z", undo);
758
+ bind("Shift-Mod-z", redo);
759
+ bind("Mod-y", redo);
760
+ const li = schema.nodes.list_item;
761
+ bind(
762
+ "Enter",
763
+ chainCommands(splitListItem(li), (state, dispatch) => {
764
+ const { $head } = state.selection;
765
+ if ($head.parent.type === state.schema.nodes.paragraph) {
766
+ splitBlockAs((n) => {
767
+ return {
768
+ type: n.type,
769
+ attrs: n.attrs
770
+ };
771
+ })(state, dispatch);
772
+ return true;
773
+ }
774
+ return false;
775
+ })
776
+ );
777
+ bind("ArrowDown", (state, dispatch) => {
778
+ const doc = state.doc;
779
+ const lastNode = doc.lastChild;
780
+ if (lastNode && lastNode.type.name !== "paragraph") {
781
+ const paragraphType = state.schema.nodes.paragraph;
782
+ let tr = state.tr;
783
+ const endPos = doc.content.size;
784
+ tr = tr.insert(endPos, paragraphType.create());
785
+ tr = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
786
+ if (dispatch) {
787
+ dispatch(tr);
788
+ }
789
+ return true;
790
+ }
791
+ return false;
792
+ });
793
+ return keys;
794
+ }
795
+
796
+ // src/cn.ts
797
+ function cn(...classes) {
798
+ return classes.filter(Boolean).join(" ").trim();
799
+ }
800
+
801
+ // src/attach_file.tsx
802
+ import "prosemirror-view";
803
+
804
+ // src/base64_file_uploader.ts
805
+ var base64FileUploader = async (file) => {
806
+ const base64 = await new Promise((resolve, reject) => {
807
+ const reader = new FileReader();
808
+ reader.onload = () => {
809
+ resolve(reader.result);
810
+ };
811
+ reader.onerror = reject;
812
+ reader.readAsDataURL(file);
813
+ });
814
+ return {
815
+ src: base64,
816
+ alt: file.name
817
+ };
818
+ };
819
+
820
+ // src/attach_file.tsx
821
+ function createAttachFile({
822
+ schema,
823
+ generateMetadata,
824
+ uploadFile = base64FileUploader
825
+ }) {
826
+ const attachEachFile = async (view, file, pos) => {
827
+ const metadata = generateMetadata ? await generateMetadata(file) : {};
828
+ const id = {};
829
+ view.focus();
830
+ const tr = view.state.tr;
831
+ if (!tr.selection.empty) {
832
+ tr.deleteSelection();
833
+ }
834
+ tr.setMeta(uploadPlaceholderPlugin, {
835
+ add: {
836
+ id,
837
+ pos: pos ?? tr.selection.from,
838
+ type: file.type,
839
+ ...metadata
840
+ }
841
+ });
842
+ view.dispatch(tr);
843
+ const $pos = findPlaceholder(view.state, id);
844
+ if (!$pos) {
845
+ return;
846
+ }
847
+ try {
848
+ const { src, alt } = await uploadFile(file);
849
+ const tr2 = view.state.tr.setMeta(uploadPlaceholderPlugin, {
850
+ remove: { id }
851
+ });
852
+ const createNode = () => {
853
+ if (file.type.startsWith("image/")) {
854
+ return schema.nodes.image.create({
855
+ src,
856
+ alt,
857
+ width: metadata.width,
858
+ height: metadata.height
859
+ });
860
+ }
861
+ if (file.type.startsWith("video/")) {
862
+ return schema.nodes.video.create({
863
+ src,
864
+ width: metadata.width,
865
+ height: metadata.height,
866
+ poster: metadata.poster
867
+ });
868
+ }
869
+ };
870
+ const node = createNode();
871
+ if (!node) {
872
+ return;
873
+ }
874
+ view.dispatch(tr2.replaceWith($pos, $pos, node));
875
+ } catch (e) {
876
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
877
+ }
878
+ };
879
+ return async (view, files, pos) => {
880
+ for (let i = 0; i < files.length; i++) {
881
+ const file = files[i];
882
+ await attachEachFile(view, file, pos);
883
+ }
884
+ };
885
+ }
886
+
887
+ // src/text_editor_controller.tsx
888
+ import { DOMParser, DOMSerializer } from "prosemirror-model";
889
+ import { Subject } from "rxjs";
890
+ function createTextEditorController(container, schema, options, {
891
+ mode,
892
+ state,
893
+ editor,
894
+ defaultValue,
895
+ updateDelay = 500,
896
+ placeholder
897
+ }) {
898
+ const subject = new Subject();
899
+ const prosemirrorParser = DOMParser.fromSchema(schema);
900
+ const prosemirrorSerializer = DOMSerializer.fromSchema(schema);
901
+ const wrapper = document.createElement("div");
902
+ const toInnerHTML = (value) => {
903
+ if (mode === "html") {
904
+ return value;
905
+ }
906
+ return value.split("\n").map((line) => `<p>${line}</p>`).join("");
907
+ };
908
+ wrapper.innerHTML = toInnerHTML(defaultValue ? String(defaultValue) : "");
909
+ const attachFile = createAttachFile({
910
+ schema,
911
+ generateMetadata: options.attachFile?.generateMetadata,
912
+ uploadFile: options.attachFile?.uploadFile
913
+ });
914
+ const view = new EditorView3(container, {
915
+ ...editor,
916
+ attributes: (state2) => {
917
+ const propsAttributes = (() => {
918
+ if (typeof editor?.attributes === "function") {
919
+ return editor.attributes(state2);
920
+ }
921
+ return editor?.attributes;
922
+ })();
923
+ return {
924
+ ...propsAttributes,
925
+ class: cn(options?.className, propsAttributes?.class),
926
+ spellcheck: propsAttributes?.spellcheck || "false",
927
+ style: options.style || "width: 100%; height: inherit; outline: none;"
928
+ };
929
+ },
930
+ state: EditorState2.create({
931
+ ...state,
932
+ schema: state?.schema || schema,
933
+ doc: state?.doc || prosemirrorParser.parse(wrapper),
934
+ plugins: [
935
+ ...state?.plugins || [],
936
+ history({
937
+ newGroupDelay: updateDelay
938
+ }),
939
+ keymap(buildKeymap(schema)),
940
+ keymap(commands2.baseKeymap),
941
+ uploadPlaceholderPlugin,
942
+ dragAndDropPlugin({
943
+ attachFile
944
+ }),
945
+ placeholder && placeholderPlugin(placeholder)
946
+ ].filter((e) => !!e)
947
+ }),
948
+ dispatchTransaction(tr) {
949
+ let result;
950
+ if (editor?.dispatchTransaction) {
951
+ result = editor.dispatchTransaction(tr);
952
+ } else {
953
+ view.updateState(view.state.apply(tr));
954
+ }
955
+ subject.next(tr);
956
+ return result;
957
+ }
958
+ });
959
+ function setValue(value) {
960
+ const wrap = document.createElement("div");
961
+ wrap.innerHTML = toInnerHTML(value);
962
+ const doc = prosemirrorParser.parse(wrap);
963
+ const tr = view.state.tr.replaceWith(
964
+ 0,
965
+ view.state.doc.content.size,
966
+ doc.content
967
+ );
968
+ view.dispatch(tr);
969
+ }
970
+ function toHTML() {
971
+ const fragment = prosemirrorSerializer.serializeFragment(
972
+ view.state.doc.content
973
+ );
974
+ const container2 = document.createElement("div");
975
+ container2.appendChild(fragment);
976
+ return container2.innerHTML;
977
+ }
978
+ function toTextContent() {
979
+ const state2 = view.state;
980
+ return state2.doc.textBetween(0, state2.doc.content.size, "\n");
981
+ }
982
+ const textEditorCommands = createCommands(schema, view, {
983
+ attachFile
984
+ });
985
+ const textEditorController = {
986
+ schema,
987
+ view,
988
+ subject,
989
+ set value(value) {
990
+ setValue(value);
991
+ },
992
+ get value() {
993
+ switch (mode) {
994
+ case "text":
995
+ return toTextContent();
996
+ default:
997
+ return toHTML();
998
+ }
999
+ },
1000
+ commands: textEditorCommands
1001
+ };
1002
+ return textEditorController;
1003
+ }
1004
+
1005
+ // src/create_text_editor.tsx
1006
+ function createTextEditor(options = {}) {
1007
+ const schema = createSchema();
1008
+ function Component({
1009
+ ref,
1010
+ className,
1011
+ autoFocus,
1012
+ placeholder,
1013
+ defaultValue,
1014
+ onChange,
1015
+ updateDelay,
1016
+ ...props
1017
+ } = {}) {
1018
+ const containerRef = useRef(null);
1019
+ const controllerRef = useRef(null);
1020
+ useImperativeHandle(ref, () => {
1021
+ const container = containerRef.current;
1022
+ const textEditorController = createTextEditorController(
1023
+ container,
1024
+ schema,
1025
+ options,
1026
+ props
1027
+ );
1028
+ controllerRef.current = textEditorController;
1029
+ return textEditorController;
1030
+ });
1031
+ useEffect2(() => {
1032
+ const controller = controllerRef.current;
1033
+ if (!controller) {
1034
+ return;
1035
+ }
1036
+ if (autoFocus) {
1037
+ controller.view.focus();
1038
+ }
1039
+ }, []);
1040
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { ...props, ref: containerRef, className }), /* @__PURE__ */ React2.createElement(
1041
+ TextEditorInput,
1042
+ {
1043
+ ref: controllerRef,
1044
+ updateDelay,
1045
+ onChange
1046
+ }
1047
+ ));
1048
+ }
1049
+ return Component;
1050
+ }
1051
+ export {
1052
+ createTextEditor
1053
+ };