@dxos/react-ui-editor 0.3.10-main.5515dfe → 0.3.10-main.560f82b
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 +1049 -285
- 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/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.d.ts +3 -0
- package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts +16 -0
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
- package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +1 -1
- package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
- 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/index.d.ts +0 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +3 -4
- 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 +137 -31
- package/src/components/TextEditor/TextEditor.tsx +52 -25
- 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 +375 -0
- package/src/components/TextEditor/extensions/comments.ts +190 -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 +68 -0
- package/src/components/TextEditor/extensions/index.ts +6 -0
- package/src/components/TextEditor/extensions/link.ts +25 -25
- package/src/components/TextEditor/extensions/listener.ts +4 -3
- package/src/components/TextEditor/extensions/markdown/bundle.ts +18 -18
- 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/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.ts +12 -9
- package/src/{automerge → hooks/automerge}/automerge.stories.tsx +5 -3
- package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +1 -0
- package/src/{automerge/automerge-plugin/amToCodemirror.ts → hooks/automerge/translation/automerge-to-codemirror.ts} +10 -2
- package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → hooks/automerge/translation/codemirror-to-automerge.ts} +7 -4
- package/src/hooks/automerge/translation/index.ts +6 -0
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useTextModel.ts +49 -41
- package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
- package/src/styles/markdown.ts +14 -8
- package/src/testing/replicator.ts +6 -7
- package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +0 -7
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
- package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
- package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
- package/src/hooks/useCollaboration.ts +0 -45
- package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
- package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
- package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
- package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Extension } from '@codemirror/state';
|
|
6
|
+
import { keymap } from '@codemirror/view';
|
|
7
|
+
|
|
8
|
+
// TODO(burdon): Review https://github.com/sergeche/codemirror-movie?tab=readme-ov-file
|
|
9
|
+
|
|
10
|
+
export type DemoOptions = {
|
|
11
|
+
delay?: number;
|
|
12
|
+
items?: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const defaultItems = ['hello world!', 'this is a test.', 'this is [DXOS](https://dxos.org)'];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Demo script.
|
|
19
|
+
* Configurable plugin that let's user cycle through pre-configured input script.
|
|
20
|
+
*/
|
|
21
|
+
export const demo = ({ delay = 75, items = defaultItems }: DemoOptions = {}): Extension => {
|
|
22
|
+
let t: any;
|
|
23
|
+
let idx = 0;
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
keymap.of([
|
|
27
|
+
{
|
|
28
|
+
// Reset.
|
|
29
|
+
key: "alt-meta-'",
|
|
30
|
+
run: (view) => {
|
|
31
|
+
clearTimeout(t);
|
|
32
|
+
idx = 0;
|
|
33
|
+
return true;
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
// Next prompt.
|
|
38
|
+
// TODO(burdon): Press 1-9 to select prompt?
|
|
39
|
+
key: "shift-meta-'",
|
|
40
|
+
run: (view) => {
|
|
41
|
+
clearTimeout(t);
|
|
42
|
+
// TODO(burdon): Add space if needed.
|
|
43
|
+
const text = items[idx++];
|
|
44
|
+
if (idx === items?.length) {
|
|
45
|
+
idx = 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let i = 0;
|
|
49
|
+
const insert = (d = 0) => {
|
|
50
|
+
t = setTimeout(() => {
|
|
51
|
+
const pos = view.state.selection.main.head;
|
|
52
|
+
view.dispatch({
|
|
53
|
+
changes: { from: pos, insert: text[i++] },
|
|
54
|
+
selection: { anchor: pos + 1 },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (i < text.length) {
|
|
58
|
+
insert(Math.random() * delay * (text[i] === ' ' ? 2 : 1));
|
|
59
|
+
}
|
|
60
|
+
}, d);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
insert();
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
]),
|
|
68
|
+
];
|
|
69
|
+
};
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
// TODO(burdon): Add-on: dialog.
|
|
20
20
|
// TODO(burdon): Comments: https://codemirror.net/5/doc/manual.html#setBookmark
|
|
21
21
|
// TODO(burdon): Split view: https://codemirror.net/examples/split
|
|
22
|
+
// TODO(burdon): https://codemirror.net/5/demo/simplemode.html
|
|
22
23
|
|
|
23
24
|
// https://codemirror.net/examples/autocompletion
|
|
24
25
|
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
|
|
29
|
+
// TODO(burdon): If not-readonly then show text.
|
|
30
|
+
// if (state.readOnly) {
|
|
31
|
+
syntaxTree(state).iterate({
|
|
32
|
+
enter: (node) => {
|
|
33
|
+
if (node.name === 'Image') {
|
|
34
|
+
const urlNode = node.node.getChild('URL');
|
|
35
|
+
if (urlNode) {
|
|
36
|
+
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
|
37
|
+
builder.add(
|
|
38
|
+
state.readOnly ? node.from : node.to,
|
|
39
|
+
node.to,
|
|
40
|
+
Decoration.widget({
|
|
41
|
+
widget: new ImageWidget(url),
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
// }
|
|
49
|
+
|
|
50
|
+
return builder.finish();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
class ImageWidget extends WidgetType {
|
|
54
|
+
constructor(readonly _url: string) {
|
|
55
|
+
super();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override eq(other: WidgetType) {
|
|
59
|
+
return this._url === (other as any as ImageWidget)?._url;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
toDOM(view: EditorView) {
|
|
63
|
+
const img = document.createElement('img');
|
|
64
|
+
img.setAttribute('src', this._url);
|
|
65
|
+
img.setAttribute('class', 'cm-image');
|
|
66
|
+
return img;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -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,6 +86,11 @@ 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;
|
|
@@ -89,6 +103,9 @@ const update = (state: EditorState, options: LinkOptions) => {
|
|
|
89
103
|
|
|
90
104
|
const urlNode = node.node.getChild('URL');
|
|
91
105
|
const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
|
|
106
|
+
if (!url) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
92
109
|
|
|
93
110
|
if (cursor < node.from || cursor > node.to) {
|
|
94
111
|
builder.add(
|
|
@@ -109,28 +126,11 @@ const update = (state: EditorState, options: LinkOptions) => {
|
|
|
109
126
|
}),
|
|
110
127
|
);
|
|
111
128
|
}
|
|
112
|
-
}
|
|
113
129
|
|
|
114
|
-
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
115
132
|
},
|
|
116
133
|
});
|
|
117
134
|
|
|
118
135
|
return builder.finish();
|
|
119
136
|
};
|
|
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
|
+
};
|
|
@@ -5,15 +5,8 @@
|
|
|
5
5
|
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
|
6
6
|
import { defaultKeymap, history, historyKeymap, indentWithTab } 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,14 +59,17 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
|
|
|
62
59
|
indentOnInput(),
|
|
63
60
|
rectangularSelection(),
|
|
64
61
|
|
|
62
|
+
// https://codemirror.net/docs/ref/#view.keymap
|
|
65
63
|
keymap.of([
|
|
66
|
-
|
|
67
|
-
// ...completionKeymap,
|
|
64
|
+
// https://codemirror.net/docs/ref/#commands.defaultKeymap
|
|
68
65
|
...defaultKeymap,
|
|
69
|
-
|
|
66
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
70
67
|
...historyKeymap,
|
|
71
|
-
|
|
68
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
69
|
+
...closeBracketsKeymap,
|
|
70
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
72
71
|
...searchKeymap,
|
|
72
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
73
73
|
indentWithTab,
|
|
74
74
|
]),
|
|
75
75
|
|
|
@@ -94,10 +94,10 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
|
|
|
94
94
|
],
|
|
95
95
|
}),
|
|
96
96
|
|
|
97
|
-
//
|
|
98
|
-
syntaxHighlighting(markdownHighlightStyle),
|
|
99
|
-
|
|
100
|
-
// TODO(thure): All but one rule here apply to both themes; rename or refactor.
|
|
97
|
+
// https://github.com/codemirror/theme-one-dark
|
|
101
98
|
themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
|
|
99
|
+
|
|
100
|
+
// Custom styles.
|
|
101
|
+
syntaxHighlighting(markdownHighlightStyle(readonly)),
|
|
102
102
|
].filter(Boolean) as Extension[];
|
|
103
103
|
};
|
|
@@ -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
|
+
};
|