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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/lib/browser/index.mjs +2174 -935
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +66 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +9 -4
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +12 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +10 -0
  11. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/extensions/{basic/bundle.d.ts → basic.d.ts} +3 -2
  13. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  15. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/code.d.ts +2 -0
  17. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +1 -0
  18. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +48 -0
  19. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  20. package/dist/types/src/components/TextEditor/extensions/hr.d.ts +2 -0
  21. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +1 -0
  22. package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
  23. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
  24. package/dist/types/src/components/TextEditor/extensions/index.d.ts +14 -2
  25. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  26. package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
  27. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
  28. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +9 -0
  29. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
  30. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +10 -1
  31. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  32. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +36 -0
  33. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
  34. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
  35. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
  36. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
  37. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
  38. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +3 -0
  39. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +1 -0
  40. package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
  41. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
  42. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +3 -0
  43. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
  44. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
  45. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
  46. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts +10 -0
  47. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +1 -0
  48. package/dist/types/src/components/TextEditor/extensions/util.d.ts +5 -0
  49. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +1 -0
  50. package/dist/types/src/components/TextEditor/index.d.ts +2 -0
  51. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  52. package/dist/types/src/components/TextEditor/themes/default.d.ts +17 -0
  53. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
  54. package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
  55. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
  56. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +15 -0
  57. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  58. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  59. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +1 -1
  60. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  61. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  62. package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → hooks/automerge/semaphore.d.ts} +4 -1
  63. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
  64. package/dist/types/src/{automerge/automerge-plugin/codeMirrorToAm.d.ts → hooks/automerge/update-automerge.d.ts} +2 -3
  65. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
  66. package/dist/types/src/hooks/automerge/update-codemirror.d.ts +5 -0
  67. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
  68. package/dist/types/src/hooks/index.d.ts +0 -1
  69. package/dist/types/src/hooks/index.d.ts.map +1 -1
  70. package/dist/types/src/hooks/useTextModel.d.ts +19 -5
  71. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  72. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +2 -1
  73. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  74. package/dist/types/src/hooks/yjs/yjs.test.d.ts +2 -0
  75. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
  76. package/dist/types/src/index.d.ts +1 -0
  77. package/dist/types/src/index.d.ts.map +1 -1
  78. package/dist/types/src/styles/markdown.d.ts +14 -11
  79. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  80. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  81. package/package.json +28 -33
  82. package/src/components/TextEditor/MarkdownEditor.stories.tsx +428 -0
  83. package/src/components/TextEditor/TextEditor.stories.tsx +57 -43
  84. package/src/components/TextEditor/TextEditor.tsx +98 -52
  85. package/src/components/TextEditor/extensions/autocomplete.ts +59 -0
  86. package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +8 -4
  87. package/src/components/TextEditor/extensions/blast.ts +373 -0
  88. package/src/components/TextEditor/extensions/code.ts +99 -0
  89. package/src/components/TextEditor/extensions/comments.ts +388 -0
  90. package/src/components/TextEditor/extensions/hr.ts +74 -0
  91. package/src/components/TextEditor/extensions/image.ts +69 -0
  92. package/src/components/TextEditor/extensions/index.ts +14 -2
  93. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +75 -38
  94. package/src/components/TextEditor/extensions/{change.ts → listener.ts} +5 -4
  95. package/src/components/TextEditor/extensions/markdown/bundle.ts +56 -49
  96. package/src/components/TextEditor/extensions/markdown/highlight.ts +204 -0
  97. package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
  98. package/src/components/TextEditor/extensions/mention.ts +31 -0
  99. package/src/components/TextEditor/extensions/mermaid.ts +11 -0
  100. package/src/components/TextEditor/extensions/table.ts +132 -0
  101. package/src/components/TextEditor/extensions/tasklist.ts +121 -0
  102. package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.ts} +9 -9
  103. package/src/components/TextEditor/extensions/typewriter.ts +68 -0
  104. package/src/components/TextEditor/extensions/util.ts +18 -0
  105. package/src/components/TextEditor/index.ts +3 -0
  106. package/src/components/TextEditor/themes/default.ts +302 -0
  107. package/src/components/TextEditor/themes/index.ts +5 -0
  108. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +7 -5
  109. package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +2 -1
  110. package/src/{automerge/automerge-plugin/PatchSemaphore.ts → hooks/automerge/semaphore.ts} +13 -9
  111. package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → hooks/automerge/update-automerge.ts} +5 -2
  112. package/src/{automerge/automerge-plugin/amToCodemirror.ts → hooks/automerge/update-codemirror.ts} +4 -1
  113. package/src/hooks/index.ts +0 -1
  114. package/src/hooks/useTextModel.ts +101 -42
  115. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +2 -1
  116. package/src/hooks/yjs/yjs.test.ts +59 -0
  117. package/src/index.ts +1 -0
  118. package/src/styles/markdown.ts +33 -24
  119. package/src/testing/replicator.ts +6 -6
  120. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  121. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
  122. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  123. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  124. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  125. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  126. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  127. package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
  128. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  129. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  130. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  131. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  132. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  133. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  134. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  135. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  136. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  137. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  138. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  139. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  140. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
  141. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  142. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  143. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  144. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  145. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  146. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  147. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  149. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  150. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  151. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  152. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  153. package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
  154. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  155. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  156. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  157. package/src/hooks/useCollaboration.ts +0 -45
  158. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  159. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  160. package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  161. package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +1 -1
@@ -0,0 +1,428 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import { EditorView } from '@codemirror/view';
8
+ import { faker } from '@faker-js/faker';
9
+ import { ArrowSquareOut } from '@phosphor-icons/react';
10
+ import defaultsDeep from 'lodash.defaultsdeep';
11
+ import React, { type FC, StrictMode, useRef, useState } from 'react';
12
+ import { createRoot } from 'react-dom/client';
13
+
14
+ import { TextObject } from '@dxos/echo-schema';
15
+ import { PublicKey } from '@dxos/keys';
16
+ import { fixedInsetFlexLayout, getSize, groupSurface, mx } from '@dxos/react-ui-theme';
17
+ import { withTheme } from '@dxos/storybook-utils';
18
+
19
+ import { MarkdownEditor, type TextEditorProps, type TextEditorRef } from './TextEditor';
20
+ import {
21
+ autocomplete,
22
+ blast,
23
+ code,
24
+ comments,
25
+ defaultOptions,
26
+ hr,
27
+ image,
28
+ listener,
29
+ link,
30
+ mention,
31
+ table,
32
+ tasklist,
33
+ tooltip,
34
+ typewriter,
35
+ type CommentsOptions,
36
+ type TooltipOptions,
37
+ type LinkOptions,
38
+ mermaid,
39
+ } from './extensions';
40
+ import { type CommentRange, useTextModel } from '../../hooks';
41
+
42
+ // Extensions:
43
+ // TODO(burdon): Table of contents.
44
+ // TODO(burdon): Front-matter
45
+
46
+ faker.seed(101);
47
+
48
+ const str = (...lines: string[]) => lines.join('\n');
49
+
50
+ const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
51
+
52
+ // prettier-ignore
53
+ const text = {
54
+ tasks: str(
55
+ '## Tasks',
56
+ '',
57
+ '- [x] decorator',
58
+ '- [ ] checkbox',
59
+ ' - [ ] state',
60
+ ' - [ ] indent',
61
+ ' - [x] style',
62
+ '',
63
+ ),
64
+
65
+ list: str(
66
+ '## List',
67
+ '',
68
+ '- new york',
69
+ '- london',
70
+ '- tokyo',
71
+ '',
72
+ ),
73
+
74
+ numbered: str(
75
+ '## Numbered',
76
+ '',
77
+ '1. one',
78
+ '2. two',
79
+ '3. three',
80
+ ''
81
+ ),
82
+
83
+ code: str(
84
+ '## Code',
85
+ '',
86
+ '```',
87
+ 'const x = 100;',
88
+ '```',
89
+ '',
90
+ '```tsx',
91
+ 'const Component = () => {',
92
+ ' const x = 100;',
93
+ '',
94
+ ' return () => <div>Test</div>;',
95
+ '};',
96
+ '```',
97
+ '',
98
+ ),
99
+
100
+ links: str(
101
+ '## Links',
102
+ '',
103
+ 'This is a naked link https://dxos.org within a sentence.',
104
+ '',
105
+ 'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
106
+ '',
107
+ ),
108
+
109
+ table: str(
110
+ '# Table',
111
+ '',
112
+ `| ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} |`,
113
+ `|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|`,
114
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
115
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
116
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
117
+ '', // TODO(burdon): Possible GFM parsing bug if no newline?
118
+ ),
119
+
120
+ image: str('# Image', '', '![dxos](https://pbs.twimg.com/profile_banners/1268328127673044992/1684766689/1500x500)'),
121
+
122
+ headings: str(
123
+ ...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
124
+ ),
125
+
126
+ paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
127
+
128
+ mermaid: str('```mermaid', 'graph TD;', 'A-->B;', 'A-->C;', 'B-->D;', 'C-->D;', '```'),
129
+
130
+ footer: str('', '', '', '', '')
131
+ };
132
+
133
+ const document = str(
134
+ '# Markdown',
135
+ '',
136
+ '> This is a block quote.',
137
+ '',
138
+ 'This is all about https://dxos.org and related technologies.',
139
+ '',
140
+ 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.',
141
+ '',
142
+ '---',
143
+ text.links,
144
+ '---',
145
+ text.list,
146
+ '---',
147
+ text.tasks,
148
+ '---',
149
+ text.numbered,
150
+ '---',
151
+ text.code,
152
+ '---',
153
+ text.headings,
154
+ '---',
155
+ text.table,
156
+ '---',
157
+ text.image,
158
+ text.footer,
159
+ );
160
+
161
+ const links = [
162
+ { label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
163
+ { label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
164
+ { label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
165
+ { label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
166
+ { label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
167
+ ];
168
+
169
+ const hover =
170
+ 'rounded-sm text-base text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
171
+
172
+ const onTooltipHover: TooltipOptions['onHover'] = (el, url) => {
173
+ const web = new URL(url);
174
+ createRoot(el).render(
175
+ <StrictMode>
176
+ <a href={url} target='_blank' rel='noreferrer' className={hover}>
177
+ {web.origin}
178
+ <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1')} />
179
+ </a>
180
+ </StrictMode>,
181
+ );
182
+ };
183
+
184
+ const Key: FC<{ char: string }> = ({ char }) => (
185
+ <span className='flex justify-center items-center w-[24px] h-[24px] rounded text-xs bg-neutral-200 text-black'>
186
+ {char}
187
+ </span>
188
+ );
189
+
190
+ const onCommentsHover: CommentsOptions['onHover'] = (el) => {
191
+ createRoot(el).render(
192
+ <StrictMode>
193
+ <div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
194
+ <div>Create comment</div>
195
+ {/* TODO(burdon): Unify shortcuts. */}
196
+ <div className='flex gap-1'>
197
+ <Key char='⌘' />
198
+ <Key char="'" />
199
+ </div>
200
+ </div>
201
+ </StrictMode>,
202
+ );
203
+ };
204
+
205
+ const onLinkRender: LinkOptions['onRender'] = (el, url) => {
206
+ createRoot(el).render(
207
+ <StrictMode>
208
+ <a href={url} target='_blank' rel='noreferrer' className={hover}>
209
+ <ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-1')} />
210
+ </a>
211
+ </StrictMode>,
212
+ );
213
+ };
214
+
215
+ type StoryProps = {
216
+ text?: string;
217
+ automerge?: boolean;
218
+ } & Pick<TextEditorProps, 'comments' | 'readonly' | 'extensions' | 'slots'>;
219
+
220
+ const Story = ({ text, automerge, ...props }: StoryProps) => {
221
+ const ref = useRef<TextEditorRef>(null);
222
+ const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
223
+ const model = useTextModel({ text: item.text });
224
+ if (!model) {
225
+ return null;
226
+ }
227
+
228
+ return (
229
+ <div className={mx(fixedInsetFlexLayout, groupSurface)}>
230
+ <div className='flex justify-center overflow-y-scroll'>
231
+ <div className='flex flex-col w-[800px] py-16'>
232
+ <MarkdownEditor ref={ref} model={model} {...props} />
233
+ <div className='flex shrink-0 h-[300px]'></div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ );
238
+ };
239
+
240
+ export default {
241
+ title: 'react-ui-editor/MarkdownEditor',
242
+ component: MarkdownEditor,
243
+ decorators: [withTheme],
244
+ render: Story,
245
+ };
246
+
247
+ const extensions = [
248
+ autocomplete({
249
+ onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
250
+ }),
251
+ code(),
252
+ hr(),
253
+ image(),
254
+ link({ onRender: onLinkRender }),
255
+ table(),
256
+ tasklist(),
257
+ tooltip({ onHover: onTooltipHover }),
258
+ ];
259
+
260
+ export const Default = {
261
+ render: () => <Story text={document} extensions={extensions} />,
262
+ };
263
+
264
+ export const Readonly = {
265
+ render: () => <Story text={document} extensions={extensions} readonly />,
266
+ };
267
+
268
+ export const NoExtensions = {
269
+ render: () => <Story text={document} />,
270
+ };
271
+
272
+ // TODO(burdon): Cursor bug if no newline after BR (cursor down just loops back to start of each paragraph).
273
+ export const HorizontalRule = {
274
+ render: () => (
275
+ <Story
276
+ text={str('# Horizontal Rule', '', text.paragraphs, '---', '', text.paragraphs, '---', '', text.paragraphs)}
277
+ extensions={[hr()]}
278
+ />
279
+ ),
280
+ };
281
+
282
+ export const Tooltips = {
283
+ render: () => <Story text={str(text.links, text.footer)} extensions={[tooltip({ onHover: onTooltipHover })]} />,
284
+ };
285
+
286
+ export const Links = {
287
+ render: () => <Story text={str(text.links, text.footer)} extensions={[link({ onRender: onLinkRender })]} />,
288
+ };
289
+
290
+ export const Code = {
291
+ render: () => <Story text={str(text.code, text.footer)} extensions={[code()]} readonly />,
292
+ };
293
+
294
+ export const Table = {
295
+ render: () => <Story text={str(text.table, text.footer)} extensions={[table()]} />,
296
+ };
297
+
298
+ export const Image = {
299
+ render: () => <Story text={str(text.image, text.footer)} readonly extensions={[image()]} />,
300
+ };
301
+
302
+ export const Tasklist = {
303
+ render: () => (
304
+ <Story
305
+ text={str(text.tasks, '', text.list, text.footer)}
306
+ extensions={[
307
+ tasklist(),
308
+ listener({
309
+ onChange: (text) => {
310
+ console.log(text);
311
+ },
312
+ }),
313
+ ]}
314
+ />
315
+ ),
316
+ };
317
+
318
+ export const Autocomplete = {
319
+ render: () => (
320
+ <Story
321
+ text={str('# Autocomplete', '', 'Press CTRL-SPACE', text.footer)}
322
+ extensions={[
323
+ link({ onRender: onLinkRender }),
324
+ autocomplete({
325
+ onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
326
+ }),
327
+ ]}
328
+ />
329
+ ),
330
+ };
331
+
332
+ const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
333
+
334
+ export const Mention = {
335
+ render: () => (
336
+ <Story
337
+ text={str('# Mention', '', 'Type @...', text.footer)}
338
+ extensions={[
339
+ mention({
340
+ onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
341
+ }),
342
+ ]}
343
+ />
344
+ ),
345
+ };
346
+
347
+ export const Mermaid = {
348
+ render: () => <Story text={str('# Mermaid', '', text.mermaid, text.footer)} extensions={[code(), mermaid()]} />,
349
+ };
350
+
351
+ export const Comments = {
352
+ render: () => {
353
+ const [commentsStates, setCommentStates] = useState<CommentRange[]>([]);
354
+
355
+ return (
356
+ <Story
357
+ text={str('# Comments', '', text.paragraphs, text.footer)}
358
+ comments={commentsStates}
359
+ extensions={[
360
+ comments({
361
+ onHover: onCommentsHover,
362
+ onCreate: (relPos) => {
363
+ const id = PublicKey.random().toHex();
364
+ setCommentStates((comments) => [...comments, { id, cursor: relPos }]);
365
+ return id;
366
+ },
367
+ onSelect: (state) => {
368
+ const debug = false;
369
+ if (debug) {
370
+ console.log(
371
+ 'update',
372
+ JSON.stringify({
373
+ active: state.active?.slice(0, 8),
374
+ closest: state.closest?.slice(0, 8),
375
+ ranges: state.ranges.length,
376
+ }),
377
+ );
378
+ }
379
+ },
380
+ }),
381
+ ]}
382
+ />
383
+ );
384
+ },
385
+ };
386
+
387
+ export const Typewriter = {
388
+ render: () => <Story text={str('# Typewriter', '', text.paragraphs, text.footer)} extensions={[typewriter()]} />,
389
+ };
390
+
391
+ export const Blast = {
392
+ render: () => (
393
+ <Story
394
+ text={str('# Blast', '', text.paragraphs, text.code, text.paragraphs)}
395
+ extensions={[
396
+ typewriter({
397
+ items: localStorage.getItem('dxos.composer.extension.demo')?.split(','),
398
+ }),
399
+ blast(
400
+ defaultsDeep(
401
+ {
402
+ effect: 2,
403
+ particleGravity: 0.2,
404
+ particleShrinkRate: 0.995,
405
+ color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
406
+ // color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
407
+ },
408
+ defaultOptions,
409
+ ),
410
+ ),
411
+ ]}
412
+ />
413
+ ),
414
+ };
415
+
416
+ export const Diagnostics = {
417
+ render: () => (
418
+ <Story
419
+ text={document}
420
+ extensions={[
421
+ // Cursor moved.
422
+ EditorView.updateListener.of((update) => {
423
+ console.log('update', update.view.state.selection.main.head);
424
+ }),
425
+ ]}
426
+ />
427
+ ),
428
+ };
@@ -3,63 +3,46 @@
3
3
  //
4
4
 
5
5
  import '@dxosTheme';
6
- import { markdown } from '@codemirror/lang-markdown';
7
- import { ArrowCircleUp } from '@phosphor-icons/react';
8
- import React, { StrictMode, useState } from 'react';
9
- import { createRoot } from 'react-dom/client';
6
+
7
+ import defaultsDeep from 'lodash.defaultsdeep';
8
+ import React, { useRef, useState } from 'react';
10
9
 
11
10
  import { TextObject } from '@dxos/echo-schema';
12
- import { fixedInsetFlexLayout, getSize, groupSurface, inputSurface, mx } from '@dxos/react-ui-theme';
11
+ import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
13
12
  import { withTheme } from '@dxos/storybook-utils';
14
13
 
15
- import { TextEditor } from './TextEditor';
16
- import { createHyperlinkTooltip, hyperlinkDecoration } from './extensions';
14
+ import { defaultSlots, TextEditor, type TextEditorProps, type TextEditorRef, type TextEditorSlots } from './TextEditor';
15
+ import { textTheme } from './themes';
17
16
  import { useTextModel } from '../../hooks';
18
17
 
19
- const text = [
20
- '',
21
- '',
22
- 'This is all about [DXOS](https://dxos.org); read more [here](https://docs.dxos.org/guide/getting-started.html).',
18
+ const initialText = [
19
+ '# TextEditor',
23
20
  '',
21
+ 'This is the basic plain text editor within minimal formatting.',
22
+ 'You can add custom styles and create custom extensions.',
23
+ 'Or use the MarkdownEditor to edit documents.',
24
24
  '',
25
+ 'https://dxos.org',
25
26
  ].join('\n');
26
27
 
27
- const hyperLinkTooltip = () =>
28
- createHyperlinkTooltip((el, url) => {
29
- const web = new URL(url);
30
- createRoot(el).render(
31
- <StrictMode>
32
- <div className='flex gap-1 items-center'>
33
- <ArrowCircleUp className={mx(getSize(6), 'text-blue-500')} />
34
- <p className='pr-1'>{web.origin}</p>
35
- </div>
36
- </StrictMode>,
37
- );
38
- });
39
-
40
- const Story = ({ automerge }: { automerge?: boolean }) => {
41
- const [item] = useState({ text: new TextObject(text, undefined, undefined, { useAutomergeBackend: !!automerge }) });
28
+ const Story = ({
29
+ text,
30
+ automerge,
31
+ ...props
32
+ }: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
33
+ const ref = useRef<TextEditorRef>(null);
34
+ const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
42
35
  const model = useTextModel({ text: item.text });
43
36
  if (!model) {
44
- return <></>;
37
+ return null;
45
38
  }
46
39
 
47
40
  return (
48
41
  <div className={mx(fixedInsetFlexLayout, groupSurface)}>
49
- <div className='flex justify-center p-8'>
50
- <div className='w-[800px]'>
51
- <TextEditor
52
- model={model}
53
- extensions={[
54
- markdown(),
55
- hyperlinkDecoration({ link: false }),
56
- hyperLinkTooltip(),
57
- // EditorView.domEventHandlers({
58
- // mousedown: (e, view) => {},
59
- // }),
60
- ]}
61
- slots={{ root: { className: mx(inputSurface, 'p-2') } }}
62
- />
42
+ <div className='flex justify-center overflow-y-scroll'>
43
+ <div className='flex flex-col w-[800px] py-16'>
44
+ <TextEditor ref={ref} model={model} {...props} />
45
+ <div className='flex shrink-0 h-[300px]'></div>
63
46
  </div>
64
47
  </div>
65
48
  </div>
@@ -67,13 +50,44 @@ const Story = ({ automerge }: { automerge?: boolean }) => {
67
50
  };
68
51
 
69
52
  export default {
53
+ title: 'react-ui-editor/TextEditor',
70
54
  component: TextEditor,
71
55
  decorators: [withTheme],
72
56
  render: Story,
73
57
  };
74
58
 
75
- export const Default = {};
59
+ export const Default = {
60
+ render: () => (
61
+ <Story
62
+ slots={defaultsDeep(
63
+ { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
64
+ defaultSlots,
65
+ )}
66
+ />
67
+ ),
68
+ };
69
+
70
+ export const Text = {
71
+ render: () => (
72
+ <Story
73
+ text={initialText}
74
+ slots={defaultsDeep(
75
+ { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
76
+ defaultSlots,
77
+ )}
78
+ />
79
+ ),
80
+ };
76
81
 
77
82
  export const Automerge = {
78
- render: () => <Story automerge />,
83
+ render: () => (
84
+ <Story
85
+ automerge
86
+ text={initialText}
87
+ slots={defaultsDeep(
88
+ { editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
89
+ defaultSlots,
90
+ )}
91
+ />
92
+ ),
79
93
  };