dn-react-text-editor 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -1,222 +1,3 @@
1
- # React Store Input
1
+ # React Text Editor
2
2
 
3
- The goal of this package is to make state management easier when using input elements in React.
4
-
5
- It eliminates repetitive code required to implement state changes and subscriptions for input elements, and provides a simple interface.
6
-
7
- At the same time, it allows you to use all the attributes originally provided by the input tag as-is, without needing to learn this package.
8
-
9
- ## Get Started
10
-
11
- This is a simple example of how to use this package.
12
-
13
- ```tsx
14
- import { useFormStore } from "dn-react-input";
15
-
16
- export default function App() {
17
- const store = useFormStore({
18
- email: "",
19
- password: "",
20
- });
21
-
22
- const submit = async () => {
23
- const { email, password } = store.state;
24
-
25
- alert(`Email: ${email}\nPassword: ${password}`);
26
- };
27
-
28
- return (
29
- <form
30
- onSubmit={(e) => {
31
- e.preventDefault();
32
- submit();
33
- }}
34
- >
35
- <store.input name="email" type="email" />
36
- <store.input name="password" type="password" />
37
- <button type="submit">Submit</button>
38
- </form>
39
- );
40
- }
41
- ```
42
-
43
- ## How to define state?
44
-
45
- You can define any state you want as an object when calling `useStore`.
46
-
47
- ```tsx
48
- function Component() {
49
- ...
50
-
51
- const store = useStore({
52
- email: "",
53
- password: "",
54
- rememberMe: false,
55
- });
56
-
57
- ...
58
- }
59
- ```
60
-
61
- It's a single source of truth for your form state.
62
-
63
- ## How to get input values?
64
-
65
- You can access the current values of the input elements through the `state` property of the store.
66
-
67
- ```tsx
68
- function Component() {
69
- ...
70
-
71
- const submit = () => {
72
- const { email, password, rememberMe } = store.state;
73
- };
74
-
75
- ...
76
- }
77
- ```
78
-
79
- ## How to add input elements?
80
-
81
- You can add input elements using the `Input` component provided by the store. There are 'Select' and 'Textarea' components as well.
82
-
83
- ```tsx
84
- import { Input } from "dn-react-input";
85
-
86
- function Component() {
87
- ...
88
-
89
- return (
90
- <form>
91
- <Input store={store} name="email" type="email" />
92
- <Input store={store} name="password" type="password" />
93
- <Input store={store} name="rememberMe" type="checkbox" />
94
- </form>
95
- );
96
- }
97
- ```
98
-
99
- If you want to avoid passing the store to each input component, use `useStoreInput`. This hook provides input components that are already connected to the store.
100
-
101
- ```tsx
102
- import { useStoreInput } from "dn-react-input";
103
-
104
- function Component() {
105
- ...
106
- const Input = useStoreInput(store);
107
-
108
- return (
109
- <form>
110
- <Input.input name="email" type="email" />
111
- <Input.input name="password" type="password" />
112
- <Input.input name="rememberMe" type="checkbox" />
113
- </form>
114
- );
115
- }
116
- ```
117
-
118
- `useFormStore` is a facade that combines `useStore` and `useStoreInput` for convenience.
119
-
120
- ```tsx
121
- import { useFormStore } from "dn-react-input";
122
-
123
- function Component() {
124
- ...
125
- const store = useFormStore({
126
- email: "",
127
- password: "",
128
- rememberMe: false,
129
- });
130
-
131
- return (
132
- <form>
133
- <store.input name="email" type="email" />
134
- <store.input name="password" type="password" />
135
- <store.input name="rememberMe" type="checkbox" />
136
- </form>
137
- );
138
- }
139
- ```
140
-
141
- ## How to render components on state changes?
142
-
143
- If you want to render a component only when specific parts of the state change, use the `useSelector` hook.
144
-
145
- ```tsx
146
- import { useSelector } from "dn-react-input";
147
-
148
- function Component() {
149
- ...
150
- const email = useSelector(store, (state) => state.email);
151
-
152
- return <div>Your email is: {email}</div>;
153
- }
154
- ```
155
-
156
- If you want to render components in an inline manner, use the `createRender` function. By using this, you can avoid creating separate components for each part of the state you want to track.
157
-
158
- ```tsx
159
- import { createRender } from "dn-react-input";
160
-
161
- function Component() {
162
- ...
163
- return (
164
- <div>
165
- {createRender(store, (state) => <p>{state.email}</p>)}
166
- {createRender(store, (state) => <p>{state.password}</p>)}
167
- </div>
168
- );
169
- }
170
- ```
171
-
172
- ## How to subscribe to state changes?
173
-
174
- You can subscribe to state changes using the `subscribe` method of the store.
175
-
176
- ```tsx
177
- function Component() {
178
- ...
179
- useEffect(() => {
180
- const unsubscribe = store.subscribe((state) => {
181
- console.log(`State changed`, state);
182
- });
183
-
184
- return () => {
185
- unsubscribe();
186
- };
187
- }, []);
188
-
189
- ...
190
- }
191
- ```
192
-
193
- ## How to update state manually?
194
-
195
- You can update the state manually using the `dispatch` method of the store.
196
-
197
- ```tsx
198
- function Component() {
199
- ...
200
- const updateEmail = () => {
201
- store.dispatch({ email: "ohjinsu98@icloud.com" });
202
- };
203
-
204
- return <button onClick={updateEmail}>Update Email</button>;
205
- }
206
- ```
207
-
208
- The `dispatch` method uses immerjs internally to update the state, so you can also use a function to update the state based on the previous state.
209
-
210
- ```tsx
211
- function Component() {
212
- ...
213
-
214
- const updateEmail = () => {
215
- store.dispatch((state) => {
216
- state.email = "ohjinsu98@icloud.com";
217
- });
218
- };
219
-
220
- return <button onClick={updateEmail}>Update Email</button>;
221
- }
222
- ```
3
+ A rich text editor component for React built on ProseMirror.
@@ -23,6 +23,10 @@ type AttachFileOptions = {
23
23
  alt?: string;
24
24
  };
25
25
  };
26
+ declare const base64ImageUploader: (file: File) => Promise<{
27
+ src: string;
28
+ alt: string;
29
+ }>;
26
30
  declare function createAttachFile({ schema, generateMetadata, uploadFile, }: AttachFileOptions): AttachFile;
27
31
 
28
- export { type AttachFile, createAttachFile };
32
+ export { type AttachFile, base64ImageUploader, createAttachFile };
@@ -23,6 +23,10 @@ type AttachFileOptions = {
23
23
  alt?: string;
24
24
  };
25
25
  };
26
+ declare const base64ImageUploader: (file: File) => Promise<{
27
+ src: string;
28
+ alt: string;
29
+ }>;
26
30
  declare function createAttachFile({ schema, generateMetadata, uploadFile, }: AttachFileOptions): AttachFile;
27
31
 
28
- export { type AttachFile, createAttachFile };
32
+ export { type AttachFile, base64ImageUploader, createAttachFile };
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/attach_file.tsx
21
21
  var attach_file_exports = {};
22
22
  __export(attach_file_exports, {
23
+ base64ImageUploader: () => base64ImageUploader,
23
24
  createAttachFile: () => createAttachFile
24
25
  });
25
26
  module.exports = __toCommonJS(attach_file_exports);
@@ -91,15 +92,24 @@ var findPlaceholder = (state, id) => {
91
92
  };
92
93
 
93
94
  // src/attach_file.tsx
95
+ var base64ImageUploader = async (file) => {
96
+ const base64 = await new Promise((resolve, reject) => {
97
+ const reader = new FileReader();
98
+ reader.onload = () => {
99
+ resolve(reader.result);
100
+ };
101
+ reader.onerror = reject;
102
+ reader.readAsDataURL(file);
103
+ });
104
+ return {
105
+ src: base64,
106
+ alt: file.name
107
+ };
108
+ };
94
109
  function createAttachFile({
95
110
  schema,
96
111
  generateMetadata,
97
- uploadFile = (file) => {
98
- return {
99
- src: URL.createObjectURL(file),
100
- alt: file.name
101
- };
102
- }
112
+ uploadFile = base64ImageUploader
103
113
  }) {
104
114
  const attachEachFile = async (view, file, pos) => {
105
115
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -151,9 +161,7 @@ function createAttachFile({
151
161
  }
152
162
  view.dispatch(tr2.replaceWith($pos, $pos, node));
153
163
  } catch (e) {
154
- view.dispatch(
155
- tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
156
- );
164
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
157
165
  }
158
166
  };
159
167
  return async (view, files, pos) => {
@@ -165,5 +173,6 @@ function createAttachFile({
165
173
  }
166
174
  // Annotate the CommonJS export names for ESM import in node:
167
175
  0 && (module.exports = {
176
+ base64ImageUploader,
168
177
  createAttachFile
169
178
  });
@@ -67,15 +67,24 @@ var findPlaceholder = (state, id) => {
67
67
  };
68
68
 
69
69
  // src/attach_file.tsx
70
+ var base64ImageUploader = async (file) => {
71
+ const base64 = await new Promise((resolve, reject) => {
72
+ const reader = new FileReader();
73
+ reader.onload = () => {
74
+ resolve(reader.result);
75
+ };
76
+ reader.onerror = reject;
77
+ reader.readAsDataURL(file);
78
+ });
79
+ return {
80
+ src: base64,
81
+ alt: file.name
82
+ };
83
+ };
70
84
  function createAttachFile({
71
85
  schema,
72
86
  generateMetadata,
73
- uploadFile = (file) => {
74
- return {
75
- src: URL.createObjectURL(file),
76
- alt: file.name
77
- };
78
- }
87
+ uploadFile = base64ImageUploader
79
88
  }) {
80
89
  const attachEachFile = async (view, file, pos) => {
81
90
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -127,9 +136,7 @@ function createAttachFile({
127
136
  }
128
137
  view.dispatch(tr2.replaceWith($pos, $pos, node));
129
138
  } catch (e) {
130
- view.dispatch(
131
- tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
132
- );
139
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
133
140
  }
134
141
  };
135
142
  return async (view, files, pos) => {
@@ -140,5 +147,6 @@ function createAttachFile({
140
147
  };
141
148
  }
142
149
  export {
150
+ base64ImageUploader,
143
151
  createAttachFile
144
152
  };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { TextEditorController, TextEditorProps, createTextEditor } from './text_editor.mjs';
2
2
  export { createSchema } from './schema.mjs';
3
- export { AttachFile, createAttachFile } from './attach_file.mjs';
3
+ export { AttachFile, base64ImageUploader, createAttachFile } from './attach_file.mjs';
4
4
  import 'orderedmap';
5
5
  import 'prosemirror-model';
6
6
  import 'react';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { TextEditorController, TextEditorProps, createTextEditor } from './text_editor.js';
2
2
  export { createSchema } from './schema.js';
3
- export { AttachFile, createAttachFile } from './attach_file.js';
3
+ export { AttachFile, base64ImageUploader, createAttachFile } from './attach_file.js';
4
4
  import 'orderedmap';
5
5
  import 'prosemirror-model';
6
6
  import 'react';
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ base64ImageUploader: () => base64ImageUploader,
33
34
  createAttachFile: () => createAttachFile,
34
35
  createSchema: () => createSchema,
35
36
  createTextEditor: () => createTextEditor
@@ -472,15 +473,24 @@ function cn(...classes) {
472
473
 
473
474
  // src/attach_file.tsx
474
475
  var import_prosemirror_view3 = require("prosemirror-view");
476
+ var base64ImageUploader = async (file) => {
477
+ const base64 = await new Promise((resolve, reject) => {
478
+ const reader = new FileReader();
479
+ reader.onload = () => {
480
+ resolve(reader.result);
481
+ };
482
+ reader.onerror = reject;
483
+ reader.readAsDataURL(file);
484
+ });
485
+ return {
486
+ src: base64,
487
+ alt: file.name
488
+ };
489
+ };
475
490
  function createAttachFile({
476
491
  schema,
477
492
  generateMetadata,
478
- uploadFile = (file) => {
479
- return {
480
- src: URL.createObjectURL(file),
481
- alt: file.name
482
- };
483
- }
493
+ uploadFile = base64ImageUploader
484
494
  }) {
485
495
  const attachEachFile = async (view, file, pos) => {
486
496
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -532,9 +542,7 @@ function createAttachFile({
532
542
  }
533
543
  view.dispatch(tr2.replaceWith($pos, $pos, node));
534
544
  } catch (e) {
535
- view.dispatch(
536
- tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
537
- );
545
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
538
546
  }
539
547
  };
540
548
  return async (view, files, pos) => {
@@ -573,12 +581,9 @@ function createTextEditor(options = {}) {
573
581
  } = {}) {
574
582
  const containerRef = (0, import_react2.useRef)(null);
575
583
  const inputRef = (0, import_react2.useRef)(null);
576
- (0, import_react2.useEffect)(() => {
577
- const element = containerRef.current;
578
- if (!element) {
579
- return;
580
- }
581
- const subject = new import_rxjs.Subject();
584
+ const controllerRef = (0, import_react2.useRef)(null);
585
+ const subject = (0, import_react.useMemo)(() => new import_rxjs.Subject(), []);
586
+ (0, import_react.useImperativeHandle)(ref, () => {
582
587
  const wrapper = document.createElement("div");
583
588
  const toInnerHTML = (value) => {
584
589
  if (mode === "html") {
@@ -587,7 +592,7 @@ function createTextEditor(options = {}) {
587
592
  return value.split("\n").map((line) => `<p>${line}</p>`).join("");
588
593
  };
589
594
  wrapper.innerHTML = toInnerHTML(defaultValue ? String(defaultValue) : "");
590
- const view = new import_prosemirror_view4.EditorView(element, {
595
+ const view = new import_prosemirror_view4.EditorView(containerRef.current, {
591
596
  ...editor,
592
597
  attributes: (state2) => {
593
598
  const propsAttributes = (() => {
@@ -662,23 +667,6 @@ function createTextEditor(options = {}) {
662
667
  const state2 = view.state;
663
668
  return state2.doc.textBetween(0, state2.doc.content.size, "\n");
664
669
  }
665
- const sub = subject.pipe(
666
- (0, import_rxjs.filter)((tr) => tr.docChanged),
667
- (0, import_rxjs.debounceTime)(updateDelay)
668
- ).subscribe(() => {
669
- if (inputRef.current) {
670
- switch (mode) {
671
- case "text":
672
- inputRef.current.value = toTextContent();
673
- break;
674
- default:
675
- inputRef.current.value = toHTML();
676
- break;
677
- }
678
- const event = new Event("input", { bubbles: true });
679
- inputRef.current.dispatchEvent(event);
680
- }
681
- });
682
670
  if (autoFocus) {
683
671
  view.focus();
684
672
  }
@@ -699,15 +687,30 @@ function createTextEditor(options = {}) {
699
687
  },
700
688
  clear
701
689
  };
702
- if (typeof ref === "function") {
703
- ref(textEditorController);
704
- } else if (ref) {
705
- ref.current = textEditorController;
690
+ controllerRef.current = textEditorController;
691
+ return textEditorController;
692
+ });
693
+ (0, import_react2.useEffect)(() => {
694
+ const controller = controllerRef.current;
695
+ if (!controller) {
696
+ return;
697
+ }
698
+ const sub = controller.subject.pipe(
699
+ (0, import_rxjs.filter)((tr) => tr.docChanged),
700
+ (0, import_rxjs.debounceTime)(updateDelay)
701
+ ).subscribe(() => {
702
+ if (inputRef.current) {
703
+ inputRef.current.value = controller.value;
704
+ const event = new Event("input", { bubbles: true });
705
+ inputRef.current.dispatchEvent(event);
706
+ }
707
+ });
708
+ if (autoFocus) {
709
+ controller.view.focus();
706
710
  }
707
711
  return () => {
708
712
  sub.unsubscribe();
709
- view.destroy();
710
- element.innerHTML = "";
713
+ controller.view.destroy();
711
714
  };
712
715
  }, []);
713
716
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { ref: containerRef, className: container, ...props }), /* @__PURE__ */ import_react.default.createElement("input", { ref: inputRef, type: "hidden", name, onInput: onChange }));
@@ -720,6 +723,7 @@ function createTextEditor(options = {}) {
720
723
  }
721
724
  // Annotate the CommonJS export names for ESM import in node:
722
725
  0 && (module.exports = {
726
+ base64ImageUploader,
723
727
  createAttachFile,
724
728
  createSchema,
725
729
  createTextEditor
package/dist/index.mjs CHANGED
@@ -1,5 +1,8 @@
1
1
  // src/text_editor.tsx
2
- import React from "react";
2
+ import React, {
3
+ useImperativeHandle,
4
+ useMemo
5
+ } from "react";
3
6
  import {
4
7
  EditorState as EditorState2
5
8
  } from "prosemirror-state";
@@ -436,15 +439,24 @@ function cn(...classes) {
436
439
 
437
440
  // src/attach_file.tsx
438
441
  import "prosemirror-view";
442
+ var base64ImageUploader = async (file) => {
443
+ const base64 = await new Promise((resolve, reject) => {
444
+ const reader = new FileReader();
445
+ reader.onload = () => {
446
+ resolve(reader.result);
447
+ };
448
+ reader.onerror = reject;
449
+ reader.readAsDataURL(file);
450
+ });
451
+ return {
452
+ src: base64,
453
+ alt: file.name
454
+ };
455
+ };
439
456
  function createAttachFile({
440
457
  schema,
441
458
  generateMetadata,
442
- uploadFile = (file) => {
443
- return {
444
- src: URL.createObjectURL(file),
445
- alt: file.name
446
- };
447
- }
459
+ uploadFile = base64ImageUploader
448
460
  }) {
449
461
  const attachEachFile = async (view, file, pos) => {
450
462
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -496,9 +508,7 @@ function createAttachFile({
496
508
  }
497
509
  view.dispatch(tr2.replaceWith($pos, $pos, node));
498
510
  } catch (e) {
499
- view.dispatch(
500
- tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
501
- );
511
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
502
512
  }
503
513
  };
504
514
  return async (view, files, pos) => {
@@ -537,12 +547,9 @@ function createTextEditor(options = {}) {
537
547
  } = {}) {
538
548
  const containerRef = useRef(null);
539
549
  const inputRef = useRef(null);
540
- useEffect(() => {
541
- const element = containerRef.current;
542
- if (!element) {
543
- return;
544
- }
545
- const subject = new Subject();
550
+ const controllerRef = useRef(null);
551
+ const subject = useMemo(() => new Subject(), []);
552
+ useImperativeHandle(ref, () => {
546
553
  const wrapper = document.createElement("div");
547
554
  const toInnerHTML = (value) => {
548
555
  if (mode === "html") {
@@ -551,7 +558,7 @@ function createTextEditor(options = {}) {
551
558
  return value.split("\n").map((line) => `<p>${line}</p>`).join("");
552
559
  };
553
560
  wrapper.innerHTML = toInnerHTML(defaultValue ? String(defaultValue) : "");
554
- const view = new EditorView3(element, {
561
+ const view = new EditorView3(containerRef.current, {
555
562
  ...editor,
556
563
  attributes: (state2) => {
557
564
  const propsAttributes = (() => {
@@ -626,23 +633,6 @@ function createTextEditor(options = {}) {
626
633
  const state2 = view.state;
627
634
  return state2.doc.textBetween(0, state2.doc.content.size, "\n");
628
635
  }
629
- const sub = subject.pipe(
630
- filter((tr) => tr.docChanged),
631
- debounceTime(updateDelay)
632
- ).subscribe(() => {
633
- if (inputRef.current) {
634
- switch (mode) {
635
- case "text":
636
- inputRef.current.value = toTextContent();
637
- break;
638
- default:
639
- inputRef.current.value = toHTML();
640
- break;
641
- }
642
- const event = new Event("input", { bubbles: true });
643
- inputRef.current.dispatchEvent(event);
644
- }
645
- });
646
636
  if (autoFocus) {
647
637
  view.focus();
648
638
  }
@@ -663,15 +653,30 @@ function createTextEditor(options = {}) {
663
653
  },
664
654
  clear
665
655
  };
666
- if (typeof ref === "function") {
667
- ref(textEditorController);
668
- } else if (ref) {
669
- ref.current = textEditorController;
656
+ controllerRef.current = textEditorController;
657
+ return textEditorController;
658
+ });
659
+ useEffect(() => {
660
+ const controller = controllerRef.current;
661
+ if (!controller) {
662
+ return;
663
+ }
664
+ const sub = controller.subject.pipe(
665
+ filter((tr) => tr.docChanged),
666
+ debounceTime(updateDelay)
667
+ ).subscribe(() => {
668
+ if (inputRef.current) {
669
+ inputRef.current.value = controller.value;
670
+ const event = new Event("input", { bubbles: true });
671
+ inputRef.current.dispatchEvent(event);
672
+ }
673
+ });
674
+ if (autoFocus) {
675
+ controller.view.focus();
670
676
  }
671
677
  return () => {
672
678
  sub.unsubscribe();
673
- view.destroy();
674
- element.innerHTML = "";
679
+ controller.view.destroy();
675
680
  };
676
681
  }, []);
677
682
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { ref: containerRef, className: container, ...props }), /* @__PURE__ */ React.createElement("input", { ref: inputRef, type: "hidden", name, onInput: onChange }));
@@ -683,6 +688,7 @@ function createTextEditor(options = {}) {
683
688
  };
684
689
  }
685
690
  export {
691
+ base64ImageUploader,
686
692
  createAttachFile,
687
693
  createSchema,
688
694
  createTextEditor
@@ -468,15 +468,24 @@ function cn(...classes) {
468
468
 
469
469
  // src/attach_file.tsx
470
470
  var import_prosemirror_view3 = require("prosemirror-view");
471
+ var base64ImageUploader = async (file) => {
472
+ const base64 = await new Promise((resolve, reject) => {
473
+ const reader = new FileReader();
474
+ reader.onload = () => {
475
+ resolve(reader.result);
476
+ };
477
+ reader.onerror = reject;
478
+ reader.readAsDataURL(file);
479
+ });
480
+ return {
481
+ src: base64,
482
+ alt: file.name
483
+ };
484
+ };
471
485
  function createAttachFile({
472
486
  schema,
473
487
  generateMetadata,
474
- uploadFile = (file) => {
475
- return {
476
- src: URL.createObjectURL(file),
477
- alt: file.name
478
- };
479
- }
488
+ uploadFile = base64ImageUploader
480
489
  }) {
481
490
  const attachEachFile = async (view, file, pos) => {
482
491
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -528,9 +537,7 @@ function createAttachFile({
528
537
  }
529
538
  view.dispatch(tr2.replaceWith($pos, $pos, node));
530
539
  } catch (e) {
531
- view.dispatch(
532
- tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
533
- );
540
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
534
541
  }
535
542
  };
536
543
  return async (view, files, pos) => {
@@ -569,12 +576,9 @@ function createTextEditor(options = {}) {
569
576
  } = {}) {
570
577
  const containerRef = (0, import_react2.useRef)(null);
571
578
  const inputRef = (0, import_react2.useRef)(null);
572
- (0, import_react2.useEffect)(() => {
573
- const element = containerRef.current;
574
- if (!element) {
575
- return;
576
- }
577
- const subject = new import_rxjs.Subject();
579
+ const controllerRef = (0, import_react2.useRef)(null);
580
+ const subject = (0, import_react.useMemo)(() => new import_rxjs.Subject(), []);
581
+ (0, import_react.useImperativeHandle)(ref, () => {
578
582
  const wrapper = document.createElement("div");
579
583
  const toInnerHTML = (value) => {
580
584
  if (mode === "html") {
@@ -583,7 +587,7 @@ function createTextEditor(options = {}) {
583
587
  return value.split("\n").map((line) => `<p>${line}</p>`).join("");
584
588
  };
585
589
  wrapper.innerHTML = toInnerHTML(defaultValue ? String(defaultValue) : "");
586
- const view = new import_prosemirror_view4.EditorView(element, {
590
+ const view = new import_prosemirror_view4.EditorView(containerRef.current, {
587
591
  ...editor,
588
592
  attributes: (state2) => {
589
593
  const propsAttributes = (() => {
@@ -658,23 +662,6 @@ function createTextEditor(options = {}) {
658
662
  const state2 = view.state;
659
663
  return state2.doc.textBetween(0, state2.doc.content.size, "\n");
660
664
  }
661
- const sub = subject.pipe(
662
- (0, import_rxjs.filter)((tr) => tr.docChanged),
663
- (0, import_rxjs.debounceTime)(updateDelay)
664
- ).subscribe(() => {
665
- if (inputRef.current) {
666
- switch (mode) {
667
- case "text":
668
- inputRef.current.value = toTextContent();
669
- break;
670
- default:
671
- inputRef.current.value = toHTML();
672
- break;
673
- }
674
- const event = new Event("input", { bubbles: true });
675
- inputRef.current.dispatchEvent(event);
676
- }
677
- });
678
665
  if (autoFocus) {
679
666
  view.focus();
680
667
  }
@@ -695,15 +682,30 @@ function createTextEditor(options = {}) {
695
682
  },
696
683
  clear
697
684
  };
698
- if (typeof ref === "function") {
699
- ref(textEditorController);
700
- } else if (ref) {
701
- ref.current = textEditorController;
685
+ controllerRef.current = textEditorController;
686
+ return textEditorController;
687
+ });
688
+ (0, import_react2.useEffect)(() => {
689
+ const controller = controllerRef.current;
690
+ if (!controller) {
691
+ return;
692
+ }
693
+ const sub = controller.subject.pipe(
694
+ (0, import_rxjs.filter)((tr) => tr.docChanged),
695
+ (0, import_rxjs.debounceTime)(updateDelay)
696
+ ).subscribe(() => {
697
+ if (inputRef.current) {
698
+ inputRef.current.value = controller.value;
699
+ const event = new Event("input", { bubbles: true });
700
+ inputRef.current.dispatchEvent(event);
701
+ }
702
+ });
703
+ if (autoFocus) {
704
+ controller.view.focus();
702
705
  }
703
706
  return () => {
704
707
  sub.unsubscribe();
705
- view.destroy();
706
- element.innerHTML = "";
708
+ controller.view.destroy();
707
709
  };
708
710
  }, []);
709
711
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { ref: containerRef, className: container, ...props }), /* @__PURE__ */ import_react.default.createElement("input", { ref: inputRef, type: "hidden", name, onInput: onChange }));
@@ -1,5 +1,8 @@
1
1
  // src/text_editor.tsx
2
- import React from "react";
2
+ import React, {
3
+ useImperativeHandle,
4
+ useMemo
5
+ } from "react";
3
6
  import {
4
7
  EditorState as EditorState2
5
8
  } from "prosemirror-state";
@@ -436,15 +439,24 @@ function cn(...classes) {
436
439
 
437
440
  // src/attach_file.tsx
438
441
  import "prosemirror-view";
442
+ var base64ImageUploader = async (file) => {
443
+ const base64 = await new Promise((resolve, reject) => {
444
+ const reader = new FileReader();
445
+ reader.onload = () => {
446
+ resolve(reader.result);
447
+ };
448
+ reader.onerror = reject;
449
+ reader.readAsDataURL(file);
450
+ });
451
+ return {
452
+ src: base64,
453
+ alt: file.name
454
+ };
455
+ };
439
456
  function createAttachFile({
440
457
  schema,
441
458
  generateMetadata,
442
- uploadFile = (file) => {
443
- return {
444
- src: URL.createObjectURL(file),
445
- alt: file.name
446
- };
447
- }
459
+ uploadFile = base64ImageUploader
448
460
  }) {
449
461
  const attachEachFile = async (view, file, pos) => {
450
462
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -496,9 +508,7 @@ function createAttachFile({
496
508
  }
497
509
  view.dispatch(tr2.replaceWith($pos, $pos, node));
498
510
  } catch (e) {
499
- view.dispatch(
500
- tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
501
- );
511
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
502
512
  }
503
513
  };
504
514
  return async (view, files, pos) => {
@@ -537,12 +547,9 @@ function createTextEditor(options = {}) {
537
547
  } = {}) {
538
548
  const containerRef = useRef(null);
539
549
  const inputRef = useRef(null);
540
- useEffect(() => {
541
- const element = containerRef.current;
542
- if (!element) {
543
- return;
544
- }
545
- const subject = new Subject();
550
+ const controllerRef = useRef(null);
551
+ const subject = useMemo(() => new Subject(), []);
552
+ useImperativeHandle(ref, () => {
546
553
  const wrapper = document.createElement("div");
547
554
  const toInnerHTML = (value) => {
548
555
  if (mode === "html") {
@@ -551,7 +558,7 @@ function createTextEditor(options = {}) {
551
558
  return value.split("\n").map((line) => `<p>${line}</p>`).join("");
552
559
  };
553
560
  wrapper.innerHTML = toInnerHTML(defaultValue ? String(defaultValue) : "");
554
- const view = new EditorView3(element, {
561
+ const view = new EditorView3(containerRef.current, {
555
562
  ...editor,
556
563
  attributes: (state2) => {
557
564
  const propsAttributes = (() => {
@@ -626,23 +633,6 @@ function createTextEditor(options = {}) {
626
633
  const state2 = view.state;
627
634
  return state2.doc.textBetween(0, state2.doc.content.size, "\n");
628
635
  }
629
- const sub = subject.pipe(
630
- filter((tr) => tr.docChanged),
631
- debounceTime(updateDelay)
632
- ).subscribe(() => {
633
- if (inputRef.current) {
634
- switch (mode) {
635
- case "text":
636
- inputRef.current.value = toTextContent();
637
- break;
638
- default:
639
- inputRef.current.value = toHTML();
640
- break;
641
- }
642
- const event = new Event("input", { bubbles: true });
643
- inputRef.current.dispatchEvent(event);
644
- }
645
- });
646
636
  if (autoFocus) {
647
637
  view.focus();
648
638
  }
@@ -663,15 +653,30 @@ function createTextEditor(options = {}) {
663
653
  },
664
654
  clear
665
655
  };
666
- if (typeof ref === "function") {
667
- ref(textEditorController);
668
- } else if (ref) {
669
- ref.current = textEditorController;
656
+ controllerRef.current = textEditorController;
657
+ return textEditorController;
658
+ });
659
+ useEffect(() => {
660
+ const controller = controllerRef.current;
661
+ if (!controller) {
662
+ return;
663
+ }
664
+ const sub = controller.subject.pipe(
665
+ filter((tr) => tr.docChanged),
666
+ debounceTime(updateDelay)
667
+ ).subscribe(() => {
668
+ if (inputRef.current) {
669
+ inputRef.current.value = controller.value;
670
+ const event = new Event("input", { bubbles: true });
671
+ inputRef.current.dispatchEvent(event);
672
+ }
673
+ });
674
+ if (autoFocus) {
675
+ controller.view.focus();
670
676
  }
671
677
  return () => {
672
678
  sub.unsubscribe();
673
- view.destroy();
674
- element.innerHTML = "";
679
+ controller.view.destroy();
675
680
  };
676
681
  }, []);
677
682
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { ref: containerRef, className: container, ...props }), /* @__PURE__ */ React.createElement("input", { ref: inputRef, type: "hidden", name, onInput: onChange }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dn-react-text-editor",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "types": "./dist/index.d.ts",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.js",