dn-react-text-editor 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +1093 -0
  17. package/dist/create_text_editor.mjs +1064 -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 +782 -365
  25. package/dist/index.mjs +777 -360
  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,229 @@ 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 = "html",
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 === "text") {
904
+ return value.split("\n").map((line) => `<p>${line}</p>`).join("");
905
+ }
906
+ return value;
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,
1010
+ className,
1011
+ autoFocus,
1012
+ onChange,
1013
+ mode,
534
1014
  state,
535
1015
  editor,
536
- mode = "html",
537
- container,
538
- autoFocus,
539
- name,
540
- placeholder,
541
- className,
542
1016
  defaultValue,
543
- onClick,
544
- onChange,
545
- updateDelay = 0,
1017
+ updateDelay,
1018
+ placeholder,
546
1019
  ...props
547
1020
  } = {}) {
548
1021
  const containerRef = useRef(null);
549
- const inputRef = useRef(null);
550
1022
  const controllerRef = useRef(null);
551
- const subject = useMemo(() => new Subject(), []);
552
1023
  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 = {
1024
+ const container = containerRef.current;
1025
+ const textEditorController = createTextEditorController(
1026
+ container,
640
1027
  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
- };
1028
+ options,
1029
+ {
1030
+ mode,
1031
+ state,
1032
+ editor,
1033
+ defaultValue,
1034
+ updateDelay,
1035
+ placeholder
1036
+ }
1037
+ );
656
1038
  controllerRef.current = textEditorController;
657
1039
  return textEditorController;
658
1040
  });
659
- useEffect(() => {
1041
+ useEffect2(() => {
660
1042
  const controller = controllerRef.current;
661
1043
  if (!controller) {
662
1044
  return;
663
1045
  }
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
1046
  if (autoFocus) {
675
1047
  controller.view.focus();
676
1048
  }
677
- return () => {
678
- sub.unsubscribe();
679
- controller.view.destroy();
680
- };
681
1049
  }, []);
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 }));
1050
+ return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { ...props, ref: containerRef, className }), /* @__PURE__ */ React2.createElement(
1051
+ TextEditorInput,
1052
+ {
1053
+ ref: controllerRef,
1054
+ updateDelay,
1055
+ defaultValue,
1056
+ onChange
1057
+ }
1058
+ ));
683
1059
  }
684
- return {
685
- schema,
686
- attachFile,
687
- Component
1060
+ return Component;
1061
+ }
1062
+
1063
+ // src/html.tsx
1064
+ import { decode } from "html-entities";
1065
+ import React3 from "react";
1066
+ function createInnerHTML(raw) {
1067
+ return raw.replace(/<\/p>/g, "<br></p>").replace(/(<p><br><\/p>)+$/g, "").replace(
1068
+ /<code class="language-(\w+)">([\s\S]*?)<\/code>/g,
1069
+ (_, lang, code) => {
1070
+ try {
1071
+ const highlighted = highlighter.highlight(code, {
1072
+ language: lang
1073
+ }).value;
1074
+ return `<code class="language-${lang}">${decode(highlighted)}</code>`;
1075
+ } catch (e) {
1076
+ return `<code class="language-${lang}">${decode(code)}</code>`;
1077
+ }
1078
+ }
1079
+ ).replace(
1080
+ /<a([^>]*target="_blank"[^>]*)>(.*?)<\/a>/g,
1081
+ (_, attrs, content) => {
1082
+ 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>`;
1083
+ }
1084
+ );
1085
+ }
1086
+ function createTextEditorView(options = {}) {
1087
+ return function Component({
1088
+ className,
1089
+ dangerouslySetInnerHTML,
1090
+ ...props
1091
+ }) {
1092
+ return /* @__PURE__ */ React3.createElement(
1093
+ "div",
1094
+ {
1095
+ ...props,
1096
+ className: cn(options?.className, className),
1097
+ dangerouslySetInnerHTML: {
1098
+ __html: createInnerHTML(
1099
+ String(dangerouslySetInnerHTML?.__html || "")
1100
+ )
1101
+ }
1102
+ }
1103
+ );
688
1104
  };
689
1105
  }
690
1106
  export {
691
- base64ImageUploader,
692
1107
  createAttachFile,
1108
+ createInnerHTML,
693
1109
  createSchema,
694
- createTextEditor
1110
+ createTextEditor,
1111
+ createTextEditorView
695
1112
  };