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

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 (56) hide show
  1. package/dist/lib/browser/index.mjs +123 -689
  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 +1 -19
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +1 -2
  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 +1 -2
  10. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +1 -2
  12. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -4
  14. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -1
  16. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -1
  17. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +1 -2
  18. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  19. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +2 -6
  20. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
  21. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +1 -2
  22. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -1
  23. package/dist/types/src/components/TextEditor/themes/default.d.ts +0 -5
  24. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
  25. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  26. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  27. package/dist/types/src/styles/markdown.d.ts +2 -3
  28. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  29. package/package.json +20 -20
  30. package/src/components/TextEditor/MarkdownEditor.stories.tsx +25 -98
  31. package/src/components/TextEditor/TextEditor.tsx +7 -16
  32. package/src/components/TextEditor/extensions/autocomplete.ts +20 -5
  33. package/src/components/TextEditor/extensions/basic.ts +1 -2
  34. package/src/components/TextEditor/extensions/demo.ts +1 -4
  35. package/src/components/TextEditor/extensions/index.ts +0 -4
  36. package/src/components/TextEditor/extensions/link.ts +23 -15
  37. package/src/components/TextEditor/extensions/listener.ts +3 -3
  38. package/src/components/TextEditor/extensions/markdown/bundle.ts +5 -10
  39. package/src/components/TextEditor/extensions/markdown/highlight.ts +104 -118
  40. package/src/components/TextEditor/extensions/tasklist.ts +20 -34
  41. package/src/components/TextEditor/themes/default.ts +2 -54
  42. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  43. package/src/styles/markdown.ts +8 -14
  44. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +0 -25
  45. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  46. package/dist/types/src/components/TextEditor/extensions/image.d.ts +0 -4
  47. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  48. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +0 -6
  49. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  50. package/dist/types/src/components/TextEditor/extensions/table.d.ts +0 -8
  51. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  52. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  53. package/src/components/TextEditor/extensions/blast.ts +0 -375
  54. package/src/components/TextEditor/extensions/image.ts +0 -68
  55. package/src/components/TextEditor/extensions/mention.ts +0 -31
  56. package/src/components/TextEditor/extensions/table.ts +0 -123
@@ -3,11 +3,9 @@
3
3
  //
4
4
 
5
5
  import '@dxosTheme';
6
-
7
6
  import { EditorView } from '@codemirror/view';
8
7
  import { faker } from '@faker-js/faker';
9
8
  import { ArrowSquareOut } from '@phosphor-icons/react';
10
- import defaultsDeep from 'lodash.defaultsdeep';
11
9
  import React, { StrictMode, useRef, useState } from 'react';
12
10
  import { createRoot } from 'react-dom/client';
13
11
 
@@ -26,18 +24,17 @@ import {
26
24
  type TooltipOptions,
27
25
  type LinkOptions,
28
26
  comments,
29
- table,
30
- image,
31
- mention,
32
- blast,
33
- demo,
34
- defaultOptions,
35
27
  } from './extensions';
36
28
  import { useTextModel } from '../../hooks';
37
29
 
38
- const str = (...lines: string[]) => lines.join('\n');
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.
39
36
 
40
- const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
37
+ const str = (...lines: string[]) => lines.join('\n');
41
38
 
42
39
  // prettier-ignore
43
40
  const text = {
@@ -80,7 +77,6 @@ const text = {
80
77
  ' const x = 100;',
81
78
  ' return () => <div>Test</div>;',
82
79
  '};',
83
- '',
84
80
  '```'
85
81
  ),
86
82
 
@@ -97,20 +93,7 @@ const text = {
97
93
  ...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
98
94
  ),
99
95
 
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)'),
96
+ paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat())
114
97
  };
115
98
 
116
99
  const document = str(
@@ -120,7 +103,9 @@ const document = str(
120
103
  '',
121
104
  'This is all about https://dxos.org and related technologies.',
122
105
  '',
123
- 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.',
106
+ 'This this is **bold**, __underlined__, _italic_, and `f(INLINE)`.',
107
+ '',
108
+ '__NOTE__: Fenced code uses the base font.',
124
109
  '',
125
110
  '---',
126
111
  text.links,
@@ -134,10 +119,6 @@ const document = str(
134
119
  text.code,
135
120
  '---',
136
121
  text.headings,
137
- '---',
138
- text.table,
139
- '---',
140
- text.image,
141
122
  );
142
123
 
143
124
  const links = [
@@ -177,7 +158,7 @@ const Story = ({
177
158
  text,
178
159
  automerge,
179
160
  ...props
180
- }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'readonly' | 'extensions' | 'slots'>) => {
161
+ }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
181
162
  const ref = useRef<TextEditorRef>(null);
182
163
  const [item] = useState({ text: new TextObject(text, undefined, undefined, { useAutomergeBackend: automerge }) });
183
164
  const model = useTextModel({ text: item.text });
@@ -204,23 +185,20 @@ export default {
204
185
  render: Story,
205
186
  };
206
187
 
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
-
218
188
  export const Default = {
219
- render: () => <Story text={document} extensions={extensions} />,
220
- };
221
-
222
- export const Readonly = {
223
- render: () => <Story text={document} extensions={extensions} readonly />,
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
+ ),
224
202
  };
225
203
 
226
204
  export const Simple = {
@@ -235,16 +213,6 @@ export const Links = {
235
213
  render: () => <Story text={text.links} extensions={[link({ onRender })]} />,
236
214
  };
237
215
 
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
-
248
216
  export const TaskList = {
249
217
  render: () => (
250
218
  <Story
@@ -275,21 +243,6 @@ export const Autocomplete = {
275
243
  ),
276
244
  };
277
245
 
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
-
293
246
  const mark = () => `[^${PublicKey.random().toHex()}]`;
294
247
  export const Comments = {
295
248
  render: () => (
@@ -320,29 +273,3 @@ export const Diagnostics = {
320
273
  />
321
274
  ),
322
275
  };
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,10 +56,9 @@ export type TextEditorSlots = {
56
56
 
57
57
  export type TextEditorProps = {
58
58
  model: EditorModel;
59
- readonly?: boolean;
60
- editorMode?: EditorMode;
61
59
  extensions?: Extension[];
62
60
  slots?: TextEditorSlots;
61
+ editorMode?: EditorMode;
63
62
  };
64
63
 
65
64
  /**
@@ -67,7 +66,7 @@ export type TextEditorProps = {
67
66
  * NOTE: Rather than adding properties, try to create extensions that can be reused.
68
67
  */
69
68
  export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
70
- ({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
69
+ ({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
71
70
  const { themeMode } = useThemeContext();
72
71
  const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
73
72
 
@@ -96,8 +95,6 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
96
95
  const state = EditorState.create({
97
96
  doc: model.text(),
98
97
  extensions: [
99
- readonly && EditorState.readOnly.of(readonly),
100
-
101
98
  // TODO(burdon): Factor out VIM mode?
102
99
  editorMode === 'vim' && vim(),
103
100
 
@@ -127,7 +124,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
127
124
  setView(undefined);
128
125
  setState(undefined);
129
126
  };
130
- }, [root, model, themeMode, readonly, editorMode]);
127
+ }, [root, model, themeMode, editorMode]);
131
128
 
132
129
  const handleKeyUp = useCallback(
133
130
  (event: KeyboardEvent) => {
@@ -172,18 +169,13 @@ const maybeDebug = (): Extension => {
172
169
  };
173
170
 
174
171
  export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
175
- ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
172
+ ({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
176
173
  const { themeMode } = useThemeContext();
177
174
  const slots = defaultsDeep({}, _slots, defaultTextSlots);
178
175
  return (
179
176
  <BaseTextEditor
180
177
  ref={forwardedRef}
181
- readonly={readonly}
182
- extensions={[
183
- basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }),
184
- maybeDebug(),
185
- ...extensions,
186
- ]}
178
+ extensions={[basicBundle({ themeMode, placeholder: slots?.editor?.placeholder }), maybeDebug(), ...extensions]}
187
179
  slots={slots}
188
180
  {...props}
189
181
  />
@@ -192,15 +184,14 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
192
184
  );
193
185
 
194
186
  export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
195
- ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
187
+ ({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
196
188
  const { themeMode } = useThemeContext();
197
189
  const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
198
190
  return (
199
191
  <BaseTextEditor
200
192
  ref={forwardedRef}
201
- readonly={readonly}
202
193
  extensions={[
203
- markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }),
194
+ markdownBundle({ themeMode, placeholder: slots?.editor?.placeholder }),
204
195
  maybeDebug(),
205
196
  ...extensions,
206
197
  ]}
@@ -2,15 +2,23 @@
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
+
5
13
  // https://codemirror.net/examples/autocompletion
6
14
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
7
15
  // https://codemirror.net/docs/ref/#autocomplete.Completion
8
16
 
9
17
  import {
10
18
  autocompletion,
11
- completionKeymap,
12
19
  type Completion,
13
20
  type CompletionContext,
21
+ completionKeymap,
14
22
  type CompletionResult,
15
23
  } from '@codemirror/autocomplete';
16
24
  import { keymap } from '@codemirror/view';
@@ -43,14 +51,21 @@ export const autocomplete = ({ onSearch }: AutocompleteOptions) => {
43
51
  // TODO(burdon): Optional decoration via addToOptions
44
52
  override: [
45
53
  (context: CompletionContext): CompletionResult | null => {
46
- const match = context.matchBefore(/\w*/);
47
- if (!match || (match.from === match.to && !context.explicit)) {
54
+ const word = context.matchBefore(/\w*/);
55
+ if (!word || (word.from === word.to && !context.explicit)) {
48
56
  return null;
49
57
  }
50
58
 
59
+ // TODO(burdon): Option to convert to links?
51
60
  return {
52
- from: match.from,
53
- options: onSearch(match.text.toLowerCase()),
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
+ // ],
54
69
  };
55
70
  },
56
71
  ],
@@ -11,12 +11,11 @@ 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;
15
14
  themeMode?: ThemeMode;
16
15
  placeholder?: string;
17
16
  };
18
17
 
19
- export const basicBundle = ({ readonly, themeMode, placeholder: _placeholder }: BasicBundleOptions): Extension[] =>
18
+ export const basicBundle = ({ themeMode, placeholder: _placeholder }: BasicBundleOptions): Extension[] =>
20
19
  [
21
20
  // TODO(burdon): Compare with minimalSetup.
22
21
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
@@ -2,11 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Extension } from '@codemirror/state';
6
5
  import { keymap } from '@codemirror/view';
7
6
 
8
- // TODO(burdon): Review https://github.com/sergeche/codemirror-movie?tab=readme-ov-file
9
-
10
7
  export type DemoOptions = {
11
8
  delay?: number;
12
9
  items?: string[];
@@ -18,7 +15,7 @@ const defaultItems = ['hello world!', 'this is a test.', 'this is [DXOS](https:/
18
15
  * Demo script.
19
16
  * Configurable plugin that let's user cycle through pre-configured input script.
20
17
  */
21
- export const demo = ({ delay = 75, items = defaultItems }: DemoOptions = {}): Extension => {
18
+ export const demo = ({ delay = 75, items = defaultItems }: DemoOptions = {}) => {
22
19
  let t: any;
23
20
  let idx = 0;
24
21
 
@@ -4,14 +4,10 @@
4
4
 
5
5
  export * from './autocomplete';
6
6
  export * from './basic';
7
- export * from './blast';
8
7
  export * from './demo';
9
8
  export * from './comments';
10
- export * from './image';
11
9
  export * from './link';
12
10
  export * from './listener';
13
11
  export * from './markdown';
14
- export * from './mention';
15
- export * from './table';
16
12
  export * from './tasklist';
17
13
  export * from './tooltip';
@@ -18,22 +18,13 @@ import { Decoration, EditorView, WidgetType } from '@codemirror/view';
18
18
  // https://codemirror.net/try/?c=aW1wb3J0IHsgYmFzaWNTZXR1cCwgRWRpdG9yVmlldyB9IGZyb20gImNvZGVtaXJyb3IiCmltcG9ydCB7IG1hcmtkb3duIH0gZnJvbSAiQGNvZGVtaXJyb3IvbGFuZy1tYXJrZG93biIKaW1wb3J0IHsgc3ludGF4VHJlZSB9IGZyb20gIkBjb2RlbWlycm9yL2xhbmd1YWdlIjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIFJhbmdlU2V0QnVpbGRlciwgU3RhdGVGaWVsZCB9IGZyb20gIkBjb2RlbWlycm9yL3N0YXRlIjsKaW1wb3J0IHsgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSB9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciOwoKY2xhc3MgTGluayBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKHRleHQsIHVybCkgewogICAgc3VwZXIoKQogICAgdGhpcy50ZXh0ID0gdGV4dAogICAgdGhpcy51cmwgPSB1cmwKICB9CiAgZXEob3RoZXIpIHsgcmV0dXJuIHRoaXMudGV4dCA9PSBvdGhlci50ZXh0ICYmIHRoaXMudXJsID09IG90aGVyLnVybCB9CiAgdG9ET00oKSB7CiAgICBsZXQgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKQogICAgbGluay50ZXh0Q29udGVudCA9IHRoaXMudGV4dAogICAgbGluay5ocmVmID0gdGhpcy51cmwKICAgIHJldHVybiBsaW5rOwogIH0KfQoKbGV0IGRlY29yYXRpb25zRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShfLCB0cikgewogICAgY29uc3QgYnVpbGRlciA9IG5ldyBSYW5nZVNldEJ1aWxkZXIoKTsKICAgIGxldCBjdXJzb3IgPSB0ci5zdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkOwogICAgc3ludGF4VHJlZSh0ci5zdGF0ZSkuaXRlcmF0ZSh7CiAgICAgIGVudGVyOiAobm9kZSkgPT4gewogICAgICAgIGlmICgoY3Vyc29yIDwgbm9kZS5mcm9tIHx8IGN1cnNvciA+IG5vZGUudG8pICYmIG5vZGUubmFtZSA9PSAiTGluayIpIHsKICAgICAgICAgIGxldCBtYXJrcyA9IG5vZGUubm9kZS5nZXRDaGlsZHJlbigiTGlua01hcmsiKTsKICAgICAgICAgIGxldCB0ZXh0ID0gbWFya3MubGVuZ3RoID49IDIgPyB0ci5zdGF0ZS5zbGljZURvYyhtYXJrc1swXS50bywgbWFya3NbMV0uZnJvbSkgOiAiIgoKICAgICAgICAgIGxldCB1cmxOb2RlID0gbm9kZS5ub2RlLmdldENoaWxkKCJVUkwiKTsKICAgICAgICAgIGxldCB1cmwgPSB1cmxOb2RlID8gdHIuc3RhdGUuc2xpY2VEb2ModXJsTm9kZS5mcm9tLCB1cmxOb2RlLnRvKSA6ICIiCiAgICAgICAgICBidWlsZGVyLmFkZChub2RlLmZyb20sIG5vZGUudG8sIERlY29yYXRpb24ucmVwbGFjZSh7CiAgICAgICAgICAgIHdpZGdldDogbmV3IExpbmsodGV4dCwgdXJsKSwKICAgICAgICAgIH0pLAogICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfSwKICAgIH0pOwogICAgcmV0dXJuIGJ1aWxkZXIuZmluaXNoKCk7CiAgfSwKICBwcm92aWRlOiAoZikgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGYpLAp9KTsKCmxldCB2aWV3ID0gbmV3IEVkaXRvclZpZXcoewogIGRvYzogIiMgSGVsbG8gV29ybGRcblxuW1Rlc3RdKGh0dHBzOi8vZXhhbXBsZS5jb20pXG5cblxuIiwKICBleHRlbnNpb25zOiBbZGVjb3JhdGlvbnNGaWVsZCwgbWFya2Rvd24oKV0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5LAp9KTsKCnZpZXcuZm9jdXMoKTs=
19
19
  // https://discuss.codemirror.net/t/link-widget-is-clicked-only-if-editor-is-out-of-focus/7433
20
20
 
21
- export type LinkOptions = {
22
- link?: boolean;
23
- onRender?: (el: Element, url: string) => void;
24
- };
21
+ // TODO(burdon): Adapt to make all markdown editable?
25
22
 
26
- /**
27
- * Creates a state field to replace AST elements with a hyperlink widget.
28
- * https://codemirror.net/docs/ref/#state.StateField
29
- */
30
- export const link = (options: LinkOptions = {}): Extension => {
31
- return StateField.define<RangeSet<any>>({
32
- create: (state) => update(state, options),
33
- update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
34
- provide: (field) => EditorView.decorations.from(field),
35
- });
36
- };
23
+ // TODO(burdon): Research overlays.
24
+ // https://stackoverflow.com/questions/26933344/codemirror-detect-and-create-links-inside-editor
25
+ // https://codemirror.net/5/doc/manual.html#addOverlay
26
+ // https://codemirror.net/5/demo/simplemode.html
27
+ // https://codemirror.net/5/demo/search.html (Search dialog).
37
28
 
38
29
  class LinkButton extends WidgetType {
39
30
  constructor(
@@ -134,3 +125,20 @@ const update = (state: EditorState, options: LinkOptions) => {
134
125
 
135
126
  return builder.finish();
136
127
  };
128
+
129
+ export type LinkOptions = {
130
+ link?: boolean;
131
+ onRender?: (el: Element, url: string) => void;
132
+ };
133
+
134
+ /**
135
+ * Creates a state field to replace AST elements with a hyperlink widget.
136
+ * https://codemirror.net/docs/ref/#state.StateField
137
+ */
138
+ export const link = (options: LinkOptions = {}): Extension => {
139
+ return StateField.define<RangeSet<any>>({
140
+ create: (state) => update(state, options),
141
+ update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
142
+ provide: (field) => EditorView.decorations.from(field),
143
+ });
144
+ };
@@ -9,8 +9,9 @@ export type ListenerOptions = { onChange: (text: string) => void };
9
9
  /**
10
10
  * Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799
11
11
  */
12
- export const listener = ({ onChange }: ListenerOptions) => {
13
- return StateField.define({
12
+ // TODO(burdon): Expose options.
13
+ export const listener = ({ onChange }: ListenerOptions) =>
14
+ StateField.define({
14
15
  create: () => null,
15
16
  update: (_value, transaction) => {
16
17
  if (transaction.docChanged && onChange) {
@@ -20,4 +21,3 @@ export const listener = ({ onChange }: ListenerOptions) => {
20
21
  return null;
21
22
  },
22
23
  });
23
- };
@@ -28,16 +28,11 @@ import type { ThemeMode } from '@dxos/react-ui';
28
28
  import { markdownHighlightStyle, markdownTagsExtension } from './highlight';
29
29
 
30
30
  export type MarkdownBundleOptions = {
31
- readonly?: boolean;
32
31
  themeMode?: ThemeMode;
33
32
  placeholder?: string;
34
33
  };
35
34
 
36
- export const markdownBundle = ({
37
- readonly,
38
- themeMode,
39
- placeholder: _placeholder,
40
- }: MarkdownBundleOptions): Extension[] => {
35
+ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: MarkdownBundleOptions): Extension[] => {
41
36
  // All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
42
37
  // https://codemirror.net/docs/ref/#codemirror.basicSetup
43
38
  return [
@@ -94,10 +89,10 @@ export const markdownBundle = ({
94
89
  ],
95
90
  }),
96
91
 
97
- // https://github.com/codemirror/theme-one-dark
98
- themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
99
-
100
92
  // Custom styles.
101
- syntaxHighlighting(markdownHighlightStyle(readonly)),
93
+ syntaxHighlighting(markdownHighlightStyle),
94
+
95
+ // TODO(thure): All but one rule here apply to both themes; rename or refactor.
96
+ themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
102
97
  ].filter(Boolean) as Extension[];
103
98
  };