@dxos/react-ui-editor 0.3.10-main.a21ec31 → 0.3.10-main.c5193cc
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 +1006 -219
- 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 +22 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +3 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +3 -2
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/basic.d.ts +2 -1
- package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
- package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts +18 -0
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/demo.d.ts +11 -0
- package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
- package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +6 -0
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts +4 -2
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +2 -1
- 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 +6 -2
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
- package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
- package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +2 -4
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/themes/default.d.ts +8 -0
- package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
- package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts +3 -0
- package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -1
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts +8 -1
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/automerge/index.d.ts +1 -1
- package/dist/types/src/hooks/automerge/index.d.ts.map +1 -1
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -1
- package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +5 -0
- package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +6 -0
- package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/translation/index.d.ts +3 -0
- package/dist/types/src/hooks/automerge/translation/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +1 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
- package/dist/types/src/styles/markdown.d.ts +3 -2
- 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 +20 -19
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +140 -31
- package/src/components/TextEditor/TextEditor.tsx +26 -15
- package/src/components/TextEditor/extensions/autocomplete.ts +9 -22
- package/src/components/TextEditor/extensions/basic.ts +2 -1
- package/src/components/TextEditor/extensions/blast.ts +387 -0
- package/src/components/TextEditor/extensions/comments.ts +191 -0
- package/src/components/TextEditor/extensions/demo.ts +69 -0
- package/src/components/TextEditor/extensions/experimental.tsx +1 -0
- package/src/components/TextEditor/extensions/image.ts +67 -0
- package/src/components/TextEditor/extensions/index.ts +6 -0
- package/src/components/TextEditor/extensions/link.ts +26 -25
- package/src/components/TextEditor/extensions/listener.ts +4 -3
- package/src/components/TextEditor/extensions/markdown/bundle.ts +29 -26
- package/src/components/TextEditor/extensions/markdown/highlight.ts +118 -103
- package/src/components/TextEditor/extensions/mention.ts +31 -0
- package/src/components/TextEditor/extensions/table.ts +123 -0
- package/src/components/TextEditor/extensions/tasklist.ts +89 -81
- package/src/components/TextEditor/themes/default.ts +69 -2
- package/src/hooks/automerge/PatchSemaphore.ts +12 -9
- package/src/hooks/automerge/automerge.stories.tsx +3 -2
- package/src/hooks/automerge/index.ts +1 -1
- package/src/hooks/automerge/plugin.ts +1 -0
- package/src/hooks/automerge/{amToCodemirror.ts → translation/automerge-to-codemirror.ts} +10 -2
- package/src/hooks/automerge/{codeMirrorToAm.ts → translation/codemirror-to-automerge.ts} +7 -4
- package/src/hooks/automerge/translation/index.ts +6 -0
- package/src/hooks/useTextModel.ts +13 -11
- package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
- package/src/styles/markdown.ts +14 -8
- package/src/testing/replicator.ts +5 -5
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/amToCodemirror.d.ts +0 -6
- package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts +0 -7
- package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts.map +0 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import {
|
|
7
|
+
type EditorState,
|
|
8
|
+
type Extension,
|
|
9
|
+
type RangeSet,
|
|
10
|
+
RangeSetBuilder,
|
|
11
|
+
StateField,
|
|
12
|
+
type Transaction,
|
|
13
|
+
} from '@codemirror/state';
|
|
14
|
+
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
|
15
|
+
|
|
16
|
+
export type ImageOptions = {};
|
|
17
|
+
|
|
18
|
+
export const image = (options: ImageOptions = {}): Extension => {
|
|
19
|
+
return StateField.define<RangeSet<any>>({
|
|
20
|
+
create: (state) => update(state, options),
|
|
21
|
+
update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
|
|
22
|
+
provide: (field) => EditorView.decorations.from(field),
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const update = (state: EditorState, options: ImageOptions) => {
|
|
27
|
+
const builder = new RangeSetBuilder();
|
|
28
|
+
const cursor = state.selection.main.head;
|
|
29
|
+
|
|
30
|
+
syntaxTree(state).iterate({
|
|
31
|
+
enter: (node) => {
|
|
32
|
+
if (node.name === 'Image') {
|
|
33
|
+
const urlNode = node.node.getChild('URL');
|
|
34
|
+
if (urlNode) {
|
|
35
|
+
const hide = state.readOnly || cursor < node.from || cursor > node.to;
|
|
36
|
+
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
|
37
|
+
builder.add(
|
|
38
|
+
hide ? node.from : node.to,
|
|
39
|
+
node.to,
|
|
40
|
+
Decoration.replace({
|
|
41
|
+
widget: new ImageWidget(url),
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return builder.finish();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
class ImageWidget extends WidgetType {
|
|
53
|
+
constructor(readonly _url: string) {
|
|
54
|
+
super();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override eq(other: WidgetType) {
|
|
58
|
+
return this._url === (other as any as ImageWidget)?._url;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
toDOM(view: EditorView) {
|
|
62
|
+
const img = document.createElement('img');
|
|
63
|
+
img.setAttribute('src', this._url);
|
|
64
|
+
img.setAttribute('class', 'cm-image');
|
|
65
|
+
return img;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
export * from './autocomplete';
|
|
6
6
|
export * from './basic';
|
|
7
|
+
export * from './blast';
|
|
8
|
+
export * from './demo';
|
|
9
|
+
export * from './comments';
|
|
10
|
+
export * from './image';
|
|
7
11
|
export * from './link';
|
|
8
12
|
export * from './listener';
|
|
9
13
|
export * from './markdown';
|
|
14
|
+
export * from './mention';
|
|
15
|
+
export * from './table';
|
|
10
16
|
export * from './tasklist';
|
|
11
17
|
export * from './tooltip';
|
|
@@ -18,13 +18,22 @@ import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
|
|
18
18
|
// https://codemirror.net/try/?c=aW1wb3J0IHsgYmFzaWNTZXR1cCwgRWRpdG9yVmlldyB9IGZyb20gImNvZGVtaXJyb3IiCmltcG9ydCB7IG1hcmtkb3duIH0gZnJvbSAiQGNvZGVtaXJyb3IvbGFuZy1tYXJrZG93biIKaW1wb3J0IHsgc3ludGF4VHJlZSB9IGZyb20gIkBjb2RlbWlycm9yL2xhbmd1YWdlIjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIFJhbmdlU2V0QnVpbGRlciwgU3RhdGVGaWVsZCB9IGZyb20gIkBjb2RlbWlycm9yL3N0YXRlIjsKaW1wb3J0IHsgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSB9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciOwoKY2xhc3MgTGluayBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKHRleHQsIHVybCkgewogICAgc3VwZXIoKQogICAgdGhpcy50ZXh0ID0gdGV4dAogICAgdGhpcy51cmwgPSB1cmwKICB9CiAgZXEob3RoZXIpIHsgcmV0dXJuIHRoaXMudGV4dCA9PSBvdGhlci50ZXh0ICYmIHRoaXMudXJsID09IG90aGVyLnVybCB9CiAgdG9ET00oKSB7CiAgICBsZXQgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKQogICAgbGluay50ZXh0Q29udGVudCA9IHRoaXMudGV4dAogICAgbGluay5ocmVmID0gdGhpcy51cmwKICAgIHJldHVybiBsaW5rOwogIH0KfQoKbGV0IGRlY29yYXRpb25zRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShfLCB0cikgewogICAgY29uc3QgYnVpbGRlciA9IG5ldyBSYW5nZVNldEJ1aWxkZXIoKTsKICAgIGxldCBjdXJzb3IgPSB0ci5zdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkOwogICAgc3ludGF4VHJlZSh0ci5zdGF0ZSkuaXRlcmF0ZSh7CiAgICAgIGVudGVyOiAobm9kZSkgPT4gewogICAgICAgIGlmICgoY3Vyc29yIDwgbm9kZS5mcm9tIHx8IGN1cnNvciA+IG5vZGUudG8pICYmIG5vZGUubmFtZSA9PSAiTGluayIpIHsKICAgICAgICAgIGxldCBtYXJrcyA9IG5vZGUubm9kZS5nZXRDaGlsZHJlbigiTGlua01hcmsiKTsKICAgICAgICAgIGxldCB0ZXh0ID0gbWFya3MubGVuZ3RoID49IDIgPyB0ci5zdGF0ZS5zbGljZURvYyhtYXJrc1swXS50bywgbWFya3NbMV0uZnJvbSkgOiAiIgoKICAgICAgICAgIGxldCB1cmxOb2RlID0gbm9kZS5ub2RlLmdldENoaWxkKCJVUkwiKTsKICAgICAgICAgIGxldCB1cmwgPSB1cmxOb2RlID8gdHIuc3RhdGUuc2xpY2VEb2ModXJsTm9kZS5mcm9tLCB1cmxOb2RlLnRvKSA6ICIiCiAgICAgICAgICBidWlsZGVyLmFkZChub2RlLmZyb20sIG5vZGUudG8sIERlY29yYXRpb24ucmVwbGFjZSh7CiAgICAgICAgICAgIHdpZGdldDogbmV3IExpbmsodGV4dCwgdXJsKSwKICAgICAgICAgIH0pLAogICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfSwKICAgIH0pOwogICAgcmV0dXJuIGJ1aWxkZXIuZmluaXNoKCk7CiAgfSwKICBwcm92aWRlOiAoZikgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGYpLAp9KTsKCmxldCB2aWV3ID0gbmV3IEVkaXRvclZpZXcoewogIGRvYzogIiMgSGVsbG8gV29ybGRcblxuW1Rlc3RdKGh0dHBzOi8vZXhhbXBsZS5jb20pXG5cblxuIiwKICBleHRlbnNpb25zOiBbZGVjb3JhdGlvbnNGaWVsZCwgbWFya2Rvd24oKV0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5LAp9KTsKCnZpZXcuZm9jdXMoKTs=
|
|
19
19
|
// https://discuss.codemirror.net/t/link-widget-is-clicked-only-if-editor-is-out-of-focus/7433
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
export type LinkOptions = {
|
|
22
|
+
link?: boolean;
|
|
23
|
+
onRender?: (el: Element, url: string) => void;
|
|
24
|
+
};
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Creates a state field to replace AST elements with a hyperlink widget.
|
|
28
|
+
* https://codemirror.net/docs/ref/#state.StateField
|
|
29
|
+
*/
|
|
30
|
+
export const link = (options: LinkOptions = {}): Extension => {
|
|
31
|
+
return StateField.define<RangeSet<any>>({
|
|
32
|
+
create: (state) => update(state, options),
|
|
33
|
+
update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
|
|
34
|
+
provide: (field) => EditorView.decorations.from(field),
|
|
35
|
+
});
|
|
36
|
+
};
|
|
28
37
|
|
|
29
38
|
class LinkButton extends WidgetType {
|
|
30
39
|
constructor(
|
|
@@ -77,9 +86,15 @@ class LinkText extends WidgetType {
|
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Range sets provide a data structure that can hold a collection of tagged,
|
|
91
|
+
* possibly overlapping ranges in such a way that they can efficiently be mapped though document changes.
|
|
92
|
+
* https://codemirror.net/docs/ref/#state
|
|
93
|
+
*/
|
|
80
94
|
const update = (state: EditorState, options: LinkOptions) => {
|
|
81
95
|
const builder = new RangeSetBuilder();
|
|
82
96
|
const cursor = state.selection.main.head;
|
|
97
|
+
|
|
83
98
|
syntaxTree(state).iterate({
|
|
84
99
|
enter: (node) => {
|
|
85
100
|
// Check if cursor is inside text.
|
|
@@ -89,6 +104,9 @@ const update = (state: EditorState, options: LinkOptions) => {
|
|
|
89
104
|
|
|
90
105
|
const urlNode = node.node.getChild('URL');
|
|
91
106
|
const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
|
|
107
|
+
if (!url) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
92
110
|
|
|
93
111
|
if (cursor < node.from || cursor > node.to) {
|
|
94
112
|
builder.add(
|
|
@@ -109,28 +127,11 @@ const update = (state: EditorState, options: LinkOptions) => {
|
|
|
109
127
|
}),
|
|
110
128
|
);
|
|
111
129
|
}
|
|
112
|
-
}
|
|
113
130
|
|
|
114
|
-
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
115
133
|
},
|
|
116
134
|
});
|
|
117
135
|
|
|
118
136
|
return builder.finish();
|
|
119
137
|
};
|
|
120
|
-
|
|
121
|
-
export type LinkOptions = {
|
|
122
|
-
link?: boolean;
|
|
123
|
-
onRender?: (el: Element, url: string) => void;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Creates a state field to replace AST elements with a hyperlink widget.
|
|
128
|
-
* https://codemirror.net/docs/ref/#state.StateField
|
|
129
|
-
*/
|
|
130
|
-
export const link = (options: LinkOptions = {}): Extension => {
|
|
131
|
-
return StateField.define<RangeSet<any>>({
|
|
132
|
-
create: (state) => update(state, options),
|
|
133
|
-
update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
|
|
134
|
-
provide: (field) => EditorView.decorations.from(field),
|
|
135
|
-
});
|
|
136
|
-
};
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { StateField } from '@codemirror/state';
|
|
6
6
|
|
|
7
|
-
export type
|
|
7
|
+
export type ListenerOptions = { onChange: (text: string) => void };
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Based on https://github.com/codemirror/dev/issues/44#issuecomment-789093799
|
|
11
11
|
*/
|
|
12
|
-
export const listener = (onChange:
|
|
13
|
-
StateField.define({
|
|
12
|
+
export const listener = ({ onChange }: ListenerOptions) => {
|
|
13
|
+
return StateField.define({
|
|
14
14
|
create: () => null,
|
|
15
15
|
update: (_value, transaction) => {
|
|
16
16
|
if (transaction.docChanged && onChange) {
|
|
@@ -20,3 +20,4 @@ export const listener = (onChange: TextListener) =>
|
|
|
20
20
|
return null;
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
+
};
|
|
@@ -3,17 +3,10 @@
|
|
|
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
|
-
import {
|
|
9
|
-
bracketMatching,
|
|
10
|
-
defaultHighlightStyle,
|
|
11
|
-
foldKeymap,
|
|
12
|
-
indentOnInput,
|
|
13
|
-
syntaxHighlighting,
|
|
14
|
-
} from '@codemirror/language';
|
|
8
|
+
import { bracketMatching, defaultHighlightStyle, indentOnInput, syntaxHighlighting } from '@codemirror/language';
|
|
15
9
|
import { languages } from '@codemirror/language-data';
|
|
16
|
-
import { lintKeymap } from '@codemirror/lint';
|
|
17
10
|
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
|
|
18
11
|
import { EditorState, type Extension } from '@codemirror/state';
|
|
19
12
|
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
@@ -35,11 +28,16 @@ import type { ThemeMode } from '@dxos/react-ui';
|
|
|
35
28
|
import { markdownHighlightStyle, markdownTagsExtension } from './highlight';
|
|
36
29
|
|
|
37
30
|
export type MarkdownBundleOptions = {
|
|
31
|
+
readonly?: boolean;
|
|
38
32
|
themeMode?: ThemeMode;
|
|
39
33
|
placeholder?: string;
|
|
40
34
|
};
|
|
41
35
|
|
|
42
|
-
export const markdownBundle = ({
|
|
36
|
+
export const markdownBundle = ({
|
|
37
|
+
readonly,
|
|
38
|
+
themeMode,
|
|
39
|
+
placeholder: _placeholder,
|
|
40
|
+
}: MarkdownBundleOptions): Extension[] => {
|
|
43
41
|
// All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
|
|
44
42
|
// https://codemirror.net/docs/ref/#codemirror.basicSetup
|
|
45
43
|
return [
|
|
@@ -50,7 +48,6 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
|
|
|
50
48
|
EditorState.allowMultipleSelections.of(true),
|
|
51
49
|
EditorView.lineWrapping,
|
|
52
50
|
|
|
53
|
-
// autocompletion(),
|
|
54
51
|
crosshairCursor(),
|
|
55
52
|
dropCursor(),
|
|
56
53
|
drawSelection(),
|
|
@@ -62,16 +59,7 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
|
|
|
62
59
|
indentOnInput(),
|
|
63
60
|
rectangularSelection(),
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
...closeBracketsKeymap,
|
|
67
|
-
// ...completionKeymap,
|
|
68
|
-
...defaultKeymap,
|
|
69
|
-
...foldKeymap,
|
|
70
|
-
...historyKeymap,
|
|
71
|
-
...lintKeymap,
|
|
72
|
-
...searchKeymap,
|
|
73
|
-
indentWithTab,
|
|
74
|
-
]),
|
|
62
|
+
customkeymap(),
|
|
75
63
|
|
|
76
64
|
// Main extension.
|
|
77
65
|
// https://github.com/codemirror/lang-markdown
|
|
@@ -89,15 +77,30 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
|
|
|
89
77
|
|
|
90
78
|
// Parser extensions.
|
|
91
79
|
extensions: [
|
|
92
|
-
// GFM
|
|
80
|
+
// GFM provided by default.
|
|
93
81
|
markdownTagsExtension,
|
|
94
82
|
],
|
|
95
83
|
}),
|
|
96
84
|
|
|
97
|
-
//
|
|
98
|
-
syntaxHighlighting(markdownHighlightStyle),
|
|
99
|
-
|
|
100
|
-
// TODO(thure): All but one rule here apply to both themes; rename or refactor.
|
|
85
|
+
// https://github.com/codemirror/theme-one-dark
|
|
101
86
|
themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
|
|
87
|
+
|
|
88
|
+
// Custom styles.
|
|
89
|
+
syntaxHighlighting(markdownHighlightStyle(readonly)),
|
|
102
90
|
].filter(Boolean) as Extension[];
|
|
103
91
|
};
|
|
92
|
+
|
|
93
|
+
// https://codemirror.net/docs/ref/#view.keymap
|
|
94
|
+
const customkeymap = () =>
|
|
95
|
+
keymap.of([
|
|
96
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
97
|
+
indentWithTab,
|
|
98
|
+
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
99
|
+
...standardKeymap,
|
|
100
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
101
|
+
...historyKeymap,
|
|
102
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
103
|
+
...closeBracketsKeymap,
|
|
104
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
105
|
+
...searchKeymap,
|
|
106
|
+
]);
|
|
@@ -8,6 +8,8 @@ import { tags, styleTags, Tag } from '@lezer/highlight';
|
|
|
8
8
|
import { type MarkdownConfig } from '@lezer/markdown';
|
|
9
9
|
import get from 'lodash.get';
|
|
10
10
|
|
|
11
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
12
|
+
|
|
11
13
|
import {
|
|
12
14
|
blockquote,
|
|
13
15
|
bold,
|
|
@@ -22,6 +24,11 @@ import {
|
|
|
22
24
|
tokens,
|
|
23
25
|
} from '../../../../styles';
|
|
24
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
|
+
|
|
25
32
|
/**
|
|
26
33
|
* Custom tags defined and processed by the GFM lezer extension.
|
|
27
34
|
* https://github.com/lezer-parser/markdown
|
|
@@ -50,117 +57,125 @@ export const markdownTagsExtension: MarkdownConfig = {
|
|
|
50
57
|
* Styling based on `lezer` parser tags.
|
|
51
58
|
* https://codemirror.net/examples/styling
|
|
52
59
|
* https://github.com/lezer-parser/highlight
|
|
53
|
-
* https://lezer.codemirror.net/docs/ref/#highlight (list of tags)
|
|
60
|
+
* https://lezer.codemirror.net/docs/ref/#highlight.tags (list of tags)
|
|
61
|
+
*
|
|
62
|
+
* Examples:
|
|
63
|
+
* - https://github.com/codemirror/language/blob/main/src/highlight.ts#L194
|
|
64
|
+
* - https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts#L115
|
|
54
65
|
*/
|
|
55
|
-
export const markdownHighlightStyle =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
66
|
+
export const markdownHighlightStyle = (readonly?: boolean) =>
|
|
67
|
+
HighlightStyle.define(
|
|
68
|
+
[
|
|
69
|
+
{
|
|
70
|
+
tag: [
|
|
71
|
+
tags.keyword,
|
|
72
|
+
tags.name,
|
|
73
|
+
tags.deleted,
|
|
74
|
+
tags.character,
|
|
75
|
+
tags.propertyName,
|
|
76
|
+
tags.macroName,
|
|
77
|
+
tags.color,
|
|
78
|
+
tags.constant(tags.name),
|
|
79
|
+
tags.standard(tags.name),
|
|
80
|
+
tags.definition(tags.name),
|
|
81
|
+
tags.separator,
|
|
82
|
+
tags.typeName,
|
|
83
|
+
tags.className,
|
|
84
|
+
tags.number,
|
|
85
|
+
tags.changed,
|
|
86
|
+
tags.annotation,
|
|
87
|
+
tags.modifier,
|
|
88
|
+
tags.self,
|
|
89
|
+
tags.namespace,
|
|
90
|
+
tags.operator,
|
|
91
|
+
tags.operatorKeyword,
|
|
92
|
+
tags.escape,
|
|
93
|
+
tags.regexp,
|
|
94
|
+
tags.special(tags.string),
|
|
95
|
+
tags.meta,
|
|
96
|
+
tags.comment,
|
|
97
|
+
tags.atom,
|
|
98
|
+
tags.bool,
|
|
99
|
+
tags.special(tags.variableName),
|
|
100
|
+
tags.processingInstruction,
|
|
101
|
+
tags.string,
|
|
102
|
+
tags.inserted,
|
|
103
|
+
tags.invalid,
|
|
104
|
+
],
|
|
105
|
+
color: 'inherit !important',
|
|
106
|
+
},
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
markdownTags.QuoteMark,
|
|
108
|
-
],
|
|
109
|
-
class: mark,
|
|
110
|
-
},
|
|
108
|
+
// Markdown marks.
|
|
109
|
+
{
|
|
110
|
+
tag: [
|
|
111
|
+
tags.meta,
|
|
112
|
+
tags.processingInstruction,
|
|
113
|
+
markdownTags.LinkLabel,
|
|
114
|
+
markdownTags.LinkReference,
|
|
115
|
+
markdownTags.ListMark,
|
|
116
|
+
],
|
|
117
|
+
class: mark,
|
|
118
|
+
},
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{ tag: tags.heading5, class: heading[5] },
|
|
118
|
-
{ tag: tags.heading6, class: heading[6] },
|
|
120
|
+
// Markdown marks.
|
|
121
|
+
{
|
|
122
|
+
tag: [markdownTags.CodeMark, markdownTags.HeaderMark, markdownTags.QuoteMark, markdownTags.EmphasisMark],
|
|
123
|
+
class: readonly ? 'hidden' : mark,
|
|
124
|
+
},
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
// E.g., code block language (after ```).
|
|
127
|
+
{
|
|
128
|
+
tag: [tags.function(tags.variableName), tags.labelName],
|
|
129
|
+
class: readonly ? 'hidden' : codeMark,
|
|
130
|
+
},
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
tag:
|
|
128
|
-
class:
|
|
129
|
-
|
|
132
|
+
// Headings.
|
|
133
|
+
{ tag: tags.heading1, class: heading(1, readonly) },
|
|
134
|
+
{ tag: tags.heading2, class: heading(2, readonly) },
|
|
135
|
+
{ tag: tags.heading3, class: heading(3, readonly) },
|
|
136
|
+
{ tag: tags.heading4, class: heading(4, readonly) },
|
|
137
|
+
{ tag: tags.heading5, class: heading(5, readonly) },
|
|
138
|
+
{ tag: tags.heading6, class: heading(6, readonly) },
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
tag:
|
|
134
|
-
class:
|
|
135
|
-
},
|
|
140
|
+
// Emphasis.
|
|
141
|
+
{ tag: tags.emphasis, class: italic },
|
|
142
|
+
{ tag: tags.strong, class: bold },
|
|
143
|
+
{ tag: tags.strikethrough, class: strikethrough },
|
|
136
144
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
tag: [markdownTags.CodeText, markdownTags.InlineCode],
|
|
143
|
-
class: code,
|
|
144
|
-
},
|
|
145
|
+
// Naked URLs.
|
|
146
|
+
{
|
|
147
|
+
tag: [markdownTags.URL],
|
|
148
|
+
class: inlineUrl,
|
|
149
|
+
},
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
// NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
|
|
152
|
+
// However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
|
|
153
|
+
// when a language is specified. In this case, the syntax highlighting extensions will colorize
|
|
154
|
+
// the code, but all other CSS properties will be inherited.
|
|
155
|
+
// IMPORTANT: Therefore, the fenced code block will use the base editor font.
|
|
156
|
+
{
|
|
157
|
+
tag: [markdownTags.CodeText, markdownTags.InlineCode],
|
|
158
|
+
class: code,
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
{
|
|
162
|
+
tag: [markdownTags.QuoteMark],
|
|
163
|
+
class: blockquote,
|
|
164
|
+
},
|
|
150
165
|
|
|
166
|
+
{
|
|
167
|
+
tag: [markdownTags.HorizontalRule],
|
|
168
|
+
class: mx(horizontalRule, readonly && '-indent-[100rem]'),
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// Main content, paragraphs, etc.
|
|
172
|
+
// {
|
|
173
|
+
// tag: [tags.content],
|
|
174
|
+
// fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
175
|
+
// },
|
|
176
|
+
],
|
|
151
177
|
{
|
|
152
|
-
|
|
153
|
-
|
|
178
|
+
scope: markdownLanguage,
|
|
179
|
+
all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
|
|
154
180
|
},
|
|
155
|
-
|
|
156
|
-
// Main content, paragraphs, etc.
|
|
157
|
-
// {
|
|
158
|
-
// tag: [tags.content],
|
|
159
|
-
// fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
160
|
-
// },
|
|
161
|
-
],
|
|
162
|
-
{
|
|
163
|
-
scope: markdownLanguage,
|
|
164
|
-
all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
|
|
165
|
-
},
|
|
166
|
-
);
|
|
181
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { autocompletion, type CompletionContext, type CompletionResult } from '@codemirror/autocomplete';
|
|
6
|
+
import type { Extension } from '@codemirror/state';
|
|
7
|
+
|
|
8
|
+
export type MentionOptions = {
|
|
9
|
+
onSearch: (text: string) => string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const mention = ({ onSearch }: MentionOptions): Extension => {
|
|
13
|
+
return autocompletion({
|
|
14
|
+
closeOnBlur: false, // For debugging.
|
|
15
|
+
override: [
|
|
16
|
+
(context: CompletionContext): CompletionResult | null => {
|
|
17
|
+
const match = context.matchBefore(/@(\w+)?/);
|
|
18
|
+
if (!match || (match.from === match.to && !context.explicit)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(match);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
from: match.from,
|
|
26
|
+
options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({ label: `@${value}` })),
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
};
|