@dxos/react-ui-editor 0.3.10-main.560f82b → 0.3.10-main.5e11230
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 +328 -260
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- 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/extensions/blast.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts +1 -1
- 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/markdown/bundle.d.ts +7 -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/package.json +20 -20
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +14 -12
- package/src/components/TextEditor/TextEditor.tsx +4 -22
- package/src/components/TextEditor/extensions/blast.ts +13 -1
- package/src/components/TextEditor/extensions/comments.ts +21 -39
- package/src/components/TextEditor/extensions/experimental.tsx +14 -35
- package/src/components/TextEditor/extensions/image.ts +4 -5
- package/src/components/TextEditor/extensions/link.ts +2 -2
- package/src/components/TextEditor/extensions/markdown/bundle.ts +35 -42
- package/src/components/TextEditor/extensions/markdown/highlight.ts +37 -11
- package/src/components/TextEditor/extensions/table.ts +47 -48
- package/src/components/TextEditor/extensions/tasklist.ts +8 -8
- package/src/components/TextEditor/themes/default.ts +37 -0
|
@@ -18,25 +18,21 @@ import {
|
|
|
18
18
|
import { debounce } from '@dxos/async';
|
|
19
19
|
import { invariant } from '@dxos/invariant';
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// TODO(burdon): Import note (performance):
|
|
35
|
-
// Easily track & remove content with decorations
|
|
36
|
-
// https://discuss.codemirror.net/t/easily-track-remove-content-with-decorations/4606
|
|
21
|
+
// TODO(burdon): Reconcile with theme.
|
|
22
|
+
const styles = EditorView.baseTheme({
|
|
23
|
+
'& .cm-bookmark': {
|
|
24
|
+
cursor: 'pointer',
|
|
25
|
+
margin: '4px',
|
|
26
|
+
padding: '4px',
|
|
27
|
+
backgroundColor: 'yellow',
|
|
28
|
+
},
|
|
29
|
+
'& .cm-bookmark-selected': {
|
|
30
|
+
backgroundColor: 'orange',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
37
33
|
|
|
38
34
|
class BookmarkWidget extends WidgetType {
|
|
39
|
-
constructor(private readonly _pos: number, private readonly _id: string) {
|
|
35
|
+
constructor(private readonly _pos: number, private readonly _id: string, private readonly _handleClick: () => void) {
|
|
40
36
|
super();
|
|
41
37
|
invariant(this._id);
|
|
42
38
|
}
|
|
@@ -48,29 +44,15 @@ class BookmarkWidget extends WidgetType {
|
|
|
48
44
|
override toDOM() {
|
|
49
45
|
const span = document.createElement('span');
|
|
50
46
|
span.className = 'cm-bookmark';
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
span.textContent = '💬';
|
|
47
|
+
span.textContent = '§';
|
|
48
|
+
span.onclick = () => this._handleClick();
|
|
54
49
|
return span;
|
|
55
50
|
}
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
// TODO(burdon): Reconcile with theme.
|
|
59
|
-
const styles = EditorView.baseTheme({
|
|
60
|
-
'& .cm-bookmark': {
|
|
61
|
-
cursor: 'pointer',
|
|
62
|
-
margin: '4px',
|
|
63
|
-
padding: '4px',
|
|
64
|
-
backgroundColor: 'yellow',
|
|
65
|
-
},
|
|
66
|
-
'& .cm-bookmark-selected': {
|
|
67
|
-
backgroundColor: 'orange',
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
53
|
type CommentsInfo = {
|
|
72
54
|
active?: string;
|
|
73
|
-
items
|
|
55
|
+
items?: { id: string; pos: number; location: Rect | null }[];
|
|
74
56
|
};
|
|
75
57
|
|
|
76
58
|
export type CommentsOptions = {
|
|
@@ -91,7 +73,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
91
73
|
const id = match[1];
|
|
92
74
|
return Decoration.replace({
|
|
93
75
|
id,
|
|
94
|
-
widget: new BookmarkWidget(pos, id),
|
|
76
|
+
widget: new BookmarkWidget(pos, id, () => handleUpdate({ active: id })),
|
|
95
77
|
});
|
|
96
78
|
},
|
|
97
79
|
});
|
|
@@ -116,7 +98,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
116
98
|
},
|
|
117
99
|
);
|
|
118
100
|
|
|
119
|
-
const
|
|
101
|
+
const handleUpdate = debounce((data: CommentsInfo) => {
|
|
120
102
|
options.onUpdate?.(data);
|
|
121
103
|
}, 200);
|
|
122
104
|
|
|
@@ -158,8 +140,8 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
158
140
|
const decorations: { from: number; to: number; value: Decoration }[] = [];
|
|
159
141
|
const rangeSet = view.plugin(bookmarks)?.bookmarks;
|
|
160
142
|
let closest = Infinity;
|
|
161
|
-
// TODO(burdon):
|
|
162
|
-
const { from, to } = view.visibleRanges?.[0] ?? { from: 0, to: view.state.doc.length };
|
|
143
|
+
// TODO(burdon): Handle multiple visible ranges in large documents.
|
|
144
|
+
const { from, to } = /* view.visibleRanges?.[0] ?? */ { from: 0, to: view.state.doc.length };
|
|
163
145
|
rangeSet?.between(from, to, (from, to, value) => {
|
|
164
146
|
decorations.push({ from, to, value });
|
|
165
147
|
const d = Math.min(Math.abs(pos - from), Math.abs(pos - to));
|
|
@@ -171,7 +153,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
171
153
|
});
|
|
172
154
|
|
|
173
155
|
if (decorations.length) {
|
|
174
|
-
|
|
156
|
+
handleUpdate({
|
|
175
157
|
active,
|
|
176
158
|
items: decorations.map(
|
|
177
159
|
({
|
|
@@ -13,28 +13,21 @@ import {
|
|
|
13
13
|
type ViewUpdate,
|
|
14
14
|
} from '@codemirror/view';
|
|
15
15
|
|
|
16
|
-
// TODO(burdon):
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Notes: Can't edit widget content (or cursor nav through them).
|
|
32
|
-
// - https://discuss.codemirror.net/t/focusing-inputs-within-widgets/5178/7
|
|
33
|
-
// Widgets intentionally always get set to contenteditable=false,
|
|
34
|
-
// or they would become part of CodeMirror’s editable content element.
|
|
35
|
-
// You should be able to introduce new contenteditable=true child elements inside of them.
|
|
16
|
+
// TODO(burdon): Reconcile with theme.
|
|
17
|
+
export const styles = EditorView.baseTheme({
|
|
18
|
+
'.cm-hyperlink-label': {
|
|
19
|
+
cursor: 'pointer',
|
|
20
|
+
textDecoration: 'underline',
|
|
21
|
+
},
|
|
22
|
+
'.cm-hyperlink-bracket': {
|
|
23
|
+
opacity: 0.2,
|
|
24
|
+
},
|
|
25
|
+
'.cm-hyperlink-url': {
|
|
26
|
+
opacity: 0.5,
|
|
27
|
+
fontFamily: 'monospace',
|
|
28
|
+
},
|
|
29
|
+
});
|
|
36
30
|
|
|
37
|
-
// TODO(burdon): URL match?
|
|
38
31
|
const markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
|
|
39
32
|
|
|
40
33
|
/**
|
|
@@ -118,18 +111,4 @@ export function hyperLinkExtension() {
|
|
|
118
111
|
);
|
|
119
112
|
}
|
|
120
113
|
|
|
121
|
-
export const styles = EditorView.baseTheme({
|
|
122
|
-
'.cm-hyperlink-label': {
|
|
123
|
-
cursor: 'pointer',
|
|
124
|
-
textDecoration: 'underline',
|
|
125
|
-
},
|
|
126
|
-
'.cm-hyperlink-bracket': {
|
|
127
|
-
opacity: 0.2,
|
|
128
|
-
},
|
|
129
|
-
'.cm-hyperlink-url': {
|
|
130
|
-
opacity: 0.5,
|
|
131
|
-
fontFamily: 'monospace',
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
|
|
135
114
|
export const hyperlinkWidget: Extension = [hyperLinkExtension(), styles];
|
|
@@ -25,19 +25,19 @@ export const image = (options: ImageOptions = {}): Extension => {
|
|
|
25
25
|
|
|
26
26
|
const update = (state: EditorState, options: ImageOptions) => {
|
|
27
27
|
const builder = new RangeSetBuilder();
|
|
28
|
+
const cursor = state.selection.main.head;
|
|
28
29
|
|
|
29
|
-
// TODO(burdon): If not-readonly then show text.
|
|
30
|
-
// if (state.readOnly) {
|
|
31
30
|
syntaxTree(state).iterate({
|
|
32
31
|
enter: (node) => {
|
|
33
32
|
if (node.name === 'Image') {
|
|
34
33
|
const urlNode = node.node.getChild('URL');
|
|
35
34
|
if (urlNode) {
|
|
35
|
+
const hide = state.readOnly || cursor < node.from || cursor > node.to;
|
|
36
36
|
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
|
37
37
|
builder.add(
|
|
38
|
-
|
|
38
|
+
hide ? node.from : node.to,
|
|
39
39
|
node.to,
|
|
40
|
-
Decoration.
|
|
40
|
+
Decoration.replace({
|
|
41
41
|
widget: new ImageWidget(url),
|
|
42
42
|
}),
|
|
43
43
|
);
|
|
@@ -45,7 +45,6 @@ const update = (state: EditorState, options: ImageOptions) => {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
|
-
// }
|
|
49
48
|
|
|
50
49
|
return builder.finish();
|
|
51
50
|
};
|
|
@@ -94,20 +94,20 @@ class LinkText extends WidgetType {
|
|
|
94
94
|
const update = (state: EditorState, options: LinkOptions) => {
|
|
95
95
|
const builder = new RangeSetBuilder();
|
|
96
96
|
const cursor = state.selection.main.head;
|
|
97
|
+
|
|
97
98
|
syntaxTree(state).iterate({
|
|
98
99
|
enter: (node) => {
|
|
99
100
|
// Check if cursor is inside text.
|
|
100
101
|
if (node.name === 'Link') {
|
|
101
102
|
const marks = node.node.getChildren('LinkMark');
|
|
102
103
|
const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
|
|
103
|
-
|
|
104
104
|
const urlNode = node.node.getChild('URL');
|
|
105
105
|
const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
|
|
106
106
|
if (!url) {
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
if (cursor < node.from || cursor > node.to) {
|
|
110
|
+
if (state.readOnly || cursor < node.from || cursor > node.to) {
|
|
111
111
|
builder.add(
|
|
112
112
|
node.from,
|
|
113
113
|
node.to,
|
|
@@ -3,29 +3,18 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
|
6
|
-
import {
|
|
6
|
+
import { history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
|
|
7
7
|
import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
|
|
8
8
|
import { bracketMatching, defaultHighlightStyle, indentOnInput, syntaxHighlighting } from '@codemirror/language';
|
|
9
9
|
import { languages } from '@codemirror/language-data';
|
|
10
|
-
import {
|
|
10
|
+
import { searchKeymap } from '@codemirror/search';
|
|
11
11
|
import { EditorState, type Extension } from '@codemirror/state';
|
|
12
12
|
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
13
|
-
import {
|
|
14
|
-
crosshairCursor,
|
|
15
|
-
drawSelection,
|
|
16
|
-
dropCursor,
|
|
17
|
-
EditorView,
|
|
18
|
-
highlightActiveLine,
|
|
19
|
-
highlightActiveLineGutter,
|
|
20
|
-
highlightSpecialChars,
|
|
21
|
-
keymap,
|
|
22
|
-
placeholder,
|
|
23
|
-
rectangularSelection,
|
|
24
|
-
} from '@codemirror/view';
|
|
13
|
+
import { crosshairCursor, drawSelection, dropCursor, EditorView, keymap, placeholder } from '@codemirror/view';
|
|
25
14
|
|
|
26
15
|
import type { ThemeMode } from '@dxos/react-ui';
|
|
27
16
|
|
|
28
|
-
import { markdownHighlightStyle,
|
|
17
|
+
import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
|
|
29
18
|
|
|
30
19
|
export type MarkdownBundleOptions = {
|
|
31
20
|
readonly?: boolean;
|
|
@@ -33,45 +22,34 @@ export type MarkdownBundleOptions = {
|
|
|
33
22
|
placeholder?: string;
|
|
34
23
|
};
|
|
35
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Markdown bundle.
|
|
27
|
+
* Refs:
|
|
28
|
+
* https://github.com/codemirror/basic-setup
|
|
29
|
+
* https://codemirror.net/docs/community
|
|
30
|
+
* https://codemirror.net/docs/ref/#codemirror.basicSetup
|
|
31
|
+
*/
|
|
32
|
+
// TODO(burdon): Add Composer here: https://codemirror.net/docs/community
|
|
36
33
|
export const markdownBundle = ({
|
|
37
34
|
readonly,
|
|
38
35
|
themeMode,
|
|
39
36
|
placeholder: _placeholder,
|
|
40
37
|
}: MarkdownBundleOptions): Extension[] => {
|
|
41
|
-
// All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
|
|
42
|
-
// https://codemirror.net/docs/ref/#codemirror.basicSetup
|
|
43
38
|
return [
|
|
44
|
-
bracketMatching(),
|
|
45
|
-
closeBrackets(),
|
|
46
|
-
_placeholder && placeholder(_placeholder),
|
|
47
|
-
|
|
48
39
|
EditorState.allowMultipleSelections.of(true),
|
|
40
|
+
EditorState.tabSize.of(2),
|
|
49
41
|
EditorView.lineWrapping,
|
|
50
42
|
|
|
43
|
+
customKeymap(),
|
|
44
|
+
|
|
45
|
+
bracketMatching(),
|
|
46
|
+
closeBrackets(),
|
|
51
47
|
crosshairCursor(),
|
|
52
48
|
dropCursor(),
|
|
53
49
|
drawSelection(),
|
|
54
|
-
highlightActiveLine(),
|
|
55
|
-
highlightActiveLineGutter(),
|
|
56
|
-
highlightSelectionMatches(),
|
|
57
|
-
highlightSpecialChars(),
|
|
58
50
|
history(),
|
|
59
51
|
indentOnInput(),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// https://codemirror.net/docs/ref/#view.keymap
|
|
63
|
-
keymap.of([
|
|
64
|
-
// https://codemirror.net/docs/ref/#commands.defaultKeymap
|
|
65
|
-
...defaultKeymap,
|
|
66
|
-
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
67
|
-
...historyKeymap,
|
|
68
|
-
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
69
|
-
...closeBracketsKeymap,
|
|
70
|
-
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
71
|
-
...searchKeymap,
|
|
72
|
-
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
73
|
-
indentWithTab,
|
|
74
|
-
]),
|
|
52
|
+
_placeholder && placeholder(_placeholder),
|
|
75
53
|
|
|
76
54
|
// Main extension.
|
|
77
55
|
// https://github.com/codemirror/lang-markdown
|
|
@@ -89,8 +67,8 @@ export const markdownBundle = ({
|
|
|
89
67
|
|
|
90
68
|
// Parser extensions.
|
|
91
69
|
extensions: [
|
|
92
|
-
// GFM
|
|
93
|
-
|
|
70
|
+
// GFM provided by default.
|
|
71
|
+
markdownTagsExtensions,
|
|
94
72
|
],
|
|
95
73
|
}),
|
|
96
74
|
|
|
@@ -101,3 +79,18 @@ export const markdownBundle = ({
|
|
|
101
79
|
syntaxHighlighting(markdownHighlightStyle(readonly)),
|
|
102
80
|
].filter(Boolean) as Extension[];
|
|
103
81
|
};
|
|
82
|
+
|
|
83
|
+
// https://codemirror.net/docs/ref/#view.keymap
|
|
84
|
+
const customKeymap = () =>
|
|
85
|
+
keymap.of([
|
|
86
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
87
|
+
indentWithTab,
|
|
88
|
+
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
89
|
+
...standardKeymap,
|
|
90
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
91
|
+
...historyKeymap,
|
|
92
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
93
|
+
...closeBracketsKeymap,
|
|
94
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
95
|
+
...searchKeymap,
|
|
96
|
+
]);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { markdownLanguage } from '@codemirror/lang-markdown';
|
|
6
6
|
import { HighlightStyle } from '@codemirror/language';
|
|
7
7
|
import { tags, styleTags, Tag } from '@lezer/highlight';
|
|
8
|
-
import { type MarkdownConfig } from '@lezer/markdown';
|
|
8
|
+
import { type MarkdownConfig, Table } from '@lezer/markdown';
|
|
9
9
|
import get from 'lodash.get';
|
|
10
10
|
|
|
11
11
|
import { mx } from '@dxos/react-ui-theme';
|
|
@@ -24,11 +24,6 @@ import {
|
|
|
24
24
|
tokens,
|
|
25
25
|
} from '../../../../styles';
|
|
26
26
|
|
|
27
|
-
// TODO(burdon): Style table in monospace.
|
|
28
|
-
// https://discuss.codemirror.net/t/markdown-table-highlighting/658
|
|
29
|
-
|
|
30
|
-
// TODO(burdon): Readonly: use remark rather than hiding markup chars (which cursor can still move into).
|
|
31
|
-
|
|
32
27
|
/**
|
|
33
28
|
* Custom tags defined and processed by the GFM lezer extension.
|
|
34
29
|
* https://github.com/lezer-parser/markdown
|
|
@@ -47,24 +42,42 @@ export const markdownTags = {
|
|
|
47
42
|
ListMark: Tag.define(),
|
|
48
43
|
QuoteMark: Tag.define(),
|
|
49
44
|
URL: Tag.define(),
|
|
50
|
-
};
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
// Custom.
|
|
47
|
+
TableCell: Tag.define(),
|
|
54
48
|
};
|
|
55
49
|
|
|
50
|
+
// TODO(burdon): Customize table parser (make all content monospace).
|
|
51
|
+
// https://github.com/lezer-parser/markdown/blob/main/src/extension.ts
|
|
52
|
+
Table.defineNodes?.forEach((node: any) => {
|
|
53
|
+
switch (node?.name) {
|
|
54
|
+
case 'TableCell': {
|
|
55
|
+
node.style = markdownTags.TableCell;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const markdownTagsExtensions: MarkdownConfig[] = [
|
|
62
|
+
Table,
|
|
63
|
+
{
|
|
64
|
+
props: [styleTags(markdownTags)],
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
56
68
|
/**
|
|
57
69
|
* Styling based on `lezer` parser tags.
|
|
58
70
|
* https://codemirror.net/examples/styling
|
|
59
71
|
* https://github.com/lezer-parser/highlight
|
|
72
|
+
* https://github.com/lezer-parser/highlight/blob/main/src/highlight.ts#L427
|
|
60
73
|
* https://lezer.codemirror.net/docs/ref/#highlight.tags (list of tags)
|
|
61
74
|
*
|
|
62
75
|
* Examples:
|
|
63
76
|
* - https://github.com/codemirror/language/blob/main/src/highlight.ts#L194
|
|
64
77
|
* - https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts#L115
|
|
65
78
|
*/
|
|
66
|
-
export const markdownHighlightStyle = (readonly?: boolean) =>
|
|
67
|
-
HighlightStyle.define(
|
|
79
|
+
export const markdownHighlightStyle = (readonly?: boolean) => {
|
|
80
|
+
return HighlightStyle.define(
|
|
68
81
|
[
|
|
69
82
|
{
|
|
70
83
|
tag: [
|
|
@@ -129,6 +142,11 @@ export const markdownHighlightStyle = (readonly?: boolean) =>
|
|
|
129
142
|
class: readonly ? 'hidden' : codeMark,
|
|
130
143
|
},
|
|
131
144
|
|
|
145
|
+
{
|
|
146
|
+
tag: [tags.monospace],
|
|
147
|
+
class: 'font-mono',
|
|
148
|
+
},
|
|
149
|
+
|
|
132
150
|
// Headings.
|
|
133
151
|
{ tag: tags.heading1, class: heading(1, readonly) },
|
|
134
152
|
{ tag: tags.heading2, class: heading(2, readonly) },
|
|
@@ -163,11 +181,18 @@ export const markdownHighlightStyle = (readonly?: boolean) =>
|
|
|
163
181
|
class: blockquote,
|
|
164
182
|
},
|
|
165
183
|
|
|
184
|
+
// TODO(burdon): Replace with extension.
|
|
166
185
|
{
|
|
167
186
|
tag: [markdownTags.HorizontalRule],
|
|
168
187
|
class: mx(horizontalRule, readonly && '-indent-[100rem]'),
|
|
169
188
|
},
|
|
170
189
|
|
|
190
|
+
// TODO(burdon): Only if being edited.
|
|
191
|
+
{
|
|
192
|
+
tag: [markdownTags.TableCell],
|
|
193
|
+
class: 'font-mono',
|
|
194
|
+
},
|
|
195
|
+
|
|
171
196
|
// Main content, paragraphs, etc.
|
|
172
197
|
// {
|
|
173
198
|
// tag: [tags.content],
|
|
@@ -179,3 +204,4 @@ export const markdownHighlightStyle = (readonly?: boolean) =>
|
|
|
179
204
|
all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
|
|
180
205
|
},
|
|
181
206
|
);
|
|
207
|
+
};
|
|
@@ -25,61 +25,60 @@ export const table = (options: TableOptions = {}) => {
|
|
|
25
25
|
|
|
26
26
|
type Table = {
|
|
27
27
|
from: number;
|
|
28
|
-
to
|
|
28
|
+
to: number;
|
|
29
29
|
header?: string[];
|
|
30
30
|
rows?: string[][];
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
const update = (state: EditorState, options: TableOptions) => {
|
|
34
34
|
const builder = new RangeSetBuilder();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
case 'TableDelimiter': {
|
|
72
|
-
getTable().to = node.to;
|
|
73
|
-
break;
|
|
35
|
+
const cursor = state.selection.main.head;
|
|
36
|
+
|
|
37
|
+
const tables: Table[] = [];
|
|
38
|
+
const getTable = () => tables[tables.length - 1];
|
|
39
|
+
const getRow = () => {
|
|
40
|
+
const table = getTable();
|
|
41
|
+
return table.rows?.[table.rows.length - 1];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Parse table.
|
|
45
|
+
// TODO(burdon): Use remark?: https://www.npmjs.com/package/remark-gfm
|
|
46
|
+
// TODO(burdon): Style in monospace:
|
|
47
|
+
// https://discuss.codemirror.net/t/markdown-table-highlighting/658
|
|
48
|
+
// https://github.com/lezer-parser/markdown/blob/main/src/extension.ts
|
|
49
|
+
syntaxTree(state).iterate({
|
|
50
|
+
enter: (node) => {
|
|
51
|
+
// Check if cursor is inside text.
|
|
52
|
+
switch (node.name) {
|
|
53
|
+
case 'Table': {
|
|
54
|
+
tables.push({ from: node.from, to: node.to });
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'TableHeader': {
|
|
58
|
+
getTable().header = [];
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case 'TableRow': {
|
|
62
|
+
(getTable().rows ??= []).push([]);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'TableCell': {
|
|
66
|
+
const row = getRow();
|
|
67
|
+
if (row) {
|
|
68
|
+
row.push(state.sliceDoc(node.from, node.to));
|
|
69
|
+
} else {
|
|
70
|
+
getTable().header?.push(state.sliceDoc(node.from, node.to));
|
|
74
71
|
}
|
|
72
|
+
break;
|
|
75
73
|
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
});
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
82
|
-
}
|
|
78
|
+
tables.forEach((table) => {
|
|
79
|
+
const hide = state.readOnly || cursor < table.from || cursor > table.to;
|
|
80
|
+
hide && builder.add(table.from, table.to!, Decoration.replace({ widget: new TableWidget(table) }));
|
|
81
|
+
});
|
|
83
82
|
|
|
84
83
|
return builder.finish();
|
|
85
84
|
};
|
|
@@ -98,11 +97,11 @@ class TableWidget extends WidgetType {
|
|
|
98
97
|
|
|
99
98
|
{
|
|
100
99
|
const header = table.appendChild(document.createElement('thead'));
|
|
101
|
-
const
|
|
100
|
+
const tr = header.appendChild(document.createElement('tr'));
|
|
102
101
|
this._table.header?.forEach((cell) => {
|
|
103
102
|
const th = document.createElement('th');
|
|
104
103
|
th.setAttribute('class', 'cm-table-head');
|
|
105
|
-
|
|
104
|
+
tr.appendChild(th).textContent = cell;
|
|
106
105
|
});
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -12,6 +12,14 @@ import {
|
|
|
12
12
|
type ViewUpdate,
|
|
13
13
|
} from '@codemirror/view';
|
|
14
14
|
|
|
15
|
+
// TODO(burdon): Reconcile with theme.
|
|
16
|
+
const styles = EditorView.baseTheme({
|
|
17
|
+
'& .cm-task-item': {
|
|
18
|
+
paddingLeft: '4px',
|
|
19
|
+
paddingRight: '8px',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
15
23
|
class CheckboxWidget extends WidgetType {
|
|
16
24
|
constructor(
|
|
17
25
|
private readonly _pos: number,
|
|
@@ -53,14 +61,6 @@ class CheckboxWidget extends WidgetType {
|
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
// TODO(burdon): Reconcile with theme.
|
|
57
|
-
const styles = EditorView.baseTheme({
|
|
58
|
-
'& .cm-task-item': {
|
|
59
|
-
paddingLeft: '4px',
|
|
60
|
-
paddingRight: '8px',
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
64
|
export type TasklistOptions = {};
|
|
65
65
|
|
|
66
66
|
export const tasklist = (options: TasklistOptions = {}) => {
|
|
@@ -203,6 +203,43 @@ export const defaultTheme: {
|
|
|
203
203
|
acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
|
|
204
204
|
return acc;
|
|
205
205
|
}, {}),
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Panels
|
|
209
|
+
* https://github.com/codemirror/search/blob/main/src/search.ts#L745
|
|
210
|
+
*
|
|
211
|
+
* Find/replace panel.
|
|
212
|
+
* <div class="cm-announced">...</div>
|
|
213
|
+
* <div class="cm-scroller">...</div>
|
|
214
|
+
* <div class="cm-panels cm-panels-bottom">
|
|
215
|
+
* <div class="cm-search cm-panel">
|
|
216
|
+
* <input class="cm-textfield" />
|
|
217
|
+
* <button class="cm-button">...</button>
|
|
218
|
+
* <label><input type="checkbox" />...</label>
|
|
219
|
+
* </div>
|
|
220
|
+
* </div
|
|
221
|
+
*/
|
|
222
|
+
'& .cm-panels': {
|
|
223
|
+
border: `1px solid ${get(tokens, 'extend.colors.neutral.200')}`,
|
|
224
|
+
borderBottom: 'none',
|
|
225
|
+
},
|
|
226
|
+
'& .cm-panel': {
|
|
227
|
+
background: get(tokens, 'extend.colors.neutral.50'),
|
|
228
|
+
fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
229
|
+
},
|
|
230
|
+
'& .cm-button': {
|
|
231
|
+
margin: '4px',
|
|
232
|
+
fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
233
|
+
background: get(tokens, 'extend.colors.neutral.100'),
|
|
234
|
+
border: 'none',
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
'& .cm-searchMatch': {
|
|
238
|
+
backgroundColor: get(tokens, 'extend.colors.yellow.100'),
|
|
239
|
+
},
|
|
240
|
+
'& .cm-searchMatch.cm-searchMatch-selected': {
|
|
241
|
+
backgroundColor: get(tokens, 'extend.colors.primary.100'),
|
|
242
|
+
},
|
|
206
243
|
};
|
|
207
244
|
|
|
208
245
|
export const textTheme: {
|