@dxos/react-ui-editor 0.6.11-staging.e6894a4 → 0.6.12-main.15a606f

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 (43) hide show
  1. package/dist/lib/browser/index.mjs +127 -74
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/InputMode.stories.d.ts.map +1 -1
  5. package/dist/types/src/TextEditor.stories.d.ts +10 -2
  6. package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
  7. package/dist/types/src/defaults.d.ts.map +1 -1
  8. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +0 -1
  9. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  10. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -1
  11. package/dist/types/src/extensions/factories.d.ts +5 -1
  12. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  13. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  14. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  15. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  16. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -1
  17. package/dist/types/src/extensions/util/react.d.ts +4 -0
  18. package/dist/types/src/extensions/util/react.d.ts.map +1 -1
  19. package/dist/types/src/hooks/useTextEditor.d.ts +2 -2
  20. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  21. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  22. package/dist/types/src/styles/theme.d.ts.map +1 -1
  23. package/package.json +32 -27
  24. package/src/InputMode.stories.tsx +8 -10
  25. package/src/TextEditor.stories.tsx +61 -34
  26. package/src/defaults.ts +3 -3
  27. package/src/extensions/automerge/automerge.stories.tsx +5 -6
  28. package/src/extensions/automerge/{automerge.spec.tsx → automerge.test.tsx} +1 -0
  29. package/src/extensions/automerge/automerge.ts +1 -1
  30. package/src/extensions/factories.ts +15 -4
  31. package/src/extensions/folding.tsx +17 -4
  32. package/src/extensions/markdown/bundle.ts +1 -5
  33. package/src/extensions/markdown/changes.test.ts +1 -3
  34. package/src/extensions/markdown/decorate.ts +40 -23
  35. package/src/extensions/markdown/formatting.test.ts +1 -3
  36. package/src/extensions/markdown/parser.test.ts +1 -2
  37. package/src/extensions/util/react.tsx +15 -0
  38. package/src/hooks/useTextEditor.ts +3 -5
  39. package/src/styles/markdown.ts +0 -2
  40. package/src/styles/theme.ts +13 -9
  41. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +0 -2
  42. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +0 -1
  43. package/src/extensions/automerge/automerge.test.ts +0 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.6.11-staging.e6894a4",
3
+ "version": "0.6.12-main.15a606f",
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",
@@ -34,6 +34,7 @@
34
34
  "@codemirror/view": "^6.29.1",
35
35
  "@fluentui/react-tabster": "^9.19.0",
36
36
  "@lezer/common": "^1.2.1",
37
+ "@lezer/generator": "^1.7.1",
37
38
  "@lezer/highlight": "^1.2.0",
38
39
  "@lezer/markdown": "^1.3.0",
39
40
  "@radix-ui/react-context": "^1.0.0",
@@ -47,19 +48,19 @@
47
48
  "lodash.sortby": "^4.7.0",
48
49
  "react-dropzone": "^14.2.3",
49
50
  "style-mod": "^4.1.0",
50
- "@dxos/async": "0.6.11-staging.e6894a4",
51
- "@dxos/automerge": "0.6.11-staging.e6894a4",
52
- "@dxos/debug": "0.6.11-staging.e6894a4",
53
- "@dxos/context": "0.6.11-staging.e6894a4",
54
- "@dxos/display-name": "0.6.11-staging.e6894a4",
55
- "@dxos/echo-schema": "0.6.11-staging.e6894a4",
56
- "@dxos/invariant": "0.6.11-staging.e6894a4",
57
- "@dxos/log": "0.6.11-staging.e6894a4",
58
- "@dxos/protocols": "0.6.11-staging.e6894a4",
59
- "@dxos/react-async": "0.6.11-staging.e6894a4",
60
- "@dxos/util": "0.6.11-staging.e6894a4",
61
- "@dxos/react-ui": "0.6.11-staging.e6894a4",
62
- "@dxos/react-ui-theme": "0.6.11-staging.e6894a4"
51
+ "@dxos/async": "0.6.12-main.15a606f",
52
+ "@dxos/context": "0.6.12-main.15a606f",
53
+ "@dxos/debug": "0.6.12-main.15a606f",
54
+ "@dxos/automerge": "0.6.12-main.15a606f",
55
+ "@dxos/display-name": "0.6.12-main.15a606f",
56
+ "@dxos/log": "0.6.12-main.15a606f",
57
+ "@dxos/echo-schema": "0.6.12-main.15a606f",
58
+ "@dxos/invariant": "0.6.12-main.15a606f",
59
+ "@dxos/protocols": "0.6.12-main.15a606f",
60
+ "@dxos/react-ui-theme": "0.6.12-main.15a606f",
61
+ "@dxos/react-hooks": "0.6.12-main.15a606f",
62
+ "@dxos/util": "0.6.12-main.15a606f",
63
+ "@dxos/react-ui": "0.6.12-main.15a606f"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@phosphor-icons/react": "^2.1.5",
@@ -67,6 +68,8 @@
67
68
  "@testing-library/dom": "^8.17.1",
68
69
  "@testing-library/react": "^13.4.0",
69
70
  "@testing-library/user-event": "^14.4.3",
71
+ "@types/chai": "^4.2.15",
72
+ "@types/chai-dom": "^1.11.0",
70
73
  "@types/lodash.defaultsdeep": "^4.6.6",
71
74
  "@types/lodash.get": "^4.4.7",
72
75
  "@types/lodash.merge": "^4.6.6",
@@ -74,29 +77,31 @@
74
77
  "@types/react": "~18.2.0",
75
78
  "@types/react-dom": "~18.2.0",
76
79
  "@types/react-test-renderer": "^17.0.2",
80
+ "chai": "^4.4.1",
81
+ "chai-dom": "^1.11.0",
77
82
  "happy-dom": "^13.3.1",
78
83
  "jsdom": "^24.0.0",
84
+ "mocha": "^10.6.0",
79
85
  "react": "~18.2.0",
80
86
  "react-dom": "~18.2.0",
81
87
  "react-test-renderer": "~18.2.0",
82
- "vite": "^5.3.4",
88
+ "vite": "5.4.7",
83
89
  "vite-plugin-top-level-await": "^1.4.1",
84
90
  "vite-plugin-wasm": "^3.3.0",
85
- "@dxos/config": "0.6.11-staging.e6894a4",
86
- "@dxos/automerge": "0.6.11-staging.e6894a4",
87
- "@dxos/echo-signals": "0.6.11-staging.e6894a4",
88
- "@dxos/echo-typegen": "0.6.11-staging.e6894a4",
89
- "@dxos/keyboard": "0.6.11-staging.e6894a4",
90
- "@dxos/random": "0.6.11-staging.e6894a4",
91
- "@dxos/storybook-utils": "0.6.11-staging.e6894a4",
92
- "@dxos/react-client": "0.6.11-staging.e6894a4",
93
- "@dxos/react-ui": "0.6.11-staging.e6894a4"
91
+ "@dxos/automerge": "0.6.12-main.15a606f",
92
+ "@dxos/config": "0.6.12-main.15a606f",
93
+ "@dxos/echo-signals": "0.6.12-main.15a606f",
94
+ "@dxos/keyboard": "0.6.12-main.15a606f",
95
+ "@dxos/random": "0.6.12-main.15a606f",
96
+ "@dxos/react-client": "0.6.12-main.15a606f",
97
+ "@dxos/storybook-utils": "0.6.12-main.15a606f",
98
+ "@dxos/react-ui": "0.6.12-main.15a606f"
94
99
  },
95
100
  "peerDependencies": {
96
101
  "@phosphor-icons/react": "^2.1.5",
97
- "react": "^18.0.0",
98
- "react-dom": "^18.0.0",
99
- "@dxos/react-client": "0.6.11-staging.e6894a4"
102
+ "react": "~18.2.0",
103
+ "react-dom": "~18.2.0",
104
+ "@dxos/react-client": "0.6.12-main.15a606f"
100
105
  },
101
106
  "publishConfig": {
102
107
  "access": "public"
@@ -6,9 +6,9 @@ import '@dxos-theme';
6
6
 
7
7
  import React, { useState } from 'react';
8
8
 
9
- import { Toolbar as NaturalToolbar, Select, useThemeContext, Tooltip } from '@dxos/react-ui';
9
+ import { Toolbar as NaturalToolbar, Select, useThemeContext } from '@dxos/react-ui';
10
10
  import { attentionSurface, mx, textBlockWidth } from '@dxos/react-ui-theme';
11
- import { withFullscreen, withTheme } from '@dxos/storybook-utils';
11
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
12
12
 
13
13
  import { Toolbar } from './components';
14
14
  import {
@@ -39,7 +39,7 @@ const Story = ({ autoFocus, initialValue, placeholder, readonly }: StoryProps) =
39
39
  editorInputMode ? InputModeExtensions[editorInputMode] : [],
40
40
  createBasicExtensions({ placeholder, lineWrapping: true, readonly }),
41
41
  createMarkdownExtensions({ themeMode }),
42
- createThemeExtensions({ themeMode }),
42
+ createThemeExtensions({ themeMode, syntaxHighlighting: true }),
43
43
  decorateMarkdown(),
44
44
  formattingKeymap(),
45
45
  trackFormatting,
@@ -54,12 +54,10 @@ const Story = ({ autoFocus, initialValue, placeholder, readonly }: StoryProps) =
54
54
  // Also not sure if view is even guaranteed to exist at this point.
55
55
  return (
56
56
  <div role='none' className={mx('fixed inset-0 flex flex-col')}>
57
- <Tooltip.Provider>
58
- <Toolbar.Root onAction={handleAction} state={formattingState} classNames={textBlockWidth}>
59
- <Toolbar.Markdown />
60
- <EditorInputModeToolbar editorInputMode={editorInputMode} setEditorInputMode={setEditorInputMode} />
61
- </Toolbar.Root>
62
- </Tooltip.Provider>
57
+ <Toolbar.Root onAction={handleAction} state={formattingState} classNames={textBlockWidth}>
58
+ <Toolbar.Markdown />
59
+ <EditorInputModeToolbar editorInputMode={editorInputMode} setEditorInputMode={setEditorInputMode} />
60
+ </Toolbar.Root>
63
61
 
64
62
  <div role='none' className='grow overflow-hidden'>
65
63
  <div className={attentionSurface} ref={parentRef} />
@@ -100,7 +98,7 @@ const EditorInputModeToolbar = ({
100
98
 
101
99
  export default {
102
100
  title: 'react-ui-editor/InputMode',
103
- decorators: [withTheme, withFullscreen()],
101
+ decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
104
102
  parameters: { translations, layout: 'fullscreen' },
105
103
  render: Story,
106
104
  };
@@ -4,6 +4,7 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
+ import { javascript } from '@codemirror/lang-javascript';
7
8
  import { markdown } from '@codemirror/lang-markdown';
8
9
  import { openSearchPanel } from '@codemirror/search';
9
10
  import { type Extension } from '@codemirror/state';
@@ -22,9 +23,9 @@ import { faker } from '@dxos/random';
22
23
  import { createDocAccessor, createEchoObject } from '@dxos/react-client/echo';
23
24
  import { Button, DensityProvider, Input, useThemeContext } from '@dxos/react-ui';
24
25
  import { baseSurface, mx, getSize } from '@dxos/react-ui-theme';
25
- import { withFullscreen, withTheme } from '@dxos/storybook-utils';
26
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
26
27
 
27
- import { editorContent, editorGutter } from './defaults';
28
+ import { editorContent, editorGutter, editorMonospace } from './defaults';
28
29
  import {
29
30
  InputModeExtensions,
30
31
  annotations,
@@ -68,6 +69,15 @@ const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
68
69
 
69
70
  const img = '![dxos](https://pbs.twimg.com/profile_banners/1268328127673044992/1684766689/1500x500)';
70
71
 
72
+ const code = str(
73
+ '// Code',
74
+ 'const Component = () => {',
75
+ ' const x = 100;',
76
+ '',
77
+ ' return () => <div>Test</div>;',
78
+ '};',
79
+ );
80
+
71
81
  const content = {
72
82
  tasks: str(
73
83
  //
@@ -111,22 +121,9 @@ const content = {
111
121
  '',
112
122
  ),
113
123
 
114
- code: str(
115
- '### Code',
116
- '',
117
- '```bash',
118
- '$ ls -las',
119
- '```',
120
- '',
121
- '```tsx',
122
- 'const Component = () => {',
123
- ' const x = 100;',
124
- '',
125
- ' return () => <div>Test</div>;',
126
- '};',
127
- '```',
128
- '',
129
- ),
124
+ typescript: code,
125
+
126
+ codeblocks: str('### Code', '', '```bash', '$ ls -las', '```', '', '```tsx', code, '```', ''),
130
127
 
131
128
  comment: str('<!--', 'A comment', '-->', '', 'No comment.', 'Partial comment. <!-- comment. -->'),
132
129
 
@@ -195,12 +192,9 @@ const text = str(
195
192
  content.tasks,
196
193
  content.numbered,
197
194
 
198
- '---',
199
- content.headings,
200
-
201
195
  '---',
202
196
  '## Misc',
203
- content.code,
197
+ content.codeblocks,
204
198
  content.table,
205
199
  content.image,
206
200
  content.footer,
@@ -261,12 +255,15 @@ const renderLinkButton = (el: Element, url: string) => {
261
255
  // Story
262
256
  //
263
257
 
258
+ type DebugMode = 'raw' | 'tree' | 'raw+tree';
259
+
264
260
  type StoryProps = {
265
261
  id?: string;
266
- debug?: boolean;
262
+ debug?: DebugMode;
267
263
  text?: string;
268
264
  readonly?: boolean;
269
265
  placeholder?: string;
266
+ lineNumbers?: boolean;
270
267
  onReady?: (view: EditorView) => void;
271
268
  } & Pick<UseTextEditorProps, 'scrollTo' | 'selection' | 'extensions'>;
272
269
 
@@ -279,6 +276,7 @@ const Story = ({
279
276
  placeholder = 'New document.',
280
277
  scrollTo,
281
278
  selection,
279
+ lineNumbers,
282
280
  onReady,
283
281
  }: StoryProps) => {
284
282
  const [object] = useState(createEchoObject(create(Expando, { content: text ?? '' })));
@@ -290,16 +288,18 @@ const Story = ({
290
288
  initialValue: text,
291
289
  extensions: [
292
290
  createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
293
- createBasicExtensions({ readonly, placeholder, scrollPastEnd: true }),
291
+ createBasicExtensions({ readonly, placeholder, lineNumbers, scrollPastEnd: true }),
294
292
  createMarkdownExtensions({ themeMode }),
295
293
  createThemeExtensions({
296
294
  themeMode,
295
+ syntaxHighlighting: true,
297
296
  slots: {
298
297
  content: {
299
298
  className: editorContent,
300
299
  },
301
300
  },
302
301
  }),
302
+ editorGutter,
303
303
  extensions || [],
304
304
  debug ? debugTree(setTree) : [],
305
305
  ],
@@ -319,10 +319,15 @@ const Story = ({
319
319
  <div className='flex w-full'>
320
320
  <div role='none' className='flex w-full overflow-hidden' ref={parentRef} {...focusAttributes} />
321
321
  {debug && (
322
- <div className='w-[800px] border-l border-separator overflow-auto'>
323
- <pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>
324
- {JSON.stringify(tree, null, 2)}
325
- </pre>
322
+ <div className='flex flex-col w-[800px] border-l border-separator divide-y divide-separator overflow-auto'>
323
+ {(debug === 'raw' || debug === 'raw+tree') && (
324
+ <pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>{view?.state.doc.toString()}</pre>
325
+ )}
326
+ {(debug === 'tree' || debug === 'raw+tree') && (
327
+ <pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>
328
+ {JSON.stringify(tree, null, 2)}
329
+ </pre>
330
+ )}
326
331
  </div>
327
332
  )}
328
333
  </div>
@@ -331,7 +336,7 @@ const Story = ({
331
336
 
332
337
  export default {
333
338
  title: 'react-ui-editor/TextEditor',
334
- decorators: [withTheme, withFullscreen()],
339
+ decorators: [withTheme, withLayout({ fullscreen: true })],
335
340
  render: Story,
336
341
  parameters: { translations, layout: 'fullscreen' },
337
342
  };
@@ -345,8 +350,24 @@ const defaultExtensions: Extension[] = [
345
350
  linkTooltip(renderLinkTooltip),
346
351
  ];
347
352
 
353
+ const allExtensions: Extension[] = [
354
+ autocomplete({
355
+ onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
356
+ }),
357
+ decorateMarkdown({ numberedHeadings: { from: 2, to: 4 }, renderLinkButton, selectionChangeDelay: 100 }),
358
+ formattingKeymap(),
359
+ linkTooltip(renderLinkTooltip),
360
+ image(),
361
+ table(),
362
+ folding(),
363
+ ];
364
+
348
365
  export const Default = {
349
- render: () => <Story text={text} extensions={defaultExtensions} selection={{ anchor: 99, head: 110 }} />,
366
+ render: () => <Story text={text} extensions={defaultExtensions} />,
367
+ };
368
+
369
+ export const Everything = {
370
+ render: () => <Story text={text} extensions={allExtensions} selection={{ anchor: 99, head: 110 }} />,
350
371
  };
351
372
 
352
373
  export const Empty = {
@@ -390,7 +411,7 @@ const headings = str(
390
411
  const global = new Map<string, EditorSelectionState>();
391
412
 
392
413
  export const Folding = {
393
- render: () => <Story text={text} extensions={[editorGutter, folding()]} />,
414
+ render: () => <Story text={text} extensions={[folding()]} />,
394
415
  };
395
416
 
396
417
  export const Scrolling = {
@@ -445,7 +466,7 @@ export const Image = {
445
466
  };
446
467
 
447
468
  export const Code = {
448
- render: () => <Story text={str(content.code, content.footer)} extensions={[decorateMarkdown()]} />,
469
+ render: () => <Story text={str(content.codeblocks, content.footer)} extensions={[decorateMarkdown()]} />,
449
470
  };
450
471
 
451
472
  export const Lists = {
@@ -466,7 +487,7 @@ export const OrderedList = {
466
487
  };
467
488
 
468
489
  export const TaskList = {
469
- render: () => <Story text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} debug />,
490
+ render: () => <Story text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} debug='raw+tree' />,
470
491
  };
471
492
 
472
493
  export const Table = {
@@ -486,6 +507,12 @@ export const CommentedOut = {
486
507
  ),
487
508
  };
488
509
 
510
+ export const Typescript = {
511
+ render: () => (
512
+ <Story text={content.typescript} lineNumbers extensions={[editorMonospace, javascript({ typescript: true })]} />
513
+ ),
514
+ };
515
+
489
516
  //
490
517
  // Custom
491
518
  //
@@ -669,7 +696,7 @@ export const Typewriter = {
669
696
  export const Blast = {
670
697
  render: () => (
671
698
  <Story
672
- text={str('# Blast', '', content.paragraphs, content.code, content.paragraphs)}
699
+ text={str('# Blast', '', content.paragraphs, content.codeblocks, content.paragraphs)}
673
700
  extensions={[
674
701
  typewriter({ items: typewriterItems }),
675
702
  blast(
package/src/defaults.ts CHANGED
@@ -8,7 +8,7 @@ import { mx } from '@dxos/react-ui-theme';
8
8
 
9
9
  import { fontMono } from './styles';
10
10
 
11
- const margin = '!mt-[16px]';
11
+ const margin = '!mt-[1rem]';
12
12
 
13
13
  /**
14
14
  * CodeMirror content width.
@@ -20,16 +20,16 @@ export const editorContent = mx(margin, '!mli-auto w-full max-w-[min(50rem,100%-
20
20
  /**
21
21
  * Margin for numbers.
22
22
  */
23
- export const editorFullWidth = mx(margin, '!ml-[3rem]');
23
+ export const editorFullWidth = mx(margin);
24
24
 
25
25
  export const editorWithToolbarLayout =
26
26
  'grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden';
27
27
 
28
- // TODO(burdon): Define scrollMargins for fixed gutter.
29
28
  export const editorGutter = EditorView.theme({
30
29
  // Match margin from content.
31
30
  '.cm-gutters': {
32
31
  marginTop: '16px',
32
+ paddingRight: '1rem',
33
33
  },
34
34
  });
35
35
 
@@ -10,12 +10,11 @@ import React, { useEffect, useState } from 'react';
10
10
  import { Repo } from '@dxos/automerge/automerge-repo';
11
11
  import { BroadcastChannelNetworkAdapter } from '@dxos/automerge/automerge-repo-network-broadcastchannel';
12
12
  import { Expando, create } from '@dxos/echo-schema';
13
- import { type PublicKey } from '@dxos/keys';
14
13
  import { Filter, DocAccessor, createDocAccessor, useSpace, useQuery, type Space } from '@dxos/react-client/echo';
15
14
  import { useIdentity, type Identity } from '@dxos/react-client/halo';
16
- import { ClientRepeater } from '@dxos/react-client/testing';
15
+ import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
17
16
  import { useThemeContext } from '@dxos/react-ui';
18
- import { withTheme } from '@dxos/storybook-utils';
17
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
19
18
 
20
19
  import { editorContent } from '../../defaults';
21
20
  import { useTextEditor } from '../../hooks';
@@ -95,12 +94,12 @@ const Story = () => {
95
94
  export default {
96
95
  title: 'react-ui-editor/Automerge',
97
96
  component: Editor,
98
- decorators: [withTheme],
97
+ decorators: [withTheme, withLayout({ fullscreen: true })],
99
98
  render: () => <Story />,
100
- parameters: { translations, layout: 'fullscreen' },
99
+ parameters: { translations },
101
100
  };
102
101
 
103
- const EchoStory = ({ spaceKey }: { spaceKey: PublicKey }) => {
102
+ const EchoStory = ({ spaceKey }: ClientRepeatedComponentProps) => {
104
103
  const identity = useIdentity();
105
104
  const space = useSpace(spaceKey);
106
105
  const [source, setSource] = useState<DocAccessor>();
@@ -5,6 +5,7 @@
5
5
  import { EditorState } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
7
  import { render, screen } from '@testing-library/react';
8
+ // TODO(wittjosiah): Move to vitest expect.
8
9
  import chai, { expect } from 'chai';
9
10
  import chaiDom from 'chai-dom';
10
11
  import get from 'lodash.get';
@@ -1,5 +1,5 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  // Copyright 2024 Automerge
4
4
  // Ref: https://github.com/automerge/automerge-codemirror
5
5
  //
@@ -4,9 +4,10 @@
4
4
 
5
5
  import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
6
6
  import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
7
- import { bracketMatching } from '@codemirror/language';
7
+ import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
8
8
  import { searchKeymap } from '@codemirror/search';
9
9
  import { EditorState, type Extension } from '@codemirror/state';
10
+ import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
10
11
  import {
11
12
  EditorView,
12
13
  type KeyBinding,
@@ -131,6 +132,7 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
131
132
  export type ThemeExtensionsOptions = {
132
133
  themeMode?: ThemeMode;
133
134
  styles?: ThemeStyles;
135
+ syntaxHighlighting?: boolean;
134
136
  slots?: {
135
137
  editor?: {
136
138
  className?: string;
@@ -147,13 +149,22 @@ const defaultThemeSlots = {
147
149
  },
148
150
  };
149
151
 
150
- // TODO(burdon): Should only have one baseTheme?
151
- // https://codemirror.net/examples/styling
152
- export const createThemeExtensions = ({ themeMode, styles, slots: _slots }: ThemeExtensionsOptions = {}): Extension => {
152
+ /**
153
+ * https://codemirror.net/examples/styling
154
+ */
155
+ export const createThemeExtensions = ({
156
+ themeMode,
157
+ styles,
158
+ syntaxHighlighting: _syntaxHighlighting,
159
+ slots: _slots,
160
+ }: ThemeExtensionsOptions = {}): Extension => {
153
161
  const slots = defaultsDeep({}, _slots, defaultThemeSlots);
154
162
  return [
155
163
  EditorView.darkTheme.of(themeMode === 'dark'),
156
164
  EditorView.baseTheme(styles ? merge({}, defaultTheme, styles) : defaultTheme),
165
+ // https://github.com/codemirror/theme-one-dark
166
+ _syntaxHighlighting &&
167
+ (themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)),
157
168
  slots.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
158
169
  slots.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
159
170
  ].filter(isNotFalsy);
@@ -4,28 +4,41 @@
4
4
 
5
5
  import { codeFolding, foldGutter } from '@codemirror/language';
6
6
  import { type Extension } from '@codemirror/state';
7
+ import { EditorView } from '@codemirror/view';
7
8
  import React from 'react';
8
9
 
9
10
  import { Icon } from '@dxos/react-ui';
10
11
  import { getSize } from '@dxos/react-ui-theme';
11
12
 
12
- import { renderRoot } from './util';
13
+ import { createElement, renderRoot } from './util';
13
14
 
14
15
  export type FoldingOptions = {};
15
16
 
16
17
  /**
17
18
  * https://codemirror.net/examples/gutter
18
19
  */
20
+ // TODO(burdon): Remember folding state.
19
21
  export const folding = (_props: FoldingOptions = {}): Extension => [
20
22
  codeFolding({
21
- placeholderDOM: () => document.createElement('div'),
23
+ placeholderDOM: () => {
24
+ return document.createElement('span'); // Collapse content.
25
+ },
22
26
  }),
23
27
  foldGutter({
24
28
  markerDOM: (open) => {
25
29
  return renderRoot(
26
- document.createElement('div'),
27
- <Icon icon='ph--caret-right--regular' classNames={[getSize(3), 'm-2 cursor-pointer', open && 'rotate-90']} />,
30
+ createElement('div', { className: 'flex h-full items-center' }),
31
+ <Icon icon='ph--caret-right--regular' classNames={[getSize(3), 'mx-3 cursor-pointer', open && 'rotate-90']} />,
28
32
  );
29
33
  },
30
34
  }),
35
+ EditorView.theme({
36
+ '.cm-foldGutter': {
37
+ opacity: 0.3,
38
+ transition: 'opacity 0.3s',
39
+ },
40
+ '.cm-foldGutter:hover': {
41
+ opacity: 1,
42
+ },
43
+ }),
31
44
  ];
@@ -5,11 +5,10 @@
5
5
  import { completionKeymap } from '@codemirror/autocomplete';
6
6
  import { defaultKeymap, indentWithTab } from '@codemirror/commands';
7
7
  import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
8
- import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
8
+ import { syntaxHighlighting } from '@codemirror/language';
9
9
  import { languages } from '@codemirror/language-data';
10
10
  import { lintKeymap } from '@codemirror/lint';
11
11
  import { type Extension } from '@codemirror/state';
12
- import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
13
12
  import { keymap } from '@codemirror/view';
14
13
 
15
14
  import { type ThemeMode } from '@dxos/react-ui';
@@ -54,9 +53,6 @@ export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions =
54
53
  ],
55
54
  }),
56
55
 
57
- // https://github.com/codemirror/theme-one-dark
58
- themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
59
-
60
56
  // Custom styles.
61
57
  syntaxHighlighting(markdownHighlightStyle()),
62
58
 
@@ -2,9 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
6
-
7
- import { describe, test } from '@dxos/test';
5
+ import { describe, expect, test } from 'vitest';
8
6
 
9
7
  import { createLinkLabel } from './changes';
10
8