@apollohg/react-native-rich-text-editor 2.0.0 → 2.0.1
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 +1 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/android/build.gradle +3 -0
- 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 +84 -37
- 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/Info.plist +5 -5
- 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/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/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/dist/atoms.js
CHANGED
|
@@ -7,10 +7,12 @@ const attributeValidation_1 = require("./attributeValidation");
|
|
|
7
7
|
const atomConstants_1 = require("./atomConstants");
|
|
8
8
|
const atomPolicy_1 = require("./atomPolicy");
|
|
9
9
|
function isAtomComponent(value) {
|
|
10
|
-
if (typeof value === 'function')
|
|
10
|
+
if (typeof value === 'function') {
|
|
11
11
|
return true;
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
if (value == null || typeof value !== 'object' || !('$$typeof' in value)) {
|
|
13
14
|
return false;
|
|
15
|
+
}
|
|
14
16
|
return (value.$$typeof === Symbol.for('react.memo') ||
|
|
15
17
|
value.$$typeof === Symbol.for('react.forward_ref') ||
|
|
16
18
|
value.$$typeof === Symbol.for('react.lazy'));
|
|
@@ -48,7 +50,7 @@ function normalizedHtmlRules(config) {
|
|
|
48
50
|
throw new Error(`atom '${config.name}' requires HTML rules`);
|
|
49
51
|
}
|
|
50
52
|
const raw = config.html;
|
|
51
|
-
if (Object.keys(raw).some(
|
|
53
|
+
if (Object.keys(raw).some(key => !['tag', 'staticAttrs', 'attrMap'].includes(key))) {
|
|
52
54
|
throw new Error(`atom '${config.name}' has an unknown HTML rule field`);
|
|
53
55
|
}
|
|
54
56
|
if (typeof raw.tag !== 'string' || !isSafeTag(raw.tag)) {
|
|
@@ -65,7 +67,7 @@ function normalizedHtmlRules(config) {
|
|
|
65
67
|
}
|
|
66
68
|
const attrs = config.attrs ?? {};
|
|
67
69
|
const attrMap = raw.attrMap == null
|
|
68
|
-
? Object.fromEntries(Object.keys(attrs).map(
|
|
70
|
+
? Object.fromEntries(Object.keys(attrs).map(name => [name, `data-${kebabCase(name)}`]))
|
|
69
71
|
: stringRecord(raw.attrMap, `atom '${config.name}' attrMap`);
|
|
70
72
|
for (const name of Object.keys(attrMap)) {
|
|
71
73
|
if (!Object.prototype.hasOwnProperty.call(attrs, name)) {
|
|
@@ -115,10 +117,12 @@ function defineAtomNode(config) {
|
|
|
115
117
|
if (config.attrs != null && (typeof config.attrs !== 'object' || Array.isArray(config.attrs))) {
|
|
116
118
|
throw new Error(`atom '${config.name}' attrs must be an object`);
|
|
117
119
|
}
|
|
118
|
-
for (const [name, spec] of Object.entries(config.attrs ?? {}))
|
|
120
|
+
for (const [name, spec] of Object.entries(config.attrs ?? {})) {
|
|
119
121
|
(0, attributeValidation_1.validateAttributeSpec)(spec, name);
|
|
120
|
-
|
|
122
|
+
}
|
|
123
|
+
if (config.idAttribute !== undefined && config.attrs?.[config.idAttribute]?.type !== 'string') {
|
|
121
124
|
throw new Error(`atom '${config.name}' identifier attribute must declare type string`);
|
|
125
|
+
}
|
|
122
126
|
const html = normalizedHtmlRules(config);
|
|
123
127
|
const nodeSpec = {
|
|
124
128
|
name: config.name,
|
|
@@ -152,8 +156,9 @@ function defineAtomNode(config) {
|
|
|
152
156
|
};
|
|
153
157
|
}
|
|
154
158
|
function schemaValuesEqual(left, right) {
|
|
155
|
-
if (Object.is(left, right))
|
|
159
|
+
if (Object.is(left, right)) {
|
|
156
160
|
return true;
|
|
161
|
+
}
|
|
157
162
|
if (left == null || right == null || typeof left !== 'object' || typeof right !== 'object') {
|
|
158
163
|
return false;
|
|
159
164
|
}
|
|
@@ -171,13 +176,14 @@ function schemaValuesEqual(left, right) {
|
|
|
171
176
|
leftKeys.every((key, index) => key === rightKeys[index] && schemaValuesEqual(leftRecord[key], rightRecord[key])));
|
|
172
177
|
}
|
|
173
178
|
function assertUnambiguousHtmlRules(nodes) {
|
|
174
|
-
const ruled = nodes.filter(
|
|
179
|
+
const ruled = nodes.filter(node => node.html != null);
|
|
175
180
|
for (let rightIndex = 1; rightIndex < ruled.length; rightIndex += 1) {
|
|
176
181
|
const right = ruled[rightIndex];
|
|
177
182
|
for (let leftIndex = 0; leftIndex < rightIndex; leftIndex += 1) {
|
|
178
183
|
const left = ruled[leftIndex];
|
|
179
|
-
if (left.html?.tag !== right.html?.tag)
|
|
184
|
+
if (left.html?.tag !== right.html?.tag) {
|
|
180
185
|
continue;
|
|
186
|
+
}
|
|
181
187
|
const leftStatic = left.html?.staticAttrs ?? {};
|
|
182
188
|
const rightStatic = right.html?.staticAttrs ?? {};
|
|
183
189
|
const conflicts = Object.entries(leftStatic).some(([name, value]) => Object.prototype.hasOwnProperty.call(rightStatic, name) &&
|
|
@@ -192,7 +198,7 @@ function withAtomsSchema(schema, atoms) {
|
|
|
192
198
|
const nodes = [...schema.nodes];
|
|
193
199
|
let changed = false;
|
|
194
200
|
for (const atom of atoms) {
|
|
195
|
-
const existing = nodes.find(
|
|
201
|
+
const existing = nodes.find(node => node.name === atom.name);
|
|
196
202
|
if (existing != null) {
|
|
197
203
|
if (!schemaValuesEqual(existing, atom.nodeSpec)) {
|
|
198
204
|
throw new Error(`schema already declares conflicting node '${atom.name}'`);
|
|
@@ -206,15 +212,17 @@ function withAtomsSchema(schema, atoms) {
|
|
|
206
212
|
return changed ? { ...schema, nodes } : schema;
|
|
207
213
|
}
|
|
208
214
|
function serializeEditorAtoms(atoms) {
|
|
209
|
-
if (atoms == null || atoms.length === 0)
|
|
215
|
+
if (atoms == null || atoms.length === 0) {
|
|
210
216
|
return undefined;
|
|
217
|
+
}
|
|
211
218
|
const serialized = {
|
|
212
219
|
nodeTypes: [],
|
|
213
220
|
estimatedHeights: {},
|
|
214
221
|
};
|
|
215
222
|
for (const atom of atoms) {
|
|
216
|
-
if (Object.prototype.hasOwnProperty.call(serialized.estimatedHeights, atom.name))
|
|
223
|
+
if (Object.prototype.hasOwnProperty.call(serialized.estimatedHeights, atom.name)) {
|
|
217
224
|
continue;
|
|
225
|
+
}
|
|
218
226
|
serialized.nodeTypes.push(atom.name);
|
|
219
227
|
serialized.estimatedHeights[atom.name] = atom.estimatedHeight ?? atomConstants_1.DEFAULT_ATOM_CHIP_HEIGHT;
|
|
220
228
|
}
|
|
@@ -7,41 +7,51 @@ function jsonType(value) {
|
|
|
7
7
|
return Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value;
|
|
8
8
|
}
|
|
9
9
|
function validateJson(value, ancestors = new Set()) {
|
|
10
|
-
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
10
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
11
11
|
return;
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
13
14
|
return;
|
|
14
|
-
|
|
15
|
+
}
|
|
16
|
+
if (typeof value !== 'object' || ancestors.has(value) || ancestors.size > 128) {
|
|
15
17
|
throw new Error('Attribute values must be finite, acyclic JSON.');
|
|
18
|
+
}
|
|
16
19
|
if (!Array.isArray(value) &&
|
|
17
20
|
Object.getPrototypeOf(value) !== Object.prototype &&
|
|
18
|
-
Object.getPrototypeOf(value) !== null)
|
|
21
|
+
Object.getPrototypeOf(value) !== null) {
|
|
19
22
|
throw new Error('Attribute values must be plain JSON objects.');
|
|
23
|
+
}
|
|
20
24
|
ancestors.add(value);
|
|
21
25
|
const entries = Array.isArray(value) ? value : Object.values(value);
|
|
22
|
-
for (let index = 0; index < entries.length; index += 1)
|
|
26
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
23
27
|
validateJson(entries[index], ancestors);
|
|
28
|
+
}
|
|
24
29
|
ancestors.delete(value);
|
|
25
30
|
}
|
|
26
31
|
function equal(left, right) {
|
|
27
|
-
if (left === right)
|
|
32
|
+
if (left === right) {
|
|
28
33
|
return true;
|
|
29
|
-
|
|
34
|
+
}
|
|
35
|
+
if (left == null || right == null || typeof left !== 'object' || typeof right !== 'object') {
|
|
30
36
|
return false;
|
|
31
|
-
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(left) !== Array.isArray(right)) {
|
|
32
39
|
return false;
|
|
40
|
+
}
|
|
33
41
|
const a = Object.keys(left), b = Object.keys(right);
|
|
34
42
|
return (a.length === b.length &&
|
|
35
|
-
a.every(
|
|
43
|
+
a.every(key => Object.prototype.hasOwnProperty.call(right, key) &&
|
|
36
44
|
equal(left[key], right[key])));
|
|
37
45
|
}
|
|
38
46
|
function validateAttribute(value, spec, name) {
|
|
39
47
|
validateJson(value);
|
|
40
48
|
const type = jsonType(value);
|
|
41
|
-
if ((spec.type != null && type !== spec.type) || (type === 'number' && !Number.isFinite(value)))
|
|
49
|
+
if ((spec.type != null && type !== spec.type) || (type === 'number' && !Number.isFinite(value))) {
|
|
42
50
|
throw new Error(`invalid attribute '${name}': expected ${spec.type ?? 'finite number'}`);
|
|
43
|
-
|
|
51
|
+
}
|
|
52
|
+
if (spec.enum != null && !spec.enum.some(entry => equal(entry, value))) {
|
|
44
53
|
throw new Error(`invalid attribute '${name}': outside enum`);
|
|
54
|
+
}
|
|
45
55
|
const size = typeof value === 'number'
|
|
46
56
|
? value
|
|
47
57
|
: typeof value === 'string'
|
|
@@ -50,48 +60,69 @@ function validateAttribute(value, spec, name) {
|
|
|
50
60
|
? value.length
|
|
51
61
|
: undefined;
|
|
52
62
|
if ((spec.min != null && (size == null || size < spec.min)) ||
|
|
53
|
-
(spec.max != null && (size == null || size > spec.max)))
|
|
63
|
+
(spec.max != null && (size == null || size > spec.max))) {
|
|
54
64
|
throw new Error(`invalid attribute '${name}': outside bounds`);
|
|
65
|
+
}
|
|
55
66
|
}
|
|
56
67
|
function validateAttributeSpec(spec, name) {
|
|
57
|
-
if (spec == null || typeof spec !== 'object' || Array.isArray(spec))
|
|
68
|
+
if (spec == null || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
58
69
|
throw new Error(`invalid attribute '${name}' declaration`);
|
|
59
|
-
|
|
70
|
+
}
|
|
71
|
+
if (Object.keys(spec).some(key => !['default',
|
|
72
|
+
'type',
|
|
73
|
+
'enum',
|
|
74
|
+
'min',
|
|
75
|
+
'max'].includes(key))) {
|
|
60
76
|
throw new Error(`invalid attribute '${name}' declaration field`);
|
|
77
|
+
}
|
|
61
78
|
if (spec.type !== undefined &&
|
|
62
|
-
!['string',
|
|
79
|
+
!['string',
|
|
80
|
+
'number',
|
|
81
|
+
'boolean',
|
|
82
|
+
'object',
|
|
83
|
+
'array'].includes(spec.type)) {
|
|
63
84
|
throw new Error(`invalid attribute '${name}' type`);
|
|
85
|
+
}
|
|
64
86
|
if (spec.min !== undefined || spec.max !== undefined) {
|
|
65
87
|
if (!['number', 'string', 'array'].includes(spec.type ?? '') ||
|
|
66
88
|
(spec.min !== undefined && !Number.isFinite(spec.min)) ||
|
|
67
89
|
(spec.max !== undefined && !Number.isFinite(spec.max)) ||
|
|
68
|
-
(spec.min !== undefined && spec.max !== undefined && spec.min > spec.max))
|
|
90
|
+
(spec.min !== undefined && spec.max !== undefined && spec.min > spec.max)) {
|
|
69
91
|
throw new Error(`invalid attribute '${name}' bounds`);
|
|
92
|
+
}
|
|
70
93
|
if (spec.type !== 'number' &&
|
|
71
|
-
[spec.min, spec.max].some(
|
|
94
|
+
[spec.min, spec.max].some(bound => bound !== undefined && (!Number.isInteger(bound) || bound < 0))) {
|
|
72
95
|
throw new Error(`invalid attribute '${name}' length bounds`);
|
|
96
|
+
}
|
|
73
97
|
}
|
|
74
98
|
if (spec.enum !== undefined) {
|
|
75
|
-
if (!Array.isArray(spec.enum) || spec.enum.length === 0)
|
|
99
|
+
if (!Array.isArray(spec.enum) || spec.enum.length === 0) {
|
|
76
100
|
throw new Error(`invalid attribute '${name}' enum`);
|
|
77
|
-
|
|
101
|
+
}
|
|
102
|
+
if (new Set(spec.enum.map(jsonType)).size !== 1) {
|
|
78
103
|
throw new Error(`invalid attribute '${name}': enum values must share one JSON type`);
|
|
79
|
-
|
|
104
|
+
}
|
|
105
|
+
for (const entry of spec.enum) {
|
|
80
106
|
validateAttribute(entry, { ...spec, enum: undefined }, name);
|
|
107
|
+
}
|
|
81
108
|
}
|
|
82
|
-
if (spec.default !== undefined)
|
|
109
|
+
if (spec.default !== undefined) {
|
|
83
110
|
validateAttribute(spec.default, spec, name);
|
|
111
|
+
}
|
|
84
112
|
}
|
|
85
113
|
function validateAttributes(attrs, specs) {
|
|
86
|
-
for (const name of Object.keys(attrs))
|
|
87
|
-
if (!Object.prototype.hasOwnProperty.call(specs, name))
|
|
114
|
+
for (const name of Object.keys(attrs)) {
|
|
115
|
+
if (!Object.prototype.hasOwnProperty.call(specs, name)) {
|
|
88
116
|
throw new Error(`undeclared attribute '${name}'`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
89
119
|
for (const [name, spec] of Object.entries(specs)) {
|
|
90
120
|
const value = Object.prototype.hasOwnProperty.call(attrs, name)
|
|
91
121
|
? attrs[name]
|
|
92
122
|
: spec.default;
|
|
93
|
-
if (value === undefined)
|
|
123
|
+
if (value === undefined) {
|
|
94
124
|
throw new Error(`required attribute '${name}' missing`);
|
|
125
|
+
}
|
|
95
126
|
validateAttribute(value, spec, name);
|
|
96
127
|
}
|
|
97
128
|
}
|
|
@@ -13,11 +13,14 @@ function collectExpressionSymbols(expression, symbols) {
|
|
|
13
13
|
symbols.add(expression.symbol);
|
|
14
14
|
}
|
|
15
15
|
else if (expression.kind !== 'empty') {
|
|
16
|
-
if (expression.kind === 'repeat')
|
|
16
|
+
if (expression.kind === 'repeat') {
|
|
17
17
|
collectExpressionSymbols(expression.expression, symbols);
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
for (const nested of expression.expressions) {
|
|
20
21
|
collectExpressionSymbols(nested, symbols);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
/** Return every referenced node/group symbol, or null for an invalid expression. */
|
|
@@ -41,23 +44,26 @@ class ContentExpressionParser {
|
|
|
41
44
|
this.depth = 0;
|
|
42
45
|
}
|
|
43
46
|
parse() {
|
|
44
|
-
if (this.source.trim() === '')
|
|
47
|
+
if (this.source.trim() === '') {
|
|
45
48
|
return { kind: 'empty' };
|
|
49
|
+
}
|
|
46
50
|
const expression = this.parseAlternation();
|
|
47
51
|
this.skipWhitespace();
|
|
48
|
-
if (!this.atEnd())
|
|
52
|
+
if (!this.atEnd()) {
|
|
49
53
|
throw new Error(`unexpected '${this.peek()}'`);
|
|
54
|
+
}
|
|
50
55
|
return expression;
|
|
51
56
|
}
|
|
52
57
|
parseAlternation() {
|
|
53
58
|
const expressions = [this.parseSequence()];
|
|
54
59
|
while (true) {
|
|
55
60
|
this.skipWhitespace();
|
|
56
|
-
if (!this.consume('|'))
|
|
61
|
+
if (!this.consume('|')) {
|
|
57
62
|
break;
|
|
63
|
+
}
|
|
58
64
|
this.skipWhitespace();
|
|
59
65
|
if (this.atEnd() || this.peek() === ')' || this.peek() === '|') {
|
|
60
|
-
throw new Error(
|
|
66
|
+
throw new Error('missing expression after \'|\'');
|
|
61
67
|
}
|
|
62
68
|
expressions.push(this.parseSequence());
|
|
63
69
|
}
|
|
@@ -67,26 +73,32 @@ class ContentExpressionParser {
|
|
|
67
73
|
const expressions = [];
|
|
68
74
|
while (true) {
|
|
69
75
|
this.skipWhitespace();
|
|
70
|
-
if (this.atEnd() || this.peek() === ')' || this.peek() === '|')
|
|
76
|
+
if (this.atEnd() || this.peek() === ')' || this.peek() === '|') {
|
|
71
77
|
break;
|
|
78
|
+
}
|
|
72
79
|
expressions.push(this.parseRepeated());
|
|
73
80
|
}
|
|
74
|
-
if (expressions.length === 0)
|
|
81
|
+
if (expressions.length === 0) {
|
|
75
82
|
throw new Error('expected expression');
|
|
83
|
+
}
|
|
76
84
|
return expressions.length === 1 ? expressions[0] : { kind: 'sequence', expressions };
|
|
77
85
|
}
|
|
78
86
|
parseRepeated() {
|
|
79
87
|
const atom = this.parseAtom();
|
|
80
88
|
this.skipWhitespace();
|
|
81
89
|
let expression = atom;
|
|
82
|
-
if (this.consume('?'))
|
|
90
|
+
if (this.consume('?')) {
|
|
83
91
|
expression = { kind: 'repeat', expression: atom, min: 0, max: 1 };
|
|
84
|
-
|
|
92
|
+
}
|
|
93
|
+
else if (this.consume('*')) {
|
|
85
94
|
expression = { kind: 'repeat', expression: atom, min: 0, max: null };
|
|
86
|
-
|
|
95
|
+
}
|
|
96
|
+
else if (this.consume('+')) {
|
|
87
97
|
expression = { kind: 'repeat', expression: atom, min: 1, max: null };
|
|
88
|
-
|
|
98
|
+
}
|
|
99
|
+
else if (this.consume('{')) {
|
|
89
100
|
expression = this.parseRange(atom);
|
|
101
|
+
}
|
|
90
102
|
this.skipWhitespace();
|
|
91
103
|
if (['?', '*', '+', '{'].includes(this.peek() ?? '')) {
|
|
92
104
|
throw new Error('multiple quantifiers');
|
|
@@ -96,17 +108,20 @@ class ContentExpressionParser {
|
|
|
96
108
|
parseAtom() {
|
|
97
109
|
this.skipWhitespace();
|
|
98
110
|
if (this.consume('(')) {
|
|
99
|
-
if (this.depth >= exports.CONTENT_EXPRESSION_MAX_DEPTH)
|
|
111
|
+
if (this.depth >= exports.CONTENT_EXPRESSION_MAX_DEPTH) {
|
|
100
112
|
throw new Error('expression nesting too deep');
|
|
113
|
+
}
|
|
101
114
|
this.depth += 1;
|
|
102
115
|
try {
|
|
103
116
|
this.skipWhitespace();
|
|
104
|
-
if (this.peek() === ')')
|
|
117
|
+
if (this.peek() === ')') {
|
|
105
118
|
throw new Error('empty group');
|
|
119
|
+
}
|
|
106
120
|
const expression = this.parseAlternation();
|
|
107
121
|
this.skipWhitespace();
|
|
108
|
-
if (!this.consume(')'))
|
|
109
|
-
throw new Error(
|
|
122
|
+
if (!this.consume(')')) {
|
|
123
|
+
throw new Error('missing \')\'');
|
|
124
|
+
}
|
|
110
125
|
return expression;
|
|
111
126
|
}
|
|
112
127
|
finally {
|
|
@@ -114,55 +129,68 @@ class ContentExpressionParser {
|
|
|
114
129
|
}
|
|
115
130
|
}
|
|
116
131
|
const start = this.position;
|
|
117
|
-
while (this.peek() != null && /[A-Za-z0-9_-]/.test(this.peek()))
|
|
132
|
+
while (this.peek() != null && /[A-Za-z0-9_-]/.test(this.peek())) {
|
|
118
133
|
this.position += 1;
|
|
119
|
-
|
|
134
|
+
}
|
|
135
|
+
if (start === this.position) {
|
|
120
136
|
throw new Error('expected node or group name');
|
|
137
|
+
}
|
|
121
138
|
return { kind: 'symbol', symbol: this.source.slice(start, this.position) };
|
|
122
139
|
}
|
|
123
140
|
parseRange(expression) {
|
|
124
141
|
const min = this.parseNumber();
|
|
125
142
|
let max;
|
|
126
|
-
if (this.consume('}'))
|
|
143
|
+
if (this.consume('}')) {
|
|
127
144
|
max = min;
|
|
145
|
+
}
|
|
128
146
|
else {
|
|
129
|
-
if (!this.consume(','))
|
|
130
|
-
throw new Error(
|
|
131
|
-
|
|
147
|
+
if (!this.consume(',')) {
|
|
148
|
+
throw new Error('expected \',\' or \'}\'');
|
|
149
|
+
}
|
|
150
|
+
if (this.consume('}')) {
|
|
132
151
|
max = null;
|
|
152
|
+
}
|
|
133
153
|
else {
|
|
134
154
|
max = this.parseNumber();
|
|
135
|
-
if (!this.consume('}'))
|
|
136
|
-
throw new Error(
|
|
155
|
+
if (!this.consume('}')) {
|
|
156
|
+
throw new Error('expected \'}\'');
|
|
157
|
+
}
|
|
137
158
|
}
|
|
138
159
|
}
|
|
139
|
-
if (max != null && max < min)
|
|
160
|
+
if (max != null && max < min) {
|
|
140
161
|
throw new Error('range maximum is smaller than minimum');
|
|
141
|
-
|
|
162
|
+
}
|
|
163
|
+
if ((max ?? min) > MAX_REPETITION_BOUND) {
|
|
142
164
|
throw new Error('repetition bound too large');
|
|
165
|
+
}
|
|
143
166
|
return { kind: 'repeat', expression, min, max };
|
|
144
167
|
}
|
|
145
168
|
parseNumber() {
|
|
146
169
|
const start = this.position;
|
|
147
|
-
while (this.peek() != null && /[0-9]/.test(this.peek()))
|
|
170
|
+
while (this.peek() != null && /[0-9]/.test(this.peek())) {
|
|
148
171
|
this.position += 1;
|
|
149
|
-
|
|
172
|
+
}
|
|
173
|
+
if (start === this.position) {
|
|
150
174
|
throw new Error('expected number');
|
|
175
|
+
}
|
|
151
176
|
const value = Number(this.source.slice(start, this.position));
|
|
152
|
-
if (!Number.isSafeInteger(value) || value > 4294967295)
|
|
177
|
+
if (!Number.isSafeInteger(value) || value > 4294967295) {
|
|
153
178
|
throw new Error('invalid count');
|
|
179
|
+
}
|
|
154
180
|
return value;
|
|
155
181
|
}
|
|
156
182
|
skipWhitespace() {
|
|
157
|
-
while (this.peek() != null && /\s/.test(this.peek()))
|
|
183
|
+
while (this.peek() != null && /\s/.test(this.peek())) {
|
|
158
184
|
this.position += 1;
|
|
185
|
+
}
|
|
159
186
|
}
|
|
160
187
|
peek() {
|
|
161
188
|
return this.source[this.position];
|
|
162
189
|
}
|
|
163
190
|
consume(character) {
|
|
164
|
-
if (this.peek() !== character)
|
|
191
|
+
if (this.peek() !== character) {
|
|
165
192
|
return false;
|
|
193
|
+
}
|
|
166
194
|
this.position += 1;
|
|
167
195
|
return true;
|
|
168
196
|
}
|
|
@@ -175,8 +203,9 @@ class ContentExpressionCompiler {
|
|
|
175
203
|
this.states = [];
|
|
176
204
|
}
|
|
177
205
|
compile(expression, depth = 0) {
|
|
178
|
-
if (depth > exports.CONTENT_EXPRESSION_MAX_DEPTH)
|
|
206
|
+
if (depth > exports.CONTENT_EXPRESSION_MAX_DEPTH) {
|
|
179
207
|
throw new Error('expression nesting too deep');
|
|
208
|
+
}
|
|
180
209
|
if (expression.kind === 'empty') {
|
|
181
210
|
const start = this.state();
|
|
182
211
|
const end = this.state();
|
|
@@ -238,8 +267,9 @@ class ContentExpressionCompiler {
|
|
|
238
267
|
return [start, end];
|
|
239
268
|
}
|
|
240
269
|
state() {
|
|
241
|
-
if (this.states.length >= MAX_AUTOMATON_STATES)
|
|
270
|
+
if (this.states.length >= MAX_AUTOMATON_STATES) {
|
|
242
271
|
throw new Error('too many automaton states');
|
|
272
|
+
}
|
|
243
273
|
this.states.push({ epsilon: [], transitions: [] });
|
|
244
274
|
return this.states.length - 1;
|
|
245
275
|
}
|
|
@@ -249,8 +279,9 @@ function epsilonClosure(states, initial) {
|
|
|
249
279
|
const pending = [...initial];
|
|
250
280
|
while (pending.length > 0) {
|
|
251
281
|
const state = pending.pop();
|
|
252
|
-
if (result.has(state))
|
|
282
|
+
if (result.has(state)) {
|
|
253
283
|
continue;
|
|
284
|
+
}
|
|
254
285
|
result.add(state);
|
|
255
286
|
pending.push(...states[state].epsilon);
|
|
256
287
|
}
|
|
@@ -266,8 +297,9 @@ function acceptingContentSymbols(content, existingChildTypes = [], symbolMatches
|
|
|
266
297
|
const next = new Set();
|
|
267
298
|
for (const state of current) {
|
|
268
299
|
for (const transition of compiler.states[state].transitions) {
|
|
269
|
-
if (symbolMatches(childType, transition.symbol))
|
|
300
|
+
if (symbolMatches(childType, transition.symbol)) {
|
|
270
301
|
next.add(transition.target);
|
|
302
|
+
}
|
|
271
303
|
}
|
|
272
304
|
}
|
|
273
305
|
current = epsilonClosure(compiler.states, next);
|
|
@@ -295,30 +327,36 @@ function minimalContentMatch(content, choose, consumeWork = () => true) {
|
|
|
295
327
|
const closure = new Map();
|
|
296
328
|
const pending = [...current.entries()];
|
|
297
329
|
while (pending.length > 0) {
|
|
298
|
-
if (!consumeWork())
|
|
330
|
+
if (!consumeWork()) {
|
|
299
331
|
return undefined;
|
|
332
|
+
}
|
|
300
333
|
const [state, path] = pending.pop();
|
|
301
|
-
if (closure.has(state))
|
|
334
|
+
if (closure.has(state)) {
|
|
302
335
|
continue;
|
|
336
|
+
}
|
|
303
337
|
closure.set(state, path);
|
|
304
|
-
for (const target of compiler.states[state].epsilon)
|
|
338
|
+
for (const target of compiler.states[state].epsilon) {
|
|
305
339
|
pending.push([target, path]);
|
|
340
|
+
}
|
|
306
341
|
}
|
|
307
342
|
const accepted = closure.get(accept);
|
|
308
343
|
if (closure.has(accept)) {
|
|
309
344
|
const result = [];
|
|
310
|
-
for (let path = accepted; path; path = path.previous)
|
|
345
|
+
for (let path = accepted; path; path = path.previous) {
|
|
311
346
|
result.push(path.value);
|
|
347
|
+
}
|
|
312
348
|
return result.reverse();
|
|
313
349
|
}
|
|
314
350
|
const next = new Map();
|
|
315
351
|
for (const [state, path] of closure) {
|
|
316
|
-
if (visited.has(state))
|
|
352
|
+
if (visited.has(state)) {
|
|
317
353
|
continue;
|
|
354
|
+
}
|
|
318
355
|
visited.add(state);
|
|
319
356
|
for (const transition of compiler.states[state].transitions) {
|
|
320
|
-
if (!consumeWork())
|
|
357
|
+
if (!consumeWork()) {
|
|
321
358
|
return undefined;
|
|
359
|
+
}
|
|
322
360
|
const value = choose(transition.symbol);
|
|
323
361
|
if (value !== undefined && !next.has(transition.target)) {
|
|
324
362
|
next.set(transition.target, { value, previous: path });
|
package/dist/listMarkers.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* CROSS-LAYER CONTRACT: these values must match the Rust core
|
|
5
5
|
* (rust/editor-core/src/render/mod.rs: task_list_marker_string /
|
|
6
|
-
* list_marker_string
|
|
6
|
+
* list_marker_string : pinned by marker_strings_are_the_cross_layer_contract
|
|
7
7
|
* in render_test.rs), Android (LayoutConstants.TASK_LIST_MARKER_* /
|
|
8
8
|
* UNORDERED_LIST_BULLET), and iOS (RenderBridge.listMarkerString). The
|
|
9
9
|
* marker's scalar length feeds position mapping; a mismatch corrupts
|
package/dist/listMarkers.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.orderedListMarker = orderedListMarker;
|
|
|
7
7
|
*
|
|
8
8
|
* CROSS-LAYER CONTRACT: these values must match the Rust core
|
|
9
9
|
* (rust/editor-core/src/render/mod.rs: task_list_marker_string /
|
|
10
|
-
* list_marker_string
|
|
10
|
+
* list_marker_string : pinned by marker_strings_are_the_cross_layer_contract
|
|
11
11
|
* in render_test.rs), Android (LayoutConstants.TASK_LIST_MARKER_* /
|
|
12
12
|
* UNORDERED_LIST_BULLET), and iOS (RenderBridge.listMarkerString). The
|
|
13
13
|
* marker's scalar length feeds position mapping; a mismatch corrupts
|
|
@@ -63,7 +63,7 @@ export interface NodeSpec {
|
|
|
63
63
|
* `insert_content_json`) admits attrs on this node that are not declared
|
|
64
64
|
* in `attrs`, instead of filtering them out. Default `false`. Intended
|
|
65
65
|
* for node types with an intentional pass-through-metadata contract
|
|
66
|
-
* (e.g. the mention node
|
|
66
|
+
* (e.g. the mention node : see `mentionNodeSpec()` in addons.ts).
|
|
67
67
|
*/
|
|
68
68
|
allowUndeclaredAttrs?: boolean;
|
|
69
69
|
/** Public JSON representation when it differs from the native node name. */
|
package/dist/schemaDefinition.js
CHANGED
|
@@ -27,35 +27,42 @@ function outputTag(spec) {
|
|
|
27
27
|
}
|
|
28
28
|
function appendGroup(group, name) {
|
|
29
29
|
const groups = group?.split(/\s+/).filter(Boolean) ?? [];
|
|
30
|
-
if (!groups.includes(name))
|
|
30
|
+
if (!groups.includes(name)) {
|
|
31
31
|
groups.push(name);
|
|
32
|
+
}
|
|
32
33
|
return groups.join(' ');
|
|
33
34
|
}
|
|
34
35
|
function schemaNodeRole(name, node) {
|
|
35
|
-
if (node.role != null)
|
|
36
|
+
if (node.role != null) {
|
|
36
37
|
return node.role === 'heading' ? 'textBlock' : node.role;
|
|
37
|
-
|
|
38
|
+
}
|
|
39
|
+
if (name === 'doc') {
|
|
38
40
|
return 'doc';
|
|
39
|
-
|
|
41
|
+
}
|
|
42
|
+
if (name === 'text') {
|
|
40
43
|
return 'text';
|
|
44
|
+
}
|
|
41
45
|
if (node.content === 'inline*' && node.group?.split(/\s+/).includes('block')) {
|
|
42
46
|
return 'textBlock';
|
|
43
47
|
}
|
|
44
|
-
if (node.group?.split(/\s+/).includes('inline'))
|
|
48
|
+
if (node.group?.split(/\s+/).includes('inline')) {
|
|
45
49
|
return 'inline';
|
|
50
|
+
}
|
|
46
51
|
return 'block';
|
|
47
52
|
}
|
|
48
53
|
function caseAttributeValue(key, attribute, tag, parseDOM, defaultValue) {
|
|
49
|
-
const rule = parseDOM?.find(
|
|
54
|
+
const rule = parseDOM?.find(candidate => candidate.tag === tag &&
|
|
50
55
|
candidate.attrs != null &&
|
|
51
56
|
String(candidate.attrs[attribute]) === key);
|
|
52
57
|
if (rule?.attrs && Object.prototype.hasOwnProperty.call(rule.attrs, attribute)) {
|
|
53
58
|
return rule.attrs[attribute];
|
|
54
59
|
}
|
|
55
|
-
if (typeof defaultValue === 'number')
|
|
60
|
+
if (typeof defaultValue === 'number') {
|
|
56
61
|
return Number(key);
|
|
57
|
-
|
|
62
|
+
}
|
|
63
|
+
if (typeof defaultValue === 'boolean') {
|
|
58
64
|
return key === 'true';
|
|
65
|
+
}
|
|
59
66
|
return key;
|
|
60
67
|
}
|
|
61
68
|
function validateAttributeDOMRules(name, switched, parseDOM, defaultValue) {
|
|
@@ -66,7 +73,7 @@ function validateAttributeDOMRules(name, switched, parseDOM, defaultValue) {
|
|
|
66
73
|
? typeof defaultValue
|
|
67
74
|
: undefined;
|
|
68
75
|
if (discriminatorType == null && parseDOM != null) {
|
|
69
|
-
const parsedTypes = parseDOM.map(
|
|
76
|
+
const parsedTypes = parseDOM.map(rule => {
|
|
70
77
|
const value = rule.attrs?.[switched.switchOn];
|
|
71
78
|
return typeof value === 'string' ||
|
|
72
79
|
typeof value === 'number' ||
|
|
@@ -75,7 +82,7 @@ function validateAttributeDOMRules(name, switched, parseDOM, defaultValue) {
|
|
|
75
82
|
: undefined;
|
|
76
83
|
});
|
|
77
84
|
const firstType = parsedTypes[0];
|
|
78
|
-
if (firstType != null && parsedTypes.every(
|
|
85
|
+
if (firstType != null && parsedTypes.every(type => type === firstType)) {
|
|
79
86
|
discriminatorType = firstType;
|
|
80
87
|
}
|
|
81
88
|
}
|
|
@@ -94,7 +101,7 @@ function validateAttributeDOMRules(name, switched, parseDOM, defaultValue) {
|
|
|
94
101
|
}
|
|
95
102
|
return;
|
|
96
103
|
}
|
|
97
|
-
const matchesCase = ([caseKey, output]) => parseDOM.filter(
|
|
104
|
+
const matchesCase = ([caseKey, output]) => parseDOM.filter(rule => rule.tag === outputTag(output) &&
|
|
98
105
|
rule.attrs != null &&
|
|
99
106
|
Object.prototype.hasOwnProperty.call(rule.attrs, switched.switchOn) &&
|
|
100
107
|
String(rule.attrs[switched.switchOn]) === caseKey).length === 1;
|
|
@@ -102,7 +109,7 @@ function validateAttributeDOMRules(name, switched, parseDOM, defaultValue) {
|
|
|
102
109
|
throw new Error(`node '${name}' DOM parse rules must map one-to-one with '${switched.switchOn}' output cases`);
|
|
103
110
|
}
|
|
104
111
|
if (discriminatorType != null &&
|
|
105
|
-
parseDOM.some(
|
|
112
|
+
parseDOM.some(rule => rule.attrs != null && typeof rule.attrs[switched.switchOn] !== discriminatorType)) {
|
|
106
113
|
throw new Error(`node '${name}' DOM discriminator '${switched.switchOn}' must use ${discriminatorType} values`);
|
|
107
114
|
}
|
|
108
115
|
}
|
|
@@ -153,8 +160,9 @@ function defineSchema(spec) {
|
|
|
153
160
|
const { attrs: _attrs, ...variantCommon } = common;
|
|
154
161
|
for (const [caseKey, output] of Object.entries(switched.cases)) {
|
|
155
162
|
const tag = outputTag(output);
|
|
156
|
-
if (names.has(tag))
|
|
163
|
+
if (names.has(tag)) {
|
|
157
164
|
throw new Error(`schema produces duplicate native node '${tag}'`);
|
|
165
|
+
}
|
|
158
166
|
names.add(tag);
|
|
159
167
|
const { [switched.switchOn]: _discriminator, ...variantAttrs } = node.attrs ?? {};
|
|
160
168
|
nodes.push({
|
|
@@ -173,8 +181,9 @@ function defineSchema(spec) {
|
|
|
173
181
|
}
|
|
174
182
|
continue;
|
|
175
183
|
}
|
|
176
|
-
if (names.has(name))
|
|
184
|
+
if (names.has(name)) {
|
|
177
185
|
throw new Error(`schema produces duplicate native node '${name}'`);
|
|
186
|
+
}
|
|
178
187
|
names.add(name);
|
|
179
188
|
const tag = staticDOMTag('node', name, node.parseDOM, node.toDOM);
|
|
180
189
|
nodes.push({
|