@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.
Files changed (91) hide show
  1. package/dist/lib/browser/index.mjs +1006 -219
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +22 -1
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +3 -1
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +3 -2
  9. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +2 -1
  11. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -1
  12. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  13. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +18 -0
  15. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  16. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +11 -0
  17. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -0
  18. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -1
  19. package/dist/types/src/components/TextEditor/extensions/image.d.ts +4 -0
  20. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +1 -0
  21. package/dist/types/src/components/TextEditor/extensions/index.d.ts +6 -0
  22. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  23. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -1
  24. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +4 -2
  25. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -1
  26. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +2 -1
  27. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  28. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +6 -2
  29. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
  30. package/dist/types/src/components/TextEditor/extensions/mention.d.ts +6 -0
  31. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +1 -0
  32. package/dist/types/src/components/TextEditor/extensions/table.d.ts +8 -0
  33. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +1 -0
  34. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +2 -4
  35. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -1
  36. package/dist/types/src/components/TextEditor/themes/default.d.ts +8 -0
  37. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
  38. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts +3 -0
  39. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -1
  40. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +8 -1
  41. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -1
  42. package/dist/types/src/hooks/automerge/index.d.ts +1 -1
  43. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -1
  44. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -1
  45. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +5 -0
  46. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +1 -0
  47. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +6 -0
  48. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +1 -0
  49. package/dist/types/src/hooks/automerge/translation/index.d.ts +3 -0
  50. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +1 -0
  51. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  52. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +1 -1
  53. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  54. package/dist/types/src/styles/markdown.d.ts +3 -2
  55. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  56. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  57. package/package.json +20 -19
  58. package/src/components/TextEditor/MarkdownEditor.stories.tsx +140 -31
  59. package/src/components/TextEditor/TextEditor.tsx +26 -15
  60. package/src/components/TextEditor/extensions/autocomplete.ts +9 -22
  61. package/src/components/TextEditor/extensions/basic.ts +2 -1
  62. package/src/components/TextEditor/extensions/blast.ts +387 -0
  63. package/src/components/TextEditor/extensions/comments.ts +191 -0
  64. package/src/components/TextEditor/extensions/demo.ts +69 -0
  65. package/src/components/TextEditor/extensions/experimental.tsx +1 -0
  66. package/src/components/TextEditor/extensions/image.ts +67 -0
  67. package/src/components/TextEditor/extensions/index.ts +6 -0
  68. package/src/components/TextEditor/extensions/link.ts +26 -25
  69. package/src/components/TextEditor/extensions/listener.ts +4 -3
  70. package/src/components/TextEditor/extensions/markdown/bundle.ts +29 -26
  71. package/src/components/TextEditor/extensions/markdown/highlight.ts +118 -103
  72. package/src/components/TextEditor/extensions/mention.ts +31 -0
  73. package/src/components/TextEditor/extensions/table.ts +123 -0
  74. package/src/components/TextEditor/extensions/tasklist.ts +89 -81
  75. package/src/components/TextEditor/themes/default.ts +69 -2
  76. package/src/hooks/automerge/PatchSemaphore.ts +12 -9
  77. package/src/hooks/automerge/automerge.stories.tsx +3 -2
  78. package/src/hooks/automerge/index.ts +1 -1
  79. package/src/hooks/automerge/plugin.ts +1 -0
  80. package/src/hooks/automerge/{amToCodemirror.ts → translation/automerge-to-codemirror.ts} +10 -2
  81. package/src/hooks/automerge/{codeMirrorToAm.ts → translation/codemirror-to-automerge.ts} +7 -4
  82. package/src/hooks/automerge/translation/index.ts +6 -0
  83. package/src/hooks/useTextModel.ts +13 -11
  84. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
  85. package/src/styles/markdown.ts +14 -8
  86. package/src/testing/replicator.ts +5 -5
  87. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  88. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts +0 -6
  89. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +0 -1
  90. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts +0 -7
  91. 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
- // TODO(burdon): Adapt to make all markdown editable?
21
+ export type LinkOptions = {
22
+ link?: boolean;
23
+ onRender?: (el: Element, url: string) => void;
24
+ };
22
25
 
23
- // TODO(burdon): Research overlays.
24
- // https://stackoverflow.com/questions/26933344/codemirror-detect-and-create-links-inside-editor
25
- // https://codemirror.net/5/doc/manual.html#addOverlay
26
- // https://codemirror.net/5/demo/simplemode.html
27
- // https://codemirror.net/5/demo/search.html (Search dialog).
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
- return true;
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 TextListener = (text: string) => void;
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: TextListener) =>
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 { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
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 = ({ themeMode, placeholder: _placeholder }: MarkdownBundleOptions): Extension[] => {
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
- keymap.of([
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, // TODO(burdon): Provided by default?
80
+ // GFM provided by default.
93
81
  markdownTagsExtension,
94
82
  ],
95
83
  }),
96
84
 
97
- // Custom styles.
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 = HighlightStyle.define(
56
- [
57
- {
58
- tag: [
59
- tags.keyword,
60
- tags.name,
61
- tags.deleted,
62
- tags.character,
63
- tags.propertyName,
64
- tags.macroName,
65
- tags.color,
66
- tags.constant(tags.name),
67
- tags.standard(tags.name),
68
- tags.definition(tags.name),
69
- tags.separator,
70
- tags.typeName,
71
- tags.className,
72
- tags.number,
73
- tags.changed,
74
- tags.annotation,
75
- tags.modifier,
76
- tags.self,
77
- tags.namespace,
78
- tags.operator,
79
- tags.operatorKeyword,
80
- tags.escape,
81
- tags.regexp,
82
- tags.special(tags.string),
83
- tags.meta,
84
- tags.comment,
85
- tags.atom,
86
- tags.bool,
87
- tags.special(tags.variableName),
88
- tags.processingInstruction,
89
- tags.string,
90
- tags.inserted,
91
- tags.invalid,
92
- ],
93
- color: 'inherit !important',
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
- // Markdown marks.
97
- {
98
- tag: [
99
- tags.meta,
100
- tags.processingInstruction,
101
- markdownTags.CodeMark,
102
- markdownTags.EmphasisMark,
103
- markdownTags.HeaderMark,
104
- markdownTags.LinkLabel,
105
- markdownTags.LinkReference,
106
- markdownTags.ListMark,
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
- // Headings.
113
- { tag: tags.heading1, class: heading[1] },
114
- { tag: tags.heading2, class: heading[2] },
115
- { tag: tags.heading3, class: heading[3] },
116
- { tag: tags.heading4, class: heading[4] },
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
- // Emphasis.
121
- { tag: tags.emphasis, class: italic },
122
- { tag: tags.strong, class: bold },
123
- { tag: tags.strikethrough, class: strikethrough },
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
- // Naked URLs.
126
- {
127
- tag: [markdownTags.URL],
128
- class: inlineUrl,
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
- // E.g., code block language (after ```).
132
- {
133
- tag: [tags.function(tags.variableName), tags.labelName],
134
- class: codeMark,
135
- },
140
+ // Emphasis.
141
+ { tag: tags.emphasis, class: italic },
142
+ { tag: tags.strong, class: bold },
143
+ { tag: tags.strikethrough, class: strikethrough },
136
144
 
137
- // The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
138
- // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
139
- // when a language is specified. In this case, the syntax highlighting extensions will colorize
140
- // the code, but all other CSS properties will be inherited.
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
- tag: [markdownTags.QuoteMark],
148
- class: blockquote,
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
- tag: [markdownTags.HorizontalRule],
153
- class: horizontalRule,
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
+ };