@dxos/react-ui-editor 0.3.11-main.f5e7551 → 0.3.11-main.f7c1031

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 (262) hide show
  1. package/dist/lib/browser/index.mjs +3878 -1921
  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 +14 -15
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -3
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/hooks.stories.d.ts +23 -0
  13. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  14. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  15. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  16. package/dist/types/src/components/Toolbar/Toolbar.d.ts +29 -0
  17. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  18. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +25 -0
  19. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  20. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  21. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  22. package/dist/types/src/components/index.d.ts +1 -0
  23. package/dist/types/src/components/index.d.ts.map +1 -1
  24. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  26. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  28. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  30. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  31. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  32. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/defs.d.ts +24 -0
  34. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  35. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  36. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  37. package/dist/types/src/extensions/automerge/semaphore.d.ts +15 -0
  38. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  39. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  40. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  41. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  42. package/dist/types/src/extensions/awareness.d.ts +46 -0
  43. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  44. package/dist/types/src/extensions/basic.d.ts +8 -0
  45. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  46. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  47. package/dist/types/src/extensions/comments.d.ts +71 -0
  48. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  49. package/dist/types/src/extensions/cursor.d.ts +25 -0
  50. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  51. package/dist/types/src/extensions/index.d.ts +13 -0
  52. package/dist/types/src/extensions/index.d.ts.map +1 -0
  53. package/dist/types/src/extensions/listener.d.ts +10 -0
  54. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/bundle.d.ts +14 -0
  56. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  58. package/dist/types/src/extensions/markdown/formatting.d.ts +57 -0
  59. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  60. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  61. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  62. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  63. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  64. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  65. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  66. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  67. package/dist/types/src/extensions/markdown/index.d.ts +11 -0
  68. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  69. package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/link.d.ts +1 -0
  70. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  71. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  72. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  73. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  74. package/dist/types/src/extensions/outliner.d.ts +4 -0
  75. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  76. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  77. package/dist/types/src/hooks/awareness-provider.d.ts +31 -0
  78. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  79. package/dist/types/src/hooks/defs.d.ts +25 -0
  80. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  81. package/dist/types/src/hooks/index.d.ts +5 -1
  82. package/dist/types/src/hooks/index.d.ts.map +1 -1
  83. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  84. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  85. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  86. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  87. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  88. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  89. package/dist/types/src/hooks/useTextModel.d.ts +14 -31
  90. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  91. package/dist/types/src/index.d.ts +5 -0
  92. package/dist/types/src/index.d.ts.map +1 -1
  93. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  94. package/dist/types/src/styles/index.d.ts +1 -0
  95. package/dist/types/src/styles/index.d.ts.map +1 -1
  96. package/dist/types/src/styles/markdown.d.ts +2 -2
  97. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  98. package/dist/types/src/styles/tokens.d.ts +1 -0
  99. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  100. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +1 -1
  101. package/dist/types/src/themes/default.d.ts.map +1 -0
  102. package/dist/types/src/themes/index.d.ts.map +1 -0
  103. package/dist/types/src/util.d.ts +7 -0
  104. package/dist/types/src/util.d.ts.map +1 -0
  105. package/package.json +48 -36
  106. package/src/components/TextEditor/MarkdownEditor.stories.tsx +93 -102
  107. package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
  108. package/src/components/TextEditor/TextEditor.tsx +137 -97
  109. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +65 -6
  110. package/src/components/TextEditor/hooks.stories.tsx +111 -0
  111. package/src/components/TextEditor/index.ts +0 -3
  112. package/src/components/Toolbar/Toolbar.stories.tsx +102 -0
  113. package/src/components/Toolbar/Toolbar.tsx +245 -0
  114. package/src/components/Toolbar/index.ts +5 -0
  115. package/src/components/index.ts +1 -0
  116. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +23 -19
  117. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  118. package/src/extensions/automerge/automerge.test.ts +13 -0
  119. package/src/extensions/automerge/automerge.ts +103 -0
  120. package/src/extensions/automerge/cursor.ts +52 -0
  121. package/src/extensions/automerge/defs.ts +41 -0
  122. package/src/extensions/automerge/index.ts +6 -0
  123. package/src/extensions/automerge/semaphore.ts +81 -0
  124. package/src/extensions/automerge/update-automerge.ts +49 -0
  125. package/src/{hooks → extensions}/automerge/update-codemirror.ts +17 -9
  126. package/src/extensions/awareness.ts +313 -0
  127. package/src/{components/TextEditor/extensions → extensions}/basic.ts +17 -10
  128. package/src/{components/TextEditor/extensions → extensions}/blast.ts +4 -1
  129. package/src/extensions/comments.ts +486 -0
  130. package/src/extensions/cursor.ts +50 -0
  131. package/src/{components/TextEditor/extensions → extensions}/index.ts +4 -8
  132. package/src/extensions/listener.ts +35 -0
  133. package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +23 -28
  134. package/src/extensions/markdown/code.ts +150 -0
  135. package/src/extensions/markdown/formatting.test.ts +481 -0
  136. package/src/extensions/markdown/formatting.ts +1198 -0
  137. package/src/extensions/markdown/heading.ts +59 -0
  138. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +12 -27
  139. package/src/extensions/markdown/hr.ts +71 -0
  140. package/src/extensions/markdown/image.ts +87 -0
  141. package/src/extensions/markdown/index.ts +14 -0
  142. package/src/{components/TextEditor/extensions → extensions/markdown}/link.ts +121 -58
  143. package/src/{components/TextEditor/extensions → extensions/markdown}/table.ts +10 -4
  144. package/src/extensions/markdown/tasklist.ts +110 -0
  145. package/src/{components/TextEditor/extensions → extensions}/mention.ts +9 -2
  146. package/src/extensions/outliner.ts +12 -0
  147. package/src/{components/TextEditor/extensions → extensions}/typewriter.ts +1 -1
  148. package/src/hooks/awareness-provider.ts +124 -0
  149. package/src/hooks/defs.ts +42 -0
  150. package/src/hooks/index.ts +6 -1
  151. package/src/hooks/useActionHandler.ts +93 -0
  152. package/src/hooks/useEditorView.ts +29 -0
  153. package/src/hooks/useTextEditor.ts +153 -0
  154. package/src/hooks/useTextModel.ts +48 -114
  155. package/src/index.ts +5 -0
  156. package/src/styles/index.ts +1 -0
  157. package/src/styles/markdown.ts +9 -12
  158. package/src/styles/tokens.ts +3 -0
  159. package/src/{components/TextEditor/themes → themes}/default.ts +60 -84
  160. package/src/util.ts +41 -0
  161. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  162. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  163. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  164. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +0 -9
  165. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  166. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  167. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  168. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -48
  169. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  170. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +0 -1
  171. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  172. package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -17
  173. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  174. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  175. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  176. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  177. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -17
  178. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  179. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  180. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
  181. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  182. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  183. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +0 -3
  184. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +0 -1
  185. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  186. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  187. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  188. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  189. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +0 -1
  190. package/dist/types/src/components/TextEditor/extensions/util.d.ts +0 -5
  191. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +0 -1
  192. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  193. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  194. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  195. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  196. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  197. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  198. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  199. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  200. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  201. package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
  202. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  203. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  204. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  205. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  206. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  207. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  208. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  209. package/dist/types/src/hooks/yjs/space-provider.d.ts +0 -27
  210. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  211. package/dist/types/src/hooks/yjs/yjs.stories.d.ts +0 -12
  212. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  213. package/dist/types/src/hooks/yjs/yjs.test.d.ts +0 -2
  214. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  215. package/dist/types/src/testing/index.d.ts +0 -3
  216. package/dist/types/src/testing/index.d.ts.map +0 -1
  217. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  218. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  219. package/dist/types/src/testing/proto/index.d.ts +0 -2
  220. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  221. package/dist/types/src/testing/replicator.d.ts +0 -45
  222. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  223. package/src/components/TextEditor/codemirror.stories.ts +0 -114
  224. package/src/components/TextEditor/extensions/code.ts +0 -99
  225. package/src/components/TextEditor/extensions/comments.ts +0 -388
  226. package/src/components/TextEditor/extensions/hr.ts +0 -74
  227. package/src/components/TextEditor/extensions/image.ts +0 -69
  228. package/src/components/TextEditor/extensions/listener.ts +0 -23
  229. package/src/components/TextEditor/extensions/markdown/index.ts +0 -6
  230. package/src/components/TextEditor/extensions/mermaid.ts +0 -11
  231. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  232. package/src/components/TextEditor/extensions/tooltip.ts +0 -61
  233. package/src/components/TextEditor/extensions/util.ts +0 -18
  234. package/src/hooks/automerge/handle.ts +0 -14
  235. package/src/hooks/automerge/index.ts +0 -6
  236. package/src/hooks/automerge/plugin.ts +0 -134
  237. package/src/hooks/automerge/semaphore.ts +0 -78
  238. package/src/hooks/automerge/update-automerge.ts +0 -44
  239. package/src/hooks/yjs/index.ts +0 -6
  240. package/src/hooks/yjs/space-provider.ts +0 -126
  241. package/src/hooks/yjs/yjs.stories.tsx +0 -56
  242. package/src/hooks/yjs/yjs.test.ts +0 -59
  243. package/src/testing/index.ts +0 -6
  244. package/src/testing/proto/gen/schema.ts +0 -49
  245. package/src/testing/proto/index.ts +0 -5
  246. package/src/testing/proto/schema.proto +0 -15
  247. package/src/testing/replicator.ts +0 -189
  248. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  249. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  250. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  251. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/code.d.ts +0 -0
  252. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -0
  253. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/hr.d.ts +0 -0
  254. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/image.d.ts +0 -0
  255. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/table.d.ts +0 -0
  256. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/tasklist.d.ts +0 -0
  257. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  258. /package/dist/types/src/{components/TextEditor/extensions → extensions}/typewriter.d.ts +0 -0
  259. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  260. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  261. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  262. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -0,0 +1,59 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { syntaxTree } from '@codemirror/language';
6
+ import { RangeSetBuilder } from '@codemirror/state';
7
+ import { Decoration, type DecorationSet, type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
8
+
9
+ const buildDecorations = (view: EditorView): DecorationSet => {
10
+ const builder = new RangeSetBuilder<Decoration>();
11
+ const { state } = view;
12
+ const cursor = state.selection.main.head;
13
+
14
+ for (const { from, to } of view.visibleRanges) {
15
+ syntaxTree(state).iterate({
16
+ enter: (node) => {
17
+ switch (node.name) {
18
+ case 'ATXHeading1':
19
+ case 'ATXHeading2':
20
+ case 'ATXHeading3':
21
+ case 'ATXHeading4':
22
+ case 'ATXHeading5':
23
+ case 'ATXHeading6': {
24
+ const mark = node.node.getChild('HeaderMark');
25
+ if (mark) {
26
+ if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
27
+ builder.add(mark.from, mark.to + 1, Decoration.replace({}));
28
+ }
29
+ }
30
+ }
31
+ }
32
+ },
33
+ from,
34
+ to,
35
+ });
36
+ }
37
+
38
+ return builder.finish();
39
+ };
40
+
41
+ export const heading = () => {
42
+ return [
43
+ ViewPlugin.fromClass(
44
+ class {
45
+ decorations: DecorationSet;
46
+ constructor(view: EditorView) {
47
+ this.decorations = buildDecorations(view);
48
+ }
49
+
50
+ update(update: ViewUpdate) {
51
+ this.decorations = buildDecorations(update.view);
52
+ }
53
+ },
54
+ {
55
+ decorations: (value) => value.decorations,
56
+ },
57
+ ),
58
+ ];
59
+ };
@@ -6,20 +6,19 @@ import { markdownLanguage } from '@codemirror/lang-markdown';
6
6
  import { HighlightStyle } from '@codemirror/language';
7
7
  import { tags, styleTags, Tag } from '@lezer/highlight';
8
8
  import { type MarkdownConfig, Table } from '@lezer/markdown';
9
- import get from 'lodash.get';
10
9
 
11
10
  import {
12
11
  blockquote,
13
12
  bold,
14
13
  code,
15
14
  codeMark,
15
+ getToken,
16
16
  heading,
17
17
  inlineUrl,
18
18
  italic,
19
19
  mark,
20
20
  strikethrough,
21
- tokens,
22
- } from '../../../../styles';
21
+ } from '../../styles';
23
22
 
24
23
  /**
25
24
  * Custom tags defined and processed by the GFM lezer extension.
@@ -32,7 +31,6 @@ export const markdownTags = {
32
31
  CodeText: Tag.define(),
33
32
  EmphasisMark: Tag.define(),
34
33
  HeaderMark: Tag.define(),
35
- // HorizontalRule: Tag.define(),
36
34
  InlineCode: Tag.define(),
37
35
  LinkLabel: Tag.define(),
38
36
  LinkReference: Tag.define(),
@@ -130,13 +128,13 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
130
128
  // Markdown marks.
131
129
  {
132
130
  tag: [markdownTags.CodeMark, markdownTags.HeaderMark, markdownTags.QuoteMark, markdownTags.EmphasisMark],
133
- class: readonly ? 'hidden' : mark,
131
+ class: mark,
134
132
  },
135
133
 
136
134
  // E.g., code block language (after ```).
137
135
  {
138
136
  tag: [tags.function(tags.variableName), tags.labelName],
139
- class: readonly ? 'hidden' : codeMark,
137
+ class: codeMark,
140
138
  },
141
139
 
142
140
  {
@@ -145,12 +143,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
145
143
  },
146
144
 
147
145
  // 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) },
146
+ { tag: tags.heading1, class: heading(1) },
147
+ { tag: tags.heading2, class: heading(2) },
148
+ { tag: tags.heading3, class: heading(3) },
149
+ { tag: tags.heading4, class: heading(4) },
150
+ { tag: tags.heading5, class: heading(5) },
151
+ { tag: tags.heading6, class: heading(6) },
154
152
 
155
153
  // Emphasis.
156
154
  { tag: tags.emphasis, class: italic },
@@ -167,7 +165,7 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
167
165
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
168
166
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
169
167
  // the code, but all other CSS properties will be inherited.
170
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
168
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
171
169
  {
172
170
  tag: [markdownTags.CodeText, markdownTags.InlineCode],
173
171
  class: code,
@@ -178,27 +176,14 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
178
176
  class: blockquote,
179
177
  },
180
178
 
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
179
  {
189
180
  tag: [markdownTags.TableCell],
190
181
  class: 'font-mono',
191
182
  },
192
-
193
- // Main content, paragraphs, etc.
194
- // {
195
- // tag: [tags.content],
196
- // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
197
- // },
198
183
  ],
199
184
  {
200
185
  scope: markdownLanguage,
201
- all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
186
+ all: { fontFamily: getToken('fontFamily.body', []).join(',') },
202
187
  },
203
188
  );
204
189
  };
@@ -0,0 +1,71 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { syntaxTree } from '@codemirror/language';
6
+ import { RangeSetBuilder } from '@codemirror/state';
7
+ import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
8
+
9
+ import { getToken } from '../../styles';
10
+
11
+ // TODO(burdon): Reconcile with theme.
12
+ const styles = EditorView.baseTheme({
13
+ '& .cm-hr': {
14
+ // Note that block-level decorations should not have vertical margins,
15
+ borderTop: `1px solid ${getToken('extend.colors.neutral.200')}`,
16
+ },
17
+ });
18
+
19
+ class HorizontalRuleWidget extends WidgetType {
20
+ override toDOM() {
21
+ const el = document.createElement('div');
22
+ el.className = 'cm-hr';
23
+ return el;
24
+ }
25
+ }
26
+
27
+ const decoration = Decoration.replace({ widget: new HorizontalRuleWidget() });
28
+
29
+ const buildDecorations = (view: EditorView): DecorationSet => {
30
+ const builder = new RangeSetBuilder<Decoration>();
31
+ const { state } = view;
32
+ const cursor = state.selection.main.head;
33
+
34
+ for (const { from, to } of view.visibleRanges) {
35
+ syntaxTree(state).iterate({
36
+ enter: (node) => {
37
+ if (node.name === 'HorizontalRule') {
38
+ // Check if cursor is inside text.
39
+ if (cursor <= node.from || cursor >= node.to) {
40
+ builder.add(node.from, node.to, decoration);
41
+ }
42
+ }
43
+ },
44
+ from,
45
+ to,
46
+ });
47
+ }
48
+
49
+ return builder.finish();
50
+ };
51
+
52
+ export const hr = () => {
53
+ return [
54
+ ViewPlugin.fromClass(
55
+ class {
56
+ decorations: DecorationSet;
57
+ constructor(view: EditorView) {
58
+ this.decorations = buildDecorations(view);
59
+ }
60
+
61
+ update(update: ViewUpdate) {
62
+ this.decorations = buildDecorations(update.view);
63
+ }
64
+ },
65
+ {
66
+ decorations: (value) => value.decorations,
67
+ },
68
+ ),
69
+ styles,
70
+ ];
71
+ };
@@ -0,0 +1,87 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { syntaxTree } from '@codemirror/language';
6
+ import { type EditorState, type Extension, StateField, type Transaction, type Range } from '@codemirror/state';
7
+ import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
8
+
9
+ export type ImageOptions = {};
10
+
11
+ export const image = (options: ImageOptions = {}): Extension => {
12
+ return StateField.define<DecorationSet>({
13
+ create: (state) => {
14
+ return Decoration.set(buildDecorations(0, state.doc.length, state));
15
+ },
16
+ update: (value: DecorationSet, tr: Transaction) => {
17
+ if (!tr.docChanged && !tr.selection) {
18
+ return value;
19
+ }
20
+ // Find range of changes and cursor changes.
21
+ const cursor = tr.state.selection.main.head;
22
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
23
+ let from = Math.min(cursor, oldCursor);
24
+ let to = Math.max(cursor, oldCursor);
25
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
26
+ from = Math.min(from, fromB);
27
+ to = Math.max(to, toB);
28
+ });
29
+ // Expand to cover lines
30
+ from = tr.state.doc.lineAt(from).from;
31
+ to = tr.state.doc.lineAt(to).to;
32
+ return value.map(tr.changes).update({
33
+ filterFrom: from,
34
+ filterTo: to,
35
+ filter: () => false,
36
+ add: buildDecorations(from, to, tr.state),
37
+ });
38
+ },
39
+ provide: (field) => EditorView.decorations.from(field),
40
+ });
41
+ };
42
+
43
+ const buildDecorations = (from: number, to: number, state: EditorState) => {
44
+ const decorations: Range<Decoration>[] = [];
45
+ const cursor = state.selection.main.head;
46
+
47
+ syntaxTree(state).iterate({
48
+ enter: (node) => {
49
+ if (node.name === 'Image') {
50
+ const urlNode = node.node.getChild('URL');
51
+ if (urlNode) {
52
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
53
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
54
+ decorations.push(
55
+ Decoration.replace({
56
+ block: true, // Prevent cursor from entering.
57
+ widget: new ImageWidget(url),
58
+ }).range(hide ? node.from : node.to, node.to),
59
+ );
60
+ }
61
+ }
62
+ },
63
+ from,
64
+ to,
65
+ });
66
+
67
+ return decorations;
68
+ };
69
+
70
+ class ImageWidget extends WidgetType {
71
+ constructor(readonly _url: string) {
72
+ super();
73
+ }
74
+
75
+ override eq(other: this) {
76
+ return this._url === (other as any as ImageWidget)._url;
77
+ }
78
+
79
+ override toDOM(view: EditorView) {
80
+ const img = document.createElement('img');
81
+ img.setAttribute('src', this._url);
82
+ img.setAttribute('class', 'cm-image');
83
+ // Images are hidden until successfully loaded to avoid flickering effects.
84
+ img.onload = () => img.classList.add('cm-loaded-image');
85
+ return img;
86
+ }
87
+ }
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './bundle';
6
+ export * from './code';
7
+ export * from './formatting';
8
+ export * from './heading';
9
+ export * from './highlight';
10
+ export * from './hr';
11
+ export * from './image';
12
+ export * from './link';
13
+ export * from './table';
14
+ export * from './tasklist';
@@ -4,22 +4,28 @@
4
4
  //
5
5
 
6
6
  import { syntaxTree } from '@codemirror/language';
7
+ import { type Extension, RangeSetBuilder } from '@codemirror/state';
7
8
  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 } from '@codemirror/view';
9
+ hoverTooltip,
10
+ Decoration,
11
+ type DecorationSet,
12
+ type EditorView,
13
+ ViewPlugin,
14
+ type ViewUpdate,
15
+ WidgetType,
16
+ } from '@codemirror/view';
17
+ import { type SyntaxNode } from '@lezer/common';
18
+
19
+ import { tooltipContent } from '@dxos/react-ui-theme';
16
20
 
17
21
  // Adapted from:
18
22
  // https://codemirror.net/try/?c=aW1wb3J0IHsgYmFzaWNTZXR1cCwgRWRpdG9yVmlldyB9IGZyb20gImNvZGVtaXJyb3IiCmltcG9ydCB7IG1hcmtkb3duIH0gZnJvbSAiQGNvZGVtaXJyb3IvbGFuZy1tYXJrZG93biIKaW1wb3J0IHsgc3ludGF4VHJlZSB9IGZyb20gIkBjb2RlbWlycm9yL2xhbmd1YWdlIjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIFJhbmdlU2V0QnVpbGRlciwgU3RhdGVGaWVsZCB9IGZyb20gIkBjb2RlbWlycm9yL3N0YXRlIjsKaW1wb3J0IHsgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSB9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciOwoKY2xhc3MgTGluayBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKHRleHQsIHVybCkgewogICAgc3VwZXIoKQogICAgdGhpcy50ZXh0ID0gdGV4dAogICAgdGhpcy51cmwgPSB1cmwKICB9CiAgZXEob3RoZXIpIHsgcmV0dXJuIHRoaXMudGV4dCA9PSBvdGhlci50ZXh0ICYmIHRoaXMudXJsID09IG90aGVyLnVybCB9CiAgdG9ET00oKSB7CiAgICBsZXQgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKQogICAgbGluay50ZXh0Q29udGVudCA9IHRoaXMudGV4dAogICAgbGluay5ocmVmID0gdGhpcy51cmwKICAgIHJldHVybiBsaW5rOwogIH0KfQoKbGV0IGRlY29yYXRpb25zRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShfLCB0cikgewogICAgY29uc3QgYnVpbGRlciA9IG5ldyBSYW5nZVNldEJ1aWxkZXIoKTsKICAgIGxldCBjdXJzb3IgPSB0ci5zdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkOwogICAgc3ludGF4VHJlZSh0ci5zdGF0ZSkuaXRlcmF0ZSh7CiAgICAgIGVudGVyOiAobm9kZSkgPT4gewogICAgICAgIGlmICgoY3Vyc29yIDwgbm9kZS5mcm9tIHx8IGN1cnNvciA+IG5vZGUudG8pICYmIG5vZGUubmFtZSA9PSAiTGluayIpIHsKICAgICAgICAgIGxldCBtYXJrcyA9IG5vZGUubm9kZS5nZXRDaGlsZHJlbigiTGlua01hcmsiKTsKICAgICAgICAgIGxldCB0ZXh0ID0gbWFya3MubGVuZ3RoID49IDIgPyB0ci5zdGF0ZS5zbGljZURvYyhtYXJrc1swXS50bywgbWFya3NbMV0uZnJvbSkgOiAiIgoKICAgICAgICAgIGxldCB1cmxOb2RlID0gbm9kZS5ub2RlLmdldENoaWxkKCJVUkwiKTsKICAgICAgICAgIGxldCB1cmwgPSB1cmxOb2RlID8gdHIuc3RhdGUuc2xpY2VEb2ModXJsTm9kZS5mcm9tLCB1cmxOb2RlLnRvKSA6ICIiCiAgICAgICAgICBidWlsZGVyLmFkZChub2RlLmZyb20sIG5vZGUudG8sIERlY29yYXRpb24ucmVwbGFjZSh7CiAgICAgICAgICAgIHdpZGdldDogbmV3IExpbmsodGV4dCwgdXJsKSwKICAgICAgICAgIH0pLAogICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfSwKICAgIH0pOwogICAgcmV0dXJuIGJ1aWxkZXIuZmluaXNoKCk7CiAgfSwKICBwcm92aWRlOiAoZikgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGYpLAp9KTsKCmxldCB2aWV3ID0gbmV3IEVkaXRvclZpZXcoewogIGRvYzogIiMgSGVsbG8gV29ybGRcblxuW1Rlc3RdKGh0dHBzOi8vZXhhbXBsZS5jb20pXG5cblxuIiwKICBleHRlbnNpb25zOiBbZGVjb3JhdGlvbnNGaWVsZCwgbWFya2Rvd24oKV0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5LAp9KTsKCnZpZXcuZm9jdXMoKTs=
19
23
  // https://discuss.codemirror.net/t/link-widget-is-clicked-only-if-editor-is-out-of-focus/7433
20
24
 
25
+ // TODO(burdon): Callback to dynamically lookup title of linked pages.
21
26
  export type LinkOptions = {
22
27
  link?: boolean;
28
+ onHover?: (el: Element, url: string) => void;
23
29
  onRender?: (el: Element, url: string) => void;
24
30
  };
25
31
 
@@ -28,27 +34,75 @@ export type LinkOptions = {
28
34
  * https://codemirror.net/docs/ref/#state.StateField
29
35
  */
30
36
  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
- });
37
+ const extensions: Extension[] = [
38
+ ViewPlugin.fromClass(
39
+ class {
40
+ decorations: DecorationSet;
41
+ constructor(view: EditorView) {
42
+ this.decorations = buildDecorations(view, options);
43
+ }
44
+
45
+ update(update: ViewUpdate) {
46
+ if (update.docChanged || update.viewportChanged || update.selectionSet) {
47
+ this.decorations = buildDecorations(update.view, options);
48
+ }
49
+ }
50
+ },
51
+ {
52
+ decorations: (value) => value.decorations,
53
+ },
54
+ ),
55
+ ];
56
+
57
+ if (options.onHover) {
58
+ extensions.push(
59
+ // https://codemirror.net/examples/tooltip
60
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
61
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
62
+ hoverTooltip((view, pos, side) => {
63
+ const syntax = syntaxTree(view.state).resolveInner(pos, side);
64
+ let link = null;
65
+ for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
66
+ link = node.name === 'Link' ? node : null;
67
+ }
68
+
69
+ const url = link && link.getChild('URL');
70
+ if (!url || !link) {
71
+ return null;
72
+ }
73
+
74
+ const urlText = view.state.sliceDoc(url.from, url.to);
75
+ return {
76
+ pos: link.from,
77
+ end: link.to,
78
+ above: true,
79
+ create: () => {
80
+ const el = document.createElement('div');
81
+ el.className = tooltipContent({}, 'pli-2 plb-1');
82
+ options.onHover!(el, urlText);
83
+ return { dom: el, offset: { x: 0, y: 4 } };
84
+ },
85
+ };
86
+ }),
87
+ );
88
+ }
89
+
90
+ return extensions;
36
91
  };
37
92
 
38
93
  class LinkButton extends WidgetType {
39
94
  constructor(
40
- private readonly _pos: number,
41
95
  private readonly _url: string,
42
96
  private readonly _onAttach: LinkOptions['onRender'],
43
97
  ) {
44
98
  super();
45
99
  }
46
100
 
47
- override eq(other: LinkButton) {
48
- return this._pos === other._pos && this._url === other._url;
101
+ override eq(other: this) {
102
+ return this._url === other._url;
49
103
  }
50
104
 
51
- toDOM(view: EditorView) {
105
+ override toDOM(view: EditorView) {
52
106
  const el = document.createElement('span');
53
107
  this._onAttach!(el, this._url);
54
108
  return el;
@@ -56,15 +110,18 @@ class LinkButton extends WidgetType {
56
110
  }
57
111
 
58
112
  class LinkText extends WidgetType {
59
- constructor(private readonly _pos: number, private readonly _text: string, private readonly _url?: string) {
113
+ constructor(
114
+ private readonly _text: string,
115
+ private readonly _url?: string,
116
+ ) {
60
117
  super();
61
118
  }
62
119
 
63
- override eq(other: LinkText) {
64
- return this._pos === other._pos && this._url === other._url;
120
+ override eq(other: this) {
121
+ return this._url === other._url;
65
122
  }
66
123
 
67
- toDOM(view: EditorView) {
124
+ override toDOM(view: EditorView) {
68
125
  const link = document.createElement('a');
69
126
  link.setAttribute('class', 'cm-link');
70
127
  link.textContent = this._text;
@@ -76,7 +133,7 @@ class LinkText extends WidgetType {
76
133
  link.onclick = () => {
77
134
  view.dispatch({
78
135
  selection: {
79
- anchor: this._pos,
136
+ anchor: view.posAtDOM(link),
80
137
  },
81
138
  });
82
139
  };
@@ -91,46 +148,52 @@ class LinkText extends WidgetType {
91
148
  * possibly overlapping ranges in such a way that they can efficiently be mapped though document changes.
92
149
  * https://codemirror.net/docs/ref/#state
93
150
  */
94
- const update = (state: EditorState, options: LinkOptions) => {
95
- const builder = new RangeSetBuilder();
151
+ const buildDecorations = (view: EditorView, options: LinkOptions): DecorationSet => {
152
+ const builder = new RangeSetBuilder<Decoration>();
153
+ const { state } = view;
96
154
  const cursor = state.selection.main.head;
97
155
 
98
- syntaxTree(state).iterate({
99
- enter: (node) => {
100
- // Check if cursor is inside text.
101
- if (node.name === 'Link') {
102
- const marks = node.node.getChildren('LinkMark');
103
- const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
104
- const urlNode = node.node.getChild('URL');
105
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
106
- if (!url) {
107
- return false;
108
- }
156
+ for (const { from, to } of view.visibleRanges) {
157
+ syntaxTree(state).iterate({
158
+ enter: (node) => {
159
+ // Check if cursor is inside text.
160
+ if (node.name === 'Link') {
161
+ const marks = node.node.getChildren('LinkMark');
162
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
163
+ const urlNode = node.node.getChild('URL');
164
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
165
+ if (!url) {
166
+ return false;
167
+ }
168
+
169
+ // TODO(burdon): Don't expand if moving cursor up/down.
170
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
171
+ builder.add(
172
+ node.from,
173
+ node.to,
174
+ Decoration.replace({
175
+ widget: new LinkText(text, options.link ? url : undefined),
176
+ }),
177
+ );
178
+ }
179
+
180
+ if (options.onRender) {
181
+ builder.add(
182
+ node.to,
183
+ node.to,
184
+ Decoration.replace({
185
+ widget: new LinkButton(url, options.onRender),
186
+ }),
187
+ );
188
+ }
109
189
 
110
- if (state.readOnly || cursor < node.from || cursor > node.to) {
111
- builder.add(
112
- node.from,
113
- node.to,
114
- Decoration.replace({
115
- widget: new LinkText(node.from, text, options.link ? url : undefined),
116
- }),
117
- );
118
- }
119
-
120
- if (options.onRender) {
121
- builder.add(
122
- node.to,
123
- node.to,
124
- Decoration.replace({
125
- widget: new LinkButton(node.from, url, options.onRender),
126
- }),
127
- );
190
+ return false;
128
191
  }
129
-
130
- return false;
131
- }
132
- },
133
- });
192
+ },
193
+ from,
194
+ to,
195
+ });
196
+ }
134
197
 
135
198
  return builder.finish();
136
199
  };
@@ -81,7 +81,6 @@ const update = (state: EditorState, options: TableOptions) => {
81
81
  table.from,
82
82
  table.to,
83
83
  Decoration.replace({
84
- block: true,
85
84
  widget: new TableWidget(table),
86
85
  }),
87
86
  );
@@ -98,11 +97,14 @@ class TableWidget extends WidgetType {
98
97
  super();
99
98
  }
100
99
 
101
- override eq(other: WidgetType) {
102
- return this._table.from === (other as any as TableWidget)?._table?.from;
100
+ override eq(other: this) {
101
+ return (
102
+ this._table.header?.join() === other._table.header?.join() &&
103
+ this._table.rows?.join() === other._table.rows?.join()
104
+ );
103
105
  }
104
106
 
105
- toDOM(view: EditorView) {
107
+ override toDOM(view: EditorView) {
106
108
  const table = document.createElement('table');
107
109
 
108
110
  {
@@ -129,4 +131,8 @@ class TableWidget extends WidgetType {
129
131
 
130
132
  return table;
131
133
  }
134
+
135
+ override ignoreEvent(e: Event) {
136
+ return !/^mouse/.test(e.type);
137
+ }
132
138
  }