@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.1caa3af

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/dist/lib/browser/index.mjs +2174 -935
  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/MarkdownEditor.stories.d.ts +66 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +9 -4
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +12 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +10 -0
  11. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/extensions/{basic/bundle.d.ts → basic.d.ts} +3 -2
  13. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  15. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/code.d.ts +2 -0
  17. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +1 -0
  18. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +48 -0
  19. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  20. package/dist/types/src/components/TextEditor/extensions/hr.d.ts +2 -0
  21. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
  23. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/index.d.ts +14 -2
  25. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  26. package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
  27. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
  28. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +9 -0
  29. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
  30. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +10 -1
  31. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  32. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +36 -0
  33. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
  34. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
  35. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
  36. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
  37. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
  38. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +3 -0
  39. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +1 -0
  40. package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
  41. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
  42. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +3 -0
  43. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
  44. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
  45. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
  46. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts +10 -0
  47. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +1 -0
  48. package/dist/types/src/components/TextEditor/extensions/util.d.ts +5 -0
  49. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +1 -0
  50. package/dist/types/src/components/TextEditor/index.d.ts +2 -0
  51. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  52. package/dist/types/src/components/TextEditor/themes/default.d.ts +17 -0
  53. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
  54. package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
  55. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
  56. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +15 -0
  57. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  58. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  59. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +1 -1
  60. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  61. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  62. package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → hooks/automerge/semaphore.d.ts} +4 -1
  63. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
  64. package/dist/types/src/{automerge/automerge-plugin/codeMirrorToAm.d.ts → hooks/automerge/update-automerge.d.ts} +2 -3
  65. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
  66. package/dist/types/src/hooks/automerge/update-codemirror.d.ts +5 -0
  67. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
  68. package/dist/types/src/hooks/index.d.ts +0 -1
  69. package/dist/types/src/hooks/index.d.ts.map +1 -1
  70. package/dist/types/src/hooks/useTextModel.d.ts +19 -5
  71. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  72. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +2 -1
  73. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  74. package/dist/types/src/hooks/yjs/yjs.test.d.ts +2 -0
  75. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
  76. package/dist/types/src/index.d.ts +1 -0
  77. package/dist/types/src/index.d.ts.map +1 -1
  78. package/dist/types/src/styles/markdown.d.ts +14 -11
  79. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  80. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  81. package/package.json +28 -33
  82. package/src/components/TextEditor/MarkdownEditor.stories.tsx +428 -0
  83. package/src/components/TextEditor/TextEditor.stories.tsx +57 -43
  84. package/src/components/TextEditor/TextEditor.tsx +98 -52
  85. package/src/components/TextEditor/extensions/autocomplete.ts +59 -0
  86. package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +8 -4
  87. package/src/components/TextEditor/extensions/blast.ts +373 -0
  88. package/src/components/TextEditor/extensions/code.ts +99 -0
  89. package/src/components/TextEditor/extensions/comments.ts +388 -0
  90. package/src/components/TextEditor/extensions/hr.ts +74 -0
  91. package/src/components/TextEditor/extensions/image.ts +69 -0
  92. package/src/components/TextEditor/extensions/index.ts +14 -2
  93. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +75 -38
  94. package/src/components/TextEditor/extensions/{change.ts → listener.ts} +5 -4
  95. package/src/components/TextEditor/extensions/markdown/bundle.ts +56 -49
  96. package/src/components/TextEditor/extensions/markdown/highlight.ts +204 -0
  97. package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
  98. package/src/components/TextEditor/extensions/mention.ts +31 -0
  99. package/src/components/TextEditor/extensions/mermaid.ts +11 -0
  100. package/src/components/TextEditor/extensions/table.ts +132 -0
  101. package/src/components/TextEditor/extensions/tasklist.ts +121 -0
  102. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.ts} +9 -9
  103. package/src/components/TextEditor/extensions/typewriter.ts +68 -0
  104. package/src/components/TextEditor/extensions/util.ts +18 -0
  105. package/src/components/TextEditor/index.ts +3 -0
  106. package/src/components/TextEditor/themes/default.ts +302 -0
  107. package/src/components/TextEditor/themes/index.ts +5 -0
  108. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +7 -5
  109. package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +2 -1
  110. package/src/{automerge/automerge-plugin/PatchSemaphore.ts → hooks/automerge/semaphore.ts} +13 -9
  111. package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → hooks/automerge/update-automerge.ts} +5 -2
  112. package/src/{automerge/automerge-plugin/amToCodemirror.ts → hooks/automerge/update-codemirror.ts} +4 -1
  113. package/src/hooks/index.ts +0 -1
  114. package/src/hooks/useTextModel.ts +101 -42
  115. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +2 -1
  116. package/src/hooks/yjs/yjs.test.ts +59 -0
  117. package/src/index.ts +1 -0
  118. package/src/styles/markdown.ts +33 -24
  119. package/src/testing/replicator.ts +6 -6
  120. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  121. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
  122. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  123. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  124. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  125. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  126. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  127. package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
  128. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  129. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  130. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  131. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  132. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  133. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  134. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  135. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  136. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  137. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  138. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  139. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  140. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
  141. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  142. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  143. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  144. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  145. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  146. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  147. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  149. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  150. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  151. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  152. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  153. package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
  154. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  155. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  156. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  157. package/src/hooks/useCollaboration.ts +0 -45
  158. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  159. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  160. package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  161. package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +1 -1
@@ -7,23 +7,24 @@ import { EditorView } from '@codemirror/view';
7
7
  import { useFocusableGroup } from '@fluentui/react-tabster';
8
8
  import { vim } from '@replit/codemirror-vim';
9
9
  import defaultsDeep from 'lodash.defaultsdeep';
10
- import get from 'lodash.get';
11
10
  import React, {
11
+ type ComponentProps,
12
12
  type KeyboardEvent,
13
13
  forwardRef,
14
14
  useCallback,
15
15
  useEffect,
16
16
  useImperativeHandle,
17
17
  useState,
18
- type ComponentProps,
19
18
  } from 'react';
20
19
 
21
- import { isDocAccessor } from '@dxos/echo-schema';
20
+ import { generateName } from '@dxos/display-name';
22
21
  import { useThemeContext } from '@dxos/react-ui';
22
+ import { getColorForValue, inputSurface, mx } from '@dxos/react-ui-theme';
23
23
 
24
- import { basicBundle, basicTheme, markdownBundle, markdownTheme } from './extensions';
25
- import { type EditorModel, useCollaboration } from '../../hooks';
26
- import type { ThemeStyles } from '../../styles';
24
+ import { basicBundle, markdownBundle, setCommentRange } from './extensions';
25
+ import { defaultTheme, markdownTheme, textTheme } from './themes';
26
+ import { type CommentRange, type EditorModel } from '../../hooks';
27
+ import { type ThemeStyles } from '../../styles';
27
28
 
28
29
  export const EditorModes = ['default', 'vim'] as const;
29
30
  export type EditorMode = (typeof EditorModes)[number];
@@ -37,7 +38,7 @@ export type CursorInfo = {
37
38
  };
38
39
 
39
40
  export type TextEditorRef = {
40
- editor: HTMLDivElement | null;
41
+ root: HTMLDivElement | null;
41
42
  state?: EditorState;
42
43
  view?: EditorView;
43
44
  };
@@ -55,9 +56,11 @@ export type TextEditorSlots = {
55
56
 
56
57
  export type TextEditorProps = {
57
58
  model: EditorModel;
59
+ comments?: CommentRange[];
60
+ readonly?: boolean;
61
+ editorMode?: EditorMode;
58
62
  extensions?: Extension[];
59
63
  slots?: TextEditorSlots;
60
- editorMode?: EditorMode;
61
64
  };
62
65
 
63
66
  /**
@@ -65,66 +68,87 @@ export type TextEditorProps = {
65
68
  * NOTE: Rather than adding properties, try to create extensions that can be reused.
66
69
  */
67
70
  export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
68
- ({ model, extensions = [], slots, editorMode }, forwardedRef) => {
71
+ ({ model, comments, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
69
72
  const { themeMode } = useThemeContext();
70
73
  const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
74
+ const [root, setRoot] = useState<HTMLDivElement | null>(null);
75
+ const [{ state = undefined, view = undefined } = {}, setEditor] = useState<
76
+ { state?: EditorState; view?: EditorView } | undefined
77
+ >();
78
+ useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
79
+
80
+ // TODO(burdon): Factor out?
81
+ const { awareness, peer } = model;
82
+ useEffect(() => {
83
+ if (awareness && peer) {
84
+ awareness.setLocalStateField('user', {
85
+ name: peer.name ?? generateName(peer.id),
86
+ color: getColorForValue({ value: peer.id, type: 'color' }),
87
+ colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
88
+ });
89
+ }
90
+ }, [awareness, peer, themeMode]);
71
91
 
72
- const [parent, setParent] = useState<HTMLDivElement | null>(null);
73
- const [state, setState] = useState<EditorState>();
74
- const [view, setView] = useState<EditorView>();
75
- useImperativeHandle(
76
- forwardedRef,
77
- () => ({
78
- editor: parent,
79
- state,
80
- view,
81
- }),
82
- [view, state, parent],
83
- );
84
-
85
- // TODO(burdon): Pass in extension?
86
- const collaboration = useCollaboration(model, themeMode);
92
+ // TODO(burdon): Factor out as extension.
93
+ useEffect(() => {
94
+ if (view && comments?.length) {
95
+ view.dispatch({
96
+ effects: setCommentRange.of({ model, comments }),
97
+ });
98
+ }
99
+ }, [view, comments]);
87
100
 
88
101
  useEffect(() => {
89
- if (!parent) {
102
+ if (!root) {
90
103
  return;
91
104
  }
92
105
 
93
- const initialText = isDocAccessor(model.content)
94
- ? get(model.content.handle.docSync(), model.content.path)
95
- : model.content?.toString();
96
-
106
+ // TODO(burdon): Remember cursor position.
107
+ // https://codemirror.net/docs/ref/#state.EditorStateConfig
97
108
  const state = EditorState.create({
98
- doc: initialText,
109
+ doc: model.text(),
99
110
  extensions: [
100
- // TODO(burdon): Factor out VIM mode?
101
- editorMode === 'vim' && vim(),
111
+ readonly && EditorState.readOnly.of(readonly),
102
112
 
103
- // Replication.
104
- collaboration,
113
+ // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
114
+ editorMode === 'vim' && vim(),
105
115
 
106
116
  // Theme.
107
- slots?.editor?.theme && EditorView.theme(slots?.editor?.theme),
117
+ // TODO(burdon): Make theme configurable.
118
+ EditorView.baseTheme(defaultTheme),
119
+ EditorView.theme(slots?.editor?.theme ?? {}),
120
+ EditorView.darkTheme.of(themeMode === 'dark'),
121
+
122
+ // Storage and replication.
123
+ model.extension,
108
124
 
109
125
  // Custom.
110
126
  ...extensions,
111
127
  ].filter(Boolean) as Extension[],
112
128
  });
113
129
 
114
- setState(state);
115
-
116
130
  // NOTE: This repaints the editor.
117
131
  // If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
118
- // Ideally this should not be hit except when changing between text objects.
132
+ // Ideally this should not happen except when changing between text objects.
119
133
  view?.destroy();
120
- setView(new EditorView({ state, parent }));
134
+ setEditor({
135
+ state,
136
+ view: new EditorView({
137
+ state,
138
+ parent: root,
139
+ // NOTE: Uncomment to spy on all transactions.
140
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
141
+ // dispatch: (transaction, view) => {
142
+ // view.update([transaction]);
143
+ // },
144
+ }),
145
+ });
121
146
 
122
147
  return () => {
123
148
  view?.destroy();
124
- setView(undefined);
125
- setState(undefined);
149
+ setEditor(undefined);
126
150
  };
127
- }, [parent, model.content, themeMode, editorMode]);
151
+ }, [root, model, readonly, editorMode, themeMode]);
128
152
 
129
153
  const handleKeyUp = useCallback(
130
154
  (event: KeyboardEvent) => {
@@ -136,7 +160,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
136
160
  }
137
161
 
138
162
  case 'Escape': {
139
- editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
163
+ editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
140
164
  break;
141
165
  }
142
166
  }
@@ -147,7 +171,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
147
171
  return (
148
172
  <div
149
173
  key={model.id}
150
- ref={setParent}
174
+ ref={setRoot}
151
175
  tabIndex={0}
152
176
  {...slots?.root}
153
177
  {...(editorMode !== 'vim' && tabsterDOMAttribute)}
@@ -158,14 +182,15 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
158
182
  );
159
183
 
160
184
  export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
161
- ({ extensions: _extensions, slots, ...props }, forwardedRef) => {
185
+ ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
162
186
  const { themeMode } = useThemeContext();
163
- const extensions = [...(_extensions ?? []), basicBundle({ themeMode, placeholder: slots?.editor?.placeholder })];
187
+ const slots = defaultsDeep({}, _slots, defaultTextSlots);
164
188
  return (
165
189
  <BaseTextEditor
166
190
  ref={forwardedRef}
167
- extensions={extensions}
168
- slots={defaultsDeep({}, slots, { editor: { theme: basicTheme } })}
191
+ readonly={readonly}
192
+ extensions={[basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
193
+ slots={slots}
169
194
  {...props}
170
195
  />
171
196
  );
@@ -173,16 +198,37 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
173
198
  );
174
199
 
175
200
  export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
176
- ({ extensions: _extensions, slots, ...props }, forwardedRef) => {
201
+ ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
177
202
  const { themeMode } = useThemeContext();
178
- const extensions = [...(_extensions ?? []), markdownBundle({ themeMode })];
203
+ const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
179
204
  return (
180
205
  <BaseTextEditor
181
206
  ref={forwardedRef}
182
- extensions={extensions}
183
- slots={defaultsDeep({}, slots, { editor: { theme: markdownTheme } })}
207
+ readonly={readonly}
208
+ extensions={[markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
209
+ slots={slots}
184
210
  {...props}
185
211
  />
186
212
  );
187
213
  },
188
214
  );
215
+
216
+ export const defaultSlots: TextEditorSlots = {
217
+ root: {
218
+ className: mx('p-2', inputSurface),
219
+ },
220
+ };
221
+
222
+ export const defaultTextSlots: TextEditorSlots = {
223
+ ...defaultSlots,
224
+ editor: {
225
+ theme: textTheme,
226
+ },
227
+ };
228
+
229
+ export const defaultMarkdownSlots: TextEditorSlots = {
230
+ ...defaultSlots,
231
+ editor: {
232
+ theme: markdownTheme,
233
+ },
234
+ };
@@ -0,0 +1,59 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ // https://codemirror.net/examples/autocompletion
6
+ // https://codemirror.net/docs/ref/#autocomplete.autocompletion
7
+ // https://codemirror.net/docs/ref/#autocomplete.Completion
8
+
9
+ import {
10
+ autocompletion,
11
+ completionKeymap,
12
+ type Completion,
13
+ type CompletionContext,
14
+ type CompletionResult,
15
+ } from '@codemirror/autocomplete';
16
+ import { keymap } from '@codemirror/view';
17
+
18
+ export type AutocompleteResult = Completion;
19
+
20
+ export type AutocompleteOptions = {
21
+ onSearch: (text: string) => Completion[];
22
+ };
23
+
24
+ /**
25
+ * Autocomplete extension.
26
+ */
27
+ export const autocomplete = ({ onSearch }: AutocompleteOptions) => {
28
+ return [
29
+ // https://codemirror.net/docs/ref/#view.keymap
30
+ // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
31
+ keymap.of(completionKeymap),
32
+
33
+ // https://codemirror.net/examples/autocompletion
34
+ // https://codemirror.net/docs/ref/#autocomplete.autocompletion
35
+ autocompletion({
36
+ // TODO(burdon): Set custom keymap.
37
+ // defaultKeymap: false,
38
+
39
+ // Don't start unless key press.
40
+ activateOnTyping: false,
41
+
42
+ // TODO(burdon): Option to create new page?
43
+ // TODO(burdon): Optional decoration via addToOptions
44
+ override: [
45
+ (context: CompletionContext): CompletionResult | null => {
46
+ const match = context.matchBefore(/\w*/);
47
+ if (!match || (match.from === match.to && !context.explicit)) {
48
+ return null;
49
+ }
50
+
51
+ return {
52
+ from: match.from,
53
+ options: onSearch(match.text.toLowerCase()),
54
+ };
55
+ },
56
+ ],
57
+ }),
58
+ ];
59
+ };
@@ -6,23 +6,27 @@ import { closeBrackets } from '@codemirror/autocomplete';
6
6
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
7
7
  import { type Extension } from '@codemirror/state';
8
8
  import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
9
- import { EditorView, placeholder } from '@codemirror/view';
9
+ import { drawSelection, EditorView, placeholder } from '@codemirror/view';
10
10
 
11
11
  import type { ThemeMode } from '@dxos/react-ui';
12
12
 
13
13
  export type BasicBundleOptions = {
14
+ readonly?: boolean;
14
15
  themeMode?: ThemeMode;
15
16
  placeholder?: string;
16
17
  };
17
18
 
18
- export const basicBundle = ({ themeMode, placeholder: _placeholder }: BasicBundleOptions): Extension[] =>
19
+ export const basicBundle = ({ readonly, themeMode, placeholder: _placeholder }: BasicBundleOptions): Extension[] =>
19
20
  [
21
+ EditorView.lineWrapping,
22
+
23
+ // TODO(burdon): Compare with minimalSetup.
24
+ // https://codemirror.net/docs/ref/#codemirror.minimalSetup
20
25
  bracketMatching(),
21
26
  closeBrackets(),
27
+ drawSelection(),
22
28
  _placeholder && placeholder(_placeholder),
23
29
 
24
- EditorView.lineWrapping,
25
-
26
30
  // TODO(burdon): Is this required?
27
31
  themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
28
32
  ].filter(Boolean) as Extension[];