@cyberyozh/text-editor 0.2.2 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberyozh/text-editor",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Rich text editor library based on Tiptap for CyberYozh",
5
5
  "author": "CyberYozh",
6
6
  "license": "MIT",
@@ -107,7 +107,7 @@ function TiptapContent({
107
107
  onPaste,
108
108
  onDrop,
109
109
  onMarkupInsertion,
110
- } = useInsertionHandlers();
110
+ } = useInsertionHandlers(imageHandlers);
111
111
 
112
112
  const editor = useEditor(
113
113
  {
@@ -143,7 +143,6 @@ function TiptapContent({
143
143
  event,
144
144
  slice,
145
145
  setIsDisabledInternal,
146
- imageHandlers?.uploadMultipleByURL
147
146
  );
148
147
 
149
148
  if (typeof toReturn === 'boolean') return toReturn;
@@ -156,7 +155,6 @@ function TiptapContent({
156
155
  event,
157
156
  slice,
158
157
  setIsDisabledInternal,
159
- imageHandlers?.uploadMultipleByURL
160
158
  );
161
159
 
162
160
  if (typeof toReturn === 'boolean') return toReturn;
@@ -2,10 +2,10 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react";
2
2
  import { useDialogCommand } from "../context";
3
3
  import { EditorView } from "@tiptap/pm/view";
4
4
  import { Slice } from "@tiptap/pm/model";
5
- import { ImageUploadMultipleByURLHandler } from "@/types";
6
- import { applyUploadResultsToNodes, collectRehostableImages } from "./markup-insertion";
5
+ import { ImageHandlers } from "@/types";
6
+ import { applyUploadResultsToNodes, collectRehostableImages, removeImagesFromSlice } from "./markup-insertion";
7
7
 
8
- export function useInsertionHandlers() {
8
+ export function useInsertionHandlers(imageHandlers?: ImageHandlers) {
9
9
  const [insertedImage, setInsertedImage] = useState<File | null>(null);
10
10
 
11
11
  const { activeDialog, openDialogCommand } = useDialogCommand();
@@ -22,6 +22,8 @@ export function useInsertionHandlers() {
22
22
  event.preventDefault();
23
23
  event.stopPropagation();
24
24
 
25
+ if (!imageHandlers?.upload) return;
26
+
25
27
  setInsertedImage(file);
26
28
  openDialogCommand("image");
27
29
  }
@@ -41,10 +43,7 @@ export function useInsertionHandlers() {
41
43
  event: ClipboardEvent | DragEvent,
42
44
  slice: Slice,
43
45
  setDisabled: Dispatch<SetStateAction<boolean>>,
44
- uploadMultipleByURL?: ImageUploadMultipleByURLHandler
45
46
  ): boolean | void {
46
- if (!uploadMultipleByURL) return;
47
-
48
47
  let hasMarkupInsertion = false;
49
48
 
50
49
  if (
@@ -66,18 +65,22 @@ export function useInsertionHandlers() {
66
65
  }
67
66
 
68
67
  if (hasMarkupInsertion) {
69
- const imagesToNodes = collectRehostableImages(slice);
70
-
71
- setDisabled(true);
72
-
73
- uploadMultipleByURL(imagesToNodes.map(([url]) => url))
74
- .then((results) => {
75
- applyUploadResultsToNodes(imagesToNodes, results);
76
- view.dispatch(view.state.tr.replaceSelection(slice));
77
- })
78
- .finally(() => {
79
- setDisabled(false);
80
- });
68
+ if (imageHandlers?.uploadMultipleByURL) {
69
+ setDisabled(true);
70
+
71
+ const imagesToNodes = collectRehostableImages(slice);
72
+
73
+ imageHandlers.uploadMultipleByURL(imagesToNodes.map(([url]) => url))
74
+ .then((results) => {
75
+ applyUploadResultsToNodes(imagesToNodes, results);
76
+ view.dispatch(view.state.tr.replaceSelection(slice));
77
+ })
78
+ .finally(() => {
79
+ setDisabled(false);
80
+ });
81
+ } else {
82
+ view.dispatch(view.state.tr.replaceSelection(removeImagesFromSlice(slice)));
83
+ }
81
84
 
82
85
  return true;
83
86
  }
@@ -19,6 +19,37 @@ export function collectRehostableImages(slice: Slice | Fragment | Node): ImageTo
19
19
  return imagesToNodes;
20
20
  }
21
21
 
22
+ function removeImagesFromFragment(fragment: Fragment): Fragment {
23
+ const newNodes: Node[] = [];
24
+ fragment.forEach((node) => {
25
+ if (node.type.name === "image" && !node.attrs.fileId) return;
26
+ newNodes.push(node.childCount > 0 ? node.copy(removeImagesFromFragment(node.content)) : node);
27
+ });
28
+ return Fragment.from(newNodes);
29
+ }
30
+
31
+ function validOpenDepth(fragment: Fragment, requestedDepth: number, fromEnd = false): number {
32
+ let depth = 0;
33
+ let current = fragment;
34
+ while (depth < requestedDepth) {
35
+ if (current.childCount === 0) break;
36
+ const node = fromEnd ? current.lastChild! : current.firstChild!;
37
+ if (node.isLeaf) break;
38
+ current = node.content;
39
+ depth++;
40
+ }
41
+ return depth;
42
+ }
43
+
44
+ export function removeImagesFromSlice(slice: Slice): Slice {
45
+ const newContent = removeImagesFromFragment(slice.content);
46
+ return new Slice(
47
+ newContent,
48
+ validOpenDepth(newContent, slice.openStart),
49
+ validOpenDepth(newContent, slice.openEnd, true),
50
+ );
51
+ }
52
+
22
53
  export function applyUploadResultsToNodes(
23
54
  imagesToNodes: ImageToNode[],
24
55
  results: (ImageUploadResult | null)[],