@dxos/react-ui-editor 0.6.9 → 0.6.10-main.48c066e
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 +66 -62
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/InputMode.stories.d.ts +1 -1
- package/dist/types/src/InputMode.stories.d.ts.map +1 -1
- package/dist/types/src/TextEditor.stories.d.ts +1 -1
- package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +5 -1
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +3 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts +1 -2
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/package.json +24 -27
- package/src/InputMode.stories.tsx +1 -1
- package/src/TextEditor.stories.tsx +3 -3
- package/src/defaults.ts +13 -5
- package/src/extensions/autocomplete.ts +4 -1
- package/src/extensions/automerge/automerge.stories.tsx +1 -1
- package/src/extensions/factories.ts +4 -4
- package/src/extensions/markdown/bundle.ts +3 -0
- package/src/extensions/markdown/decorate.ts +31 -25
- package/src/extensions/markdown/parser.test.ts +29 -0
- package/src/styles/theme.ts +26 -32
- package/src/styles/tokens.ts +4 -4
package/src/defaults.ts
CHANGED
|
@@ -4,14 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
import { EditorView } from '@codemirror/view';
|
|
6
6
|
|
|
7
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
8
|
+
|
|
7
9
|
import { getToken } from './styles';
|
|
8
10
|
|
|
11
|
+
const marginY = '!mt-[16px] !mb-[32px]';
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* CodeMirror content width.
|
|
11
15
|
* 40rem = 640px. Corresponds to initial plank width (Google docs, Stashpad, etc.)
|
|
12
16
|
* 50rem = 800px. Maximum content width for solo mode.
|
|
13
17
|
*/
|
|
14
|
-
export const editorContent = '!
|
|
18
|
+
export const editorContent = mx(marginY, '!mli-auto w-full max-w-[min(50rem,100%-2rem)]');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Margin for numbers.
|
|
22
|
+
*/
|
|
23
|
+
export const editorFullWidth = mx(marginY, '!ml-[3rem]');
|
|
15
24
|
|
|
16
25
|
export const editorWithToolbarLayout =
|
|
17
26
|
'grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden';
|
|
@@ -21,10 +30,9 @@ export const editorGutter = EditorView.baseTheme({
|
|
|
21
30
|
// Match margin from content.
|
|
22
31
|
marginTop: '16px',
|
|
23
32
|
marginBottom: '16px',
|
|
24
|
-
//
|
|
25
|
-
marginRight:
|
|
26
|
-
width:
|
|
27
|
-
backgroundColor: 'transparent !important',
|
|
33
|
+
// TODO(burdon): Inset next to content.
|
|
34
|
+
// marginRight: `-${marginWidth}rem`,
|
|
35
|
+
// width: `${marginWidth}rem`,
|
|
28
36
|
},
|
|
29
37
|
});
|
|
30
38
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import {
|
|
10
10
|
autocompletion,
|
|
11
11
|
completionKeymap,
|
|
12
|
+
type CompletionSource,
|
|
12
13
|
type Completion,
|
|
13
14
|
type CompletionContext,
|
|
14
15
|
type CompletionResult,
|
|
@@ -20,13 +21,14 @@ export type AutocompleteResult = Completion;
|
|
|
20
21
|
|
|
21
22
|
export type AutocompleteOptions = {
|
|
22
23
|
activateOnTyping?: boolean;
|
|
24
|
+
override?: CompletionSource[];
|
|
23
25
|
onSearch?: (text: string) => Completion[];
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Autocomplete extension.
|
|
28
30
|
*/
|
|
29
|
-
export const autocomplete = ({ activateOnTyping, onSearch }: AutocompleteOptions = {}) => {
|
|
31
|
+
export const autocomplete = ({ activateOnTyping, override, onSearch }: AutocompleteOptions = {}) => {
|
|
30
32
|
const extentions = [
|
|
31
33
|
// https://codemirror.net/docs/ref/#view.keymap
|
|
32
34
|
// https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
|
|
@@ -37,6 +39,7 @@ export const autocomplete = ({ activateOnTyping, onSearch }: AutocompleteOptions
|
|
|
37
39
|
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
38
40
|
autocompletion({
|
|
39
41
|
activateOnTyping,
|
|
42
|
+
override,
|
|
40
43
|
|
|
41
44
|
// closeOnBlur: false,
|
|
42
45
|
// defaultKeymap: false,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
|
6
|
-
import { defaultKeymap, history, historyKeymap, standardKeymap } from '@codemirror/commands';
|
|
6
|
+
import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
|
|
7
7
|
import { bracketMatching } from '@codemirror/language';
|
|
8
8
|
import { searchKeymap } from '@codemirror/search';
|
|
9
9
|
import { EditorState, type Extension } from '@codemirror/state';
|
|
@@ -95,7 +95,7 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
|
95
95
|
props.bracketMatching && bracketMatching(),
|
|
96
96
|
props.closeBrackets && closeBrackets(),
|
|
97
97
|
props.dropCursor && dropCursor(),
|
|
98
|
-
props.drawSelection && drawSelection(),
|
|
98
|
+
props.drawSelection && drawSelection({ cursorBlinkRate: 1_200 }),
|
|
99
99
|
props.highlightActiveLine && highlightActiveLine(),
|
|
100
100
|
props.history && history(),
|
|
101
101
|
props.lineNumbers && lineNumbers(),
|
|
@@ -109,9 +109,9 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
|
109
109
|
keymap.of(
|
|
110
110
|
[
|
|
111
111
|
...((props.keymap && keymaps[props.keymap]) ?? []),
|
|
112
|
-
// NOTE:
|
|
112
|
+
// NOTE: Tabs are also configured by markdown extension.
|
|
113
113
|
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
114
|
-
|
|
114
|
+
...(props.indentWithTab ? [indentWithTab] : []),
|
|
115
115
|
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
116
116
|
...(props.closeBrackets ? closeBracketsKeymap : []),
|
|
117
117
|
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
@@ -44,6 +44,9 @@ export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions =
|
|
|
44
44
|
// Languages for syntax highlighting fenced code blocks.
|
|
45
45
|
codeLanguages: languages,
|
|
46
46
|
|
|
47
|
+
// Don't complete HTML tags.
|
|
48
|
+
completeHTMLTags: false,
|
|
49
|
+
|
|
47
50
|
// Parser extensions.
|
|
48
51
|
extensions: [
|
|
49
52
|
// GFM provided by default.
|
|
@@ -145,7 +145,7 @@ const autoHideTags = new Set([
|
|
|
145
145
|
type NumberingLevel = { type: string; from: number; to: number; level: number; number: number };
|
|
146
146
|
|
|
147
147
|
const bulletListIndentationWidth = 24;
|
|
148
|
-
const orderedListIndentationWidth =
|
|
148
|
+
const orderedListIndentationWidth = 36; // TODO(burdon): Make variable length based on number of digits.
|
|
149
149
|
|
|
150
150
|
const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boolean) => {
|
|
151
151
|
const deco = new RangeSetBuilder<Decoration>();
|
|
@@ -182,8 +182,9 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
|
182
182
|
return listLevels[listLevels.length - 1];
|
|
183
183
|
};
|
|
184
184
|
|
|
185
|
+
// const count = 0;
|
|
185
186
|
const enterNode = (node: SyntaxNodeRef) => {
|
|
186
|
-
// console.log(
|
|
187
|
+
// console.log(`[${count++}]`, { node: node.name, from: node.from, to: node.to });
|
|
187
188
|
switch (node.name) {
|
|
188
189
|
// ATXHeading > HeaderMark > Paragraph
|
|
189
190
|
// NOTE: Numbering requires processing the entire document since otherwise only the visible range will be
|
|
@@ -253,50 +254,55 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
|
253
254
|
const list = getCurrentList();
|
|
254
255
|
const width = list.type === 'OrderedList' ? orderedListIndentationWidth : bulletListIndentationWidth;
|
|
255
256
|
const offset = ((list.level ?? 0) + 1) * width;
|
|
256
|
-
const
|
|
257
|
+
const line = state.doc.lineAt(node.from);
|
|
258
|
+
if (node.from === line.to - 1) {
|
|
259
|
+
// Abort if only the hyphen is typed.
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
257
262
|
|
|
263
|
+
// Add line decoration to indent.
|
|
258
264
|
deco.add(
|
|
259
|
-
|
|
260
|
-
|
|
265
|
+
line.from,
|
|
266
|
+
line.from,
|
|
261
267
|
Decoration.line({
|
|
262
268
|
class: 'cm-list-item',
|
|
263
269
|
attributes: {
|
|
264
|
-
|
|
265
|
-
// Note: This makes the cursor appear to be left of the margin.
|
|
266
|
-
style: `padding-left: ${offset}px; text-indent: calc(-${width}px - 0.25em);`,
|
|
270
|
+
style: `padding-left: ${offset}px; text-indent: -${width}px;`,
|
|
267
271
|
},
|
|
268
272
|
}),
|
|
269
273
|
);
|
|
270
274
|
|
|
271
275
|
// Remove indentation spaces.
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
const whitespace = line.match(/^ */)?.[0].length ?? 0;
|
|
276
|
+
const text = state.doc.sliceString(line.from, node.to);
|
|
277
|
+
const whitespace = text.match(/^ */)?.[0].length ?? 0;
|
|
275
278
|
if (whitespace) {
|
|
276
|
-
atomicDeco.add(
|
|
279
|
+
atomicDeco.add(line.from, line.from + whitespace, hide);
|
|
277
280
|
}
|
|
278
281
|
|
|
279
|
-
// const mark = node.node.firstChild!;
|
|
280
|
-
// console.log(mark?.name);
|
|
281
|
-
// if (mark?.name === 'ListMark') {}
|
|
282
282
|
break;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
case 'ListMark': {
|
|
286
286
|
// Look-ahead for task marker.
|
|
287
|
-
const
|
|
288
|
-
if (
|
|
289
|
-
atomicDeco.add(node.from, node.to, hide);
|
|
287
|
+
const next = tree.resolve(node.to + 1, 1);
|
|
288
|
+
if (next?.name === 'TaskMarker') {
|
|
289
|
+
atomicDeco.add(node.from, node.to + 1, hide);
|
|
290
290
|
break;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
// TODO(burdon): Cursor stops for 1 character when moving back into number (but not dashes).
|
|
294
|
-
// TODO(burdon): Option to make hierarchical; or a, b, c. etc.
|
|
295
293
|
const list = getCurrentList();
|
|
296
|
-
|
|
294
|
+
|
|
295
|
+
// Abort unless followed by space.
|
|
296
|
+
const text = state.doc.sliceString(node.from, node.to + 1);
|
|
297
|
+
if (list.type === 'BulletList' && text[1] !== ' ') {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// TODO(burdon): Option to make hierarchical; or a), i), etc.
|
|
302
|
+
const label = list.type === 'OrderedList' ? `${++list.number}.` : '•';
|
|
297
303
|
atomicDeco.add(
|
|
298
304
|
node.from,
|
|
299
|
-
node.to,
|
|
305
|
+
node.to + 1,
|
|
300
306
|
Decoration.replace({
|
|
301
307
|
widget: new TextWidget(
|
|
302
308
|
label,
|
|
@@ -310,8 +316,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
|
310
316
|
case 'TaskMarker': {
|
|
311
317
|
if (!editingRange(state, node, focus)) {
|
|
312
318
|
const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
|
|
313
|
-
atomicDeco.add(node.from
|
|
314
|
-
atomicDeco.add(node.from, node.to, checked ? checkedTask : uncheckedTask);
|
|
319
|
+
atomicDeco.add(node.from, node.to + 1, checked ? checkedTask : uncheckedTask);
|
|
315
320
|
}
|
|
316
321
|
break;
|
|
317
322
|
}
|
|
@@ -574,7 +579,7 @@ const formattingStyles = EditorView.baseTheme({
|
|
|
574
579
|
|
|
575
580
|
'& .cm-task': {
|
|
576
581
|
display: 'inline-block',
|
|
577
|
-
width:
|
|
582
|
+
width: `${bulletListIndentationWidth}px`,
|
|
578
583
|
color: getToken('extend.colors.blue.500'),
|
|
579
584
|
},
|
|
580
585
|
'& .cm-task-checkbox': {
|
|
@@ -587,6 +592,7 @@ const formattingStyles = EditorView.baseTheme({
|
|
|
587
592
|
'& .cm-list-mark': {
|
|
588
593
|
display: 'inline-block',
|
|
589
594
|
textAlign: 'right',
|
|
595
|
+
paddingRight: '0.5em',
|
|
590
596
|
fontVariant: 'tabular-nums',
|
|
591
597
|
},
|
|
592
598
|
'& .cm-list-mark-bullet': {
|
|
@@ -9,6 +9,34 @@ import { parser } from '@lezer/markdown';
|
|
|
9
9
|
import { describe, test } from '@dxos/test';
|
|
10
10
|
|
|
11
11
|
describe('parser', () => {
|
|
12
|
+
// test.only('list-mark', () => {
|
|
13
|
+
// const newParser = parser.configure({
|
|
14
|
+
// parseBlock: [
|
|
15
|
+
// {
|
|
16
|
+
// name: 'ListItem',
|
|
17
|
+
// parse: (cx, line) => {
|
|
18
|
+
// console.log(`[${line.text}]`, cx.lineStart, line.text.length);
|
|
19
|
+
// // line.skipSpace(1);
|
|
20
|
+
// return true;
|
|
21
|
+
// },
|
|
22
|
+
// },
|
|
23
|
+
// ],
|
|
24
|
+
// });
|
|
25
|
+
//
|
|
26
|
+
// {
|
|
27
|
+
// const result = newParser.parse(' - ');
|
|
28
|
+
// testTree(result, 'Document(BulletList(ListItem(ListMark)))');
|
|
29
|
+
// }
|
|
30
|
+
// {
|
|
31
|
+
// const result = newParser.parse('-x');
|
|
32
|
+
// testTree(result, 'Document(Paragraph)');
|
|
33
|
+
// }
|
|
34
|
+
// {
|
|
35
|
+
// const result = newParser.parse('- x');
|
|
36
|
+
// testTree(result, 'Document(BulletList(ListItem(ListMark,Paragraph)))');
|
|
37
|
+
// }
|
|
38
|
+
// });
|
|
39
|
+
|
|
12
40
|
// https://www.markdownguide.org/basic-syntax/#lists-1
|
|
13
41
|
test('lists', () => {
|
|
14
42
|
// Indented list must have 4 spaces.
|
|
@@ -25,6 +53,7 @@ describe('parser', () => {
|
|
|
25
53
|
'1. one',
|
|
26
54
|
].join('\n'),
|
|
27
55
|
);
|
|
56
|
+
|
|
28
57
|
testTree(
|
|
29
58
|
result,
|
|
30
59
|
[
|
package/src/styles/theme.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { getToken } from './tokens';
|
|
|
9
9
|
export type ThemeStyles = Record<string, StyleSpec>;
|
|
10
10
|
|
|
11
11
|
// TODO(burdon): Factor out theme.
|
|
12
|
-
// TODO(burdon):
|
|
13
|
-
//
|
|
12
|
+
// TODO(burdon): Factor out extension-specific logic.
|
|
13
|
+
// TODO(burdon): Remove getToken and use var/semantic colors.
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Minimal styles.
|
|
@@ -82,29 +82,14 @@ export const defaultTheme: ThemeStyles = {
|
|
|
82
82
|
* NOTE: Gutters should have the same top margin as the content.
|
|
83
83
|
*/
|
|
84
84
|
'.cm-gutters': {
|
|
85
|
-
background: '
|
|
85
|
+
background: 'var(--dx-base)',
|
|
86
86
|
},
|
|
87
87
|
'.cm-gutter': {},
|
|
88
88
|
'.cm-gutterElement': {
|
|
89
|
+
fontSize: '16px',
|
|
89
90
|
lineHeight: 1.5,
|
|
90
91
|
},
|
|
91
92
|
|
|
92
|
-
//
|
|
93
|
-
// Cursor
|
|
94
|
-
//
|
|
95
|
-
'&light .cm-cursor, &light .cm-dropCursor': {
|
|
96
|
-
borderLeft: '2px solid black',
|
|
97
|
-
},
|
|
98
|
-
'&dark .cm-cursor, &dark .cm-dropCursor': {
|
|
99
|
-
borderLeft: '2px solid white',
|
|
100
|
-
},
|
|
101
|
-
'&light .cm-placeholder': {
|
|
102
|
-
color: getToken('extend.semanticColors.description.light', 'rgba(0,0,0,.2)'),
|
|
103
|
-
},
|
|
104
|
-
'&dark .cm-placeholder': {
|
|
105
|
-
color: getToken('extend.semanticColors.description.dark', 'rgba(255,255,255,.2)'),
|
|
106
|
-
},
|
|
107
|
-
|
|
108
93
|
//
|
|
109
94
|
// line
|
|
110
95
|
//
|
|
@@ -112,7 +97,7 @@ export const defaultTheme: ThemeStyles = {
|
|
|
112
97
|
paddingInline: 0,
|
|
113
98
|
},
|
|
114
99
|
'.cm-activeLine': {
|
|
115
|
-
background: '
|
|
100
|
+
background: 'var(--dx-hoverSurface)',
|
|
116
101
|
},
|
|
117
102
|
|
|
118
103
|
//
|
|
@@ -123,20 +108,31 @@ export const defaultTheme: ThemeStyles = {
|
|
|
123
108
|
},
|
|
124
109
|
|
|
125
110
|
//
|
|
126
|
-
//
|
|
111
|
+
// Cursor
|
|
127
112
|
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
'&light .cm-cursor, &light .cm-dropCursor': {
|
|
114
|
+
borderLeft: '2px solid black',
|
|
115
|
+
},
|
|
116
|
+
'&dark .cm-cursor, &dark .cm-dropCursor': {
|
|
117
|
+
borderLeft: '2px solid white',
|
|
118
|
+
},
|
|
119
|
+
'&light .cm-placeholder': {
|
|
120
|
+
color: getToken('extend.semanticColors.description.light', 'rgba(0,0,0,.2)'),
|
|
131
121
|
},
|
|
132
|
-
'&
|
|
133
|
-
|
|
122
|
+
'&dark .cm-placeholder': {
|
|
123
|
+
color: getToken('extend.semanticColors.description.dark', 'rgba(255,255,255,.2)'),
|
|
134
124
|
},
|
|
135
|
-
|
|
136
|
-
|
|
125
|
+
|
|
126
|
+
//
|
|
127
|
+
// Selection
|
|
128
|
+
//
|
|
129
|
+
|
|
130
|
+
'.cm-selectionBackground': {
|
|
131
|
+
background: 'var(--dx-selectionSurface) !important',
|
|
137
132
|
},
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
|
|
134
|
+
'.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
|
135
|
+
background: 'var(--dx-selectionSurface) !important',
|
|
140
136
|
},
|
|
141
137
|
|
|
142
138
|
//
|
|
@@ -274,8 +270,6 @@ export const defaultTheme: ThemeStyles = {
|
|
|
274
270
|
},
|
|
275
271
|
},
|
|
276
272
|
|
|
277
|
-
// TODO(burdon): Factor out element specific logic.
|
|
278
|
-
|
|
279
273
|
//
|
|
280
274
|
// table
|
|
281
275
|
//
|
package/src/styles/tokens.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import get from 'lodash.get';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { tokens } from '@dxos/react-ui-theme';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
(window as any).__tokens = tokens;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Replace with CSS vars.
|
|
12
|
+
* Returns the tailwind token value.
|
|
14
13
|
*/
|
|
14
|
+
// TODO(burdon): Replace with CSS vars.
|
|
15
15
|
export const getToken = (path: string, defaultValue?: string | string[]): string => {
|
|
16
16
|
const value = get(tokens, path, defaultValue);
|
|
17
17
|
return value?.toString() ?? '';
|