@dxos/react-ui-editor 0.3.10-main.5c4230b → 0.3.10-main.5e11230
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 +1644 -791
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +56 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +7 -3
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +9 -4
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +10 -0
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/{basic/bundle.d.ts → basic.d.ts} +3 -2
- package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
- package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts +18 -0
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/demo.d.ts +11 -0
- package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.d.ts → experimental.d.ts} +2 -2
- package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
- package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +11 -2
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
- package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts +9 -0
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +9 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +37 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
- package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
- package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +3 -0
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/index.d.ts +2 -0
- package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/themes/default.d.ts +25 -0
- package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
- package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
- package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.d.ts +3 -0
- package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts +16 -0
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
- package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +1 -1
- package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +5 -0
- package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +6 -0
- package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/translation/index.d.ts +3 -0
- package/dist/types/src/hooks/automerge/translation/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +0 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +4 -4
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +2 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
- package/dist/types/src/styles/markdown.d.ts +14 -11
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/testing/replicator.d.ts.map +1 -1
- package/package.json +21 -32
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +350 -0
- package/src/components/TextEditor/TextEditor.stories.tsx +46 -43
- package/src/components/TextEditor/TextEditor.tsx +71 -45
- package/src/components/TextEditor/extensions/autocomplete.ts +59 -0
- package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +6 -3
- package/src/components/TextEditor/extensions/blast.ts +387 -0
- package/src/components/TextEditor/extensions/comments.ts +172 -0
- package/src/components/TextEditor/extensions/demo.ts +69 -0
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.tsx → experimental.tsx} +15 -20
- package/src/components/TextEditor/extensions/image.ts +67 -0
- package/src/components/TextEditor/extensions/index.ts +11 -2
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +75 -38
- package/src/components/TextEditor/extensions/{change.ts → listener.ts} +5 -4
- package/src/components/TextEditor/extensions/markdown/bundle.ts +56 -59
- package/src/components/TextEditor/extensions/markdown/highlight.ts +207 -0
- package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
- package/src/components/TextEditor/extensions/mention.ts +31 -0
- package/src/components/TextEditor/extensions/table.ts +122 -0
- package/src/components/TextEditor/extensions/tasklist.ts +121 -0
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.tsx} +9 -9
- package/src/components/TextEditor/index.ts +3 -0
- package/src/components/TextEditor/themes/default.ts +274 -0
- package/src/components/TextEditor/themes/index.ts +5 -0
- package/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.ts +12 -9
- package/src/{automerge → hooks/automerge}/automerge.stories.tsx +5 -3
- package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +1 -0
- package/src/{automerge/automerge-plugin/amToCodemirror.ts → hooks/automerge/translation/automerge-to-codemirror.ts} +10 -2
- package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → hooks/automerge/translation/codemirror-to-automerge.ts} +7 -4
- package/src/hooks/automerge/translation/index.ts +6 -0
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useTextModel.ts +52 -39
- package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +2 -1
- package/src/styles/markdown.ts +32 -24
- package/src/testing/replicator.ts +6 -6
- package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +0 -7
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
- package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
- package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
- package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
- package/src/components/TextEditor/extensions/basic/index.ts +0 -6
- package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
- package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
- package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
- package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
- package/src/hooks/useCollaboration.ts +0 -45
- package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
- package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
- package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
- package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +1 -1
|
@@ -0,0 +1,350 @@
|
|
|
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, { 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
|
+
listener,
|
|
23
|
+
link,
|
|
24
|
+
tasklist,
|
|
25
|
+
tooltip,
|
|
26
|
+
type TooltipOptions,
|
|
27
|
+
type LinkOptions,
|
|
28
|
+
comments,
|
|
29
|
+
table,
|
|
30
|
+
image,
|
|
31
|
+
mention,
|
|
32
|
+
blast,
|
|
33
|
+
demo,
|
|
34
|
+
defaultOptions,
|
|
35
|
+
} from './extensions';
|
|
36
|
+
import { useTextModel } from '../../hooks';
|
|
37
|
+
|
|
38
|
+
const str = (...lines: string[]) => lines.join('\n');
|
|
39
|
+
|
|
40
|
+
const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
|
|
41
|
+
|
|
42
|
+
// prettier-ignore
|
|
43
|
+
const text = {
|
|
44
|
+
tasks: str(
|
|
45
|
+
'## Tasks',
|
|
46
|
+
'',
|
|
47
|
+
'- [x] decorator',
|
|
48
|
+
'- [ ] checkbox',
|
|
49
|
+
' - [ ] state',
|
|
50
|
+
' - [ ] indent',
|
|
51
|
+
' - [x] style',
|
|
52
|
+
),
|
|
53
|
+
|
|
54
|
+
list: str(
|
|
55
|
+
'## List',
|
|
56
|
+
'',
|
|
57
|
+
'- new york',
|
|
58
|
+
'- london',
|
|
59
|
+
'- tokyo',
|
|
60
|
+
),
|
|
61
|
+
|
|
62
|
+
numbered: str(
|
|
63
|
+
'## Numbered',
|
|
64
|
+
'',
|
|
65
|
+
'1. one',
|
|
66
|
+
'2. two',
|
|
67
|
+
'3. three',
|
|
68
|
+
''
|
|
69
|
+
),
|
|
70
|
+
|
|
71
|
+
code: str(
|
|
72
|
+
'## Code',
|
|
73
|
+
'',
|
|
74
|
+
'```',
|
|
75
|
+
'const x = 100;',
|
|
76
|
+
'```',
|
|
77
|
+
'',
|
|
78
|
+
'```tsx',
|
|
79
|
+
'const Component = () => {',
|
|
80
|
+
' const x = 100;',
|
|
81
|
+
' return () => <div>Test</div>;',
|
|
82
|
+
'};',
|
|
83
|
+
'',
|
|
84
|
+
'```'
|
|
85
|
+
),
|
|
86
|
+
|
|
87
|
+
links: str(
|
|
88
|
+
'## Links',
|
|
89
|
+
'',
|
|
90
|
+
'This is a naked link https://dxos.org within a sentence.',
|
|
91
|
+
'',
|
|
92
|
+
'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
|
|
93
|
+
'',
|
|
94
|
+
),
|
|
95
|
+
|
|
96
|
+
table: str(
|
|
97
|
+
'# Table',
|
|
98
|
+
'',
|
|
99
|
+
`| ${faker.lorem.word()} | ${faker.lorem.word()} | ${faker.lorem.word()} |`,
|
|
100
|
+
'|---|---|---|',
|
|
101
|
+
`| ${num()} | ${num()} | ${num()} |`,
|
|
102
|
+
`| ${num()} | ${num()} | ${num()} |`,
|
|
103
|
+
`| ${num()} | ${num()} | ${num()} |`,
|
|
104
|
+
'', // TODO(burdon): Possible GFM parsing bug if no newline?
|
|
105
|
+
),
|
|
106
|
+
|
|
107
|
+
image: str('# Image', '', ''),
|
|
108
|
+
|
|
109
|
+
headings: str(
|
|
110
|
+
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
|
111
|
+
),
|
|
112
|
+
|
|
113
|
+
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const document = str(
|
|
117
|
+
'# Markdown',
|
|
118
|
+
'',
|
|
119
|
+
'> This is a block quote.',
|
|
120
|
+
'',
|
|
121
|
+
'This is all about https://dxos.org and related technologies.',
|
|
122
|
+
'',
|
|
123
|
+
'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.',
|
|
124
|
+
'',
|
|
125
|
+
'---',
|
|
126
|
+
text.links,
|
|
127
|
+
'---',
|
|
128
|
+
text.list,
|
|
129
|
+
'---',
|
|
130
|
+
text.tasks,
|
|
131
|
+
'---',
|
|
132
|
+
text.numbered,
|
|
133
|
+
'---',
|
|
134
|
+
text.code,
|
|
135
|
+
'---',
|
|
136
|
+
text.headings,
|
|
137
|
+
'---',
|
|
138
|
+
text.table,
|
|
139
|
+
'---',
|
|
140
|
+
text.image,
|
|
141
|
+
'',
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const links = [
|
|
145
|
+
{ label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
|
|
146
|
+
{ label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
|
|
147
|
+
{ label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
|
|
148
|
+
{ label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
|
|
149
|
+
{ label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const hover =
|
|
153
|
+
'rounded-sm text-base text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
|
|
154
|
+
|
|
155
|
+
const onHover: TooltipOptions['onHover'] = (el, url) => {
|
|
156
|
+
const web = new URL(url);
|
|
157
|
+
createRoot(el).render(
|
|
158
|
+
<StrictMode>
|
|
159
|
+
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
160
|
+
{web.origin}
|
|
161
|
+
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1')} />
|
|
162
|
+
</a>
|
|
163
|
+
</StrictMode>,
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const onRender: LinkOptions['onRender'] = (el, url) => {
|
|
168
|
+
createRoot(el).render(
|
|
169
|
+
<StrictMode>
|
|
170
|
+
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
171
|
+
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-1')} />
|
|
172
|
+
</a>
|
|
173
|
+
</StrictMode>,
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const Story = ({
|
|
178
|
+
text,
|
|
179
|
+
automerge,
|
|
180
|
+
...props
|
|
181
|
+
}: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'readonly' | 'extensions' | 'slots'>) => {
|
|
182
|
+
const ref = useRef<TextEditorRef>(null);
|
|
183
|
+
const [item] = useState({ text: new TextObject(text, undefined, undefined, { useAutomergeBackend: automerge }) });
|
|
184
|
+
const model = useTextModel({ text: item.text });
|
|
185
|
+
if (!model) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
191
|
+
<div className='flex justify-center overflow-y-scroll'>
|
|
192
|
+
<div className='flex flex-col w-[800px] py-16'>
|
|
193
|
+
<MarkdownEditor ref={ref} model={model} {...props} />
|
|
194
|
+
<div className='flex shrink-0 h-[300px]'></div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export default {
|
|
202
|
+
title: 'react-ui-editor/MarkdownEditor',
|
|
203
|
+
component: MarkdownEditor,
|
|
204
|
+
decorators: [withTheme],
|
|
205
|
+
render: Story,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const extensions = [
|
|
209
|
+
link({ onRender }),
|
|
210
|
+
tooltip({ onHover }),
|
|
211
|
+
tasklist(),
|
|
212
|
+
table(),
|
|
213
|
+
image(),
|
|
214
|
+
autocomplete({
|
|
215
|
+
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
216
|
+
}),
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
export const Default = {
|
|
220
|
+
render: () => <Story text={document} extensions={extensions} />,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const Readonly = {
|
|
224
|
+
render: () => <Story text={document} extensions={extensions} readonly />,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const Simple = {
|
|
228
|
+
render: () => <Story text={document} />,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const Tooltips = {
|
|
232
|
+
render: () => <Story text={str(text.links, '')} extensions={[tooltip({ onHover })]} />,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export const Links = {
|
|
236
|
+
render: () => <Story text={str(text.links, '')} extensions={[link({ onRender })]} />,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const Table = {
|
|
240
|
+
render: () => <Story text={str(text.table, '')} extensions={[table()]} />,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const Image = {
|
|
244
|
+
render: () => <Story text={str(text.image, '', '')} readonly extensions={[image()]} />,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export const TaskList = {
|
|
248
|
+
render: () => (
|
|
249
|
+
<Story
|
|
250
|
+
text={str(text.tasks, '', text.list)}
|
|
251
|
+
extensions={[
|
|
252
|
+
tasklist(),
|
|
253
|
+
listener({
|
|
254
|
+
onChange: (text) => {
|
|
255
|
+
console.log(text);
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
]}
|
|
259
|
+
/>
|
|
260
|
+
),
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export const Autocomplete = {
|
|
264
|
+
render: () => (
|
|
265
|
+
<Story
|
|
266
|
+
text={str('# Autocomplete', '', '', '', '', '', '')}
|
|
267
|
+
extensions={[
|
|
268
|
+
link({ onRender }),
|
|
269
|
+
autocomplete({
|
|
270
|
+
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
271
|
+
}),
|
|
272
|
+
]}
|
|
273
|
+
/>
|
|
274
|
+
),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
|
|
278
|
+
|
|
279
|
+
export const Mention = {
|
|
280
|
+
render: () => (
|
|
281
|
+
<Story
|
|
282
|
+
text={str('# Mention', '', '', '', '', '', '')}
|
|
283
|
+
extensions={[
|
|
284
|
+
mention({
|
|
285
|
+
onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
|
|
286
|
+
}),
|
|
287
|
+
]}
|
|
288
|
+
/>
|
|
289
|
+
),
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const mark = () => `[^${PublicKey.random().toHex()}]`;
|
|
293
|
+
export const Comments = {
|
|
294
|
+
render: () => (
|
|
295
|
+
<Story
|
|
296
|
+
text={str(mark(), '', text.paragraphs, mark(), '', mark(), '')}
|
|
297
|
+
extensions={[
|
|
298
|
+
comments({
|
|
299
|
+
onCreate: () => PublicKey.random().toHex(),
|
|
300
|
+
onUpdate: (info) => {
|
|
301
|
+
console.log('update', info);
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
]}
|
|
305
|
+
/>
|
|
306
|
+
),
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export const Diagnostics = {
|
|
310
|
+
render: () => (
|
|
311
|
+
<Story
|
|
312
|
+
text={document}
|
|
313
|
+
extensions={[
|
|
314
|
+
// Cursor moved.
|
|
315
|
+
EditorView.updateListener.of((update) => {
|
|
316
|
+
console.log('update', update.view.state.selection.main.head);
|
|
317
|
+
}),
|
|
318
|
+
]}
|
|
319
|
+
/>
|
|
320
|
+
),
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const Demo = {
|
|
324
|
+
render: () => <Story text={text.paragraphs} extensions={[demo()]} />,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
export const Blast = {
|
|
328
|
+
render: () => (
|
|
329
|
+
<Story
|
|
330
|
+
text={str(text.paragraphs, text.code, text.paragraphs)}
|
|
331
|
+
extensions={[
|
|
332
|
+
demo({
|
|
333
|
+
items: localStorage.getItem('dxos.composer.extension.demo')?.split(','),
|
|
334
|
+
}),
|
|
335
|
+
blast(
|
|
336
|
+
defaultsDeep(
|
|
337
|
+
{
|
|
338
|
+
effect: 2,
|
|
339
|
+
particleGravity: 0.2,
|
|
340
|
+
particleShrinkRate: 0.995,
|
|
341
|
+
color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
|
|
342
|
+
// color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
|
|
343
|
+
},
|
|
344
|
+
defaultOptions,
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
]}
|
|
348
|
+
/>
|
|
349
|
+
),
|
|
350
|
+
};
|
|
@@ -3,63 +3,46 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import 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,
|
|
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 {
|
|
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
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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, { useAutomergeBackend: 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
|
|
50
|
-
<div className='w-[800px]'>
|
|
51
|
-
<TextEditor
|
|
52
|
-
|
|
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,33 @@ 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
|
+
text={initialText}
|
|
63
|
+
slots={defaultsDeep(
|
|
64
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
65
|
+
defaultSlots,
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
),
|
|
69
|
+
};
|
|
76
70
|
|
|
77
71
|
export const Automerge = {
|
|
78
|
-
render: () =>
|
|
72
|
+
render: () => (
|
|
73
|
+
<Story
|
|
74
|
+
text={initialText}
|
|
75
|
+
slots={defaultsDeep(
|
|
76
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
77
|
+
defaultSlots,
|
|
78
|
+
)}
|
|
79
|
+
automerge
|
|
80
|
+
/>
|
|
81
|
+
),
|
|
79
82
|
};
|
|
@@ -7,23 +7,24 @@ import { EditorView } from '@codemirror/view';
|
|
|
7
7
|
import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
8
8
|
import { vim } from '@replit/codemirror-vim';
|
|
9
9
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
10
|
-
import get from 'lodash.get';
|
|
11
10
|
import React, {
|
|
11
|
+
type ComponentProps,
|
|
12
12
|
type KeyboardEvent,
|
|
13
13
|
forwardRef,
|
|
14
14
|
useCallback,
|
|
15
15
|
useEffect,
|
|
16
16
|
useImperativeHandle,
|
|
17
17
|
useState,
|
|
18
|
-
type ComponentProps,
|
|
19
18
|
} from 'react';
|
|
20
19
|
|
|
21
|
-
import {
|
|
20
|
+
import { generateName } from '@dxos/display-name';
|
|
22
21
|
import { useThemeContext } from '@dxos/react-ui';
|
|
22
|
+
import { getColorForValue, inputSurface, mx } from '@dxos/react-ui-theme';
|
|
23
23
|
|
|
24
|
-
import { basicBundle,
|
|
25
|
-
import {
|
|
26
|
-
import type
|
|
24
|
+
import { basicBundle, markdownBundle } from './extensions';
|
|
25
|
+
import { defaultTheme, markdownTheme, textTheme } from './themes';
|
|
26
|
+
import { type EditorModel } from '../../hooks';
|
|
27
|
+
import { type ThemeStyles } from '../../styles';
|
|
27
28
|
|
|
28
29
|
export const EditorModes = ['default', 'vim'] as const;
|
|
29
30
|
export type EditorMode = (typeof EditorModes)[number];
|
|
@@ -37,7 +38,7 @@ export type CursorInfo = {
|
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export type TextEditorRef = {
|
|
40
|
-
|
|
41
|
+
root: HTMLDivElement | null;
|
|
41
42
|
state?: EditorState;
|
|
42
43
|
view?: EditorView;
|
|
43
44
|
};
|
|
@@ -55,9 +56,10 @@ export type TextEditorSlots = {
|
|
|
55
56
|
|
|
56
57
|
export type TextEditorProps = {
|
|
57
58
|
model: EditorModel;
|
|
59
|
+
readonly?: boolean;
|
|
60
|
+
editorMode?: EditorMode;
|
|
58
61
|
extensions?: Extension[];
|
|
59
62
|
slots?: TextEditorSlots;
|
|
60
|
-
editorMode?: EditorMode;
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
/**
|
|
@@ -65,66 +67,68 @@ export type TextEditorProps = {
|
|
|
65
67
|
* NOTE: Rather than adding properties, try to create extensions that can be reused.
|
|
66
68
|
*/
|
|
67
69
|
export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
68
|
-
({ model, extensions = [], slots
|
|
70
|
+
({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
|
|
69
71
|
const { themeMode } = useThemeContext();
|
|
70
72
|
const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
|
|
71
73
|
|
|
72
|
-
const [
|
|
74
|
+
const [root, setRoot] = useState<HTMLDivElement | null>(null);
|
|
73
75
|
const [state, setState] = useState<EditorState>();
|
|
74
76
|
const [view, setView] = useState<EditorView>();
|
|
75
|
-
useImperativeHandle(
|
|
76
|
-
forwardedRef,
|
|
77
|
-
() => ({
|
|
78
|
-
editor: parent,
|
|
79
|
-
state,
|
|
80
|
-
view,
|
|
81
|
-
}),
|
|
82
|
-
[view, state, parent],
|
|
83
|
-
);
|
|
77
|
+
useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
|
|
84
78
|
|
|
85
|
-
// TODO(burdon):
|
|
86
|
-
const
|
|
79
|
+
// TODO(burdon): Factor out?
|
|
80
|
+
const { awareness, peer } = model;
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (awareness && peer) {
|
|
83
|
+
awareness.setLocalStateField('user', {
|
|
84
|
+
name: peer.name ?? generateName(peer.id),
|
|
85
|
+
color: getColorForValue({ value: peer.id, type: 'color' }),
|
|
86
|
+
colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}, [awareness, peer, themeMode]);
|
|
87
90
|
|
|
88
91
|
useEffect(() => {
|
|
89
|
-
if (!
|
|
92
|
+
if (!root) {
|
|
90
93
|
return;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
const initialText = isDocAccessor(model.content)
|
|
94
|
-
? get(model.content.handle.docSync(), model.content.path)
|
|
95
|
-
: model.content?.toString();
|
|
96
|
-
|
|
97
96
|
const state = EditorState.create({
|
|
98
|
-
doc:
|
|
97
|
+
doc: model.text(),
|
|
99
98
|
extensions: [
|
|
99
|
+
readonly && EditorState.readOnly.of(readonly),
|
|
100
|
+
|
|
100
101
|
// TODO(burdon): Factor out VIM mode?
|
|
101
102
|
editorMode === 'vim' && vim(),
|
|
102
103
|
|
|
103
|
-
// Replication.
|
|
104
|
-
collaboration,
|
|
105
|
-
|
|
106
104
|
// Theme.
|
|
107
|
-
|
|
105
|
+
// TODO(burdon): Make optional.
|
|
106
|
+
EditorView.baseTheme(defaultTheme),
|
|
107
|
+
EditorView.theme(slots?.editor?.theme ?? {}),
|
|
108
|
+
// TODO(burdon): themeMode doesn't change in storybooks.
|
|
109
|
+
EditorView.darkTheme.of(themeMode === 'dark'),
|
|
110
|
+
|
|
111
|
+
// Storage and replication.
|
|
112
|
+
model.extension,
|
|
108
113
|
|
|
109
114
|
// Custom.
|
|
110
115
|
...extensions,
|
|
111
116
|
].filter(Boolean) as Extension[],
|
|
112
117
|
});
|
|
113
118
|
|
|
114
|
-
setState(state);
|
|
115
|
-
|
|
116
119
|
// NOTE: This repaints the editor.
|
|
117
120
|
// If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
|
|
118
121
|
// Ideally this should not be hit except when changing between text objects.
|
|
119
122
|
view?.destroy();
|
|
120
|
-
setView(new EditorView({ state, parent }));
|
|
123
|
+
setView(new EditorView({ state, parent: root }));
|
|
124
|
+
setState(state);
|
|
121
125
|
|
|
122
126
|
return () => {
|
|
123
127
|
view?.destroy();
|
|
124
128
|
setView(undefined);
|
|
125
129
|
setState(undefined);
|
|
126
130
|
};
|
|
127
|
-
}, [
|
|
131
|
+
}, [root, model, themeMode, readonly, editorMode]);
|
|
128
132
|
|
|
129
133
|
const handleKeyUp = useCallback(
|
|
130
134
|
(event: KeyboardEvent) => {
|
|
@@ -136,7 +140,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
case 'Escape': {
|
|
139
|
-
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) &&
|
|
143
|
+
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
|
|
140
144
|
break;
|
|
141
145
|
}
|
|
142
146
|
}
|
|
@@ -147,7 +151,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
147
151
|
return (
|
|
148
152
|
<div
|
|
149
153
|
key={model.id}
|
|
150
|
-
ref={
|
|
154
|
+
ref={setRoot}
|
|
151
155
|
tabIndex={0}
|
|
152
156
|
{...slots?.root}
|
|
153
157
|
{...(editorMode !== 'vim' && tabsterDOMAttribute)}
|
|
@@ -158,14 +162,15 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
158
162
|
);
|
|
159
163
|
|
|
160
164
|
export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
161
|
-
({ extensions
|
|
165
|
+
({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
162
166
|
const { themeMode } = useThemeContext();
|
|
163
|
-
const
|
|
167
|
+
const slots = defaultsDeep({}, _slots, defaultTextSlots);
|
|
164
168
|
return (
|
|
165
169
|
<BaseTextEditor
|
|
166
170
|
ref={forwardedRef}
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
readonly={readonly}
|
|
172
|
+
extensions={[basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
173
|
+
slots={slots}
|
|
169
174
|
{...props}
|
|
170
175
|
/>
|
|
171
176
|
);
|
|
@@ -173,16 +178,37 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
173
178
|
);
|
|
174
179
|
|
|
175
180
|
export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
176
|
-
({ extensions
|
|
181
|
+
({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
177
182
|
const { themeMode } = useThemeContext();
|
|
178
|
-
const
|
|
183
|
+
const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
|
|
179
184
|
return (
|
|
180
185
|
<BaseTextEditor
|
|
181
186
|
ref={forwardedRef}
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
readonly={readonly}
|
|
188
|
+
extensions={[markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
189
|
+
slots={slots}
|
|
184
190
|
{...props}
|
|
185
191
|
/>
|
|
186
192
|
);
|
|
187
193
|
},
|
|
188
194
|
);
|
|
195
|
+
|
|
196
|
+
export const defaultSlots: TextEditorSlots = {
|
|
197
|
+
root: {
|
|
198
|
+
className: mx('p-2', inputSurface),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const defaultTextSlots: TextEditorSlots = {
|
|
203
|
+
...defaultSlots,
|
|
204
|
+
editor: {
|
|
205
|
+
theme: textTheme,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const defaultMarkdownSlots: TextEditorSlots = {
|
|
210
|
+
...defaultSlots,
|
|
211
|
+
editor: {
|
|
212
|
+
theme: markdownTheme,
|
|
213
|
+
},
|
|
214
|
+
};
|