@dxos/react-ui-editor 0.4.10-main.fa5a270 → 0.4.10-main.fd8ea31

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 (45) hide show
  1. package/dist/lib/browser/index.mjs +707 -694
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  5. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  7. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  8. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  9. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  10. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
  11. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  12. package/dist/types/src/extensions/automerge/cursor.d.ts +2 -2
  13. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/sync.d.ts +1 -1
  15. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  16. package/dist/types/src/extensions/automerge/update-automerge.d.ts +1 -1
  17. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  18. package/dist/types/src/extensions/factories.d.ts +2 -3
  19. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  20. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  21. package/dist/types/src/extensions/markdown/formatting.d.ts +1 -1
  22. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  23. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  24. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  25. package/dist/types/src/hooks/index.d.ts +0 -1
  26. package/dist/types/src/hooks/index.d.ts.map +1 -1
  27. package/package.json +30 -29
  28. package/src/components/TextEditor/TextEditor.stories.tsx +21 -18
  29. package/src/components/TextEditor/TextEditor.tsx +6 -3
  30. package/src/components/Toolbar/Toolbar.stories.tsx +10 -9
  31. package/src/extensions/automerge/automerge.stories.tsx +7 -6
  32. package/src/extensions/automerge/automerge.ts +1 -1
  33. package/src/extensions/automerge/cursor.ts +4 -37
  34. package/src/extensions/automerge/sync.ts +1 -1
  35. package/src/extensions/automerge/update-automerge.ts +1 -1
  36. package/src/extensions/factories.ts +3 -4
  37. package/src/extensions/markdown/decorate.ts +90 -62
  38. package/src/extensions/markdown/formatting.ts +42 -35
  39. package/src/extensions/markdown/highlight.ts +3 -1
  40. package/src/extensions/markdown/table.ts +7 -6
  41. package/src/hooks/index.ts +0 -1
  42. package/src/hooks/useTextEditor.ts +1 -1
  43. package/dist/types/src/hooks/useDocAccessor.d.ts +0 -9
  44. package/dist/types/src/hooks/useDocAccessor.d.ts.map +0 -1
  45. package/src/hooks/useDocAccessor.ts +0 -27
@@ -81,6 +81,7 @@ const horizontalRule = Decoration.replace({ widget: new HorizontalRuleWidget() }
81
81
  const checkedTask = Decoration.replace({ widget: new CheckboxWidget(true) });
82
82
  const uncheckedTask = Decoration.replace({ widget: new CheckboxWidget(false) });
83
83
 
84
+ // Check if cursor is inside text.
84
85
  const editingRange = (state: EditorState, range: { from: number; to: number }, focus: boolean) => {
85
86
  const {
86
87
  readOnly,
@@ -103,76 +104,103 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
103
104
  from,
104
105
  to,
105
106
  enter: (node) => {
106
- if (node.name === 'FencedCode') {
107
- const editing = editingRange(state, node, focus);
107
+ switch (node.name) {
108
108
  // FencedCode > CodeMark > [CodeInfo] > CodeText > CodeMark
109
- for (const block of view.viewportLineBlocks) {
110
- if (block.to < node.from) {
111
- continue;
112
- }
113
- if (block.from > node.to) {
114
- break;
115
- }
116
- const first = block.from <= node.from;
117
- const last = block.to >= node.to && /^(\s>)*```$/.test(state.doc.sliceString(block.from, block.to));
118
- deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
119
- if (!editing && (first || last)) {
120
- atomicDeco.add(block.from, block.to, hide);
109
+ case 'FencedCode': {
110
+ const editing = editingRange(state, node, focus);
111
+ for (const block of view.viewportLineBlocks) {
112
+ if (block.to < node.from) {
113
+ continue;
114
+ }
115
+ if (block.from > node.to) {
116
+ break;
117
+ }
118
+ const first = block.from <= node.from;
119
+ const last = block.to >= node.to && /^(\s>)*```$/.test(state.doc.sliceString(block.from, block.to));
120
+ deco.add(
121
+ block.from,
122
+ block.from,
123
+ first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine,
124
+ );
125
+ if (!editing && (first || last)) {
126
+ atomicDeco.add(block.from, block.to, hide);
127
+ }
121
128
  }
129
+ return false;
122
130
  }
123
- return false;
124
- } else if (node.name === 'Link') {
125
- const marks = node.node.getChildren('LinkMark');
126
- const urlNode = node.node.getChild('URL');
127
- const editing = editingRange(state, node, focus);
128
- if (urlNode && marks.length >= 2) {
129
- const url = state.sliceDoc(urlNode.from, urlNode.to);
130
- if (!editing) {
131
- atomicDeco.add(node.from, marks[0].to, hide);
132
- }
133
- deco.add(
134
- marks[0].to,
135
- marks[1].from,
136
- Decoration.mark({
137
- tagName: 'a',
138
- attributes: {
139
- class: 'cm-link',
140
- href: url,
141
- rel: 'noreferrer',
142
- target: '_blank',
143
- },
144
- }),
145
- );
146
- if (!editing) {
147
- atomicDeco.add(
131
+
132
+ case 'Link': {
133
+ const marks = node.node.getChildren('LinkMark');
134
+ const urlNode = node.node.getChild('URL');
135
+ const editing = editingRange(state, node, focus);
136
+ if (urlNode && marks.length >= 2) {
137
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
138
+ if (!editing) {
139
+ atomicDeco.add(node.from, marks[0].to, hide);
140
+ }
141
+ deco.add(
142
+ marks[0].to,
148
143
  marks[1].from,
149
- node.to,
150
- options.renderLinkButton
151
- ? Decoration.replace({ widget: new LinkButton(url, options.renderLinkButton) })
152
- : hide,
144
+ Decoration.mark({
145
+ tagName: 'a',
146
+ attributes: {
147
+ class: 'cm-link',
148
+ href: url,
149
+ rel: 'noreferrer',
150
+ target: '_blank',
151
+ },
152
+ }),
153
153
  );
154
+ if (!editing) {
155
+ atomicDeco.add(
156
+ marks[1].from,
157
+ node.to,
158
+ options.renderLinkButton
159
+ ? Decoration.replace({ widget: new LinkButton(url, options.renderLinkButton) })
160
+ : hide,
161
+ );
162
+ }
163
+ }
164
+ break;
165
+ }
166
+
167
+ case 'HeaderMark': {
168
+ const parent = node.node.parent!;
169
+ if (/^ATX/.test(parent.name) && !editingRange(state, state.doc.lineAt(node.from), focus)) {
170
+ const next = state.doc.sliceString(node.to, node.to + 1);
171
+ atomicDeco.add(node.from, node.to + (next === ' ' ? 1 : 0), hide);
154
172
  }
173
+ break;
155
174
  }
156
- } else if (node.name === 'HeaderMark') {
157
- const parent = node.node.parent!;
158
- if (/^ATX/.test(parent.name) && !editingRange(state, state.doc.lineAt(node.from), focus)) {
159
- const next = state.doc.sliceString(node.to, node.to + 1);
160
- atomicDeco.add(node.from, node.to + (next === ' ' ? 1 : 0), hide);
175
+
176
+ case 'HorizontalRule': {
177
+ if (!editingRange(state, node, focus)) {
178
+ deco.add(node.from, node.to, horizontalRule);
179
+ }
180
+ break;
161
181
  }
162
- } else if (node.name === 'HorizontalRule') {
163
- if (!editingRange(state, node, focus)) {
164
- deco.add(node.from, node.to, horizontalRule);
182
+
183
+ case 'TaskMarker': {
184
+ if (!editingRange(state, node, focus)) {
185
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
186
+ atomicDeco.add(node.from - 2, node.from - 1, Decoration.mark({ class: 'cm-task' }));
187
+ atomicDeco.add(node.from, node.to, checked ? checkedTask : uncheckedTask);
188
+ }
189
+ break;
165
190
  }
166
- } else if (node.name === 'TaskMarker') {
167
- // Check if cursor is inside text.
168
- if (!editingRange(state, node, focus)) {
169
- const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
170
- atomicDeco.add(node.from - 2, node.from - 1, Decoration.mark({ class: 'cm-task' }));
171
- atomicDeco.add(node.from, node.to, checked ? checkedTask : uncheckedTask);
191
+
192
+ case 'ListItem': {
193
+ const start = state.doc.lineAt(node.from);
194
+ deco.add(start.from, start.from, Decoration.line({ class: 'cm-list-item' }));
195
+ break;
172
196
  }
173
- } else if (MarksByParent.has(node.name)) {
174
- if (!editingRange(state, node.node.parent!, focus)) {
175
- atomicDeco.add(node.from, node.to, hide);
197
+
198
+ default: {
199
+ if (MarksByParent.has(node.name)) {
200
+ if (!editingRange(state, node.node.parent!, focus)) {
201
+ atomicDeco.add(node.from, node.to, hide);
202
+ }
203
+ }
176
204
  }
177
205
  }
178
206
  },
@@ -224,8 +252,6 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
224
252
  }
225
253
  }
226
254
 
227
- // TODO(burdon): BUG: If the cursor is at the end of a link at the end of a line,
228
- // the cursor will float in space or be in the wrong position after the decoration is applied.
229
255
  scheduleUpdate(view: EditorView) {
230
256
  this.clearUpdate();
231
257
  this.pendingUpdate = setTimeout(() => {
@@ -304,4 +330,6 @@ const formattingStyles = EditorView.baseTheme({
304
330
  marginLeft: '4px',
305
331
  marginRight: '4px',
306
332
  },
333
+
334
+ // '& .cm-list-item > span:nth-child(2)': {},
307
335
  });
@@ -83,9 +83,8 @@ export enum List {
83
83
  // Headings
84
84
  //
85
85
 
86
- export const setHeading =
87
- (level: number): StateCommand =>
88
- ({ state, dispatch }) => {
86
+ export const setHeading = (level: number): StateCommand => {
87
+ return ({ state, dispatch }) => {
89
88
  const {
90
89
  selection: { ranges },
91
90
  doc,
@@ -151,14 +150,14 @@ export const setHeading =
151
150
  );
152
151
  return true;
153
152
  };
153
+ };
154
154
 
155
155
  //
156
156
  // Styles
157
157
  //
158
158
 
159
- export const setStyle =
160
- (type: Inline, enable: boolean): StateCommand =>
161
- ({ state, dispatch }) => {
159
+ export const setStyle = (type: Inline, enable: boolean): StateCommand => {
160
+ return ({ state, dispatch }) => {
162
161
  const marker = inlineMarkerText(type);
163
162
  const changes = state.changeByRange((range) => {
164
163
  // Special case for markers directly around the cursor, which will often not be parsed as valid styling.
@@ -322,14 +321,14 @@ export const setStyle =
322
321
 
323
322
  return true;
324
323
  };
324
+ };
325
325
 
326
326
  export const addStyle = (style: Inline): StateCommand => setStyle(style, true);
327
327
 
328
328
  export const removeStyle = (style: Inline): StateCommand => setStyle(style, false);
329
329
 
330
- export const toggleStyle =
331
- (style: Inline): StateCommand =>
332
- (arg) => {
330
+ export const toggleStyle = (style: Inline): StateCommand => {
331
+ return (arg) => {
333
332
  const form = getFormatting(arg.state);
334
333
  return setStyle(
335
334
  style,
@@ -342,6 +341,7 @@ export const toggleStyle =
342
341
  : !form.code,
343
342
  )(arg);
344
343
  };
344
+ };
345
345
 
346
346
  export const toggleStrong = toggleStyle(Inline.Strong);
347
347
  export const toggleEmphasis = toggleStyle(Inline.Emphasis);
@@ -469,10 +469,9 @@ export const removeLink: StateCommand = ({ state, dispatch }) => {
469
469
  return true;
470
470
  };
471
471
 
472
- // Add link markup around the selection
473
- export const addLink =
474
- ({ url, image }: { url?: string; image?: boolean } = {}): StateCommand =>
475
- ({ state, dispatch }) => {
472
+ // Add link markup around the selection.
473
+ export const addLink = ({ url, image }: { url?: string; image?: boolean } = {}): StateCommand => {
474
+ return ({ state, dispatch }) => {
476
475
  const changes = state.changeByRange((range) => {
477
476
  let { from, to } = range;
478
477
  const cutStyles: SyntaxNode[] = [];
@@ -519,7 +518,7 @@ export const addLink =
519
518
  }
520
519
 
521
520
  const changes: ChangeSpec[] = [];
522
- // Some changes must be moved to end of change array so that they are applied in the right order
521
+ // Some changes must be moved to end of change array so that they are applied in the right order.
523
522
  const changesAfter: ChangeSpec[] = [];
524
523
  // Clear existing links.
525
524
  removeLinkInner(from, to, changesAfter, state);
@@ -551,28 +550,34 @@ export const addLink =
551
550
  range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset - (url ? url.length + 2 : 0)),
552
551
  };
553
552
  });
553
+
554
554
  if (changes.changes.empty) {
555
555
  return false;
556
556
  }
557
557
 
558
- dispatch(state.update(changes, { userEvent: 'format.link.add', scrollIntoView: true }));
558
+ dispatch(
559
+ state.update(changes, {
560
+ userEvent: 'format.link.add',
561
+ scrollIntoView: true,
562
+ }),
563
+ );
559
564
  return true;
560
565
  };
566
+ };
561
567
 
562
568
  //
563
569
  // Lists
564
570
  //
565
571
 
566
- export const addList =
567
- (type: List): StateCommand =>
568
- ({ state, dispatch }) => {
572
+ export const addList = (type: List): StateCommand => {
573
+ return ({ state, dispatch }) => {
569
574
  let lastBlock = -1;
570
575
  let counter = 1;
571
576
  let first = true;
572
577
  let parentColumn: number | null = null;
573
- const blocks: { node: SyntaxNode; counter: number; parentColumn: number | null }[] = [];
574
578
 
575
579
  // Scan the syntax tree to locate textblocks that can be wrapped.
580
+ const blocks: { node: SyntaxNode; counter: number; parentColumn: number | null }[] = [];
576
581
  for (const { from, to } of state.selection.ranges) {
577
582
  syntaxTree(state).iterate({
578
583
  from,
@@ -604,6 +609,7 @@ export const addList =
604
609
  if (node.from === lastBlock) {
605
610
  return;
606
611
  }
612
+
607
613
  lastBlock = node.from;
608
614
  blocks.push({ node: node.node, counter, parentColumn });
609
615
  counter++;
@@ -638,7 +644,6 @@ export const addList =
638
644
  scrollIntoView: true,
639
645
  }),
640
646
  );
641
-
642
647
  return true;
643
648
  }
644
649
 
@@ -680,7 +685,7 @@ export const addList =
680
685
  }
681
686
 
682
687
  changes.push({ from: nodeFrom, insert: mark });
683
- // Add indentation for the other lines in this block
688
+ // Add indentation for the other lines in this block.
684
689
  while (line.to < node.to) {
685
690
  line = state.doc.lineAt(line.to + 1);
686
691
  const open = /^[\s>]*/.exec(line.text)![0].length;
@@ -700,7 +705,7 @@ export const addList =
700
705
  renumberListItems(next.firstChild, last.counter + 1, changes, state.doc);
701
706
  }
702
707
  }
703
-
708
+ ('Oeswe');
704
709
  const changeSet = state.changes(changes);
705
710
  dispatch(
706
711
  state.update({
@@ -712,10 +717,10 @@ export const addList =
712
717
  );
713
718
  return true;
714
719
  };
720
+ };
715
721
 
716
- export const removeList =
717
- (type: List): StateCommand =>
718
- ({ state, dispatch }) => {
722
+ export const removeList = (type: List): StateCommand => {
723
+ return ({ state, dispatch }) => {
719
724
  let lastBlock = -1;
720
725
  const changes: ChangeSpec[] = [];
721
726
  const stack: string[] = [];
@@ -771,15 +776,16 @@ export const removeList =
771
776
  dispatch(state.update({ changes, userEvent: 'format.list.remove', scrollIntoView: true }));
772
777
  return true;
773
778
  };
779
+ };
774
780
 
775
- export const toggleList =
776
- (type: List): StateCommand =>
777
- (target) => {
781
+ export const toggleList = (type: List): StateCommand => {
782
+ return (target) => {
778
783
  const formatting = getFormatting(target.state);
779
784
  const active =
780
785
  formatting.listStyle === (type === List.Bullet ? 'bullet' : type === List.Ordered ? 'ordered' : 'task');
781
786
  return (active ? removeList(type) : addList(type))(target);
782
787
  };
788
+ };
783
789
 
784
790
  const renumberListItems = (item: SyntaxNode | null, counter: number, changes: ChangeSpec[], doc: Text) => {
785
791
  for (; item; item = item.nextSibling) {
@@ -800,9 +806,8 @@ const renumberListItems = (item: SyntaxNode | null, counter: number, changes: Ch
800
806
  // Block quotes
801
807
  //
802
808
 
803
- export const setBlockquote =
804
- (enable: boolean): StateCommand =>
805
- ({ state, dispatch }) => {
809
+ export const setBlockquote = (enable: boolean): StateCommand => {
810
+ return ({ state, dispatch }) => {
806
811
  const lines: Line[] = [];
807
812
  let lastBlock = -1;
808
813
  for (const { from, to } of state.selection.ranges) {
@@ -874,6 +879,7 @@ export const setBlockquote =
874
879
  );
875
880
  return true;
876
881
  };
882
+ };
877
883
 
878
884
  export const addBlockquote = setBlockquote(true);
879
885
 
@@ -944,9 +950,10 @@ export const addCodeblock: StateCommand = (target) => {
944
950
  };
945
951
 
946
952
  export const removeCodeblock: StateCommand = ({ state, dispatch }) => {
947
- const changes: ChangeSpec[] = [];
948
953
  let lastBlock = -1;
949
- // Find all code blocks, remove their markup
954
+
955
+ // Find all code blocks, remove their markup.
956
+ const changes: ChangeSpec[] = [];
950
957
  for (const { from, to } of state.selection.ranges) {
951
958
  syntaxTree(state).iterate({
952
959
  from,
@@ -965,7 +972,7 @@ export const removeCodeblock: StateCommand = ({ state, dispatch }) => {
965
972
  });
966
973
  }
967
974
  } else {
968
- // Indented code block
975
+ // Indented code block.
969
976
  const column = node.from - firstLine.from;
970
977
  for (let line = firstLine; ; line = state.doc.line(line.number + 1)) {
971
978
  changes.push({ from: line.from + column - 4, to: line.from + column });
@@ -1050,7 +1057,7 @@ const Textblocks: { [name: string]: NonNullable<Formatting['blockType']> } = {
1050
1057
  };
1051
1058
 
1052
1059
  /**
1053
- * Query an editor state for the active formatting at the selection.
1060
+ * Query the editor state for the active formatting at the selection.
1054
1061
  */
1055
1062
  export const getFormatting = (state: EditorState): Formatting => {
1056
1063
  // These will track the formatting we've seen so far.
@@ -183,7 +183,9 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
183
183
  ],
184
184
  {
185
185
  scope: markdownLanguage,
186
- all: { fontFamily: getToken('fontFamily.body', []).join(',') },
186
+ all: {
187
+ fontFamily: getToken('fontFamily.body', []).join(','),
188
+ },
187
189
  },
188
190
  );
189
191
  };
@@ -15,7 +15,6 @@ import { Decoration, EditorView, WidgetType } from '@codemirror/view';
15
15
 
16
16
  // TODO(burdon): Snippet to create basic table.
17
17
  // https://codemirror.net/docs/ref/#autocomplete.snippet
18
-
19
18
  // TODO(burdon): Advanced formatting (left/right/center).
20
19
  // TODO(burdon): Editor to auto balance columns.
21
20
 
@@ -82,19 +81,21 @@ const update = (state: EditorState, options: TableOptions) => {
82
81
  });
83
82
 
84
83
  tables.forEach((table) => {
85
- const hide = state.readOnly || cursor < table.from || cursor > table.to;
86
- if (hide) {
84
+ const replace = state.readOnly || cursor < table.from || cursor > table.to;
85
+ if (replace) {
87
86
  builder.add(
88
87
  table.from,
89
88
  table.to,
90
89
  Decoration.replace({
90
+ block: true,
91
91
  widget: new TableWidget(table),
92
92
  }),
93
93
  );
94
+ } else {
95
+ // Add class for styling.
96
+ // TODO(burdon): Apply to each line?
97
+ builder.add(table.from, table.to, Decoration.mark({ class: 'cm-table' }));
94
98
  }
95
-
96
- // Add class for styling.
97
- builder.add(table.from, table.to, Decoration.mark({ class: 'cm-table' }));
98
99
  });
99
100
 
100
101
  return builder.finish();
@@ -3,5 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './useActionHandler';
6
- export * from './useDocAccessor';
7
6
  export * from './useTextEditor';
@@ -36,9 +36,9 @@ export const useTextEditor = (cb: () => UseTextEditorProps = () => ({}), deps: D
36
36
  log('create', { id });
37
37
 
38
38
  // https://codemirror.net/docs/ref/#state.EditorStateConfig
39
+ // NOTE: Don't set selection here in case it is invalid (and crashes the state); dispatch below.
39
40
  const state = EditorState.create({
40
41
  doc,
41
- selection,
42
42
  extensions: [
43
43
  id && documentId.of(id),
44
44
  // TODO(burdon): Doesn't catch errors in keymap functions.
@@ -1,9 +0,0 @@
1
- import { type DocAccessor, type TextObject, type EchoReactiveObject } from '@dxos/echo-schema';
2
- export declare const useDocAccessor: <T = any>(text: TextObject | EchoReactiveObject<{
3
- content: string;
4
- }>) => {
5
- id: string;
6
- doc: string | undefined;
7
- accessor: DocAccessor<T>;
8
- };
9
- //# sourceMappingURL=useDocAccessor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useDocAccessor.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useDocAccessor.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAG3B,eAAO,MAAM,cAAc,kBACnB,UAAU,GAAG,mBAAmB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;QACnD,MAAM;SAAO,MAAM,GAAG,SAAS;;CASvC,CAAC"}
@@ -1,27 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { useMemo } from 'react';
6
-
7
- import {
8
- createDocAccessor,
9
- type DocAccessor,
10
- getTextContent,
11
- type TextObject,
12
- type EchoReactiveObject,
13
- } from '@dxos/echo-schema';
14
-
15
- // TODO(burdon): Factor out.
16
- export const useDocAccessor = <T = any>(
17
- text: TextObject | EchoReactiveObject<{ content: string }>,
18
- ): { id: string; doc: string | undefined; accessor: DocAccessor<T> } => {
19
- return useMemo(
20
- () => ({
21
- id: text.id,
22
- doc: getTextContent(text),
23
- accessor: createDocAccessor<T>(text),
24
- }),
25
- [text],
26
- );
27
- };