@dxos/react-ui-editor 0.3.9 → 0.3.10-main.3c0616c
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 +722 -701
- 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 +35 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +4 -2
- 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 +9 -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} +1 -1
- package/dist/types/src/components/TextEditor/extensions/basic.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/index.d.ts +5 -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 +7 -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.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +31 -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/tasklist.d.ts +5 -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 +17 -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/components/TextEditor/yjs.stories.d.ts +1 -0
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +1 -0
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts +12 -10
- 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 +242 -0
- package/src/components/TextEditor/TextEditor.stories.tsx +46 -43
- package/src/components/TextEditor/TextEditor.tsx +62 -45
- package/src/components/TextEditor/extensions/autocomplete.ts +72 -0
- package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +4 -2
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.tsx → experimental.tsx} +17 -2
- package/src/components/TextEditor/extensions/index.ts +5 -2
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +68 -31
- package/src/components/TextEditor/extensions/{change.ts → listener.ts} +3 -3
- package/src/components/TextEditor/extensions/markdown/bundle.ts +15 -11
- package/src/components/TextEditor/extensions/markdown/highlight.ts +166 -0
- package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
- package/src/components/TextEditor/extensions/tasklist.ts +113 -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 +170 -0
- package/src/components/TextEditor/themes/index.ts +5 -0
- package/src/components/TextEditor/yjs.stories.tsx +1 -0
- package/src/hooks/useCollaboration.ts +4 -4
- package/src/hooks/useTextModel.ts +10 -5
- package/src/styles/markdown.ts +26 -24
- package/src/testing/replicator.ts +1 -0
- 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/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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-editor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10-main.3c0616c",
|
|
4
4
|
"description": "Document editing experience within a DXOS shell.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -28,60 +28,49 @@
|
|
|
28
28
|
"@lezer/highlight": "^1.2.0",
|
|
29
29
|
"@lezer/markdown": "^1.1.1",
|
|
30
30
|
"@replit/codemirror-vim": "^6.0.14",
|
|
31
|
-
"@tiptap/core": "2.0.0-beta.220",
|
|
32
|
-
"@tiptap/extension-collaboration": "2.0.0-beta.220",
|
|
33
|
-
"@tiptap/extension-collaboration-cursor": "2.0.0-beta.220",
|
|
34
|
-
"@tiptap/extension-heading": "2.0.0-beta.220",
|
|
35
|
-
"@tiptap/extension-list-item": "2.0.0-beta.220",
|
|
36
|
-
"@tiptap/extension-placeholder": "2.0.0-beta.220",
|
|
37
|
-
"@tiptap/pm": "2.0.0-beta.220",
|
|
38
|
-
"@tiptap/react": "2.0.0-beta.220",
|
|
39
|
-
"@tiptap/starter-kit": "2.0.0-beta.220",
|
|
40
31
|
"codemirror": "6.0.1",
|
|
41
32
|
"lib0": "^0.2.65",
|
|
42
33
|
"lodash.defaultsdeep": "^4.6.1",
|
|
43
34
|
"lodash.get": "^4.4.2",
|
|
44
|
-
"prosemirror-model": "^1.19.0",
|
|
45
|
-
"prosemirror-state": "^1.4.2",
|
|
46
|
-
"prosemirror-view": "^1.30.1",
|
|
47
35
|
"style-mod": "^4.1.0",
|
|
48
36
|
"y-codemirror.next": "^0.3.2",
|
|
49
|
-
"y-prosemirror": "~1.0.20",
|
|
50
37
|
"y-protocols": "^1.0.5",
|
|
51
38
|
"yjs": "^13.6.10",
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/echo-schema": "0.3.
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/react-
|
|
60
|
-
"@dxos/react-
|
|
61
|
-
"@dxos/react-ui
|
|
62
|
-
"@dxos/text-model": "0.3.
|
|
63
|
-
"@dxos/util": "0.3.
|
|
39
|
+
"@dxos/automerge": "0.3.10-main.3c0616c",
|
|
40
|
+
"@dxos/echo-schema": "0.3.10-main.3c0616c",
|
|
41
|
+
"@dxos/debug": "0.3.10-main.3c0616c",
|
|
42
|
+
"@dxos/invariant": "0.3.10-main.3c0616c",
|
|
43
|
+
"@dxos/log": "0.3.10-main.3c0616c",
|
|
44
|
+
"@dxos/protocols": "0.3.10-main.3c0616c",
|
|
45
|
+
"@dxos/display-name": "0.3.10-main.3c0616c",
|
|
46
|
+
"@dxos/react-async": "0.3.10-main.3c0616c",
|
|
47
|
+
"@dxos/react-ui-theme": "0.3.10-main.3c0616c",
|
|
48
|
+
"@dxos/react-ui": "0.3.10-main.3c0616c",
|
|
49
|
+
"@dxos/text-model": "0.3.10-main.3c0616c",
|
|
50
|
+
"@dxos/util": "0.3.10-main.3c0616c"
|
|
64
51
|
},
|
|
65
52
|
"devDependencies": {
|
|
66
53
|
"@automerge/automerge-repo-network-broadcastchannel": "^1.0.19",
|
|
54
|
+
"@faker-js/faker": "^8.0.2",
|
|
67
55
|
"@phosphor-icons/react": "^2.0.5",
|
|
56
|
+
"@preact/signals-react": "^1.3.6",
|
|
68
57
|
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
69
58
|
"@types/lodash.get": "^4.4.7",
|
|
70
59
|
"@types/react": "^18.0.21",
|
|
71
60
|
"@types/react-dom": "^18.0.6",
|
|
72
61
|
"react": "^18.2.0",
|
|
73
62
|
"react-dom": "^18.2.0",
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/
|
|
63
|
+
"@dxos/config": "0.3.10-main.3c0616c",
|
|
64
|
+
"@dxos/echo-typegen": "0.3.10-main.3c0616c",
|
|
65
|
+
"@dxos/react-ui": "0.3.10-main.3c0616c",
|
|
66
|
+
"@dxos/react-client": "0.3.10-main.3c0616c",
|
|
67
|
+
"@dxos/storybook-utils": "0.3.10-main.3c0616c"
|
|
79
68
|
},
|
|
80
69
|
"peerDependencies": {
|
|
81
70
|
"@phosphor-icons/react": "^2.0.5",
|
|
82
71
|
"react": "^18.2.0",
|
|
83
72
|
"react-dom": "^18.2.0",
|
|
84
|
-
"@dxos/react-client": "0.3.
|
|
73
|
+
"@dxos/react-client": "0.3.10-main.3c0616c"
|
|
85
74
|
},
|
|
86
75
|
"publishConfig": {
|
|
87
76
|
"access": "public"
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
|
+
import { faker } from '@faker-js/faker';
|
|
8
|
+
import { ArrowSquareOut } from '@phosphor-icons/react';
|
|
9
|
+
import React, { StrictMode, useRef, useState } from 'react';
|
|
10
|
+
import { createRoot } from 'react-dom/client';
|
|
11
|
+
|
|
12
|
+
import { TextObject } from '@dxos/echo-schema';
|
|
13
|
+
import { fixedInsetFlexLayout, getSize, groupSurface, mx } from '@dxos/react-ui-theme';
|
|
14
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
15
|
+
|
|
16
|
+
import { MarkdownEditor, type TextEditorProps, type TextEditorRef } from './TextEditor';
|
|
17
|
+
import { autocomplete, listener, link, tasklist, tooltip, type TooltipOptions, type LinkOptions } from './extensions';
|
|
18
|
+
import { useTextModel } from '../../hooks';
|
|
19
|
+
|
|
20
|
+
// TODO(burdon): Read-only render mode (presentation).
|
|
21
|
+
// TODO(burdon): Slides.
|
|
22
|
+
// TODO(burdon): Autocomplete.
|
|
23
|
+
// TODO(burdon): Block quote.
|
|
24
|
+
// TODO(burdon): Images.
|
|
25
|
+
// TODO(burdon): Tables.
|
|
26
|
+
|
|
27
|
+
const str = (...lines: string[]) => lines.join('\n');
|
|
28
|
+
|
|
29
|
+
// prettier-ignore
|
|
30
|
+
const text = {
|
|
31
|
+
tasks: str(
|
|
32
|
+
'## Tasks',
|
|
33
|
+
'',
|
|
34
|
+
'- [x] parsing',
|
|
35
|
+
'- [ ] styling',
|
|
36
|
+
'- [ ] rendering',
|
|
37
|
+
),
|
|
38
|
+
|
|
39
|
+
list: str(
|
|
40
|
+
'## List',
|
|
41
|
+
'',
|
|
42
|
+
'- new york',
|
|
43
|
+
'- london',
|
|
44
|
+
'- tokyo',
|
|
45
|
+
),
|
|
46
|
+
|
|
47
|
+
numbered: str(
|
|
48
|
+
'## Numbered',
|
|
49
|
+
'',
|
|
50
|
+
'1. one',
|
|
51
|
+
'2. two',
|
|
52
|
+
'3. three',
|
|
53
|
+
''
|
|
54
|
+
),
|
|
55
|
+
|
|
56
|
+
code: str(
|
|
57
|
+
'## Code',
|
|
58
|
+
'',
|
|
59
|
+
'```',
|
|
60
|
+
'const x = 100;',
|
|
61
|
+
'```',
|
|
62
|
+
'',
|
|
63
|
+
'```tsx',
|
|
64
|
+
'const Component = () => {',
|
|
65
|
+
' const x = 100;',
|
|
66
|
+
' return () => <div>Test</div>;',
|
|
67
|
+
'};',
|
|
68
|
+
'```'
|
|
69
|
+
),
|
|
70
|
+
|
|
71
|
+
links: str(
|
|
72
|
+
'## Links',
|
|
73
|
+
'',
|
|
74
|
+
'This is a naked link https://dxos.org within a sentence.',
|
|
75
|
+
'',
|
|
76
|
+
'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
|
|
77
|
+
'',
|
|
78
|
+
),
|
|
79
|
+
|
|
80
|
+
headings: str(
|
|
81
|
+
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
|
82
|
+
),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const document = str(
|
|
86
|
+
'# Markdown',
|
|
87
|
+
'',
|
|
88
|
+
'> This is a block quote.',
|
|
89
|
+
'',
|
|
90
|
+
'This is all about https://dxos.org and related technologies.',
|
|
91
|
+
'',
|
|
92
|
+
'This this is **bold**, __underlined__, _italic_, and `f(INLINE)`.',
|
|
93
|
+
'',
|
|
94
|
+
'__NOTE__: Fenced code uses the base font.',
|
|
95
|
+
'',
|
|
96
|
+
'---',
|
|
97
|
+
text.links,
|
|
98
|
+
'---',
|
|
99
|
+
text.list,
|
|
100
|
+
'---',
|
|
101
|
+
text.tasks,
|
|
102
|
+
'---',
|
|
103
|
+
text.numbered,
|
|
104
|
+
'---',
|
|
105
|
+
text.code,
|
|
106
|
+
'---',
|
|
107
|
+
text.headings,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const links = [
|
|
111
|
+
{ label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
|
|
112
|
+
{ label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
|
|
113
|
+
{ label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
|
|
114
|
+
{ label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
|
|
115
|
+
{ label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const hover =
|
|
119
|
+
'rounded-sm text-base text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
|
|
120
|
+
|
|
121
|
+
const onHover: TooltipOptions['onHover'] = (el, url) => {
|
|
122
|
+
const web = new URL(url);
|
|
123
|
+
createRoot(el).render(
|
|
124
|
+
<StrictMode>
|
|
125
|
+
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
126
|
+
{web.origin}
|
|
127
|
+
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1')} />
|
|
128
|
+
</a>
|
|
129
|
+
</StrictMode>,
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const onRender: LinkOptions['onRender'] = (el, url) => {
|
|
134
|
+
createRoot(el).render(
|
|
135
|
+
<StrictMode>
|
|
136
|
+
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
137
|
+
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-1')} />
|
|
138
|
+
</a>
|
|
139
|
+
</StrictMode>,
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const Story = ({
|
|
144
|
+
text,
|
|
145
|
+
automerge,
|
|
146
|
+
...props
|
|
147
|
+
}: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
|
|
148
|
+
const ref = useRef<TextEditorRef>(null);
|
|
149
|
+
const [item] = useState({ text: new TextObject(text, undefined, undefined, { useAutomergeBackend: automerge }) });
|
|
150
|
+
const model = useTextModel({ text: item.text });
|
|
151
|
+
if (!model) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
157
|
+
<div className='flex justify-center overflow-y-scroll'>
|
|
158
|
+
<div className='flex flex-col w-[800px] py-16'>
|
|
159
|
+
<MarkdownEditor ref={ref} model={model} {...props} />
|
|
160
|
+
<div className='flex shrink-0 h-[300px]'></div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default {
|
|
168
|
+
title: 'react-ui-editor/MarkdownEditor',
|
|
169
|
+
component: MarkdownEditor,
|
|
170
|
+
decorators: [withTheme],
|
|
171
|
+
render: Story,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const Default = {
|
|
175
|
+
render: () => (
|
|
176
|
+
<Story
|
|
177
|
+
text={document}
|
|
178
|
+
extensions={[
|
|
179
|
+
link({ onRender }),
|
|
180
|
+
tooltip({ onHover }),
|
|
181
|
+
tasklist(),
|
|
182
|
+
autocomplete({
|
|
183
|
+
getOptions: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
184
|
+
}),
|
|
185
|
+
]}
|
|
186
|
+
/>
|
|
187
|
+
),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const Simple = {
|
|
191
|
+
render: () => <Story text={document} />,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const Tooltips = {
|
|
195
|
+
render: () => <Story text={text.links} extensions={[tooltip({ onHover })]} />,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export const Links = {
|
|
199
|
+
render: () => <Story text={text.links} extensions={[link({ onRender })]} />,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const TaskList = {
|
|
203
|
+
render: () => (
|
|
204
|
+
<Story
|
|
205
|
+
text={str(text.tasks, '', text.list)}
|
|
206
|
+
extensions={[
|
|
207
|
+
tasklist(),
|
|
208
|
+
listener((text) => {
|
|
209
|
+
console.log(text);
|
|
210
|
+
}),
|
|
211
|
+
]}
|
|
212
|
+
/>
|
|
213
|
+
),
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const Autocomplete = {
|
|
217
|
+
render: () => (
|
|
218
|
+
<Story
|
|
219
|
+
text={str('# Autocomplete', '', '', '', '', '', '')}
|
|
220
|
+
extensions={[
|
|
221
|
+
link({ onRender }),
|
|
222
|
+
autocomplete({
|
|
223
|
+
getOptions: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
224
|
+
}),
|
|
225
|
+
]}
|
|
226
|
+
/>
|
|
227
|
+
),
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const Diagnostics = {
|
|
231
|
+
render: () => (
|
|
232
|
+
<Story
|
|
233
|
+
text={document}
|
|
234
|
+
extensions={[
|
|
235
|
+
// Cursor moved.
|
|
236
|
+
EditorView.updateListener.of((update) => {
|
|
237
|
+
console.log('update', update.view.state.selection.main.head);
|
|
238
|
+
}),
|
|
239
|
+
]}
|
|
240
|
+
/>
|
|
241
|
+
),
|
|
242
|
+
};
|
|
@@ -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,26 @@ 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';
|
|
19
|
+
import { yCollab } from 'y-codemirror.next';
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { generateName } from '@dxos/display-name';
|
|
22
22
|
import { useThemeContext } from '@dxos/react-ui';
|
|
23
|
+
import { getColorForValue, inputSurface, mx } from '@dxos/react-ui-theme';
|
|
24
|
+
import { type YText } from '@dxos/text-model';
|
|
23
25
|
|
|
24
|
-
import { basicBundle,
|
|
25
|
-
import {
|
|
26
|
-
import type
|
|
26
|
+
import { basicBundle, markdownBundle } from './extensions';
|
|
27
|
+
import { defaultTheme, textTheme } from './themes';
|
|
28
|
+
import { type EditorModel } from '../../hooks';
|
|
29
|
+
import { type ThemeStyles } from '../../styles';
|
|
27
30
|
|
|
28
31
|
export const EditorModes = ['default', 'vim'] as const;
|
|
29
32
|
export type EditorMode = (typeof EditorModes)[number];
|
|
@@ -37,7 +40,7 @@ export type CursorInfo = {
|
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
export type TextEditorRef = {
|
|
40
|
-
|
|
43
|
+
root: HTMLDivElement | null;
|
|
41
44
|
state?: EditorState;
|
|
42
45
|
view?: EditorView;
|
|
43
46
|
};
|
|
@@ -65,66 +68,66 @@ export type TextEditorProps = {
|
|
|
65
68
|
* NOTE: Rather than adding properties, try to create extensions that can be reused.
|
|
66
69
|
*/
|
|
67
70
|
export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
68
|
-
({ model, extensions = [], slots, editorMode }, forwardedRef) => {
|
|
71
|
+
({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
|
|
69
72
|
const { themeMode } = useThemeContext();
|
|
70
73
|
const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
// TODO(burdon): Factor out extension (remove logic from react-ui-editor).
|
|
76
|
+
// const collaboration = useCollaboration(model, themeMode);
|
|
77
|
+
const { provider, peer } = model;
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (provider && peer) {
|
|
80
|
+
provider.awareness.setLocalStateField('user', {
|
|
81
|
+
name: peer.name ?? generateName(peer.id),
|
|
82
|
+
color: getColorForValue({ value: peer.id, type: 'color' }),
|
|
83
|
+
colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}, [provider, peer, themeMode]);
|
|
87
|
+
|
|
88
|
+
const [root, setRoot] = useState<HTMLDivElement | null>(null);
|
|
73
89
|
const [state, setState] = useState<EditorState>();
|
|
74
90
|
const [view, setView] = useState<EditorView>();
|
|
75
|
-
useImperativeHandle(
|
|
76
|
-
forwardedRef,
|
|
77
|
-
() => ({
|
|
78
|
-
editor: parent,
|
|
79
|
-
state,
|
|
80
|
-
view,
|
|
81
|
-
}),
|
|
82
|
-
[view, state, parent],
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// TODO(burdon): Pass in extension?
|
|
86
|
-
const collaboration = useCollaboration(model, themeMode);
|
|
91
|
+
useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
|
|
87
92
|
|
|
88
93
|
useEffect(() => {
|
|
89
|
-
if (!
|
|
94
|
+
if (!root) {
|
|
90
95
|
return;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
const initialText = isDocAccessor(model.content)
|
|
94
|
-
? get(model.content.handle.docSync(), model.content.path)
|
|
95
|
-
: model.content?.toString();
|
|
96
|
-
|
|
97
98
|
const state = EditorState.create({
|
|
98
|
-
doc:
|
|
99
|
+
doc: model.text(),
|
|
99
100
|
extensions: [
|
|
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
|
+
EditorView.baseTheme(defaultTheme),
|
|
106
|
+
EditorView.theme(slots?.editor?.theme ?? {}),
|
|
107
|
+
// TODO(burdon): themeMode doesn't change in storybooks.
|
|
108
|
+
EditorView.darkTheme.of(themeMode === 'dark'),
|
|
109
|
+
|
|
110
|
+
// Storage and replication.
|
|
111
|
+
yCollab(model.content as YText, model.provider?.awareness),
|
|
108
112
|
|
|
109
113
|
// Custom.
|
|
110
114
|
...extensions,
|
|
111
115
|
].filter(Boolean) as Extension[],
|
|
112
116
|
});
|
|
113
117
|
|
|
114
|
-
setState(state);
|
|
115
|
-
|
|
116
118
|
// NOTE: This repaints the editor.
|
|
117
119
|
// If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
|
|
118
120
|
// Ideally this should not be hit except when changing between text objects.
|
|
119
121
|
view?.destroy();
|
|
120
|
-
setView(new EditorView({ state, parent }));
|
|
122
|
+
setView(new EditorView({ state, parent: root }));
|
|
123
|
+
setState(state);
|
|
121
124
|
|
|
122
125
|
return () => {
|
|
123
126
|
view?.destroy();
|
|
124
127
|
setView(undefined);
|
|
125
128
|
setState(undefined);
|
|
126
129
|
};
|
|
127
|
-
}, [
|
|
130
|
+
}, [root, model.content, themeMode, editorMode]);
|
|
128
131
|
|
|
129
132
|
const handleKeyUp = useCallback(
|
|
130
133
|
(event: KeyboardEvent) => {
|
|
@@ -136,7 +139,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
case 'Escape': {
|
|
139
|
-
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) &&
|
|
142
|
+
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
|
|
140
143
|
break;
|
|
141
144
|
}
|
|
142
145
|
}
|
|
@@ -147,7 +150,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
147
150
|
return (
|
|
148
151
|
<div
|
|
149
152
|
key={model.id}
|
|
150
|
-
ref={
|
|
153
|
+
ref={setRoot}
|
|
151
154
|
tabIndex={0}
|
|
152
155
|
{...slots?.root}
|
|
153
156
|
{...(editorMode !== 'vim' && tabsterDOMAttribute)}
|
|
@@ -157,15 +160,16 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
157
160
|
},
|
|
158
161
|
);
|
|
159
162
|
|
|
163
|
+
// TODO(burdon): Set default text theme?
|
|
160
164
|
export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
161
|
-
({ extensions
|
|
165
|
+
({ 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
|
-
extensions={extensions}
|
|
168
|
-
slots={
|
|
171
|
+
extensions={[basicBundle({ themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
172
|
+
slots={slots}
|
|
169
173
|
{...props}
|
|
170
174
|
/>
|
|
171
175
|
);
|
|
@@ -173,16 +177,29 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
173
177
|
);
|
|
174
178
|
|
|
175
179
|
export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
176
|
-
({ extensions
|
|
180
|
+
({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
177
181
|
const { themeMode } = useThemeContext();
|
|
178
|
-
const
|
|
182
|
+
const slots = defaultsDeep({}, _slots, defaultSlots);
|
|
179
183
|
return (
|
|
180
184
|
<BaseTextEditor
|
|
181
185
|
ref={forwardedRef}
|
|
182
|
-
extensions={extensions}
|
|
183
|
-
slots={
|
|
186
|
+
extensions={[markdownBundle({ themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
187
|
+
slots={slots}
|
|
184
188
|
{...props}
|
|
185
189
|
/>
|
|
186
190
|
);
|
|
187
191
|
},
|
|
188
192
|
);
|
|
193
|
+
|
|
194
|
+
export const defaultSlots: TextEditorSlots = {
|
|
195
|
+
root: {
|
|
196
|
+
className: mx('p-2', inputSurface),
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const defaultTextSlots: TextEditorSlots = {
|
|
201
|
+
...defaultSlots,
|
|
202
|
+
editor: {
|
|
203
|
+
theme: textTheme,
|
|
204
|
+
},
|
|
205
|
+
};
|