@dxos/react-ui-editor 0.4.8-main.2d8e727 → 0.4.8-main.512e4c8

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 (89) hide show
  1. package/dist/lib/browser/index.mjs +920 -1084
  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 +18 -34
  5. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +79 -5
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  9. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  10. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  11. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
  12. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  13. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  14. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/defs.d.ts +1 -15
  16. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  18. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/sync.d.ts +2 -1
  20. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -1
  22. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  23. package/dist/types/src/extensions/comments.d.ts +1 -1
  24. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  25. package/dist/types/src/extensions/cursor-line-margin.d.ts +6 -7
  26. package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -1
  27. package/dist/types/src/extensions/doc.d.ts +3 -0
  28. package/dist/types/src/extensions/doc.d.ts.map +1 -0
  29. package/dist/types/src/extensions/index.d.ts +1 -1
  30. package/dist/types/src/extensions/index.d.ts.map +1 -1
  31. package/dist/types/src/extensions/markdown/bundle.d.ts +5 -4
  32. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  33. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  34. package/dist/types/src/extensions/markdown/formatting.d.ts +1 -1
  35. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  36. package/dist/types/src/extensions/markdown/table.d.ts +2 -2
  37. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  38. package/dist/types/src/hooks/defs.d.ts +0 -11
  39. package/dist/types/src/hooks/defs.d.ts.map +1 -1
  40. package/dist/types/src/hooks/extension-utils.d.ts +54 -0
  41. package/dist/types/src/hooks/extension-utils.d.ts.map +1 -0
  42. package/dist/types/src/hooks/index.d.ts +1 -2
  43. package/dist/types/src/hooks/index.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useTextEditor.d.ts +3 -44
  45. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useTextEditor.stories.d.ts +6 -0
  47. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  48. package/dist/types/src/themes/default.d.ts +0 -3
  49. package/dist/types/src/themes/default.d.ts.map +1 -1
  50. package/package.json +24 -24
  51. package/src/components/TextEditor/TextEditor.stories.tsx +510 -23
  52. package/src/components/TextEditor/TextEditor.tsx +37 -149
  53. package/src/components/Toolbar/Toolbar.stories.tsx +46 -42
  54. package/src/extensions/automerge/automerge.stories.tsx +14 -16
  55. package/src/extensions/automerge/automerge.ts +2 -1
  56. package/src/extensions/automerge/cursor.ts +1 -1
  57. package/src/extensions/automerge/defs.ts +1 -22
  58. package/src/extensions/automerge/index.ts +0 -1
  59. package/src/extensions/automerge/sync.ts +2 -9
  60. package/src/extensions/automerge/update-automerge.ts +2 -1
  61. package/src/extensions/command/state.ts +1 -1
  62. package/src/extensions/comments.ts +8 -4
  63. package/src/extensions/cursor-line-margin.ts +17 -18
  64. package/src/extensions/doc.ts +14 -0
  65. package/src/extensions/index.ts +1 -1
  66. package/src/extensions/markdown/bundle.ts +11 -28
  67. package/src/extensions/markdown/decorate.ts +1 -3
  68. package/src/extensions/markdown/formatting.ts +15 -10
  69. package/src/extensions/markdown/image.ts +1 -0
  70. package/src/extensions/markdown/table.ts +11 -3
  71. package/src/hooks/defs.ts +0 -15
  72. package/src/hooks/extension-utils.ts +185 -0
  73. package/src/hooks/index.ts +1 -2
  74. package/src/hooks/useTextEditor.stories.tsx +39 -34
  75. package/src/hooks/useTextEditor.ts +11 -120
  76. package/src/index.ts +1 -1
  77. package/src/themes/default.ts +1 -28
  78. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +0 -103
  79. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +0 -1
  80. package/dist/types/src/extensions/outliner.d.ts +0 -4
  81. package/dist/types/src/extensions/outliner.d.ts.map +0 -1
  82. package/dist/types/src/hooks/useEditorView.d.ts +0 -20
  83. package/dist/types/src/hooks/useEditorView.d.ts.map +0 -1
  84. package/dist/types/src/hooks/useTextModel.d.ts +0 -33
  85. package/dist/types/src/hooks/useTextModel.d.ts.map +0 -1
  86. package/src/components/TextEditor/MarkdownEditor.stories.tsx +0 -524
  87. package/src/extensions/outliner.ts +0 -12
  88. package/src/hooks/useEditorView.ts +0 -32
  89. package/src/hooks/useTextModel.ts +0 -121
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Facet } from '@codemirror/state';
6
+
7
+ import { invariant } from '@dxos/invariant';
8
+
9
+ export const documentId = Facet.define<string, string>({
10
+ combine: (providers) => {
11
+ invariant(providers.length <= 1);
12
+ return providers[0];
13
+ },
14
+ });
@@ -11,10 +11,10 @@ export * from './command';
11
11
  export * from './comments';
12
12
  export * from './cursor';
13
13
  export * from './cursor-line-margin';
14
+ export * from './doc';
14
15
  export * from './dnd';
15
16
  export * from './listener';
16
17
  export * from './markdown';
17
18
  export * from './mention';
18
19
  export * from './modes';
19
- export * from './outliner';
20
20
  export * from './typewriter';
@@ -2,51 +2,34 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
6
- import { history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
5
+ import { closeBracketsKeymap } from '@codemirror/autocomplete';
6
+ import { historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
7
7
  import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
8
- import { bracketMatching, defaultHighlightStyle, indentOnInput, syntaxHighlighting } from '@codemirror/language';
8
+ import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
9
9
  import { languages } from '@codemirror/language-data';
10
10
  import { searchKeymap } from '@codemirror/search';
11
- import { EditorState, type Extension } from '@codemirror/state';
11
+ import { type Extension } from '@codemirror/state';
12
12
  import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
13
- import { EditorView, drawSelection, dropCursor, keymap, placeholder } from '@codemirror/view';
13
+ import { keymap } from '@codemirror/view';
14
14
 
15
15
  import { type ThemeMode } from '@dxos/react-ui';
16
- import { isNotFalsy } from '@dxos/util';
17
16
 
18
17
  import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
19
- import { type TextEditorProps } from '../../components';
20
18
 
21
19
  export type MarkdownBundleOptions = {
22
20
  themeMode?: ThemeMode;
23
- } & Pick<TextEditorProps, 'lineWrapping' | 'placeholder'>;
21
+ };
24
22
 
25
23
  /**
26
- * Markdown bundle.
24
+ * Creates markdown extensions.
25
+ * To be used in conjunction with createBasicExtensions.
26
+ *
27
27
  * Refs:
28
28
  * https://codemirror.net/docs/community
29
29
  * https://codemirror.net/docs/ref/#codemirror.basicSetup
30
30
  */
31
- export const createMarkdownExtensions = ({
32
- themeMode,
33
- placeholder: _placeholder,
34
- lineWrapping = true,
35
- }: MarkdownBundleOptions = {}): Extension[] => {
31
+ export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions = {}): Extension[] => {
36
32
  return [
37
- // TODO(burdon): Reconcile with createBasicExtensions.
38
- lineWrapping && EditorView.lineWrapping,
39
- EditorState.allowMultipleSelections.of(true),
40
- EditorState.tabSize.of(2),
41
-
42
- bracketMatching(),
43
- closeBrackets(),
44
- dropCursor(),
45
- drawSelection(),
46
- history(),
47
- indentOnInput(),
48
- _placeholder && placeholder(_placeholder),
49
-
50
33
  // Main extension.
51
34
  // https://github.com/codemirror/lang-markdown
52
35
  // https://codemirror.net/5/mode/markdown/index.html (demo).
@@ -86,5 +69,5 @@ export const createMarkdownExtensions = ({
86
69
  // https://codemirror.net/docs/ref/#commands.standardKeymap
87
70
  ...standardKeymap,
88
71
  ]),
89
- ].filter(isNotFalsy);
72
+ ];
90
73
  };
@@ -148,9 +148,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
148
148
  marks[1].from,
149
149
  node.to,
150
150
  options.renderLinkButton
151
- ? Decoration.replace({
152
- widget: new LinkButton(url, options.renderLinkButton),
153
- })
151
+ ? Decoration.replace({ widget: new LinkButton(url, options.renderLinkButton) })
154
152
  : hide,
155
153
  );
156
154
  }
@@ -161,7 +161,7 @@ export const setStyle =
161
161
  ({ state, dispatch }) => {
162
162
  const marker = inlineMarkerText(type);
163
163
  const changes = state.changeByRange((range) => {
164
- // Special case for markers directly around the cursor, which will often not be parsed as valid styling
164
+ // Special case for markers directly around the cursor, which will often not be parsed as valid styling.
165
165
  if (!enable && range.empty) {
166
166
  const after = state.doc.sliceString(range.head, range.head + 6);
167
167
  const found = after.indexOf(marker);
@@ -190,16 +190,16 @@ export const setStyle =
190
190
  let startCovered: boolean | { from: number; to: number } = false;
191
191
  let endCovered: boolean | { from: number; to: number } = false;
192
192
  let { from, to } = range;
193
- // Iterate the selected range. For each textblock, determine a
194
- // start and end position, the overlap of the selected range and
195
- // the block's extent, that should be styled/unstyled.
193
+
194
+ // Iterate the selected range. For each textblock, determine a start and end position,
195
+ // the overlap of the selected range and the block's extent, that should be styled/unstyled.
196
196
  syntaxTree(state).iterate({
197
197
  from,
198
198
  to,
199
199
  enter: (node) => {
200
200
  const { name } = node;
201
201
  if (Object.hasOwn(Textblocks, name) && Textblocks[name] !== 'codeblock') {
202
- // Set up for this textblock
202
+ // Set up for this textblock.
203
203
  blockStart = blockContentStart(node);
204
204
  blockEnd = blockContentEnd(node, state.doc);
205
205
  startCovered = endCovered = false;
@@ -243,8 +243,7 @@ export const setStyle =
243
243
  : true;
244
244
  }
245
245
  }
246
- // Marks of the same type in range, or any mark if we're
247
- // adding code style, need to be removed.
246
+ // Marks of the same type in range, or any mark if we're adding code style, need to be removed.
248
247
  if (markType === type || (type === Inline.Code && enable)) {
249
248
  if (node.from >= from && openEnd <= to) {
250
249
  changes.push({ from: node.from, to: openEnd });
@@ -271,7 +270,7 @@ export const setStyle =
271
270
  },
272
271
  leave: (node) => {
273
272
  if (Object.hasOwn(Textblocks, node.name) && Textblocks[node.name] !== 'codeblock') {
274
- // Finish opening/closing the marks for this textblock
273
+ // Finish opening/closing the marks for this textblock.
275
274
  const rangeStart = Math.max(from, blockStart);
276
275
  const rangeEnd = Math.min(to, blockEnd);
277
276
  if (enable) {
@@ -296,12 +295,14 @@ export const setStyle =
296
295
  }
297
296
  },
298
297
  });
298
+
299
299
  if (blockStart < 0 && range.empty && enable && !/\S/.test(state.doc.lineAt(range.from).text)) {
300
300
  return {
301
301
  changes: { from: range.head, insert: marker + marker },
302
302
  range: EditorSelection.cursor(range.head + marker.length),
303
303
  };
304
304
  }
305
+
305
306
  const changeSet = state.changes(changes.concat(changesAtEnd));
306
307
  return {
307
308
  changes: changeSet,
@@ -313,8 +314,12 @@ export const setStyle =
313
314
  });
314
315
 
315
316
  dispatch(
316
- state.update(changes, { userEvent: enable ? 'format.style.add' : 'format.style.remove', scrollIntoView: true }),
317
+ state.update(changes, {
318
+ userEvent: enable ? 'format.style.add' : 'format.style.remove',
319
+ scrollIntoView: true,
320
+ }),
317
321
  );
322
+
318
323
  return true;
319
324
  };
320
325
 
@@ -987,7 +992,7 @@ export const toggleCodeblock: StateCommand = (target) => {
987
992
 
988
993
  export type FormattingOptions = {};
989
994
 
990
- export const formatting = (options: FormattingOptions = {}): Extension => {
995
+ export const formattingKeymap = (options: FormattingOptions = {}): Extension => {
991
996
  return [
992
997
  keymap.of([
993
998
  {
@@ -61,6 +61,7 @@ const buildDecorations = (from: number, to: number, state: EditorState) => {
61
61
  if (urlNode) {
62
62
  const hide = state.readOnly || cursor < node.from || cursor > node.to;
63
63
  const url = state.sliceDoc(urlNode.from, urlNode.to);
64
+ // TODO(burdon): Doesn't load if scrolling with mouse.
64
65
  preloadImage(url);
65
66
  decorations.push(
66
67
  Decoration.replace({
@@ -3,7 +3,14 @@
3
3
  //
4
4
 
5
5
  import { syntaxTree } from '@codemirror/language';
6
- import { type EditorState, type RangeSet, RangeSetBuilder, StateField, type Transaction } from '@codemirror/state';
6
+ import {
7
+ type EditorState,
8
+ type Extension,
9
+ type RangeSet,
10
+ RangeSetBuilder,
11
+ StateField,
12
+ type Transaction,
13
+ } from '@codemirror/state';
7
14
  import { Decoration, EditorView, WidgetType } from '@codemirror/view';
8
15
 
9
16
  // TODO(burdon): Snippet to create basic table.
@@ -18,7 +25,7 @@ export type TableOptions = {};
18
25
  * GFM tables.
19
26
  * https://github.github.com/gfm/#tables-extension
20
27
  */
21
- export const table = (options: TableOptions = {}) => {
28
+ export const table = (options: TableOptions = {}): Extension => {
22
29
  return StateField.define<RangeSet<any>>({
23
30
  create: (state) => update(state, options),
24
31
  update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
@@ -76,7 +83,7 @@ const update = (state: EditorState, options: TableOptions) => {
76
83
 
77
84
  tables.forEach((table) => {
78
85
  const hide = state.readOnly || cursor < table.from || cursor > table.to;
79
- hide &&
86
+ if (hide) {
80
87
  builder.add(
81
88
  table.from,
82
89
  table.to,
@@ -84,6 +91,7 @@ const update = (state: EditorState, options: TableOptions) => {
84
91
  widget: new TableWidget(table),
85
92
  }),
86
93
  );
94
+ }
87
95
 
88
96
  // Add class for styling.
89
97
  builder.add(table.from, table.to, Decoration.mark({ class: 'cm-table' }));
package/src/hooks/defs.ts CHANGED
@@ -2,10 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Extension } from '@codemirror/state';
6
-
7
- import { type DocAccessor } from '@dxos/echo-schema';
8
-
9
5
  // Runtime data structure.
10
6
  export type Range = {
11
7
  from: number;
@@ -17,14 +13,3 @@ export type Comment = {
17
13
  id: string;
18
14
  cursor?: string;
19
15
  };
20
-
21
- /**
22
- * @deprecated
23
- */
24
- // TODO(burdon): Remove.
25
- export type EditorModel = {
26
- id: string;
27
- text: () => string;
28
- content: string | DocAccessor;
29
- extension?: Extension;
30
- };
@@ -0,0 +1,185 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { closeBrackets } from '@codemirror/autocomplete';
6
+ import { history } from '@codemirror/commands';
7
+ import { bracketMatching } from '@codemirror/language';
8
+ import { EditorState, type Extension } from '@codemirror/state';
9
+ import {
10
+ EditorView,
11
+ crosshairCursor,
12
+ drawSelection,
13
+ dropCursor,
14
+ highlightActiveLine,
15
+ lineNumbers,
16
+ placeholder,
17
+ scrollPastEnd,
18
+ } from '@codemirror/view';
19
+ import defaultsDeep from 'lodash.defaultsdeep';
20
+ import { useMemo } from 'react';
21
+
22
+ import { generateName } from '@dxos/display-name';
23
+ import { createDocAccessor, getTextContent, type TextObject, type DocAccessor } from '@dxos/echo-schema';
24
+ import { log } from '@dxos/log';
25
+ import { type Space } from '@dxos/react-client/echo';
26
+ import { type Identity } from '@dxos/react-client/halo';
27
+ import { type ThemeMode } from '@dxos/react-ui';
28
+ import { type HuePalette, hueTokens } from '@dxos/react-ui-theme';
29
+ import { hexToHue, isNotFalsy } from '@dxos/util';
30
+
31
+ import { SpaceAwarenessProvider } from './awareness-provider';
32
+ import { automerge, awareness } from '../extensions';
33
+ import { type ThemeStyles } from '../styles';
34
+ import { defaultTheme } from '../themes';
35
+
36
+ // TODO(burdon): Move into extensions folder.
37
+
38
+ //
39
+ // Basic
40
+ //
41
+
42
+ /**
43
+ * https://codemirror.net/docs/extensions
44
+ * https://github.com/codemirror/basic-setup
45
+ */
46
+ // TODO(burdon): Reconcile with createMarkdownExtensions.
47
+ export type BasicExtensionsOptions = {
48
+ allowMultipleSelections?: boolean;
49
+ bracketMatching?: boolean;
50
+ closeBrackets?: boolean;
51
+ crosshairCursor?: boolean;
52
+ dropCursor?: boolean;
53
+ drawSelection?: boolean;
54
+ editable?: boolean;
55
+ highlightActiveLine?: boolean;
56
+ history?: boolean;
57
+ lineNumbers?: boolean;
58
+ lineWrapping?: boolean;
59
+ placeholder?: string;
60
+ readonly?: boolean;
61
+ scrollPastEnd?: boolean;
62
+ tabSize?: number;
63
+ };
64
+
65
+ const defaults: BasicExtensionsOptions = {
66
+ bracketMatching: true,
67
+ closeBrackets: true,
68
+ drawSelection: true,
69
+ editable: true,
70
+ history: true,
71
+ lineWrapping: true,
72
+ };
73
+
74
+ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
75
+ const props: BasicExtensionsOptions = defaultsDeep({}, _props, defaults);
76
+ return [
77
+ // TODO(burdon): Doesn't catch errors in keymap functions.
78
+ EditorView.exceptionSink.of((err) => {
79
+ log.catch(err);
80
+ }),
81
+
82
+ props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
83
+ props.bracketMatching && bracketMatching(),
84
+ props.closeBrackets && closeBrackets(),
85
+ props.crosshairCursor && crosshairCursor(),
86
+ props.dropCursor && dropCursor(),
87
+ props.drawSelection && drawSelection(),
88
+ props.highlightActiveLine && highlightActiveLine(),
89
+ props.history && history(),
90
+ props.lineNumbers && lineNumbers(),
91
+ props.lineWrapping && EditorView.lineWrapping,
92
+ props.placeholder && placeholder(props.placeholder),
93
+ props.readonly && [EditorState.readOnly.of(true), EditorView.editable.of(false)],
94
+ props.scrollPastEnd && scrollPastEnd(),
95
+ props.tabSize && EditorState.tabSize.of(props.tabSize),
96
+ ].filter(isNotFalsy);
97
+ };
98
+
99
+ //
100
+ // Theme
101
+ //
102
+
103
+ export type ThemeExtensionsOptions = {
104
+ theme?: ThemeStyles;
105
+ themeMode?: ThemeMode;
106
+ slots?: {
107
+ editor?: {
108
+ className?: string;
109
+ };
110
+ content?: {
111
+ className?: string;
112
+ };
113
+ };
114
+ };
115
+
116
+ const defaultSlots = {
117
+ editor: {
118
+ className: 'w-full bs-full',
119
+ },
120
+ content: {
121
+ className: '!p-2',
122
+ },
123
+ };
124
+
125
+ export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: ThemeExtensionsOptions = {}): Extension => {
126
+ const slots = defaultsDeep({}, _slots, defaultSlots);
127
+ return [
128
+ EditorView.baseTheme(defaultTheme),
129
+ EditorView.darkTheme.of(themeMode === 'dark'),
130
+ theme && EditorView.theme(theme),
131
+ slots.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
132
+ slots.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
133
+ ].filter(isNotFalsy);
134
+ };
135
+
136
+ //
137
+ // Data
138
+ //
139
+
140
+ export type DataExtensionsProps = {
141
+ id: string;
142
+ text: DocAccessor; // TODO(burdon): Rename content.
143
+ space?: Space;
144
+ identity?: Identity | null;
145
+ };
146
+
147
+ // TODO(burdon): Factor out automerge defs and extension (not hook).
148
+ // TODO(burdon): Move out of react-ui-editor (remove echo deps).
149
+ export const createDataExtensions = ({ id, text, space, identity }: DataExtensionsProps): Extension[] => {
150
+ const extensions: Extension[] = [automerge(text)];
151
+
152
+ if (space && identity) {
153
+ const peerId = identity?.identityKey.toHex();
154
+ const { cursorLightValue, cursorDarkValue } =
155
+ hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
156
+ const awarenessProvider = new SpaceAwarenessProvider({
157
+ space,
158
+ channel: `awareness.${id}`,
159
+ peerId: identity.identityKey.toHex(),
160
+ info: {
161
+ displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
162
+ color: cursorDarkValue,
163
+ lightColor: cursorLightValue,
164
+ },
165
+ });
166
+
167
+ extensions.push(awareness(awarenessProvider));
168
+ }
169
+
170
+ return extensions;
171
+ };
172
+
173
+ // TODO(burdon): Factor out.
174
+ export const useDocAccessor = <T = any>(
175
+ text: TextObject,
176
+ ): { id: string; doc: string | undefined; accessor: DocAccessor<T> } => {
177
+ return useMemo(
178
+ () => ({
179
+ id: text.id,
180
+ doc: getTextContent(text),
181
+ accessor: createDocAccessor<T>(text),
182
+ }),
183
+ [text],
184
+ );
185
+ };
@@ -4,8 +4,7 @@
4
4
 
5
5
  export * from './awareness-provider';
6
6
  export * from './defs';
7
+ export * from './extension-utils';
7
8
 
8
9
  export * from './useActionHandler';
9
- export * from './useEditorView';
10
10
  export * from './useTextEditor';
11
- export * from './useTextModel';
@@ -4,34 +4,28 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import React, { useMemo, useState } from 'react';
7
+ import React, { useState } from 'react';
8
8
 
9
9
  import { Toolbar as NaturalToolbar, Select, useThemeContext, Tooltip } from '@dxos/react-ui';
10
10
  import { attentionSurface, mx, textBlockWidth } from '@dxos/react-ui-theme';
11
11
  import { withTheme } from '@dxos/storybook-utils';
12
12
 
13
+ import { createBasicExtensions, createThemeExtensions } from './extension-utils';
13
14
  import { useActionHandler } from './useActionHandler';
14
- import { createBasicExtensions, createThemeExtensions, useTextEditor } from './useTextEditor';
15
+ import { useTextEditor } from './useTextEditor';
15
16
  import { Toolbar } from '../components';
16
17
  import {
17
18
  type EditorMode,
18
19
  EditorModes,
19
20
  decorateMarkdown,
20
21
  createMarkdownExtensions,
21
- formatting,
22
- image,
22
+ formattingKeymap,
23
23
  table,
24
24
  useFormattingState,
25
+ image,
25
26
  } from '../extensions';
26
- import { markdownTheme } from '../themes';
27
27
  import translations from '../translations';
28
28
 
29
- // TODO(burdon): Demo toolbar with hooks.
30
- // TODO(burdon): Build components from hooks and adapters for model/extensions, etc.
31
- // TODO(burdon): Remove BaseTextEditor.
32
- // TODO(burdon): Move scrolling container layout/logic into TextEditor/MarkdownEditor components.
33
- // TODO(burdon): Define keymap in composer framework format.
34
-
35
29
  type StoryProps = {
36
30
  autoFocus?: boolean;
37
31
  placeholder?: string;
@@ -43,29 +37,24 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
43
37
  const { themeMode } = useThemeContext();
44
38
  const [formattingState, trackFormatting] = useFormattingState();
45
39
  const [editorMode, setEditorMode] = useState<EditorMode>('default');
46
- const extensions = useMemo(
47
- () => [
48
- editorMode ? EditorModes[editorMode] : [],
49
- createBasicExtensions({ readonly }),
50
- createThemeExtensions({
51
- themeMode,
52
- theme: markdownTheme,
53
- slots: {
54
- editor: { className: 'p-2' },
55
- },
56
- }),
57
- // TODO(burdon): Move lineWrapping.
58
- createMarkdownExtensions({ placeholder, lineWrapping: true }),
59
- // TODO(burdon): Move into markdown bundle (with React callbacks).
60
- decorateMarkdown(),
61
- formatting(),
62
- image(),
63
- table(),
64
- trackFormatting,
65
- ],
66
- [editorMode, themeMode, placeholder, trackFormatting, readonly],
40
+ const { parentRef, view } = useTextEditor(
41
+ () => ({
42
+ autoFocus,
43
+ doc,
44
+ extensions: [
45
+ editorMode ? EditorModes[editorMode] : [],
46
+ createBasicExtensions({ placeholder, lineWrapping: true, readonly }),
47
+ createMarkdownExtensions({ themeMode }),
48
+ createThemeExtensions({ themeMode }),
49
+ decorateMarkdown(),
50
+ formattingKeymap(),
51
+ image(),
52
+ table(),
53
+ trackFormatting,
54
+ ],
55
+ }),
56
+ [editorMode, themeMode, placeholder, readonly],
67
57
  );
68
- const { parentRef, view } = useTextEditor({ autoFocus, doc, extensions });
69
58
 
70
59
  const handleAction = useActionHandler(view);
71
60
 
@@ -116,7 +105,6 @@ const EditorModeToolbar = ({
116
105
 
117
106
  export default {
118
107
  title: 'react-ui-editor/useTextEditor',
119
- // component: TextEditor,
120
108
  decorators: [withTheme],
121
109
  render: (args: StoryProps) => (
122
110
  <Tooltip.Provider>
@@ -127,6 +115,23 @@ export default {
127
115
  };
128
116
 
129
117
  export const Default = {
118
+ render: () => {
119
+ const { parentRef } = useTextEditor();
120
+ return <div role='textbox' className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />;
121
+ },
122
+ };
123
+
124
+ export const Basic = {
125
+ render: () => {
126
+ const { themeMode } = useThemeContext();
127
+ const { parentRef } = useTextEditor(() => ({
128
+ extensions: [createBasicExtensions({ placeholder: 'Enter text...' }), createThemeExtensions({ themeMode })],
129
+ }));
130
+ return <div role='textbox' className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />;
131
+ },
132
+ };
133
+
134
+ export const Markdown = {
130
135
  args: {
131
136
  autoFocus: true,
132
137
  placeholder: 'Text...',