@dxos/react-ui-editor 0.8.1-staging.9eaf14f → 0.8.2-main.12df754
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 +499 -371
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +67 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/node/index.cjs +515 -379
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +101 -0
- package/dist/lib/node/testing/index.cjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +499 -371
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +69 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/components/EditorToolbar/util.d.ts +3 -3
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/{viewMode.d.ts → view-mode.d.ts} +1 -1
- package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -0
- package/dist/types/src/defaults.d.ts +1 -0
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/command/action.d.ts +17 -0
- package/dist/types/src/extensions/command/action.d.ts.map +1 -0
- package/dist/types/src/extensions/command/command.d.ts +5 -10
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts +4 -2
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +1 -0
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +7 -2
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +9 -11
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +9 -7
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +1 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts +4 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +2 -2
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts +4 -1
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
- package/dist/types/src/extensions/preview/index.d.ts +2 -0
- package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
- package/dist/types/src/extensions/preview/preview.d.ts +39 -0
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextEditor.d.ts +2 -1
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/{InputMode.stories.d.ts → stories/InputMode.stories.d.ts} +1 -1
- package/dist/types/src/stories/InputMode.stories.d.ts.map +1 -0
- package/dist/types/src/{TextEditor.stories.d.ts → stories/TextEditorBasic.stories.d.ts} +2 -35
- package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +1 -0
- package/dist/types/src/stories/TextEditorComments.stories.d.ts +13 -0
- package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +1 -0
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts +13 -0
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +1 -0
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts +19 -0
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +1 -0
- package/dist/types/src/stories/story-utils.d.ts +53 -0
- package/dist/types/src/stories/story-utils.d.ts.map +1 -0
- package/dist/types/src/testing/RefPopover.d.ts +21 -0
- package/dist/types/src/testing/RefPopover.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +5 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/react.d.ts +6 -1
- package/dist/types/src/util/react.d.ts.map +1 -1
- package/package.json +33 -27
- package/src/components/EditorToolbar/EditorToolbar.tsx +2 -2
- package/src/components/EditorToolbar/util.ts +3 -3
- package/src/defaults.ts +5 -3
- package/src/extensions/automerge/automerge.stories.tsx +3 -11
- package/src/extensions/command/action.ts +49 -0
- package/src/extensions/command/command.ts +9 -27
- package/src/extensions/command/hint.ts +33 -30
- package/src/extensions/command/index.ts +1 -0
- package/src/extensions/command/menu.ts +11 -8
- package/src/extensions/command/state.ts +41 -61
- package/src/extensions/comments.ts +9 -9
- package/src/extensions/folding.tsx +1 -1
- package/src/extensions/index.ts +1 -0
- package/src/extensions/markdown/decorate.ts +4 -3
- package/src/extensions/markdown/formatting.ts +2 -2
- package/src/extensions/markdown/image.ts +12 -11
- package/src/extensions/markdown/link.ts +33 -24
- package/src/extensions/preview/index.ts +5 -0
- package/src/extensions/preview/preview.ts +271 -0
- package/src/hooks/useTextEditor.ts +4 -3
- package/src/{InputMode.stories.tsx → stories/InputMode.stories.tsx} +4 -4
- package/src/stories/TextEditorBasic.stories.tsx +289 -0
- package/src/stories/TextEditorComments.stories.tsx +99 -0
- package/src/stories/TextEditorPreview.stories.tsx +239 -0
- package/src/stories/TextEditorSpecial.stories.tsx +107 -0
- package/src/stories/story-utils.tsx +329 -0
- package/src/testing/RefPopover.tsx +74 -0
- package/src/testing/index.ts +5 -0
- package/src/types.ts +7 -0
- package/src/util/react.tsx +20 -2
- package/dist/types/src/InputMode.stories.d.ts.map +0 -1
- package/dist/types/src/TextEditor.stories.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +0 -1
- package/dist/types/src/extensions/command/preview.d.ts +0 -12
- package/dist/types/src/extensions/command/preview.d.ts.map +0 -1
- package/dist/types/src/fragments.d.ts +0 -3
- package/dist/types/src/fragments.d.ts.map +0 -1
- package/src/TextEditor.stories.tsx +0 -856
- package/src/extensions/command/preview.ts +0 -79
- package/src/fragments.ts +0 -19
- /package/src/components/EditorToolbar/{viewMode.ts → view-mode.ts} +0 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import '@dxos-theme';
|
6
|
+
|
7
|
+
import { javascript } from '@codemirror/lang-javascript';
|
8
|
+
import { markdown } from '@codemirror/lang-markdown';
|
9
|
+
import { openSearchPanel } from '@codemirror/search';
|
10
|
+
import React from 'react';
|
11
|
+
|
12
|
+
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
13
|
+
|
14
|
+
import {
|
15
|
+
DefaultStory,
|
16
|
+
defaultExtensions,
|
17
|
+
allExtensions,
|
18
|
+
text,
|
19
|
+
str,
|
20
|
+
content,
|
21
|
+
longText,
|
22
|
+
largeWithImages,
|
23
|
+
headings,
|
24
|
+
global,
|
25
|
+
renderLinkButton,
|
26
|
+
renderLinkTooltip,
|
27
|
+
links,
|
28
|
+
names,
|
29
|
+
} from './story-utils';
|
30
|
+
import { editorMonospace } from '../defaults';
|
31
|
+
import {
|
32
|
+
InputModeExtensions,
|
33
|
+
selectionState,
|
34
|
+
decorateMarkdown,
|
35
|
+
folding,
|
36
|
+
image,
|
37
|
+
linkTooltip,
|
38
|
+
table,
|
39
|
+
autocomplete,
|
40
|
+
mention,
|
41
|
+
} from '../extensions';
|
42
|
+
|
43
|
+
const meta: Meta<typeof DefaultStory> = {
|
44
|
+
title: 'ui/react-ui-editor/TextEditor',
|
45
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
46
|
+
render: DefaultStory,
|
47
|
+
parameters: { layout: 'fullscreen' },
|
48
|
+
};
|
49
|
+
|
50
|
+
export default meta;
|
51
|
+
|
52
|
+
//
|
53
|
+
// Default
|
54
|
+
//
|
55
|
+
|
56
|
+
export const Default = {
|
57
|
+
render: () => <DefaultStory text={text} extensions={defaultExtensions} />,
|
58
|
+
};
|
59
|
+
|
60
|
+
//
|
61
|
+
// Everything
|
62
|
+
//
|
63
|
+
|
64
|
+
export const Everything = {
|
65
|
+
render: () => <DefaultStory text={text} extensions={allExtensions} selection={{ anchor: 99, head: 110 }} />,
|
66
|
+
};
|
67
|
+
|
68
|
+
//
|
69
|
+
// Empty
|
70
|
+
//
|
71
|
+
|
72
|
+
export const Empty = {
|
73
|
+
render: () => <DefaultStory extensions={defaultExtensions} />,
|
74
|
+
};
|
75
|
+
|
76
|
+
//
|
77
|
+
// Readonly
|
78
|
+
//
|
79
|
+
|
80
|
+
export const Readonly = {
|
81
|
+
render: () => <DefaultStory text={text} extensions={defaultExtensions} readOnly />,
|
82
|
+
};
|
83
|
+
|
84
|
+
//
|
85
|
+
// No Extensions
|
86
|
+
//
|
87
|
+
|
88
|
+
export const NoExtensions = {
|
89
|
+
render: () => <DefaultStory text={text} />,
|
90
|
+
};
|
91
|
+
|
92
|
+
//
|
93
|
+
// Vim
|
94
|
+
//
|
95
|
+
|
96
|
+
export const Vim = {
|
97
|
+
render: () => (
|
98
|
+
<DefaultStory
|
99
|
+
text={str('# Vim Mode', '', 'The distant future. The year 2000.', '', content.paragraphs)}
|
100
|
+
extensions={[defaultExtensions, InputModeExtensions.vim]}
|
101
|
+
/>
|
102
|
+
),
|
103
|
+
};
|
104
|
+
|
105
|
+
//
|
106
|
+
// Scrolling
|
107
|
+
//
|
108
|
+
|
109
|
+
export const Folding = {
|
110
|
+
render: () => <DefaultStory text={text} extensions={[folding()]} />,
|
111
|
+
};
|
112
|
+
|
113
|
+
export const Scrolling = {
|
114
|
+
render: () => (
|
115
|
+
<DefaultStory
|
116
|
+
text={str('# Large Document', '', longText)}
|
117
|
+
extensions={selectionState({
|
118
|
+
setState: (id, state) => global.set(id, state),
|
119
|
+
getState: (id) => global.get(id),
|
120
|
+
})}
|
121
|
+
/>
|
122
|
+
),
|
123
|
+
};
|
124
|
+
|
125
|
+
export const ScrollingWithImages = {
|
126
|
+
render: () => (
|
127
|
+
<DefaultStory text={str('# Large Document', '', largeWithImages)} extensions={[decorateMarkdown(), image()]} />
|
128
|
+
),
|
129
|
+
};
|
130
|
+
|
131
|
+
export const ScrollTo = {
|
132
|
+
render: () => {
|
133
|
+
// NOTE: Selection won't appear if text is reformatted.
|
134
|
+
const word = 'Scroll to here...';
|
135
|
+
const text = str('# Scroll To', longText, '', word, '', longText);
|
136
|
+
const idx = text.indexOf(word);
|
137
|
+
return (
|
138
|
+
<DefaultStory
|
139
|
+
text={text}
|
140
|
+
extensions={defaultExtensions}
|
141
|
+
scrollTo={idx}
|
142
|
+
selection={{ anchor: idx, head: idx + word.length }}
|
143
|
+
/>
|
144
|
+
);
|
145
|
+
},
|
146
|
+
};
|
147
|
+
|
148
|
+
//
|
149
|
+
// Markdown
|
150
|
+
//
|
151
|
+
|
152
|
+
export const Blockquote = {
|
153
|
+
render: () => (
|
154
|
+
<DefaultStory
|
155
|
+
text={str('> Blockquote', 'continuation', content.footer)}
|
156
|
+
extensions={decorateMarkdown()}
|
157
|
+
debug='raw'
|
158
|
+
/>
|
159
|
+
),
|
160
|
+
};
|
161
|
+
|
162
|
+
export const Headings = {
|
163
|
+
render: () => (
|
164
|
+
<DefaultStory text={headings} extensions={decorateMarkdown({ numberedHeadings: { from: 2, to: 4 } })} />
|
165
|
+
),
|
166
|
+
};
|
167
|
+
|
168
|
+
export const Links = {
|
169
|
+
render: () => (
|
170
|
+
<DefaultStory text={str(content.links, content.footer)} extensions={[linkTooltip(renderLinkTooltip)]} />
|
171
|
+
),
|
172
|
+
};
|
173
|
+
|
174
|
+
export const Image = {
|
175
|
+
render: () => <DefaultStory text={str(content.image, content.footer)} extensions={[image()]} />,
|
176
|
+
};
|
177
|
+
|
178
|
+
export const Code = {
|
179
|
+
render: () => <DefaultStory text={str(content.codeblocks, content.footer)} extensions={[decorateMarkdown()]} />,
|
180
|
+
};
|
181
|
+
|
182
|
+
export const Lists = {
|
183
|
+
render: () => (
|
184
|
+
<DefaultStory
|
185
|
+
text={str(content.tasks, '', content.bullets, '', content.numbered, content.footer)}
|
186
|
+
extensions={[decorateMarkdown()]}
|
187
|
+
/>
|
188
|
+
),
|
189
|
+
};
|
190
|
+
|
191
|
+
export const BulletList = {
|
192
|
+
render: () => <DefaultStory text={str(content.bullets, content.footer)} extensions={[decorateMarkdown()]} />,
|
193
|
+
};
|
194
|
+
|
195
|
+
export const OrderedList = {
|
196
|
+
render: () => <DefaultStory text={str(content.numbered, content.footer)} extensions={[decorateMarkdown()]} />,
|
197
|
+
};
|
198
|
+
|
199
|
+
export const TaskList = {
|
200
|
+
render: () => (
|
201
|
+
<DefaultStory text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} debug='raw+tree' />
|
202
|
+
),
|
203
|
+
};
|
204
|
+
|
205
|
+
export const Table = {
|
206
|
+
render: () => <DefaultStory text={str(content.table, content.footer)} extensions={[decorateMarkdown(), table()]} />,
|
207
|
+
};
|
208
|
+
|
209
|
+
//
|
210
|
+
// Commented out
|
211
|
+
//
|
212
|
+
|
213
|
+
export const CommentedOut = {
|
214
|
+
render: () => (
|
215
|
+
<DefaultStory
|
216
|
+
text={str('# Commented out', '', content.comment, content.footer)}
|
217
|
+
extensions={[
|
218
|
+
decorateMarkdown(),
|
219
|
+
markdown(),
|
220
|
+
// commentBlock()
|
221
|
+
]}
|
222
|
+
/>
|
223
|
+
),
|
224
|
+
};
|
225
|
+
|
226
|
+
//
|
227
|
+
// Typescript
|
228
|
+
//
|
229
|
+
|
230
|
+
export const Typescript = {
|
231
|
+
render: () => (
|
232
|
+
<DefaultStory
|
233
|
+
text={content.typescript}
|
234
|
+
lineNumbers
|
235
|
+
extensions={[editorMonospace, javascript({ typescript: true })]}
|
236
|
+
/>
|
237
|
+
),
|
238
|
+
};
|
239
|
+
|
240
|
+
//
|
241
|
+
// Custom
|
242
|
+
//
|
243
|
+
|
244
|
+
export const Autocomplete = {
|
245
|
+
render: () => (
|
246
|
+
<DefaultStory
|
247
|
+
text={str('# Autocomplete', '', 'Press Ctrl-Space...', content.footer)}
|
248
|
+
extensions={[
|
249
|
+
decorateMarkdown({ renderLinkButton }),
|
250
|
+
autocomplete({
|
251
|
+
onSearch: (text) => {
|
252
|
+
return links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase()));
|
253
|
+
},
|
254
|
+
}),
|
255
|
+
]}
|
256
|
+
/>
|
257
|
+
),
|
258
|
+
};
|
259
|
+
|
260
|
+
//
|
261
|
+
// Mention
|
262
|
+
//
|
263
|
+
|
264
|
+
export const Mention = {
|
265
|
+
render: () => (
|
266
|
+
<DefaultStory
|
267
|
+
text={str('# Mention', '', 'Type @...', content.footer)}
|
268
|
+
extensions={[
|
269
|
+
mention({
|
270
|
+
onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
|
271
|
+
}),
|
272
|
+
]}
|
273
|
+
/>
|
274
|
+
),
|
275
|
+
};
|
276
|
+
|
277
|
+
//
|
278
|
+
// Search
|
279
|
+
//
|
280
|
+
|
281
|
+
export const Search = {
|
282
|
+
render: () => (
|
283
|
+
<DefaultStory
|
284
|
+
text={str('# Search', text)}
|
285
|
+
extensions={defaultExtensions}
|
286
|
+
onReady={(view) => openSearchPanel(view)}
|
287
|
+
/>
|
288
|
+
),
|
289
|
+
};
|
@@ -0,0 +1,99 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import '@dxos-theme';
|
6
|
+
|
7
|
+
import { effect, useSignal } from '@preact/signals-react';
|
8
|
+
import React, { type FC } from 'react';
|
9
|
+
|
10
|
+
import { keySymbols, parseShortcut } from '@dxos/keyboard';
|
11
|
+
import { PublicKey } from '@dxos/keys';
|
12
|
+
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
13
|
+
|
14
|
+
import { DefaultStory, str, content, longText } from './story-utils';
|
15
|
+
import { annotations, comments, createExternalCommentSync } from '../extensions';
|
16
|
+
import { type Comment } from '../types';
|
17
|
+
import { createRenderer } from '../util';
|
18
|
+
|
19
|
+
const meta: Meta<typeof DefaultStory> = {
|
20
|
+
title: 'ui/react-ui-editor/TextEditor',
|
21
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
22
|
+
render: DefaultStory,
|
23
|
+
parameters: { layout: 'fullscreen' },
|
24
|
+
};
|
25
|
+
|
26
|
+
export default meta;
|
27
|
+
|
28
|
+
//
|
29
|
+
// Comments
|
30
|
+
//
|
31
|
+
|
32
|
+
export const Comments = {
|
33
|
+
render: () => {
|
34
|
+
const _comments = useSignal<Comment[]>([]);
|
35
|
+
return (
|
36
|
+
<DefaultStory
|
37
|
+
text={str('# Comments', '', content.paragraphs, content.footer)}
|
38
|
+
extensions={[
|
39
|
+
createExternalCommentSync(
|
40
|
+
'test',
|
41
|
+
(sink) => effect(() => sink()),
|
42
|
+
() => _comments.value,
|
43
|
+
),
|
44
|
+
comments({
|
45
|
+
id: 'test',
|
46
|
+
renderTooltip: createRenderer(CommentTooltip),
|
47
|
+
onCreate: ({ cursor }) => {
|
48
|
+
const id = PublicKey.random().toHex();
|
49
|
+
_comments.value = [..._comments.value, { id, cursor }];
|
50
|
+
return id;
|
51
|
+
},
|
52
|
+
onSelect: (state) => {
|
53
|
+
const debug = false;
|
54
|
+
if (debug) {
|
55
|
+
console.log(
|
56
|
+
'update',
|
57
|
+
JSON.stringify({
|
58
|
+
comments: state.comments.length,
|
59
|
+
active: state.selection.current?.slice(0, 8),
|
60
|
+
closest: state.selection.closest?.slice(0, 8),
|
61
|
+
}),
|
62
|
+
);
|
63
|
+
}
|
64
|
+
},
|
65
|
+
}),
|
66
|
+
]}
|
67
|
+
/>
|
68
|
+
);
|
69
|
+
},
|
70
|
+
};
|
71
|
+
|
72
|
+
const Key: FC<{ char: string }> = ({ char }) => (
|
73
|
+
<span className='flex justify-center items-center w-[24px] h-[24px] rounded text-xs bg-neutral-200 text-black'>
|
74
|
+
{char}
|
75
|
+
</span>
|
76
|
+
);
|
77
|
+
|
78
|
+
const CommentTooltip: FC<{ shortcut: string }> = ({ shortcut }) => {
|
79
|
+
return (
|
80
|
+
<div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
|
81
|
+
<div>Create comment</div>
|
82
|
+
<div className='flex gap-1'>
|
83
|
+
{keySymbols(parseShortcut(shortcut)).map((char) => (
|
84
|
+
<Key key={char} char={char} />
|
85
|
+
))}
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
);
|
89
|
+
};
|
90
|
+
|
91
|
+
//
|
92
|
+
// Annotations
|
93
|
+
//
|
94
|
+
|
95
|
+
export const Annotations = {
|
96
|
+
render: () => (
|
97
|
+
<DefaultStory text={str('# Annotations', '', longText)} extensions={[annotations({ match: /volup/gi })]} />
|
98
|
+
),
|
99
|
+
};
|
@@ -0,0 +1,239 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import '@dxos-theme';
|
6
|
+
|
7
|
+
import React, { useState, useEffect, type FC, type KeyboardEvent } from 'react';
|
8
|
+
|
9
|
+
import { faker } from '@dxos/random';
|
10
|
+
import { Button, Icon, IconButton, Input, Popover } from '@dxos/react-ui';
|
11
|
+
import { mx, hoverableHidden } from '@dxos/react-ui-theme';
|
12
|
+
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
13
|
+
|
14
|
+
import { DefaultStory, str } from './story-utils';
|
15
|
+
import { editorWidth } from '../defaults';
|
16
|
+
import {
|
17
|
+
preview,
|
18
|
+
command,
|
19
|
+
image,
|
20
|
+
type PreviewOptions,
|
21
|
+
type PreviewLinkRef,
|
22
|
+
type PreviewLinkTarget,
|
23
|
+
type PreviewRenderProps,
|
24
|
+
type Action,
|
25
|
+
} from '../extensions';
|
26
|
+
import { RefPopover, useRefPopover } from '../testing';
|
27
|
+
import { createRenderer } from '../util';
|
28
|
+
|
29
|
+
const meta: Meta<typeof DefaultStory> = {
|
30
|
+
title: 'ui/react-ui-editor/TextEditor',
|
31
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
32
|
+
render: DefaultStory,
|
33
|
+
parameters: { layout: 'fullscreen' },
|
34
|
+
};
|
35
|
+
|
36
|
+
export default meta;
|
37
|
+
|
38
|
+
//
|
39
|
+
// Preview
|
40
|
+
//
|
41
|
+
|
42
|
+
export const Preview = {
|
43
|
+
render: () => (
|
44
|
+
<RefPopover.Provider onLookup={handlePreviewLookup}>
|
45
|
+
<DefaultStory
|
46
|
+
text={str(
|
47
|
+
'# Preview',
|
48
|
+
'',
|
49
|
+
'This project is part of the [DXOS][dxn:queue:data:123] SDK.',
|
50
|
+
'',
|
51
|
+
'![DXOS][?dxn:queue:data:123]',
|
52
|
+
'',
|
53
|
+
'It consists of [ECHO][dxn:queue:data:echo], [HALO][dxn:queue:data:halo], and [MESH][dxn:queue:data:mesh].',
|
54
|
+
'',
|
55
|
+
'## Deep dive',
|
56
|
+
'',
|
57
|
+
'![ECHO][dxn:queue:data:echo]',
|
58
|
+
'',
|
59
|
+
'',
|
60
|
+
)}
|
61
|
+
extensions={[
|
62
|
+
image(),
|
63
|
+
preview({
|
64
|
+
renderBlock: createRenderer(PreviewBlock),
|
65
|
+
onLookup: handlePreviewLookup,
|
66
|
+
}),
|
67
|
+
]}
|
68
|
+
/>
|
69
|
+
<PreviewCard />
|
70
|
+
</RefPopover.Provider>
|
71
|
+
),
|
72
|
+
};
|
73
|
+
|
74
|
+
const handlePreviewLookup = async ({ label, ref }: PreviewLinkRef): Promise<PreviewLinkTarget> => {
|
75
|
+
// Random text.
|
76
|
+
faker.seed(ref.split('').reduce((acc: number, char: string) => acc + char.charCodeAt(0), 1));
|
77
|
+
const text = Array.from({ length: 2 }, () => faker.lorem.paragraphs()).join('\n\n');
|
78
|
+
return {
|
79
|
+
label,
|
80
|
+
text,
|
81
|
+
};
|
82
|
+
};
|
83
|
+
|
84
|
+
// Async lookup.
|
85
|
+
// TODO(burdon): Handle error.s
|
86
|
+
const useRefTarget = (link: PreviewLinkRef, onLookup: PreviewOptions['onLookup']): PreviewLinkTarget | undefined => {
|
87
|
+
const [target, setTarget] = useState<PreviewLinkTarget | undefined>();
|
88
|
+
useEffect(() => {
|
89
|
+
void onLookup?.(link).then((target) => setTarget(target ?? undefined));
|
90
|
+
}, [link, onLookup]);
|
91
|
+
|
92
|
+
return target;
|
93
|
+
};
|
94
|
+
|
95
|
+
const PreviewCard = () => {
|
96
|
+
const { link, target } = useRefPopover('PreviewCard');
|
97
|
+
return (
|
98
|
+
<Popover.Portal>
|
99
|
+
<Popover.Content onOpenAutoFocus={(e) => e.preventDefault()}>
|
100
|
+
<Popover.Viewport>
|
101
|
+
<div className='grow truncate'>{link?.label}</div>
|
102
|
+
{target && <div className='line-clamp-3'>{target.text}</div>}
|
103
|
+
</Popover.Viewport>
|
104
|
+
<Popover.Arrow />
|
105
|
+
</Popover.Content>
|
106
|
+
</Popover.Portal>
|
107
|
+
);
|
108
|
+
};
|
109
|
+
|
110
|
+
// TODO(burdon): Replace with card.
|
111
|
+
const PreviewBlock: FC<PreviewRenderProps> = ({ readonly, link, onAction, onLookup }) => {
|
112
|
+
const target = useRefTarget(link, onLookup);
|
113
|
+
return (
|
114
|
+
<div className='group flex flex-col gap-2'>
|
115
|
+
<div className='flex items-center gap-4'>
|
116
|
+
<div className='grow truncate'>
|
117
|
+
{/* <span className='text-xs text-subdued mie-2'>Prompt</span> */}
|
118
|
+
{link.label}
|
119
|
+
</div>
|
120
|
+
{!readonly && (
|
121
|
+
<div className='flex gap-1'>
|
122
|
+
{(link.suggest && (
|
123
|
+
<>
|
124
|
+
{target && (
|
125
|
+
<IconButton
|
126
|
+
classNames='text-green-500'
|
127
|
+
label='Apply'
|
128
|
+
icon={'ph--check--regular'}
|
129
|
+
onClick={() => onAction({ type: 'insert', link, target })}
|
130
|
+
/>
|
131
|
+
)}
|
132
|
+
<IconButton
|
133
|
+
classNames='text-red-500'
|
134
|
+
label='Cancel'
|
135
|
+
icon={'ph--x--regular'}
|
136
|
+
onClick={() => onAction({ type: 'delete', link })}
|
137
|
+
/>
|
138
|
+
</>
|
139
|
+
)) || (
|
140
|
+
<IconButton
|
141
|
+
iconOnly
|
142
|
+
label='Delete'
|
143
|
+
icon={'ph--x--regular'}
|
144
|
+
classNames={hoverableHidden}
|
145
|
+
onClick={() => onAction({ type: 'delete', link })}
|
146
|
+
/>
|
147
|
+
)}
|
148
|
+
</div>
|
149
|
+
)}
|
150
|
+
</div>
|
151
|
+
{target && <div className='line-clamp-3'>{target.text}</div>}
|
152
|
+
</div>
|
153
|
+
);
|
154
|
+
};
|
155
|
+
|
156
|
+
//
|
157
|
+
// Command
|
158
|
+
//
|
159
|
+
|
160
|
+
export const Command = {
|
161
|
+
render: () => (
|
162
|
+
<DefaultStory
|
163
|
+
text={str(
|
164
|
+
'# Preview',
|
165
|
+
'',
|
166
|
+
'This project is part of the [DXOS][dxn:queue:data:123] SDK.',
|
167
|
+
'',
|
168
|
+
'![DXOS][dxn:queue:data:123]',
|
169
|
+
'',
|
170
|
+
)}
|
171
|
+
extensions={[
|
172
|
+
preview({
|
173
|
+
renderBlock: createRenderer(PreviewBlock),
|
174
|
+
onLookup: handlePreviewLookup,
|
175
|
+
}),
|
176
|
+
command({
|
177
|
+
renderMenu: createRenderer(CommandMenu),
|
178
|
+
renderDialog: createRenderer(CommandDialog),
|
179
|
+
onHint: () => 'Press / for commands.',
|
180
|
+
}),
|
181
|
+
]}
|
182
|
+
/>
|
183
|
+
),
|
184
|
+
};
|
185
|
+
|
186
|
+
const CommandMenu = ({ onAction }: { onAction: () => void }) => {
|
187
|
+
return (
|
188
|
+
<Button classNames='p-1 aspect-square' onClick={onAction}>
|
189
|
+
<Icon icon='ph--sparkle--regular' size={5} />
|
190
|
+
</Button>
|
191
|
+
);
|
192
|
+
};
|
193
|
+
|
194
|
+
const CommandDialog = ({ onAction }: { onAction: (action?: Action) => void }) => {
|
195
|
+
const [text, setText] = useState('');
|
196
|
+
|
197
|
+
const handleInsert = () => {
|
198
|
+
// TODO(burdon): Use queue ref.
|
199
|
+
const link = `[${text}](dxn:queue:data:123)`;
|
200
|
+
onAction(text.length ? { type: 'insert', text: link } : undefined);
|
201
|
+
};
|
202
|
+
|
203
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
204
|
+
switch (event.key) {
|
205
|
+
case 'Enter': {
|
206
|
+
handleInsert();
|
207
|
+
break;
|
208
|
+
}
|
209
|
+
case 'Escape': {
|
210
|
+
onAction();
|
211
|
+
break;
|
212
|
+
}
|
213
|
+
}
|
214
|
+
};
|
215
|
+
|
216
|
+
return (
|
217
|
+
<div className='flex w-full justify-center'>
|
218
|
+
<div
|
219
|
+
className={mx(
|
220
|
+
'flex w-full p-2 gap-2 items-center bg-modalSurface border border-separator rounded-md',
|
221
|
+
editorWidth,
|
222
|
+
)}
|
223
|
+
>
|
224
|
+
<Input.Root>
|
225
|
+
<Input.TextInput
|
226
|
+
autoFocus={true}
|
227
|
+
placeholder='Ask a question...'
|
228
|
+
value={text}
|
229
|
+
onChange={(ev) => setText(ev.target.value)}
|
230
|
+
onKeyDown={handleKeyDown}
|
231
|
+
/>
|
232
|
+
</Input.Root>
|
233
|
+
<Button variant='ghost' classNames='pli-0' onClick={() => onAction({ type: 'cancel' })}>
|
234
|
+
<Icon icon='ph--x--regular' size={5} />
|
235
|
+
</Button>
|
236
|
+
</div>
|
237
|
+
</div>
|
238
|
+
);
|
239
|
+
};
|