@meta-1/editor 1.0.2 → 1.0.4
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
|
@@ -8,26 +8,21 @@
|
|
|
8
8
|
@apply relative;
|
|
9
9
|
color: inherit;
|
|
10
10
|
font-style: inherit;
|
|
11
|
-
|
|
12
|
-
&:first-child,
|
|
13
|
-
&:first-of-type {
|
|
14
|
-
@apply mt-0;
|
|
15
|
-
}
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
h1 {
|
|
19
|
-
@apply text-2xl font-bold
|
|
14
|
+
@apply text-2xl font-bold;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
17
|
h2 {
|
|
23
|
-
@apply text-xl font-bold
|
|
18
|
+
@apply text-xl font-bold;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
21
|
h3 {
|
|
27
|
-
@apply text-lg font-semibold
|
|
22
|
+
@apply text-lg font-semibold;
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
h4 {
|
|
31
|
-
@apply text-base font-semibold
|
|
26
|
+
@apply text-base font-semibold;
|
|
32
27
|
}
|
|
33
28
|
}
|
package/src/editor/index.tsx
CHANGED
|
@@ -69,24 +69,35 @@ export function EditorContentArea() {
|
|
|
69
69
|
export type Meta1EditorProps = Meta1EditorOptions & {
|
|
70
70
|
/** Editor instance created by Meta1Editor.useEditor() */
|
|
71
71
|
editor?: Meta1EditorInstance;
|
|
72
|
+
/** Controlled value - works with FormItem */
|
|
73
|
+
value?: Meta1EditorValue;
|
|
74
|
+
/** onChange callback - works with FormItem */
|
|
75
|
+
onChange?: (content: Meta1EditorValue) => void;
|
|
72
76
|
};
|
|
73
77
|
|
|
74
78
|
const Meta1EditorInner: FC<Meta1EditorProps> = (props) => {
|
|
75
|
-
const { editor: editorInstance, ...options } = props;
|
|
79
|
+
const { editor: editorInstance, value, onChange, ...options } = props;
|
|
76
80
|
|
|
77
81
|
// Create the actual tiptap editor with value/onChange support
|
|
78
|
-
const tiptapEditor = useMeta1Editor(
|
|
82
|
+
const tiptapEditor = useMeta1Editor({
|
|
83
|
+
...options,
|
|
84
|
+
value,
|
|
85
|
+
onChange,
|
|
86
|
+
});
|
|
79
87
|
|
|
80
88
|
// Update the instance reference when editor changes
|
|
81
89
|
useEffect(() => {
|
|
82
|
-
if (editorInstance?._setEditor) {
|
|
90
|
+
if (editorInstance?._setEditor && tiptapEditor) {
|
|
83
91
|
editorInstance._setEditor(tiptapEditor);
|
|
92
|
+
if (options.contentType) {
|
|
93
|
+
editorInstance._contentType = options.contentType;
|
|
94
|
+
}
|
|
84
95
|
editorInstance._nodeExclude = options.nodeExclude;
|
|
85
96
|
}
|
|
86
97
|
return () => {
|
|
87
98
|
editorInstance?._setEditor?.(null);
|
|
88
99
|
};
|
|
89
|
-
}, [tiptapEditor, editorInstance, options.nodeExclude]);
|
|
100
|
+
}, [tiptapEditor, editorInstance, options.contentType, options.nodeExclude]);
|
|
90
101
|
|
|
91
102
|
if (!tiptapEditor) {
|
|
92
103
|
return null;
|
package/src/hooks/use-editor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useRef } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { Highlight } from "@tiptap/extension-highlight";
|
|
3
3
|
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
|
4
4
|
import { Mathematics } from "@tiptap/extension-mathematics";
|
|
@@ -46,9 +46,7 @@ export type Meta1EditorOptions = {
|
|
|
46
46
|
contentType?: ContentType;
|
|
47
47
|
editable?: boolean;
|
|
48
48
|
imageUpload?: ImageUploadOptions;
|
|
49
|
-
|
|
50
|
-
onChange?: (content: Meta1EditorValue) => void;
|
|
51
|
-
/** Node types to exclude from output (e.g., ['imageUpload']) */
|
|
49
|
+
/** Node types to exclude from output (e.g., ['imageUpload'] for filtering upload placeholders) */
|
|
52
50
|
nodeExclude?: string[];
|
|
53
51
|
};
|
|
54
52
|
|
|
@@ -58,6 +56,8 @@ export type Meta1EditorOptions = {
|
|
|
58
56
|
export interface Meta1EditorInstance {
|
|
59
57
|
/** Internal: set the actual editor reference */
|
|
60
58
|
_setEditor?: (editor: Editor | null) => void;
|
|
59
|
+
/** Internal: set contentType for value conversion */
|
|
60
|
+
_contentType?: ContentType;
|
|
61
61
|
/** Internal: set nodeExclude for filtering */
|
|
62
62
|
_nodeExclude?: string[];
|
|
63
63
|
/** Get the underlying tiptap Editor instance */
|
|
@@ -100,14 +100,12 @@ const filterJsonNodes = (json: Record<string, unknown>, excludeTypes: string[]):
|
|
|
100
100
|
* Filter out excluded nodes from HTML content
|
|
101
101
|
*/
|
|
102
102
|
const filterHtmlNodes = (html: string, excludeTypes: string[]): string => {
|
|
103
|
-
// Create patterns for each excluded type
|
|
104
|
-
// imageUpload renders as: <div data-type="image-upload" ...></div>
|
|
105
103
|
let result = html;
|
|
106
104
|
for (const type of excludeTypes) {
|
|
107
105
|
// Convert camelCase to kebab-case for data-type attribute
|
|
108
106
|
const kebabType = type.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
109
|
-
// Match self-closing
|
|
110
|
-
const pattern = new RegExp(`<div[^>]*data-type="${kebabType}"[^>]*>.*?</div>`, "
|
|
107
|
+
// Match divs with data-type attribute (handles both self-closing and content divs)
|
|
108
|
+
const pattern = new RegExp(`<div[^>]*data-type="${kebabType}"[^>]*>.*?</div>`, "gis");
|
|
111
109
|
result = result.replace(pattern, "");
|
|
112
110
|
}
|
|
113
111
|
return result;
|
|
@@ -130,7 +128,7 @@ const getValueFromEditor = (editor: Editor, contentType: ContentType, nodeExclud
|
|
|
130
128
|
return html;
|
|
131
129
|
}
|
|
132
130
|
case "markdown":
|
|
133
|
-
// Markdown doesn't
|
|
131
|
+
// Markdown export typically doesn't include custom nodes like imageUpload
|
|
134
132
|
return editor.getMarkdown();
|
|
135
133
|
default:
|
|
136
134
|
return JSON.stringify(editor.getJSON());
|
|
@@ -162,6 +160,7 @@ export const useEditorInstance = (): Meta1EditorInstance => {
|
|
|
162
160
|
if (!instanceRef.current) {
|
|
163
161
|
instanceRef.current = {
|
|
164
162
|
_setEditor,
|
|
163
|
+
_contentType: "json",
|
|
165
164
|
get _nodeExclude() {
|
|
166
165
|
return nodeExcludeRef.current;
|
|
167
166
|
},
|
|
@@ -194,7 +193,10 @@ export const useEditorInstance = (): Meta1EditorInstance => {
|
|
|
194
193
|
return instanceRef.current;
|
|
195
194
|
};
|
|
196
195
|
|
|
197
|
-
export type UseMeta1EditorProps = Meta1EditorOptions
|
|
196
|
+
export type UseMeta1EditorProps = Meta1EditorOptions & {
|
|
197
|
+
value?: Meta1EditorValue;
|
|
198
|
+
onChange?: (content: Meta1EditorValue) => void;
|
|
199
|
+
};
|
|
198
200
|
|
|
199
201
|
/**
|
|
200
202
|
* Internal hook that creates the actual tiptap editor.
|
|
@@ -210,9 +212,11 @@ export const useMeta1Editor = (props: UseMeta1EditorProps) => {
|
|
|
210
212
|
contentType = "json",
|
|
211
213
|
editable = true,
|
|
212
214
|
imageUpload,
|
|
213
|
-
nodeExclude,
|
|
215
|
+
nodeExclude = ["imageUpload"],
|
|
214
216
|
} = props;
|
|
215
217
|
const isUpdatingFromPropsRef = useRef(false);
|
|
218
|
+
// Track the last value we sent via onChange to avoid sync loops
|
|
219
|
+
const lastEmittedValueRef = useRef<string | undefined>(undefined);
|
|
216
220
|
|
|
217
221
|
const editor = useEditor({
|
|
218
222
|
content: value,
|
|
@@ -220,7 +224,10 @@ export const useMeta1Editor = (props: UseMeta1EditorProps) => {
|
|
|
220
224
|
editable,
|
|
221
225
|
onUpdate: ({ editor }) => {
|
|
222
226
|
if (!isUpdatingFromPropsRef.current) {
|
|
223
|
-
|
|
227
|
+
const newValue = getValueFromEditor(editor, contentType, nodeExclude);
|
|
228
|
+
const newValueStr = typeof newValue === "string" ? newValue : JSON.stringify(newValue);
|
|
229
|
+
lastEmittedValueRef.current = newValueStr;
|
|
230
|
+
onChange?.(newValue);
|
|
224
231
|
}
|
|
225
232
|
},
|
|
226
233
|
editorProps: {
|
|
@@ -282,5 +289,27 @@ export const useMeta1Editor = (props: UseMeta1EditorProps) => {
|
|
|
282
289
|
immediatelyRender: false,
|
|
283
290
|
});
|
|
284
291
|
|
|
292
|
+
// Sync value from props to editor (only for external changes)
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
if (!editor || value === undefined) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const valueStr = typeof value === "string" ? value : JSON.stringify(value);
|
|
299
|
+
|
|
300
|
+
if (lastEmittedValueRef.current === valueStr) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const currentContent = getValueFromEditor(editor, contentType, nodeExclude);
|
|
305
|
+
const currentStr = typeof currentContent === "string" ? currentContent : JSON.stringify(currentContent);
|
|
306
|
+
|
|
307
|
+
if (valueStr !== currentStr) {
|
|
308
|
+
isUpdatingFromPropsRef.current = true;
|
|
309
|
+
editor.commands.setContent(value);
|
|
310
|
+
isUpdatingFromPropsRef.current = false;
|
|
311
|
+
}
|
|
312
|
+
}, [editor, value, contentType, nodeExclude]);
|
|
313
|
+
|
|
285
314
|
return editor;
|
|
286
315
|
};
|