@cyberyozh/text-editor 0.1.7 → 0.1.8
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/dist/editor.js +54 -90
- package/dist/editor.js.map +1 -1
- package/dist/editor.mjs +52 -88
- package/dist/editor.mjs.map +1 -1
- package/dist/render.js +17 -11
- package/dist/render.js.map +1 -1
- package/dist/render.mjs +17 -11
- package/dist/render.mjs.map +1 -1
- package/package.json +1 -3
- package/src/editor/dialogs/image-dialog/image-dialog.tsx +0 -1
- package/src/editor/editor.tsx +15 -4
- package/src/editor/handlers/dialog-handlers.ts +65 -135
- package/src/editor/handlers/index.ts +1 -2
- package/src/extensions/views/components/table.tsx +23 -4
- package/src/render/static-renderer-config.tsx +1 -17
- package/src/editor/handlers/create-image-upload-handler.ts +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyberyozh/text-editor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Rich text editor library based on Tiptap for CyberYozh",
|
|
5
5
|
"author": "CyberYozh",
|
|
6
6
|
"license": "MIT",
|
|
@@ -88,8 +88,6 @@
|
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@changesets/cli": "^2.29.7",
|
|
90
90
|
"@types/node": "^20",
|
|
91
|
-
"@types/react": "^19",
|
|
92
|
-
"@types/react-dom": "^19",
|
|
93
91
|
"eslint": "^8",
|
|
94
92
|
"tsup": "^8.0.0",
|
|
95
93
|
"typescript": "^5"
|
|
@@ -45,7 +45,6 @@ export const ImageDialog = ({
|
|
|
45
45
|
const [dragActive, setDragActive] = useState(false);
|
|
46
46
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
47
47
|
|
|
48
|
-
// Resolve the image URL from fileId or use src directly
|
|
49
48
|
const resolvedInitialUrl = initialAttrs
|
|
50
49
|
? resolveImageSrc({ fileId: initialAttrs.fileId, src: initialAttrs.src }, { getImageById })
|
|
51
50
|
: "";
|
package/src/editor/editor.tsx
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import { ImageDialog, AddKatexDialog, AddLinkDialog, AddVideoDialog } from "./dialogs";
|
|
25
25
|
import { getEditorExtensions } from "../extensions/editor";
|
|
26
26
|
|
|
27
|
-
import type { FigureAttrs, ImageHandlers } from "../types/image-handler";
|
|
27
|
+
import type { FigureAttrs, ImageHandlers, TiptapImageAttrs } from "../types/image-handler";
|
|
28
28
|
import {
|
|
29
29
|
createHandleImageAdd,
|
|
30
30
|
createHandleImageEdit,
|
|
@@ -157,10 +157,21 @@ function TiptapContent({
|
|
|
157
157
|
closeDialogCommand();
|
|
158
158
|
};
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
// Create handlers with current savedDialogCursorPos by accessing state directly
|
|
161
|
+
const handleImageAdd = (attrs: TiptapImageAttrs) => {
|
|
162
|
+
createHandleImageAdd(editor, handleCloseDialog, savedDialogCursorPos)(attrs);
|
|
163
|
+
};
|
|
164
|
+
|
|
161
165
|
const handleImageEdit = createHandleImageEdit(editor, handleCloseDialog, editFigureContext?.pos);
|
|
162
|
-
|
|
163
|
-
const
|
|
166
|
+
|
|
167
|
+
const handleVideoAdd = (url: string) => {
|
|
168
|
+
createHandleVideoAdd(editor, handleCloseDialog, savedDialogCursorPos)(url);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const handleLinkAdd = (title: string, url: string) => {
|
|
172
|
+
createHandleLinkAdd(editor, handleCloseDialog, savedDialogCursorPos)(title, url);
|
|
173
|
+
};
|
|
174
|
+
|
|
164
175
|
const handleKatexAdd = (latex: string) => {
|
|
165
176
|
const katexHandler = createHandleKatexAdd(editor, handleCloseDialog, savedDialogCursorPos);
|
|
166
177
|
katexHandler(latex, katexDialogContext?.pos);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Editor } from "@tiptap/react";
|
|
2
|
+
import { TextSelection } from "@tiptap/pm/state";
|
|
2
3
|
|
|
3
4
|
import type { FigureAttrs, TiptapImageAttrs } from "../../types";
|
|
4
5
|
|
|
@@ -11,40 +12,26 @@ export const createHandleImageAdd = (
|
|
|
11
12
|
if (!editor) return;
|
|
12
13
|
|
|
13
14
|
const { state } = editor;
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const insertPos = savedPos ?? state.selection.from;
|
|
16
|
+
|
|
17
|
+
editor
|
|
18
|
+
.chain()
|
|
19
|
+
.focus()
|
|
20
|
+
.command(({ tr, state }) => {
|
|
21
|
+
const $pos = state.doc.resolve(insertPos);
|
|
22
|
+
const imageNode = state.schema.nodes.image.create({ ...attrs });
|
|
23
|
+
|
|
24
|
+
if ($pos.parent.type.name === "paragraph" && $pos.parent.textContent === "") {
|
|
25
|
+
tr.replaceWith($pos.before(), $pos.after(), imageNode);
|
|
26
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve($pos.before())));
|
|
27
|
+
} else {
|
|
28
|
+
tr.insert(insertPos, imageNode);
|
|
29
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(insertPos)));
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
})
|
|
33
|
+
.run();
|
|
16
34
|
|
|
17
|
-
if (text.lastIndexOf("/") !== -1) {
|
|
18
|
-
editor
|
|
19
|
-
.chain()
|
|
20
|
-
.focus()
|
|
21
|
-
.command(({ tr }) => {
|
|
22
|
-
const node = editor.schema.nodes.image.create({ ...attrs });
|
|
23
|
-
tr.replaceWith($from.before(), $from.after(), node);
|
|
24
|
-
return true;
|
|
25
|
-
})
|
|
26
|
-
.scrollIntoView()
|
|
27
|
-
.run();
|
|
28
|
-
} else {
|
|
29
|
-
const insertPos = savedPos ?? state.selection.from;
|
|
30
|
-
|
|
31
|
-
editor
|
|
32
|
-
.chain()
|
|
33
|
-
.focus()
|
|
34
|
-
.command(({ tr, state }) => {
|
|
35
|
-
const $pos = state.doc.resolve(insertPos);
|
|
36
|
-
const figureNode = state.schema.nodes.figure.create({ ...attrs });
|
|
37
|
-
|
|
38
|
-
if ($pos.parent.type.name === "paragraph" && $pos.parent.textContent === "") {
|
|
39
|
-
tr.replaceWith($pos.before(), $pos.after(), figureNode);
|
|
40
|
-
} else {
|
|
41
|
-
tr.insert(insertPos, figureNode);
|
|
42
|
-
}
|
|
43
|
-
return true;
|
|
44
|
-
})
|
|
45
|
-
.scrollIntoView()
|
|
46
|
-
.run();
|
|
47
|
-
}
|
|
48
35
|
closeDialogCommand();
|
|
49
36
|
};
|
|
50
37
|
};
|
|
@@ -55,6 +42,7 @@ export const createHandleImageEdit = (
|
|
|
55
42
|
pos?: number
|
|
56
43
|
) => {
|
|
57
44
|
return (attrs: Partial<FigureAttrs>) => {
|
|
45
|
+
console.log("👀 ~ createHandleImageEdit ~ attrs:", attrs);
|
|
58
46
|
if (!editor || pos === undefined) return;
|
|
59
47
|
|
|
60
48
|
editor.chain().focus().setNodeSelection(pos).updateFigure(attrs).run();
|
|
@@ -71,43 +59,26 @@ export const createHandleVideoAdd = (
|
|
|
71
59
|
if (!editor) return;
|
|
72
60
|
|
|
73
61
|
const { state } = editor;
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
62
|
+
const insertPos = savedPos ?? state.selection.from;
|
|
63
|
+
|
|
64
|
+
editor
|
|
65
|
+
.chain()
|
|
66
|
+
.focus()
|
|
67
|
+
.command(({ tr, state }) => {
|
|
68
|
+
const $pos = state.doc.resolve(insertPos);
|
|
69
|
+
const videoNode = state.schema.nodes.video.create({ src: url });
|
|
70
|
+
|
|
71
|
+
if ($pos.parent.type.name === "paragraph" && $pos.parent.textContent === "") {
|
|
72
|
+
tr.replaceWith($pos.before(), $pos.after(), videoNode);
|
|
73
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve($pos.before())));
|
|
74
|
+
} else {
|
|
75
|
+
tr.insert(insertPos, videoNode);
|
|
76
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(insertPos)));
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
})
|
|
80
|
+
.run();
|
|
93
81
|
|
|
94
|
-
editor
|
|
95
|
-
.chain()
|
|
96
|
-
.focus()
|
|
97
|
-
.command(({ tr, state }) => {
|
|
98
|
-
const $pos = state.doc.resolve(insertPos);
|
|
99
|
-
const videoNode = state.schema.nodes.video.create({ src: url });
|
|
100
|
-
|
|
101
|
-
if ($pos.parent.type.name === "paragraph" && $pos.parent.textContent === "") {
|
|
102
|
-
tr.replaceWith($pos.before(), $pos.after(), videoNode);
|
|
103
|
-
} else {
|
|
104
|
-
tr.insert(insertPos, videoNode);
|
|
105
|
-
}
|
|
106
|
-
return true;
|
|
107
|
-
})
|
|
108
|
-
.scrollIntoView()
|
|
109
|
-
.run();
|
|
110
|
-
}
|
|
111
82
|
closeDialogCommand();
|
|
112
83
|
};
|
|
113
84
|
};
|
|
@@ -135,39 +106,19 @@ export const createHandleLinkAdd = (
|
|
|
135
106
|
.run();
|
|
136
107
|
} else {
|
|
137
108
|
const { state } = editor;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.
|
|
147
|
-
.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
})
|
|
152
|
-
.extendMarkRange("link")
|
|
153
|
-
.setLink({ href: url })
|
|
154
|
-
.insertContent(title)
|
|
155
|
-
.scrollIntoView()
|
|
156
|
-
.run();
|
|
157
|
-
} else {
|
|
158
|
-
const insertPos = savedPos ?? state.selection.from;
|
|
159
|
-
editor
|
|
160
|
-
.chain()
|
|
161
|
-
.focus()
|
|
162
|
-
.command(({ tr }) => {
|
|
163
|
-
const linkMark = editor.schema.marks.link.create({ href: url });
|
|
164
|
-
const textNode = editor.schema.text(title, [linkMark]);
|
|
165
|
-
tr.insert(insertPos, textNode);
|
|
166
|
-
return true;
|
|
167
|
-
})
|
|
168
|
-
.scrollIntoView()
|
|
169
|
-
.run();
|
|
170
|
-
}
|
|
109
|
+
const insertPos = savedPos ?? state.selection.from;
|
|
110
|
+
|
|
111
|
+
editor
|
|
112
|
+
.chain()
|
|
113
|
+
.focus()
|
|
114
|
+
.command(({ tr }) => {
|
|
115
|
+
const linkMark = editor.schema.marks.link.create({ href: url });
|
|
116
|
+
const textNode = editor.schema.text(title, [linkMark]);
|
|
117
|
+
tr.insert(insertPos, textNode);
|
|
118
|
+
tr.setSelection(TextSelection.create(tr.doc, insertPos, insertPos + title.length));
|
|
119
|
+
return true;
|
|
120
|
+
})
|
|
121
|
+
.run();
|
|
171
122
|
}
|
|
172
123
|
closeDialogCommand();
|
|
173
124
|
};
|
|
@@ -185,39 +136,18 @@ export const createHandleKatexAdd = (
|
|
|
185
136
|
editor.chain().setNodeSelection(pos).updateInlineMath({ latex }).focus().run();
|
|
186
137
|
} else {
|
|
187
138
|
const { state } = editor;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.focus()
|
|
201
|
-
.command(({ tr, state }) => {
|
|
202
|
-
const inlineMathNode = state.schema.nodes.inlineMath.create({ latex });
|
|
203
|
-
tr.replaceRangeWith(deleteFrom, deleteTo, inlineMathNode);
|
|
204
|
-
return true;
|
|
205
|
-
})
|
|
206
|
-
.scrollIntoView()
|
|
207
|
-
.run();
|
|
208
|
-
} else {
|
|
209
|
-
const insertPos = savedPos ?? state.selection.from;
|
|
210
|
-
editor
|
|
211
|
-
.chain()
|
|
212
|
-
.focus()
|
|
213
|
-
.command(({ tr, state }) => {
|
|
214
|
-
const inlineMathNode = state.schema.nodes.inlineMath.create({ latex });
|
|
215
|
-
tr.insert(insertPos, inlineMathNode);
|
|
216
|
-
return true;
|
|
217
|
-
})
|
|
218
|
-
.scrollIntoView()
|
|
219
|
-
.run();
|
|
220
|
-
}
|
|
139
|
+
const insertPos = savedPos ?? state.selection.from;
|
|
140
|
+
|
|
141
|
+
editor
|
|
142
|
+
.chain()
|
|
143
|
+
.focus()
|
|
144
|
+
.command(({ tr, state }) => {
|
|
145
|
+
const inlineMathNode = state.schema.nodes.inlineMath.create({ latex });
|
|
146
|
+
tr.insert(insertPos, inlineMathNode);
|
|
147
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(insertPos)));
|
|
148
|
+
return true;
|
|
149
|
+
})
|
|
150
|
+
.run();
|
|
221
151
|
}
|
|
222
152
|
|
|
223
153
|
closeDialogCommand();
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
export { createImageUploadHandler } from './create-image-upload-handler';
|
|
2
1
|
export {
|
|
3
2
|
createHandleImageAdd,
|
|
4
3
|
createHandleKatexAdd,
|
|
5
4
|
createHandleLinkAdd,
|
|
6
5
|
createHandleVideoAdd,
|
|
7
6
|
createHandleImageEdit,
|
|
8
|
-
} from
|
|
7
|
+
} from "./dialog-handlers";
|
|
@@ -2,15 +2,31 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
interface TableProps {
|
|
4
4
|
children: ReactNode;
|
|
5
|
-
colWidths?: number[];
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Table component for static rendering
|
|
10
9
|
* Applies column widths from Tiptap table cells
|
|
11
10
|
*/
|
|
12
|
-
export function Table({ children
|
|
13
|
-
|
|
11
|
+
export function Table({ children }: TableProps) {
|
|
12
|
+
let colWidths: number[] = [];
|
|
13
|
+
|
|
14
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
15
|
+
const firstRow = children[0];
|
|
16
|
+
|
|
17
|
+
if (firstRow?.props?.children) {
|
|
18
|
+
const cells = Array.isArray(firstRow.props.children)
|
|
19
|
+
? firstRow.props.children
|
|
20
|
+
: [firstRow.props.children];
|
|
21
|
+
|
|
22
|
+
colWidths = cells
|
|
23
|
+
.map((cell: any) => cell?.props?.node?.attrs?.colwidth?.[0])
|
|
24
|
+
.filter((width: number | undefined) => width !== undefined);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tableWidth =
|
|
29
|
+
colWidths.length > 0 ? colWidths.reduce((total, width) => total + width, 0) : null;
|
|
14
30
|
|
|
15
31
|
return (
|
|
16
32
|
<div className="tableWrapper">
|
|
@@ -18,7 +34,10 @@ export function Table({ children, colWidths = [] }: TableProps) {
|
|
|
18
34
|
{colWidths.length > 0 && (
|
|
19
35
|
<colgroup>
|
|
20
36
|
{colWidths.map((width, index) => (
|
|
21
|
-
<col
|
|
37
|
+
<col
|
|
38
|
+
key={index}
|
|
39
|
+
style={{ width: `${width}px` }}
|
|
40
|
+
/>
|
|
22
41
|
))}
|
|
23
42
|
</colgroup>
|
|
24
43
|
)}
|
|
@@ -96,23 +96,7 @@ export const getNodeMapping = (getImageById?: (fileId: string) => string | null)
|
|
|
96
96
|
return <Video src={node.attrs.src} />;
|
|
97
97
|
},
|
|
98
98
|
table({ children }: any) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (Array.isArray(children) && children.length > 0) {
|
|
102
|
-
const firstRow = children[0];
|
|
103
|
-
|
|
104
|
-
if (firstRow?.props?.children) {
|
|
105
|
-
const cells = Array.isArray(firstRow.props.children)
|
|
106
|
-
? firstRow.props.children
|
|
107
|
-
: [firstRow.props.children];
|
|
108
|
-
|
|
109
|
-
colWidths = cells
|
|
110
|
-
.map((cell: any) => cell?.props?.node?.attrs?.colwidth?.[0])
|
|
111
|
-
.filter((width: number | undefined) => width !== undefined);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return <Table colWidths={colWidths}>{children}</Table>;
|
|
99
|
+
return <Table>{children}</Table>;
|
|
116
100
|
},
|
|
117
101
|
figure({ node }: any) {
|
|
118
102
|
return (
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { ImageUploadHandler } from "../../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates an image upload handler that wraps a custom upload action
|
|
5
|
-
* Supports two patterns:
|
|
6
|
-
* 1. Backend with fileId: { fileId: "file-123" }
|
|
7
|
-
* 2. Direct CDN: { fileUrl: "https://cdn.com/image.jpg" }
|
|
8
|
-
*/
|
|
9
|
-
export function createImageUploadHandler(
|
|
10
|
-
uploadAction: (file: File) => Promise<{ data?: { fileId?: string; fileUrl?: string } }>
|
|
11
|
-
): ImageUploadHandler {
|
|
12
|
-
return async (file: File) => {
|
|
13
|
-
const { data } = await uploadAction(file);
|
|
14
|
-
|
|
15
|
-
if (!data?.fileId && !data?.fileUrl) {
|
|
16
|
-
throw new Error("Failed to upload file: must return fileId or fileUrl");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
fileId: data.fileId,
|
|
21
|
-
fileUrl: data.fileUrl,
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
}
|