@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,107 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import '@dxos-theme';
|
6
|
+
|
7
|
+
import defaultsDeep from 'lodash.defaultsdeep';
|
8
|
+
import React from 'react';
|
9
|
+
|
10
|
+
import { log } from '@dxos/log';
|
11
|
+
import { faker } from '@dxos/random';
|
12
|
+
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
13
|
+
|
14
|
+
import { DefaultStory, str, content } from './story-utils';
|
15
|
+
import { typewriter, blast, defaultOptions, dropFile, listener } from '../extensions';
|
16
|
+
|
17
|
+
const meta: Meta<typeof DefaultStory> = {
|
18
|
+
title: 'ui/react-ui-editor/TextEditor',
|
19
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
20
|
+
render: DefaultStory,
|
21
|
+
parameters: { layout: 'fullscreen' },
|
22
|
+
};
|
23
|
+
|
24
|
+
export default meta;
|
25
|
+
|
26
|
+
//
|
27
|
+
// Listener
|
28
|
+
//
|
29
|
+
|
30
|
+
export const Listener = {
|
31
|
+
render: () => (
|
32
|
+
<DefaultStory
|
33
|
+
text={str('# Listener', '', content.footer)}
|
34
|
+
extensions={[
|
35
|
+
listener({
|
36
|
+
onFocus: (focusing) => {
|
37
|
+
console.log({ focusing });
|
38
|
+
},
|
39
|
+
onChange: (text) => {
|
40
|
+
console.log({ text });
|
41
|
+
},
|
42
|
+
}),
|
43
|
+
]}
|
44
|
+
/>
|
45
|
+
),
|
46
|
+
};
|
47
|
+
|
48
|
+
//
|
49
|
+
// Typewriter
|
50
|
+
//
|
51
|
+
|
52
|
+
const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
|
53
|
+
|
54
|
+
export const Typewriter = {
|
55
|
+
render: () => (
|
56
|
+
<DefaultStory
|
57
|
+
text={str('# Typewriter', '', content.paragraphs, content.footer)}
|
58
|
+
extensions={[typewriter({ items: typewriterItems })]}
|
59
|
+
/>
|
60
|
+
),
|
61
|
+
};
|
62
|
+
|
63
|
+
//
|
64
|
+
// Blast
|
65
|
+
//
|
66
|
+
|
67
|
+
export const Blast = {
|
68
|
+
render: () => (
|
69
|
+
<DefaultStory
|
70
|
+
text={str('# Blast', '', content.paragraphs, content.codeblocks, content.paragraphs)}
|
71
|
+
extensions={[
|
72
|
+
typewriter({ items: typewriterItems }),
|
73
|
+
blast(
|
74
|
+
defaultsDeep(
|
75
|
+
{
|
76
|
+
effect: 2,
|
77
|
+
particleGravity: 0.2,
|
78
|
+
particleShrinkRate: 0.995,
|
79
|
+
color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
|
80
|
+
// color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
|
81
|
+
},
|
82
|
+
defaultOptions,
|
83
|
+
),
|
84
|
+
),
|
85
|
+
]}
|
86
|
+
/>
|
87
|
+
),
|
88
|
+
};
|
89
|
+
|
90
|
+
//
|
91
|
+
// DND
|
92
|
+
//
|
93
|
+
|
94
|
+
export const DND = {
|
95
|
+
render: () => (
|
96
|
+
<DefaultStory
|
97
|
+
text={str('# DND', '')}
|
98
|
+
extensions={[
|
99
|
+
dropFile({
|
100
|
+
onDrop: (view, event) => {
|
101
|
+
log.info('drop', event);
|
102
|
+
},
|
103
|
+
}),
|
104
|
+
]}
|
105
|
+
/>
|
106
|
+
),
|
107
|
+
};
|
@@ -0,0 +1,329 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type Completion } from '@codemirror/autocomplete';
|
6
|
+
import { type Extension } from '@codemirror/state';
|
7
|
+
import { type EditorView } from '@codemirror/view';
|
8
|
+
import React, { useEffect, useState, type FC } from 'react';
|
9
|
+
|
10
|
+
import { Expando } from '@dxos/echo-schema';
|
11
|
+
import { PublicKey } from '@dxos/keys';
|
12
|
+
import { live } from '@dxos/live-object';
|
13
|
+
import { faker } from '@dxos/random';
|
14
|
+
import { createDocAccessor, createObject } from '@dxos/react-client/echo';
|
15
|
+
import { useThemeContext, Icon } from '@dxos/react-ui';
|
16
|
+
import { mx } from '@dxos/react-ui-theme';
|
17
|
+
|
18
|
+
import { editorContent, editorGutter } from '../defaults';
|
19
|
+
import {
|
20
|
+
type EditorSelectionState,
|
21
|
+
type DebugNode,
|
22
|
+
decorateMarkdown,
|
23
|
+
formattingKeymap,
|
24
|
+
linkTooltip,
|
25
|
+
image,
|
26
|
+
table,
|
27
|
+
folding,
|
28
|
+
} from '../extensions';
|
29
|
+
import {
|
30
|
+
createDataExtensions,
|
31
|
+
createBasicExtensions,
|
32
|
+
createMarkdownExtensions,
|
33
|
+
createThemeExtensions,
|
34
|
+
debugTree,
|
35
|
+
} from '../extensions';
|
36
|
+
import { useTextEditor, type UseTextEditorProps } from '../hooks';
|
37
|
+
import { createRenderer } from '../util';
|
38
|
+
|
39
|
+
// Utility functions
|
40
|
+
export const str = (...lines: string[]) => lines.join('\n');
|
41
|
+
|
42
|
+
export const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
|
43
|
+
|
44
|
+
export const img = '';
|
45
|
+
|
46
|
+
export const code = str(
|
47
|
+
'// Code',
|
48
|
+
'const Component = () => {',
|
49
|
+
' const x = 100;',
|
50
|
+
'',
|
51
|
+
' return () => <div>Test</div>;',
|
52
|
+
'};',
|
53
|
+
);
|
54
|
+
|
55
|
+
// Content blocks for stories
|
56
|
+
export const content = {
|
57
|
+
tasks: str(
|
58
|
+
//
|
59
|
+
'### TaskList',
|
60
|
+
'',
|
61
|
+
`- [x] ${faker.lorem.sentences()}`,
|
62
|
+
`- [ ] ${faker.lorem.sentences()}`,
|
63
|
+
` - [ ] ${faker.lorem.sentences()}`,
|
64
|
+
` - [ ] ${faker.lorem.sentences()}`,
|
65
|
+
` - [x] ${faker.lorem.sentences()}`,
|
66
|
+
'',
|
67
|
+
),
|
68
|
+
|
69
|
+
bullets: str(
|
70
|
+
//
|
71
|
+
'### BulletList',
|
72
|
+
'',
|
73
|
+
`- ${faker.lorem.sentences()}`,
|
74
|
+
`- ${faker.lorem.sentences()}`,
|
75
|
+
` - ${faker.lorem.sentences()}`,
|
76
|
+
` - ${faker.lorem.sentences()}`,
|
77
|
+
`- ${faker.lorem.sentences()}`,
|
78
|
+
'',
|
79
|
+
),
|
80
|
+
|
81
|
+
numbered: str(
|
82
|
+
//
|
83
|
+
'### OrderedList (part 1)',
|
84
|
+
'',
|
85
|
+
`1. ${faker.lorem.sentences()}`,
|
86
|
+
`1. ${faker.lorem.sentences()}`,
|
87
|
+
`1. ${faker.lorem.sentences()}`,
|
88
|
+
` 1. ${faker.lorem.sentences()}`,
|
89
|
+
` 1. ${faker.lorem.sentences()}`,
|
90
|
+
` 1. ${faker.lorem.sentences()}`,
|
91
|
+
`1. ${faker.lorem.sentences()}`,
|
92
|
+
'',
|
93
|
+
'### OrderedList (part 2)',
|
94
|
+
'',
|
95
|
+
`1. ${faker.lorem.sentences()}`,
|
96
|
+
'',
|
97
|
+
),
|
98
|
+
|
99
|
+
typescript: code,
|
100
|
+
|
101
|
+
codeblocks: str('### Code', '', '```bash', '$ ls -las', '```', '', '```tsx', code, '```', ''),
|
102
|
+
|
103
|
+
comment: str('<!--', 'A comment', '-->', '', 'No comment.', 'Partial comment. <!-- comment. -->'),
|
104
|
+
|
105
|
+
links: str(
|
106
|
+
'### Links',
|
107
|
+
'',
|
108
|
+
'This is a naked link https://dxos.org within a sentence.',
|
109
|
+
'',
|
110
|
+
'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
|
111
|
+
'',
|
112
|
+
'This is all about https://dxos.org and related technologies.',
|
113
|
+
'',
|
114
|
+
),
|
115
|
+
|
116
|
+
table: str(
|
117
|
+
'### Tables',
|
118
|
+
'',
|
119
|
+
`| ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} |`,
|
120
|
+
`|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|`,
|
121
|
+
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
122
|
+
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
123
|
+
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
124
|
+
'',
|
125
|
+
),
|
126
|
+
|
127
|
+
image: str('### Image', '', img),
|
128
|
+
|
129
|
+
headings: str(
|
130
|
+
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
131
|
+
),
|
132
|
+
|
133
|
+
formatting: str('### Formatting', '', 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.', ''),
|
134
|
+
|
135
|
+
blockquotes: str(
|
136
|
+
'### Blockquotes',
|
137
|
+
'',
|
138
|
+
'> This is a block quote.',
|
139
|
+
'',
|
140
|
+
'> This is a long wrapping block quote. Neque reiciendis ullam quae error labore sit, at, et, nulla, aut at nostrum omnis quas nostrum, at consectetur vitae eos asperiores non omnis ullam in beatae at vitae deserunt asperiores sapiente.',
|
141
|
+
'',
|
142
|
+
'> This is ...',
|
143
|
+
'... a multi-line ...',
|
144
|
+
'block quote.',
|
145
|
+
'',
|
146
|
+
),
|
147
|
+
|
148
|
+
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
149
|
+
|
150
|
+
footer: str('', '', '', '', ''),
|
151
|
+
};
|
152
|
+
|
153
|
+
// Combined text for stories
|
154
|
+
export const text = str(
|
155
|
+
'# Markdown',
|
156
|
+
'Composer Markdown Editor',
|
157
|
+
'',
|
158
|
+
|
159
|
+
'---',
|
160
|
+
'## Basics',
|
161
|
+
content.blockquotes,
|
162
|
+
content.formatting,
|
163
|
+
content.links,
|
164
|
+
|
165
|
+
'---',
|
166
|
+
'## Lists',
|
167
|
+
content.bullets,
|
168
|
+
content.tasks,
|
169
|
+
content.numbered,
|
170
|
+
|
171
|
+
'---',
|
172
|
+
'## Misc',
|
173
|
+
content.codeblocks,
|
174
|
+
content.table,
|
175
|
+
content.image,
|
176
|
+
content.footer,
|
177
|
+
'=== LAST LINE ===',
|
178
|
+
);
|
179
|
+
|
180
|
+
// Shared links for autocomplete
|
181
|
+
export const links: Completion[] = [
|
182
|
+
{ label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
|
183
|
+
{ label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
|
184
|
+
{ label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
|
185
|
+
{ label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
|
186
|
+
{ label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
|
187
|
+
];
|
188
|
+
|
189
|
+
export const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
|
190
|
+
|
191
|
+
const hover =
|
192
|
+
'rounded-sm text-baseText text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
|
193
|
+
|
194
|
+
const LinkTooltip: FC<{ url: string }> = ({ url }) => {
|
195
|
+
const web = new URL(url);
|
196
|
+
return (
|
197
|
+
<a href={url} target='_blank' rel='noreferrer' className={mx(hover, 'flex items-center gap-2')}>
|
198
|
+
{web.origin}
|
199
|
+
<Icon icon='ph--arrow-square-out--regular' size={4} />
|
200
|
+
</a>
|
201
|
+
);
|
202
|
+
};
|
203
|
+
|
204
|
+
export const renderLinkTooltip = createRenderer(LinkTooltip);
|
205
|
+
|
206
|
+
const LinkButton: FC<{ url: string }> = ({ url }) => {
|
207
|
+
return (
|
208
|
+
<a href={url} target='_blank' rel='noreferrer' className={mx(hover)}>
|
209
|
+
<Icon icon='ph--arrow-square-out--regular' size={4} classNames='inline-block mis-1 mb-[3px]' />
|
210
|
+
</a>
|
211
|
+
);
|
212
|
+
};
|
213
|
+
|
214
|
+
export const renderLinkButton = createRenderer(LinkButton);
|
215
|
+
|
216
|
+
// Shared extensions
|
217
|
+
export const defaultExtensions: Extension[] = [
|
218
|
+
decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100 }),
|
219
|
+
formattingKeymap(),
|
220
|
+
linkTooltip(renderLinkTooltip),
|
221
|
+
];
|
222
|
+
|
223
|
+
export const allExtensions: Extension[] = [
|
224
|
+
decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100, numberedHeadings: { from: 2, to: 4 } }),
|
225
|
+
formattingKeymap(),
|
226
|
+
linkTooltip(renderLinkTooltip),
|
227
|
+
image(),
|
228
|
+
table(),
|
229
|
+
folding(),
|
230
|
+
];
|
231
|
+
|
232
|
+
// Long text for scrolling stories
|
233
|
+
export const longText = faker.helpers
|
234
|
+
.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 })
|
235
|
+
.join('\n\n');
|
236
|
+
|
237
|
+
export const largeWithImages = faker.helpers
|
238
|
+
.multiple(() => [faker.lorem.paragraph({ min: 12, max: 16 }), img], { count: 20 })
|
239
|
+
.flatMap((x) => x)
|
240
|
+
.join('\n\n');
|
241
|
+
|
242
|
+
export const headings = str(
|
243
|
+
...[1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 2, 3, 3, 2, 2, 6, 1]
|
244
|
+
.map((level) => ['#'.repeat(level) + ' ' + faker.lorem.sentence(3), faker.lorem.sentences(), ''])
|
245
|
+
.flat(),
|
246
|
+
);
|
247
|
+
|
248
|
+
export const global = new Map<string, EditorSelectionState>();
|
249
|
+
|
250
|
+
// Type definitions
|
251
|
+
export type DebugMode = 'raw' | 'tree' | 'raw+tree';
|
252
|
+
|
253
|
+
export type StoryProps = {
|
254
|
+
id?: string;
|
255
|
+
debug?: DebugMode;
|
256
|
+
text?: string;
|
257
|
+
readOnly?: boolean;
|
258
|
+
placeholder?: string;
|
259
|
+
lineNumbers?: boolean;
|
260
|
+
onReady?: (view: EditorView) => void;
|
261
|
+
} & Pick<UseTextEditorProps, 'scrollTo' | 'selection' | 'extensions'>;
|
262
|
+
|
263
|
+
// Default story component
|
264
|
+
export const DefaultStory = ({
|
265
|
+
id = 'editor-' + PublicKey.random().toHex().slice(0, 8),
|
266
|
+
debug,
|
267
|
+
text,
|
268
|
+
extensions,
|
269
|
+
readOnly,
|
270
|
+
placeholder = 'New document.',
|
271
|
+
scrollTo,
|
272
|
+
selection,
|
273
|
+
lineNumbers,
|
274
|
+
onReady,
|
275
|
+
}: StoryProps) => {
|
276
|
+
const [object] = useState(createObject(live(Expando, { content: text ?? '' })));
|
277
|
+
const { themeMode } = useThemeContext();
|
278
|
+
const [tree, setTree] = useState<DebugNode>();
|
279
|
+
const { parentRef, focusAttributes, view } = useTextEditor(
|
280
|
+
() => ({
|
281
|
+
id,
|
282
|
+
initialValue: text,
|
283
|
+
extensions: [
|
284
|
+
createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
|
285
|
+
createBasicExtensions({ readOnly, placeholder, lineNumbers, scrollPastEnd: true }),
|
286
|
+
createMarkdownExtensions({ themeMode }),
|
287
|
+
createThemeExtensions({
|
288
|
+
themeMode,
|
289
|
+
syntaxHighlighting: true,
|
290
|
+
slots: {
|
291
|
+
content: {
|
292
|
+
className: editorContent,
|
293
|
+
},
|
294
|
+
},
|
295
|
+
}),
|
296
|
+
editorGutter,
|
297
|
+
extensions || [],
|
298
|
+
debug ? debugTree(setTree) : [],
|
299
|
+
],
|
300
|
+
scrollTo,
|
301
|
+
selection,
|
302
|
+
}),
|
303
|
+
[object, extensions, themeMode],
|
304
|
+
);
|
305
|
+
|
306
|
+
useEffect(() => {
|
307
|
+
if (view) {
|
308
|
+
onReady?.(view);
|
309
|
+
}
|
310
|
+
}, [view]);
|
311
|
+
|
312
|
+
return (
|
313
|
+
<div className='flex w-full'>
|
314
|
+
<div role='none' className='flex w-full overflow-hidden' ref={parentRef} {...focusAttributes} />
|
315
|
+
{debug && (
|
316
|
+
<div className='flex flex-col w-[800px] border-l border-separator divide-y divide-separator overflow-auto'>
|
317
|
+
{(debug === 'raw' || debug === 'raw+tree') && (
|
318
|
+
<pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>{view?.state.doc.toString()}</pre>
|
319
|
+
)}
|
320
|
+
{(debug === 'tree' || debug === 'raw+tree') && (
|
321
|
+
<pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>
|
322
|
+
{JSON.stringify(tree, null, 2)}
|
323
|
+
</pre>
|
324
|
+
)}
|
325
|
+
</div>
|
326
|
+
)}
|
327
|
+
</div>
|
328
|
+
);
|
329
|
+
};
|
@@ -0,0 +1,74 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
6
|
+
import React, { type PropsWithChildren, useRef, useState, useEffect, useCallback } from 'react';
|
7
|
+
|
8
|
+
import { addEventListener } from '@dxos/async';
|
9
|
+
import { type DxRefTagActivate } from '@dxos/lit-ui';
|
10
|
+
import { Popover } from '@dxos/react-ui';
|
11
|
+
|
12
|
+
import { type PreviewLinkRef, type PreviewLinkTarget, type PreviewLookup } from '../extensions';
|
13
|
+
|
14
|
+
const customEventOptions = { capture: true, passive: false };
|
15
|
+
|
16
|
+
// Create a context for the dxn value.
|
17
|
+
type RefPopoverValue = Partial<{ link: PreviewLinkRef; target: PreviewLinkTarget; pending: boolean }>;
|
18
|
+
const REF_POPOVER = 'RefPopover';
|
19
|
+
const [RefPopoverContextProvider, useRefPopover] = createContext<RefPopoverValue>(REF_POPOVER, {});
|
20
|
+
|
21
|
+
type RefPopoverProviderProps = PropsWithChildren<{ onLookup?: PreviewLookup }>;
|
22
|
+
|
23
|
+
const RefPopoverProvider = ({ children, onLookup }: RefPopoverProviderProps) => {
|
24
|
+
const trigger = useRef<HTMLButtonElement | null>(null);
|
25
|
+
const [value, setValue] = useState<RefPopoverValue>({});
|
26
|
+
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
27
|
+
const [open, setOpen] = useState(false);
|
28
|
+
|
29
|
+
const handleDxRefTagActivate = useCallback(
|
30
|
+
(event: DxRefTagActivate) => {
|
31
|
+
const { ref, label, trigger: dxTrigger } = event;
|
32
|
+
setValue((value) => ({
|
33
|
+
...value,
|
34
|
+
link: { label, ref },
|
35
|
+
pending: true,
|
36
|
+
}));
|
37
|
+
trigger.current = dxTrigger;
|
38
|
+
queueMicrotask(() => setOpen(true));
|
39
|
+
void onLookup?.({ label, ref }).then((target) =>
|
40
|
+
setValue((value) => ({
|
41
|
+
...value,
|
42
|
+
target: target ?? undefined,
|
43
|
+
pending: false,
|
44
|
+
})),
|
45
|
+
);
|
46
|
+
},
|
47
|
+
[onLookup],
|
48
|
+
);
|
49
|
+
|
50
|
+
useEffect(() => {
|
51
|
+
return rootRef
|
52
|
+
? addEventListener(rootRef, 'dx-ref-tag-activate', handleDxRefTagActivate, customEventOptions)
|
53
|
+
: undefined;
|
54
|
+
}, [rootRef]);
|
55
|
+
|
56
|
+
return (
|
57
|
+
<RefPopoverContextProvider pending={value.pending} link={value.link} target={value.target}>
|
58
|
+
<Popover.Root open={open} onOpenChange={setOpen}>
|
59
|
+
<Popover.VirtualTrigger virtualRef={trigger} />
|
60
|
+
<div role='none' className='contents' ref={setRootRef}>
|
61
|
+
{children}
|
62
|
+
</div>
|
63
|
+
</Popover.Root>
|
64
|
+
</RefPopoverContextProvider>
|
65
|
+
);
|
66
|
+
};
|
67
|
+
|
68
|
+
export const RefPopover = {
|
69
|
+
Provider: RefPopoverProvider,
|
70
|
+
};
|
71
|
+
|
72
|
+
export { useRefPopover };
|
73
|
+
|
74
|
+
export type { RefPopoverProviderProps, RefPopoverValue };
|
package/src/types.ts
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
+
import { type EditorView } from '@codemirror/view';
|
6
|
+
|
5
7
|
// Runtime data structure.
|
6
8
|
export type Range = {
|
7
9
|
from: number;
|
@@ -14,3 +16,8 @@ export type Comment = {
|
|
14
16
|
id: string;
|
15
17
|
cursor?: string;
|
16
18
|
};
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Callback that renders into a DOM element within the editor.
|
22
|
+
*/
|
23
|
+
export type RenderCallback<Props extends object> = (el: HTMLElement, props: Props, view: EditorView) => void;
|
package/src/util/react.tsx
CHANGED
@@ -2,12 +2,14 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
-
import React, { type ReactNode } from 'react';
|
5
|
+
import React, { type FC, type ReactNode } from 'react';
|
6
6
|
import { createRoot } from 'react-dom/client';
|
7
7
|
|
8
|
-
import { ThemeProvider } from '@dxos/react-ui';
|
8
|
+
import { ThemeProvider, Tooltip } from '@dxos/react-ui';
|
9
9
|
import { defaultTx } from '@dxos/react-ui-theme';
|
10
10
|
|
11
|
+
import { type RenderCallback } from '../types';
|
12
|
+
|
11
13
|
// TODO(burdon): Factor out.
|
12
14
|
|
13
15
|
export type ElementOptions = {
|
@@ -32,3 +34,19 @@ export const renderRoot = <T extends Element>(root: T, node: ReactNode): T => {
|
|
32
34
|
createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
|
33
35
|
return root;
|
34
36
|
};
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Utility to create a renderer for a React component.
|
40
|
+
*/
|
41
|
+
export const createRenderer =
|
42
|
+
<Props extends object>(Component: FC<Props>): RenderCallback<Props> =>
|
43
|
+
(el, props) => {
|
44
|
+
renderRoot(
|
45
|
+
el,
|
46
|
+
<ThemeProvider tx={defaultTx}>
|
47
|
+
<Tooltip.Provider>
|
48
|
+
<Component {...props} />
|
49
|
+
</Tooltip.Provider>
|
50
|
+
</ThemeProvider>,
|
51
|
+
);
|
52
|
+
};
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"InputMode.stories.d.ts","sourceRoot":"","sources":["../../../src/InputMode.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,KAAmB,MAAM,OAAO,CAAC;AAiBxC,OAAO,EAAmC,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGnF,KAAK,UAAU,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,kBAAkB,CAAC;AAqEpF,eAAO,MAAM,OAAO;;CAanB,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAC;;;iEAxFwE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FpF,wBAKE"}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"TextEditor.stories.d.ts","sourceRoot":"","sources":["../../../src/TextEditor.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAOrB,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAA2D,MAAM,OAAO,CAAC;AAYhF,OAAO,EAAE,KAAK,IAAI,EAAyB,MAAM,uBAAuB,CAAC;AAiCzE,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AA2MjE,KAAK,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,CAAC;AAE7C,KAAK,UAAU,GAAG;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC,GAAG,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;AAEtE,QAAA,MAAM,YAAY,uGAWf,UAAU,sBAsDZ,CAAC;AAEF,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAKnC,CAAC;AAEF,eAAe,IAAI,CAAC;AAqBpB,eAAO,MAAM,OAAO;;CAEnB,CAAC;AAMF,eAAO,MAAM,UAAU;;CAEtB,CAAC;AAMF,eAAO,MAAM,KAAK;;CAEjB,CAAC;AAMF,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAMF,eAAO,MAAM,YAAY;;CAExB,CAAC;AAMF,eAAO,MAAM,GAAG;;CAOf,CAAC;AAqBF,eAAO,MAAM,OAAO;;CAEnB,CAAC;AAEF,eAAO,MAAM,SAAS;;CAUrB,CAAC;AAEF,eAAO,MAAM,mBAAmB;;CAI/B,CAAC;AAEF,eAAO,MAAM,QAAQ;;CAepB,CAAC;AAMF,eAAO,MAAM,UAAU;;CAQtB,CAAC;AAEF,eAAO,MAAM,QAAQ;;CAIpB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAIjB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAEjB,CAAC;AAEF,eAAO,MAAM,IAAI;;CAEhB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAOjB,CAAC;AAEF,eAAO,MAAM,UAAU;;CAEtB,CAAC;AAEF,eAAO,MAAM,WAAW;;CAEvB,CAAC;AAEF,eAAO,MAAM,QAAQ;;CAIpB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAEjB,CAAC;AAMF,eAAO,MAAM,YAAY;;CAWxB,CAAC;AAMF,eAAO,MAAM,UAAU;;CAQtB,CAAC;AAMF,eAAO,MAAM,YAAY;;CAcxB,CAAC;AAMF,eAAO,MAAM,OAAO;;CAWnB,CAAC;AAMF,eAAO,MAAM,MAAM;;CAQlB,CAAC;AAMF,eAAO,MAAM,OAAO;;CA8CnB,CAAC;AAsDF,eAAO,MAAM,QAAQ;;CAsCpB,CAAC;AAMF,eAAO,MAAM,WAAW;;CAIvB,CAAC;AAMF,eAAO,MAAM,GAAG;;CAaf,CAAC;AAMF,eAAO,MAAM,QAAQ;;CAgBpB,CAAC;AAQF,eAAO,MAAM,UAAU;;CAOtB,CAAC;AAMF,eAAO,MAAM,KAAK;;CAqBjB,CAAC"}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"viewMode.d.ts","sourceRoot":"","sources":["../../../../../src/components/EditorToolbar/viewMode.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAA+C,KAAK,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AA6B9F,eAAO,MAAM,cAAc,UAAW,kBAAkB;;;;;;;;;;;;;;CAWvD,CAAC"}
|
@@ -1,12 +0,0 @@
|
|
1
|
-
import { type Extension } from '@codemirror/state';
|
2
|
-
export type PreviewOptions = {
|
3
|
-
onRenderPreview: (el: HTMLElement, props: {
|
4
|
-
url: string;
|
5
|
-
text: string;
|
6
|
-
}) => void;
|
7
|
-
};
|
8
|
-
/**
|
9
|
-
* Create image decorations.
|
10
|
-
*/
|
11
|
-
export declare const preview: (options: PreviewOptions) => Extension;
|
12
|
-
//# sourceMappingURL=preview.d.ts.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/command/preview.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,SAAS,EAKf,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,YAAa,cAAc,KAAG,SASjD,CAAC"}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"fragments.d.ts","sourceRoot":"","sources":["../../../src/fragments.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,gCAAgC,UAAW,MAAM,WAI3D,CAAC;AAEJ,eAAO,MAAM,iCAAiC,UAAW,MAAM,WAI5D,CAAC"}
|