@dxos/react-ui-editor 0.3.10-main.cbe7692 → 0.3.10-main.cc91b2d
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 +654 -412
- 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 +6 -3
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +3 -0
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/blast.d.ts +1 -1
- package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/code.d.ts +2 -0
- package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts +2 -2
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +1 -1
- package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/highlight.d.ts +24 -0
- package/dist/types/src/components/TextEditor/extensions/highlight.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +2 -0
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +8 -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 +3 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +6 -0
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts +1 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -1
- 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 +22 -20
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +51 -32
- package/src/components/TextEditor/TextEditor.stories.tsx +12 -1
- package/src/components/TextEditor/TextEditor.tsx +21 -23
- package/src/components/TextEditor/extensions/basic.ts +4 -3
- package/src/components/TextEditor/extensions/blast.ts +27 -29
- package/src/components/TextEditor/extensions/code.ts +99 -0
- package/src/components/TextEditor/extensions/comments.ts +29 -46
- package/src/components/TextEditor/extensions/experimental.tsx +15 -36
- package/src/components/TextEditor/extensions/highlight.ts +128 -0
- package/src/components/TextEditor/extensions/image.ts +4 -5
- package/src/components/TextEditor/extensions/index.ts +2 -0
- package/src/components/TextEditor/extensions/link.ts +2 -2
- package/src/components/TextEditor/extensions/markdown/bundle.ts +35 -32
- package/src/components/TextEditor/extensions/markdown/highlight.ts +37 -11
- package/src/components/TextEditor/extensions/table.ts +44 -48
- package/src/components/TextEditor/extensions/tasklist.ts +9 -9
- package/src/components/TextEditor/themes/default.ts +43 -2
- package/src/hooks/automerge/automerge.stories.tsx +1 -0
- package/src/hooks/useTextModel.ts +9 -0
- package/src/hooks/yjs/yjs.stories.tsx +1 -1
- package/src/styles/markdown.ts +5 -4
- package/src/testing/replicator.ts +1 -0
|
@@ -32,9 +32,15 @@ import {
|
|
|
32
32
|
blast,
|
|
33
33
|
demo,
|
|
34
34
|
defaultOptions,
|
|
35
|
+
highlight,
|
|
36
|
+
code,
|
|
35
37
|
} from './extensions';
|
|
36
38
|
import { useTextModel } from '../../hooks';
|
|
37
39
|
|
|
40
|
+
// Extensions:
|
|
41
|
+
// TODO(burdon): Table of contents.
|
|
42
|
+
// TODO(burdon): Front-matter
|
|
43
|
+
|
|
38
44
|
const str = (...lines: string[]) => lines.join('\n');
|
|
39
45
|
|
|
40
46
|
const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
|
|
@@ -78,6 +84,7 @@ const text = {
|
|
|
78
84
|
'```tsx',
|
|
79
85
|
'const Component = () => {',
|
|
80
86
|
' const x = 100;',
|
|
87
|
+
'',
|
|
81
88
|
' return () => <div>Test</div>;',
|
|
82
89
|
'};',
|
|
83
90
|
'',
|
|
@@ -93,24 +100,26 @@ const text = {
|
|
|
93
100
|
'',
|
|
94
101
|
),
|
|
95
102
|
|
|
96
|
-
headings: str(
|
|
97
|
-
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
|
98
|
-
),
|
|
99
|
-
|
|
100
|
-
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
|
101
|
-
|
|
102
103
|
table: str(
|
|
103
104
|
'# Table',
|
|
104
105
|
'',
|
|
105
|
-
`| ${faker.lorem.word()} | ${faker.lorem.word()} | ${faker.lorem.word()} |`,
|
|
106
|
-
'
|
|
107
|
-
`| ${num()} | ${num()} | ${num()} |`,
|
|
108
|
-
`| ${num()} | ${num()} | ${num()} |`,
|
|
109
|
-
`| ${num()} | ${num()} | ${num()} |`,
|
|
106
|
+
`| ${faker.lorem.word().padStart(8)} | ${faker.lorem.word().padStart(8)} | ${faker.lorem.word().padStart(8)} |`,
|
|
107
|
+
'|----------|----------|----------|',
|
|
108
|
+
`| ${num().padStart(8)} | ${num().padStart(8)} | ${num().padStart(8)} |`,
|
|
109
|
+
`| ${num().padStart(8)} | ${num().padStart(8)} | ${num().padStart(8)} |`,
|
|
110
|
+
`| ${num().padStart(8)} | ${num().padStart(8)} | ${num().padStart(8)} |`,
|
|
110
111
|
'', // TODO(burdon): Possible GFM parsing bug if no newline?
|
|
111
112
|
),
|
|
112
113
|
|
|
113
114
|
image: str('# Image', '', ''),
|
|
115
|
+
|
|
116
|
+
headings: str(
|
|
117
|
+
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
|
118
|
+
),
|
|
119
|
+
|
|
120
|
+
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
|
121
|
+
|
|
122
|
+
footer: str('', '', '', '', '')
|
|
114
123
|
};
|
|
115
124
|
|
|
116
125
|
const document = str(
|
|
@@ -138,6 +147,8 @@ const document = str(
|
|
|
138
147
|
text.table,
|
|
139
148
|
'---',
|
|
140
149
|
text.image,
|
|
150
|
+
'',
|
|
151
|
+
text.footer,
|
|
141
152
|
);
|
|
142
153
|
|
|
143
154
|
const links = [
|
|
@@ -173,6 +184,7 @@ const onRender: LinkOptions['onRender'] = (el, url) => {
|
|
|
173
184
|
);
|
|
174
185
|
};
|
|
175
186
|
|
|
187
|
+
// TODO(burdon): Pass in model.
|
|
176
188
|
const Story = ({
|
|
177
189
|
text,
|
|
178
190
|
automerge,
|
|
@@ -205,14 +217,15 @@ export default {
|
|
|
205
217
|
};
|
|
206
218
|
|
|
207
219
|
const extensions = [
|
|
208
|
-
link({ onRender }),
|
|
209
|
-
tooltip({ onHover }),
|
|
210
|
-
tasklist(),
|
|
211
|
-
table(),
|
|
212
|
-
image(),
|
|
213
220
|
autocomplete({
|
|
214
221
|
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
215
222
|
}),
|
|
223
|
+
code(),
|
|
224
|
+
image(),
|
|
225
|
+
link({ onRender }),
|
|
226
|
+
table(),
|
|
227
|
+
tasklist(),
|
|
228
|
+
tooltip({ onHover }),
|
|
216
229
|
];
|
|
217
230
|
|
|
218
231
|
export const Default = {
|
|
@@ -223,32 +236,30 @@ export const Readonly = {
|
|
|
223
236
|
render: () => <Story text={document} extensions={extensions} readonly />,
|
|
224
237
|
};
|
|
225
238
|
|
|
226
|
-
export const Simple = {
|
|
227
|
-
render: () => <Story text={document} />,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
239
|
export const Tooltips = {
|
|
231
|
-
render: () => <Story text={text.links} extensions={[tooltip({ onHover })]} />,
|
|
240
|
+
render: () => <Story text={str(text.links, text.footer)} extensions={[tooltip({ onHover })]} />,
|
|
232
241
|
};
|
|
233
242
|
|
|
234
243
|
export const Links = {
|
|
235
|
-
render: () => <Story text={text.links} extensions={[link({ onRender })]} />,
|
|
244
|
+
render: () => <Story text={str(text.links, text.footer)} extensions={[link({ onRender })]} />,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export const Code = {
|
|
248
|
+
render: () => <Story text={str(text.code, text.footer)} extensions={[code()]} readonly />,
|
|
236
249
|
};
|
|
237
250
|
|
|
238
251
|
export const Table = {
|
|
239
|
-
render: () => (
|
|
240
|
-
<Story text={str('This is a table', '', text.table, '', text.table, '', '')} readonly extensions={[table()]} />
|
|
241
|
-
),
|
|
252
|
+
render: () => <Story text={str(text.table, text.footer)} extensions={[table()]} />,
|
|
242
253
|
};
|
|
243
254
|
|
|
244
255
|
export const Image = {
|
|
245
|
-
render: () => <Story text={str(
|
|
256
|
+
render: () => <Story text={str(text.image, text.footer)} readonly extensions={[image()]} />,
|
|
246
257
|
};
|
|
247
258
|
|
|
248
259
|
export const TaskList = {
|
|
249
260
|
render: () => (
|
|
250
261
|
<Story
|
|
251
|
-
text={str(text.tasks, '', text.list)}
|
|
262
|
+
text={str(text.tasks, '', text.list, text.footer)}
|
|
252
263
|
extensions={[
|
|
253
264
|
tasklist(),
|
|
254
265
|
listener({
|
|
@@ -264,7 +275,7 @@ export const TaskList = {
|
|
|
264
275
|
export const Autocomplete = {
|
|
265
276
|
render: () => (
|
|
266
277
|
<Story
|
|
267
|
-
text={str('# Autocomplete', '', '
|
|
278
|
+
text={str('# Autocomplete', '', 'Press CTRL-SPACE', text.footer)}
|
|
268
279
|
extensions={[
|
|
269
280
|
link({ onRender }),
|
|
270
281
|
autocomplete({
|
|
@@ -280,7 +291,7 @@ const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', '
|
|
|
280
291
|
export const Mention = {
|
|
281
292
|
render: () => (
|
|
282
293
|
<Story
|
|
283
|
-
text={str('# Mention', '', '
|
|
294
|
+
text={str('# Mention', '', 'Type @...', text.footer)}
|
|
284
295
|
extensions={[
|
|
285
296
|
mention({
|
|
286
297
|
onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
|
|
@@ -294,14 +305,15 @@ const mark = () => `[^${PublicKey.random().toHex()}]`;
|
|
|
294
305
|
export const Comments = {
|
|
295
306
|
render: () => (
|
|
296
307
|
<Story
|
|
297
|
-
text={str(
|
|
308
|
+
text={str(text.paragraphs, mark(), '', mark(), text.footer)}
|
|
298
309
|
extensions={[
|
|
299
310
|
comments({
|
|
300
311
|
onCreate: () => PublicKey.random().toHex(),
|
|
301
312
|
onUpdate: (info) => {
|
|
302
|
-
console.log('update', info);
|
|
313
|
+
// console.log('update', info);
|
|
303
314
|
},
|
|
304
315
|
}),
|
|
316
|
+
highlight(),
|
|
305
317
|
]}
|
|
306
318
|
/>
|
|
307
319
|
),
|
|
@@ -322,7 +334,7 @@ export const Diagnostics = {
|
|
|
322
334
|
};
|
|
323
335
|
|
|
324
336
|
export const Demo = {
|
|
325
|
-
render: () => <Story text={text.paragraphs} extensions={[demo()]} />,
|
|
337
|
+
render: () => <Story text={str(text.paragraphs, text.footer)} extensions={[demo()]} />,
|
|
326
338
|
};
|
|
327
339
|
|
|
328
340
|
export const Blast = {
|
|
@@ -330,6 +342,9 @@ export const Blast = {
|
|
|
330
342
|
<Story
|
|
331
343
|
text={str(text.paragraphs, text.code, text.paragraphs)}
|
|
332
344
|
extensions={[
|
|
345
|
+
demo({
|
|
346
|
+
items: localStorage.getItem('dxos.composer.extension.demo')?.split(','),
|
|
347
|
+
}),
|
|
333
348
|
blast(
|
|
334
349
|
defaultsDeep(
|
|
335
350
|
{
|
|
@@ -346,3 +361,7 @@ export const Blast = {
|
|
|
346
361
|
/>
|
|
347
362
|
),
|
|
348
363
|
};
|
|
364
|
+
|
|
365
|
+
export const NoExtensions = {
|
|
366
|
+
render: () => <Story text={document} />,
|
|
367
|
+
};
|
|
@@ -57,6 +57,17 @@ export default {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
export const Default = {
|
|
60
|
+
render: () => (
|
|
61
|
+
<Story
|
|
62
|
+
slots={defaultsDeep(
|
|
63
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
64
|
+
defaultSlots,
|
|
65
|
+
)}
|
|
66
|
+
/>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Text = {
|
|
60
71
|
render: () => (
|
|
61
72
|
<Story
|
|
62
73
|
text={initialText}
|
|
@@ -71,12 +82,12 @@ export const Default = {
|
|
|
71
82
|
export const Automerge = {
|
|
72
83
|
render: () => (
|
|
73
84
|
<Story
|
|
85
|
+
automerge
|
|
74
86
|
text={initialText}
|
|
75
87
|
slots={defaultsDeep(
|
|
76
88
|
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
77
89
|
defaultSlots,
|
|
78
90
|
)}
|
|
79
|
-
automerge
|
|
80
91
|
/>
|
|
81
92
|
),
|
|
82
93
|
};
|
|
@@ -21,7 +21,7 @@ import { generateName } from '@dxos/display-name';
|
|
|
21
21
|
import { useThemeContext } from '@dxos/react-ui';
|
|
22
22
|
import { getColorForValue, inputSurface, mx } from '@dxos/react-ui-theme';
|
|
23
23
|
|
|
24
|
-
import { basicBundle,
|
|
24
|
+
import { basicBundle, markdownBundle } from './extensions';
|
|
25
25
|
import { defaultTheme, markdownTheme, textTheme } from './themes';
|
|
26
26
|
import { type EditorModel } from '../../hooks';
|
|
27
27
|
import { type ThemeStyles } from '../../styles';
|
|
@@ -88,6 +88,12 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
88
88
|
}
|
|
89
89
|
}, [awareness, peer, themeMode]);
|
|
90
90
|
|
|
91
|
+
// TODO(burdon): Get from model.
|
|
92
|
+
// useHighlights(view, [
|
|
93
|
+
// { id: 'c-1', from: 0, to: 20 },
|
|
94
|
+
// { id: 'c-2', from: 100, to: 120 },
|
|
95
|
+
// ]);
|
|
96
|
+
|
|
91
97
|
useEffect(() => {
|
|
92
98
|
if (!root) {
|
|
93
99
|
return;
|
|
@@ -102,6 +108,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
102
108
|
editorMode === 'vim' && vim(),
|
|
103
109
|
|
|
104
110
|
// Theme.
|
|
111
|
+
// TODO(burdon): Make optional.
|
|
105
112
|
EditorView.baseTheme(defaultTheme),
|
|
106
113
|
EditorView.theme(slots?.editor?.theme ?? {}),
|
|
107
114
|
// TODO(burdon): themeMode doesn't change in storybooks.
|
|
@@ -119,7 +126,17 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
119
126
|
// If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
|
|
120
127
|
// Ideally this should not be hit except when changing between text objects.
|
|
121
128
|
view?.destroy();
|
|
122
|
-
setView(
|
|
129
|
+
setView(
|
|
130
|
+
new EditorView({
|
|
131
|
+
state,
|
|
132
|
+
parent: root,
|
|
133
|
+
// NOTE: Uncomment to spy on all transactions.
|
|
134
|
+
// https://codemirror.net/docs/ref/#view.EditorView.dispatch
|
|
135
|
+
// dispatch: (transaction, view) => {
|
|
136
|
+
// view.update([transaction]);
|
|
137
|
+
// },
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
123
140
|
setState(state);
|
|
124
141
|
|
|
125
142
|
return () => {
|
|
@@ -160,17 +177,6 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
160
177
|
},
|
|
161
178
|
);
|
|
162
179
|
|
|
163
|
-
// TODO(burdon): Allow plugins to set extensions (factory).
|
|
164
|
-
const maybeDebug = (): Extension => {
|
|
165
|
-
// TODO(burdon): Parse JSON script format (with key bindings?)
|
|
166
|
-
const items = localStorage.getItem('dxos.composer.demo');
|
|
167
|
-
if (items) {
|
|
168
|
-
return demo({ items: items.split(',') });
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return [];
|
|
172
|
-
};
|
|
173
|
-
|
|
174
180
|
export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
175
181
|
({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
176
182
|
const { themeMode } = useThemeContext();
|
|
@@ -179,11 +185,7 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
179
185
|
<BaseTextEditor
|
|
180
186
|
ref={forwardedRef}
|
|
181
187
|
readonly={readonly}
|
|
182
|
-
extensions={[
|
|
183
|
-
basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }),
|
|
184
|
-
maybeDebug(),
|
|
185
|
-
...extensions,
|
|
186
|
-
]}
|
|
188
|
+
extensions={[basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
187
189
|
slots={slots}
|
|
188
190
|
{...props}
|
|
189
191
|
/>
|
|
@@ -199,11 +201,7 @@ export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
199
201
|
<BaseTextEditor
|
|
200
202
|
ref={forwardedRef}
|
|
201
203
|
readonly={readonly}
|
|
202
|
-
extensions={[
|
|
203
|
-
markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }),
|
|
204
|
-
maybeDebug(),
|
|
205
|
-
...extensions,
|
|
206
|
-
]}
|
|
204
|
+
extensions={[markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
207
205
|
slots={slots}
|
|
208
206
|
{...props}
|
|
209
207
|
/>
|
|
@@ -6,7 +6,7 @@ import { closeBrackets } from '@codemirror/autocomplete';
|
|
|
6
6
|
import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
7
7
|
import { type Extension } from '@codemirror/state';
|
|
8
8
|
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
9
|
-
import { EditorView, placeholder } from '@codemirror/view';
|
|
9
|
+
import { drawSelection, EditorView, placeholder } from '@codemirror/view';
|
|
10
10
|
|
|
11
11
|
import type { ThemeMode } from '@dxos/react-ui';
|
|
12
12
|
|
|
@@ -18,12 +18,13 @@ export type BasicBundleOptions = {
|
|
|
18
18
|
|
|
19
19
|
export const basicBundle = ({ readonly, themeMode, placeholder: _placeholder }: BasicBundleOptions): Extension[] =>
|
|
20
20
|
[
|
|
21
|
+
EditorView.lineWrapping,
|
|
22
|
+
|
|
21
23
|
// TODO(burdon): Compare with minimalSetup.
|
|
22
24
|
// https://codemirror.net/docs/ref/#codemirror.minimalSetup
|
|
23
25
|
bracketMatching(),
|
|
24
26
|
closeBrackets(),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
drawSelection(),
|
|
27
28
|
_placeholder && placeholder(_placeholder),
|
|
28
29
|
|
|
29
30
|
// TODO(burdon): Is this required?
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// https://github.com/chinchang/code-blast-codemirror/blob/master/code-blast.js
|
|
6
6
|
//
|
|
7
7
|
|
|
8
|
-
import { type Extension
|
|
8
|
+
import { type Extension } from '@codemirror/state';
|
|
9
9
|
import { EditorView, keymap } from '@codemirror/view';
|
|
10
10
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
11
11
|
|
|
@@ -25,6 +25,7 @@ export type BlastOptions = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
export const defaultOptions: BlastOptions = {
|
|
28
|
+
effect: 2,
|
|
28
29
|
maxParticles: 200,
|
|
29
30
|
particleGravity: 0.08,
|
|
30
31
|
particleAlphaFadeout: 0.996,
|
|
@@ -35,7 +36,7 @@ export const defaultOptions: BlastOptions = {
|
|
|
35
36
|
shakeIntensity: 5,
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
export const blast = (options: Partial<BlastOptions>): Extension => {
|
|
39
|
+
export const blast = (options: Partial<BlastOptions> = defaultOptions): Extension => {
|
|
39
40
|
let blaster: Blaster | undefined;
|
|
40
41
|
let last = 0;
|
|
41
42
|
|
|
@@ -58,46 +59,43 @@ export const blast = (options: Partial<BlastOptions>): Extension => {
|
|
|
58
59
|
blaster.destroy();
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
blaster = new Blaster(
|
|
62
|
+
blaster = new Blaster(
|
|
63
|
+
update.view.scrollDOM,
|
|
64
|
+
defaultsDeep(
|
|
65
|
+
{
|
|
66
|
+
particleGravity: 0.2,
|
|
67
|
+
particleShrinkRate: 0.995,
|
|
68
|
+
color: () => [100 + Math.random() * 100, 0, 0],
|
|
69
|
+
},
|
|
70
|
+
options,
|
|
71
|
+
defaultOptions,
|
|
72
|
+
),
|
|
73
|
+
);
|
|
62
74
|
blaster.initialize();
|
|
63
75
|
blaster.start(); // TODO(burdon): Stop/clean-up.
|
|
64
76
|
} else {
|
|
65
77
|
blaster.resize();
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
const current = update.
|
|
80
|
+
const current = update.state.selection.main.head;
|
|
69
81
|
if (current !== last) {
|
|
70
82
|
last = current;
|
|
71
83
|
// TODO(burdon): Null if end of line.
|
|
72
|
-
const { element, point } = getPoint(update.view, current);
|
|
84
|
+
const { element, point } = getPoint(update.view, current - 1);
|
|
73
85
|
if (element && point) {
|
|
74
86
|
blaster.spawn({ element, point });
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
89
|
}),
|
|
78
90
|
|
|
79
|
-
// Document changed.
|
|
80
|
-
StateField.define({
|
|
81
|
-
create: () => null,
|
|
82
|
-
update: (_value, transaction) => {
|
|
83
|
-
if (blaster && transaction.docChanged) {
|
|
84
|
-
// TODO(burdon): Only on delete.
|
|
85
|
-
const view = EditorView.findFromDOM(blaster.node)!;
|
|
86
|
-
const { element, point } = getPoint(view, transaction.selection!.main.head);
|
|
87
|
-
if (element && point) {
|
|
88
|
-
blaster.spawn({ element, point });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return null;
|
|
93
|
-
},
|
|
94
|
-
}),
|
|
95
|
-
|
|
96
91
|
keymap.of([
|
|
97
92
|
{
|
|
98
|
-
any: (
|
|
99
|
-
if (blaster
|
|
100
|
-
|
|
93
|
+
any: (_, event) => {
|
|
94
|
+
if (blaster) {
|
|
95
|
+
if (event.key === 'Enter' && event.shiftKey) {
|
|
96
|
+
blaster.shake({ time: 0.8 });
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
return false;
|
|
@@ -112,11 +110,11 @@ export const blast = (options: Partial<BlastOptions>): Extension => {
|
|
|
112
110
|
//
|
|
113
111
|
|
|
114
112
|
class Blaster {
|
|
113
|
+
private readonly _effect: Effect;
|
|
114
|
+
|
|
115
115
|
_canvas: HTMLCanvasElement | undefined;
|
|
116
116
|
_ctx: CanvasRenderingContext2D | undefined | null;
|
|
117
117
|
|
|
118
|
-
private readonly _effect: Effect;
|
|
119
|
-
|
|
120
118
|
_running = false;
|
|
121
119
|
_lastTime: number | undefined;
|
|
122
120
|
_shakeTime = 0;
|
|
@@ -144,7 +142,7 @@ class Blaster {
|
|
|
144
142
|
this._canvas.style.position = 'absolute';
|
|
145
143
|
this._canvas.style.zIndex = '0';
|
|
146
144
|
this._canvas.style.pointerEvents = 'none';
|
|
147
|
-
// this._canvas.style.border = '2px
|
|
145
|
+
// this._canvas.style.border = '2px dashed red';
|
|
148
146
|
|
|
149
147
|
this._ctx = this._canvas.getContext('2d');
|
|
150
148
|
|
|
@@ -214,7 +212,7 @@ class Blaster {
|
|
|
214
212
|
}
|
|
215
213
|
|
|
216
214
|
shake = throttle<{ time: number }>(({ time }) => {
|
|
217
|
-
this._shakeTime = this._shakeTimeMax;
|
|
215
|
+
this._shakeTime = this._shakeTimeMax || time;
|
|
218
216
|
this._shakeTimeMax = time;
|
|
219
217
|
}, 100);
|
|
220
218
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Range } from '@codemirror/state';
|
|
6
|
+
import {
|
|
7
|
+
ViewPlugin,
|
|
8
|
+
type DecorationSet,
|
|
9
|
+
EditorView,
|
|
10
|
+
type ViewUpdate,
|
|
11
|
+
Decoration,
|
|
12
|
+
type BlockInfo,
|
|
13
|
+
} from '@codemirror/view';
|
|
14
|
+
import get from 'lodash.get';
|
|
15
|
+
|
|
16
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
17
|
+
|
|
18
|
+
import { tokens } from '../../../styles';
|
|
19
|
+
|
|
20
|
+
const CODE_REGEX = /```[\s\S]*?```/gs;
|
|
21
|
+
|
|
22
|
+
// TODO(burdon): Reconcile with theme.
|
|
23
|
+
const styles = EditorView.baseTheme({
|
|
24
|
+
'& .cm-code-block': {
|
|
25
|
+
display: 'block',
|
|
26
|
+
paddingInline: '4px !important',
|
|
27
|
+
},
|
|
28
|
+
'&light .cm-code-block': {
|
|
29
|
+
background: get(tokens, 'extend.colors.neutral.50'),
|
|
30
|
+
},
|
|
31
|
+
'&dark .cm-code-block': {
|
|
32
|
+
background: get(tokens, 'extend.colors.neutral.850'),
|
|
33
|
+
},
|
|
34
|
+
'& .cm-code-block-first': {
|
|
35
|
+
paddingTop: '4px !important',
|
|
36
|
+
borderTopLeftRadius: '4px',
|
|
37
|
+
borderTopRightRadius: '4px',
|
|
38
|
+
},
|
|
39
|
+
'& .cm-code-block-last': {
|
|
40
|
+
paddingBottom: '4px !important',
|
|
41
|
+
borderBottomLeftRadius: '4px',
|
|
42
|
+
borderBottomRightRadius: '4px',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
|
|
47
|
+
const start = lines.findIndex((line) => line.from >= from);
|
|
48
|
+
const end = lines.findIndex((line) => line.to >= to);
|
|
49
|
+
return [start, end];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const code = () => {
|
|
53
|
+
const getDecorations = (view: EditorView) => {
|
|
54
|
+
const decorations: Range<Decoration>[] = [];
|
|
55
|
+
if (view.state.readOnly) {
|
|
56
|
+
const text = view.state.doc.sliceString(0);
|
|
57
|
+
const matches = text.matchAll(CODE_REGEX);
|
|
58
|
+
const blocks = view.viewportLineBlocks;
|
|
59
|
+
for (const match of matches) {
|
|
60
|
+
const range = getLineRange(blocks, match.index!, match.index! + match[0].length);
|
|
61
|
+
for (let i = range[0]; i <= range[1]; i++) {
|
|
62
|
+
const block = blocks[i];
|
|
63
|
+
decorations.push(
|
|
64
|
+
Decoration.line({
|
|
65
|
+
class: mx(
|
|
66
|
+
'cm-code-block',
|
|
67
|
+
i === range[0] && 'cm-code-block-first',
|
|
68
|
+
i === range[1] && 'cm-code-block-last',
|
|
69
|
+
),
|
|
70
|
+
}).range(block.from),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return Decoration.set(decorations);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return [
|
|
80
|
+
ViewPlugin.fromClass(
|
|
81
|
+
class {
|
|
82
|
+
decorations: DecorationSet;
|
|
83
|
+
constructor(view: EditorView) {
|
|
84
|
+
this.decorations = getDecorations(view);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
update(update: ViewUpdate) {
|
|
88
|
+
if (update.docChanged || update.viewportChanged) {
|
|
89
|
+
this.decorations = getDecorations(update.view);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
decorations: (value) => value.decorations,
|
|
95
|
+
},
|
|
96
|
+
),
|
|
97
|
+
styles,
|
|
98
|
+
];
|
|
99
|
+
};
|