@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.
Files changed (161) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/android/build.gradle +3 -0
  4. package/app.plugin.js +42 -39
  5. package/dist/AtomHost.js +18 -11
  6. package/dist/EditorAddon.js +4 -2
  7. package/dist/EditorAddonNormalization.js +17 -9
  8. package/dist/EditorMentionThemeNormalization.js +6 -3
  9. package/dist/EditorMentionThemeValidation.js +28 -11
  10. package/dist/EditorStyleSheetNormalization.js +84 -37
  11. package/dist/EditorTheme.d.ts +4 -0
  12. package/dist/EditorTheme.js +5 -3
  13. package/dist/EditorToolbarComponent.d.ts +1 -1
  14. package/dist/EditorToolbarComponent.js +1 -1
  15. package/dist/EditorToolbarRegistry.js +5 -5
  16. package/dist/EditorToolbarTypes.d.ts +12 -12
  17. package/dist/EditorToolbarVisuals.js +1 -1
  18. package/dist/ExternalTextComposition.js +89 -43
  19. package/dist/ImageLoadingPolicy.d.ts +1 -1
  20. package/dist/NativeEditorBoundaryError.d.ts +7 -7
  21. package/dist/NativeEditorBoundaryError.js +9 -5
  22. package/dist/NativeEditorCollaborationTransport.d.ts +7 -7
  23. package/dist/NativeEditorCollaborationTransport.js +28 -15
  24. package/dist/NativeEditorCreateConfig.js +11 -6
  25. package/dist/NativeEditorCreateJson.js +51 -25
  26. package/dist/NativeEditorCreateValidation.js +11 -6
  27. package/dist/NativeEditorDocumentBridge.d.ts +1 -1
  28. package/dist/NativeEditorDocumentBridge.js +32 -18
  29. package/dist/NativeEditorDocumentHandle.d.ts +4 -4
  30. package/dist/NativeEditorDocumentHandle.js +1 -1
  31. package/dist/NativeEditorLocalAwareness.js +25 -13
  32. package/dist/NativeEditorNativeModule.d.ts +8 -1
  33. package/dist/NativeEditorNativeModule.js +7 -5
  34. package/dist/NativeEditorRenderNormalization.js +66 -25
  35. package/dist/NativeEditorResultNormalization.js +53 -29
  36. package/dist/NativeEditorTypes.d.ts +10 -10
  37. package/dist/NativeEditorV2Decimal.js +6 -3
  38. package/dist/NativeProseViewer.js +11 -3
  39. package/dist/ResourceLimits.d.ts +2 -2
  40. package/dist/ResourceLimits.js +7 -2
  41. package/dist/RichTextEditor.js +1 -1
  42. package/dist/RichTextEditorNativeTypes.d.ts +1 -1
  43. package/dist/RichTextEditorNativeView.d.ts +1 -3
  44. package/dist/RichTextEditorSerialization.js +24 -14
  45. package/dist/RichTextEditorTypes.d.ts +1 -1
  46. package/dist/YjsCollaboration.d.ts +7 -7
  47. package/dist/YjsCollaboration.js +42 -26
  48. package/dist/addons.d.ts +4 -4
  49. package/dist/addons.js +3 -3
  50. package/dist/atomInstances.js +6 -3
  51. package/dist/atomUpdates.js +7 -4
  52. package/dist/atoms.js +20 -12
  53. package/dist/attributeValidation.js +55 -24
  54. package/dist/contentExpression.js +80 -42
  55. package/dist/listMarkers.d.ts +1 -1
  56. package/dist/listMarkers.js +1 -1
  57. package/dist/schemaDefinition.d.ts +1 -1
  58. package/dist/schemaDefinition.js +23 -14
  59. package/dist/schemaDocument.js +1 -1
  60. package/dist/schemaDocumentConstruction.js +25 -14
  61. package/dist/schemaNormalization.js +51 -29
  62. package/dist/schemaPresets.d.ts +1 -1
  63. package/dist/schemaPresets.js +9 -4
  64. package/dist/schemaResolution.js +46 -25
  65. package/dist/specs/PreparedProseViewerNativeComponent.d.ts +4 -0
  66. package/dist/useEditorToolbarInteractions.d.ts +2 -2
  67. package/dist/useEditorToolbarInteractions.js +45 -23
  68. package/dist/useEditorToolbarItems.d.ts +1 -1
  69. package/dist/useEditorToolbarItems.js +4 -3
  70. package/dist/useEditorToolbarPresentation.d.ts +3 -3
  71. package/dist/useEditorToolbarPresentation.js +4 -4
  72. package/dist/useEditorToolbarState.d.ts +1 -1
  73. package/dist/useEditorToolbarState.js +1 -1
  74. package/dist/useFocusPreservingFrames.js +11 -8
  75. package/dist/useNativeEditor.d.ts +1 -1
  76. package/dist/useNativeEditor.js +21 -11
  77. package/dist/useRichTextEditorCommands.d.ts +2 -2
  78. package/dist/useRichTextEditorCommands.js +59 -29
  79. package/dist/useRichTextEditorEvents.d.ts +3 -3
  80. package/dist/useRichTextEditorEvents.js +75 -32
  81. package/dist/useRichTextEditorMentions.d.ts +4 -4
  82. package/dist/useRichTextEditorMentions.js +48 -29
  83. package/dist/useRichTextEditorPresentation.d.ts +4 -4
  84. package/dist/useRichTextEditorPresentation.js +25 -23
  85. package/dist/useRichTextEditorState.d.ts +3 -3
  86. package/dist/useRichTextEditorState.js +20 -15
  87. package/dist/useRichTextEditorUpdates.d.ts +1 -1
  88. package/dist/useRichTextEditorUpdates.js +99 -41
  89. package/dist/useViewerAtoms.js +63 -36
  90. package/ios/EditorCore.xcframework/Info.plist +5 -5
  91. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  92. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  93. package/package.json +11 -3
  94. package/react-native.config.js +1 -1
  95. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  96. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  97. package/rust/android/x86/libeditor_core.so +0 -0
  98. package/rust/android/x86_64/libeditor_core.so +0 -0
  99. package/src/AtomHost.tsx +58 -28
  100. package/src/EditorAddon.ts +18 -8
  101. package/src/EditorAddonNormalization.ts +40 -11
  102. package/src/EditorMentionThemeNormalization.ts +15 -4
  103. package/src/EditorMentionThemeValidation.ts +51 -14
  104. package/src/EditorStyleSheet.ts +1 -0
  105. package/src/EditorStyleSheetNormalization.ts +195 -64
  106. package/src/EditorTheme.ts +13 -5
  107. package/src/EditorToolbarComponent.tsx +2 -1
  108. package/src/EditorToolbarRegistry.tsx +23 -13
  109. package/src/EditorToolbarTypes.tsx +12 -12
  110. package/src/EditorToolbarVisuals.tsx +6 -3
  111. package/src/EditorUpdateRevision.ts +1 -0
  112. package/src/ExternalTextComposition.ts +259 -49
  113. package/src/ImageLoadingPolicy.ts +3 -1
  114. package/src/NativeEditorBoundaryError.ts +38 -11
  115. package/src/NativeEditorCollaborationTransport.ts +108 -27
  116. package/src/NativeEditorCreateConfig.ts +92 -19
  117. package/src/NativeEditorCreateJson.ts +163 -26
  118. package/src/NativeEditorCreateValidation.ts +33 -12
  119. package/src/NativeEditorDocumentBridge.ts +111 -19
  120. package/src/NativeEditorDocumentHandle.ts +9 -4
  121. package/src/NativeEditorLocalAwareness.ts +70 -15
  122. package/src/NativeEditorNativeModule.ts +13 -9
  123. package/src/NativeEditorRenderNormalization.ts +128 -29
  124. package/src/NativeEditorResultNormalization.ts +143 -32
  125. package/src/NativeEditorTypes.ts +12 -11
  126. package/src/NativeEditorV2Decimal.ts +12 -3
  127. package/src/NativeProseViewer.tsx +46 -16
  128. package/src/ResourceLimits.ts +15 -8
  129. package/src/RichTextEditor.tsx +3 -1
  130. package/src/RichTextEditorNativeTypes.tsx +1 -1
  131. package/src/RichTextEditorNativeView.tsx +2 -8
  132. package/src/RichTextEditorSerialization.tsx +70 -24
  133. package/src/RichTextEditorTypes.tsx +1 -1
  134. package/src/YjsCollaboration.ts +143 -47
  135. package/src/addons.ts +12 -8
  136. package/src/atomInstances.ts +25 -5
  137. package/src/atomUpdates.ts +23 -6
  138. package/src/atoms.ts +85 -18
  139. package/src/attributeValidation.ts +97 -33
  140. package/src/contentExpression.ts +218 -63
  141. package/src/listMarkers.ts +1 -1
  142. package/src/schemaDefinition.ts +82 -20
  143. package/src/schemaDocument.ts +15 -1
  144. package/src/schemaDocumentConstruction.ts +80 -16
  145. package/src/schemaNormalization.ts +155 -41
  146. package/src/schemaPresets.ts +37 -31
  147. package/src/schemaResolution.ts +126 -27
  148. package/src/specs/PreparedProseViewerNativeComponent.ts +4 -2
  149. package/src/useEditorToolbarInteractions.tsx +85 -36
  150. package/src/useEditorToolbarItems.tsx +50 -22
  151. package/src/useEditorToolbarPresentation.tsx +81 -58
  152. package/src/useEditorToolbarState.tsx +8 -6
  153. package/src/useFocusPreservingFrames.ts +43 -15
  154. package/src/useNativeEditor.ts +111 -51
  155. package/src/useRichTextEditorCommands.tsx +127 -54
  156. package/src/useRichTextEditorEvents.tsx +144 -42
  157. package/src/useRichTextEditorMentions.tsx +118 -37
  158. package/src/useRichTextEditorPresentation.tsx +128 -114
  159. package/src/useRichTextEditorState.tsx +76 -40
  160. package/src/useRichTextEditorUpdates.tsx +207 -56
  161. 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
- if (value == null || typeof value !== 'object' || !('$$typeof' in value))
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((key) => !['tag', 'staticAttrs', 'attrMap'].includes(key))) {
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((name) => [name, `data-${kebabCase(name)}`]))
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
- if (config.idAttribute !== undefined && config.attrs?.[config.idAttribute]?.type !== 'string')
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((node) => node.html != null);
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((node) => node.name === atom.name);
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
- if (typeof value === 'number' && Number.isFinite(value))
12
+ }
13
+ if (typeof value === 'number' && Number.isFinite(value)) {
13
14
  return;
14
- if (typeof value !== 'object' || ancestors.has(value) || ancestors.size > 128)
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
- if (left == null || right == null || typeof left !== 'object' || typeof right !== 'object')
34
+ }
35
+ if (left == null || right == null || typeof left !== 'object' || typeof right !== 'object') {
30
36
  return false;
31
- if (Array.isArray(left) !== Array.isArray(right))
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((key) => Object.prototype.hasOwnProperty.call(right, key) &&
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
- if (spec.enum != null && !spec.enum.some((entry) => equal(entry, value)))
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
- if (Object.keys(spec).some((key) => !['default', 'type', 'enum', 'min', 'max'].includes(key)))
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', 'number', 'boolean', 'object', 'array'].includes(spec.type))
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((bound) => bound !== undefined && (!Number.isInteger(bound) || bound < 0)))
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
- if (new Set(spec.enum.map(jsonType)).size !== 1)
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
- for (const entry of spec.enum)
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
- else
19
- for (const nested of expression.expressions)
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("missing expression after '|'");
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
- else if (this.consume('*'))
92
+ }
93
+ else if (this.consume('*')) {
85
94
  expression = { kind: 'repeat', expression: atom, min: 0, max: null };
86
- else if (this.consume('+'))
95
+ }
96
+ else if (this.consume('+')) {
87
97
  expression = { kind: 'repeat', expression: atom, min: 1, max: null };
88
- else if (this.consume('{'))
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("missing ')'");
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
- if (start === this.position)
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("expected ',' or '}'");
131
- if (this.consume('}'))
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("expected '}'");
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
- if ((max ?? min) > MAX_REPETITION_BOUND)
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
- if (start === this.position)
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 });
@@ -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 pinned by marker_strings_are_the_cross_layer_contract
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
@@ -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 pinned by marker_strings_are_the_cross_layer_contract
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 see `mentionNodeSpec()` in addons.ts).
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. */
@@ -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
- if (name === 'doc')
38
+ }
39
+ if (name === 'doc') {
38
40
  return 'doc';
39
- if (name === 'text')
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((candidate) => candidate.tag === tag &&
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
- if (typeof defaultValue === 'boolean')
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((rule) => {
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((type) => type === firstType)) {
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((rule) => rule.tag === outputTag(output) &&
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((rule) => rule.attrs != null && typeof rule.attrs[switched.switchOn] !== discriminatorType)) {
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({