@dxos/react-ui-editor 0.4.8-main.27faba7 → 0.4.8-main.512e4c8
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.
- package/dist/lib/browser/index.mjs +920 -1084
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +18 -34
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +79 -5
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts +1 -15
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/index.d.ts +0 -1
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts +2 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/cursor-line-margin.d.ts +6 -7
- package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -1
- package/dist/types/src/extensions/doc.d.ts +3 -0
- package/dist/types/src/extensions/doc.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +1 -1
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts +5 -4
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/table.d.ts +2 -2
- package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
- package/dist/types/src/hooks/defs.d.ts +0 -11
- package/dist/types/src/hooks/defs.d.ts.map +1 -1
- package/dist/types/src/hooks/extension-utils.d.ts +54 -0
- package/dist/types/src/hooks/extension-utils.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +1 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts +3 -44
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.stories.d.ts +6 -0
- package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts +0 -3
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/components/TextEditor/TextEditor.stories.tsx +510 -23
- package/src/components/TextEditor/TextEditor.tsx +37 -149
- package/src/components/Toolbar/Toolbar.stories.tsx +46 -42
- package/src/extensions/automerge/automerge.stories.tsx +14 -16
- package/src/extensions/automerge/automerge.ts +2 -1
- package/src/extensions/automerge/cursor.ts +1 -1
- package/src/extensions/automerge/defs.ts +1 -22
- package/src/extensions/automerge/index.ts +0 -1
- package/src/extensions/automerge/sync.ts +2 -9
- package/src/extensions/automerge/update-automerge.ts +2 -1
- package/src/extensions/command/state.ts +1 -1
- package/src/extensions/comments.ts +8 -4
- package/src/extensions/cursor-line-margin.ts +17 -18
- package/src/extensions/doc.ts +14 -0
- package/src/extensions/index.ts +1 -1
- package/src/extensions/markdown/bundle.ts +11 -28
- package/src/extensions/markdown/decorate.ts +1 -3
- package/src/extensions/markdown/formatting.ts +15 -10
- package/src/extensions/markdown/image.ts +1 -0
- package/src/extensions/markdown/table.ts +11 -3
- package/src/hooks/defs.ts +0 -15
- package/src/hooks/extension-utils.ts +185 -0
- package/src/hooks/index.ts +1 -2
- package/src/hooks/useTextEditor.stories.tsx +39 -34
- package/src/hooks/useTextEditor.ts +11 -120
- package/src/index.ts +1 -1
- package/src/themes/default.ts +1 -28
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +0 -103
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner.d.ts +0 -4
- package/dist/types/src/extensions/outliner.d.ts.map +0 -1
- package/dist/types/src/hooks/useEditorView.d.ts +0 -20
- package/dist/types/src/hooks/useEditorView.d.ts.map +0 -1
- package/dist/types/src/hooks/useTextModel.d.ts +0 -33
- package/dist/types/src/hooks/useTextModel.d.ts.map +0 -1
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +0 -524
- package/src/extensions/outliner.ts +0 -12
- package/src/hooks/useEditorView.ts +0 -32
- package/src/hooks/useTextModel.ts +0 -121
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import '@dxosTheme';
|
|
6
|
-
|
|
7
|
-
import { type EditorView } from '@codemirror/view';
|
|
8
|
-
import { ArrowSquareOut, X } from '@phosphor-icons/react';
|
|
9
|
-
import defaultsDeep from 'lodash.defaultsdeep';
|
|
10
|
-
import React, { type FC, type KeyboardEvent, StrictMode, useRef, useState } from 'react';
|
|
11
|
-
import { createRoot } from 'react-dom/client';
|
|
12
|
-
|
|
13
|
-
import { TextObject } from '@dxos/echo-schema';
|
|
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 } from '@dxos/react-ui';
|
|
18
|
-
import { baseSurface, defaultTx, getSize, mx, textBlockWidth } from '@dxos/react-ui-theme';
|
|
19
|
-
import { withTheme } from '@dxos/storybook-utils';
|
|
20
|
-
|
|
21
|
-
import { MarkdownEditor, type TextEditorProps } from './TextEditor';
|
|
22
|
-
import {
|
|
23
|
-
annotations,
|
|
24
|
-
autocomplete,
|
|
25
|
-
blast,
|
|
26
|
-
command,
|
|
27
|
-
comments,
|
|
28
|
-
decorateMarkdown,
|
|
29
|
-
defaultOptions,
|
|
30
|
-
dnd,
|
|
31
|
-
formatting,
|
|
32
|
-
image,
|
|
33
|
-
linkTooltip,
|
|
34
|
-
mention,
|
|
35
|
-
table,
|
|
36
|
-
typewriter,
|
|
37
|
-
useComments,
|
|
38
|
-
EditorModes,
|
|
39
|
-
type CommandAction,
|
|
40
|
-
type CommandOptions,
|
|
41
|
-
type CommentsOptions,
|
|
42
|
-
listener,
|
|
43
|
-
} from '../../extensions';
|
|
44
|
-
import { type Comment, useTextModel } from '../../hooks';
|
|
45
|
-
import translations from '../../translations';
|
|
46
|
-
|
|
47
|
-
faker.seed(101);
|
|
48
|
-
|
|
49
|
-
const str = (...lines: string[]) => lines.join('\n');
|
|
50
|
-
|
|
51
|
-
const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
|
|
52
|
-
|
|
53
|
-
const img = '';
|
|
54
|
-
|
|
55
|
-
const text = {
|
|
56
|
-
tasks: str(
|
|
57
|
-
//
|
|
58
|
-
'## Tasks',
|
|
59
|
-
'',
|
|
60
|
-
'- [x] decorator',
|
|
61
|
-
'- [ ] checkbox',
|
|
62
|
-
' - [ ] state',
|
|
63
|
-
' - [ ] indent',
|
|
64
|
-
' - [x] style',
|
|
65
|
-
'',
|
|
66
|
-
),
|
|
67
|
-
|
|
68
|
-
list: str(
|
|
69
|
-
//
|
|
70
|
-
'## List',
|
|
71
|
-
'',
|
|
72
|
-
'- new york',
|
|
73
|
-
'- london',
|
|
74
|
-
'- tokyo',
|
|
75
|
-
'',
|
|
76
|
-
),
|
|
77
|
-
|
|
78
|
-
numbered: str(
|
|
79
|
-
//
|
|
80
|
-
'## Numbered',
|
|
81
|
-
'',
|
|
82
|
-
'1. one',
|
|
83
|
-
'2. two',
|
|
84
|
-
'3. three',
|
|
85
|
-
'',
|
|
86
|
-
),
|
|
87
|
-
|
|
88
|
-
code: str(
|
|
89
|
-
'## Code',
|
|
90
|
-
'',
|
|
91
|
-
'```',
|
|
92
|
-
'$ ls -las',
|
|
93
|
-
'```',
|
|
94
|
-
'',
|
|
95
|
-
'```tsx',
|
|
96
|
-
'const Component = () => {',
|
|
97
|
-
' const x = 100;',
|
|
98
|
-
'',
|
|
99
|
-
' return () => <div>Test</div>;',
|
|
100
|
-
'};',
|
|
101
|
-
'```',
|
|
102
|
-
'',
|
|
103
|
-
),
|
|
104
|
-
|
|
105
|
-
links: str(
|
|
106
|
-
'## Links',
|
|
107
|
-
'',
|
|
108
|
-
'This is a naked link https://dxos.org within a sentence.',
|
|
109
|
-
'',
|
|
110
|
-
'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
|
|
111
|
-
'',
|
|
112
|
-
),
|
|
113
|
-
|
|
114
|
-
table: str(
|
|
115
|
-
'# Table',
|
|
116
|
-
'',
|
|
117
|
-
`| ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} |`,
|
|
118
|
-
`|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|`,
|
|
119
|
-
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
|
120
|
-
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
|
121
|
-
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
|
122
|
-
'',
|
|
123
|
-
),
|
|
124
|
-
|
|
125
|
-
image: str('# Image', '', img),
|
|
126
|
-
|
|
127
|
-
headings: str(
|
|
128
|
-
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
|
129
|
-
),
|
|
130
|
-
|
|
131
|
-
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
|
132
|
-
|
|
133
|
-
footer: str('', '', '', '', ''),
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const document = str(
|
|
137
|
-
'# Markdown',
|
|
138
|
-
'',
|
|
139
|
-
'> This is a block quote.',
|
|
140
|
-
'',
|
|
141
|
-
'> 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.',
|
|
142
|
-
'',
|
|
143
|
-
'> This is',
|
|
144
|
-
'> a multi-line',
|
|
145
|
-
'> block quote.',
|
|
146
|
-
'',
|
|
147
|
-
'This is all about https://dxos.org and related technologies.',
|
|
148
|
-
'',
|
|
149
|
-
'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.',
|
|
150
|
-
'',
|
|
151
|
-
'---',
|
|
152
|
-
text.links,
|
|
153
|
-
'---',
|
|
154
|
-
text.list,
|
|
155
|
-
'---',
|
|
156
|
-
text.tasks,
|
|
157
|
-
'---',
|
|
158
|
-
text.numbered,
|
|
159
|
-
'---',
|
|
160
|
-
text.code,
|
|
161
|
-
'---',
|
|
162
|
-
text.headings,
|
|
163
|
-
'---',
|
|
164
|
-
text.table,
|
|
165
|
-
'---',
|
|
166
|
-
text.image,
|
|
167
|
-
text.footer,
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
const links = [
|
|
171
|
-
{ label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
|
|
172
|
-
{ label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
|
|
173
|
-
{ label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
|
|
174
|
-
{ label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
|
|
175
|
-
{ label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
|
|
176
|
-
];
|
|
177
|
-
|
|
178
|
-
const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
|
|
179
|
-
|
|
180
|
-
const hover =
|
|
181
|
-
'rounded-sm text-base text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
|
|
182
|
-
|
|
183
|
-
const renderLinkTooltip = (el: Element, url: string) => {
|
|
184
|
-
const web = new URL(url);
|
|
185
|
-
createRoot(el).render(
|
|
186
|
-
<StrictMode>
|
|
187
|
-
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
188
|
-
{web.origin}
|
|
189
|
-
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1')} />
|
|
190
|
-
</a>
|
|
191
|
-
</StrictMode>,
|
|
192
|
-
);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const Key: FC<{ char: string }> = ({ char }) => (
|
|
196
|
-
<span className='flex justify-center items-center w-[24px] h-[24px] rounded text-xs bg-neutral-200 text-black'>
|
|
197
|
-
{char}
|
|
198
|
-
</span>
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const onCommentsHover: CommentsOptions['onHover'] = (el, shortcut) => {
|
|
202
|
-
createRoot(el).render(
|
|
203
|
-
<StrictMode>
|
|
204
|
-
<div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
|
|
205
|
-
<div>Create comment</div>
|
|
206
|
-
<div className='flex gap-1'>
|
|
207
|
-
{keySymbols(parseShortcut(shortcut)).map((char) => (
|
|
208
|
-
<Key key={char} char={char} />
|
|
209
|
-
))}
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
</StrictMode>,
|
|
213
|
-
);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const renderLinkButton = (el: Element, url: string) => {
|
|
217
|
-
createRoot(el).render(
|
|
218
|
-
<StrictMode>
|
|
219
|
-
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
220
|
-
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-[2px]')} />
|
|
221
|
-
</a>
|
|
222
|
-
</StrictMode>,
|
|
223
|
-
);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
type StoryProps = {
|
|
227
|
-
id?: string;
|
|
228
|
-
text?: string;
|
|
229
|
-
comments?: Comment[];
|
|
230
|
-
} & Pick<TextEditorProps, 'readonly' | 'placeholder' | 'extensions'>;
|
|
231
|
-
|
|
232
|
-
const Story = ({ id = 'test', text, comments, placeholder = 'New document.', ...props }: StoryProps) => {
|
|
233
|
-
const [item] = useState({ text: new TextObject(text) });
|
|
234
|
-
const view = useRef<EditorView>(null);
|
|
235
|
-
const model = useTextModel({ text: item.text });
|
|
236
|
-
useComments(view.current, id, comments);
|
|
237
|
-
if (!model) {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<MarkdownEditor
|
|
243
|
-
ref={view}
|
|
244
|
-
model={model}
|
|
245
|
-
placeholder={placeholder}
|
|
246
|
-
slots={{
|
|
247
|
-
root: { className: mx(textBlockWidth, 'min-bs-dvh') },
|
|
248
|
-
editor: { className: 'min-bs-dvh px-8 bg-white dark:bg-black' },
|
|
249
|
-
}}
|
|
250
|
-
{...props}
|
|
251
|
-
/>
|
|
252
|
-
);
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
export default {
|
|
256
|
-
title: 'react-ui-editor/MarkdownEditor',
|
|
257
|
-
component: MarkdownEditor,
|
|
258
|
-
decorators: [withTheme],
|
|
259
|
-
render: Story,
|
|
260
|
-
parameters: { translations, layout: 'fullscreen' },
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const defaults = [
|
|
264
|
-
autocomplete({
|
|
265
|
-
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
266
|
-
}),
|
|
267
|
-
decorateMarkdown({ renderLinkButton }),
|
|
268
|
-
formatting(),
|
|
269
|
-
image(),
|
|
270
|
-
linkTooltip(renderLinkTooltip),
|
|
271
|
-
table(),
|
|
272
|
-
];
|
|
273
|
-
|
|
274
|
-
export const Default = {
|
|
275
|
-
render: () => <Story text={document} extensions={defaults} />,
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
export const Readonly = {
|
|
279
|
-
render: () => <Story text={document} extensions={defaults} readonly />,
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
export const NoExtensions = {
|
|
283
|
-
render: () => <Story text={document} />,
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const large = faker.helpers.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 }).join('\n\n');
|
|
287
|
-
|
|
288
|
-
const largeWithImages = faker.helpers
|
|
289
|
-
.multiple(() => [faker.lorem.paragraph({ min: 12, max: 16 }), img], { count: 20 })
|
|
290
|
-
.flatMap((x) => x)
|
|
291
|
-
.join('\n\n');
|
|
292
|
-
|
|
293
|
-
export const Empty = {
|
|
294
|
-
render: () => <Story />,
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
export const Scrolling = {
|
|
298
|
-
render: () => <Story text={str('# Large Document', '', large)} />,
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
export const ScrollingWithImages = {
|
|
302
|
-
render: () => <Story text={str('# Large Document', '', largeWithImages)} extensions={[image()]} />,
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
export const Links = {
|
|
306
|
-
render: () => <Story text={str(text.links, text.footer)} extensions={[linkTooltip(renderLinkTooltip)]} />,
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
export const Image = {
|
|
310
|
-
render: () => <Story text={str(text.image, text.footer)} extensions={[image()]} />,
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
export const Code = {
|
|
314
|
-
render: () => <Story text={str(text.code, text.footer)} extensions={[decorateMarkdown()]} />,
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
export const Lists = {
|
|
318
|
-
render: () => (
|
|
319
|
-
<Story text={str(text.tasks, '', text.list, '', text.numbered, text.footer)} extensions={[decorateMarkdown()]} />
|
|
320
|
-
),
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
export const Table = {
|
|
324
|
-
render: () => <Story text={str(text.table, text.footer)} extensions={[table()]} />,
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
// export const Outliner = {
|
|
328
|
-
// render: () => (
|
|
329
|
-
// <Story
|
|
330
|
-
// text={str('# Outliner', '', 'Block', ': this is a block', ': with multiple lines', text.footer)}
|
|
331
|
-
// extensions={[outliner()]}
|
|
332
|
-
// />
|
|
333
|
-
// ),
|
|
334
|
-
// };
|
|
335
|
-
|
|
336
|
-
export const Autocomplete = {
|
|
337
|
-
render: () => (
|
|
338
|
-
<Story
|
|
339
|
-
text={str('# Autocomplete', '', 'Press Ctrl-Space...', text.footer)}
|
|
340
|
-
extensions={[
|
|
341
|
-
decorateMarkdown({ renderLinkButton }),
|
|
342
|
-
autocomplete({
|
|
343
|
-
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
344
|
-
}),
|
|
345
|
-
]}
|
|
346
|
-
/>
|
|
347
|
-
),
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
export const Mention = {
|
|
351
|
-
render: () => (
|
|
352
|
-
<Story
|
|
353
|
-
text={str('# Mention', '', 'Type @...', text.footer)}
|
|
354
|
-
extensions={[
|
|
355
|
-
mention({
|
|
356
|
-
onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
|
|
357
|
-
}),
|
|
358
|
-
]}
|
|
359
|
-
/>
|
|
360
|
-
),
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
const CommandDialog: FC<{ onClose: (action?: CommandAction) => void }> = ({ onClose }) => {
|
|
364
|
-
const [text, setText] = useState('');
|
|
365
|
-
const handleInsert = () => {
|
|
366
|
-
onClose(text.length ? { insert: text + '\n' } : undefined);
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
370
|
-
switch (event.key) {
|
|
371
|
-
case 'Enter': {
|
|
372
|
-
handleInsert();
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
case 'Escape': {
|
|
376
|
-
onClose();
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
return (
|
|
383
|
-
<DensityProvider density='fine'>
|
|
384
|
-
<div className={mx('flex items-center p-2 gap-2 border rounded-md', baseSurface)}>
|
|
385
|
-
<Input.Root>
|
|
386
|
-
<Input.TextInput
|
|
387
|
-
autoFocus={true}
|
|
388
|
-
placeholder='Enter command.'
|
|
389
|
-
value={text}
|
|
390
|
-
onChange={({ target: { value } }) => setText(value)}
|
|
391
|
-
onKeyDown={handleKeyDown}
|
|
392
|
-
/>
|
|
393
|
-
</Input.Root>
|
|
394
|
-
<Button variant='ghost' classNames='pli-0' onClick={() => onClose()}>
|
|
395
|
-
<X className={getSize(5)} />
|
|
396
|
-
</Button>
|
|
397
|
-
</div>
|
|
398
|
-
</DensityProvider>
|
|
399
|
-
);
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
const renderCommandDialog: CommandOptions['onRender'] = (el, onClose) => {
|
|
403
|
-
createRoot(el).render(
|
|
404
|
-
<StrictMode>
|
|
405
|
-
<ThemeProvider tx={defaultTx}>
|
|
406
|
-
<CommandDialog onClose={onClose} />
|
|
407
|
-
</ThemeProvider>
|
|
408
|
-
</StrictMode>,
|
|
409
|
-
);
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
export const Command = {
|
|
413
|
-
render: () => (
|
|
414
|
-
<Story
|
|
415
|
-
text={str('# Command', '')}
|
|
416
|
-
extensions={[command({ onRender: renderCommandDialog, onHint: () => 'Press / for commands.' })]}
|
|
417
|
-
/>
|
|
418
|
-
),
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
export const Comments = {
|
|
422
|
-
render: () => {
|
|
423
|
-
const [_comments, setComments] = useState<Comment[]>([]);
|
|
424
|
-
|
|
425
|
-
return (
|
|
426
|
-
<Story
|
|
427
|
-
text={str('# Comments', '', text.paragraphs, text.footer)}
|
|
428
|
-
comments={_comments}
|
|
429
|
-
extensions={[
|
|
430
|
-
comments({
|
|
431
|
-
onHover: onCommentsHover,
|
|
432
|
-
onCreate: ({ cursor }) => {
|
|
433
|
-
const id = PublicKey.random().toHex();
|
|
434
|
-
setComments((commentRanges) => [...commentRanges, { id, cursor }]);
|
|
435
|
-
return id;
|
|
436
|
-
},
|
|
437
|
-
onSelect: (state) => {
|
|
438
|
-
const debug = false;
|
|
439
|
-
if (debug) {
|
|
440
|
-
console.log(
|
|
441
|
-
'update',
|
|
442
|
-
JSON.stringify({
|
|
443
|
-
comments: state.comments.length,
|
|
444
|
-
active: state.selection.current?.slice(0, 8),
|
|
445
|
-
closest: state.selection.closest?.slice(0, 8),
|
|
446
|
-
}),
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
}),
|
|
451
|
-
]}
|
|
452
|
-
/>
|
|
453
|
-
);
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
export const Vim = {
|
|
458
|
-
render: () => (
|
|
459
|
-
<Story
|
|
460
|
-
text={str('# Vim Mode', '', 'The distant future. The year 2000.', '', text.paragraphs)}
|
|
461
|
-
extensions={[defaults, EditorModes.vim]}
|
|
462
|
-
/>
|
|
463
|
-
),
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
export const Annotations = {
|
|
467
|
-
render: () => <Story text={str('# Annotations', '', large)} extensions={[annotations({ match: /volup/gi })]} />,
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
export const DND = {
|
|
471
|
-
render: () => <Story text={str('# DND', '')} extensions={[dnd()]} />,
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
|
|
475
|
-
|
|
476
|
-
export const Listener = {
|
|
477
|
-
render: () => (
|
|
478
|
-
<Story
|
|
479
|
-
text={str('# Listener', '', text.footer)}
|
|
480
|
-
extensions={[
|
|
481
|
-
listener({
|
|
482
|
-
onFocus: (focusing) => {
|
|
483
|
-
console.log({ focusing });
|
|
484
|
-
},
|
|
485
|
-
onChange: (text) => {
|
|
486
|
-
console.log({ text });
|
|
487
|
-
},
|
|
488
|
-
}),
|
|
489
|
-
]}
|
|
490
|
-
/>
|
|
491
|
-
),
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
export const Typewriter = {
|
|
495
|
-
render: () => (
|
|
496
|
-
<Story
|
|
497
|
-
text={str('# Typewriter', '', text.paragraphs, text.footer)}
|
|
498
|
-
extensions={[typewriter({ items: typewriterItems })]}
|
|
499
|
-
/>
|
|
500
|
-
),
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
export const Blast = {
|
|
504
|
-
render: () => (
|
|
505
|
-
<Story
|
|
506
|
-
text={str('# Blast', '', text.paragraphs, text.code, text.paragraphs)}
|
|
507
|
-
extensions={[
|
|
508
|
-
typewriter({ items: typewriterItems }),
|
|
509
|
-
blast(
|
|
510
|
-
defaultsDeep(
|
|
511
|
-
{
|
|
512
|
-
effect: 2,
|
|
513
|
-
particleGravity: 0.2,
|
|
514
|
-
particleShrinkRate: 0.995,
|
|
515
|
-
color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
|
|
516
|
-
// color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
|
|
517
|
-
},
|
|
518
|
-
defaultOptions,
|
|
519
|
-
),
|
|
520
|
-
),
|
|
521
|
-
]}
|
|
522
|
-
/>
|
|
523
|
-
),
|
|
524
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Extension } from '@codemirror/state';
|
|
6
|
-
|
|
7
|
-
export type OutlinerOptions = {};
|
|
8
|
-
|
|
9
|
-
// TODO(burdon): Experimental component: multiple hierarchical draggable editor sections.
|
|
10
|
-
export const outliner = (options: OutlinerOptions = {}): Extension => {
|
|
11
|
-
return [];
|
|
12
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type EditorView } from '@codemirror/view';
|
|
6
|
-
import { type MutableRefObject, useRef, useState } from 'react';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Hook for accessing the editor view.
|
|
10
|
-
* @deprecated
|
|
11
|
-
*
|
|
12
|
-
* ```tsx
|
|
13
|
-
* const Test = () => {
|
|
14
|
-
* const [editorRef, viewInvalidated] = useEditorView();
|
|
15
|
-
* useEffect(() => {
|
|
16
|
-
* if (editorRef.current && !viewInvalidated) {
|
|
17
|
-
* editorView?.focus();
|
|
18
|
-
* }
|
|
19
|
-
* }, [editorView]);
|
|
20
|
-
* return <TextEditor ref={editorRef} />;
|
|
21
|
-
* };
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export const useEditorView = (id: string): [MutableRefObject<EditorView | null>, boolean] => {
|
|
25
|
-
const editorRef = useRef<EditorView | null>(null);
|
|
26
|
-
const [prev, setPrev] = useState<[EditorView | null, string]>([null, '']);
|
|
27
|
-
if (prev[0] !== editorRef.current) {
|
|
28
|
-
setPrev([editorRef.current, id]);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return [editorRef, prev[1] !== id];
|
|
32
|
-
};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Extension } from '@codemirror/state';
|
|
6
|
-
import get from 'lodash.get';
|
|
7
|
-
import { type Dispatch, type SetStateAction, useState, useMemo } from 'react';
|
|
8
|
-
|
|
9
|
-
import { generateName } from '@dxos/display-name';
|
|
10
|
-
import { type AutomergeTextCompat, getRawDoc } from '@dxos/echo-schema';
|
|
11
|
-
import { invariant } from '@dxos/invariant';
|
|
12
|
-
import { isAutomergeObject, type Space, type TextObject } from '@dxos/react-client/echo';
|
|
13
|
-
import { type Identity } from '@dxos/react-client/halo';
|
|
14
|
-
import { type HuePalette, hueTokens } from '@dxos/react-ui-theme';
|
|
15
|
-
import { hexToHue } from '@dxos/util';
|
|
16
|
-
|
|
17
|
-
import { SpaceAwarenessProvider } from './awareness-provider';
|
|
18
|
-
import { type EditorModel } from './defs';
|
|
19
|
-
import { automerge, awareness } from '../extensions';
|
|
20
|
-
import { type DocAccessor } from '../extensions/automerge/defs';
|
|
21
|
-
|
|
22
|
-
export type useTextExtensionsProps = {
|
|
23
|
-
id: string;
|
|
24
|
-
text: DocAccessor;
|
|
25
|
-
space?: Space;
|
|
26
|
-
identity?: Identity;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// TODO(burdon): Factor out automerge defs and extension (not hook).
|
|
30
|
-
export const useTextExtensions = ({ id, text, space, identity }: useTextExtensionsProps): Extension[] => {
|
|
31
|
-
const extensions: Extension[] = [automerge(text)];
|
|
32
|
-
|
|
33
|
-
const peerId = identity?.identityKey.toHex();
|
|
34
|
-
const { cursorLightValue, cursorDarkValue } =
|
|
35
|
-
hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
|
|
36
|
-
|
|
37
|
-
if (space && identity) {
|
|
38
|
-
const awarenessProvider = new SpaceAwarenessProvider({
|
|
39
|
-
space,
|
|
40
|
-
channel: `awareness.${id}`,
|
|
41
|
-
peerId: identity.identityKey.toHex(),
|
|
42
|
-
info: {
|
|
43
|
-
displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
|
|
44
|
-
color: cursorDarkValue,
|
|
45
|
-
lightColor: cursorLightValue,
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
extensions.push(awareness(awarenessProvider));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return extensions;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export type UseTextModelProps = {
|
|
56
|
-
text?: TextObject;
|
|
57
|
-
space?: Space;
|
|
58
|
-
identity?: Identity | null;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* @deprecated
|
|
63
|
-
*/
|
|
64
|
-
// TODO(burdon): Remove once automerge lands.
|
|
65
|
-
export const useTextModel = (props: UseTextModelProps): EditorModel | undefined =>
|
|
66
|
-
useMemo(() => createModel(props), Object.values(props));
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* For use primarily in stories & tests so the dependence on TextObject can be avoided.
|
|
70
|
-
* @deprecated
|
|
71
|
-
*/
|
|
72
|
-
// TODO(burdon): Remove.
|
|
73
|
-
export const useInMemoryTextModel = ({
|
|
74
|
-
id,
|
|
75
|
-
defaultContent,
|
|
76
|
-
}: {
|
|
77
|
-
id: string;
|
|
78
|
-
defaultContent?: string;
|
|
79
|
-
}): EditorModel & { setContent: Dispatch<SetStateAction<string>> } => {
|
|
80
|
-
const [content, setContent] = useState(defaultContent ?? '');
|
|
81
|
-
return { id, content, text: () => content, setContent };
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const createModel = ({ space, identity, text }: UseTextModelProps): EditorModel | undefined => {
|
|
85
|
-
if (!text) {
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
invariant(isAutomergeObject(text));
|
|
90
|
-
const obj = text as any as AutomergeTextCompat;
|
|
91
|
-
const doc = getRawDoc(obj, [obj.field]);
|
|
92
|
-
|
|
93
|
-
const peerId = identity?.identityKey.toHex();
|
|
94
|
-
const { cursorLightValue, cursorDarkValue } =
|
|
95
|
-
hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
|
|
96
|
-
|
|
97
|
-
const awarenessProvider =
|
|
98
|
-
space &&
|
|
99
|
-
new SpaceAwarenessProvider({
|
|
100
|
-
space,
|
|
101
|
-
channel: `automerge.awareness.${obj.id}`,
|
|
102
|
-
info: {
|
|
103
|
-
displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : undefined,
|
|
104
|
-
color: cursorLightValue,
|
|
105
|
-
lightColor: cursorDarkValue,
|
|
106
|
-
},
|
|
107
|
-
peerId: identity?.identityKey.toHex() ?? 'Anonymous',
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const extensions = [automerge({ handle: doc.handle, path: doc.path })];
|
|
111
|
-
if (awarenessProvider) {
|
|
112
|
-
extensions.push(awareness(awarenessProvider));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
id: obj.id,
|
|
117
|
-
content: doc,
|
|
118
|
-
text: () => get(doc.handle.docSync(), doc.path),
|
|
119
|
-
extension: extensions,
|
|
120
|
-
};
|
|
121
|
-
};
|