@apollohg/react-native-rich-text-editor 2.0.0 → 2.0.2
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/README.md +8 -116
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/android/build.gradle +3 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextImages.kt +25 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2RenderValidation.kt +1 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +101 -33
- package/android/src/main/java/com/apollohg/editor/RenderMediaSpans.kt +15 -3
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +1 -1
- package/app.plugin.js +42 -39
- package/dist/AtomHost.js +18 -11
- package/dist/EditorAddon.js +4 -2
- package/dist/EditorAddonNormalization.js +17 -9
- package/dist/EditorMentionThemeNormalization.js +6 -3
- package/dist/EditorMentionThemeValidation.js +28 -11
- package/dist/EditorStyleSheetNormalization.js +85 -38
- package/dist/EditorStyleSheetTypes.d.ts +1 -1
- package/dist/EditorTheme.d.ts +4 -0
- package/dist/EditorTheme.js +5 -3
- package/dist/EditorToolbarComponent.d.ts +1 -1
- package/dist/EditorToolbarComponent.js +1 -1
- package/dist/EditorToolbarRegistry.js +5 -5
- package/dist/EditorToolbarTypes.d.ts +12 -12
- package/dist/EditorToolbarVisuals.js +1 -1
- package/dist/ExternalTextComposition.js +89 -43
- package/dist/ImageLoadingPolicy.d.ts +1 -1
- package/dist/NativeEditorBoundaryError.d.ts +7 -7
- package/dist/NativeEditorBoundaryError.js +9 -5
- package/dist/NativeEditorCollaborationTransport.d.ts +7 -7
- package/dist/NativeEditorCollaborationTransport.js +28 -15
- package/dist/NativeEditorCreateConfig.js +11 -6
- package/dist/NativeEditorCreateJson.js +51 -25
- package/dist/NativeEditorCreateValidation.js +11 -6
- package/dist/NativeEditorDocumentBridge.d.ts +1 -1
- package/dist/NativeEditorDocumentBridge.js +32 -18
- package/dist/NativeEditorDocumentHandle.d.ts +4 -4
- package/dist/NativeEditorDocumentHandle.js +1 -1
- package/dist/NativeEditorLocalAwareness.js +25 -13
- package/dist/NativeEditorNativeModule.d.ts +8 -1
- package/dist/NativeEditorNativeModule.js +7 -5
- package/dist/NativeEditorRenderNormalization.js +66 -25
- package/dist/NativeEditorResultNormalization.js +53 -29
- package/dist/NativeEditorTypes.d.ts +10 -10
- package/dist/NativeEditorV2Decimal.js +6 -3
- package/dist/NativeProseViewer.js +11 -3
- package/dist/ResourceLimits.d.ts +2 -2
- package/dist/ResourceLimits.js +7 -2
- package/dist/RichTextEditor.js +1 -1
- package/dist/RichTextEditorNativeTypes.d.ts +1 -1
- package/dist/RichTextEditorNativeView.d.ts +1 -3
- package/dist/RichTextEditorSerialization.js +24 -14
- package/dist/RichTextEditorTypes.d.ts +1 -1
- package/dist/YjsCollaboration.d.ts +7 -7
- package/dist/YjsCollaboration.js +42 -26
- package/dist/addons.d.ts +4 -4
- package/dist/addons.js +3 -3
- package/dist/atomInstances.js +6 -3
- package/dist/atomUpdates.js +7 -4
- package/dist/atoms.js +20 -12
- package/dist/attributeValidation.js +55 -24
- package/dist/contentExpression.js +80 -42
- package/dist/listMarkers.d.ts +1 -1
- package/dist/listMarkers.js +1 -1
- package/dist/schemaDefinition.d.ts +1 -1
- package/dist/schemaDefinition.js +23 -14
- package/dist/schemaDocument.js +1 -1
- package/dist/schemaDocumentConstruction.js +25 -14
- package/dist/schemaNormalization.js +51 -29
- package/dist/schemaPresets.d.ts +1 -1
- package/dist/schemaPresets.js +9 -4
- package/dist/schemaResolution.js +46 -25
- package/dist/specs/PreparedProseViewerNativeComponent.d.ts +4 -0
- package/dist/useEditorToolbarInteractions.d.ts +2 -2
- package/dist/useEditorToolbarInteractions.js +45 -23
- package/dist/useEditorToolbarItems.d.ts +1 -1
- package/dist/useEditorToolbarItems.js +4 -3
- package/dist/useEditorToolbarPresentation.d.ts +3 -3
- package/dist/useEditorToolbarPresentation.js +4 -4
- package/dist/useEditorToolbarState.d.ts +1 -1
- package/dist/useEditorToolbarState.js +1 -1
- package/dist/useFocusPreservingFrames.js +11 -8
- package/dist/useNativeEditor.d.ts +1 -1
- package/dist/useNativeEditor.js +21 -11
- package/dist/useRichTextEditorCommands.d.ts +2 -2
- package/dist/useRichTextEditorCommands.js +59 -29
- package/dist/useRichTextEditorEvents.d.ts +3 -3
- package/dist/useRichTextEditorEvents.js +75 -32
- package/dist/useRichTextEditorMentions.d.ts +4 -4
- package/dist/useRichTextEditorMentions.js +48 -29
- package/dist/useRichTextEditorPresentation.d.ts +4 -4
- package/dist/useRichTextEditorPresentation.js +25 -23
- package/dist/useRichTextEditorState.d.ts +3 -3
- package/dist/useRichTextEditorState.js +20 -15
- package/dist/useRichTextEditorUpdates.d.ts +1 -1
- package/dist/useRichTextEditorUpdates.js +99 -41
- package/dist/useViewerAtoms.js +63 -36
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorInteractionOverlays.swift +114 -122
- package/ios/EditorLayoutManager.swift +13 -3
- package/ios/EditorStyleSheet.swift +12 -1
- package/ios/EditorTextView+Images.swift +32 -10
- package/ios/EditorTextView.swift +1 -1
- package/ios/EditorV2Adapter+RenderValidation.swift +2 -2
- package/ios/NativeToolbar+ButtonStyle.swift +17 -5
- package/ios/NativeToolbar+Diagnostics.swift +15 -0
- package/ios/RenderBridge+Atoms.swift +0 -1
- package/ios/RenderBridge+Attachments.swift +0 -29
- package/ios/RichTextEditorView.swift +4 -12
- package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +3 -2
- package/package.json +11 -3
- package/react-native.config.js +1 -1
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/src/AtomHost.tsx +58 -28
- package/src/EditorAddon.ts +18 -8
- package/src/EditorAddonNormalization.ts +40 -11
- package/src/EditorMentionThemeNormalization.ts +15 -4
- package/src/EditorMentionThemeValidation.ts +51 -14
- package/src/EditorStyleSheet.ts +1 -0
- package/src/EditorStyleSheetNormalization.ts +195 -64
- package/src/EditorStyleSheetTypes.ts +1 -1
- package/src/EditorTheme.ts +13 -5
- package/src/EditorToolbarComponent.tsx +2 -1
- package/src/EditorToolbarRegistry.tsx +23 -13
- package/src/EditorToolbarTypes.tsx +12 -12
- package/src/EditorToolbarVisuals.tsx +6 -3
- package/src/EditorUpdateRevision.ts +1 -0
- package/src/ExternalTextComposition.ts +259 -49
- package/src/ImageLoadingPolicy.ts +3 -1
- package/src/NativeEditorBoundaryError.ts +38 -11
- package/src/NativeEditorCollaborationTransport.ts +108 -27
- package/src/NativeEditorCreateConfig.ts +92 -19
- package/src/NativeEditorCreateJson.ts +163 -26
- package/src/NativeEditorCreateValidation.ts +33 -12
- package/src/NativeEditorDocumentBridge.ts +111 -19
- package/src/NativeEditorDocumentHandle.ts +9 -4
- package/src/NativeEditorLocalAwareness.ts +70 -15
- package/src/NativeEditorNativeModule.ts +13 -9
- package/src/NativeEditorRenderNormalization.ts +128 -29
- package/src/NativeEditorResultNormalization.ts +143 -32
- package/src/NativeEditorTypes.ts +12 -11
- package/src/NativeEditorV2Decimal.ts +12 -3
- package/src/NativeProseViewer.tsx +46 -16
- package/src/ResourceLimits.ts +15 -8
- package/src/RichTextEditor.tsx +3 -1
- package/src/RichTextEditorNativeTypes.tsx +1 -1
- package/src/RichTextEditorNativeView.tsx +2 -8
- package/src/RichTextEditorSerialization.tsx +70 -24
- package/src/RichTextEditorTypes.tsx +1 -1
- package/src/YjsCollaboration.ts +143 -47
- package/src/addons.ts +12 -8
- package/src/atomInstances.ts +25 -5
- package/src/atomUpdates.ts +23 -6
- package/src/atoms.ts +85 -18
- package/src/attributeValidation.ts +97 -33
- package/src/contentExpression.ts +218 -63
- package/src/listMarkers.ts +1 -1
- package/src/schemaDefinition.ts +82 -20
- package/src/schemaDocument.ts +15 -1
- package/src/schemaDocumentConstruction.ts +80 -16
- package/src/schemaNormalization.ts +155 -41
- package/src/schemaPresets.ts +37 -31
- package/src/schemaResolution.ts +126 -27
- package/src/specs/PreparedProseViewerNativeComponent.ts +4 -2
- package/src/useEditorToolbarInteractions.tsx +85 -36
- package/src/useEditorToolbarItems.tsx +50 -22
- package/src/useEditorToolbarPresentation.tsx +81 -58
- package/src/useEditorToolbarState.tsx +8 -6
- package/src/useFocusPreservingFrames.ts +43 -15
- package/src/useNativeEditor.ts +111 -51
- package/src/useRichTextEditorCommands.tsx +127 -54
- package/src/useRichTextEditorEvents.tsx +144 -42
- package/src/useRichTextEditorMentions.tsx +118 -37
- package/src/useRichTextEditorPresentation.tsx +128 -114
- package/src/useRichTextEditorState.tsx +76 -40
- package/src/useRichTextEditorUpdates.tsx +207 -56
- package/src/useViewerAtoms.tsx +162 -66
|
@@ -28,11 +28,16 @@ export function schemaBoundaryError(limit: number, actual: number): NativeEditor
|
|
|
28
28
|
/** Mirror native's invalid-schema fallback before constructing an empty doc. */
|
|
29
29
|
export function utf8ByteLengthUpTo(value: string, maximum: number): number {
|
|
30
30
|
let bytes = 0;
|
|
31
|
+
|
|
31
32
|
for (const char of value) {
|
|
32
33
|
const codePoint = char.codePointAt(0) ?? 0;
|
|
33
34
|
bytes += codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
|
|
34
|
-
|
|
35
|
+
|
|
36
|
+
if (bytes > maximum) {
|
|
37
|
+
return bytes;
|
|
38
|
+
}
|
|
35
39
|
}
|
|
40
|
+
|
|
36
41
|
return bytes;
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -43,16 +48,28 @@ export function forEachGroupToken(
|
|
|
43
48
|
consumeToken?: () => boolean
|
|
44
49
|
): boolean {
|
|
45
50
|
let tokenStart = -1;
|
|
51
|
+
|
|
46
52
|
for (let index = 0; index <= group.length; index += 1) {
|
|
47
|
-
if (index < group.length && consumeCharacter && !consumeCharacter())
|
|
53
|
+
if (index < group.length && consumeCharacter && !consumeCharacter()) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
const isBoundary = index === group.length || /\s/.test(group[index]);
|
|
49
|
-
|
|
58
|
+
|
|
59
|
+
if (!isBoundary && tokenStart < 0) {
|
|
60
|
+
tokenStart = index;
|
|
61
|
+
}
|
|
62
|
+
|
|
50
63
|
if (isBoundary && tokenStart >= 0) {
|
|
51
|
-
if (consumeToken && !consumeToken())
|
|
64
|
+
if (consumeToken && !consumeToken()) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
52
68
|
visit(group.slice(tokenStart, index));
|
|
53
69
|
tokenStart = -1;
|
|
54
70
|
}
|
|
55
71
|
}
|
|
72
|
+
|
|
56
73
|
return true;
|
|
57
74
|
}
|
|
58
75
|
|
|
@@ -74,9 +91,12 @@ export function consumeSchemaWork(budget: SchemaWorkBudget): boolean {
|
|
|
74
91
|
export function consumeSchemaWorkAmount(budget: SchemaWorkBudget, amount: number): boolean {
|
|
75
92
|
if (amount > budget.limit - budget.work) {
|
|
76
93
|
budget.exhausted = true;
|
|
94
|
+
|
|
77
95
|
return false;
|
|
78
96
|
}
|
|
97
|
+
|
|
79
98
|
budget.work += amount;
|
|
99
|
+
|
|
80
100
|
return true;
|
|
81
101
|
}
|
|
82
102
|
|
|
@@ -88,34 +108,58 @@ export function collectOwnAttrs(
|
|
|
88
108
|
value: unknown,
|
|
89
109
|
budget: SchemaWorkBudget
|
|
90
110
|
): Array<[string, AttrSpec]> | null {
|
|
91
|
-
if (value == null || typeof value !== 'object' || Array.isArray(value))
|
|
111
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
92
115
|
const attrs: Array<[string, AttrSpec]> = [];
|
|
116
|
+
|
|
93
117
|
for (const name in value) {
|
|
94
|
-
if (!Object.prototype.hasOwnProperty.call(value, name))
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
if (!Object.prototype.hasOwnProperty.call(value, name)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!consumeSchemaWork(budget)) {
|
|
123
|
+
return attrs;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
attrs.push([ name, (value as Record<string, AttrSpec>)[name] ]);
|
|
97
127
|
}
|
|
128
|
+
|
|
98
129
|
return attrs;
|
|
99
130
|
}
|
|
100
131
|
|
|
101
132
|
export function isSafeHtmlTag(tag: string): boolean {
|
|
102
|
-
if (tag.length === 0 || tag[0] < 'a' || tag[0] > 'z')
|
|
133
|
+
if (tag.length === 0 || tag[0] < 'a' || tag[0] > 'z') {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
103
137
|
for (let index = 1; index < tag.length; index += 1) {
|
|
104
138
|
const char = tag[index];
|
|
139
|
+
|
|
105
140
|
if (!((char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char === '-')) {
|
|
106
141
|
return false;
|
|
107
142
|
}
|
|
108
143
|
}
|
|
144
|
+
|
|
109
145
|
return true;
|
|
110
146
|
}
|
|
111
147
|
|
|
112
148
|
export function isSafeHtmlAttr(name: string): boolean {
|
|
113
|
-
if (name.length === 0)
|
|
149
|
+
if (name.length === 0) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
114
153
|
const isAlpha = (char: string): boolean =>
|
|
115
154
|
(char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z');
|
|
116
|
-
|
|
155
|
+
|
|
156
|
+
if (!(isAlpha(name[0]) || name[0] === '_' || name[0] === ':')) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
117
160
|
for (let index = 1; index < name.length; index += 1) {
|
|
118
161
|
const char = name[index];
|
|
162
|
+
|
|
119
163
|
if (
|
|
120
164
|
!(
|
|
121
165
|
isAlpha(char) ||
|
|
@@ -129,21 +173,27 @@ export function isSafeHtmlAttr(name: string): boolean {
|
|
|
129
173
|
return false;
|
|
130
174
|
}
|
|
131
175
|
}
|
|
176
|
+
|
|
132
177
|
return true;
|
|
133
178
|
}
|
|
134
179
|
|
|
135
180
|
export function normalizeAttrs(value: unknown): Record<string, AttrSpec> {
|
|
136
|
-
if (value == null || typeof value !== 'object' || Array.isArray(value))
|
|
181
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
|
|
137
185
|
return Object.fromEntries(
|
|
138
|
-
Object.entries(value).map(([name, rawSpec]) => {
|
|
186
|
+
Object.entries(value as Record<string, unknown>).map(([ name, rawSpec ]) => {
|
|
139
187
|
if (rawSpec == null || typeof rawSpec !== 'object' || Array.isArray(rawSpec)) {
|
|
140
|
-
return [name, {}];
|
|
188
|
+
return [ name, {} ];
|
|
141
189
|
}
|
|
142
|
-
|
|
190
|
+
|
|
191
|
+
validateAttributeSpec(rawSpec, name);
|
|
192
|
+
|
|
143
193
|
return [
|
|
144
194
|
name,
|
|
145
195
|
Object.fromEntries(
|
|
146
|
-
Object.entries(rawSpec).filter(([, entry]) => entry !== undefined)
|
|
196
|
+
Object.entries(rawSpec).filter(([ , entry ]) => entry !== undefined)
|
|
147
197
|
),
|
|
148
198
|
];
|
|
149
199
|
})
|
|
@@ -151,15 +201,35 @@ export function normalizeAttrs(value: unknown): Record<string, AttrSpec> {
|
|
|
151
201
|
}
|
|
152
202
|
|
|
153
203
|
export function normalizeNodeJSONProjection(value: unknown): NodeJSONProjection | null | undefined {
|
|
154
|
-
if (value === undefined)
|
|
155
|
-
|
|
204
|
+
if (value === undefined) {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
156
212
|
const raw = value as Record<string, unknown>;
|
|
157
|
-
|
|
158
|
-
if (raw.
|
|
159
|
-
|
|
213
|
+
|
|
214
|
+
if (typeof raw.type !== 'string' || raw.type.length === 0) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (raw.attrs === undefined) {
|
|
219
|
+
return { type: raw.type };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (raw.attrs == null || typeof raw.attrs !== 'object' || Array.isArray(raw.attrs)) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
160
226
|
const attrs: Record<string, unknown> = {};
|
|
161
|
-
|
|
162
|
-
|
|
227
|
+
|
|
228
|
+
for (const [ name, attrValue ] of Object.entries(raw.attrs)) {
|
|
229
|
+
if (!isSafeHtmlAttr(name)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
163
233
|
if (
|
|
164
234
|
attrValue !== null &&
|
|
165
235
|
typeof attrValue !== 'string' &&
|
|
@@ -168,8 +238,10 @@ export function normalizeNodeJSONProjection(value: unknown): NodeJSONProjection
|
|
|
168
238
|
) {
|
|
169
239
|
return null;
|
|
170
240
|
}
|
|
241
|
+
|
|
171
242
|
attrs[name] = attrValue;
|
|
172
243
|
}
|
|
244
|
+
|
|
173
245
|
return Object.keys(attrs).length === 0 ? { type: raw.type } : { type: raw.type, attrs };
|
|
174
246
|
}
|
|
175
247
|
|
|
@@ -186,42 +258,62 @@ export function isSafeAtomHtmlAttr(name: string): boolean {
|
|
|
186
258
|
}
|
|
187
259
|
|
|
188
260
|
export function normalizeNodeHtmlRules(value: unknown): NodeHtmlRules | null | undefined {
|
|
189
|
-
if (value === undefined)
|
|
190
|
-
|
|
261
|
+
if (value === undefined) {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
191
269
|
const raw = value as Record<string, unknown>;
|
|
270
|
+
|
|
192
271
|
if (
|
|
193
272
|
Object.keys(raw).some(
|
|
194
|
-
|
|
273
|
+
key => key !== 'tag' && key !== 'staticAttrs' && key !== 'attrMap'
|
|
195
274
|
) ||
|
|
196
275
|
typeof raw.tag !== 'string' ||
|
|
197
276
|
!isSafeAtomHtmlTag(raw.tag)
|
|
198
277
|
) {
|
|
199
278
|
return null;
|
|
200
279
|
}
|
|
280
|
+
|
|
201
281
|
const normalizeMap = (
|
|
202
282
|
candidate: unknown,
|
|
203
283
|
validateName: (name: string) => boolean
|
|
204
284
|
): Record<string, string> | null | undefined => {
|
|
205
|
-
if (candidate === undefined)
|
|
285
|
+
if (candidate === undefined) {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
206
289
|
if (candidate == null || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
207
290
|
return null;
|
|
208
291
|
}
|
|
292
|
+
|
|
209
293
|
const normalized: Record<string, string> = {};
|
|
210
|
-
|
|
211
|
-
|
|
294
|
+
|
|
295
|
+
for (const [ name, mapped ] of Object.entries(candidate)) {
|
|
296
|
+
if (!validateName(name) || typeof mapped !== 'string') {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
212
300
|
normalized[name] = mapped;
|
|
213
301
|
}
|
|
302
|
+
|
|
214
303
|
return normalized;
|
|
215
304
|
};
|
|
305
|
+
|
|
216
306
|
const staticAttrs = normalizeMap(raw.staticAttrs, isSafeAtomHtmlAttr);
|
|
217
307
|
const attrMap = normalizeMap(raw.attrMap, isSafeHtmlAttr);
|
|
308
|
+
|
|
218
309
|
if (
|
|
219
310
|
staticAttrs === null ||
|
|
220
311
|
attrMap === null ||
|
|
221
|
-
(attrMap != null && Object.values(attrMap).some(
|
|
312
|
+
(attrMap != null && Object.values(attrMap).some(name => !isSafeAtomHtmlAttr(name)))
|
|
222
313
|
) {
|
|
223
314
|
return null;
|
|
224
315
|
}
|
|
316
|
+
|
|
225
317
|
return {
|
|
226
318
|
tag: raw.tag,
|
|
227
319
|
...(staticAttrs == null ? {} : { staticAttrs }),
|
|
@@ -234,20 +326,25 @@ export function projectionAttrsOverlap(
|
|
|
234
326
|
right: Record<string, unknown>
|
|
235
327
|
): boolean {
|
|
236
328
|
return Object.entries(left).every(
|
|
237
|
-
([name, value]) =>
|
|
329
|
+
([ name, value ]) =>
|
|
238
330
|
!Object.prototype.hasOwnProperty.call(right, name) || right[name] === value
|
|
239
331
|
);
|
|
240
332
|
}
|
|
241
333
|
|
|
242
334
|
export function legacyHeadingProjectionName(projection: NodeJSONProjection): string | undefined {
|
|
243
|
-
if (projection.type !== 'heading')
|
|
335
|
+
if (projection.type !== 'heading') {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
244
339
|
const value = projection.attrs?.level;
|
|
245
340
|
let level: number | undefined;
|
|
341
|
+
|
|
246
342
|
if (typeof value === 'number' && Number.isInteger(value)) {
|
|
247
343
|
level = value;
|
|
248
344
|
} else if (typeof value === 'string' && value.length <= 3 && /^\+?\d+$/.test(value)) {
|
|
249
345
|
level = Number(value);
|
|
250
346
|
}
|
|
347
|
+
|
|
251
348
|
return level != null && level >= 1 && level <= 6 ? `h${level}` : undefined;
|
|
252
349
|
}
|
|
253
350
|
|
|
@@ -269,25 +366,31 @@ export function normalizeSchemaDefinition(
|
|
|
269
366
|
return 'block';
|
|
270
367
|
}
|
|
271
368
|
};
|
|
369
|
+
|
|
272
370
|
const nodes: NodeSpec[] = [];
|
|
371
|
+
|
|
273
372
|
for (let nodeIndex = 0; nodeIndex < schema.nodes.length; nodeIndex += 1) {
|
|
274
373
|
const rawNode = schema.nodes[nodeIndex];
|
|
374
|
+
|
|
275
375
|
if (rawNode == null || typeof rawNode !== 'object' || typeof rawNode.name !== 'string') {
|
|
276
376
|
return null;
|
|
277
377
|
}
|
|
278
|
-
|
|
378
|
+
|
|
379
|
+
const raw = rawNode;
|
|
279
380
|
const htmlTag = typeof raw.htmlTag === 'string' ? raw.htmlTag : undefined;
|
|
280
381
|
const html = normalizeNodeHtmlRules(raw.html);
|
|
281
382
|
const attrs = normalizeAttrs(raw.attrs);
|
|
282
383
|
const json = normalizeNodeJSONProjection(raw.json);
|
|
384
|
+
|
|
283
385
|
if (
|
|
284
386
|
(htmlTag != null && !isSafeHtmlTag(htmlTag)) ||
|
|
285
387
|
html === null ||
|
|
286
388
|
json === null ||
|
|
287
|
-
Object.keys(attrs).some(
|
|
389
|
+
Object.keys(attrs).some(name => !isSafeHtmlAttr(name))
|
|
288
390
|
) {
|
|
289
391
|
return null;
|
|
290
392
|
}
|
|
393
|
+
|
|
291
394
|
nodes.push({
|
|
292
395
|
name: rawNode.name,
|
|
293
396
|
content: typeof raw.content === 'string' ? raw.content : '',
|
|
@@ -306,29 +409,34 @@ export function normalizeSchemaDefinition(
|
|
|
306
409
|
: {}),
|
|
307
410
|
});
|
|
308
411
|
}
|
|
309
|
-
|
|
310
|
-
const
|
|
412
|
+
|
|
413
|
+
const nativeNodeNames = new Set(nodes.map(node => node.name));
|
|
414
|
+
const projectedNodes = nodes.filter(node => node.json != null);
|
|
415
|
+
|
|
311
416
|
for (let index = 0; index < projectedNodes.length; index += 1) {
|
|
312
417
|
const node = projectedNodes[index];
|
|
313
418
|
const projection = node.json as NodeJSONProjection;
|
|
314
419
|
const legacyHeadingName = legacyHeadingProjectionName(projection);
|
|
420
|
+
|
|
315
421
|
if (
|
|
316
422
|
nativeNodeNames.has(projection.type) ||
|
|
317
423
|
RESERVED_WIRE_NODE_TYPES.has(projection.type) ||
|
|
318
424
|
(legacyHeadingName != null &&
|
|
319
425
|
legacyHeadingName !== node.name &&
|
|
320
426
|
nativeNodeNames.has(legacyHeadingName)) ||
|
|
321
|
-
Object.keys(projection.attrs ?? {}).some(
|
|
322
|
-
Object.prototype.hasOwnProperty.call(node.attrs ?? {}, name)
|
|
323
|
-
)
|
|
427
|
+
Object.keys(projection.attrs ?? {}).some(name =>
|
|
428
|
+
Object.prototype.hasOwnProperty.call(node.attrs ?? {}, name))
|
|
324
429
|
) {
|
|
325
430
|
return null;
|
|
326
431
|
}
|
|
432
|
+
|
|
327
433
|
for (let previousIndex = 0; previousIndex < index; previousIndex += 1) {
|
|
328
434
|
if (budget != null && !consumeSchemaWork(budget)) {
|
|
329
435
|
throw schemaBoundaryError(budget.limit, budget.limit + 1);
|
|
330
436
|
}
|
|
437
|
+
|
|
331
438
|
const previous = projectedNodes[previousIndex];
|
|
439
|
+
|
|
332
440
|
if (
|
|
333
441
|
previous.json?.type === projection.type &&
|
|
334
442
|
projectionAttrsOverlap(projection.attrs ?? {}, previous.json.attrs ?? {})
|
|
@@ -339,20 +447,25 @@ export function normalizeSchemaDefinition(
|
|
|
339
447
|
}
|
|
340
448
|
|
|
341
449
|
const marks: MarkSpec[] = [];
|
|
450
|
+
|
|
342
451
|
for (let markIndex = 0; markIndex < schema.marks.length; markIndex += 1) {
|
|
343
452
|
const rawMark = schema.marks[markIndex];
|
|
453
|
+
|
|
344
454
|
if (rawMark == null || typeof rawMark !== 'object' || typeof rawMark.name !== 'string') {
|
|
345
455
|
return null;
|
|
346
456
|
}
|
|
347
|
-
|
|
457
|
+
|
|
458
|
+
const raw = rawMark;
|
|
348
459
|
const htmlTag = typeof raw.htmlTag === 'string' ? raw.htmlTag.toLowerCase() : undefined;
|
|
349
460
|
const attrs = normalizeAttrs(raw.attrs);
|
|
461
|
+
|
|
350
462
|
if (
|
|
351
463
|
(htmlTag != null && !ALLOWED_MARK_HTML_TAGS.has(htmlTag)) ||
|
|
352
|
-
Object.keys(attrs).some(
|
|
464
|
+
Object.keys(attrs).some(name => !isSafeHtmlAttr(name))
|
|
353
465
|
) {
|
|
354
466
|
return null;
|
|
355
467
|
}
|
|
468
|
+
|
|
356
469
|
marks.push({
|
|
357
470
|
name: rawMark.name,
|
|
358
471
|
...(Object.keys(attrs).length > 0 ? { attrs } : {}),
|
|
@@ -363,5 +476,6 @@ export function normalizeSchemaDefinition(
|
|
|
363
476
|
: {}),
|
|
364
477
|
});
|
|
365
478
|
}
|
|
479
|
+
|
|
366
480
|
return { nodes, marks };
|
|
367
481
|
}
|
package/src/schemaPresets.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface ImageNodeAttributes {
|
|
|
24
24
|
export interface ResolvedDocumentSchema {
|
|
25
25
|
/** The schema itself, defaulted to {@link defaultSchema} when none was supplied. */
|
|
26
26
|
schema: SchemaDefinition;
|
|
27
|
-
/** Name of the node with `role: 'doc'`
|
|
27
|
+
/** Name of the node with `role: 'doc'` : the `type` of a document's root. */
|
|
28
28
|
documentNodeName: string;
|
|
29
29
|
/** The smallest document this schema admits. Used by `clearContent()`. */
|
|
30
30
|
emptyDocument: DocumentJSON;
|
|
@@ -38,7 +38,12 @@ export type DocumentDescriptorLimits = Pick<
|
|
|
38
38
|
/** Node name the built-in image node is stored under. */
|
|
39
39
|
export const IMAGE_NODE_NAME = 'image';
|
|
40
40
|
|
|
41
|
-
export const HEADING_LEVELS = [1,
|
|
41
|
+
export const HEADING_LEVELS = [ 1,
|
|
42
|
+
2,
|
|
43
|
+
3,
|
|
44
|
+
4,
|
|
45
|
+
5,
|
|
46
|
+
6 ] as const;
|
|
42
47
|
|
|
43
48
|
/**
|
|
44
49
|
* The built-in image node spec: a void block node carrying
|
|
@@ -57,15 +62,15 @@ export function imageSchemaNodeSpec(): SchemaNodeSpec {
|
|
|
57
62
|
height: { default: null },
|
|
58
63
|
},
|
|
59
64
|
role: 'block',
|
|
60
|
-
parseDOM: [{ tag: 'img' }],
|
|
61
|
-
toDOM: ['img'],
|
|
65
|
+
parseDOM: [ { tag: 'img' } ],
|
|
66
|
+
toDOM: [ 'img' ],
|
|
62
67
|
isVoid: true,
|
|
63
68
|
deletableOnBackspace: false,
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
export function imageNodeSpec(name: string = IMAGE_NODE_NAME): NodeSpec {
|
|
68
|
-
return defineSchema({ nodes: { [name]: imageSchemaNodeSpec() } }).nodes[0]
|
|
73
|
+
return defineSchema({ nodes: { [name]: imageSchemaNodeSpec() } }).nodes[0];
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
/**
|
|
@@ -73,14 +78,15 @@ export function imageNodeSpec(name: string = IMAGE_NODE_NAME): NodeSpec {
|
|
|
73
78
|
* already declares one. {@link tiptapCompatibleSchema} already includes it.
|
|
74
79
|
*/
|
|
75
80
|
export function withImagesSchema(schema: SchemaDefinition): SchemaDefinition {
|
|
76
|
-
const hasImageNode = schema.nodes.some(
|
|
81
|
+
const hasImageNode = schema.nodes.some(node => node.name === IMAGE_NODE_NAME);
|
|
82
|
+
|
|
77
83
|
if (hasImageNode) {
|
|
78
84
|
return schema;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
return {
|
|
82
88
|
...schema,
|
|
83
|
-
nodes: [...schema.nodes, imageNodeSpec()],
|
|
89
|
+
nodes: [ ...schema.nodes, imageNodeSpec() ],
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
|
|
@@ -130,22 +136,22 @@ export const tiptapCompatibleSchemaSpec: SchemaSpec = {
|
|
|
130
136
|
content: 'inline*',
|
|
131
137
|
group: 'block',
|
|
132
138
|
role: 'textBlock',
|
|
133
|
-
parseDOM: [{ tag: 'p' }],
|
|
134
|
-
toDOM: ['p', 0],
|
|
139
|
+
parseDOM: [ { tag: 'p' } ],
|
|
140
|
+
toDOM: [ 'p', 0 ],
|
|
135
141
|
},
|
|
136
142
|
heading: {
|
|
137
143
|
content: 'inline*',
|
|
138
144
|
group: 'block',
|
|
139
145
|
role: 'heading',
|
|
140
146
|
attrs: { level: { default: 1 } },
|
|
141
|
-
parseDOM: HEADING_LEVELS.map(
|
|
147
|
+
parseDOM: HEADING_LEVELS.map(level => ({
|
|
142
148
|
tag: `h${level}`,
|
|
143
149
|
attrs: { level },
|
|
144
150
|
})),
|
|
145
151
|
toDOM: {
|
|
146
152
|
switchOn: 'level',
|
|
147
153
|
cases: Object.fromEntries(
|
|
148
|
-
HEADING_LEVELS.map(
|
|
154
|
+
HEADING_LEVELS.map(level => [ level, [ `h${level}`, 0 ] as const ])
|
|
149
155
|
),
|
|
150
156
|
},
|
|
151
157
|
},
|
|
@@ -153,62 +159,62 @@ export const tiptapCompatibleSchemaSpec: SchemaSpec = {
|
|
|
153
159
|
content: 'block+',
|
|
154
160
|
group: 'block',
|
|
155
161
|
role: 'block',
|
|
156
|
-
parseDOM: [{ tag: 'blockquote' }],
|
|
157
|
-
toDOM: ['blockquote', 0],
|
|
162
|
+
parseDOM: [ { tag: 'blockquote' } ],
|
|
163
|
+
toDOM: [ 'blockquote', 0 ],
|
|
158
164
|
},
|
|
159
165
|
codeBlock: {
|
|
160
166
|
attrs: { language: { default: null } },
|
|
161
167
|
content: 'text*',
|
|
162
168
|
group: 'block',
|
|
163
169
|
role: 'textBlock',
|
|
164
|
-
parseDOM: [{ tag: 'pre' }],
|
|
165
|
-
toDOM: ['pre', 0],
|
|
170
|
+
parseDOM: [ { tag: 'pre' } ],
|
|
171
|
+
toDOM: [ 'pre', 0 ],
|
|
166
172
|
},
|
|
167
173
|
bulletList: {
|
|
168
174
|
content: 'listItem+',
|
|
169
175
|
group: 'block',
|
|
170
176
|
role: 'list',
|
|
171
|
-
parseDOM: [{ tag: 'ul' }],
|
|
172
|
-
toDOM: ['ul', 0],
|
|
177
|
+
parseDOM: [ { tag: 'ul' } ],
|
|
178
|
+
toDOM: [ 'ul', 0 ],
|
|
173
179
|
},
|
|
174
180
|
orderedList: {
|
|
175
181
|
content: 'listItem+',
|
|
176
182
|
group: 'block',
|
|
177
183
|
attrs: { start: { default: 1 } },
|
|
178
184
|
role: 'list',
|
|
179
|
-
parseDOM: [{ tag: 'ol' }],
|
|
180
|
-
toDOM: ['ol', 0],
|
|
185
|
+
parseDOM: [ { tag: 'ol' } ],
|
|
186
|
+
toDOM: [ 'ol', 0 ],
|
|
181
187
|
},
|
|
182
188
|
listItem: {
|
|
183
189
|
content: 'paragraph block*',
|
|
184
190
|
role: 'listItem',
|
|
185
|
-
parseDOM: [{ tag: 'li' }],
|
|
186
|
-
toDOM: ['li', 0],
|
|
191
|
+
parseDOM: [ { tag: 'li' } ],
|
|
192
|
+
toDOM: [ 'li', 0 ],
|
|
187
193
|
},
|
|
188
194
|
hardBreak: {
|
|
189
195
|
group: 'inline',
|
|
190
196
|
role: 'hardBreak',
|
|
191
|
-
parseDOM: [{ tag: 'br' }],
|
|
192
|
-
toDOM: ['br'],
|
|
197
|
+
parseDOM: [ { tag: 'br' } ],
|
|
198
|
+
toDOM: [ 'br' ],
|
|
193
199
|
isVoid: true,
|
|
194
200
|
},
|
|
195
201
|
horizontalRule: {
|
|
196
202
|
group: 'block',
|
|
197
203
|
role: 'block',
|
|
198
|
-
parseDOM: [{ tag: 'hr' }],
|
|
199
|
-
toDOM: ['hr'],
|
|
204
|
+
parseDOM: [ { tag: 'hr' } ],
|
|
205
|
+
toDOM: [ 'hr' ],
|
|
200
206
|
isVoid: true,
|
|
201
207
|
},
|
|
202
208
|
[IMAGE_NODE_NAME]: imageSchemaNodeSpec(),
|
|
203
209
|
text: { group: 'inline', role: 'text' },
|
|
204
210
|
},
|
|
205
211
|
marks: {
|
|
206
|
-
bold: { parseDOM: [{ tag: 'strong' }], toDOM: ['strong', 0] },
|
|
207
|
-
italic: { parseDOM: [{ tag: 'em' }], toDOM: ['em', 0] },
|
|
208
|
-
underline: { parseDOM: [{ tag: 'u' }], toDOM: ['u', 0] },
|
|
209
|
-
strike: { parseDOM: [{ tag: 's' }], toDOM: ['s', 0] },
|
|
210
|
-
code: { parseDOM: [{ tag: 'code' }], toDOM: ['code', 0] },
|
|
211
|
-
link: { attrs: { href: {} }, parseDOM: [{ tag: 'a' }], toDOM: ['a', 0] },
|
|
212
|
+
bold: { parseDOM: [ { tag: 'strong' } ], toDOM: [ 'strong', 0 ] },
|
|
213
|
+
italic: { parseDOM: [ { tag: 'em' } ], toDOM: [ 'em', 0 ] },
|
|
214
|
+
underline: { parseDOM: [ { tag: 'u' } ], toDOM: [ 'u', 0 ] },
|
|
215
|
+
strike: { parseDOM: [ { tag: 's' } ], toDOM: [ 's', 0 ] },
|
|
216
|
+
code: { parseDOM: [ { tag: 'code' } ], toDOM: [ 'code', 0 ] },
|
|
217
|
+
link: { attrs: { href: {} }, parseDOM: [ { tag: 'a' } ], toDOM: [ 'a', 0 ] },
|
|
212
218
|
},
|
|
213
219
|
};
|
|
214
220
|
|