dn-react-text-editor 0.2.4 → 0.3.1

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.
package/dist/index.mjs CHANGED
@@ -1,11 +1,265 @@
1
1
  // src/create_text_editor.tsx
2
2
  import React2, {
3
- useImperativeHandle
3
+ useMemo
4
4
  } from "react";
5
- import "prosemirror-state";
6
- import "prosemirror-view";
7
5
  import { useEffect as useEffect2, useRef } from "react";
8
6
 
7
+ // src/input.tsx
8
+ import React, {
9
+ useEffect
10
+ } from "react";
11
+ import { debounceTime, filter } from "rxjs";
12
+ function TextEditorInput({ controller, onChange, ...props }) {
13
+ const inputRef = React.useRef(null);
14
+ useEffect(() => {
15
+ const sub = controller.subject.pipe(
16
+ filter((tr) => tr.docChanged),
17
+ debounceTime(controller.props.updateDelay || 0)
18
+ ).subscribe(() => {
19
+ if (inputRef.current) {
20
+ inputRef.current.value = controller.value;
21
+ const event = new Event("input", { bubbles: true });
22
+ inputRef.current.dispatchEvent(event);
23
+ }
24
+ });
25
+ return () => {
26
+ sub.unsubscribe();
27
+ };
28
+ }, []);
29
+ return /* @__PURE__ */ React.createElement(
30
+ "input",
31
+ {
32
+ ...props,
33
+ ref: inputRef,
34
+ type: "hidden",
35
+ onInput: onChange,
36
+ defaultValue: controller.props.defaultValue
37
+ }
38
+ );
39
+ }
40
+
41
+ // src/text_editor_controller.tsx
42
+ import {
43
+ EditorState as EditorState2
44
+ } from "prosemirror-state";
45
+ import { EditorView as EditorView3 } from "prosemirror-view";
46
+ import * as commands2 from "prosemirror-commands";
47
+ import { keymap } from "prosemirror-keymap";
48
+
49
+ // src/plugins/drag_and_drop.tsx
50
+ import { Plugin } from "prosemirror-state";
51
+ function dragAndDropPlugin({ attachFile }) {
52
+ return new Plugin({
53
+ props: {
54
+ handleDOMEvents: {
55
+ drop(view, event) {
56
+ if (!attachFile) {
57
+ return;
58
+ }
59
+ const files = event.dataTransfer?.files;
60
+ if (!files || files.length === 0) {
61
+ return;
62
+ }
63
+ event.preventDefault();
64
+ const pos = view.state.selection.$from.pos || view.posAtCoords({
65
+ left: event.clientX,
66
+ top: event.clientY
67
+ })?.pos || null;
68
+ if (pos === null) {
69
+ return;
70
+ }
71
+ const medias = Array.from(files).filter(
72
+ (file) => file.type.startsWith("image/") || file.type.startsWith("video/")
73
+ );
74
+ attachFile(view, medias);
75
+ return true;
76
+ }
77
+ }
78
+ }
79
+ });
80
+ }
81
+
82
+ // src/plugins/upload_placeholder.tsx
83
+ import { Plugin as Plugin2 } from "prosemirror-state";
84
+ import { Decoration, DecorationSet } from "prosemirror-view";
85
+ var uploadPlaceholderPlugin = new Plugin2({
86
+ state: {
87
+ init() {
88
+ return DecorationSet.empty;
89
+ },
90
+ apply(tr, set) {
91
+ set = set.map(tr.mapping, tr.doc);
92
+ const action = tr.getMeta(this);
93
+ if (action && action.add) {
94
+ const { type, width, height } = action.add;
95
+ const widget = document.createElement("div");
96
+ widget.className = "upload-placeholder";
97
+ widget.style.width = `100%`;
98
+ if (type.startsWith("image/") || type.startsWith("video/")) {
99
+ widget.style.aspectRatio = `${width} / ${height}`;
100
+ widget.style.maxWidth = `${width}px`;
101
+ } else {
102
+ widget.style.height = "80px";
103
+ }
104
+ const progress = document.createElement("div");
105
+ progress.className = "upload-progress";
106
+ widget.appendChild(progress);
107
+ const deco = Decoration.widget(action.add.pos, widget, {
108
+ id: action.add.id
109
+ });
110
+ set = set.add(tr.doc, [deco]);
111
+ } else if (action && action.progress) {
112
+ const found = set.find(
113
+ void 0,
114
+ void 0,
115
+ (spec) => spec.id === action.progress.id
116
+ );
117
+ if (found.length) {
118
+ const widget = found[0].type.toDOM;
119
+ const progress = widget.querySelector(".upload-progress");
120
+ if (progress) {
121
+ progress.innerHTML = `${Math.round(action.progress.progress)}%`;
122
+ }
123
+ }
124
+ } else if (action && action.remove) {
125
+ set = set.remove(
126
+ set.find(void 0, void 0, (spec) => spec.id === action.remove.id)
127
+ );
128
+ }
129
+ return set;
130
+ }
131
+ },
132
+ props: {
133
+ decorations(state) {
134
+ return this.getState(state);
135
+ }
136
+ }
137
+ });
138
+ var findPlaceholder = (state, id) => {
139
+ const decos = uploadPlaceholderPlugin.getState(state);
140
+ if (!decos) {
141
+ return null;
142
+ }
143
+ const found = decos.find(void 0, void 0, (spec) => spec.id === id);
144
+ return found.length ? found[0].from : null;
145
+ };
146
+
147
+ // src/plugins/placehoder.tsx
148
+ import "prosemirror-model";
149
+ import { Plugin as Plugin3 } from "prosemirror-state";
150
+ import "prosemirror-view";
151
+ var getFirstChildDescendants = (view) => {
152
+ const nodes = [];
153
+ view.state.doc?.descendants((n) => {
154
+ nodes.push(n);
155
+ });
156
+ return nodes;
157
+ };
158
+ function placeholderPlugin(text) {
159
+ const update = (view) => {
160
+ const decos = uploadPlaceholderPlugin.getState(view.state);
161
+ 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) {
162
+ view.dom.removeAttribute("data-placeholder");
163
+ } else {
164
+ view.dom.setAttribute("data-placeholder", text);
165
+ }
166
+ };
167
+ return new Plugin3({
168
+ view(view) {
169
+ update(view);
170
+ return { update };
171
+ }
172
+ });
173
+ }
174
+
175
+ // src/text_editor_controller.tsx
176
+ import { history } from "prosemirror-history";
177
+
178
+ // src/plugins/keymap.tsx
179
+ import { TextSelection } from "prosemirror-state";
180
+ import { undo, redo } from "prosemirror-history";
181
+ import { chainCommands, splitBlockAs } from "prosemirror-commands";
182
+ import { splitListItem } from "prosemirror-schema-list";
183
+ function buildKeymap(schema) {
184
+ const keys = {};
185
+ function bind(key, cmd) {
186
+ keys[key] = cmd;
187
+ }
188
+ bind("Mod-z", undo);
189
+ bind("Shift-Mod-z", redo);
190
+ bind("Mod-y", redo);
191
+ bind("ArrowDown", (state, dispatch) => {
192
+ const doc = state.doc;
193
+ const lastNode = doc.lastChild;
194
+ if (lastNode && lastNode.type.name !== "paragraph") {
195
+ const paragraphType = state.schema.nodes.paragraph;
196
+ let tr = state.tr;
197
+ const endPos = doc.content.size;
198
+ tr = tr.insert(endPos, paragraphType.create());
199
+ tr = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
200
+ if (dispatch) {
201
+ dispatch(tr);
202
+ }
203
+ return true;
204
+ }
205
+ return false;
206
+ });
207
+ const li = schema.nodes.list_item;
208
+ bind(
209
+ "Enter",
210
+ chainCommands(
211
+ splitListItem(li),
212
+ (state, dispatch) => {
213
+ const { $head } = state.selection;
214
+ if ($head.parent.type === state.schema.nodes.paragraph) {
215
+ splitBlockAs((n) => {
216
+ return {
217
+ type: n.type,
218
+ attrs: n.attrs
219
+ };
220
+ })(state, dispatch);
221
+ return true;
222
+ }
223
+ return false;
224
+ },
225
+ (state, dispatch) => {
226
+ const { selection } = state;
227
+ const { $from, $to } = selection;
228
+ const lines = state.doc.textBetween($from.before(), $to.pos).split("\n");
229
+ const currentLine = lines[lines.length - 1];
230
+ const match = currentLine.match(/^(\s+).*$/);
231
+ if (match) {
232
+ if (dispatch) {
233
+ dispatch(state.tr.insertText("\n" + match[1], $from.pos));
234
+ }
235
+ return true;
236
+ }
237
+ return false;
238
+ }
239
+ )
240
+ );
241
+ bind("Tab", (state, dispatch) => {
242
+ const { selection } = state;
243
+ const { $from, $to } = selection;
244
+ if ($from.parent.type === schema.nodes.code_block) {
245
+ if (dispatch) {
246
+ dispatch(state.tr.insertText(" ", $from.pos, $to.pos));
247
+ }
248
+ return true;
249
+ }
250
+ return false;
251
+ });
252
+ return keys;
253
+ }
254
+
255
+ // src/cn.ts
256
+ function cn(...classes) {
257
+ return classes.filter(Boolean).join(" ").trim();
258
+ }
259
+
260
+ // src/attach_file.tsx
261
+ import "prosemirror-view";
262
+
9
263
  // src/schema.tsx
10
264
  import { Schema } from "prosemirror-model";
11
265
  import { addListNodes } from "prosemirror-schema-list";
@@ -496,317 +750,12 @@ function createSchema(spec = { nodes: {}, marks: {} }) {
496
750
  return prosemirrorSchema;
497
751
  }
498
752
 
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);
753
+ // src/base64_file_uploader.ts
754
+ var base64FileUploader = async (file) => {
755
+ const base64 = await new Promise((resolve, reject) => {
756
+ const reader = new FileReader();
757
+ reader.onload = () => {
758
+ resolve(reader.result);
810
759
  };
811
760
  reader.onerror = reject;
812
761
  reader.readAsDataURL(file);
@@ -871,7 +820,12 @@ function createAttachFile({
871
820
  if (!node) {
872
821
  return;
873
822
  }
874
- view.dispatch(tr2.replaceWith($pos, $pos, node));
823
+ const current = view.state.doc.resolve($pos);
824
+ if (current.parentOffset === 0) {
825
+ view.dispatch(tr2.replaceWith($pos - 1, $pos, node));
826
+ } else {
827
+ view.dispatch(tr2.replaceWith($pos, $pos, node));
828
+ }
875
829
  } catch (e) {
876
830
  view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
877
831
  }
@@ -884,129 +838,212 @@ function createAttachFile({
884
838
  };
885
839
  }
886
840
 
841
+ // src/commands.tsx
842
+ import * as commands from "prosemirror-commands";
843
+ import * as schemaList from "prosemirror-schema-list";
844
+ var createCommands = (schema, view, options = {}) => {
845
+ {
846
+ const isBlockTypeActive = (node, attrs, excludes = []) => {
847
+ const state = view.state;
848
+ const ranges = state.selection.ranges;
849
+ let active = false;
850
+ for (const range of ranges) {
851
+ const { $from, $to } = range;
852
+ state.doc.nodesBetween($from.pos, $to.pos, (n) => {
853
+ if (active) {
854
+ return true;
855
+ }
856
+ if (n.type !== node || excludes.includes(n.type)) {
857
+ return;
858
+ }
859
+ if (!attrs || Object.keys(attrs).every((key) => n.attrs[key] === attrs[key])) {
860
+ active = true;
861
+ }
862
+ });
863
+ return active;
864
+ }
865
+ };
866
+ const setBlockType2 = (node, attrs) => {
867
+ view.focus();
868
+ const nodeType = schema.nodes[node];
869
+ const command = commands.setBlockType(nodeType, attrs);
870
+ command(view.state, view.dispatch);
871
+ };
872
+ const toggleBlockType = (node, attrs) => {
873
+ view.focus();
874
+ const nodeType = schema.nodes[node];
875
+ const command = commands.setBlockType(nodeType, attrs);
876
+ if (isBlockTypeActive(nodeType, attrs)) {
877
+ command(view.state, view.dispatch);
878
+ }
879
+ };
880
+ const toggleMark2 = (mark, attrs, options2) => {
881
+ view.focus();
882
+ const markType = schema.marks[mark];
883
+ const command = commands.toggleMark(markType, attrs, options2);
884
+ command(view.state, view.dispatch);
885
+ };
886
+ const wrapInList2 = (listType, attrs) => {
887
+ view.focus();
888
+ const nodeType = schema.nodes[listType];
889
+ const command = schemaList.wrapInList(nodeType, attrs);
890
+ command(view.state, view.dispatch);
891
+ };
892
+ const clear = () => {
893
+ const tr = view.state.tr.replaceWith(
894
+ 0,
895
+ view.state.doc.content.size,
896
+ schema.nodes.doc.createAndFill()
897
+ );
898
+ view.dispatch(tr);
899
+ };
900
+ return {
901
+ isBlockTypeActive,
902
+ setBlockType: setBlockType2,
903
+ toggleBlockType,
904
+ toggleMark: toggleMark2,
905
+ wrapInList: wrapInList2,
906
+ clear,
907
+ attachFile: (files) => {
908
+ options.attachFile?.(view, files);
909
+ }
910
+ };
911
+ }
912
+ };
913
+
887
914
  // src/text_editor_controller.tsx
888
915
  import { DOMParser, DOMSerializer } from "prosemirror-model";
889
916
  import { Subject } from "rxjs";
890
- function createTextEditorController(container, schema, options, {
891
- mode = "html",
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 === "text") {
917
+ import { highlightPlugin } from "prosemirror-highlightjs";
918
+ var TextEditorController = class {
919
+ schema;
920
+ props;
921
+ subject;
922
+ view;
923
+ prosemirrorParser;
924
+ prosemirrorSerializer;
925
+ get value() {
926
+ if (this.props.mode === "text") {
927
+ return this.toTextContent();
928
+ }
929
+ return this.toHTML();
930
+ }
931
+ set value(value) {
932
+ const wrap = document.createElement("div");
933
+ wrap.innerHTML = this.toInnerHTML(value);
934
+ const doc = this.prosemirrorParser.parse(wrap);
935
+ const tr = this.view.state.tr.replaceWith(
936
+ 0,
937
+ this.view.state.doc.content.size,
938
+ doc.content
939
+ );
940
+ this.view.dispatch(tr);
941
+ }
942
+ constructor(props = {}) {
943
+ this.schema = createSchema();
944
+ this.props = props;
945
+ this.subject = new Subject();
946
+ this.prosemirrorParser = DOMParser.fromSchema(this.schema);
947
+ this.prosemirrorSerializer = DOMSerializer.fromSchema(this.schema);
948
+ }
949
+ toInnerHTML(value) {
950
+ if (this.props.mode === "text") {
904
951
  return value.split("\n").map((line) => `<p>${line}</p>`).join("");
905
952
  }
906
953
  return value;
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);
954
+ }
955
+ attachFile(files) {
956
+ return createAttachFile({
957
+ schema: this.schema,
958
+ generateMetadata: this.props.attachFile?.generateMetadata,
959
+ uploadFile: this.props.attachFile?.uploadFile
960
+ })(this.view, files);
961
+ }
962
+ bind(container) {
963
+ const wrapper = document.createElement("div");
964
+ wrapper.innerHTML = this.toInnerHTML(
965
+ this.props.defaultValue ? String(this.props.defaultValue) : ""
966
+ );
967
+ this.view = new EditorView3(container, {
968
+ ...this.props.editor,
969
+ attributes: (state) => {
970
+ const propsAttributes = (() => {
971
+ if (typeof this.props.editor?.attributes === "function") {
972
+ return this.props.editor.attributes(state);
973
+ }
974
+ return this.props.editor?.attributes;
975
+ })();
976
+ return {
977
+ ...propsAttributes,
978
+ class: cn(this.props?.className, propsAttributes?.class),
979
+ spellcheck: propsAttributes?.spellcheck || "false",
980
+ style: this.props.style || "width: 100%; height: inherit; outline: none;"
981
+ };
982
+ },
983
+ state: EditorState2.create({
984
+ ...this.props.state,
985
+ schema: this.props.state?.schema || this.schema,
986
+ doc: this.props.state?.doc || this.prosemirrorParser.parse(wrapper),
987
+ plugins: [
988
+ ...this.props.state?.plugins || [],
989
+ history({
990
+ newGroupDelay: this.props.updateDelay
991
+ }),
992
+ keymap(buildKeymap(this.schema)),
993
+ keymap(commands2.baseKeymap),
994
+ uploadPlaceholderPlugin,
995
+ dragAndDropPlugin({
996
+ attachFile: (view, files) => this.attachFile(files)
997
+ }),
998
+ this.props.placeholder && placeholderPlugin(this.props.placeholder),
999
+ highlightPlugin(highlighter, ["code_block"], (node) => {
1000
+ const auto = highlighter.highlightAuto(node.textContent);
1001
+ return auto.language || "";
1002
+ })
1003
+ ].filter((e) => !!e)
1004
+ }),
1005
+ dispatchTransaction: (tr) => {
1006
+ let result;
1007
+ if (this.props.editor?.dispatchTransaction) {
1008
+ result = this.props.editor.dispatchTransaction(tr);
1009
+ } else {
1010
+ this.view?.updateState(this.view.state.apply(tr));
920
1011
  }
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));
1012
+ this.subject.next(tr);
1013
+ return result;
954
1014
  }
955
- subject.next(tr);
956
- return result;
1015
+ });
1016
+ if (this.props.autoFocus) {
1017
+ this.view?.focus();
957
1018
  }
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
1019
  }
970
- function toHTML() {
971
- const fragment = prosemirrorSerializer.serializeFragment(
972
- view.state.doc.content
1020
+ toHTML() {
1021
+ const fragment = this.prosemirrorSerializer.serializeFragment(
1022
+ this.view.state.doc.content
973
1023
  );
974
- const container2 = document.createElement("div");
975
- container2.appendChild(fragment);
976
- return container2.innerHTML;
1024
+ const container = document.createElement("div");
1025
+ container.appendChild(fragment);
1026
+ return container.innerHTML;
977
1027
  }
978
- function toTextContent() {
979
- const state2 = view.state;
980
- return state2.doc.textBetween(0, state2.doc.content.size, "\n");
1028
+ toTextContent() {
1029
+ const state = this.view.state;
1030
+ return state.doc.textBetween(0, state.doc.content.size, "\n");
981
1031
  }
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
- }
1032
+ get commands() {
1033
+ return createCommands(this.schema, this.view, {
1034
+ attachFile: (view, files) => this.attachFile(files)
1035
+ });
1036
+ }
1037
+ dispose() {
1038
+ this.view?.destroy();
1039
+ }
1040
+ };
1004
1041
 
1005
1042
  // src/create_text_editor.tsx
1006
1043
  function createTextEditor(options = {}) {
1007
- const schema = createSchema();
1008
1044
  function Component({
1009
- ref,
1045
+ controller: externalController,
1046
+ name,
1010
1047
  className,
1011
1048
  autoFocus,
1012
1049
  onChange,
@@ -1019,47 +1056,37 @@ function createTextEditor(options = {}) {
1019
1056
  ...props
1020
1057
  } = {}) {
1021
1058
  const containerRef = useRef(null);
1022
- const controllerRef = useRef(null);
1023
- useImperativeHandle(
1024
- ref || controllerRef,
1025
- () => {
1026
- const container = containerRef.current;
1027
- const textEditorController = createTextEditorController(
1028
- container,
1029
- schema,
1030
- options,
1031
- {
1032
- mode,
1033
- state,
1034
- editor,
1035
- defaultValue,
1036
- updateDelay,
1037
- placeholder
1038
- }
1039
- );
1040
- controllerRef.current = textEditorController;
1041
- return textEditorController;
1042
- },
1059
+ const innerController = useMemo(
1060
+ () => new TextEditorController({
1061
+ mode,
1062
+ state,
1063
+ editor,
1064
+ autoFocus,
1065
+ placeholder,
1066
+ updateDelay,
1067
+ defaultValue,
1068
+ className: options.className,
1069
+ style: options.style,
1070
+ attachFile: options.attachFile
1071
+ }),
1043
1072
  []
1044
1073
  );
1074
+ const controller = externalController || innerController;
1045
1075
  useEffect2(() => {
1046
- const controller = controllerRef.current;
1047
- if (!controller) {
1076
+ const container = containerRef.current;
1077
+ if (!container) {
1048
1078
  return;
1049
1079
  }
1050
- if (autoFocus) {
1051
- controller.view.focus();
1052
- }
1080
+ controller.bind(container);
1053
1081
  return () => {
1054
- controller.view.destroy();
1082
+ controller.dispose();
1055
1083
  };
1056
- }, []);
1084
+ }, [controller]);
1057
1085
  return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { ...props, ref: containerRef, className }), /* @__PURE__ */ React2.createElement(
1058
1086
  TextEditorInput,
1059
1087
  {
1060
- ref: controllerRef,
1061
- updateDelay,
1062
- defaultValue,
1088
+ name,
1089
+ controller,
1063
1090
  onChange
1064
1091
  }
1065
1092
  ));
@@ -1074,11 +1101,12 @@ function createInnerHTML(raw) {
1074
1101
  return raw.replace(/<\/p>/g, "<br></p>").replace(/(<p><br><\/p>)+$/g, "").replace(
1075
1102
  /<code class="language-(\w+)">([\s\S]*?)<\/code>/g,
1076
1103
  (_, lang, code) => {
1104
+ if (lang === "undefined") {
1105
+ return `<code>${decode(code)}</code>`;
1106
+ }
1077
1107
  try {
1078
- const highlighted = highlighter.highlight(code, {
1079
- language: lang
1080
- }).value;
1081
- return `<code class="language-${lang}">${decode(highlighted)}</code>`;
1108
+ const { language, value } = highlighter.highlightAuto(code);
1109
+ return `<code class="language-${language}">${decode(value)}</code>`;
1082
1110
  } catch (e) {
1083
1111
  return `<code class="language-${lang}">${decode(code)}</code>`;
1084
1112
  }
@@ -1086,7 +1114,7 @@ function createInnerHTML(raw) {
1086
1114
  ).replace(
1087
1115
  /<a([^>]*target="_blank"[^>]*)>(.*?)<\/a>/g,
1088
1116
  (_, attrs, content) => {
1089
- return `<a${attrs} rel="noopener noreferrer" target="_blank">${content}<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18.25 15.5a.75.75 0 0 1-.75-.75V7.56L7.28 17.78a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L16.44 6.5H9.25a.75.75 0 0 1 0-1.5h9a.75.75 0 0 1 .75.75v9a.75.75 0 0 1-.75.75Z"></path></svg></a>`;
1117
+ return `<a${attrs} rel="noopener noreferrer" target="_blank">${content}<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="display:inline; height:1em;"><path d="M18.25 15.5a.75.75 0 0 1-.75-.75V7.56L7.28 17.78a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L16.44 6.5H9.25a.75.75 0 0 1 0-1.5h9a.75.75 0 0 1 .75.75v9a.75.75 0 0 1-.75.75Z"></path></svg></a>`;
1090
1118
  }
1091
1119
  );
1092
1120
  }
@@ -1111,6 +1139,7 @@ function createTextEditorView(options = {}) {
1111
1139
  };
1112
1140
  }
1113
1141
  export {
1142
+ TextEditorController,
1114
1143
  createAttachFile,
1115
1144
  createInnerHTML,
1116
1145
  createSchema,