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
package/dist/index.mjs CHANGED
@@ -1,176 +1,83 @@
1
- // src/text_editor.tsx
2
- import React from "react";
3
- import {
4
- EditorState as EditorState2
5
- } from "prosemirror-state";
6
- import { EditorView as EditorView3 } from "prosemirror-view";
7
- import { useEffect, useRef } from "react";
8
- import { baseKeymap } from "prosemirror-commands";
9
- import { keymap } from "prosemirror-keymap";
10
-
11
- // src/plugins/drag_and_drop.tsx
12
- import { Plugin } from "prosemirror-state";
13
- function dragAndDropPlugin({ attachFile }) {
14
- return new Plugin({
15
- props: {
16
- handleDOMEvents: {
17
- drop(view, event) {
18
- const files = event.dataTransfer?.files;
19
- if (!files || files.length === 0) {
20
- return;
21
- }
22
- event.preventDefault();
23
- const pos = view.state.selection.$from.pos || view.posAtCoords({
24
- left: event.clientX,
25
- top: event.clientY
26
- })?.pos || null;
27
- if (pos === null) {
28
- return;
29
- }
30
- const medias = Array.from(files).filter(
31
- (file) => file.type.startsWith("image/") || file.type.startsWith("video/")
32
- );
33
- attachFile(view, medias);
34
- return true;
35
- }
36
- }
37
- }
38
- });
39
- }
40
-
41
- // src/plugins/upload_placeholder.tsx
42
- import { Plugin as Plugin2 } from "prosemirror-state";
43
- import { Decoration, DecorationSet } from "prosemirror-view";
44
- var uploadPlaceholderPlugin = new Plugin2({
45
- state: {
46
- init() {
47
- return DecorationSet.empty;
48
- },
49
- apply(tr, set) {
50
- set = set.map(tr.mapping, tr.doc);
51
- const action = tr.getMeta(this);
52
- if (action && action.add) {
53
- const { type, width, height } = action.add;
54
- const widget = document.createElement("div");
55
- widget.className = "upload-placeholder";
56
- widget.style.width = `100%`;
57
- if (type.startsWith("image/") || type.startsWith("video/")) {
58
- widget.style.aspectRatio = `${width} / ${height}`;
59
- widget.style.maxWidth = `${width}px`;
60
- } else {
61
- widget.style.height = "80px";
62
- }
63
- const progress = document.createElement("div");
64
- progress.className = "upload-progress";
65
- widget.appendChild(progress);
66
- const deco = Decoration.widget(action.add.pos, widget, {
67
- id: action.add.id
68
- });
69
- set = set.add(tr.doc, [deco]);
70
- } else if (action && action.progress) {
71
- const found = set.find(
72
- void 0,
73
- void 0,
74
- (spec) => spec.id === action.progress.id
75
- );
76
- if (found.length) {
77
- const widget = found[0].type.toDOM;
78
- const progress = widget.querySelector(".upload-progress");
79
- if (progress) {
80
- progress.innerHTML = `${Math.round(action.progress.progress)}%`;
81
- }
82
- }
83
- } else if (action && action.remove) {
84
- set = set.remove(
85
- set.find(void 0, void 0, (spec) => spec.id === action.remove.id)
86
- );
87
- }
88
- return set;
89
- }
90
- },
91
- props: {
92
- decorations(state) {
93
- return this.getState(state);
94
- }
95
- }
96
- });
97
- var findPlaceholder = (state, id) => {
98
- const decos = uploadPlaceholderPlugin.getState(state);
99
- if (!decos) {
100
- return null;
101
- }
102
- const found = decos.find(void 0, void 0, (spec) => spec.id === id);
103
- return found.length ? found[0].from : null;
104
- };
105
-
106
- // src/plugins/placehoder.tsx
107
- import "prosemirror-model";
108
- import { Plugin as Plugin3 } from "prosemirror-state";
1
+ // src/create_text_editor.tsx
2
+ import React2, {
3
+ useImperativeHandle
4
+ } from "react";
5
+ import "prosemirror-state";
109
6
  import "prosemirror-view";
110
- var getFirstChildDescendants = (view) => {
111
- const nodes = [];
112
- view.state.doc?.descendants((n) => {
113
- nodes.push(n);
114
- });
115
- return nodes;
116
- };
117
- function placeholderPlugin(text) {
118
- const update = (view) => {
119
- const decos = uploadPlaceholderPlugin.getState(view.state);
120
- 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) {
121
- view.dom.removeAttribute("data-placeholder");
122
- } else {
123
- view.dom.setAttribute("data-placeholder", text);
124
- }
125
- };
126
- return new Plugin3({
127
- view(view) {
128
- update(view);
129
- return { update };
130
- }
131
- });
132
- }
133
-
134
- // src/text_editor.tsx
135
- import { DOMSerializer, DOMParser } from "prosemirror-model";
136
- import { history } from "prosemirror-history";
137
-
138
- // src/plugins/keymap.tsx
139
- import { undo, redo } from "prosemirror-history";
140
- import { chainCommands, splitBlockAs } from "prosemirror-commands";
141
- import { splitListItem } from "prosemirror-schema-list";
142
- function buildKeymap(schema) {
143
- const keys = {};
144
- function bind(key, cmd) {
145
- keys[key] = cmd;
146
- }
147
- bind("Mod-z", undo);
148
- bind("Shift-Mod-z", redo);
149
- bind("Mod-y", redo);
150
- const li = schema.nodes.list_item;
151
- bind(
152
- "Enter",
153
- chainCommands(splitListItem(li), (state, dispatch) => {
154
- const { $head } = state.selection;
155
- if ($head.parent.type === state.schema.nodes.paragraph) {
156
- splitBlockAs((n) => {
157
- return {
158
- type: n.type,
159
- attrs: n.attrs
160
- };
161
- })(state, dispatch);
162
- return true;
163
- }
164
- return false;
165
- })
166
- );
167
- return keys;
168
- }
7
+ import { useEffect as useEffect2, useRef } from "react";
169
8
 
170
9
  // src/schema.tsx
171
10
  import { Schema } from "prosemirror-model";
172
11
  import { addListNodes } from "prosemirror-schema-list";
173
- function createSchema() {
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: {} }) {
174
81
  const customSchema = new Schema({
175
82
  nodes: {
176
83
  doc: { content: "block+" },
@@ -336,68 +243,233 @@ function createSchema() {
336
243
  }
337
244
  ];
338
245
  }
339
- }
340
- },
341
- marks: {
342
- link: {
246
+ },
247
+ video: {
248
+ inline: false,
249
+ group: "block",
250
+ draggable: true,
343
251
  attrs: {
344
- href: { default: "" },
345
- title: { default: null }
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
+ }
346
269
  },
347
- inclusive: false,
348
270
  parseDOM: [
349
271
  {
350
- tag: "a[href]",
272
+ tag: "video",
351
273
  getAttrs(dom) {
352
274
  return {
353
- href: dom.getAttribute("href"),
354
- title: dom.getAttribute("title")
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")
355
280
  };
356
281
  }
357
282
  }
358
283
  ],
359
284
  toDOM(node) {
360
- const { href, title } = node.attrs;
361
- const target = "_blank";
362
- const rel = "noopener noreferrer";
285
+ const { src, title, width, height, poster } = node.attrs;
363
286
  return [
364
- "a",
365
- { href, title: title || href, target, rel },
366
- 0
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
+ }
367
298
  ];
368
299
  }
369
300
  },
370
- bold: {
371
- parseDOM: [
372
- { tag: "strong" },
373
- {
374
- tag: "b",
375
- getAttrs: (node) => node.style.fontWeight != "normal" && null
301
+ iframe: {
302
+ group: "block",
303
+ draggable: true,
304
+ attrs: {
305
+ src: { validate: "string" },
306
+ title: {
307
+ default: null,
308
+ validate: "string|null"
376
309
  },
377
- {
378
- style: "font-weight=400",
379
- clearMark: (m) => m.type.name == "strong"
310
+ width: {
311
+ default: null,
312
+ validate: "number|null"
380
313
  },
381
- {
382
- style: "font-weight",
383
- getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null
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"
384
333
  }
385
- ],
386
- toDOM() {
387
- return ["strong", 0];
388
- }
389
- },
390
- italic: {
334
+ },
391
335
  parseDOM: [
392
- { tag: "em" },
393
- { tag: "i" },
394
- { style: "font-style=italic" },
395
336
  {
396
- style: "font-style=normal",
397
- clearMark: (m) => m.type.name == "em"
398
- }
399
- ],
400
- toDOM() {
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() {
401
473
  return ["em", 0];
402
474
  }
403
475
  },
@@ -412,22 +484,314 @@ function createSchema() {
412
484
  toDOM() {
413
485
  return ["u", 0];
414
486
  }
415
- }
416
- }
487
+ },
488
+ ...spec.marks
489
+ },
490
+ topNode: spec.topNode
417
491
  });
418
492
  const prosemirrorSchema = new Schema({
419
- nodes: addListNodes(
420
- customSchema.spec.nodes,
421
- "paragraph block*",
422
- "block"
423
- ),
493
+ nodes: addListNodes(customSchema.spec.nodes, "paragraph block*", "block"),
424
494
  marks: customSchema.spec.marks
425
495
  });
426
496
  return prosemirrorSchema;
427
497
  }
428
498
 
429
- // src/text_editor.tsx
430
- import { debounceTime, filter, Subject } from "rxjs";
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
+ }
431
795
 
432
796
  // src/cn.ts
433
797
  function cn(...classes) {
@@ -436,15 +800,28 @@ function cn(...classes) {
436
800
 
437
801
  // src/attach_file.tsx
438
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
439
821
  function createAttachFile({
440
822
  schema,
441
823
  generateMetadata,
442
- uploadFile = (file) => {
443
- return {
444
- src: URL.createObjectURL(file),
445
- alt: file.name
446
- };
447
- }
824
+ uploadFile = base64FileUploader
448
825
  }) {
449
826
  const attachEachFile = async (view, file, pos) => {
450
827
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -496,9 +873,7 @@ function createAttachFile({
496
873
  }
497
874
  view.dispatch(tr2.replaceWith($pos, $pos, node));
498
875
  } catch (e) {
499
- view.dispatch(
500
- tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
501
- );
876
+ view.dispatch(tr.setMeta(uploadPlaceholderPlugin, { remove: { id } }));
502
877
  }
503
878
  };
504
879
  return async (view, files, pos) => {
@@ -509,181 +884,218 @@ function createAttachFile({
509
884
  };
510
885
  }
511
886
 
512
- // src/text_editor.tsx
513
- function createTextEditor(options = {}) {
514
- const schema = createSchema();
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();
515
899
  const prosemirrorParser = DOMParser.fromSchema(schema);
516
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) : "");
517
909
  const attachFile = createAttachFile({
518
910
  schema,
519
911
  generateMetadata: options.attachFile?.generateMetadata,
520
912
  uploadFile: options.attachFile?.uploadFile
521
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();
522
1008
  function Component({
523
1009
  ref,
524
- state,
525
- editor,
526
- mode = "html",
527
- container,
1010
+ className,
528
1011
  autoFocus,
529
- name,
530
1012
  placeholder,
531
- className,
532
1013
  defaultValue,
533
- onClick,
534
1014
  onChange,
535
- updateDelay = 0,
1015
+ updateDelay,
536
1016
  ...props
537
1017
  } = {}) {
538
1018
  const containerRef = useRef(null);
539
- const inputRef = useRef(null);
540
- useEffect(() => {
541
- const element = containerRef.current;
542
- if (!element) {
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) {
543
1034
  return;
544
1035
  }
545
- const subject = new Subject();
546
- const wrapper = document.createElement("div");
547
- const toInnerHTML = (value) => {
548
- if (mode === "html") {
549
- return value;
550
- }
551
- return value.split("\n").map((line) => `<p>${line}</p>`).join("");
552
- };
553
- wrapper.innerHTML = toInnerHTML(defaultValue ? String(defaultValue) : "");
554
- const view = new EditorView3(element, {
555
- ...editor,
556
- attributes: (state2) => {
557
- const propsAttributes = (() => {
558
- if (typeof editor?.attributes === "function") {
559
- return editor.attributes(state2);
560
- }
561
- return editor?.attributes;
562
- })();
563
- return {
564
- ...propsAttributes,
565
- class: cn(propsAttributes?.class, className),
566
- spellcheck: propsAttributes?.spellcheck || "false"
567
- };
568
- },
569
- state: EditorState2.create({
570
- ...state,
571
- schema: state?.schema || schema,
572
- doc: state?.doc || prosemirrorParser.parse(wrapper),
573
- plugins: [
574
- ...state?.plugins || [],
575
- history({
576
- newGroupDelay: updateDelay
577
- }),
578
- keymap(buildKeymap(schema)),
579
- keymap(baseKeymap),
580
- uploadPlaceholderPlugin,
581
- attachFile ? dragAndDropPlugin({
582
- attachFile
583
- }) : null,
584
- placeholder && placeholderPlugin(placeholder)
585
- ].filter((e) => !!e)
586
- }),
587
- dispatchTransaction(tr) {
588
- let result;
589
- if (editor?.dispatchTransaction) {
590
- result = editor.dispatchTransaction(tr);
591
- } else {
592
- view.updateState(view.state.apply(tr));
593
- }
594
- subject.next(tr);
595
- return result;
596
- }
597
- });
598
- function setValue(value) {
599
- const wrap = document.createElement("div");
600
- wrap.innerHTML = toInnerHTML(value);
601
- const doc = prosemirrorParser.parse(wrap);
602
- const tr = view.state.tr.replaceWith(
603
- 0,
604
- view.state.doc.content.size,
605
- doc.content
606
- );
607
- view.dispatch(tr);
608
- }
609
- function clear() {
610
- const tr = view.state.tr.replaceWith(
611
- 0,
612
- view.state.doc.content.size,
613
- schema.nodes.doc.createAndFill()
614
- );
615
- view.dispatch(tr);
1036
+ if (autoFocus) {
1037
+ controller.view.focus();
616
1038
  }
617
- function toHTML() {
618
- const fragment = prosemirrorSerializer.serializeFragment(
619
- view.state.doc.content
620
- );
621
- const container2 = document.createElement("div");
622
- container2.appendChild(fragment);
623
- return container2.innerHTML;
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
624
1046
  }
625
- function toTextContent() {
626
- const state2 = view.state;
627
- return state2.doc.textBetween(0, state2.doc.content.size, "\n");
1047
+ ));
1048
+ }
1049
+ return Component;
1050
+ }
1051
+
1052
+ // src/html.tsx
1053
+ import { decode } from "html-entities";
1054
+ import React3 from "react";
1055
+ function createInnerHTML(raw) {
1056
+ return raw.replace(/<\/p>/g, "<br></p>").replace(/(<p><br><\/p>)+$/g, "").replace(
1057
+ /<code class="language-(\w+)">([\s\S]*?)<\/code>/g,
1058
+ (_, lang, code) => {
1059
+ try {
1060
+ const highlighted = highlighter.highlight(code, {
1061
+ language: lang
1062
+ }).value;
1063
+ return `<code class="language-${lang}">${decode(highlighted)}</code>`;
1064
+ } catch (e) {
1065
+ return `<code class="language-${lang}">${decode(code)}</code>`;
628
1066
  }
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);
1067
+ }
1068
+ ).replace(
1069
+ /<a([^>]*target="_blank"[^>]*)>(.*?)<\/a>/g,
1070
+ (_, attrs, content) => {
1071
+ 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>`;
1072
+ }
1073
+ );
1074
+ }
1075
+ function createTextEditorView(options = {}) {
1076
+ return function Component({
1077
+ className,
1078
+ dangerouslySetInnerHTML,
1079
+ ...props
1080
+ }) {
1081
+ return /* @__PURE__ */ React3.createElement(
1082
+ "div",
1083
+ {
1084
+ ...props,
1085
+ className: cn(options?.className, className),
1086
+ dangerouslySetInnerHTML: {
1087
+ __html: createInnerHTML(
1088
+ String(dangerouslySetInnerHTML?.__html || "")
1089
+ )
644
1090
  }
645
- });
646
- if (autoFocus) {
647
- view.focus();
648
1091
  }
649
- const textEditorController = {
650
- schema,
651
- view,
652
- subject,
653
- set value(value) {
654
- setValue(value);
655
- },
656
- get value() {
657
- switch (mode) {
658
- case "text":
659
- return toTextContent();
660
- default:
661
- return toHTML();
662
- }
663
- },
664
- clear
665
- };
666
- if (typeof ref === "function") {
667
- ref(textEditorController);
668
- } else if (ref) {
669
- ref.current = textEditorController;
670
- }
671
- return () => {
672
- sub.unsubscribe();
673
- view.destroy();
674
- element.innerHTML = "";
675
- };
676
- }, []);
677
- 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 }));
678
- }
679
- return {
680
- schema,
681
- attachFile,
682
- Component
1092
+ );
683
1093
  };
684
1094
  }
685
1095
  export {
686
1096
  createAttachFile,
1097
+ createInnerHTML,
687
1098
  createSchema,
688
- createTextEditor
1099
+ createTextEditor,
1100
+ createTextEditorView
689
1101
  };