dn-react-text-editor 0.1.2 → 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 +19 -0
  2. package/dist/attach_file.d.mts +20 -22
  3. package/dist/attach_file.d.ts +20 -22
  4. package/dist/attach_file.js +5 -5
  5. package/dist/attach_file.mjs +5 -4
  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 +770 -364
  25. package/dist/index.mjs +765 -359
  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 -722
  55. package/dist/text_editor.mjs +0 -692
@@ -1,692 +0,0 @@
1
- // src/text_editor.tsx
2
- import React, {
3
- useImperativeHandle,
4
- useMemo
5
- } from "react";
6
- import {
7
- EditorState as EditorState2
8
- } from "prosemirror-state";
9
- import { EditorView as EditorView3 } from "prosemirror-view";
10
- import { useEffect, useRef } from "react";
11
- import { baseKeymap } from "prosemirror-commands";
12
- import { keymap } from "prosemirror-keymap";
13
-
14
- // src/plugins/drag_and_drop.tsx
15
- import { Plugin } from "prosemirror-state";
16
- function dragAndDropPlugin({ attachFile }) {
17
- return new Plugin({
18
- props: {
19
- handleDOMEvents: {
20
- drop(view, event) {
21
- const files = event.dataTransfer?.files;
22
- if (!files || files.length === 0) {
23
- return;
24
- }
25
- event.preventDefault();
26
- const pos = view.state.selection.$from.pos || view.posAtCoords({
27
- left: event.clientX,
28
- top: event.clientY
29
- })?.pos || null;
30
- if (pos === null) {
31
- return;
32
- }
33
- const medias = Array.from(files).filter(
34
- (file) => file.type.startsWith("image/") || file.type.startsWith("video/")
35
- );
36
- attachFile(view, medias);
37
- return true;
38
- }
39
- }
40
- }
41
- });
42
- }
43
-
44
- // src/plugins/upload_placeholder.tsx
45
- import { Plugin as Plugin2 } from "prosemirror-state";
46
- import { Decoration, DecorationSet } from "prosemirror-view";
47
- var uploadPlaceholderPlugin = new Plugin2({
48
- state: {
49
- init() {
50
- return DecorationSet.empty;
51
- },
52
- apply(tr, set) {
53
- set = set.map(tr.mapping, tr.doc);
54
- const action = tr.getMeta(this);
55
- if (action && action.add) {
56
- const { type, width, height } = action.add;
57
- const widget = document.createElement("div");
58
- widget.className = "upload-placeholder";
59
- widget.style.width = `100%`;
60
- if (type.startsWith("image/") || type.startsWith("video/")) {
61
- widget.style.aspectRatio = `${width} / ${height}`;
62
- widget.style.maxWidth = `${width}px`;
63
- } else {
64
- widget.style.height = "80px";
65
- }
66
- const progress = document.createElement("div");
67
- progress.className = "upload-progress";
68
- widget.appendChild(progress);
69
- const deco = Decoration.widget(action.add.pos, widget, {
70
- id: action.add.id
71
- });
72
- set = set.add(tr.doc, [deco]);
73
- } else if (action && action.progress) {
74
- const found = set.find(
75
- void 0,
76
- void 0,
77
- (spec) => spec.id === action.progress.id
78
- );
79
- if (found.length) {
80
- const widget = found[0].type.toDOM;
81
- const progress = widget.querySelector(".upload-progress");
82
- if (progress) {
83
- progress.innerHTML = `${Math.round(action.progress.progress)}%`;
84
- }
85
- }
86
- } else if (action && action.remove) {
87
- set = set.remove(
88
- set.find(void 0, void 0, (spec) => spec.id === action.remove.id)
89
- );
90
- }
91
- return set;
92
- }
93
- },
94
- props: {
95
- decorations(state) {
96
- return this.getState(state);
97
- }
98
- }
99
- });
100
- var findPlaceholder = (state, id) => {
101
- const decos = uploadPlaceholderPlugin.getState(state);
102
- if (!decos) {
103
- return null;
104
- }
105
- const found = decos.find(void 0, void 0, (spec) => spec.id === id);
106
- return found.length ? found[0].from : null;
107
- };
108
-
109
- // src/plugins/placehoder.tsx
110
- import "prosemirror-model";
111
- import { Plugin as Plugin3 } from "prosemirror-state";
112
- import "prosemirror-view";
113
- var getFirstChildDescendants = (view) => {
114
- const nodes = [];
115
- view.state.doc?.descendants((n) => {
116
- nodes.push(n);
117
- });
118
- return nodes;
119
- };
120
- function placeholderPlugin(text) {
121
- const update = (view) => {
122
- const decos = uploadPlaceholderPlugin.getState(view.state);
123
- 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) {
124
- view.dom.removeAttribute("data-placeholder");
125
- } else {
126
- view.dom.setAttribute("data-placeholder", text);
127
- }
128
- };
129
- return new Plugin3({
130
- view(view) {
131
- update(view);
132
- return { update };
133
- }
134
- });
135
- }
136
-
137
- // src/text_editor.tsx
138
- import { DOMSerializer, DOMParser } from "prosemirror-model";
139
- import { history } from "prosemirror-history";
140
-
141
- // src/plugins/keymap.tsx
142
- import { undo, redo } from "prosemirror-history";
143
- import { chainCommands, splitBlockAs } from "prosemirror-commands";
144
- import { splitListItem } from "prosemirror-schema-list";
145
- function buildKeymap(schema) {
146
- const keys = {};
147
- function bind(key, cmd) {
148
- keys[key] = cmd;
149
- }
150
- bind("Mod-z", undo);
151
- bind("Shift-Mod-z", redo);
152
- bind("Mod-y", redo);
153
- const li = schema.nodes.list_item;
154
- bind(
155
- "Enter",
156
- chainCommands(splitListItem(li), (state, dispatch) => {
157
- const { $head } = state.selection;
158
- if ($head.parent.type === state.schema.nodes.paragraph) {
159
- splitBlockAs((n) => {
160
- return {
161
- type: n.type,
162
- attrs: n.attrs
163
- };
164
- })(state, dispatch);
165
- return true;
166
- }
167
- return false;
168
- })
169
- );
170
- return keys;
171
- }
172
-
173
- // src/schema.tsx
174
- import { Schema } from "prosemirror-model";
175
- import { addListNodes } from "prosemirror-schema-list";
176
- function createSchema() {
177
- const customSchema = new Schema({
178
- nodes: {
179
- doc: { content: "block+" },
180
- paragraph: {
181
- attrs: { align: { default: null } },
182
- content: "inline*",
183
- group: "block",
184
- parseDOM: [
185
- {
186
- tag: "p",
187
- getAttrs(dom) {
188
- return {
189
- align: dom.style.textAlign || null
190
- };
191
- }
192
- }
193
- ],
194
- toDOM(node) {
195
- return [
196
- "p",
197
- {
198
- style: node.attrs.align ? `text-align: ${node.attrs.align}` : null
199
- },
200
- 0
201
- ];
202
- }
203
- },
204
- text: {
205
- group: "inline"
206
- },
207
- hard_break: {
208
- inline: true,
209
- group: "inline",
210
- selectable: false,
211
- parseDOM: [{ tag: "br" }],
212
- toDOM() {
213
- return ["br"];
214
- }
215
- },
216
- heading: {
217
- attrs: { level: { default: 1 }, align: { default: null } },
218
- content: "inline*",
219
- group: "block",
220
- defining: true,
221
- parseDOM: [
222
- {
223
- tag: "h1",
224
- getAttrs(node) {
225
- return {
226
- level: 1,
227
- algin: node.style.textAlign || null
228
- };
229
- }
230
- },
231
- {
232
- tag: "h2",
233
- getAttrs(node) {
234
- return {
235
- level: 2,
236
- algin: node.style.textAlign || null
237
- };
238
- }
239
- },
240
- {
241
- tag: "h3",
242
- getAttrs(node) {
243
- return {
244
- level: 3,
245
- algin: node.style.textAlign || null
246
- };
247
- }
248
- },
249
- {
250
- tag: "h4",
251
- getAttrs(node) {
252
- return {
253
- level: 4,
254
- algin: node.style.textAlign || null
255
- };
256
- }
257
- },
258
- {
259
- tag: "h5",
260
- getAttrs(node) {
261
- return {
262
- level: 5,
263
- algin: node.style.textAlign || null
264
- };
265
- }
266
- },
267
- {
268
- tag: "h6",
269
- getAttrs(node) {
270
- return {
271
- level: 6,
272
- algin: node.style.textAlign || null
273
- };
274
- }
275
- }
276
- ],
277
- toDOM(node) {
278
- return [
279
- "h" + node.attrs.level,
280
- {
281
- id: node.textContent.toLowerCase().replace(/\s+/g, "-"),
282
- style: node.attrs.align ? `text-align: ${node.attrs.align};` : null
283
- },
284
- 0
285
- ];
286
- }
287
- },
288
- horizontal_rule: {
289
- group: "block",
290
- parseDOM: [{ tag: "hr" }],
291
- toDOM() {
292
- return ["hr"];
293
- }
294
- },
295
- image: {
296
- attrs: {
297
- src: { validate: "string" },
298
- alt: { default: null, validate: "string|null" },
299
- title: {
300
- default: null,
301
- validate: "string|null"
302
- },
303
- width: {
304
- default: null,
305
- validate: "number|null"
306
- },
307
- height: {
308
- default: null,
309
- validate: "number|null"
310
- }
311
- },
312
- inline: false,
313
- group: "block",
314
- draggable: true,
315
- parseDOM: [
316
- {
317
- tag: "img",
318
- getAttrs(dom) {
319
- return {
320
- src: dom.getAttribute("src"),
321
- alt: dom.getAttribute("alt"),
322
- width: dom.getAttribute("width"),
323
- height: dom.getAttribute("height")
324
- };
325
- }
326
- }
327
- ],
328
- toDOM(node) {
329
- const { src, alt, srcSet, sizes, width, height } = node.attrs;
330
- return [
331
- "img",
332
- {
333
- src,
334
- alt,
335
- srcSet,
336
- sizes,
337
- width,
338
- height
339
- }
340
- ];
341
- }
342
- }
343
- },
344
- marks: {
345
- link: {
346
- attrs: {
347
- href: { default: "" },
348
- title: { default: null }
349
- },
350
- inclusive: false,
351
- parseDOM: [
352
- {
353
- tag: "a[href]",
354
- getAttrs(dom) {
355
- return {
356
- href: dom.getAttribute("href"),
357
- title: dom.getAttribute("title")
358
- };
359
- }
360
- }
361
- ],
362
- toDOM(node) {
363
- const { href, title } = node.attrs;
364
- const target = "_blank";
365
- const rel = "noopener noreferrer";
366
- return [
367
- "a",
368
- { href, title: title || href, target, rel },
369
- 0
370
- ];
371
- }
372
- },
373
- bold: {
374
- parseDOM: [
375
- { tag: "strong" },
376
- {
377
- tag: "b",
378
- getAttrs: (node) => node.style.fontWeight != "normal" && null
379
- },
380
- {
381
- style: "font-weight=400",
382
- clearMark: (m) => m.type.name == "strong"
383
- },
384
- {
385
- style: "font-weight",
386
- getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null
387
- }
388
- ],
389
- toDOM() {
390
- return ["strong", 0];
391
- }
392
- },
393
- italic: {
394
- parseDOM: [
395
- { tag: "em" },
396
- { tag: "i" },
397
- { style: "font-style=italic" },
398
- {
399
- style: "font-style=normal",
400
- clearMark: (m) => m.type.name == "em"
401
- }
402
- ],
403
- toDOM() {
404
- return ["em", 0];
405
- }
406
- },
407
- underline: {
408
- parseDOM: [
409
- { tag: "u" },
410
- {
411
- style: "text-decoration",
412
- getAttrs: (value) => value === "underline" && null
413
- }
414
- ],
415
- toDOM() {
416
- return ["u", 0];
417
- }
418
- }
419
- }
420
- });
421
- const prosemirrorSchema = new Schema({
422
- nodes: addListNodes(
423
- customSchema.spec.nodes,
424
- "paragraph block*",
425
- "block"
426
- ),
427
- marks: customSchema.spec.marks
428
- });
429
- return prosemirrorSchema;
430
- }
431
-
432
- // src/text_editor.tsx
433
- import { debounceTime, filter, Subject } from "rxjs";
434
-
435
- // src/cn.ts
436
- function cn(...classes) {
437
- return classes.filter(Boolean).join(" ").trim();
438
- }
439
-
440
- // src/attach_file.tsx
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
- };
456
- function createAttachFile({
457
- schema,
458
- generateMetadata,
459
- uploadFile = base64ImageUploader
460
- }) {
461
- const attachEachFile = async (view, file, pos) => {
462
- const metadata = generateMetadata ? await generateMetadata(file) : {};
463
- const id = {};
464
- view.focus();
465
- const tr = view.state.tr;
466
- if (!tr.selection.empty) {
467
- tr.deleteSelection();
468
- }
469
- tr.setMeta(uploadPlaceholderPlugin, {
470
- add: {
471
- id,
472
- pos: pos ?? tr.selection.from,
473
- type: file.type,
474
- ...metadata
475
- }
476
- });
477
- view.dispatch(tr);
478
- const $pos = findPlaceholder(view.state, id);
479
- if (!$pos) {
480
- return;
481
- }
482
- try {
483
- const { src, alt } = await uploadFile(file);
484
- const tr2 = view.state.tr.setMeta(uploadPlaceholderPlugin, {
485
- remove: { id }
486
- });
487
- const createNode = () => {
488
- if (file.type.startsWith("image/")) {
489
- return schema.nodes.image.create({
490
- src,
491
- alt,
492
- width: metadata.width,
493
- height: metadata.height
494
- });
495
- }
496
- if (file.type.startsWith("video/")) {
497
- return schema.nodes.video.create({
498
- src,
499
- width: metadata.width,
500
- height: metadata.height,
501
- poster: metadata.poster
502
- });
503
- }
504
- };
505
- const node = createNode();
506
- if (!node) {
507
- return;
508
- }
509
- view.dispatch(tr2.replaceWith($pos, $pos, node));
510
- } catch (e) {
511
- view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
512
- }
513
- };
514
- return async (view, files, pos) => {
515
- for (let i = 0; i < files.length; i++) {
516
- const file = files[i];
517
- await attachEachFile(view, file, pos);
518
- }
519
- };
520
- }
521
-
522
- // src/text_editor.tsx
523
- function createTextEditor(options = {}) {
524
- const schema = createSchema();
525
- const prosemirrorParser = DOMParser.fromSchema(schema);
526
- const prosemirrorSerializer = DOMSerializer.fromSchema(schema);
527
- const attachFile = createAttachFile({
528
- schema,
529
- generateMetadata: options.attachFile?.generateMetadata,
530
- uploadFile: options.attachFile?.uploadFile
531
- });
532
- function Component({
533
- ref,
534
- state,
535
- editor,
536
- mode = "html",
537
- container,
538
- autoFocus,
539
- name,
540
- placeholder,
541
- className,
542
- defaultValue,
543
- onClick,
544
- onChange,
545
- updateDelay = 0,
546
- ...props
547
- } = {}) {
548
- const containerRef = useRef(null);
549
- const inputRef = useRef(null);
550
- const controllerRef = useRef(null);
551
- const subject = useMemo(() => new Subject(), []);
552
- useImperativeHandle(ref, () => {
553
- const wrapper = document.createElement("div");
554
- const toInnerHTML = (value) => {
555
- if (mode === "html") {
556
- return value;
557
- }
558
- return value.split("\n").map((line) => `<p>${line}</p>`).join("");
559
- };
560
- wrapper.innerHTML = toInnerHTML(defaultValue ? String(defaultValue) : "");
561
- const view = new EditorView3(containerRef.current, {
562
- ...editor,
563
- attributes: (state2) => {
564
- const propsAttributes = (() => {
565
- if (typeof editor?.attributes === "function") {
566
- return editor.attributes(state2);
567
- }
568
- return editor?.attributes;
569
- })();
570
- return {
571
- ...propsAttributes,
572
- class: cn(propsAttributes?.class, className),
573
- spellcheck: propsAttributes?.spellcheck || "false"
574
- };
575
- },
576
- state: EditorState2.create({
577
- ...state,
578
- schema: state?.schema || schema,
579
- doc: state?.doc || prosemirrorParser.parse(wrapper),
580
- plugins: [
581
- ...state?.plugins || [],
582
- history({
583
- newGroupDelay: updateDelay
584
- }),
585
- keymap(buildKeymap(schema)),
586
- keymap(baseKeymap),
587
- uploadPlaceholderPlugin,
588
- attachFile ? dragAndDropPlugin({
589
- attachFile
590
- }) : null,
591
- placeholder && placeholderPlugin(placeholder)
592
- ].filter((e) => !!e)
593
- }),
594
- dispatchTransaction(tr) {
595
- let result;
596
- if (editor?.dispatchTransaction) {
597
- result = editor.dispatchTransaction(tr);
598
- } else {
599
- view.updateState(view.state.apply(tr));
600
- }
601
- subject.next(tr);
602
- return result;
603
- }
604
- });
605
- function setValue(value) {
606
- const wrap = document.createElement("div");
607
- wrap.innerHTML = toInnerHTML(value);
608
- const doc = prosemirrorParser.parse(wrap);
609
- const tr = view.state.tr.replaceWith(
610
- 0,
611
- view.state.doc.content.size,
612
- doc.content
613
- );
614
- view.dispatch(tr);
615
- }
616
- function clear() {
617
- const tr = view.state.tr.replaceWith(
618
- 0,
619
- view.state.doc.content.size,
620
- schema.nodes.doc.createAndFill()
621
- );
622
- view.dispatch(tr);
623
- }
624
- function toHTML() {
625
- const fragment = prosemirrorSerializer.serializeFragment(
626
- view.state.doc.content
627
- );
628
- const container2 = document.createElement("div");
629
- container2.appendChild(fragment);
630
- return container2.innerHTML;
631
- }
632
- function toTextContent() {
633
- const state2 = view.state;
634
- return state2.doc.textBetween(0, state2.doc.content.size, "\n");
635
- }
636
- if (autoFocus) {
637
- view.focus();
638
- }
639
- const textEditorController = {
640
- schema,
641
- view,
642
- subject,
643
- set value(value) {
644
- setValue(value);
645
- },
646
- get value() {
647
- switch (mode) {
648
- case "text":
649
- return toTextContent();
650
- default:
651
- return toHTML();
652
- }
653
- },
654
- clear
655
- };
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();
676
- }
677
- return () => {
678
- sub.unsubscribe();
679
- controller.view.destroy();
680
- };
681
- }, []);
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
- }
684
- return {
685
- schema,
686
- attachFile,
687
- Component
688
- };
689
- }
690
- export {
691
- createTextEditor
692
- };