@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
package/src/schemaDefinition.ts
CHANGED
|
@@ -71,7 +71,7 @@ export interface NodeSpec {
|
|
|
71
71
|
* `insert_content_json`) admits attrs on this node that are not declared
|
|
72
72
|
* in `attrs`, instead of filtering them out. Default `false`. Intended
|
|
73
73
|
* for node types with an intentional pass-through-metadata contract
|
|
74
|
-
* (e.g. the mention node
|
|
74
|
+
* (e.g. the mention node : see `mentionNodeSpec()` in addons.ts).
|
|
75
75
|
*/
|
|
76
76
|
allowUndeclaredAttrs?: boolean;
|
|
77
77
|
/** Public JSON representation when it differs from the native node name. */
|
|
@@ -165,18 +165,35 @@ export function outputTag(spec: DOMOutputSpec): string {
|
|
|
165
165
|
|
|
166
166
|
export function appendGroup(group: string | undefined, name: string): string {
|
|
167
167
|
const groups = group?.split(/\s+/).filter(Boolean) ?? [];
|
|
168
|
-
|
|
168
|
+
|
|
169
|
+
if (!groups.includes(name)) {
|
|
170
|
+
groups.push(name);
|
|
171
|
+
}
|
|
172
|
+
|
|
169
173
|
return groups.join(' ');
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
export function schemaNodeRole(name: string, node: SchemaNodeSpec): string {
|
|
173
|
-
if (node.role != null)
|
|
174
|
-
|
|
175
|
-
|
|
177
|
+
if (node.role != null) {
|
|
178
|
+
return node.role === 'heading' ? 'textBlock' : node.role;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (name === 'doc') {
|
|
182
|
+
return 'doc';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (name === 'text') {
|
|
186
|
+
return 'text';
|
|
187
|
+
}
|
|
188
|
+
|
|
176
189
|
if (node.content === 'inline*' && node.group?.split(/\s+/).includes('block')) {
|
|
177
190
|
return 'textBlock';
|
|
178
191
|
}
|
|
179
|
-
|
|
192
|
+
|
|
193
|
+
if (node.group?.split(/\s+/).includes('inline')) {
|
|
194
|
+
return 'inline';
|
|
195
|
+
}
|
|
196
|
+
|
|
180
197
|
return 'block';
|
|
181
198
|
}
|
|
182
199
|
|
|
@@ -188,16 +205,24 @@ export function caseAttributeValue(
|
|
|
188
205
|
defaultValue: unknown
|
|
189
206
|
): unknown {
|
|
190
207
|
const rule = parseDOM?.find(
|
|
191
|
-
|
|
208
|
+
candidate =>
|
|
192
209
|
candidate.tag === tag &&
|
|
193
210
|
candidate.attrs != null &&
|
|
194
211
|
String(candidate.attrs[attribute]) === key
|
|
195
212
|
);
|
|
213
|
+
|
|
196
214
|
if (rule?.attrs && Object.prototype.hasOwnProperty.call(rule.attrs, attribute)) {
|
|
197
215
|
return rule.attrs[attribute];
|
|
198
216
|
}
|
|
199
|
-
|
|
200
|
-
if (typeof defaultValue === '
|
|
217
|
+
|
|
218
|
+
if (typeof defaultValue === 'number') {
|
|
219
|
+
return Number(key);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (typeof defaultValue === 'boolean') {
|
|
223
|
+
return key === 'true';
|
|
224
|
+
}
|
|
225
|
+
|
|
201
226
|
return key;
|
|
202
227
|
}
|
|
203
228
|
|
|
@@ -208,62 +233,75 @@ export function validateAttributeDOMRules(
|
|
|
208
233
|
defaultValue: unknown
|
|
209
234
|
): void {
|
|
210
235
|
const cases = Object.entries(switched.cases);
|
|
236
|
+
|
|
211
237
|
let discriminatorType =
|
|
212
238
|
typeof defaultValue === 'string' ||
|
|
213
239
|
typeof defaultValue === 'number' ||
|
|
214
240
|
typeof defaultValue === 'boolean'
|
|
215
241
|
? typeof defaultValue
|
|
216
242
|
: undefined;
|
|
243
|
+
|
|
217
244
|
if (discriminatorType == null && parseDOM != null) {
|
|
218
|
-
const parsedTypes = parseDOM.map(
|
|
245
|
+
const parsedTypes = parseDOM.map(rule => {
|
|
219
246
|
const value = rule.attrs?.[switched.switchOn];
|
|
247
|
+
|
|
220
248
|
return typeof value === 'string' ||
|
|
221
249
|
typeof value === 'number' ||
|
|
222
250
|
typeof value === 'boolean'
|
|
223
251
|
? typeof value
|
|
224
252
|
: undefined;
|
|
225
253
|
});
|
|
254
|
+
|
|
226
255
|
const firstType = parsedTypes[0];
|
|
227
|
-
|
|
256
|
+
|
|
257
|
+
if (firstType != null && parsedTypes.every(type => type === firstType)) {
|
|
228
258
|
discriminatorType = firstType;
|
|
229
259
|
}
|
|
230
260
|
}
|
|
261
|
+
|
|
231
262
|
if (discriminatorType == null) {
|
|
232
263
|
throw new Error(
|
|
233
264
|
`node '${name}' DOM discriminator '${switched.switchOn}' must have a scalar type`
|
|
234
265
|
);
|
|
235
266
|
}
|
|
267
|
+
|
|
236
268
|
if (parseDOM == null) {
|
|
237
|
-
const validCases = cases.every(([caseKey]) => {
|
|
269
|
+
const validCases = cases.every(([ caseKey ]) => {
|
|
238
270
|
if (discriminatorType === 'number') {
|
|
239
271
|
return /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(caseKey);
|
|
240
272
|
}
|
|
273
|
+
|
|
241
274
|
return discriminatorType !== 'boolean' || caseKey === 'true' || caseKey === 'false';
|
|
242
275
|
});
|
|
276
|
+
|
|
243
277
|
if (!validCases) {
|
|
244
278
|
throw new Error(
|
|
245
279
|
`node '${name}' DOM discriminator '${switched.switchOn}' must use ${discriminatorType} values`
|
|
246
280
|
);
|
|
247
281
|
}
|
|
282
|
+
|
|
248
283
|
return;
|
|
249
284
|
}
|
|
250
|
-
|
|
285
|
+
|
|
286
|
+
const matchesCase = ([ caseKey, output ]: [string, DOMOutputSpec]) =>
|
|
251
287
|
parseDOM.filter(
|
|
252
|
-
|
|
288
|
+
rule =>
|
|
253
289
|
rule.tag === outputTag(output) &&
|
|
254
290
|
rule.attrs != null &&
|
|
255
291
|
Object.prototype.hasOwnProperty.call(rule.attrs, switched.switchOn) &&
|
|
256
292
|
String(rule.attrs[switched.switchOn]) === caseKey
|
|
257
293
|
).length === 1;
|
|
294
|
+
|
|
258
295
|
if (parseDOM.length !== cases.length || !cases.every(matchesCase)) {
|
|
259
296
|
throw new Error(
|
|
260
297
|
`node '${name}' DOM parse rules must map one-to-one with '${switched.switchOn}' output cases`
|
|
261
298
|
);
|
|
262
299
|
}
|
|
300
|
+
|
|
263
301
|
if (
|
|
264
302
|
discriminatorType != null &&
|
|
265
303
|
parseDOM.some(
|
|
266
|
-
|
|
304
|
+
rule =>
|
|
267
305
|
rule.attrs != null && typeof rule.attrs[switched.switchOn] !== discriminatorType
|
|
268
306
|
)
|
|
269
307
|
) {
|
|
@@ -282,11 +320,14 @@ export function staticDOMTag(
|
|
|
282
320
|
if ((parseDOM?.length ?? 0) > 1) {
|
|
283
321
|
throw new Error(`${kind} '${name}' has multiple static DOM parse rules`);
|
|
284
322
|
}
|
|
323
|
+
|
|
285
324
|
const parsed = parseDOM?.[0]?.tag;
|
|
286
325
|
const serialized = toDOM == null ? undefined : outputTag(toDOM);
|
|
326
|
+
|
|
287
327
|
if (parsed != null && serialized != null && parsed !== serialized) {
|
|
288
328
|
throw new Error(`${kind} '${name}' parses '${parsed}' but serializes '${serialized}'`);
|
|
289
329
|
}
|
|
330
|
+
|
|
290
331
|
return serialized ?? parsed;
|
|
291
332
|
}
|
|
292
333
|
|
|
@@ -294,7 +335,8 @@ export function staticDOMTag(
|
|
|
294
335
|
export function defineSchema(spec: SchemaSpec): SchemaDefinition {
|
|
295
336
|
const nodes: NodeSpec[] = [];
|
|
296
337
|
const names = new Set<string>();
|
|
297
|
-
|
|
338
|
+
|
|
339
|
+
for (const [ name, node ] of Object.entries(spec.nodes)) {
|
|
298
340
|
const common = {
|
|
299
341
|
content: node.content ?? '',
|
|
300
342
|
...(node.group == null ? {} : { group: node.group }),
|
|
@@ -309,11 +351,14 @@ export function defineSchema(spec: SchemaSpec): SchemaDefinition {
|
|
|
309
351
|
? {}
|
|
310
352
|
: { allowUndeclaredAttrs: node.allowUndeclaredAttrs }),
|
|
311
353
|
};
|
|
354
|
+
|
|
312
355
|
if (node.toDOM != null && !Array.isArray(node.toDOM)) {
|
|
313
356
|
const switched = node.toDOM as AttributeDOMOutputSpec;
|
|
357
|
+
|
|
314
358
|
if (RESERVED_WIRE_NODE_TYPES.has(name)) {
|
|
315
359
|
throw new Error(`node '${name}' uses a reserved wire projection type`);
|
|
316
360
|
}
|
|
361
|
+
|
|
317
362
|
if (
|
|
318
363
|
node.attrs == null ||
|
|
319
364
|
!Object.prototype.hasOwnProperty.call(node.attrs, switched.switchOn)
|
|
@@ -322,18 +367,25 @@ export function defineSchema(spec: SchemaSpec): SchemaDefinition {
|
|
|
322
367
|
`node '${name}' switches on undeclared attribute '${switched.switchOn}'`
|
|
323
368
|
);
|
|
324
369
|
}
|
|
370
|
+
|
|
325
371
|
if (Object.keys(switched.cases).length === 0) {
|
|
326
372
|
throw new Error(`node '${name}' has no DOM output cases`);
|
|
327
373
|
}
|
|
374
|
+
|
|
328
375
|
const discriminatorDefault = node.attrs[switched.switchOn]?.default;
|
|
329
376
|
validateAttributeDOMRules(name, switched, node.parseDOM, discriminatorDefault);
|
|
330
377
|
const { attrs: _attrs, ...variantCommon } = common;
|
|
331
|
-
|
|
378
|
+
|
|
379
|
+
for (const [ caseKey, output ] of Object.entries(switched.cases)) {
|
|
332
380
|
const tag = outputTag(output);
|
|
333
|
-
|
|
381
|
+
|
|
382
|
+
if (names.has(tag)) {
|
|
334
383
|
throw new Error(`schema produces duplicate native node '${tag}'`);
|
|
384
|
+
}
|
|
385
|
+
|
|
335
386
|
names.add(tag);
|
|
336
387
|
const { [switched.switchOn]: _discriminator, ...variantAttrs } = node.attrs ?? {};
|
|
388
|
+
|
|
337
389
|
nodes.push({
|
|
338
390
|
name: tag,
|
|
339
391
|
...variantCommon,
|
|
@@ -354,17 +406,23 @@ export function defineSchema(spec: SchemaSpec): SchemaDefinition {
|
|
|
354
406
|
},
|
|
355
407
|
});
|
|
356
408
|
}
|
|
409
|
+
|
|
357
410
|
continue;
|
|
358
411
|
}
|
|
359
412
|
|
|
360
|
-
if (names.has(name))
|
|
413
|
+
if (names.has(name)) {
|
|
414
|
+
throw new Error(`schema produces duplicate native node '${name}'`);
|
|
415
|
+
}
|
|
416
|
+
|
|
361
417
|
names.add(name);
|
|
418
|
+
|
|
362
419
|
const tag = staticDOMTag(
|
|
363
420
|
'node',
|
|
364
421
|
name,
|
|
365
422
|
node.parseDOM,
|
|
366
423
|
node.toDOM as DOMOutputSpec | undefined
|
|
367
424
|
);
|
|
425
|
+
|
|
368
426
|
nodes.push({
|
|
369
427
|
name,
|
|
370
428
|
...common,
|
|
@@ -372,12 +430,14 @@ export function defineSchema(spec: SchemaSpec): SchemaDefinition {
|
|
|
372
430
|
});
|
|
373
431
|
}
|
|
374
432
|
|
|
375
|
-
const marks: MarkSpec[] = Object.entries(spec.marks ?? {}).map(([name, mark]) => {
|
|
433
|
+
const marks: MarkSpec[] = Object.entries(spec.marks ?? {}).map(([ name, mark ]) => {
|
|
376
434
|
const tag = staticDOMTag('mark', name, mark.parseDOM, mark.toDOM);
|
|
377
435
|
const normalizedTag = tag?.toLowerCase();
|
|
436
|
+
|
|
378
437
|
if (normalizedTag != null && !ALLOWED_MARK_HTML_TAGS.has(normalizedTag)) {
|
|
379
438
|
throw new Error(`mark '${name}' has disallowed HTML tag '${tag}'`);
|
|
380
439
|
}
|
|
440
|
+
|
|
381
441
|
return {
|
|
382
442
|
name,
|
|
383
443
|
...(mark.attrs == null ? {} : { attrs: mark.attrs }),
|
|
@@ -388,7 +448,9 @@ export function defineSchema(spec: SchemaSpec): SchemaDefinition {
|
|
|
388
448
|
: { allowUndeclaredAttrs: mark.allowUndeclaredAttrs }),
|
|
389
449
|
};
|
|
390
450
|
});
|
|
451
|
+
|
|
391
452
|
const schema = { nodes, marks };
|
|
453
|
+
|
|
392
454
|
return spec.atoms == null || spec.atoms.length === 0
|
|
393
455
|
? schema
|
|
394
456
|
: withAtomsSchema(schema, spec.atoms);
|
package/src/schemaDocument.ts
CHANGED
|
@@ -20,26 +20,33 @@ export function defaultEmptyDocument(
|
|
|
20
20
|
limits?: DocumentDescriptorLimits
|
|
21
21
|
): DocumentJSON {
|
|
22
22
|
const resolvedLimits = resolveDescriptorLimits(limits);
|
|
23
|
+
|
|
23
24
|
if (schema.nodes.length > resolvedLimits.maxSchemaNodes) {
|
|
24
25
|
throw schemaBoundaryError(resolvedLimits.maxSchemaNodes, schema.nodes.length);
|
|
25
26
|
}
|
|
27
|
+
|
|
26
28
|
let expressionBytes = 0;
|
|
29
|
+
|
|
27
30
|
for (const node of schema.nodes) {
|
|
28
31
|
if (node != null && typeof node.content === 'string') {
|
|
29
32
|
expressionBytes += utf8ByteLengthUpTo(
|
|
30
33
|
node.content,
|
|
31
34
|
resolvedLimits.maxSchemaExpressionBytes - expressionBytes
|
|
32
35
|
);
|
|
36
|
+
|
|
33
37
|
if (expressionBytes > resolvedLimits.maxSchemaExpressionBytes) {
|
|
34
38
|
throw schemaBoundaryError(resolvedLimits.maxSchemaExpressionBytes, expressionBytes);
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
}
|
|
42
|
+
|
|
38
43
|
const admissionBudget = createSchemaWorkBudget(resolvedLimits);
|
|
39
44
|
const admittedCollections = admitSchemaCollections(schema, admissionBudget);
|
|
45
|
+
|
|
40
46
|
if (admittedCollections == null) {
|
|
41
47
|
throw new Error('schema cannot construct a default document: invalid schema collections');
|
|
42
48
|
}
|
|
49
|
+
|
|
43
50
|
return constructDefaultEmptyDocument(schema, resolvedLimits, admittedCollections);
|
|
44
51
|
}
|
|
45
52
|
|
|
@@ -60,10 +67,12 @@ export function resolveDocumentDescriptor(
|
|
|
60
67
|
): ResolvedDocumentSchema {
|
|
61
68
|
const resolvedLimits = resolveDescriptorLimits(limits);
|
|
62
69
|
const resolvedSchema = resolveDocumentSchema(schema, resolvedLimits);
|
|
63
|
-
const documentNode = resolvedSchema.nodes.find(
|
|
70
|
+
const documentNode = resolvedSchema.nodes.find(node => node.role === 'doc');
|
|
71
|
+
|
|
64
72
|
if (!documentNode) {
|
|
65
73
|
throw new NativeEditorBoundaryError('SCHEMA_INVALID', 'schema has no document-role node');
|
|
66
74
|
}
|
|
75
|
+
|
|
67
76
|
return {
|
|
68
77
|
schema: resolvedSchema,
|
|
69
78
|
documentNodeName: documentNode.name,
|
|
@@ -80,13 +89,18 @@ export function normalizeDocumentJson(
|
|
|
80
89
|
'documentNodeName' in schemaOrDescriptor
|
|
81
90
|
? schemaOrDescriptor
|
|
82
91
|
: resolveDocumentDescriptor(schemaOrDescriptor, limits);
|
|
92
|
+
|
|
83
93
|
const root = doc as { type?: unknown; content?: unknown } | null;
|
|
94
|
+
|
|
84
95
|
if (root?.type !== descriptor.documentNodeName) {
|
|
85
96
|
return doc;
|
|
86
97
|
}
|
|
98
|
+
|
|
87
99
|
const content = root?.content;
|
|
100
|
+
|
|
88
101
|
if (Array.isArray(content) && content.length > 0) {
|
|
89
102
|
return doc;
|
|
90
103
|
}
|
|
104
|
+
|
|
91
105
|
return descriptor.emptyDocument;
|
|
92
106
|
}
|
|
@@ -13,22 +13,31 @@ export function constructDefaultEmptyDocument(
|
|
|
13
13
|
resolvedLimits: ResolvedEditorResourceLimits,
|
|
14
14
|
admittedCollections: AdmittedSchemaCollections
|
|
15
15
|
): DocumentJSON {
|
|
16
|
-
const docNode = schema.nodes.find(
|
|
17
|
-
|
|
16
|
+
const docNode = schema.nodes.find(node => node.role === 'doc');
|
|
17
|
+
|
|
18
|
+
if (!docNode) {
|
|
19
|
+
throw new Error('schema cannot construct a default document: missing doc role');
|
|
20
|
+
}
|
|
18
21
|
|
|
19
22
|
const budget = { nodes: 0, work: 0, exhausted: false };
|
|
23
|
+
|
|
20
24
|
const workLimit = Math.min(
|
|
21
25
|
DEFAULT_CONTENT_MAX_NODES,
|
|
22
26
|
resolvedLimits.maxSchemaNodes * 64 + resolvedLimits.maxSchemaExpressionBytes * 32
|
|
23
27
|
);
|
|
28
|
+
|
|
24
29
|
const consumeWork = (): boolean => {
|
|
25
30
|
if (budget.work >= workLimit) {
|
|
26
31
|
budget.exhausted = true;
|
|
32
|
+
|
|
27
33
|
return false;
|
|
28
34
|
}
|
|
35
|
+
|
|
29
36
|
budget.work += 1;
|
|
37
|
+
|
|
30
38
|
return true;
|
|
31
39
|
};
|
|
40
|
+
|
|
32
41
|
const candidatePriority = (candidate: NodeSpec): number => {
|
|
33
42
|
if (
|
|
34
43
|
candidate.role === 'textBlock' &&
|
|
@@ -36,51 +45,75 @@ export function constructDefaultEmptyDocument(
|
|
|
36
45
|
) {
|
|
37
46
|
return 0;
|
|
38
47
|
}
|
|
48
|
+
|
|
39
49
|
return candidate.role === 'textBlock' ? 1 : 2;
|
|
40
50
|
};
|
|
51
|
+
|
|
41
52
|
const candidatesBySymbol = new Map<string, NodeSpec[]>();
|
|
42
53
|
let candidateIndexAdmitted = true;
|
|
54
|
+
|
|
43
55
|
const addCandidate = (symbol: string, candidate: NodeSpec): boolean => {
|
|
44
|
-
if (!consumeWork())
|
|
56
|
+
if (!consumeWork()) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
45
60
|
const candidates = candidatesBySymbol.get(symbol);
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
|
|
62
|
+
if (candidates) {
|
|
63
|
+
candidates.push(candidate);
|
|
64
|
+
} else {
|
|
65
|
+
candidatesBySymbol.set(symbol, [ candidate ]);
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
return true;
|
|
49
69
|
};
|
|
70
|
+
|
|
50
71
|
for (const candidate of schema.nodes) {
|
|
51
72
|
if (!addCandidate(candidate.name, candidate)) {
|
|
52
73
|
candidateIndexAdmitted = false;
|
|
53
74
|
break;
|
|
54
75
|
}
|
|
76
|
+
|
|
55
77
|
for (const group of admittedCollections.groupsByNode.get(candidate) ?? []) {
|
|
56
78
|
if (group !== candidate.name && !addCandidate(group, candidate)) {
|
|
57
79
|
candidateIndexAdmitted = false;
|
|
58
80
|
break;
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
|
-
|
|
83
|
+
|
|
84
|
+
if (!candidateIndexAdmitted) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
62
87
|
}
|
|
88
|
+
|
|
63
89
|
for (const candidates of candidatesBySymbol.values()) {
|
|
64
90
|
const sortingWork = Math.max(
|
|
65
91
|
candidates.length,
|
|
66
92
|
Math.ceil(candidates.length * Math.log2(Math.max(2, candidates.length)))
|
|
67
93
|
);
|
|
94
|
+
|
|
68
95
|
for (let index = 0; index < sortingWork; index += 1) {
|
|
69
96
|
if (!consumeWork()) {
|
|
70
97
|
candidateIndexAdmitted = false;
|
|
71
98
|
break;
|
|
72
99
|
}
|
|
73
100
|
}
|
|
74
|
-
|
|
101
|
+
|
|
102
|
+
if (!candidateIndexAdmitted) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
75
106
|
candidates.sort(
|
|
76
107
|
(left, right) =>
|
|
77
108
|
candidatePriority(left) - candidatePriority(right) ||
|
|
78
109
|
left.name.localeCompare(right.name)
|
|
79
110
|
);
|
|
80
111
|
}
|
|
112
|
+
|
|
81
113
|
if (!candidateIndexAdmitted) {
|
|
82
114
|
throw schemaBoundaryError(workLimit, workLimit + 1);
|
|
83
115
|
}
|
|
116
|
+
|
|
84
117
|
const constructNode = (
|
|
85
118
|
node: NodeSpec,
|
|
86
119
|
visiting: Set<string>,
|
|
@@ -92,57 +125,88 @@ export function constructDefaultEmptyDocument(
|
|
|
92
125
|
) {
|
|
93
126
|
return undefined;
|
|
94
127
|
}
|
|
95
|
-
|
|
128
|
+
|
|
129
|
+
if (node.role === 'text' || visiting.has(node.name)) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
96
133
|
const attrs = admittedCollections.attrsByNode.get(node) ?? [];
|
|
97
|
-
|
|
134
|
+
|
|
135
|
+
for (let index = 0; index < attrs.length; index += 1) {
|
|
98
136
|
if (!consumeWork()) {
|
|
99
137
|
budget.exhausted = true;
|
|
138
|
+
|
|
100
139
|
return undefined;
|
|
101
140
|
}
|
|
102
141
|
}
|
|
103
|
-
|
|
142
|
+
|
|
143
|
+
if (attrs.some(([ , spec ]) => spec.default === undefined)) {
|
|
104
144
|
return undefined;
|
|
105
145
|
}
|
|
106
146
|
|
|
107
147
|
visiting.add(node.name);
|
|
148
|
+
|
|
108
149
|
const children = minimalContentMatch(
|
|
109
150
|
node.content ?? '',
|
|
110
|
-
|
|
151
|
+
symbol => {
|
|
111
152
|
for (const candidate of candidatesBySymbol.get(symbol) ?? []) {
|
|
112
|
-
if (!consumeWork())
|
|
153
|
+
if (!consumeWork()) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
113
157
|
const constructed = constructNode(candidate, visiting, depth + 1);
|
|
114
|
-
|
|
158
|
+
|
|
159
|
+
if (constructed) {
|
|
160
|
+
return constructed;
|
|
161
|
+
}
|
|
115
162
|
}
|
|
163
|
+
|
|
116
164
|
return undefined;
|
|
117
165
|
},
|
|
118
166
|
consumeWork
|
|
119
167
|
);
|
|
168
|
+
|
|
120
169
|
visiting.delete(node.name);
|
|
121
|
-
|
|
170
|
+
|
|
171
|
+
if (!children) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
122
175
|
if (budget.nodes >= resolvedLimits.maxDocumentNodes) {
|
|
123
176
|
budget.exhausted = true;
|
|
177
|
+
|
|
124
178
|
return undefined;
|
|
125
179
|
}
|
|
180
|
+
|
|
126
181
|
budget.nodes += 1;
|
|
127
182
|
|
|
128
183
|
const result: DocumentJSON = { type: node.json?.type ?? node.name };
|
|
129
|
-
|
|
184
|
+
|
|
185
|
+
if (children.length > 0) {
|
|
186
|
+
result.content = children;
|
|
187
|
+
}
|
|
188
|
+
|
|
130
189
|
const projectedAttrs = Object.entries(node.json?.attrs ?? {});
|
|
190
|
+
|
|
131
191
|
if (attrs.length > 0 || projectedAttrs.length > 0) {
|
|
132
192
|
result.attrs = Object.fromEntries([
|
|
133
|
-
...attrs.map(([name, spec]) => [name, spec.default] as const),
|
|
193
|
+
...attrs.map(([ name, spec ]) => [ name, spec.default ] as const),
|
|
134
194
|
...projectedAttrs,
|
|
135
195
|
]);
|
|
136
196
|
}
|
|
197
|
+
|
|
137
198
|
return result;
|
|
138
199
|
};
|
|
139
200
|
|
|
140
201
|
const document = constructNode(docNode, new Set(), 0);
|
|
202
|
+
|
|
141
203
|
if (!document) {
|
|
142
204
|
if (budget.exhausted || budget.work >= workLimit) {
|
|
143
205
|
throw schemaBoundaryError(workLimit, workLimit + 1);
|
|
144
206
|
}
|
|
207
|
+
|
|
145
208
|
throw new Error(`schema cannot construct a default document for '${docNode.name}'`);
|
|
146
209
|
}
|
|
210
|
+
|
|
147
211
|
return document;
|
|
148
212
|
}
|