@apollohg/react-native-prose-editor 0.3.0 → 0.4.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 (37) hide show
  1. package/README.md +18 -0
  2. package/android/build.gradle +23 -0
  3. package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +515 -39
  4. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +58 -28
  5. package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +25 -0
  6. package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +232 -62
  7. package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +57 -27
  8. package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +147 -78
  9. package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +249 -71
  10. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +7 -6
  11. package/dist/EditorToolbar.d.ts +26 -6
  12. package/dist/EditorToolbar.js +299 -65
  13. package/dist/NativeEditorBridge.d.ts +40 -1
  14. package/dist/NativeEditorBridge.js +184 -90
  15. package/dist/NativeRichTextEditor.d.ts +5 -1
  16. package/dist/NativeRichTextEditor.js +201 -78
  17. package/dist/YjsCollaboration.d.ts +2 -0
  18. package/dist/YjsCollaboration.js +142 -20
  19. package/dist/index.d.ts +1 -1
  20. package/dist/schemas.js +12 -0
  21. package/dist/useNativeEditor.d.ts +2 -0
  22. package/dist/useNativeEditor.js +7 -0
  23. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  24. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  25. package/ios/EditorLayoutManager.swift +3 -3
  26. package/ios/Generated_editor_core.swift +87 -0
  27. package/ios/NativeEditorExpoView.swift +488 -178
  28. package/ios/NativeEditorModule.swift +25 -0
  29. package/ios/PositionBridge.swift +310 -75
  30. package/ios/RenderBridge.swift +362 -27
  31. package/ios/RichTextEditorView.swift +2001 -189
  32. package/ios/editor_coreFFI/editor_coreFFI.h +55 -0
  33. package/package.json +11 -2
  34. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  35. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  36. package/rust/android/x86_64/libeditor_core.so +0 -0
  37. package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +128 -0
@@ -17,11 +17,119 @@ const EMPTY_DOCUMENT = {
17
17
  ],
18
18
  };
19
19
  const SELECTION_AWARENESS_DEBOUNCE_MS = 40;
20
- function cloneDocument(doc) {
21
- return JSON.parse(JSON.stringify(doc ?? EMPTY_DOCUMENT));
20
+ function cloneJsonValue(value) {
21
+ if (Array.isArray(value)) {
22
+ return value.map((item) => cloneJsonValue(item));
23
+ }
24
+ if (value != null && typeof value === 'object') {
25
+ const clone = {};
26
+ for (const [key, nestedValue] of Object.entries(value)) {
27
+ clone[key] = cloneJsonValue(nestedValue);
28
+ }
29
+ return clone;
30
+ }
31
+ return value;
32
+ }
33
+ function acceptingGroupsForContent(content, existingChildCount) {
34
+ const tokens = content
35
+ .trim()
36
+ .split(/\s+/)
37
+ .filter(Boolean)
38
+ .map((token) => {
39
+ const quantifier = token[token.length - 1];
40
+ if (quantifier === '+' || quantifier === '*' || quantifier === '?') {
41
+ return {
42
+ group: token.slice(0, -1),
43
+ min: quantifier === '+' ? 1 : 0,
44
+ max: quantifier === '?' ? 1 : null,
45
+ };
46
+ }
47
+ return {
48
+ group: token,
49
+ min: 1,
50
+ max: 1,
51
+ };
52
+ });
53
+ let remaining = existingChildCount;
54
+ const acceptingGroups = [];
55
+ for (const token of tokens) {
56
+ if (remaining >= token.min) {
57
+ const consumed = token.max == null ? remaining : Math.min(remaining, token.max);
58
+ remaining = Math.max(0, remaining - consumed);
59
+ const atMax = token.max != null && consumed >= token.max;
60
+ if (!atMax) {
61
+ acceptingGroups.push(token.group);
62
+ }
63
+ continue;
64
+ }
65
+ acceptingGroups.push(token.group);
66
+ break;
67
+ }
68
+ return acceptingGroups;
69
+ }
70
+ function defaultEmptyDocument(schema) {
71
+ if (!schema) {
72
+ return {
73
+ type: 'doc',
74
+ content: [{ type: 'paragraph' }],
75
+ };
76
+ }
77
+ const docNode = schema.nodes.find((node) => node.role === 'doc' || node.name === 'doc');
78
+ const acceptingGroups = docNode == null ? [] : acceptingGroupsForContent(docNode.content ?? '', 0);
79
+ const matchingTextBlocks = schema.nodes.filter((node) => node.role === 'textBlock' &&
80
+ acceptingGroups.some((group) => node.name === group || node.group === group));
81
+ const preferredTextBlock = matchingTextBlocks.find((node) => node.htmlTag === 'p' || node.name === 'paragraph') ??
82
+ matchingTextBlocks[0] ??
83
+ schema.nodes.find((node) => node.htmlTag === 'p' || node.name === 'paragraph') ??
84
+ schema.nodes.find((node) => node.role === 'textBlock');
85
+ if (!preferredTextBlock) {
86
+ return {
87
+ type: 'doc',
88
+ content: [{ type: 'paragraph' }],
89
+ };
90
+ }
91
+ return {
92
+ type: 'doc',
93
+ content: [{ type: preferredTextBlock.name }],
94
+ };
95
+ }
96
+ function initialFallbackDocument(options) {
97
+ return options.initialDocumentJson
98
+ ? cloneJsonValue(options.initialDocumentJson)
99
+ : defaultEmptyDocument(options.schema);
100
+ }
101
+ function shouldUseFallbackForNativeDocument(doc, options) {
102
+ if (options.initialDocumentJson != null || options.initialEncodedState != null) {
103
+ return false;
104
+ }
105
+ if (doc.type !== 'doc') {
106
+ return false;
107
+ }
108
+ return !Array.isArray(doc.content) || doc.content.length === 0;
22
109
  }
23
110
  function awarenessToRecord(awareness) {
24
- return JSON.parse(JSON.stringify(awareness));
111
+ return awareness;
112
+ }
113
+ function localAwarenessEquals(left, right) {
114
+ const leftSelection = left.selection;
115
+ const rightSelection = right.selection;
116
+ if (leftSelection == null || rightSelection == null) {
117
+ if (leftSelection !== rightSelection) {
118
+ return false;
119
+ }
120
+ }
121
+ else if (leftSelection.anchor !== rightSelection.anchor ||
122
+ leftSelection.head !== rightSelection.head) {
123
+ return false;
124
+ }
125
+ const leftUser = left.user;
126
+ const rightUser = right.user;
127
+ return (left.focused === right.focused &&
128
+ leftUser.userId === rightUser.userId &&
129
+ leftUser.name === rightUser.name &&
130
+ leftUser.color === rightUser.color &&
131
+ leftUser.avatarUrl === rightUser.avatarUrl &&
132
+ JSON.stringify(leftUser.extra ?? null) === JSON.stringify(rightUser.extra ?? null));
25
133
  }
26
134
  function normalizeMessageBytes(data) {
27
135
  if (data instanceof ArrayBuffer) {
@@ -109,11 +217,6 @@ function encodeInitialStateKey(encodedState) {
109
217
  return '';
110
218
  return (0, NativeEditorBridge_1.encodeCollaborationStateBase64)(encodedState);
111
219
  }
112
- function encodeDocumentKey(doc) {
113
- if (doc == null)
114
- return '';
115
- return JSON.stringify(doc);
116
- }
117
220
  class YjsCollaborationControllerImpl {
118
221
  constructor(options, callbacks = {}) {
119
222
  this.socket = null;
@@ -126,23 +229,26 @@ class YjsCollaborationControllerImpl {
126
229
  this.callbacks = callbacks;
127
230
  this.createWebSocket = options.createWebSocket;
128
231
  this.retryIntervalMs = options.retryIntervalMs;
129
- const hasInitialEncodedState = options.initialEncodedState != null;
130
232
  this.localAwarenessState = {
131
233
  user: options.localAwareness,
132
234
  focused: false,
133
235
  };
134
236
  this.bridge = NativeEditorBridge_1.NativeCollaborationBridge.create({
135
237
  fragmentName: options.fragmentName ?? DEFAULT_YJS_FRAGMENT_NAME,
238
+ schema: options.schema,
136
239
  initialEncodedState: options.initialEncodedState,
137
240
  localAwareness: awarenessToRecord(this.localAwarenessState),
138
241
  });
242
+ const nativeDocumentJson = this.bridge.getDocumentJson();
139
243
  this._state = {
140
244
  documentId: options.documentId,
141
245
  status: 'idle',
142
246
  isConnected: false,
143
- documentJson: hasInitialEncodedState
144
- ? this.bridge.getDocumentJson()
145
- : cloneDocument(options.initialDocumentJson),
247
+ documentJson: options.initialDocumentJson != null
248
+ ? cloneJsonValue(options.initialDocumentJson)
249
+ : shouldUseFallbackForNativeDocument(nativeDocumentJson, options)
250
+ ? defaultEmptyDocument(options.schema)
251
+ : nativeDocumentJson,
146
252
  };
147
253
  this._peers = this.bridge.getPeers();
148
254
  if (options.connect !== false) {
@@ -314,10 +420,10 @@ class YjsCollaborationControllerImpl {
314
420
  destroy() {
315
421
  if (this.destroyed)
316
422
  return;
317
- this.destroyed = true;
318
423
  this.cancelRetry();
319
424
  this.cancelPendingAwarenessSync();
320
425
  this.disconnect();
426
+ this.destroyed = true;
321
427
  this.bridge.destroy();
322
428
  }
323
429
  updateLocalAwareness(partial) {
@@ -336,22 +442,33 @@ class YjsCollaborationControllerImpl {
336
442
  handleSelectionChange(selection) {
337
443
  if (this.destroyed)
338
444
  return;
339
- this.localAwarenessState = this.mergeLocalAwareness({
445
+ const nextAwareness = this.mergeLocalAwareness({
340
446
  focused: true,
341
447
  selection: selectionToAwarenessRange(selection),
342
448
  });
449
+ if (localAwarenessEquals(nextAwareness, this.localAwarenessState)) {
450
+ return;
451
+ }
452
+ this.localAwarenessState = nextAwareness;
343
453
  this.scheduleAwarenessSync();
344
454
  }
345
455
  handleFocusChange(focused) {
346
456
  if (this.destroyed)
347
457
  return;
348
- this.localAwarenessState = this.mergeLocalAwareness({ focused });
458
+ const nextAwareness = this.mergeLocalAwareness({ focused });
459
+ if (localAwarenessEquals(nextAwareness, this.localAwarenessState)) {
460
+ if (this.pendingAwarenessTimer != null) {
461
+ this.commitLocalAwareness();
462
+ }
463
+ return;
464
+ }
465
+ this.localAwarenessState = nextAwareness;
349
466
  this.commitLocalAwareness();
350
467
  }
351
468
  applyResult(result) {
352
469
  if (result.documentChanged && result.documentJson) {
353
470
  this.setState({
354
- documentJson: cloneDocument(result.documentJson),
471
+ documentJson: result.documentJson,
355
472
  });
356
473
  }
357
474
  if (result.peersChanged && result.peers) {
@@ -489,14 +606,14 @@ function useYjsCollaboration(options) {
489
606
  const createWebSocketRef = (0, react_1.useRef)(options.createWebSocket);
490
607
  createWebSocketRef.current = options.createWebSocket;
491
608
  const controllerRef = (0, react_1.useRef)(null);
492
- const initialDocumentKey = encodeDocumentKey(options.initialDocumentJson);
493
609
  const initialEncodedStateKey = encodeInitialStateKey(options.initialEncodedState);
494
610
  const localAwarenessKey = JSON.stringify(options.localAwareness);
611
+ const schemaKey = JSON.stringify(options.schema ?? null);
495
612
  const [state, setState] = (0, react_1.useState)({
496
613
  documentId: options.documentId,
497
614
  status: 'idle',
498
615
  isConnected: false,
499
- documentJson: cloneDocument(options.initialDocumentJson),
616
+ documentJson: initialFallbackDocument(options),
500
617
  });
501
618
  const [peers, setPeers] = (0, react_1.useState)([]);
502
619
  (0, react_1.useEffect)(() => {
@@ -529,7 +646,7 @@ function useYjsCollaboration(options) {
529
646
  documentId: options.documentId,
530
647
  status: 'error',
531
648
  isConnected: false,
532
- documentJson: cloneDocument(options.initialDocumentJson),
649
+ documentJson: initialFallbackDocument(options),
533
650
  lastError: nextError,
534
651
  };
535
652
  controllerRef.current = null;
@@ -543,7 +660,12 @@ function useYjsCollaboration(options) {
543
660
  controllerRef.current = null;
544
661
  };
545
662
  // eslint-disable-next-line react-hooks/exhaustive-deps
546
- }, [options.documentId, options.fragmentName, initialDocumentKey, initialEncodedStateKey]);
663
+ }, [
664
+ options.documentId,
665
+ options.fragmentName,
666
+ schemaKey,
667
+ initialEncodedStateKey,
668
+ ]);
547
669
  (0, react_1.useEffect)(() => {
548
670
  controllerRef.current?.updateLocalAwareness({
549
671
  user: options.localAwareness,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorHeightBehavior, type NativeRichTextEditorToolbarPlacement, type RemoteSelectionDecoration, type LinkRequestContext, type ImageRequestContext, } from './NativeRichTextEditor';
2
- export { EditorToolbar, DEFAULT_EDITOR_TOOLBAR_ITEMS, type EditorToolbarProps, type EditorToolbarItem, type EditorToolbarIcon, type EditorToolbarDefaultIconId, type EditorToolbarSFSymbolIcon, type EditorToolbarMaterialIcon, type EditorToolbarCommand, type EditorToolbarListType, } from './EditorToolbar';
2
+ export { EditorToolbar, DEFAULT_EDITOR_TOOLBAR_ITEMS, type EditorToolbarProps, type EditorToolbarItem, type EditorToolbarLeafItem, type EditorToolbarGroupChildItem, type EditorToolbarGroupItem, type EditorToolbarGroupPresentation, type EditorToolbarIcon, type EditorToolbarDefaultIconId, type EditorToolbarSFSymbolIcon, type EditorToolbarMaterialIcon, type EditorToolbarCommand, type EditorToolbarHeadingLevel, type EditorToolbarListType, } from './EditorToolbar';
3
3
  export type { EditorContentInsets, EditorTheme, EditorTextStyle, EditorHeadingTheme, EditorListTheme, EditorHorizontalRuleTheme, EditorMentionTheme, EditorToolbarTheme, EditorToolbarAppearance, EditorFontStyle, EditorFontWeight, } from './EditorTheme';
4
4
  export { MENTION_NODE_NAME, mentionNodeSpec, withMentionsSchema, buildMentionFragmentJson, type EditorAddons, type MentionsAddonConfig, type MentionSuggestion, type MentionQueryChangeEvent, type MentionSelectEvent, type EditorAddonEvent, } from './addons';
5
5
  export { tiptapSchema, prosemirrorSchema, IMAGE_NODE_NAME, imageNodeSpec, withImagesSchema, buildImageFragmentJson, type SchemaDefinition, type NodeSpec, type MarkSpec, type AttrSpec, type ImageNodeAttributes, } from './schemas';
package/dist/schemas.js CHANGED
@@ -5,6 +5,7 @@ exports.imageNodeSpec = imageNodeSpec;
5
5
  exports.withImagesSchema = withImagesSchema;
6
6
  exports.buildImageFragmentJson = buildImageFragmentJson;
7
7
  exports.IMAGE_NODE_NAME = 'image';
8
+ const HEADING_LEVELS = [1, 2, 3, 4, 5, 6];
8
9
  function imageNodeSpec(name = exports.IMAGE_NODE_NAME) {
9
10
  return {
10
11
  name,
@@ -22,6 +23,15 @@ function imageNodeSpec(name = exports.IMAGE_NODE_NAME) {
22
23
  isVoid: true,
23
24
  };
24
25
  }
26
+ function headingNodeSpec(level) {
27
+ return {
28
+ name: `h${level}`,
29
+ content: 'inline*',
30
+ group: 'block',
31
+ role: 'textBlock',
32
+ htmlTag: `h${level}`,
33
+ };
34
+ }
25
35
  function withImagesSchema(schema) {
26
36
  const hasImageNode = schema.nodes.some((node) => node.name === exports.IMAGE_NODE_NAME);
27
37
  if (hasImageNode) {
@@ -64,6 +74,7 @@ exports.tiptapSchema = {
64
74
  role: 'textBlock',
65
75
  htmlTag: 'p',
66
76
  },
77
+ ...HEADING_LEVELS.map((level) => headingNodeSpec(level)),
67
78
  {
68
79
  name: 'blockquote',
69
80
  content: 'block+',
@@ -132,6 +143,7 @@ exports.prosemirrorSchema = {
132
143
  role: 'textBlock',
133
144
  htmlTag: 'p',
134
145
  },
146
+ ...HEADING_LEVELS.map((level) => headingNodeSpec(level)),
135
147
  {
136
148
  name: 'blockquote',
137
149
  content: 'block+',
@@ -30,6 +30,8 @@ export interface UseNativeEditorReturn {
30
30
  redo: () => void;
31
31
  /** Toggle blockquote wrapping around the current block selection. */
32
32
  toggleBlockquote: () => void;
33
+ /** Toggle a heading level on the current block selection. */
34
+ toggleHeading: (level: 1 | 2 | 3 | 4 | 5 | 6) => void;
33
35
  /** Insert text at a position. */
34
36
  insertText: (pos: number, text: string) => void;
35
37
  /** Delete a range [from, to). */
@@ -82,6 +82,12 @@ function useNativeEditor(options = {}) {
82
82
  const update = bridgeRef.current.toggleBlockquote();
83
83
  applyUpdate(update);
84
84
  }, [applyUpdate]);
85
+ const toggleHeading = (0, react_1.useCallback)((level) => {
86
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
87
+ return;
88
+ const update = bridgeRef.current.toggleHeading(level);
89
+ applyUpdate(update);
90
+ }, [applyUpdate]);
85
91
  const insertText = (0, react_1.useCallback)((pos, text) => {
86
92
  if (!bridgeRef.current || bridgeRef.current.isDestroyed)
87
93
  return;
@@ -110,6 +116,7 @@ function useNativeEditor(options = {}) {
110
116
  undo,
111
117
  redo,
112
118
  toggleBlockquote,
119
+ toggleHeading,
113
120
  insertText,
114
121
  deleteRange,
115
122
  getHtml,
@@ -23,7 +23,7 @@ final class EditorLayoutManager: NSLayoutManager {
23
23
  textStorage.enumerateAttribute(
24
24
  RenderBridgeAttributes.blockquoteBorderColor,
25
25
  in: characterRange,
26
- options: []
26
+ options: [.longestEffectiveRangeNotRequired]
27
27
  ) { value, range, _ in
28
28
  guard range.length > 0, let color = value as? UIColor else { return }
29
29
 
@@ -68,7 +68,7 @@ final class EditorLayoutManager: NSLayoutManager {
68
68
  textStorage.enumerateAttribute(
69
69
  RenderBridgeAttributes.listMarkerContext,
70
70
  in: characterRange,
71
- options: []
71
+ options: [.longestEffectiveRangeNotRequired]
72
72
  ) { value, range, _ in
73
73
  guard range.length > 0, let listContext = value as? [String: Any] else { return }
74
74
 
@@ -90,7 +90,7 @@ final class EditorLayoutManager: NSLayoutManager {
90
90
  textStorage.enumerateAttribute(
91
91
  RenderBridgeAttributes.blockquoteBorderColor,
92
92
  in: characterRange,
93
- options: []
93
+ options: [.longestEffectiveRangeNotRequired]
94
94
  ) { value, range, _ in
95
95
  guard range.length > 0, let color = value as? UIColor else { return }
96
96
 
@@ -397,6 +397,22 @@ fileprivate final class UniffiHandleMap<T>: @unchecked Sendable {
397
397
  // Public interface members begin here.
398
398
 
399
399
 
400
+ #if swift(>=5.8)
401
+ @_documentation(visibility: private)
402
+ #endif
403
+ fileprivate struct FfiConverterUInt8: FfiConverterPrimitive {
404
+ typealias FfiType = UInt8
405
+ typealias SwiftType = UInt8
406
+
407
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 {
408
+ return try lift(readInt(&buf))
409
+ }
410
+
411
+ public static func write(_ value: UInt8, into buf: inout [UInt8]) {
412
+ writeInt(&buf, lower(value))
413
+ }
414
+ }
415
+
400
416
  #if swift(>=5.8)
401
417
  @_documentation(visibility: private)
402
418
  #endif
@@ -678,6 +694,18 @@ public func editorDeleteAndSplitScalar(id: UInt64, scalarFrom: UInt32, scalarTo:
678
694
  )
679
695
  })
680
696
  }
697
+ /**
698
+ * Delete backward relative to an explicit scalar selection. Returns an update JSON string.
699
+ */
700
+ public func editorDeleteBackwardAtSelectionScalar(id: UInt64, scalarAnchor: UInt32, scalarHead: UInt32) -> String {
701
+ return try! FfiConverterString.lift(try! rustCall() {
702
+ uniffi_editor_core_fn_func_editor_delete_backward_at_selection_scalar(
703
+ FfiConverterUInt64.lower(id),
704
+ FfiConverterUInt32.lower(scalarAnchor),
705
+ FfiConverterUInt32.lower(scalarHead),$0
706
+ )
707
+ })
708
+ }
681
709
  /**
682
710
  * Delete a range. Returns an update JSON string.
683
711
  */
@@ -722,6 +750,16 @@ public func editorDocToScalar(id: UInt64, docPos: UInt32) -> UInt32 {
722
750
  )
723
751
  })
724
752
  }
753
+ /**
754
+ * Get both HTML and ProseMirror JSON content in one payload.
755
+ */
756
+ public func editorGetContentSnapshot(id: UInt64) -> String {
757
+ return try! FfiConverterString.lift(try! rustCall() {
758
+ uniffi_editor_core_fn_func_editor_get_content_snapshot(
759
+ FfiConverterUInt64.lower(id),$0
760
+ )
761
+ })
762
+ }
725
763
  /**
726
764
  * Get the current editor state (render elements, selection, active state,
727
765
  * history state) without performing any edits. Used by native views to pull
@@ -764,6 +802,16 @@ public func editorGetSelection(id: UInt64) -> String {
764
802
  )
765
803
  })
766
804
  }
805
+ /**
806
+ * Get the current selection-related editor state without render elements.
807
+ */
808
+ public func editorGetSelectionState(id: UInt64) -> String {
809
+ return try! FfiConverterString.lift(try! rustCall() {
810
+ uniffi_editor_core_fn_func_editor_get_selection_state(
811
+ FfiConverterUInt64.lower(id),$0
812
+ )
813
+ })
814
+ }
767
815
  /**
768
816
  * Indent the current list item into a nested list. Returns an update JSON string.
769
817
  */
@@ -1085,6 +1133,30 @@ public func editorToggleBlockquoteAtSelectionScalar(id: UInt64, scalarAnchor: UI
1085
1133
  )
1086
1134
  })
1087
1135
  }
1136
+ /**
1137
+ * Toggle a heading level on the current text-block selection. Returns an update JSON string.
1138
+ */
1139
+ public func editorToggleHeading(id: UInt64, level: UInt8) -> String {
1140
+ return try! FfiConverterString.lift(try! rustCall() {
1141
+ uniffi_editor_core_fn_func_editor_toggle_heading(
1142
+ FfiConverterUInt64.lower(id),
1143
+ FfiConverterUInt8.lower(level),$0
1144
+ )
1145
+ })
1146
+ }
1147
+ /**
1148
+ * Toggle a heading level at an explicit scalar selection. Returns an update JSON string.
1149
+ */
1150
+ public func editorToggleHeadingAtSelectionScalar(id: UInt64, scalarAnchor: UInt32, scalarHead: UInt32, level: UInt8) -> String {
1151
+ return try! FfiConverterString.lift(try! rustCall() {
1152
+ uniffi_editor_core_fn_func_editor_toggle_heading_at_selection_scalar(
1153
+ FfiConverterUInt64.lower(id),
1154
+ FfiConverterUInt32.lower(scalarAnchor),
1155
+ FfiConverterUInt32.lower(scalarHead),
1156
+ FfiConverterUInt8.lower(level),$0
1157
+ )
1158
+ })
1159
+ }
1088
1160
  /**
1089
1161
  * Toggle a mark on the current selection. Returns an update JSON string.
1090
1162
  */
@@ -1256,6 +1328,9 @@ private let initializationResult: InitializationResult = {
1256
1328
  if (uniffi_editor_core_checksum_func_editor_delete_and_split_scalar() != 13764) {
1257
1329
  return InitializationResult.apiChecksumMismatch
1258
1330
  }
1331
+ if (uniffi_editor_core_checksum_func_editor_delete_backward_at_selection_scalar() != 7697) {
1332
+ return InitializationResult.apiChecksumMismatch
1333
+ }
1259
1334
  if (uniffi_editor_core_checksum_func_editor_delete_range() != 6109) {
1260
1335
  return InitializationResult.apiChecksumMismatch
1261
1336
  }
@@ -1268,6 +1343,9 @@ private let initializationResult: InitializationResult = {
1268
1343
  if (uniffi_editor_core_checksum_func_editor_doc_to_scalar() != 48291) {
1269
1344
  return InitializationResult.apiChecksumMismatch
1270
1345
  }
1346
+ if (uniffi_editor_core_checksum_func_editor_get_content_snapshot() != 32837) {
1347
+ return InitializationResult.apiChecksumMismatch
1348
+ }
1271
1349
  if (uniffi_editor_core_checksum_func_editor_get_current_state() != 13946) {
1272
1350
  return InitializationResult.apiChecksumMismatch
1273
1351
  }
@@ -1280,6 +1358,9 @@ private let initializationResult: InitializationResult = {
1280
1358
  if (uniffi_editor_core_checksum_func_editor_get_selection() != 20571) {
1281
1359
  return InitializationResult.apiChecksumMismatch
1282
1360
  }
1361
+ if (uniffi_editor_core_checksum_func_editor_get_selection_state() != 16471) {
1362
+ return InitializationResult.apiChecksumMismatch
1363
+ }
1283
1364
  if (uniffi_editor_core_checksum_func_editor_indent_list_item() != 10818) {
1284
1365
  return InitializationResult.apiChecksumMismatch
1285
1366
  }
@@ -1364,6 +1445,12 @@ private let initializationResult: InitializationResult = {
1364
1445
  if (uniffi_editor_core_checksum_func_editor_toggle_blockquote_at_selection_scalar() != 58523) {
1365
1446
  return InitializationResult.apiChecksumMismatch
1366
1447
  }
1448
+ if (uniffi_editor_core_checksum_func_editor_toggle_heading() != 7099) {
1449
+ return InitializationResult.apiChecksumMismatch
1450
+ }
1451
+ if (uniffi_editor_core_checksum_func_editor_toggle_heading_at_selection_scalar() != 54315) {
1452
+ return InitializationResult.apiChecksumMismatch
1453
+ }
1367
1454
  if (uniffi_editor_core_checksum_func_editor_toggle_mark() != 30661) {
1368
1455
  return InitializationResult.apiChecksumMismatch
1369
1456
  }