@dxos/react-ui-editor 0.4.8-main.eb5ae94 → 0.4.8-main.fff1521

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 (127) hide show
  1. package/dist/lib/browser/index.mjs +2725 -2711
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/TextEditor.d.ts +18 -34
  5. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +80 -5
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.d.ts +11 -5
  9. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  10. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +4 -2
  11. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  12. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  13. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +2 -1
  15. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  16. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  17. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  18. package/dist/types/src/extensions/automerge/defs.d.ts +1 -15
  19. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  20. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  21. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/sync.d.ts +2 -1
  23. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -1
  25. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  26. package/dist/types/src/{hooks → extensions/awareness}/awareness-provider.d.ts +1 -1
  27. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -0
  28. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -0
  29. package/dist/types/src/extensions/awareness/index.d.ts +3 -0
  30. package/dist/types/src/extensions/awareness/index.d.ts.map +1 -0
  31. package/dist/types/src/extensions/comments.d.ts +2 -2
  32. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  33. package/dist/types/src/extensions/cursor-line-margin.d.ts +6 -7
  34. package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -1
  35. package/dist/types/src/extensions/cursor.d.ts +1 -1
  36. package/dist/types/src/extensions/cursor.d.ts.map +1 -1
  37. package/dist/types/src/extensions/doc.d.ts +3 -0
  38. package/dist/types/src/extensions/doc.d.ts.map +1 -0
  39. package/dist/types/src/extensions/factories.d.ts +49 -0
  40. package/dist/types/src/extensions/factories.d.ts.map +1 -0
  41. package/dist/types/src/extensions/index.d.ts +4 -1
  42. package/dist/types/src/extensions/index.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/bundle.d.ts +5 -4
  44. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  45. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  46. package/dist/types/src/extensions/markdown/formatting.d.ts +8 -5
  47. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/image.d.ts +6 -0
  49. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/table.d.ts +2 -2
  51. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  52. package/dist/types/src/extensions/modes.d.ts +1 -0
  53. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  54. package/dist/types/src/extensions/state.d.ts +20 -0
  55. package/dist/types/src/extensions/state.d.ts.map +1 -0
  56. package/dist/types/src/extensions/types.d.ts +9 -0
  57. package/dist/types/src/extensions/types.d.ts.map +1 -0
  58. package/dist/types/src/hooks/index.d.ts +1 -4
  59. package/dist/types/src/hooks/index.d.ts.map +1 -1
  60. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  61. package/dist/types/src/hooks/useDocAccessor.d.ts +7 -0
  62. package/dist/types/src/hooks/useDocAccessor.d.ts.map +1 -0
  63. package/dist/types/src/hooks/useTextEditor.d.ts +3 -44
  64. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useTextEditor.stories.d.ts +7 -0
  66. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  67. package/dist/types/src/themes/default.d.ts +0 -3
  68. package/dist/types/src/themes/default.d.ts.map +1 -1
  69. package/dist/types/src/translations.d.ts +1 -0
  70. package/dist/types/src/translations.d.ts.map +1 -1
  71. package/package.json +25 -27
  72. package/src/components/TextEditor/TextEditor.stories.tsx +520 -23
  73. package/src/components/TextEditor/TextEditor.tsx +45 -156
  74. package/src/components/Toolbar/Toolbar.stories.tsx +54 -55
  75. package/src/components/Toolbar/Toolbar.tsx +96 -35
  76. package/src/extensions/automerge/automerge.stories.tsx +16 -17
  77. package/src/extensions/automerge/automerge.ts +2 -1
  78. package/src/extensions/automerge/cursor.ts +1 -1
  79. package/src/extensions/automerge/defs.ts +1 -22
  80. package/src/extensions/automerge/index.ts +0 -1
  81. package/src/extensions/automerge/sync.ts +2 -9
  82. package/src/extensions/automerge/update-automerge.ts +2 -1
  83. package/src/{hooks → extensions/awareness}/awareness-provider.ts +1 -1
  84. package/src/extensions/{awareness.ts → awareness/awareness.ts} +1 -1
  85. package/src/extensions/awareness/index.ts +6 -0
  86. package/src/extensions/command/state.ts +1 -1
  87. package/src/extensions/comments.ts +9 -5
  88. package/src/extensions/cursor-line-margin.ts +17 -18
  89. package/src/extensions/cursor.ts +1 -1
  90. package/src/extensions/doc.ts +14 -0
  91. package/src/extensions/factories.ts +167 -0
  92. package/src/extensions/index.ts +4 -1
  93. package/src/extensions/markdown/bundle.ts +11 -28
  94. package/src/extensions/markdown/decorate.ts +2 -4
  95. package/src/extensions/markdown/formatting.test.ts +13 -13
  96. package/src/extensions/markdown/formatting.ts +106 -93
  97. package/src/extensions/markdown/image.ts +7 -0
  98. package/src/extensions/markdown/table.ts +11 -3
  99. package/src/extensions/modes.ts +3 -1
  100. package/src/extensions/state.ts +90 -0
  101. package/src/extensions/types.ts +16 -0
  102. package/src/hooks/index.ts +1 -5
  103. package/src/hooks/useActionHandler.ts +5 -1
  104. package/src/hooks/useDocAccessor.ts +21 -0
  105. package/src/hooks/useTextEditor.stories.tsx +40 -35
  106. package/src/hooks/useTextEditor.ts +14 -120
  107. package/src/index.ts +1 -1
  108. package/src/themes/default.ts +1 -28
  109. package/src/translations.ts +1 -0
  110. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +0 -103
  111. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +0 -1
  112. package/dist/types/src/extensions/awareness.d.ts.map +0 -1
  113. package/dist/types/src/extensions/outliner.d.ts +0 -4
  114. package/dist/types/src/extensions/outliner.d.ts.map +0 -1
  115. package/dist/types/src/hooks/awareness-provider.d.ts.map +0 -1
  116. package/dist/types/src/hooks/defs.d.ts +0 -20
  117. package/dist/types/src/hooks/defs.d.ts.map +0 -1
  118. package/dist/types/src/hooks/useEditorView.d.ts +0 -20
  119. package/dist/types/src/hooks/useEditorView.d.ts.map +0 -1
  120. package/dist/types/src/hooks/useTextModel.d.ts +0 -33
  121. package/dist/types/src/hooks/useTextModel.d.ts.map +0 -1
  122. package/src/components/TextEditor/MarkdownEditor.stories.tsx +0 -524
  123. package/src/extensions/outliner.ts +0 -12
  124. package/src/hooks/defs.ts +0 -30
  125. package/src/hooks/useEditorView.ts +0 -32
  126. package/src/hooks/useTextModel.ts +0 -121
  127. /package/dist/types/src/extensions/{awareness.d.ts → awareness/awareness.d.ts} +0 -0
@@ -13,6 +13,7 @@ declare const _default: {
13
13
  'blockquote label': string;
14
14
  'codeblock label': string;
15
15
  'comment label': string;
16
+ 'image label': string;
16
17
  'heading label': string;
17
18
  'table label': string;
18
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;AAEhD,wBAoBE"}
1
+ {"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;AAEhD,wBAqBE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.4.8-main.eb5ae94",
3
+ "version": "0.4.8-main.fff1521",
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,21 +36,22 @@
36
36
  "lodash.get": "^4.4.2",
37
37
  "lodash.pick": "^4.4.0",
38
38
  "lodash.sortby": "^4.7.0",
39
+ "react-dropzone": "^14.2.3",
39
40
  "style-mod": "^4.1.0",
40
- "@dxos/async": "0.4.8-main.eb5ae94",
41
- "@dxos/automerge": "0.4.8-main.eb5ae94",
42
- "@dxos/context": "0.4.8-main.eb5ae94",
43
- "@dxos/display-name": "0.4.8-main.eb5ae94",
44
- "@dxos/debug": "0.4.8-main.eb5ae94",
45
- "@dxos/invariant": "0.4.8-main.eb5ae94",
46
- "@dxos/echo-schema": "0.4.8-main.eb5ae94",
47
- "@dxos/log": "0.4.8-main.eb5ae94",
48
- "@dxos/protocols": "0.4.8-main.eb5ae94",
49
- "@dxos/react-async": "0.4.8-main.eb5ae94",
50
- "@dxos/react-ui-theme": "0.4.8-main.eb5ae94",
51
- "@dxos/react-ui": "0.4.8-main.eb5ae94",
52
- "@dxos/text-model": "0.4.8-main.eb5ae94",
53
- "@dxos/util": "0.4.8-main.eb5ae94"
41
+ "@dxos/async": "0.4.8-main.fff1521",
42
+ "@dxos/automerge": "0.4.8-main.fff1521",
43
+ "@dxos/debug": "0.4.8-main.fff1521",
44
+ "@dxos/display-name": "0.4.8-main.fff1521",
45
+ "@dxos/invariant": "0.4.8-main.fff1521",
46
+ "@dxos/context": "0.4.8-main.fff1521",
47
+ "@dxos/echo-schema": "0.4.8-main.fff1521",
48
+ "@dxos/log": "0.4.8-main.fff1521",
49
+ "@dxos/protocols": "0.4.8-main.fff1521",
50
+ "@dxos/react-ui": "0.4.8-main.fff1521",
51
+ "@dxos/react-async": "0.4.8-main.fff1521",
52
+ "@dxos/text-model": "0.4.8-main.fff1521",
53
+ "@dxos/util": "0.4.8-main.fff1521",
54
+ "@dxos/react-ui-theme": "0.4.8-main.fff1521"
54
55
  },
55
56
  "devDependencies": {
56
57
  "@automerge/automerge-repo-network-broadcastchannel": "^1.1.1",
@@ -68,7 +69,6 @@
68
69
  "@types/react-test-renderer": "^17.0.2",
69
70
  "@vitest/browser": "^1.3.1",
70
71
  "@vitest/ui": "^1.3.1",
71
- "deepsignal": "^1.5.0",
72
72
  "happy-dom": "^13.3.1",
73
73
  "jsdom": "^20.0.1",
74
74
  "react": "^18.2.0",
@@ -78,22 +78,20 @@
78
78
  "vite-plugin-top-level-await": "^1.4.1",
79
79
  "vite-plugin-wasm": "^3.3.0",
80
80
  "vitest": "^1.3.1",
81
- "@dxos/config": "0.4.8-main.eb5ae94",
82
- "@dxos/echo-typegen": "0.4.8-main.eb5ae94",
83
- "@dxos/keyboard": "0.4.8-main.eb5ae94",
84
- "@dxos/echo-signals": "0.4.8-main.eb5ae94",
85
- "@dxos/random": "0.4.8-main.eb5ae94",
86
- "@dxos/react-client": "0.4.8-main.eb5ae94",
87
- "@dxos/storybook-utils": "0.4.8-main.eb5ae94",
88
- "@dxos/react-ui": "0.4.8-main.eb5ae94"
81
+ "@dxos/echo-signals": "0.4.8-main.fff1521",
82
+ "@dxos/config": "0.4.8-main.fff1521",
83
+ "@dxos/echo-typegen": "0.4.8-main.fff1521",
84
+ "@dxos/random": "0.4.8-main.fff1521",
85
+ "@dxos/keyboard": "0.4.8-main.fff1521",
86
+ "@dxos/react-ui": "0.4.8-main.fff1521",
87
+ "@dxos/react-client": "0.4.8-main.fff1521",
88
+ "@dxos/storybook-utils": "0.4.8-main.fff1521"
89
89
  },
90
90
  "peerDependencies": {
91
91
  "@phosphor-icons/react": "^2.0.5",
92
- "@vitest/browser": "^1.3.1",
93
- "@vitest/ui": "^1.3.1",
94
92
  "react": "^18.2.0",
95
93
  "react-dom": "^18.2.0",
96
- "@dxos/react-client": "0.4.8-main.eb5ae94"
94
+ "@dxos/react-client": "0.4.8-main.fff1521"
97
95
  },
98
96
  "publishConfig": {
99
97
  "access": "public"
@@ -4,45 +4,283 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import React, { useState } from 'react';
7
+ import { ArrowSquareOut, X } from '@phosphor-icons/react';
8
+ import { type EditorView } from 'codemirror';
9
+ import defaultsDeep from 'lodash.defaultsdeep';
10
+ import React, { type FC, type KeyboardEvent, StrictMode, useMemo, useRef, useState } from 'react';
11
+ import { createRoot } from 'react-dom/client';
8
12
 
9
13
  import { TextObject } from '@dxos/echo-schema';
10
- import { mx, textBlockWidth } from '@dxos/react-ui-theme';
14
+ import { keySymbols, parseShortcut } from '@dxos/keyboard';
15
+ import { PublicKey } from '@dxos/keys';
16
+ import { faker } from '@dxos/random';
17
+ import { Button, DensityProvider, Input, ThemeProvider, useThemeContext } from '@dxos/react-ui';
18
+ import { baseSurface, defaultTx, getSize, mx, textBlockWidth } from '@dxos/react-ui-theme';
11
19
  import { withTheme } from '@dxos/storybook-utils';
12
20
 
13
21
  import { TextEditor, type TextEditorProps } from './TextEditor';
14
- import { useTextModel } from '../../hooks';
22
+ import {
23
+ EditorModes,
24
+ annotations,
25
+ autocomplete,
26
+ blast,
27
+ command,
28
+ comments,
29
+ createBasicExtensions,
30
+ createDataExtensions,
31
+ createMarkdownExtensions,
32
+ createThemeExtensions,
33
+ decorateMarkdown,
34
+ defaultOptions,
35
+ dnd,
36
+ formattingKeymap,
37
+ image,
38
+ linkTooltip,
39
+ listener,
40
+ mention,
41
+ state,
42
+ table,
43
+ typewriter,
44
+ useComments,
45
+ type CommandAction,
46
+ type CommandOptions,
47
+ type Comment,
48
+ type CommentsOptions,
49
+ type SelectionState,
50
+ } from '../../extensions';
51
+ import { useDocAccessor } from '../../hooks';
15
52
  import translations from '../../translations';
16
53
 
17
- const initialText = [
18
- '# TextEditor',
54
+ faker.seed(101);
55
+
56
+ const str = (...lines: string[]) => lines.join('\n');
57
+
58
+ const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
59
+
60
+ const img = '![dxos](https://pbs.twimg.com/profile_banners/1268328127673044992/1684766689/1500x500)';
61
+
62
+ const text = {
63
+ tasks: str(
64
+ //
65
+ '## Tasks',
66
+ '',
67
+ '- [x] decorator',
68
+ '- [ ] checkbox',
69
+ ' - [ ] state',
70
+ ' - [ ] indent',
71
+ ' - [x] style',
72
+ '',
73
+ ),
74
+
75
+ list: str(
76
+ //
77
+ '## List',
78
+ '',
79
+ '- new york',
80
+ '- london',
81
+ '- tokyo',
82
+ '',
83
+ ),
84
+
85
+ numbered: str(
86
+ //
87
+ '## Numbered',
88
+ '',
89
+ '1. one',
90
+ '2. two',
91
+ '3. three',
92
+ '',
93
+ ),
94
+
95
+ code: str(
96
+ '## Code',
97
+ '',
98
+ '```',
99
+ '$ ls -las',
100
+ '```',
101
+ '',
102
+ '```tsx',
103
+ 'const Component = () => {',
104
+ ' const x = 100;',
105
+ '',
106
+ ' return () => <div>Test</div>;',
107
+ '};',
108
+ '```',
109
+ '',
110
+ ),
111
+
112
+ links: str(
113
+ '## Links',
114
+ '',
115
+ 'This is a naked link https://dxos.org within a sentence.',
116
+ '',
117
+ 'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
118
+ '',
119
+ ),
120
+
121
+ table: str(
122
+ '# Table',
123
+ '',
124
+ `| ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} |`,
125
+ `|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|`,
126
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
127
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
128
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
129
+ '',
130
+ ),
131
+
132
+ image: str('# Image', '', img),
133
+
134
+ headings: str(
135
+ ...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
136
+ ),
137
+
138
+ paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
139
+
140
+ footer: str('', '', '', '', ''),
141
+ };
142
+
143
+ const document = str(
144
+ '# Markdown',
145
+ '',
146
+ '> This is a block quote.',
147
+ '',
148
+ '> This is a long wrapping block quote. Neque reiciendis ullam quae error labore sit, at, et, nulla, aut at nostrum omnis quas nostrum, at consectetur vitae eos asperiores non omnis ullam in beatae at vitae deserunt asperiores sapiente.',
19
149
  '',
20
- 'This is the basic plain text editor within minimal formatting.',
21
- 'You can add custom styles and create custom extensions.',
22
- 'Or use the MarkdownEditor to edit documents.',
150
+ '> This is',
151
+ '> a multi-line',
152
+ '> block quote.',
23
153
  '',
24
- 'https://dxos.org',
25
- ].join('\n');
154
+ 'This is all about https://dxos.org and related technologies.',
155
+ '',
156
+ 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.',
157
+ '',
158
+ '---',
159
+ text.links,
160
+ '---',
161
+ text.list,
162
+ '---',
163
+ text.tasks,
164
+ '---',
165
+ text.numbered,
166
+ '---',
167
+ text.code,
168
+ '---',
169
+ text.headings,
170
+ '---',
171
+ text.table,
172
+ '---',
173
+ text.image,
174
+ text.footer,
175
+ );
176
+
177
+ const links = [
178
+ { label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
179
+ { label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
180
+ { label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
181
+ { label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
182
+ { label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
183
+ ];
184
+
185
+ const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
186
+
187
+ const hover =
188
+ 'rounded-sm text-base text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
189
+
190
+ const renderLinkTooltip = (el: Element, url: string) => {
191
+ const web = new URL(url);
192
+ createRoot(el).render(
193
+ <StrictMode>
194
+ <a href={url} target='_blank' rel='noreferrer' className={hover}>
195
+ {web.origin}
196
+ <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1')} />
197
+ </a>
198
+ </StrictMode>,
199
+ );
200
+ };
201
+
202
+ const Key: FC<{ char: string }> = ({ char }) => (
203
+ <span className='flex justify-center items-center w-[24px] h-[24px] rounded text-xs bg-neutral-200 text-black'>
204
+ {char}
205
+ </span>
206
+ );
207
+
208
+ const onCommentsHover: CommentsOptions['onHover'] = (el, shortcut) => {
209
+ createRoot(el).render(
210
+ <StrictMode>
211
+ <div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
212
+ <div>Create comment</div>
213
+ <div className='flex gap-1'>
214
+ {keySymbols(parseShortcut(shortcut)).map((char) => (
215
+ <Key key={char} char={char} />
216
+ ))}
217
+ </div>
218
+ </div>
219
+ </StrictMode>,
220
+ );
221
+ };
222
+
223
+ const renderLinkButton = (el: Element, url: string) => {
224
+ createRoot(el).render(
225
+ <StrictMode>
226
+ <a href={url} target='_blank' rel='noreferrer' className={hover}>
227
+ <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-[2px]')} />
228
+ </a>
229
+ </StrictMode>,
230
+ );
231
+ };
232
+
233
+ //
234
+ // Story
235
+ //
236
+
237
+ type StoryProps = {
238
+ id?: string;
239
+ text?: string;
240
+ comments?: Comment[];
241
+ readonly?: boolean;
242
+ placeholder?: string;
243
+ } & Pick<TextEditorProps, 'extensions'>;
26
244
 
27
245
  const Story = ({
246
+ id = 'editor-' + PublicKey.random().toHex().slice(0, 8),
28
247
  text,
29
- automerge,
248
+ comments,
249
+ extensions: _extensions = [],
250
+ readonly,
251
+ placeholder = 'New document.',
30
252
  ...props
31
- }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'placeholder' | 'slots'>) => {
32
- const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
33
- const model = useTextModel({ text: item.text });
34
- if (!model) {
35
- return null;
36
- }
253
+ }: StoryProps) => {
254
+ const { accessor } = useDocAccessor(new TextObject(text));
255
+
256
+ const viewRef = useRef<EditorView>(null);
257
+ useComments(viewRef.current, id, comments);
258
+
259
+ const { themeMode } = useThemeContext();
260
+ const extensions = useMemo(
261
+ () => [
262
+ createBasicExtensions({ readonly, placeholder }),
263
+ createMarkdownExtensions({ themeMode }),
264
+ createThemeExtensions({
265
+ themeMode,
266
+ slots: {
267
+ editor: { className: 'min-bs-dvh px-8 bg-white dark:bg-black' },
268
+ },
269
+ }),
270
+ createDataExtensions({ id, text: accessor }),
271
+ _extensions,
272
+ ],
273
+ [_extensions],
274
+ );
37
275
 
38
276
  return (
39
277
  <TextEditor
40
- model={model}
41
- slots={{
42
- root: { className: mx(textBlockWidth, 'min-bs-dvh') },
43
- editor: { className: 'min-bs-dvh p-2 bg-white dark:bg-black' },
44
- }}
45
278
  {...props}
279
+ id={id}
280
+ ref={viewRef}
281
+ doc={text}
282
+ extensions={extensions}
283
+ className={mx(textBlockWidth, 'min-bs-dvh')}
46
284
  />
47
285
  );
48
286
  };
@@ -55,6 +293,265 @@ export default {
55
293
  parameters: { translations, layout: 'fullscreen' },
56
294
  };
57
295
 
296
+ const defaults = [
297
+ autocomplete({
298
+ onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
299
+ }),
300
+ decorateMarkdown({ renderLinkButton }),
301
+ formattingKeymap(),
302
+ image(),
303
+ table(),
304
+ linkTooltip(renderLinkTooltip),
305
+ ];
306
+
58
307
  export const Default = {
59
- render: () => <Story text={initialText} placeholder='Enter text...' />,
308
+ render: () => <Story text={document} extensions={defaults} />,
309
+ };
310
+
311
+ export const Readonly = {
312
+ render: () => <Story text={document} extensions={defaults} readonly />,
313
+ };
314
+
315
+ export const NoExtensions = {
316
+ render: () => <Story text={document} />,
317
+ };
318
+
319
+ const large = faker.helpers.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 }).join('\n\n');
320
+
321
+ const largeWithImages = faker.helpers
322
+ .multiple(() => [faker.lorem.paragraph({ min: 12, max: 16 }), img], { count: 20 })
323
+ .flatMap((x) => x)
324
+ .join('\n\n');
325
+
326
+ export const Empty = {
327
+ render: () => <Story />,
328
+ };
329
+
330
+ const global = new Map<string, SelectionState>();
331
+
332
+ export const Scrolling = {
333
+ render: () => (
334
+ <Story
335
+ text={str('# Large Document', '', large)}
336
+ extensions={state({
337
+ setState: (id, state) => global.set(id, state),
338
+ getState: (id) => global.get(id),
339
+ })}
340
+ />
341
+ ),
342
+ };
343
+
344
+ export const ScrollingWithImages = {
345
+ render: () => <Story text={str('# Large Document', '', largeWithImages)} extensions={[image()]} />,
346
+ };
347
+
348
+ export const Links = {
349
+ render: () => <Story text={str(text.links, text.footer)} extensions={[linkTooltip(renderLinkTooltip)]} />,
350
+ };
351
+
352
+ export const Image = {
353
+ render: () => <Story text={str(text.image, text.footer)} extensions={[image()]} />,
354
+ };
355
+
356
+ export const Code = {
357
+ render: () => <Story text={str(text.code, text.footer)} extensions={[decorateMarkdown()]} />,
358
+ };
359
+
360
+ export const Lists = {
361
+ render: () => (
362
+ <Story text={str(text.tasks, '', text.list, '', text.numbered, text.footer)} extensions={[decorateMarkdown()]} />
363
+ ),
364
+ };
365
+
366
+ export const Table = {
367
+ render: () => <Story text={str(text.table, text.footer)} extensions={[table()]} />,
368
+ };
369
+
370
+ export const Autocomplete = {
371
+ render: () => (
372
+ <Story
373
+ text={str('# Autocomplete', '', 'Press Ctrl-Space...', text.footer)}
374
+ extensions={[
375
+ decorateMarkdown({ renderLinkButton }),
376
+ autocomplete({
377
+ onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
378
+ }),
379
+ ]}
380
+ />
381
+ ),
382
+ };
383
+
384
+ export const Mention = {
385
+ render: () => (
386
+ <Story
387
+ text={str('# Mention', '', 'Type @...', text.footer)}
388
+ extensions={[
389
+ mention({
390
+ onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
391
+ }),
392
+ ]}
393
+ />
394
+ ),
395
+ };
396
+
397
+ const CommandDialog: FC<{ onClose: (action?: CommandAction) => void }> = ({ onClose }) => {
398
+ const [text, setText] = useState('');
399
+ const handleInsert = () => {
400
+ onClose(text.length ? { insert: text + '\n' } : undefined);
401
+ };
402
+
403
+ const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
404
+ switch (event.key) {
405
+ case 'Enter': {
406
+ handleInsert();
407
+ break;
408
+ }
409
+ case 'Escape': {
410
+ onClose();
411
+ break;
412
+ }
413
+ }
414
+ };
415
+
416
+ return (
417
+ <DensityProvider density='fine'>
418
+ <div className={mx('flex items-center p-2 gap-2 border rounded-md', baseSurface)}>
419
+ <Input.Root>
420
+ <Input.TextInput
421
+ autoFocus={true}
422
+ placeholder='Enter command.'
423
+ value={text}
424
+ onChange={({ target: { value } }) => setText(value)}
425
+ onKeyDown={handleKeyDown}
426
+ />
427
+ </Input.Root>
428
+ <Button variant='ghost' classNames='pli-0' onClick={() => onClose()}>
429
+ <X className={getSize(5)} />
430
+ </Button>
431
+ </div>
432
+ </DensityProvider>
433
+ );
434
+ };
435
+
436
+ const renderCommandDialog: CommandOptions['onRender'] = (el, onClose) => {
437
+ createRoot(el).render(
438
+ <StrictMode>
439
+ <ThemeProvider tx={defaultTx}>
440
+ <CommandDialog onClose={onClose} />
441
+ </ThemeProvider>
442
+ </StrictMode>,
443
+ );
444
+ };
445
+
446
+ export const Command = {
447
+ render: () => (
448
+ <Story
449
+ text={str('# Command', '')}
450
+ extensions={[command({ onRender: renderCommandDialog, onHint: () => 'Press / for commands.' })]}
451
+ />
452
+ ),
453
+ };
454
+
455
+ export const Comments = {
456
+ render: () => {
457
+ const [_comments, setComments] = useState<Comment[]>([]);
458
+ return (
459
+ <Story
460
+ text={str('# Comments', '', text.paragraphs, text.footer)}
461
+ comments={_comments}
462
+ extensions={[
463
+ comments({
464
+ onHover: onCommentsHover,
465
+ onCreate: ({ cursor }) => {
466
+ const id = PublicKey.random().toHex();
467
+ setComments((commentRanges) => [...commentRanges, { id, cursor }]);
468
+ return id;
469
+ },
470
+ onSelect: (state) => {
471
+ const debug = false;
472
+ if (debug) {
473
+ console.log(
474
+ 'update',
475
+ JSON.stringify({
476
+ comments: state.comments.length,
477
+ active: state.selection.current?.slice(0, 8),
478
+ closest: state.selection.closest?.slice(0, 8),
479
+ }),
480
+ );
481
+ }
482
+ },
483
+ }),
484
+ ]}
485
+ />
486
+ );
487
+ },
488
+ };
489
+
490
+ export const Vim = {
491
+ render: () => (
492
+ <Story
493
+ text={str('# Vim Mode', '', 'The distant future. The year 2000.', '', text.paragraphs)}
494
+ extensions={[defaults, EditorModes.vim]}
495
+ />
496
+ ),
497
+ };
498
+
499
+ export const Annotations = {
500
+ render: () => <Story text={str('# Annotations', '', large)} extensions={[annotations({ match: /volup/gi })]} />,
501
+ };
502
+
503
+ export const DND = {
504
+ render: () => <Story text={str('# DND', '')} extensions={[dnd()]} />,
505
+ };
506
+
507
+ const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
508
+
509
+ export const Listener = {
510
+ render: () => (
511
+ <Story
512
+ text={str('# Listener', '', text.footer)}
513
+ extensions={[
514
+ listener({
515
+ onFocus: (focusing) => {
516
+ console.log({ focusing });
517
+ },
518
+ onChange: (text) => {
519
+ console.log({ text });
520
+ },
521
+ }),
522
+ ]}
523
+ />
524
+ ),
525
+ };
526
+
527
+ export const Typewriter = {
528
+ render: () => (
529
+ <Story
530
+ text={str('# Typewriter', '', text.paragraphs, text.footer)}
531
+ extensions={[typewriter({ items: typewriterItems })]}
532
+ />
533
+ ),
534
+ };
535
+
536
+ export const Blast = {
537
+ render: () => (
538
+ <Story
539
+ text={str('# Blast', '', text.paragraphs, text.code, text.paragraphs)}
540
+ extensions={[
541
+ typewriter({ items: typewriterItems }),
542
+ blast(
543
+ defaultsDeep(
544
+ {
545
+ effect: 2,
546
+ particleGravity: 0.2,
547
+ particleShrinkRate: 0.995,
548
+ color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
549
+ // color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
550
+ },
551
+ defaultOptions,
552
+ ),
553
+ ),
554
+ ]}
555
+ />
556
+ ),
60
557
  };