@dxos/react-ui-editor 0.4.5-main.f8d6c4e → 0.4.5
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 +464 -572
- 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 +7 -5
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -0
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts +2 -0
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts +2 -0
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +2 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/cursor-line-margin.d.ts +8 -0
- package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -0
- 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 +5 -0
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -4
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts +1 -11
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/layout.d.ts +2 -2
- package/dist/types/src/styles/layout.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +29 -26
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +29 -37
- package/src/components/TextEditor/hooks.stories.tsx +5 -17
- package/src/components/Toolbar/Toolbar.stories.tsx +4 -7
- package/src/components/Toolbar/Toolbar.tsx +79 -47
- package/src/extensions/cursor-line-margin.ts +92 -0
- package/src/extensions/index.ts +1 -0
- package/src/extensions/markdown/decorate.ts +301 -0
- package/src/extensions/markdown/formatting.test.ts +17 -0
- package/src/extensions/markdown/formatting.ts +67 -76
- package/src/extensions/markdown/index.ts +1 -4
- package/src/extensions/markdown/link.ts +23 -185
- package/src/index.ts +2 -0
- package/src/styles/layout.ts +3 -2
- package/src/translations.ts +5 -3
- package/dist/types/src/extensions/markdown/code.d.ts +0 -2
- package/dist/types/src/extensions/markdown/code.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/heading.d.ts +0 -6
- package/dist/types/src/extensions/markdown/heading.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/hr.d.ts +0 -2
- package/dist/types/src/extensions/markdown/hr.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/tasklist.d.ts +0 -3
- package/dist/types/src/extensions/markdown/tasklist.d.ts.map +0 -1
- package/src/extensions/markdown/code.ts +0 -162
- package/src/extensions/markdown/heading.ts +0 -59
- package/src/extensions/markdown/hr.ts +0 -71
- package/src/extensions/markdown/tasklist.ts +0 -110
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,oBAAoB,CAAC
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;AAEhD,wBAoBE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-editor",
|
|
3
|
-
"version": "0.4.5
|
|
3
|
+
"version": "0.4.5",
|
|
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",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@codemirror/state": "^6.4.0",
|
|
24
24
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
25
25
|
"@codemirror/view": "^6.23.0",
|
|
26
|
-
"@fluentui/react-tabster": "^9.
|
|
26
|
+
"@fluentui/react-tabster": "^9.19.0",
|
|
27
27
|
"@lezer/common": "^1.2.1",
|
|
28
28
|
"@lezer/highlight": "^1.2.0",
|
|
29
29
|
"@lezer/markdown": "^1.2.0",
|
|
@@ -36,20 +36,20 @@
|
|
|
36
36
|
"lodash.pick": "^4.4.0",
|
|
37
37
|
"lodash.sortby": "^4.7.0",
|
|
38
38
|
"style-mod": "^4.1.0",
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/react-ui": "0.4.5
|
|
49
|
-
"@dxos/react-
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/text-model": "0.4.5
|
|
52
|
-
"@dxos/util": "0.4.5
|
|
39
|
+
"@dxos/async": "0.4.5",
|
|
40
|
+
"@dxos/debug": "0.4.5",
|
|
41
|
+
"@dxos/automerge": "0.4.5",
|
|
42
|
+
"@dxos/context": "0.4.5",
|
|
43
|
+
"@dxos/echo-schema": "0.4.5",
|
|
44
|
+
"@dxos/display-name": "0.4.5",
|
|
45
|
+
"@dxos/protocols": "0.4.5",
|
|
46
|
+
"@dxos/invariant": "0.4.5",
|
|
47
|
+
"@dxos/log": "0.4.5",
|
|
48
|
+
"@dxos/react-ui": "0.4.5",
|
|
49
|
+
"@dxos/react-async": "0.4.5",
|
|
50
|
+
"@dxos/react-ui-theme": "0.4.5",
|
|
51
|
+
"@dxos/text-model": "0.4.5",
|
|
52
|
+
"@dxos/util": "0.4.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@automerge/automerge-repo-network-broadcastchannel": "^1.1.1",
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
"@types/react": "^18.0.21",
|
|
66
66
|
"@types/react-dom": "^18.0.6",
|
|
67
67
|
"@types/react-test-renderer": "^17.0.2",
|
|
68
|
-
"@vitest/
|
|
68
|
+
"@vitest/browser": "^1.2.2",
|
|
69
|
+
"@vitest/ui": "^1.2.2",
|
|
69
70
|
"deepsignal": "^1.5.0",
|
|
70
71
|
"happy-dom": "^13.3.1",
|
|
71
72
|
"jsdom": "^20.0.1",
|
|
@@ -76,20 +77,22 @@
|
|
|
76
77
|
"vite-plugin-top-level-await": "^1.4.1",
|
|
77
78
|
"vite-plugin-wasm": "^3.3.0",
|
|
78
79
|
"vitest": "^1.2.2",
|
|
79
|
-
"@dxos/
|
|
80
|
-
"@dxos/
|
|
81
|
-
"@dxos/echo-typegen": "0.4.5
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/react-client": "0.4.5
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/
|
|
80
|
+
"@dxos/config": "0.4.5",
|
|
81
|
+
"@dxos/echo-signals": "0.4.5",
|
|
82
|
+
"@dxos/echo-typegen": "0.4.5",
|
|
83
|
+
"@dxos/keyboard": "0.4.5",
|
|
84
|
+
"@dxos/react-client": "0.4.5",
|
|
85
|
+
"@dxos/random": "0.4.5",
|
|
86
|
+
"@dxos/storybook-utils": "0.4.5",
|
|
87
|
+
"@dxos/react-ui": "0.4.5"
|
|
87
88
|
},
|
|
88
89
|
"peerDependencies": {
|
|
89
90
|
"@phosphor-icons/react": "^2.0.5",
|
|
91
|
+
"@vitest/browser": "^1.2.2",
|
|
92
|
+
"@vitest/ui": "^1.2.2",
|
|
90
93
|
"react": "^18.2.0",
|
|
91
94
|
"react-dom": "^18.2.0",
|
|
92
|
-
"@dxos/react-client": "0.4.5
|
|
95
|
+
"@dxos/react-client": "0.4.5"
|
|
93
96
|
},
|
|
94
97
|
"publishConfig": {
|
|
95
98
|
"access": "public"
|
|
@@ -19,19 +19,15 @@ import { withTheme } from '@dxos/storybook-utils';
|
|
|
19
19
|
import { MarkdownEditor, type TextEditorProps } from './TextEditor';
|
|
20
20
|
import {
|
|
21
21
|
type CommentsOptions,
|
|
22
|
-
type LinkOptions,
|
|
23
22
|
autocomplete,
|
|
24
23
|
blast,
|
|
25
|
-
|
|
24
|
+
decorateMarkdown,
|
|
26
25
|
comments,
|
|
27
26
|
defaultOptions,
|
|
28
|
-
heading,
|
|
29
|
-
hr,
|
|
30
27
|
image,
|
|
31
|
-
|
|
28
|
+
linkTooltip,
|
|
32
29
|
mention,
|
|
33
30
|
table,
|
|
34
|
-
tasklist,
|
|
35
31
|
typewriter,
|
|
36
32
|
useComments,
|
|
37
33
|
formatting,
|
|
@@ -47,9 +43,11 @@ const str = (...lines: string[]) => lines.join('\n');
|
|
|
47
43
|
|
|
48
44
|
const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
const img = '';
|
|
47
|
+
|
|
51
48
|
const text = {
|
|
52
49
|
tasks: str(
|
|
50
|
+
//
|
|
53
51
|
'## Tasks',
|
|
54
52
|
'',
|
|
55
53
|
'- [x] decorator',
|
|
@@ -61,6 +59,7 @@ const text = {
|
|
|
61
59
|
),
|
|
62
60
|
|
|
63
61
|
list: str(
|
|
62
|
+
//
|
|
64
63
|
'## List',
|
|
65
64
|
'',
|
|
66
65
|
'- new york',
|
|
@@ -70,12 +69,13 @@ const text = {
|
|
|
70
69
|
),
|
|
71
70
|
|
|
72
71
|
numbered: str(
|
|
72
|
+
//
|
|
73
73
|
'## Numbered',
|
|
74
74
|
'',
|
|
75
75
|
'1. one',
|
|
76
76
|
'2. two',
|
|
77
77
|
'3. three',
|
|
78
|
-
''
|
|
78
|
+
'',
|
|
79
79
|
),
|
|
80
80
|
|
|
81
81
|
code: str(
|
|
@@ -115,7 +115,7 @@ const text = {
|
|
|
115
115
|
'',
|
|
116
116
|
),
|
|
117
117
|
|
|
118
|
-
image: str('# Image', '',
|
|
118
|
+
image: str('# Image', '', img),
|
|
119
119
|
|
|
120
120
|
headings: str(
|
|
121
121
|
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
|
@@ -123,7 +123,7 @@ const text = {
|
|
|
123
123
|
|
|
124
124
|
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
|
125
125
|
|
|
126
|
-
footer: str('', '', '', '', '')
|
|
126
|
+
footer: str('', '', '', '', ''),
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
const document = str(
|
|
@@ -173,7 +173,7 @@ const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', '
|
|
|
173
173
|
const hover =
|
|
174
174
|
'rounded-sm text-base text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
|
|
175
175
|
|
|
176
|
-
const
|
|
176
|
+
const renderLinkTooltip = (el: Element, url: string) => {
|
|
177
177
|
const web = new URL(url);
|
|
178
178
|
createRoot(el).render(
|
|
179
179
|
<StrictMode>
|
|
@@ -206,7 +206,7 @@ const onCommentsHover: CommentsOptions['onHover'] = (el, shortcut) => {
|
|
|
206
206
|
);
|
|
207
207
|
};
|
|
208
208
|
|
|
209
|
-
const
|
|
209
|
+
const renderLinkButton = (el: Element, url: string) => {
|
|
210
210
|
createRoot(el).render(
|
|
211
211
|
<StrictMode>
|
|
212
212
|
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
@@ -257,14 +257,11 @@ const defaults = [
|
|
|
257
257
|
autocomplete({
|
|
258
258
|
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
259
259
|
}),
|
|
260
|
-
|
|
260
|
+
decorateMarkdown({ renderLinkButton }),
|
|
261
261
|
formatting(),
|
|
262
|
-
heading(),
|
|
263
|
-
hr(),
|
|
264
262
|
image(),
|
|
265
|
-
|
|
263
|
+
linkTooltip(renderLinkTooltip),
|
|
266
264
|
table(),
|
|
267
|
-
tasklist(),
|
|
268
265
|
];
|
|
269
266
|
|
|
270
267
|
export const Default = {
|
|
@@ -281,6 +278,11 @@ export const NoExtensions = {
|
|
|
281
278
|
|
|
282
279
|
const large = faker.helpers.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 }).join('\n\n');
|
|
283
280
|
|
|
281
|
+
const largeWithImages = faker.helpers
|
|
282
|
+
.multiple(() => [faker.lorem.paragraph({ min: 12, max: 16 }), img], { count: 20 })
|
|
283
|
+
.flatMap((x) => x)
|
|
284
|
+
.join('\n\n');
|
|
285
|
+
|
|
284
286
|
export const Empty = {
|
|
285
287
|
render: () => <Story />,
|
|
286
288
|
};
|
|
@@ -289,26 +291,25 @@ export const Scrolling = {
|
|
|
289
291
|
render: () => <Story text={str('# Large Document', '', large)} extensions={[]} />,
|
|
290
292
|
};
|
|
291
293
|
|
|
292
|
-
export const
|
|
293
|
-
render: () => (
|
|
294
|
-
<Story
|
|
295
|
-
text={str(text.links, text.footer)}
|
|
296
|
-
extensions={[link({ onHover: onHoverLinkTooltip, onRender: onRenderLink })]}
|
|
297
|
-
/>
|
|
298
|
-
),
|
|
294
|
+
export const ScrollingWithImages = {
|
|
295
|
+
render: () => <Story text={str('# Large Document', '', largeWithImages)} extensions={[image()]} />,
|
|
299
296
|
};
|
|
300
297
|
|
|
301
|
-
export const
|
|
302
|
-
render: () => <Story text={str(text.
|
|
298
|
+
export const Links = {
|
|
299
|
+
render: () => <Story text={str(text.links, text.footer)} extensions={[linkTooltip(renderLinkTooltip)]} />,
|
|
303
300
|
};
|
|
304
301
|
|
|
305
302
|
export const Image = {
|
|
306
303
|
render: () => <Story text={str(text.image, text.footer)} extensions={[image()]} />,
|
|
307
304
|
};
|
|
308
305
|
|
|
306
|
+
export const Code = {
|
|
307
|
+
render: () => <Story text={str(text.code, text.footer)} extensions={[decorateMarkdown()]} />,
|
|
308
|
+
};
|
|
309
|
+
|
|
309
310
|
export const Lists = {
|
|
310
311
|
render: () => (
|
|
311
|
-
<Story text={str(text.tasks, '', text.list, '', text.numbered, text.footer)} extensions={[
|
|
312
|
+
<Story text={str(text.tasks, '', text.list, '', text.numbered, text.footer)} extensions={[decorateMarkdown()]} />
|
|
312
313
|
),
|
|
313
314
|
};
|
|
314
315
|
|
|
@@ -330,7 +331,7 @@ export const Autocomplete = {
|
|
|
330
331
|
<Story
|
|
331
332
|
text={str('# Autocomplete', '', 'Press Ctrl-Space...', text.footer)}
|
|
332
333
|
extensions={[
|
|
333
|
-
|
|
334
|
+
decorateMarkdown({ renderLinkButton }),
|
|
334
335
|
autocomplete({
|
|
335
336
|
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
336
337
|
}),
|
|
@@ -388,15 +389,6 @@ export const Comments = {
|
|
|
388
389
|
},
|
|
389
390
|
};
|
|
390
391
|
|
|
391
|
-
export const HorizontalRule = {
|
|
392
|
-
render: () => (
|
|
393
|
-
<Story
|
|
394
|
-
text={str('# Horizontal Rule', '', text.paragraphs, '---', text.paragraphs, '---', text.paragraphs)}
|
|
395
|
-
extensions={[hr()]}
|
|
396
|
-
/>
|
|
397
|
-
),
|
|
398
|
-
};
|
|
399
|
-
|
|
400
392
|
export const Vim = {
|
|
401
393
|
render: () => (
|
|
402
394
|
<Story
|
|
@@ -14,19 +14,15 @@ import { TextEditor } from './TextEditor';
|
|
|
14
14
|
import {
|
|
15
15
|
type EditorMode,
|
|
16
16
|
EditorModes,
|
|
17
|
-
|
|
17
|
+
decorateMarkdown,
|
|
18
18
|
createMarkdownExtensions,
|
|
19
19
|
formatting,
|
|
20
|
-
heading,
|
|
21
|
-
hr,
|
|
22
20
|
image,
|
|
23
|
-
link,
|
|
24
21
|
table,
|
|
25
|
-
tasklist,
|
|
26
22
|
useFormattingState,
|
|
27
23
|
} from '../../extensions';
|
|
28
24
|
import { createDataExtensions, createThemeExtensions, useActionHandler, useTextEditor } from '../../hooks';
|
|
29
|
-
import { editorFillLayoutEditor,
|
|
25
|
+
import { editorFillLayoutEditor, editorWithToolbarLayout } from '../../styles';
|
|
30
26
|
import { markdownTheme } from '../../themes';
|
|
31
27
|
import translations from '../../translations';
|
|
32
28
|
import { Toolbar } from '../Toolbar';
|
|
@@ -56,20 +52,16 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
|
|
|
56
52
|
themeMode,
|
|
57
53
|
theme: markdownTheme,
|
|
58
54
|
slots: {
|
|
59
|
-
|
|
55
|
+
content: { className: '!p-4' },
|
|
60
56
|
},
|
|
61
57
|
}),
|
|
62
58
|
// TODO(burdon): Move lineWrapping.
|
|
63
59
|
createMarkdownExtensions({ placeholder, lineWrapping: true }),
|
|
64
60
|
// TODO(burdon): Move into markdown bundle (with React callbacks).
|
|
65
|
-
|
|
61
|
+
decorateMarkdown(),
|
|
66
62
|
formatting(),
|
|
67
|
-
heading(),
|
|
68
|
-
hr(),
|
|
69
63
|
image(),
|
|
70
|
-
link(),
|
|
71
64
|
table(),
|
|
72
|
-
tasklist(),
|
|
73
65
|
trackFormatting,
|
|
74
66
|
],
|
|
75
67
|
[editorMode, themeMode, placeholder, trackFormatting, readonly],
|
|
@@ -87,11 +79,7 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
|
|
|
87
79
|
<EditorModeToolbar editorMode={editorMode} setEditorMode={setEditorMode} />
|
|
88
80
|
</Toolbar.Root>
|
|
89
81
|
<div role='none' className='overflow-y-auto'>
|
|
90
|
-
<div
|
|
91
|
-
role='textbox'
|
|
92
|
-
className={mx(textBlockWidth, attentionSurface, editorFillLayoutRoot, 'p-4')}
|
|
93
|
-
ref={parentRef}
|
|
94
|
-
/>
|
|
82
|
+
<div role='textbox' className={mx(textBlockWidth, attentionSurface, editorFillLayoutEditor)} ref={parentRef} />
|
|
95
83
|
</div>
|
|
96
84
|
</div>
|
|
97
85
|
);
|
|
@@ -14,13 +14,11 @@ import { withTheme } from '@dxos/storybook-utils';
|
|
|
14
14
|
|
|
15
15
|
import { Toolbar } from './Toolbar';
|
|
16
16
|
import {
|
|
17
|
-
|
|
17
|
+
decorateMarkdown,
|
|
18
18
|
comments,
|
|
19
19
|
formatting,
|
|
20
|
-
heading,
|
|
21
20
|
image,
|
|
22
21
|
table,
|
|
23
|
-
tasklist,
|
|
24
22
|
useComments,
|
|
25
23
|
useFormattingState,
|
|
26
24
|
} from '../../extensions';
|
|
@@ -39,7 +37,7 @@ const Story: FC<{ id?: string; content: string }> = ({ id = 'test', content }) =
|
|
|
39
37
|
const [formattingState, formattingObserver] = useFormattingState();
|
|
40
38
|
const extensions = useMemo(
|
|
41
39
|
() => [
|
|
42
|
-
|
|
40
|
+
decorateMarkdown(),
|
|
43
41
|
comments({
|
|
44
42
|
onCreate: ({ cursor }) => {
|
|
45
43
|
const id = PublicKey.random().toHex();
|
|
@@ -48,10 +46,8 @@ const Story: FC<{ id?: string; content: string }> = ({ id = 'test', content }) =
|
|
|
48
46
|
},
|
|
49
47
|
}),
|
|
50
48
|
formatting(),
|
|
51
|
-
heading(),
|
|
52
49
|
image(),
|
|
53
50
|
table(),
|
|
54
|
-
tasklist(),
|
|
55
51
|
formattingObserver,
|
|
56
52
|
],
|
|
57
53
|
[],
|
|
@@ -76,8 +72,9 @@ const Story: FC<{ id?: string; content: string }> = ({ id = 'test', content }) =
|
|
|
76
72
|
model={model}
|
|
77
73
|
extensions={extensions}
|
|
78
74
|
slots={{
|
|
79
|
-
root: { className: mx(textBlockWidth, editorFillLayoutRoot
|
|
75
|
+
root: { className: mx(textBlockWidth, editorFillLayoutRoot) },
|
|
80
76
|
editor: { className: editorFillLayoutEditor },
|
|
77
|
+
content: { className: '!pli-2' },
|
|
81
78
|
}}
|
|
82
79
|
/>
|
|
83
80
|
</div>
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
Select,
|
|
34
34
|
type ThemedClassName,
|
|
35
35
|
Toolbar as NaturalToolbar,
|
|
36
|
+
Tooltip,
|
|
36
37
|
type ToolbarToggleGroupItemProps,
|
|
37
38
|
useTranslation,
|
|
38
39
|
} from '@dxos/react-ui';
|
|
@@ -41,6 +42,8 @@ import { getSize } from '@dxos/react-ui-theme';
|
|
|
41
42
|
import { type Formatting } from '../../extensions';
|
|
42
43
|
import { translationKey } from '../../translations';
|
|
43
44
|
|
|
45
|
+
const tooltipProps = { side: 'top' as const, classNames: 'z-10' };
|
|
46
|
+
|
|
44
47
|
const HeadingIcons: { [key: string]: Icon } = {
|
|
45
48
|
'0': Paragraph,
|
|
46
49
|
'1': TextHOne,
|
|
@@ -112,10 +115,20 @@ type ToolbarButtonProps = ToolbarToggleGroupItemProps & { Icon: Icon };
|
|
|
112
115
|
|
|
113
116
|
const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
|
|
114
117
|
return (
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
<Tooltip.Root>
|
|
119
|
+
<Tooltip.Trigger asChild>
|
|
120
|
+
<NaturalToolbar.ToggleGroupItem variant='ghost' {...props} classNames={buttonStyles}>
|
|
121
|
+
<Icon className={iconStyles} />
|
|
122
|
+
<span className='sr-only'>{children}</span>
|
|
123
|
+
</NaturalToolbar.ToggleGroupItem>
|
|
124
|
+
</Tooltip.Trigger>
|
|
125
|
+
<Tooltip.Portal>
|
|
126
|
+
<Tooltip.Content {...tooltipProps}>
|
|
127
|
+
{children}
|
|
128
|
+
<Tooltip.Arrow />
|
|
129
|
+
</Tooltip.Content>
|
|
130
|
+
</Tooltip.Portal>
|
|
131
|
+
</Tooltip.Root>
|
|
119
132
|
);
|
|
120
133
|
};
|
|
121
134
|
|
|
@@ -129,40 +142,50 @@ const MarkdownHeading = () => {
|
|
|
129
142
|
const value = header ? header[1] : blockType === 'paragraph' || !blockType ? '0' : undefined;
|
|
130
143
|
const HeadingIcon = HeadingIcons[value ?? '0'];
|
|
131
144
|
return (
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
{level === 1 && <TextHOne className={iconStyles} />}
|
|
153
|
-
{level === 2 && <TextHTwo className={iconStyles} />}
|
|
154
|
-
{level === 3 && <TextHThree className={iconStyles} />}
|
|
155
|
-
{level === 4 && <TextHFour className={iconStyles} />}
|
|
156
|
-
{level === 5 && <TextHFive className={iconStyles} />}
|
|
157
|
-
{level === 6 && <TextHSix className={iconStyles} />}
|
|
145
|
+
<Tooltip.Root>
|
|
146
|
+
<Select.Root
|
|
147
|
+
disabled={value === null}
|
|
148
|
+
value={value ?? '0'}
|
|
149
|
+
onValueChange={(value) => onAction?.({ type: 'heading', data: parseInt(value) })}
|
|
150
|
+
>
|
|
151
|
+
<Tooltip.Trigger asChild>
|
|
152
|
+
<NaturalToolbar.Button asChild>
|
|
153
|
+
<Select.TriggerButton variant='ghost' classNames={buttonStyles}>
|
|
154
|
+
<span className='sr-only'>{t('heading label')}</span>
|
|
155
|
+
<HeadingIcon className={iconStyles} />
|
|
156
|
+
</Select.TriggerButton>
|
|
157
|
+
</NaturalToolbar.Button>
|
|
158
|
+
</Tooltip.Trigger>
|
|
159
|
+
<Select.Portal>
|
|
160
|
+
<Select.Content>
|
|
161
|
+
<Select.ScrollUpButton />
|
|
162
|
+
<Select.Viewport>
|
|
163
|
+
<Select.Option value='0'>
|
|
164
|
+
<Paragraph className={iconStyles} />
|
|
158
165
|
</Select.Option>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
{[1, 2, 3, 4, 5, 6].map((level) => (
|
|
167
|
+
<Select.Option key={level} value={String(level)}>
|
|
168
|
+
{level === 1 && <TextHOne className={iconStyles} />}
|
|
169
|
+
{level === 2 && <TextHTwo className={iconStyles} />}
|
|
170
|
+
{level === 3 && <TextHThree className={iconStyles} />}
|
|
171
|
+
{level === 4 && <TextHFour className={iconStyles} />}
|
|
172
|
+
{level === 5 && <TextHFive className={iconStyles} />}
|
|
173
|
+
{level === 6 && <TextHSix className={iconStyles} />}
|
|
174
|
+
</Select.Option>
|
|
175
|
+
))}
|
|
176
|
+
</Select.Viewport>
|
|
177
|
+
<Select.ScrollDownButton />
|
|
178
|
+
<Select.Arrow />
|
|
179
|
+
</Select.Content>
|
|
180
|
+
</Select.Portal>
|
|
181
|
+
</Select.Root>
|
|
182
|
+
<Tooltip.Portal>
|
|
183
|
+
<Tooltip.Content {...tooltipProps}>
|
|
184
|
+
{t('heading label')}
|
|
185
|
+
<Tooltip.Arrow />
|
|
186
|
+
</Tooltip.Content>
|
|
187
|
+
</Tooltip.Portal>
|
|
188
|
+
</Tooltip.Root>
|
|
166
189
|
);
|
|
167
190
|
};
|
|
168
191
|
|
|
@@ -233,7 +256,6 @@ const markdownBlocks: ButtonProps[] = [
|
|
|
233
256
|
type: 'codeblock',
|
|
234
257
|
Icon: CodeBlock,
|
|
235
258
|
getState: (state) => state.blockType === 'codeblock',
|
|
236
|
-
disabled: (state) => !state.blankLine,
|
|
237
259
|
},
|
|
238
260
|
{
|
|
239
261
|
type: 'table',
|
|
@@ -277,15 +299,25 @@ const MarkdownExtended = () => {
|
|
|
277
299
|
const { onAction } = useToolbarContext('MarkdownStyles');
|
|
278
300
|
const { t } = useTranslation(translationKey);
|
|
279
301
|
return (
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
302
|
+
<Tooltip.Root>
|
|
303
|
+
<Tooltip.Trigger asChild>
|
|
304
|
+
<NaturalToolbar.Button
|
|
305
|
+
variant='ghost'
|
|
306
|
+
data-testid='editor.toolbar.comment'
|
|
307
|
+
onClick={() => onAction?.({ type: 'comment' })}
|
|
308
|
+
classNames={buttonStyles}
|
|
309
|
+
>
|
|
310
|
+
<ChatText className={iconStyles} />
|
|
311
|
+
<span className='sr-only'>{t('comment label')}</span>
|
|
312
|
+
</NaturalToolbar.Button>
|
|
313
|
+
</Tooltip.Trigger>
|
|
314
|
+
<Tooltip.Portal>
|
|
315
|
+
<Tooltip.Content {...tooltipProps}>
|
|
316
|
+
{t('comment label')}
|
|
317
|
+
<Tooltip.Arrow />
|
|
318
|
+
</Tooltip.Content>
|
|
319
|
+
</Tooltip.Portal>
|
|
320
|
+
</Tooltip.Root>
|
|
289
321
|
);
|
|
290
322
|
};
|
|
291
323
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// Based upon @acheronfail’s implementation, fetched 16 Feb 2024
|
|
6
|
+
// https://discuss.codemirror.net/t/cursorscrollmargin-for-v6/7448/5
|
|
7
|
+
|
|
8
|
+
import { type EditorState, type Extension, Facet, Transaction } from '@codemirror/state';
|
|
9
|
+
import { EditorView } from '@codemirror/view';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Number of lines above/below the cursor to keep visible in the viewport.
|
|
13
|
+
* Defaults to 3.
|
|
14
|
+
*/
|
|
15
|
+
export const cursorLineMarginFacet = Facet.define<number, number>({
|
|
16
|
+
combine: (input) => input[0] ?? 3,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const annotation = 'cursorLineMargin';
|
|
20
|
+
const lineAtPos = (s: EditorState, pos: number) => s.doc.lineAt(pos).number;
|
|
21
|
+
// seems to the best approximation of CM5's `cursorScrollMargin`
|
|
22
|
+
// https://discuss.codemirror.net/t/cursorscrollmargin-for-v6/7448
|
|
23
|
+
export const cursorLineMargin: Extension = EditorView.updateListener.of(
|
|
24
|
+
({ transactions, state, selectionSet, startState, view }) => {
|
|
25
|
+
// make sure we don't trigger an infinite loop and ignore our own changes
|
|
26
|
+
if (transactions.length < 1 || transactions.some((tr) => tr.isUserEvent(annotation))) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const s = state;
|
|
31
|
+
const { main } = s.selection;
|
|
32
|
+
const cursorLineMargin = s.facet(cursorLineMarginFacet);
|
|
33
|
+
|
|
34
|
+
// editor rect
|
|
35
|
+
const rect = view.dom.getBoundingClientRect();
|
|
36
|
+
// top and bottom pixel positions of the visible region of the editor
|
|
37
|
+
const viewportTop = rect.top - view.documentTop;
|
|
38
|
+
const viewportBottom = rect.bottom - view.documentTop;
|
|
39
|
+
|
|
40
|
+
// line with main selection
|
|
41
|
+
const mainLine = lineAtPos(s, main.head);
|
|
42
|
+
// top most visible line
|
|
43
|
+
const _visTopLine = lineAtPos(s, view.lineBlockAtHeight(viewportTop).from);
|
|
44
|
+
|
|
45
|
+
// bottom most visible line
|
|
46
|
+
// NOTE: calculating this is slightly more complex - if the editor is
|
|
47
|
+
// larger than all the lines in it, and the `scrollPastEnd()` extension is
|
|
48
|
+
// enabled, then we need to calculate where the bottom most visible line
|
|
49
|
+
// should be if it extended all the way to the bottom of the editor
|
|
50
|
+
const botLineBlk = view.lineBlockAtHeight(viewportBottom);
|
|
51
|
+
const visBotLineDoc = lineAtPos(s, Math.min(botLineBlk.to, s.doc.length));
|
|
52
|
+
const visBotLine =
|
|
53
|
+
botLineBlk.bottom >= viewportBottom
|
|
54
|
+
? visBotLineDoc
|
|
55
|
+
: visBotLineDoc + Math.floor((viewportBottom - botLineBlk.bottom) / view.defaultLineHeight);
|
|
56
|
+
|
|
57
|
+
// const needsScrollTop = mainLine <= visTopLine + cursorLineMargin;
|
|
58
|
+
const needsScrollTop = false;
|
|
59
|
+
const needsScrollBot = mainLine >= visBotLine - cursorLineMargin;
|
|
60
|
+
|
|
61
|
+
// the scroll margins are overlapping
|
|
62
|
+
if (needsScrollTop && needsScrollBot) {
|
|
63
|
+
console.warn('is cursorScrollMargin too large for the editor size?');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// no need to scroll, we're in between scroll regions
|
|
68
|
+
if (!needsScrollTop && !needsScrollBot) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// if we're within the margin, but moving the other way, then don't scroll
|
|
73
|
+
if (selectionSet) {
|
|
74
|
+
const { head } = startState.selection.main;
|
|
75
|
+
const oldMainLine = lineAtPos(startState, head);
|
|
76
|
+
if (needsScrollTop && oldMainLine < mainLine) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (needsScrollBot && oldMainLine > mainLine) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
view.dispatch({
|
|
85
|
+
annotations: Transaction.userEvent.of(annotation),
|
|
86
|
+
effects: EditorView.scrollIntoView(s.selection.main.head, {
|
|
87
|
+
y: needsScrollTop ? 'start' : 'end',
|
|
88
|
+
yMargin: view.defaultLineHeight * cursorLineMargin,
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
);
|
package/src/extensions/index.ts
CHANGED