@dxos/react-ui-editor 0.3.10-main.8fd45da → 0.3.10-main.92d1f2c

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 (87) hide show
  1. package/dist/lib/browser/index.mjs +986 -296
  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 +19 -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 +2 -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.map +1 -1
  9. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +2 -1
  10. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/extensions/blast.d.ts +25 -0
  12. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +1 -0
  13. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +10 -6
  14. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/extensions/demo.d.ts +11 -0
  16. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +1 -0
  17. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +1 -1
  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 +5 -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 +9 -3
  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 -1
  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 +5 -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/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +1 -1
  52. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  53. package/dist/types/src/styles/markdown.d.ts +3 -2
  54. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  55. package/package.json +20 -19
  56. package/src/components/TextEditor/MarkdownEditor.stories.tsx +106 -29
  57. package/src/components/TextEditor/TextEditor.tsx +12 -8
  58. package/src/components/TextEditor/extensions/autocomplete.ts +5 -20
  59. package/src/components/TextEditor/extensions/basic.ts +2 -1
  60. package/src/components/TextEditor/extensions/blast.ts +387 -0
  61. package/src/components/TextEditor/extensions/comments.ts +59 -60
  62. package/src/components/TextEditor/extensions/demo.ts +69 -0
  63. package/src/components/TextEditor/extensions/experimental.tsx +14 -35
  64. package/src/components/TextEditor/extensions/image.ts +67 -0
  65. package/src/components/TextEditor/extensions/index.ts +5 -0
  66. package/src/components/TextEditor/extensions/link.ts +17 -25
  67. package/src/components/TextEditor/extensions/listener.ts +4 -4
  68. package/src/components/TextEditor/extensions/markdown/bundle.ts +30 -19
  69. package/src/components/TextEditor/extensions/markdown/highlight.ts +147 -108
  70. package/src/components/TextEditor/extensions/mention.ts +31 -0
  71. package/src/components/TextEditor/extensions/table.ts +122 -0
  72. package/src/components/TextEditor/extensions/tasklist.ts +41 -27
  73. package/src/components/TextEditor/themes/default.ts +54 -2
  74. package/src/hooks/automerge/PatchSemaphore.ts +12 -9
  75. package/src/hooks/automerge/automerge.stories.tsx +3 -2
  76. package/src/hooks/automerge/index.ts +1 -1
  77. package/src/hooks/automerge/plugin.ts +1 -0
  78. package/src/hooks/automerge/{amToCodemirror.ts → translation/automerge-to-codemirror.ts} +10 -2
  79. package/src/hooks/automerge/{codeMirrorToAm.ts → translation/codemirror-to-automerge.ts} +7 -4
  80. package/src/hooks/automerge/translation/index.ts +6 -0
  81. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +1 -1
  82. package/src/styles/markdown.ts +14 -8
  83. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  84. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts +0 -6
  85. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +0 -1
  86. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts +0 -7
  87. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts.map +0 -1
@@ -13,28 +13,21 @@ import {
13
13
  type ViewUpdate,
14
14
  } from '@codemirror/view';
15
15
 
16
- // TODO(burdon): Modes: parallel parsing and decoration (e.g., associated with language).
17
- // TODO(burdon): Add-on: runmode: run lexer over content (with rendering codemirror).
18
- // https://codemirror.net/5/doc/manual.html#addon_runmode
19
- // TODO(burdon): Add-on: dialog.
20
- // TODO(burdon): Comments: https://codemirror.net/5/doc/manual.html#setBookmark
21
- // TODO(burdon): Split view: https://codemirror.net/examples/split
22
- // TODO(burdon): https://codemirror.net/5/demo/simplemode.html
23
-
24
- // https://codemirror.net/examples/autocompletion
25
- // https://codemirror.net/docs/ref/#autocomplete.autocompletion
26
- // https://codemirror.net/docs/ref/#autocomplete.Completion
27
-
28
- // TODO(burdon): Hint to customize?
29
- // https://codemirror.net/examples/autocompletion
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];
@@ -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,9 +4,14 @@
4
4
 
5
5
  export * from './autocomplete';
6
6
  export * from './basic';
7
+ export * from './blast';
8
+ export * from './demo';
7
9
  export * from './comments';
10
+ export * from './image';
8
11
  export * from './link';
9
12
  export * from './listener';
10
13
  export * from './markdown';
14
+ export * from './mention';
15
+ export * from './table';
11
16
  export * from './tasklist';
12
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(
@@ -85,20 +94,20 @@ class LinkText extends WidgetType {
85
94
  const update = (state: EditorState, options: LinkOptions) => {
86
95
  const builder = new RangeSetBuilder();
87
96
  const cursor = state.selection.main.head;
97
+
88
98
  syntaxTree(state).iterate({
89
99
  enter: (node) => {
90
100
  // Check if cursor is inside text.
91
101
  if (node.name === 'Link') {
92
102
  const marks = node.node.getChildren('LinkMark');
93
103
  const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
94
-
95
104
  const urlNode = node.node.getChild('URL');
96
105
  const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
97
106
  if (!url) {
98
107
  return false;
99
108
  }
100
109
 
101
- if (cursor < node.from || cursor > node.to) {
110
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
102
111
  builder.add(
103
112
  node.from,
104
113
  node.to,
@@ -125,20 +134,3 @@ const update = (state: EditorState, options: LinkOptions) => {
125
134
 
126
135
  return builder.finish();
127
136
  };
128
-
129
- export type LinkOptions = {
130
- link?: boolean;
131
- onRender?: (el: Element, url: string) => void;
132
- };
133
-
134
- /**
135
- * Creates a state field to replace AST elements with a hyperlink widget.
136
- * https://codemirror.net/docs/ref/#state.StateField
137
- */
138
- export const link = (options: LinkOptions = {}): Extension => {
139
- return StateField.define<RangeSet<any>>({
140
- create: (state) => update(state, options),
141
- update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
142
- provide: (field) => EditorView.decorations.from(field),
143
- });
144
- };
@@ -4,14 +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
- // TODO(burdon): Expose options.
13
- export const listener = (onChange: TextListener) =>
14
- StateField.define({
12
+ export const listener = ({ onChange }: ListenerOptions) => {
13
+ return StateField.define({
15
14
  create: () => null,
16
15
  update: (_value, transaction) => {
17
16
  if (transaction.docChanged && onChange) {
@@ -21,3 +20,4 @@ export const listener = (onChange: TextListener) =>
21
20
  return null;
22
21
  },
23
22
  });
23
+ };
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
6
- import { defaultKeymap, history, indentWithTab } from '@codemirror/commands';
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';
@@ -25,14 +25,19 @@ import {
25
25
 
26
26
  import type { ThemeMode } from '@dxos/react-ui';
27
27
 
28
- import { markdownHighlightStyle, markdownTagsExtension } from './highlight';
28
+ import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
29
29
 
30
30
  export type MarkdownBundleOptions = {
31
+ readonly?: boolean;
31
32
  themeMode?: ThemeMode;
32
33
  placeholder?: string;
33
34
  };
34
35
 
35
- export const markdownBundle = ({ themeMode, placeholder: _placeholder }: MarkdownBundleOptions): Extension[] => {
36
+ export const markdownBundle = ({
37
+ readonly,
38
+ themeMode,
39
+ placeholder: _placeholder,
40
+ }: MarkdownBundleOptions): Extension[] => {
36
41
  // All of https://github.com/codemirror/basic-setup minus line numbers and fold gutter.
37
42
  // https://codemirror.net/docs/ref/#codemirror.basicSetup
38
43
  return [
@@ -54,16 +59,7 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
54
59
  indentOnInput(),
55
60
  rectangularSelection(),
56
61
 
57
- // TODO(burdon): Review.
58
- keymap.of([
59
- ...closeBracketsKeymap,
60
- ...defaultKeymap,
61
- // ...foldKeymap,
62
- // ...historyKeymap,
63
- // ...lintKeymap,
64
- ...searchKeymap,
65
- indentWithTab,
66
- ]),
62
+ customkeymap(),
67
63
 
68
64
  // Main extension.
69
65
  // https://github.com/codemirror/lang-markdown
@@ -81,15 +77,30 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
81
77
 
82
78
  // Parser extensions.
83
79
  extensions: [
84
- // GFM, // TODO(burdon): Provided by default?
85
- markdownTagsExtension,
80
+ // GFM provided by default.
81
+ markdownTagsExtensions,
86
82
  ],
87
83
  }),
88
84
 
89
- // Custom styles.
90
- syntaxHighlighting(markdownHighlightStyle),
91
-
92
- // TODO(thure): All but one rule here apply to both themes; rename or refactor.
85
+ // https://github.com/codemirror/theme-one-dark
93
86
  themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
87
+
88
+ // Custom styles.
89
+ syntaxHighlighting(markdownHighlightStyle(readonly)),
94
90
  ].filter(Boolean) as Extension[];
95
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
+ ]);
@@ -5,9 +5,11 @@
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
+ import { mx } from '@dxos/react-ui-theme';
12
+
11
13
  import {
12
14
  blockquote,
13
15
  bold,
@@ -40,128 +42,165 @@ export const markdownTags = {
40
42
  ListMark: Tag.define(),
41
43
  QuoteMark: Tag.define(),
42
44
  URL: Tag.define(),
43
- };
44
45
 
45
- export const markdownTagsExtension: MarkdownConfig = {
46
- props: [styleTags(markdownTags)],
46
+ // Custom.
47
+ TableCell: Tag.define(),
47
48
  };
48
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
+
49
68
  /**
50
69
  * Styling based on `lezer` parser tags.
51
70
  * https://codemirror.net/examples/styling
52
71
  * https://github.com/lezer-parser/highlight
53
- * https://lezer.codemirror.net/docs/ref/#highlight (list of tags)
72
+ * https://github.com/lezer-parser/highlight/blob/main/src/highlight.ts#L427
73
+ * https://lezer.codemirror.net/docs/ref/#highlight.tags (list of tags)
74
+ *
75
+ * Examples:
76
+ * - https://github.com/codemirror/language/blob/main/src/highlight.ts#L194
77
+ * - https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts#L115
54
78
  */
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
- },
79
+ export const markdownHighlightStyle = (readonly?: boolean) => {
80
+ return HighlightStyle.define(
81
+ [
82
+ {
83
+ tag: [
84
+ tags.keyword,
85
+ tags.name,
86
+ tags.deleted,
87
+ tags.character,
88
+ tags.propertyName,
89
+ tags.macroName,
90
+ tags.color,
91
+ tags.constant(tags.name),
92
+ tags.standard(tags.name),
93
+ tags.definition(tags.name),
94
+ tags.separator,
95
+ tags.typeName,
96
+ tags.className,
97
+ tags.number,
98
+ tags.changed,
99
+ tags.annotation,
100
+ tags.modifier,
101
+ tags.self,
102
+ tags.namespace,
103
+ tags.operator,
104
+ tags.operatorKeyword,
105
+ tags.escape,
106
+ tags.regexp,
107
+ tags.special(tags.string),
108
+ tags.meta,
109
+ tags.comment,
110
+ tags.atom,
111
+ tags.bool,
112
+ tags.special(tags.variableName),
113
+ tags.processingInstruction,
114
+ tags.string,
115
+ tags.inserted,
116
+ tags.invalid,
117
+ ],
118
+ color: 'inherit !important',
119
+ },
95
120
 
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
- },
121
+ // Markdown marks.
122
+ {
123
+ tag: [
124
+ tags.meta,
125
+ tags.processingInstruction,
126
+ markdownTags.LinkLabel,
127
+ markdownTags.LinkReference,
128
+ markdownTags.ListMark,
129
+ ],
130
+ class: mark,
131
+ },
111
132
 
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] },
133
+ // Markdown marks.
134
+ {
135
+ tag: [markdownTags.CodeMark, markdownTags.HeaderMark, markdownTags.QuoteMark, markdownTags.EmphasisMark],
136
+ class: readonly ? 'hidden' : mark,
137
+ },
119
138
 
120
- // Emphasis.
121
- { tag: tags.emphasis, class: italic },
122
- { tag: tags.strong, class: bold },
123
- { tag: tags.strikethrough, class: strikethrough },
139
+ // E.g., code block language (after ```).
140
+ {
141
+ tag: [tags.function(tags.variableName), tags.labelName],
142
+ class: readonly ? 'hidden' : codeMark,
143
+ },
124
144
 
125
- // Naked URLs.
126
- {
127
- tag: [markdownTags.URL],
128
- class: inlineUrl,
129
- },
145
+ {
146
+ tag: [tags.monospace],
147
+ class: 'font-mono',
148
+ },
130
149
 
131
- // E.g., code block language (after ```).
132
- {
133
- tag: [tags.function(tags.variableName), tags.labelName],
134
- class: codeMark,
135
- },
150
+ // Headings.
151
+ { tag: tags.heading1, class: heading(1, readonly) },
152
+ { tag: tags.heading2, class: heading(2, readonly) },
153
+ { tag: tags.heading3, class: heading(3, readonly) },
154
+ { tag: tags.heading4, class: heading(4, readonly) },
155
+ { tag: tags.heading5, class: heading(5, readonly) },
156
+ { tag: tags.heading6, class: heading(6, readonly) },
136
157
 
137
- // NOTE: 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
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
142
- {
143
- tag: [markdownTags.CodeText, markdownTags.InlineCode],
144
- class: code,
145
- },
158
+ // Emphasis.
159
+ { tag: tags.emphasis, class: italic },
160
+ { tag: tags.strong, class: bold },
161
+ { tag: tags.strikethrough, class: strikethrough },
146
162
 
147
- {
148
- tag: [markdownTags.QuoteMark],
149
- class: blockquote,
150
- },
163
+ // Naked URLs.
164
+ {
165
+ tag: [markdownTags.URL],
166
+ class: inlineUrl,
167
+ },
168
+
169
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
170
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
171
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
172
+ // the code, but all other CSS properties will be inherited.
173
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
174
+ {
175
+ tag: [markdownTags.CodeText, markdownTags.InlineCode],
176
+ class: code,
177
+ },
178
+
179
+ {
180
+ tag: [markdownTags.QuoteMark],
181
+ class: blockquote,
182
+ },
151
183
 
184
+ {
185
+ tag: [markdownTags.HorizontalRule],
186
+ class: mx(horizontalRule, readonly && '-indent-[100rem]'),
187
+ },
188
+
189
+ // TODO(burdon): Only if being edited.
190
+ {
191
+ tag: [markdownTags.TableCell],
192
+ class: 'font-mono',
193
+ },
194
+
195
+ // Main content, paragraphs, etc.
196
+ // {
197
+ // tag: [tags.content],
198
+ // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
199
+ // },
200
+ ],
152
201
  {
153
- tag: [markdownTags.HorizontalRule],
154
- class: horizontalRule,
202
+ scope: markdownLanguage,
203
+ all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
155
204
  },
156
-
157
- // Main content, paragraphs, etc.
158
- // {
159
- // tag: [tags.content],
160
- // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
161
- // },
162
- ],
163
- {
164
- scope: markdownLanguage,
165
- all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
166
- },
167
- );
205
+ );
206
+ };
@@ -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
+ };