@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.0e40963

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 (169) hide show
  1. package/dist/lib/browser/index.mjs +2226 -971
  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 +63 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +9 -4
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +12 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/index.d.ts +1 -1
  11. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  12. package/dist/types/src/extensions/autocomplete.d.ts +10 -0
  13. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  14. package/dist/types/src/{components/TextEditor/extensions/basic/bundle.d.ts → extensions/basic.d.ts} +3 -2
  15. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  16. package/dist/types/src/extensions/blast.d.ts +25 -0
  17. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  18. package/dist/types/src/extensions/code.d.ts +2 -0
  19. package/dist/types/src/extensions/code.d.ts.map +1 -0
  20. package/dist/types/src/extensions/comments.d.ts +48 -0
  21. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  22. package/dist/types/src/extensions/hr.d.ts +2 -0
  23. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  24. package/dist/types/src/extensions/image.d.ts +4 -0
  25. package/dist/types/src/extensions/image.d.ts.map +1 -0
  26. package/dist/types/src/extensions/index.d.ts +16 -0
  27. package/dist/types/src/extensions/index.d.ts.map +1 -0
  28. package/dist/types/src/extensions/link.d.ts +12 -0
  29. package/dist/types/src/extensions/link.d.ts.map +1 -0
  30. package/dist/types/src/extensions/listener.d.ts +10 -0
  31. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  32. package/dist/types/src/extensions/markdown/bundle.d.ts +17 -0
  33. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  34. package/dist/types/src/extensions/markdown/highlight.d.ts +36 -0
  35. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  36. package/dist/types/src/extensions/markdown/index.d.ts +3 -0
  37. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  38. package/dist/types/src/extensions/mention.d.ts +6 -0
  39. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  40. package/dist/types/src/extensions/mermaid.d.ts +3 -0
  41. package/dist/types/src/extensions/mermaid.d.ts.map +1 -0
  42. package/dist/types/src/extensions/table.d.ts +8 -0
  43. package/dist/types/src/extensions/table.d.ts.map +1 -0
  44. package/dist/types/src/extensions/tasklist.d.ts +3 -0
  45. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  46. package/dist/types/src/extensions/typewriter.d.ts +10 -0
  47. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  48. package/dist/types/src/extensions/util.d.ts +5 -0
  49. package/dist/types/src/extensions/util.d.ts.map +1 -0
  50. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +15 -0
  51. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  52. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  53. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +1 -1
  54. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  55. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  56. package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → hooks/automerge/semaphore.d.ts} +4 -1
  57. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
  58. package/dist/types/src/{automerge/automerge-plugin/codeMirrorToAm.d.ts → hooks/automerge/update-automerge.d.ts} +2 -3
  59. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
  60. package/dist/types/src/hooks/automerge/update-codemirror.d.ts +5 -0
  61. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
  62. package/dist/types/src/hooks/index.d.ts +0 -1
  63. package/dist/types/src/hooks/index.d.ts.map +1 -1
  64. package/dist/types/src/hooks/useTextModel.d.ts +19 -5
  65. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  66. package/dist/types/src/{components/TextEditor → hooks/yjs}/yjs.stories.d.ts +2 -1
  67. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +1 -0
  68. package/dist/types/src/hooks/yjs/yjs.test.d.ts +2 -0
  69. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
  70. package/dist/types/src/index.d.ts +3 -0
  71. package/dist/types/src/index.d.ts.map +1 -1
  72. package/dist/types/src/styles/markdown.d.ts +14 -11
  73. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  74. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  75. package/dist/types/src/themes/default.d.ts +17 -0
  76. package/dist/types/src/themes/default.d.ts.map +1 -0
  77. package/dist/types/src/themes/index.d.ts +2 -0
  78. package/dist/types/src/themes/index.d.ts.map +1 -0
  79. package/package.json +28 -33
  80. package/src/components/TextEditor/MarkdownEditor.stories.tsx +426 -0
  81. package/src/components/TextEditor/TextEditor.stories.tsx +57 -43
  82. package/src/components/TextEditor/TextEditor.tsx +98 -52
  83. package/src/components/TextEditor/index.ts +1 -1
  84. package/src/extensions/autocomplete.ts +59 -0
  85. package/src/{components/TextEditor/extensions/basic/bundle.ts → extensions/basic.ts} +8 -4
  86. package/src/extensions/blast.ts +373 -0
  87. package/src/extensions/code.ts +99 -0
  88. package/src/extensions/comments.ts +391 -0
  89. package/src/extensions/hr.ts +74 -0
  90. package/src/extensions/image.ts +69 -0
  91. package/src/extensions/index.ts +19 -0
  92. package/src/extensions/link.ts +192 -0
  93. package/src/extensions/listener.ts +35 -0
  94. package/src/extensions/markdown/bundle.ts +106 -0
  95. package/src/extensions/markdown/highlight.ts +204 -0
  96. package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +1 -1
  97. package/src/extensions/mention.ts +31 -0
  98. package/src/extensions/mermaid.ts +11 -0
  99. package/src/extensions/table.ts +132 -0
  100. package/src/extensions/tasklist.ts +121 -0
  101. package/src/extensions/typewriter.ts +68 -0
  102. package/src/extensions/util.ts +18 -0
  103. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +7 -5
  104. package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +2 -1
  105. package/src/{automerge/automerge-plugin/PatchSemaphore.ts → hooks/automerge/semaphore.ts} +13 -9
  106. package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → hooks/automerge/update-automerge.ts} +5 -2
  107. package/src/{automerge/automerge-plugin/amToCodemirror.ts → hooks/automerge/update-codemirror.ts} +4 -1
  108. package/src/hooks/index.ts +0 -1
  109. package/src/hooks/useTextModel.ts +102 -42
  110. package/src/{components/TextEditor → hooks/yjs}/yjs.stories.tsx +2 -1
  111. package/src/hooks/yjs/yjs.test.ts +59 -0
  112. package/src/index.ts +3 -0
  113. package/src/styles/markdown.ts +33 -24
  114. package/src/testing/replicator.ts +6 -6
  115. package/src/themes/default.ts +302 -0
  116. package/src/themes/index.ts +5 -0
  117. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  118. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
  119. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  120. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  121. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  122. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  123. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  124. package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
  125. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  126. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  127. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  128. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  129. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  130. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  131. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  132. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  133. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  134. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  135. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  136. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  137. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
  138. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  139. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  140. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  141. package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -5
  142. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  143. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -8
  144. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  145. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
  146. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  147. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  148. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  149. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  150. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  151. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  152. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  153. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  154. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  155. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  156. package/src/components/TextEditor/extensions/change.ts +0 -22
  157. package/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts +0 -99
  158. package/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx +0 -61
  159. package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
  160. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  161. package/src/components/TextEditor/extensions/index.ts +0 -8
  162. package/src/components/TextEditor/extensions/markdown/bundle.ts +0 -99
  163. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  164. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  165. package/src/hooks/useCollaboration.ts +0 -45
  166. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  167. package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  168. package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  169. package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +1 -1
@@ -0,0 +1,192 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ // Copyright CodeMirror
4
+ //
5
+
6
+ import { syntaxTree } from '@codemirror/language';
7
+ import {
8
+ type EditorState,
9
+ type Extension,
10
+ type RangeSet,
11
+ RangeSetBuilder,
12
+ StateField,
13
+ type Transaction,
14
+ } from '@codemirror/state';
15
+ import { Decoration, EditorView, WidgetType, hoverTooltip } from '@codemirror/view';
16
+
17
+ import { tooltipContent } from '@dxos/react-ui-theme';
18
+
19
+ const markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
20
+
21
+ // Adapted from:
22
+ // https://codemirror.net/try/?c=aW1wb3J0IHsgYmFzaWNTZXR1cCwgRWRpdG9yVmlldyB9IGZyb20gImNvZGVtaXJyb3IiCmltcG9ydCB7IG1hcmtkb3duIH0gZnJvbSAiQGNvZGVtaXJyb3IvbGFuZy1tYXJrZG93biIKaW1wb3J0IHsgc3ludGF4VHJlZSB9IGZyb20gIkBjb2RlbWlycm9yL2xhbmd1YWdlIjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIFJhbmdlU2V0QnVpbGRlciwgU3RhdGVGaWVsZCB9IGZyb20gIkBjb2RlbWlycm9yL3N0YXRlIjsKaW1wb3J0IHsgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSB9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciOwoKY2xhc3MgTGluayBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKHRleHQsIHVybCkgewogICAgc3VwZXIoKQogICAgdGhpcy50ZXh0ID0gdGV4dAogICAgdGhpcy51cmwgPSB1cmwKICB9CiAgZXEob3RoZXIpIHsgcmV0dXJuIHRoaXMudGV4dCA9PSBvdGhlci50ZXh0ICYmIHRoaXMudXJsID09IG90aGVyLnVybCB9CiAgdG9ET00oKSB7CiAgICBsZXQgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKQogICAgbGluay50ZXh0Q29udGVudCA9IHRoaXMudGV4dAogICAgbGluay5ocmVmID0gdGhpcy51cmwKICAgIHJldHVybiBsaW5rOwogIH0KfQoKbGV0IGRlY29yYXRpb25zRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShfLCB0cikgewogICAgY29uc3QgYnVpbGRlciA9IG5ldyBSYW5nZVNldEJ1aWxkZXIoKTsKICAgIGxldCBjdXJzb3IgPSB0ci5zdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkOwogICAgc3ludGF4VHJlZSh0ci5zdGF0ZSkuaXRlcmF0ZSh7CiAgICAgIGVudGVyOiAobm9kZSkgPT4gewogICAgICAgIGlmICgoY3Vyc29yIDwgbm9kZS5mcm9tIHx8IGN1cnNvciA+IG5vZGUudG8pICYmIG5vZGUubmFtZSA9PSAiTGluayIpIHsKICAgICAgICAgIGxldCBtYXJrcyA9IG5vZGUubm9kZS5nZXRDaGlsZHJlbigiTGlua01hcmsiKTsKICAgICAgICAgIGxldCB0ZXh0ID0gbWFya3MubGVuZ3RoID49IDIgPyB0ci5zdGF0ZS5zbGljZURvYyhtYXJrc1swXS50bywgbWFya3NbMV0uZnJvbSkgOiAiIgoKICAgICAgICAgIGxldCB1cmxOb2RlID0gbm9kZS5ub2RlLmdldENoaWxkKCJVUkwiKTsKICAgICAgICAgIGxldCB1cmwgPSB1cmxOb2RlID8gdHIuc3RhdGUuc2xpY2VEb2ModXJsTm9kZS5mcm9tLCB1cmxOb2RlLnRvKSA6ICIiCiAgICAgICAgICBidWlsZGVyLmFkZChub2RlLmZyb20sIG5vZGUudG8sIERlY29yYXRpb24ucmVwbGFjZSh7CiAgICAgICAgICAgIHdpZGdldDogbmV3IExpbmsodGV4dCwgdXJsKSwKICAgICAgICAgIH0pLAogICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfSwKICAgIH0pOwogICAgcmV0dXJuIGJ1aWxkZXIuZmluaXNoKCk7CiAgfSwKICBwcm92aWRlOiAoZikgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGYpLAp9KTsKCmxldCB2aWV3ID0gbmV3IEVkaXRvclZpZXcoewogIGRvYzogIiMgSGVsbG8gV29ybGRcblxuW1Rlc3RdKGh0dHBzOi8vZXhhbXBsZS5jb20pXG5cblxuIiwKICBleHRlbnNpb25zOiBbZGVjb3JhdGlvbnNGaWVsZCwgbWFya2Rvd24oKV0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5LAp9KTsKCnZpZXcuZm9jdXMoKTs=
23
+ // https://discuss.codemirror.net/t/link-widget-is-clicked-only-if-editor-is-out-of-focus/7433
24
+
25
+ export type LinkOptions = {
26
+ link?: boolean;
27
+ onHover?: (el: Element, url: string) => void;
28
+ onRender?: (el: Element, url: string) => void;
29
+ };
30
+
31
+ /**
32
+ * Creates a state field to replace AST elements with a hyperlink widget.
33
+ * https://codemirror.net/docs/ref/#state.StateField
34
+ */
35
+ export const link = (options: LinkOptions = {}): Extension => {
36
+ const extensions: Extension[] = [
37
+ StateField.define<RangeSet<any>>({
38
+ create: (state) => update(state, options),
39
+ update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
40
+ provide: (field) => EditorView.decorations.from(field),
41
+ }),
42
+ ];
43
+
44
+ if (options.onHover) {
45
+ extensions.push(
46
+ // https://codemirror.net/examples/tooltip
47
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
48
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
49
+ hoverTooltip((view, pos) => {
50
+ const { from, text } = view.state.doc.lineAt(pos);
51
+ const p = pos - from;
52
+
53
+ let idx = 0;
54
+ let match;
55
+ do {
56
+ match = text.substring(idx).match(markdownLinkRegexp);
57
+ if (!match) {
58
+ match = undefined;
59
+ break;
60
+ }
61
+
62
+ idx = text.indexOf(match[0]);
63
+ if (p >= idx && p < idx + match[0].length) {
64
+ break;
65
+ }
66
+
67
+ idx += match[0].length;
68
+ } while (true);
69
+
70
+ if (!match) {
71
+ return null;
72
+ }
73
+
74
+ const [, , url] = match ?? [];
75
+
76
+ return {
77
+ pos: idx + from,
78
+ end: idx + from + match[0].length,
79
+ above: true,
80
+ create: () => {
81
+ const el = document.createElement('div');
82
+ el.className = tooltipContent({}, 'pli-2 plb-1');
83
+ options.onHover!(el, url);
84
+ return { dom: el, offset: { x: 0, y: 4 } };
85
+ },
86
+ };
87
+ }),
88
+ );
89
+ }
90
+
91
+ return extensions;
92
+ };
93
+
94
+ class LinkButton extends WidgetType {
95
+ constructor(
96
+ private readonly _pos: number,
97
+ private readonly _url: string,
98
+ private readonly _onAttach: LinkOptions['onRender'],
99
+ ) {
100
+ super();
101
+ }
102
+
103
+ override eq(other: LinkButton) {
104
+ return this._pos === other._pos && this._url === other._url;
105
+ }
106
+
107
+ toDOM(view: EditorView) {
108
+ const el = document.createElement('span');
109
+ this._onAttach!(el, this._url);
110
+ return el;
111
+ }
112
+ }
113
+
114
+ class LinkText extends WidgetType {
115
+ constructor(private readonly _text: string, private readonly _url?: string) {
116
+ super();
117
+ }
118
+
119
+ override eq(other: LinkText) {
120
+ return this._url === other._url;
121
+ }
122
+
123
+ toDOM(view: EditorView) {
124
+ const link = document.createElement('a');
125
+ link.setAttribute('class', 'cm-link');
126
+ link.textContent = this._text;
127
+ if (this._url) {
128
+ link.setAttribute('rel', 'noreferrer');
129
+ link.setAttribute('target', '_blank');
130
+ link.href = this._url;
131
+ } else {
132
+ link.onclick = () => {
133
+ view.dispatch({
134
+ selection: {
135
+ anchor: view.posAtDOM(link),
136
+ },
137
+ });
138
+ };
139
+ }
140
+
141
+ return link;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Range sets provide a data structure that can hold a collection of tagged,
147
+ * possibly overlapping ranges in such a way that they can efficiently be mapped though document changes.
148
+ * https://codemirror.net/docs/ref/#state
149
+ */
150
+ const update = (state: EditorState, options: LinkOptions) => {
151
+ const builder = new RangeSetBuilder();
152
+ const cursor = state.selection.main.head;
153
+
154
+ syntaxTree(state).iterate({
155
+ enter: (node) => {
156
+ // Check if cursor is inside text.
157
+ if (node.name === 'Link') {
158
+ const marks = node.node.getChildren('LinkMark');
159
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
160
+ const urlNode = node.node.getChild('URL');
161
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
162
+ if (!url) {
163
+ return false;
164
+ }
165
+
166
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
167
+ builder.add(
168
+ node.from,
169
+ node.to,
170
+ Decoration.replace({
171
+ widget: new LinkText(text, options.link ? url : undefined),
172
+ }),
173
+ );
174
+ }
175
+
176
+ if (options.onRender) {
177
+ builder.add(
178
+ node.to,
179
+ node.to,
180
+ Decoration.replace({
181
+ widget: new LinkButton(node.from, url, options.onRender),
182
+ }),
183
+ );
184
+ }
185
+
186
+ return false;
187
+ }
188
+ },
189
+ });
190
+
191
+ return builder.finish();
192
+ };
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type Extension } from '@codemirror/state';
6
+ import { EditorView } from '@codemirror/view';
7
+
8
+ export type ListenerOptions = {
9
+ onFocus?: (focused: boolean) => void;
10
+ onChange?: (text: string) => void;
11
+ };
12
+
13
+ /**
14
+ * Event listener.
15
+ */
16
+ export const listener = ({ onFocus, onChange }: ListenerOptions): Extension => {
17
+ const extensions: Extension[] = [];
18
+
19
+ onFocus &&
20
+ extensions.push(
21
+ EditorView.focusChangeEffect.of((_, focused) => {
22
+ onFocus(focused);
23
+ return null;
24
+ }),
25
+ );
26
+
27
+ onChange &&
28
+ extensions.push(
29
+ EditorView.updateListener.of((update) => {
30
+ onChange(update.state.doc.toString());
31
+ }),
32
+ );
33
+
34
+ return extensions;
35
+ };
@@ -0,0 +1,106 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
6
+ import { history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
7
+ import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
8
+ import { bracketMatching, defaultHighlightStyle, indentOnInput, syntaxHighlighting } from '@codemirror/language';
9
+ import { languages } from '@codemirror/language-data';
10
+ import { searchKeymap } from '@codemirror/search';
11
+ import { EditorState, type Extension } from '@codemirror/state';
12
+ import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
13
+ import {
14
+ crosshairCursor,
15
+ drawSelection,
16
+ dropCursor,
17
+ EditorView,
18
+ highlightActiveLine,
19
+ keymap,
20
+ placeholder,
21
+ } from '@codemirror/view';
22
+
23
+ import type { ThemeMode } from '@dxos/react-ui';
24
+
25
+ import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
26
+
27
+ export type MarkdownBundleOptions = {
28
+ readonly?: boolean;
29
+ themeMode?: ThemeMode;
30
+ placeholder?: string;
31
+ };
32
+
33
+ /**
34
+ * Markdown bundle.
35
+ * Refs:
36
+ * https://github.com/codemirror/basic-setup
37
+ * https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts#L50
38
+ * https://codemirror.net/docs/community
39
+ * https://codemirror.net/docs/ref/#codemirror.basicSetup
40
+ */
41
+ // TODO(burdon): Add Composer here: https://codemirror.net/docs/community
42
+ export const markdownBundle = ({
43
+ readonly,
44
+ themeMode,
45
+ placeholder: _placeholder,
46
+ }: MarkdownBundleOptions): Extension[] => {
47
+ return [
48
+ EditorState.allowMultipleSelections.of(true),
49
+ EditorState.tabSize.of(2),
50
+ EditorView.lineWrapping,
51
+
52
+ customKeymap(),
53
+
54
+ bracketMatching(),
55
+ closeBrackets(),
56
+ crosshairCursor(),
57
+ dropCursor(),
58
+ drawSelection(),
59
+ highlightActiveLine(),
60
+ history(),
61
+ indentOnInput(),
62
+ _placeholder && placeholder(_placeholder),
63
+
64
+ // Main extension.
65
+ // https://github.com/codemirror/lang-markdown
66
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
67
+ markdown({
68
+ // GRM by default (vs strict CommonMark):
69
+ // Table, TaskList, Strikethrough, and Autolink.
70
+ // NOTE: This extends the parser; it doesn't affect rendering.
71
+ // https://github.github.com/gfm
72
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
73
+ base: markdownLanguage,
74
+
75
+ // Languages for syntax highlighting fenced code blocks.
76
+ codeLanguages: languages,
77
+
78
+ // Parser extensions.
79
+ extensions: [
80
+ // GFM provided by default.
81
+ markdownTagsExtensions,
82
+ ],
83
+ }),
84
+
85
+ // https://github.com/codemirror/theme-one-dark
86
+ themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
87
+
88
+ // Custom styles.
89
+ syntaxHighlighting(markdownHighlightStyle(readonly)),
90
+ ].filter(Boolean) as Extension[];
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
+ ]);
@@ -0,0 +1,204 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { markdownLanguage } from '@codemirror/lang-markdown';
6
+ import { HighlightStyle } from '@codemirror/language';
7
+ import { tags, styleTags, Tag } from '@lezer/highlight';
8
+ import { type MarkdownConfig, Table } from '@lezer/markdown';
9
+ import get from 'lodash.get';
10
+
11
+ import {
12
+ blockquote,
13
+ bold,
14
+ code,
15
+ codeMark,
16
+ heading,
17
+ inlineUrl,
18
+ italic,
19
+ mark,
20
+ strikethrough,
21
+ tokens,
22
+ } from '../../styles';
23
+
24
+ /**
25
+ * Custom tags defined and processed by the GFM lezer extension.
26
+ * https://github.com/lezer-parser/markdown
27
+ * https://github.com/lezer-parser/markdown/blob/main/src/markdown.ts
28
+ */
29
+ export const markdownTags = {
30
+ Blockquote: Tag.define(),
31
+ CodeMark: Tag.define(),
32
+ CodeText: Tag.define(),
33
+ EmphasisMark: Tag.define(),
34
+ HeaderMark: Tag.define(),
35
+ // HorizontalRule: Tag.define(),
36
+ InlineCode: Tag.define(),
37
+ LinkLabel: Tag.define(),
38
+ LinkReference: Tag.define(),
39
+ ListMark: Tag.define(),
40
+ QuoteMark: Tag.define(),
41
+ URL: Tag.define(),
42
+
43
+ // Custom.
44
+ TableCell: Tag.define(),
45
+ };
46
+
47
+ // TODO(burdon): Customize table parser (make all content monospace).
48
+ // https://github.com/lezer-parser/markdown/blob/main/src/extension.ts
49
+ Table.defineNodes?.forEach((node: any) => {
50
+ switch (node?.name) {
51
+ case 'TableCell': {
52
+ node.style = markdownTags.TableCell;
53
+ break;
54
+ }
55
+ }
56
+ });
57
+
58
+ export const markdownTagsExtensions: MarkdownConfig[] = [
59
+ Table,
60
+ {
61
+ props: [styleTags(markdownTags)],
62
+ },
63
+ ];
64
+
65
+ /**
66
+ * Styling based on `lezer` parser tags.
67
+ * https://codemirror.net/examples/styling
68
+ * https://github.com/lezer-parser/highlight
69
+ * https://github.com/lezer-parser/highlight/blob/main/src/highlight.ts#L427
70
+ * https://lezer.codemirror.net/docs/ref/#highlight.tags (list of tags)
71
+ *
72
+ * Examples:
73
+ * - https://github.com/codemirror/language/blob/main/src/highlight.ts#L194
74
+ * - https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts#L115
75
+ */
76
+ export const markdownHighlightStyle = (readonly?: boolean) => {
77
+ return HighlightStyle.define(
78
+ [
79
+ {
80
+ tag: [
81
+ tags.keyword,
82
+ tags.name,
83
+ tags.deleted,
84
+ tags.character,
85
+ tags.propertyName,
86
+ tags.macroName,
87
+ tags.color,
88
+ tags.constant(tags.name),
89
+ tags.standard(tags.name),
90
+ tags.definition(tags.name),
91
+ tags.separator,
92
+ tags.typeName,
93
+ tags.className,
94
+ tags.number,
95
+ tags.changed,
96
+ tags.annotation,
97
+ tags.modifier,
98
+ tags.self,
99
+ tags.namespace,
100
+ tags.operator,
101
+ tags.operatorKeyword,
102
+ tags.escape,
103
+ tags.regexp,
104
+ tags.special(tags.string),
105
+ tags.meta,
106
+ tags.comment,
107
+ tags.atom,
108
+ tags.bool,
109
+ tags.special(tags.variableName),
110
+ tags.processingInstruction,
111
+ tags.string,
112
+ tags.inserted,
113
+ tags.invalid,
114
+ ],
115
+ color: 'inherit !important',
116
+ },
117
+
118
+ // Markdown marks.
119
+ {
120
+ tag: [
121
+ tags.meta,
122
+ tags.processingInstruction,
123
+ markdownTags.LinkLabel,
124
+ markdownTags.LinkReference,
125
+ markdownTags.ListMark,
126
+ ],
127
+ class: mark,
128
+ },
129
+
130
+ // Markdown marks.
131
+ {
132
+ tag: [markdownTags.CodeMark, markdownTags.HeaderMark, markdownTags.QuoteMark, markdownTags.EmphasisMark],
133
+ class: readonly ? 'hidden' : mark,
134
+ },
135
+
136
+ // E.g., code block language (after ```).
137
+ {
138
+ tag: [tags.function(tags.variableName), tags.labelName],
139
+ class: readonly ? 'hidden' : codeMark,
140
+ },
141
+
142
+ {
143
+ tag: [tags.monospace],
144
+ class: 'font-mono',
145
+ },
146
+
147
+ // Headings.
148
+ { tag: tags.heading1, class: heading(1, readonly) },
149
+ { tag: tags.heading2, class: heading(2, readonly) },
150
+ { tag: tags.heading3, class: heading(3, readonly) },
151
+ { tag: tags.heading4, class: heading(4, readonly) },
152
+ { tag: tags.heading5, class: heading(5, readonly) },
153
+ { tag: tags.heading6, class: heading(6, readonly) },
154
+
155
+ // Emphasis.
156
+ { tag: tags.emphasis, class: italic },
157
+ { tag: tags.strong, class: bold },
158
+ { tag: tags.strikethrough, class: strikethrough },
159
+
160
+ // Naked URLs.
161
+ {
162
+ tag: [markdownTags.URL],
163
+ class: inlineUrl,
164
+ },
165
+
166
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
167
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
168
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
169
+ // the code, but all other CSS properties will be inherited.
170
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
171
+ {
172
+ tag: [markdownTags.CodeText, markdownTags.InlineCode],
173
+ class: code,
174
+ },
175
+
176
+ {
177
+ tag: [markdownTags.QuoteMark],
178
+ class: blockquote,
179
+ },
180
+
181
+ // TODO(burdon): Replace with extension.
182
+ // {
183
+ // tag: [markdownTags.HorizontalRule],
184
+ // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
185
+ // },
186
+
187
+ // TODO(burdon): Only if being edited.
188
+ {
189
+ tag: [markdownTags.TableCell],
190
+ class: 'font-mono',
191
+ },
192
+
193
+ // Main content, paragraphs, etc.
194
+ // {
195
+ // tag: [tags.content],
196
+ // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
197
+ // },
198
+ ],
199
+ {
200
+ scope: markdownLanguage,
201
+ all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
202
+ },
203
+ );
204
+ };
@@ -3,4 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './bundle';
6
- export * from './theme';
6
+ export * from './highlight';
@@ -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
+ };
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Extension } from '@codemirror/state';
6
+
7
+ // TODO(burdon): How will this interact with the code extension?
8
+ // TODO(burdon): Move to mermaid-plugin (which should contribute this extension).
9
+ export const mermaid = (): Extension => {
10
+ return [];
11
+ };