@verdant-web/tiptap 0.1.5 → 1.0.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/dist/esm/__browserTests__/react.test.js +108 -0
- package/dist/esm/__browserTests__/react.test.js.map +1 -1
- package/dist/esm/fields.js +26 -8
- package/dist/esm/fields.js.map +1 -1
- package/dist/esm/plugins.d.ts +26 -3
- package/dist/esm/plugins.js +202 -21
- package/dist/esm/plugins.js.map +1 -1
- package/dist/esm/react.d.ts +4 -6
- package/dist/esm/react.js +10 -70
- package/dist/esm/react.js.map +1 -1
- package/package.json +9 -9
- package/src/__browserTests__/react.test.tsx +152 -0
- package/src/fields.ts +29 -8
- package/src/plugins.ts +297 -50
- package/src/react.ts +23 -110
package/src/react.ts
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
import { Editor } from '@tiptap/core';
|
|
2
1
|
import { useEditor, UseEditorOptions } from '@tiptap/react';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
: never;
|
|
12
|
-
|
|
13
|
-
type EntitySnapshot<
|
|
14
|
-
Ent extends AnyEntity<any, any, any>,
|
|
15
|
-
Key extends AllowedKey<Ent>,
|
|
16
|
-
> =
|
|
17
|
-
Ent extends AnyEntity<any, any, infer Snap>
|
|
18
|
-
? Key extends keyof Snap
|
|
19
|
-
? Snap[Key]
|
|
20
|
-
: any
|
|
21
|
-
: never;
|
|
2
|
+
import { AnyEntity } from '@verdant-web/store';
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
VerdantExtension,
|
|
6
|
+
VerdantExtensionOptions,
|
|
7
|
+
type EntitySnapshot,
|
|
8
|
+
type ValidEntityKey,
|
|
9
|
+
} from './plugins.js';
|
|
22
10
|
|
|
23
11
|
export function useSyncedEditor<
|
|
24
12
|
Ent extends AnyEntity<any, any, any>,
|
|
25
|
-
Key extends
|
|
13
|
+
Key extends ValidEntityKey<Ent>,
|
|
26
14
|
>(
|
|
27
15
|
parent: Ent,
|
|
28
16
|
fieldName: Key,
|
|
@@ -30,10 +18,12 @@ export function useSyncedEditor<
|
|
|
30
18
|
editorOptions: extraOptions,
|
|
31
19
|
editorDependencies,
|
|
32
20
|
nullDocumentDefault,
|
|
21
|
+
extensionOptions,
|
|
33
22
|
}: {
|
|
34
23
|
editorOptions?: UseEditorOptions;
|
|
35
24
|
editorDependencies?: any[];
|
|
36
25
|
nullDocumentDefault?: EntitySnapshot<Ent, Key>;
|
|
26
|
+
extensionOptions?: Partial<VerdantExtensionOptions>;
|
|
37
27
|
} = {},
|
|
38
28
|
) {
|
|
39
29
|
const cachedOptions = useRef({
|
|
@@ -44,103 +34,26 @@ export function useSyncedEditor<
|
|
|
44
34
|
nullDocumentDefault,
|
|
45
35
|
fieldName,
|
|
46
36
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const value = parent.get(cachedOptions.current.fieldName) as ObjectEntity<
|
|
57
|
-
any,
|
|
58
|
-
any
|
|
59
|
-
> | null;
|
|
60
|
-
if (!value) {
|
|
61
|
-
parent.set(cachedOptions.current.fieldName as any, newData);
|
|
62
|
-
} else {
|
|
63
|
-
value.update(newData, {
|
|
64
|
-
merge: false,
|
|
65
|
-
dangerouslyDisableMerge: true,
|
|
66
|
-
replaceSubObjects: false,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const cachedInitialContent = useRef(
|
|
72
|
-
ensureDocShape(getFieldSnapshot(field, nullDocumentDefault, fieldName)),
|
|
37
|
+
// create a configured version of the Verdant extension, which handles
|
|
38
|
+
// the actual syncing of the editor content to the field
|
|
39
|
+
const [extension] = useState(() =>
|
|
40
|
+
VerdantExtension.configure({
|
|
41
|
+
parent,
|
|
42
|
+
fieldName: fieldName as string | number,
|
|
43
|
+
nullDocumentDefault,
|
|
44
|
+
...extensionOptions,
|
|
45
|
+
}),
|
|
73
46
|
);
|
|
74
47
|
const editor = useEditor(
|
|
75
48
|
{
|
|
76
49
|
...extraOptions,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
update(ctx.editor);
|
|
80
|
-
extraOptions?.onUpdate?.(ctx);
|
|
50
|
+
onContentError(props) {
|
|
51
|
+
console.error('Content error:', props.error);
|
|
81
52
|
},
|
|
53
|
+
extensions: [extension, ...(extraOptions?.extensions ?? [])],
|
|
82
54
|
},
|
|
83
55
|
editorDependencies,
|
|
84
56
|
);
|
|
85
57
|
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
function updateFromField() {
|
|
88
|
-
if (editor && !editor.isDestroyed) {
|
|
89
|
-
updatingRef.current = true;
|
|
90
|
-
const { from, to } = editor.state.selection;
|
|
91
|
-
editor.commands.setContent(
|
|
92
|
-
ensureDocShape(
|
|
93
|
-
getFieldSnapshot(
|
|
94
|
-
field,
|
|
95
|
-
cachedOptions.current.nullDocumentDefault,
|
|
96
|
-
cachedOptions.current.fieldName,
|
|
97
|
-
),
|
|
98
|
-
),
|
|
99
|
-
false,
|
|
100
|
-
);
|
|
101
|
-
editor.commands.setTextSelection({ from, to });
|
|
102
|
-
updatingRef.current = false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
updateFromField();
|
|
107
|
-
|
|
108
|
-
return field?.subscribe('changeDeep', (target, info) => {
|
|
109
|
-
if (!info.isLocal || target === field) {
|
|
110
|
-
updateFromField();
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
}, [field, editor, cachedOptions]);
|
|
114
|
-
|
|
115
58
|
return editor;
|
|
116
59
|
}
|
|
117
|
-
|
|
118
|
-
// since the schema doesn't enforce this shape but it's
|
|
119
|
-
// needed for the editor to work, we'll ensure it here
|
|
120
|
-
function ensureDocShape(json: any) {
|
|
121
|
-
for (const node of json.content ?? []) {
|
|
122
|
-
// remove undefined nodes
|
|
123
|
-
node.content = node.content.filter((n: any) => !!n).map(ensureDocShape);
|
|
124
|
-
}
|
|
125
|
-
return json;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function getFieldSnapshot(
|
|
129
|
-
field: ObjectEntity<any, any> | undefined | null,
|
|
130
|
-
nullDocumentDefault: any,
|
|
131
|
-
fieldName: string | symbol | number,
|
|
132
|
-
) {
|
|
133
|
-
const content = field ? field.getSnapshot() : (nullDocumentDefault ?? null);
|
|
134
|
-
if (content === null) {
|
|
135
|
-
throw new Error(`The provided field "${String(fieldName)}" is null and a default document was not provided.
|
|
136
|
-
Please provide a default document or ensure the field is not null when calling useSyncedEditor, or make your
|
|
137
|
-
field schema non-null and specify a default document there.`);
|
|
138
|
-
}
|
|
139
|
-
return content;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function useStableCallback<T extends (...args: any[]) => any>(callback: T) {
|
|
143
|
-
const ref = useRef(callback);
|
|
144
|
-
ref.current = callback;
|
|
145
|
-
return useCallback((...args: Parameters<T>) => ref.current(...args), []);
|
|
146
|
-
}
|