@dxos/react-ui-editor 0.3.10-main.cb1d9e2 → 0.3.10-main.cbe7692

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 (80) hide show
  1. package/dist/lib/browser/index.mjs +704 -135
  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 +19 -1
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -1
  9. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +2 -1
  10. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  12. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  13. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +2 -1
  14. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
  16. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
  17. package/dist/types/src/components/TextEditor/extensions/index.d.ts +4 -0
  18. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  19. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -1
  20. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -1
  21. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +2 -1
  22. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  23. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +6 -2
  24. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
  25. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
  26. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
  27. package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
  28. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
  29. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +2 -1
  30. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -1
  31. package/dist/types/src/components/TextEditor/themes/default.d.ts +5 -0
  32. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
  33. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts +3 -0
  34. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -1
  35. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +8 -1
  36. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -1
  37. package/dist/types/src/hooks/automerge/index.d.ts +1 -1
  38. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -1
  39. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -1
  40. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +5 -0
  41. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +1 -0
  42. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +6 -0
  43. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +1 -0
  44. package/dist/types/src/hooks/automerge/translation/index.d.ts +3 -0
  45. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +1 -0
  46. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +1 -1
  47. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  48. package/dist/types/src/styles/markdown.d.ts +3 -2
  49. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  50. package/package.json +20 -20
  51. package/src/components/TextEditor/MarkdownEditor.stories.tsx +98 -25
  52. package/src/components/TextEditor/TextEditor.tsx +16 -7
  53. package/src/components/TextEditor/extensions/autocomplete.ts +5 -20
  54. package/src/components/TextEditor/extensions/basic.ts +2 -1
  55. package/src/components/TextEditor/extensions/blast.ts +375 -0
  56. package/src/components/TextEditor/extensions/demo.ts +4 -1
  57. package/src/components/TextEditor/extensions/image.ts +68 -0
  58. package/src/components/TextEditor/extensions/index.ts +4 -0
  59. package/src/components/TextEditor/extensions/link.ts +15 -23
  60. package/src/components/TextEditor/extensions/listener.ts +3 -3
  61. package/src/components/TextEditor/extensions/markdown/bundle.ts +19 -11
  62. package/src/components/TextEditor/extensions/markdown/highlight.ts +118 -104
  63. package/src/components/TextEditor/extensions/mention.ts +31 -0
  64. package/src/components/TextEditor/extensions/table.ts +123 -0
  65. package/src/components/TextEditor/extensions/tasklist.ts +34 -20
  66. package/src/components/TextEditor/themes/default.ts +54 -2
  67. package/src/hooks/automerge/PatchSemaphore.ts +12 -9
  68. package/src/hooks/automerge/automerge.stories.tsx +3 -2
  69. package/src/hooks/automerge/index.ts +1 -1
  70. package/src/hooks/automerge/plugin.ts +1 -0
  71. package/src/hooks/automerge/{amToCodemirror.ts → translation/automerge-to-codemirror.ts} +10 -2
  72. package/src/hooks/automerge/{codeMirrorToAm.ts → translation/codemirror-to-automerge.ts} +7 -4
  73. package/src/hooks/automerge/translation/index.ts +6 -0
  74. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
  75. package/src/styles/markdown.ts +14 -8
  76. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  77. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts +0 -6
  78. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +0 -1
  79. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts +0 -7
  80. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts.map +0 -1
@@ -1,5 +1,6 @@
1
1
  export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
2
- export declare const heading: Record<HeadingLevel, string>;
2
+ export declare const headings: Record<HeadingLevel, string[]>;
3
+ export declare const heading: (level: HeadingLevel, readonly?: boolean) => string;
3
4
  export declare const text = "text-neutral-800 dark:text-neutral-200";
4
5
  export declare const light = "text-neutral-200 dark:text-neutral-800";
5
6
  export declare const mark: string;
@@ -12,7 +13,7 @@ export declare const codeMark = "font-mono text-primary-500";
12
13
  export declare const codeBlock = "mlb-2 font-mono bg-neutral-500/10 p-3 rounded";
13
14
  export declare const inlineUrl: string;
14
15
  export declare const blockquote: string;
15
- export declare const horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-500/50";
16
+ export declare const horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800";
16
17
  export declare const unorderedList = "mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[attr(marker)] [&>li:before]:mlb-1 [&>li:before]:mie-2";
17
18
  export declare const orderedList = "mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[counters(section,_\".\")_\"._\"] [counter-reset:section] [&>li:before]:mlb-1";
18
19
  export declare const listItem = "contents before:[counter-increment:section]";
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../../src/styles/markdown.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAGjD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAOhD,CAAC;AAEF,eAAO,MAAM,IAAI,2CAA2C,CAAC;AAC7D,eAAO,MAAM,KAAK,2CAA2C,CAAC;AAE9D,eAAO,MAAM,IAAI,QAAmE,CAAC;AAErF,eAAO,MAAM,SAAS,UAAU,CAAC;AAEjC,eAAO,MAAM,IAAI,cAAc,CAAC;AAChC,eAAO,MAAM,MAAM,WAAW,CAAC;AAC/B,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAE5C,eAAO,MAAM,IAAI,mEAAmE,CAAC;AACrF,eAAO,MAAM,QAAQ,+BAA+B,CAAC;AACrD,eAAO,MAAM,SAAS,kDAAkD,CAAC;AAEzE,eAAO,MAAM,SAAS,QAAmB,CAAC;AAG1C,eAAO,MAAM,UAAU,QAAsF,CAAC;AAG9G,eAAO,MAAM,cAAc,qFAAqF,CAAC;AAGjH,eAAO,MAAM,aAAa,wHAC6F,CAAC;AACxH,eAAO,MAAM,WAAW,+IACkH,CAAC;AAE3I,eAAO,MAAM,QAAQ,gDAAgD,CAAC"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../../src/styles/markdown.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAGjD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,CAOnD,CAAC;AAEF,eAAO,MAAM,OAAO,UAAW,YAAY,aAAa,OAAO,WAG9D,CAAC;AAEF,eAAO,MAAM,IAAI,2CAA2C,CAAC;AAC7D,eAAO,MAAM,KAAK,2CAA2C,CAAC;AAE9D,eAAO,MAAM,IAAI,QAAmE,CAAC;AAErF,eAAO,MAAM,SAAS,UAAU,CAAC;AAEjC,eAAO,MAAM,IAAI,cAAc,CAAC;AAChC,eAAO,MAAM,MAAM,WAAW,CAAC;AAC/B,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAE5C,eAAO,MAAM,IAAI,mEAAmE,CAAC;AACrF,eAAO,MAAM,QAAQ,+BAA+B,CAAC;AACrD,eAAO,MAAM,SAAS,kDAAkD,CAAC;AAEzE,eAAO,MAAM,SAAS,QAAmB,CAAC;AAG1C,eAAO,MAAM,UAAU,QAAsF,CAAC;AAG9G,eAAO,MAAM,cAAc,0GAC8E,CAAC;AAG1G,eAAO,MAAM,aAAa,wHAC6F,CAAC;AACxH,eAAO,MAAM,WAAW,+IACkH,CAAC;AAE3I,eAAO,MAAM,QAAQ,gDAAgD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.3.10-main.cb1d9e2",
3
+ "version": "0.3.10-main.cbe7692",
4
4
  "description": "Document editing experience within a DXOS shell.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -36,19 +36,19 @@
36
36
  "y-codemirror.next": "^0.3.2",
37
37
  "y-protocols": "^1.0.5",
38
38
  "yjs": "^13.6.10",
39
- "@dxos/async": "0.3.10-main.cb1d9e2",
40
- "@dxos/automerge": "0.3.10-main.cb1d9e2",
41
- "@dxos/display-name": "0.3.10-main.cb1d9e2",
42
- "@dxos/debug": "0.3.10-main.cb1d9e2",
43
- "@dxos/echo-schema": "0.3.10-main.cb1d9e2",
44
- "@dxos/invariant": "0.3.10-main.cb1d9e2",
45
- "@dxos/protocols": "0.3.10-main.cb1d9e2",
46
- "@dxos/log": "0.3.10-main.cb1d9e2",
47
- "@dxos/react-async": "0.3.10-main.cb1d9e2",
48
- "@dxos/react-ui-theme": "0.3.10-main.cb1d9e2",
49
- "@dxos/text-model": "0.3.10-main.cb1d9e2",
50
- "@dxos/util": "0.3.10-main.cb1d9e2",
51
- "@dxos/react-ui": "0.3.10-main.cb1d9e2"
39
+ "@dxos/async": "0.3.10-main.cbe7692",
40
+ "@dxos/automerge": "0.3.10-main.cbe7692",
41
+ "@dxos/debug": "0.3.10-main.cbe7692",
42
+ "@dxos/display-name": "0.3.10-main.cbe7692",
43
+ "@dxos/invariant": "0.3.10-main.cbe7692",
44
+ "@dxos/log": "0.3.10-main.cbe7692",
45
+ "@dxos/echo-schema": "0.3.10-main.cbe7692",
46
+ "@dxos/protocols": "0.3.10-main.cbe7692",
47
+ "@dxos/react-async": "0.3.10-main.cbe7692",
48
+ "@dxos/react-ui": "0.3.10-main.cbe7692",
49
+ "@dxos/react-ui-theme": "0.3.10-main.cbe7692",
50
+ "@dxos/text-model": "0.3.10-main.cbe7692",
51
+ "@dxos/util": "0.3.10-main.cbe7692"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@automerge/automerge-repo-network-broadcastchannel": "^1.0.19",
@@ -61,17 +61,17 @@
61
61
  "@types/react-dom": "^18.0.6",
62
62
  "react": "^18.2.0",
63
63
  "react-dom": "^18.2.0",
64
- "@dxos/echo-typegen": "0.3.10-main.cb1d9e2",
65
- "@dxos/react-client": "0.3.10-main.cb1d9e2",
66
- "@dxos/react-ui": "0.3.10-main.cb1d9e2",
67
- "@dxos/storybook-utils": "0.3.10-main.cb1d9e2",
68
- "@dxos/config": "0.3.10-main.cb1d9e2"
64
+ "@dxos/config": "0.3.10-main.cbe7692",
65
+ "@dxos/react-client": "0.3.10-main.cbe7692",
66
+ "@dxos/react-ui": "0.3.10-main.cbe7692",
67
+ "@dxos/echo-typegen": "0.3.10-main.cbe7692",
68
+ "@dxos/storybook-utils": "0.3.10-main.cbe7692"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@phosphor-icons/react": "^2.0.5",
72
72
  "react": "^18.2.0",
73
73
  "react-dom": "^18.2.0",
74
- "@dxos/react-client": "0.3.10-main.cb1d9e2"
74
+ "@dxos/react-client": "0.3.10-main.cbe7692"
75
75
  },
76
76
  "publishConfig": {
77
77
  "access": "public"
@@ -3,9 +3,11 @@
3
3
  //
4
4
 
5
5
  import '@dxosTheme';
6
+
6
7
  import { EditorView } from '@codemirror/view';
7
8
  import { faker } from '@faker-js/faker';
8
9
  import { ArrowSquareOut } from '@phosphor-icons/react';
10
+ import defaultsDeep from 'lodash.defaultsdeep';
9
11
  import React, { StrictMode, useRef, useState } from 'react';
10
12
  import { createRoot } from 'react-dom/client';
11
13
 
@@ -24,18 +26,19 @@ import {
24
26
  type TooltipOptions,
25
27
  type LinkOptions,
26
28
  comments,
29
+ table,
30
+ image,
31
+ mention,
32
+ blast,
33
+ demo,
34
+ defaultOptions,
27
35
  } from './extensions';
28
36
  import { useTextModel } from '../../hooks';
29
37
 
30
- // TODO(burdon): Read-only render mode (presentation).
31
- // TODO(burdon): Slides.
32
- // TODO(burdon): Autocomplete.
33
- // TODO(burdon): Block quote.
34
- // TODO(burdon): Images.
35
- // TODO(burdon): Tables.
36
-
37
38
  const str = (...lines: string[]) => lines.join('\n');
38
39
 
40
+ const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
41
+
39
42
  // prettier-ignore
40
43
  const text = {
41
44
  tasks: str(
@@ -77,6 +80,7 @@ const text = {
77
80
  ' const x = 100;',
78
81
  ' return () => <div>Test</div>;',
79
82
  '};',
83
+ '',
80
84
  '```'
81
85
  ),
82
86
 
@@ -93,7 +97,20 @@ const text = {
93
97
  ...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
94
98
  ),
95
99
 
96
- paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat())
100
+ paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
101
+
102
+ table: str(
103
+ '# Table',
104
+ '',
105
+ `| ${faker.lorem.word()} | ${faker.lorem.word()} | ${faker.lorem.word()} |`,
106
+ '|---|---|---|',
107
+ `| ${num()} | ${num()} | ${num()} |`,
108
+ `| ${num()} | ${num()} | ${num()} |`,
109
+ `| ${num()} | ${num()} | ${num()} |`,
110
+ '', // TODO(burdon): Possible GFM parsing bug if no newline?
111
+ ),
112
+
113
+ image: str('# Image', '', '![dxos](https://pbs.twimg.com/profile_banners/1268328127673044992/1684766689/1500x500)'),
97
114
  };
98
115
 
99
116
  const document = str(
@@ -103,9 +120,7 @@ const document = str(
103
120
  '',
104
121
  'This is all about https://dxos.org and related technologies.',
105
122
  '',
106
- 'This this is **bold**, __underlined__, _italic_, and `f(INLINE)`.',
107
- '',
108
- '__NOTE__: Fenced code uses the base font.',
123
+ 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.',
109
124
  '',
110
125
  '---',
111
126
  text.links,
@@ -119,6 +134,10 @@ const document = str(
119
134
  text.code,
120
135
  '---',
121
136
  text.headings,
137
+ '---',
138
+ text.table,
139
+ '---',
140
+ text.image,
122
141
  );
123
142
 
124
143
  const links = [
@@ -158,7 +177,7 @@ const Story = ({
158
177
  text,
159
178
  automerge,
160
179
  ...props
161
- }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
180
+ }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'readonly' | 'extensions' | 'slots'>) => {
162
181
  const ref = useRef<TextEditorRef>(null);
163
182
  const [item] = useState({ text: new TextObject(text, undefined, undefined, { useAutomergeBackend: automerge }) });
164
183
  const model = useTextModel({ text: item.text });
@@ -185,20 +204,23 @@ export default {
185
204
  render: Story,
186
205
  };
187
206
 
207
+ const extensions = [
208
+ link({ onRender }),
209
+ tooltip({ onHover }),
210
+ tasklist(),
211
+ table(),
212
+ image(),
213
+ autocomplete({
214
+ onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
215
+ }),
216
+ ];
217
+
188
218
  export const Default = {
189
- render: () => (
190
- <Story
191
- text={document}
192
- extensions={[
193
- link({ onRender }),
194
- tooltip({ onHover }),
195
- tasklist(),
196
- autocomplete({
197
- onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
198
- }),
199
- ]}
200
- />
201
- ),
219
+ render: () => <Story text={document} extensions={extensions} />,
220
+ };
221
+
222
+ export const Readonly = {
223
+ render: () => <Story text={document} extensions={extensions} readonly />,
202
224
  };
203
225
 
204
226
  export const Simple = {
@@ -213,6 +235,16 @@ export const Links = {
213
235
  render: () => <Story text={text.links} extensions={[link({ onRender })]} />,
214
236
  };
215
237
 
238
+ export const Table = {
239
+ render: () => (
240
+ <Story text={str('This is a table', '', text.table, '', text.table, '', '')} readonly extensions={[table()]} />
241
+ ),
242
+ };
243
+
244
+ export const Image = {
245
+ render: () => <Story text={str('This is an image', '', text.image, '', '')} readonly extensions={[image()]} />,
246
+ };
247
+
216
248
  export const TaskList = {
217
249
  render: () => (
218
250
  <Story
@@ -243,6 +275,21 @@ export const Autocomplete = {
243
275
  ),
244
276
  };
245
277
 
278
+ const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
279
+
280
+ export const Mention = {
281
+ render: () => (
282
+ <Story
283
+ text={str('# Mention', '', '', '', '', '', '')}
284
+ extensions={[
285
+ mention({
286
+ onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
287
+ }),
288
+ ]}
289
+ />
290
+ ),
291
+ };
292
+
246
293
  const mark = () => `[^${PublicKey.random().toHex()}]`;
247
294
  export const Comments = {
248
295
  render: () => (
@@ -273,3 +320,29 @@ export const Diagnostics = {
273
320
  />
274
321
  ),
275
322
  };
323
+
324
+ export const Demo = {
325
+ render: () => <Story text={text.paragraphs} extensions={[demo()]} />,
326
+ };
327
+
328
+ export const Blast = {
329
+ render: () => (
330
+ <Story
331
+ text={str(text.paragraphs, text.code, text.paragraphs)}
332
+ extensions={[
333
+ blast(
334
+ defaultsDeep(
335
+ {
336
+ effect: 2,
337
+ particleGravity: 0.2,
338
+ particleShrinkRate: 0.995,
339
+ color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
340
+ // color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
341
+ },
342
+ defaultOptions,
343
+ ),
344
+ ),
345
+ ]}
346
+ />
347
+ ),
348
+ };
@@ -56,9 +56,10 @@ export type TextEditorSlots = {
56
56
 
57
57
  export type TextEditorProps = {
58
58
  model: EditorModel;
59
+ readonly?: boolean;
60
+ editorMode?: EditorMode;
59
61
  extensions?: Extension[];
60
62
  slots?: TextEditorSlots;
61
- editorMode?: EditorMode;
62
63
  };
63
64
 
64
65
  /**
@@ -66,7 +67,7 @@ export type TextEditorProps = {
66
67
  * NOTE: Rather than adding properties, try to create extensions that can be reused.
67
68
  */
68
69
  export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
69
- ({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
70
+ ({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
70
71
  const { themeMode } = useThemeContext();
71
72
  const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
72
73
 
@@ -95,6 +96,8 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
95
96
  const state = EditorState.create({
96
97
  doc: model.text(),
97
98
  extensions: [
99
+ readonly && EditorState.readOnly.of(readonly),
100
+
98
101
  // TODO(burdon): Factor out VIM mode?
99
102
  editorMode === 'vim' && vim(),
100
103
 
@@ -124,7 +127,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
124
127
  setView(undefined);
125
128
  setState(undefined);
126
129
  };
127
- }, [root, model, themeMode, editorMode]);
130
+ }, [root, model, themeMode, readonly, editorMode]);
128
131
 
129
132
  const handleKeyUp = useCallback(
130
133
  (event: KeyboardEvent) => {
@@ -169,13 +172,18 @@ const maybeDebug = (): Extension => {
169
172
  };
170
173
 
171
174
  export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
172
- ({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
175
+ ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
173
176
  const { themeMode } = useThemeContext();
174
177
  const slots = defaultsDeep({}, _slots, defaultTextSlots);
175
178
  return (
176
179
  <BaseTextEditor
177
180
  ref={forwardedRef}
178
- extensions={[basicBundle({ themeMode, placeholder: slots?.editor?.placeholder }), maybeDebug(), ...extensions]}
181
+ readonly={readonly}
182
+ extensions={[
183
+ basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }),
184
+ maybeDebug(),
185
+ ...extensions,
186
+ ]}
179
187
  slots={slots}
180
188
  {...props}
181
189
  />
@@ -184,14 +192,15 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
184
192
  );
185
193
 
186
194
  export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
187
- ({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
195
+ ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
188
196
  const { themeMode } = useThemeContext();
189
197
  const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
190
198
  return (
191
199
  <BaseTextEditor
192
200
  ref={forwardedRef}
201
+ readonly={readonly}
193
202
  extensions={[
194
- markdownBundle({ themeMode, placeholder: slots?.editor?.placeholder }),
203
+ markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }),
195
204
  maybeDebug(),
196
205
  ...extensions,
197
206
  ]}
@@ -2,23 +2,15 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- // TODO(burdon): Automcomplete: https://codemirror.net/5/doc/manual.html#addon_runmode
6
- // TODO(burdon): Modes: parallel parsing and decoration (e.g., associated with language).
7
- // TODO(burdon): Add-on: runmode: run lexer over content (with rendering codemirror).
8
- // https://codemirror.net/5/doc/manual.html#addon_runmode
9
- // TODO(burdon): Add-on: dialog.
10
- // TODO(burdon): Comments: https://codemirror.net/5/doc/manual.html#setBookmark
11
- // TODO(burdon): Split view: https://codemirror.net/examples/split
12
-
13
5
  // https://codemirror.net/examples/autocompletion
14
6
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
15
7
  // https://codemirror.net/docs/ref/#autocomplete.Completion
16
8
 
17
9
  import {
18
10
  autocompletion,
11
+ completionKeymap,
19
12
  type Completion,
20
13
  type CompletionContext,
21
- completionKeymap,
22
14
  type CompletionResult,
23
15
  } from '@codemirror/autocomplete';
24
16
  import { keymap } from '@codemirror/view';
@@ -51,21 +43,14 @@ export const autocomplete = ({ onSearch }: AutocompleteOptions) => {
51
43
  // TODO(burdon): Optional decoration via addToOptions
52
44
  override: [
53
45
  (context: CompletionContext): CompletionResult | null => {
54
- const word = context.matchBefore(/\w*/);
55
- if (!word || (word.from === word.to && !context.explicit)) {
46
+ const match = context.matchBefore(/\w*/);
47
+ if (!match || (match.from === match.to && !context.explicit)) {
56
48
  return null;
57
49
  }
58
50
 
59
- // TODO(burdon): Option to convert to links?
60
51
  return {
61
- from: word.from,
62
- options: onSearch(word.text.toLowerCase()),
63
- // options: [
64
- // { label: 'apple', type: 'keyword' },
65
- // { label: 'amazon', type: 'keyword' },
66
- // { label: 'hello', type: 'variable', info: '(World)' },
67
- // { label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
68
- // ],
52
+ from: match.from,
53
+ options: onSearch(match.text.toLowerCase()),
69
54
  };
70
55
  },
71
56
  ],
@@ -11,11 +11,12 @@ import { EditorView, placeholder } from '@codemirror/view';
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
  [
20
21
  // TODO(burdon): Compare with minimalSetup.
21
22
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup