@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberyozh/text-editor",
3
- "version": "0.1.7",
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
  : "";
@@ -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
- const handleImageAdd = createHandleImageAdd(editor, handleCloseDialog, savedDialogCursorPos);
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
- const handleVideoAdd = createHandleVideoAdd(editor, handleCloseDialog, savedDialogCursorPos);
163
- const handleLinkAdd = createHandleLinkAdd(editor, handleCloseDialog, savedDialogCursorPos);
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 { $from } = state.selection;
15
- const text = $from.parent.textContent;
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 { $from } = state.selection;
75
- const text = $from.parent.textContent;
76
-
77
- if (text.lastIndexOf("/") !== -1) {
78
- editor
79
- .chain()
80
- .focus()
81
- .command(({ tr }) => {
82
- tr.replaceWith(
83
- $from.before(),
84
- $from.after(),
85
- editor.schema.nodes.video.create({ src: url })
86
- );
87
- return true;
88
- })
89
- .scrollIntoView()
90
- .run();
91
- } else {
92
- const insertPos = savedPos ?? state.selection.from;
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 { $from } = state.selection;
139
- const text = $from.parent.textContent;
140
- const nodeStart = $from.start();
141
- const beforeCursor = text.substring(0, $from.pos - nodeStart);
142
- const slashIndex = beforeCursor.lastIndexOf("/");
143
-
144
- if (slashIndex !== -1) {
145
- editor
146
- .chain()
147
- .focus()
148
- .command(({ tr }) => {
149
- tr.delete(nodeStart + slashIndex, $from.pos);
150
- return true;
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 { $from } = state.selection;
189
- const text = $from.parent.textContent;
190
- const nodeStart = $from.start();
191
- const beforeCursor = text.substring(0, $from.pos - nodeStart);
192
- const slashIndex = beforeCursor.lastIndexOf("/");
193
-
194
- if (slashIndex !== -1) {
195
- const deleteFrom = nodeStart + slashIndex;
196
- const deleteTo = $from.pos;
197
-
198
- editor
199
- .chain()
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 './dialog-handlers';
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, colWidths = [] }: TableProps) {
13
- const tableWidth = colWidths.length > 0 ? colWidths.reduce((total, width) => total + width, 0) : null;
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 key={index} style={{ width: `${width}px` }} />
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
- let colWidths: number[] = [];
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
- }