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
package/dist/index.mjs CHANGED
@@ -1,179 +1,83 @@
1
- // src/text_editor.tsx
2
- import React, {
3
- useImperativeHandle,
4
- useMemo
1
+ // src/create_text_editor.tsx
2
+ import React2, {
3
+ useImperativeHandle
5
4
  } 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";
5
+ import "prosemirror-state";
112
6
  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
- }
7
+ import { useEffect as useEffect2, useRef } from "react";
172
8
 
173
9
  // src/schema.tsx
174
10
  import { Schema } from "prosemirror-model";
175
11
  import { addListNodes } from "prosemirror-schema-list";
176
- 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: {} }) {
177
81
  const customSchema = new Schema({
178
82
  nodes: {
179
83
  doc: { content: "block+" },
@@ -339,67 +243,232 @@ function createSchema() {
339
243
  }
340
244
  ];
341
245
  }
342
- }
343
- },
344
- marks: {
345
- link: {
246
+ },
247
+ video: {
248
+ inline: false,
249
+ group: "block",
250
+ draggable: true,
346
251
  attrs: {
347
- href: { default: "" },
348
- 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
+ }
349
269
  },
350
- inclusive: false,
351
270
  parseDOM: [
352
271
  {
353
- tag: "a[href]",
272
+ tag: "video",
354
273
  getAttrs(dom) {
355
274
  return {
356
- href: dom.getAttribute("href"),
357
- 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")
358
280
  };
359
281
  }
360
282
  }
361
283
  ],
362
284
  toDOM(node) {
363
- const { href, title } = node.attrs;
364
- const target = "_blank";
365
- const rel = "noopener noreferrer";
285
+ const { src, title, width, height, poster } = node.attrs;
366
286
  return [
367
- "a",
368
- { href, title: title || href, target, rel },
369
- 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
+ }
370
298
  ];
371
299
  }
372
300
  },
373
- bold: {
374
- parseDOM: [
375
- { tag: "strong" },
376
- {
377
- tag: "b",
378
- 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"
379
309
  },
380
- {
381
- style: "font-weight=400",
382
- clearMark: (m) => m.type.name == "strong"
310
+ width: {
311
+ default: null,
312
+ validate: "number|null"
383
313
  },
384
- {
385
- style: "font-weight",
386
- 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"
387
333
  }
388
- ],
389
- toDOM() {
390
- return ["strong", 0];
391
- }
392
- },
393
- italic: {
334
+ },
394
335
  parseDOM: [
395
- { tag: "em" },
396
- { tag: "i" },
397
- { style: "font-style=italic" },
398
336
  {
399
- style: "font-style=normal",
400
- clearMark: (m) => m.type.name == "em"
401
- }
402
- ],
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
+ ],
403
472
  toDOM() {
404
473
  return ["em", 0];
405
474
  }
@@ -415,22 +484,314 @@ function createSchema() {
415
484
  toDOM() {
416
485
  return ["u", 0];
417
486
  }
418
- }
419
- }
487
+ },
488
+ ...spec.marks
489
+ },
490
+ topNode: spec.topNode
420
491
  });
421
492
  const prosemirrorSchema = new Schema({
422
- nodes: addListNodes(
423
- customSchema.spec.nodes,
424
- "paragraph block*",
425
- "block"
426
- ),
493
+ nodes: addListNodes(customSchema.spec.nodes, "paragraph block*", "block"),
427
494
  marks: customSchema.spec.marks
428
495
  });
429
496
  return prosemirrorSchema;
430
497
  }
431
498
 
432
- // src/text_editor.tsx
433
- 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
+ }
434
795
 
435
796
  // src/cn.ts
436
797
  function cn(...classes) {
@@ -439,7 +800,9 @@ function cn(...classes) {
439
800
 
440
801
  // src/attach_file.tsx
441
802
  import "prosemirror-view";
442
- var base64ImageUploader = async (file) => {
803
+
804
+ // src/base64_file_uploader.ts
805
+ var base64FileUploader = async (file) => {
443
806
  const base64 = await new Promise((resolve, reject) => {
444
807
  const reader = new FileReader();
445
808
  reader.onload = () => {
@@ -453,10 +816,12 @@ var base64ImageUploader = async (file) => {
453
816
  alt: file.name
454
817
  };
455
818
  };
819
+
820
+ // src/attach_file.tsx
456
821
  function createAttachFile({
457
822
  schema,
458
823
  generateMetadata,
459
- uploadFile = base64ImageUploader
824
+ uploadFile = base64FileUploader
460
825
  }) {
461
826
  const attachEachFile = async (view, file, pos) => {
462
827
  const metadata = generateMetadata ? await generateMetadata(file) : {};
@@ -519,177 +884,218 @@ function createAttachFile({
519
884
  };
520
885
  }
521
886
 
522
- // src/text_editor.tsx
523
- function createTextEditor(options = {}) {
524
- 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();
525
899
  const prosemirrorParser = DOMParser.fromSchema(schema);
526
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) : "");
527
909
  const attachFile = createAttachFile({
528
910
  schema,
529
911
  generateMetadata: options.attachFile?.generateMetadata,
530
912
  uploadFile: options.attachFile?.uploadFile
531
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();
532
1008
  function Component({
533
1009
  ref,
534
- state,
535
- editor,
536
- mode = "html",
537
- container,
1010
+ className,
538
1011
  autoFocus,
539
- name,
540
1012
  placeholder,
541
- className,
542
1013
  defaultValue,
543
- onClick,
544
1014
  onChange,
545
- updateDelay = 0,
1015
+ updateDelay,
546
1016
  ...props
547
1017
  } = {}) {
548
1018
  const containerRef = useRef(null);
549
- const inputRef = useRef(null);
550
1019
  const controllerRef = useRef(null);
551
- const subject = useMemo(() => new Subject(), []);
552
1020
  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 = {
1021
+ const container = containerRef.current;
1022
+ const textEditorController = createTextEditorController(
1023
+ container,
640
1024
  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
- };
1025
+ options,
1026
+ props
1027
+ );
656
1028
  controllerRef.current = textEditorController;
657
1029
  return textEditorController;
658
1030
  });
659
- useEffect(() => {
1031
+ useEffect2(() => {
660
1032
  const controller = controllerRef.current;
661
1033
  if (!controller) {
662
1034
  return;
663
1035
  }
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
1036
  if (autoFocus) {
675
1037
  controller.view.focus();
676
1038
  }
677
- return () => {
678
- sub.unsubscribe();
679
- controller.view.destroy();
680
- };
681
1039
  }, []);
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 }));
1040
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { ...props, ref: containerRef, className }), /* @__PURE__ */ React2.createElement(
1041
+ TextEditorInput,
1042
+ {
1043
+ ref: controllerRef,
1044
+ updateDelay,
1045
+ onChange
1046
+ }
1047
+ ));
683
1048
  }
684
- return {
685
- schema,
686
- attachFile,
687
- Component
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>`;
1066
+ }
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
+ )
1090
+ }
1091
+ }
1092
+ );
688
1093
  };
689
1094
  }
690
1095
  export {
691
- base64ImageUploader,
692
1096
  createAttachFile,
1097
+ createInnerHTML,
693
1098
  createSchema,
694
- createTextEditor
1099
+ createTextEditor,
1100
+ createTextEditorView
695
1101
  };