@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.008b7c6

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 (219) hide show
  1. package/dist/lib/browser/index.mjs +2814 -1096
  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 +15 -4
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/automerge.stories.d.ts +20 -0
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/index.d.ts +1 -1
  14. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -0
  16. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
  17. package/dist/types/src/extensions/autocomplete.d.ts +10 -0
  18. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  19. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  20. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  21. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  22. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  23. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  26. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  27. package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → extensions/automerge/semaphore.d.ts} +8 -5
  28. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  30. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  31. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
  32. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  33. package/dist/types/src/extensions/awareness.d.ts +46 -0
  34. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  35. package/dist/types/src/{components/TextEditor/extensions/basic/bundle.d.ts → extensions/basic.d.ts} +3 -2
  36. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  37. package/dist/types/src/extensions/blast.d.ts +25 -0
  38. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  39. package/dist/types/src/extensions/code.d.ts +2 -0
  40. package/dist/types/src/extensions/code.d.ts.map +1 -0
  41. package/dist/types/src/extensions/comments.d.ts +50 -0
  42. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  43. package/dist/types/src/extensions/hr.d.ts +2 -0
  44. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  45. package/dist/types/src/extensions/image.d.ts +4 -0
  46. package/dist/types/src/extensions/image.d.ts.map +1 -0
  47. package/dist/types/src/extensions/index.d.ts +19 -0
  48. package/dist/types/src/extensions/index.d.ts.map +1 -0
  49. package/dist/types/src/extensions/link.d.ts +12 -0
  50. package/dist/types/src/extensions/link.d.ts.map +1 -0
  51. package/dist/types/src/extensions/listener.d.ts +10 -0
  52. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/bundle.d.ts +17 -0
  54. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/highlight.d.ts +36 -0
  56. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/index.d.ts +3 -0
  58. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  59. package/dist/types/src/extensions/mention.d.ts +6 -0
  60. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  61. package/dist/types/src/extensions/mermaid.d.ts +3 -0
  62. package/dist/types/src/extensions/mermaid.d.ts.map +1 -0
  63. package/dist/types/src/extensions/table.d.ts +8 -0
  64. package/dist/types/src/extensions/table.d.ts.map +1 -0
  65. package/dist/types/src/extensions/tasklist.d.ts +3 -0
  66. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  67. package/dist/types/src/extensions/typewriter.d.ts +10 -0
  68. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  69. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  70. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  71. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  72. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  73. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  74. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  75. package/dist/types/src/extensions/yjs/yjs.test.d.ts +2 -0
  76. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  77. package/dist/types/src/hooks/automerge.d.ts +3 -0
  78. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  79. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  80. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  81. package/dist/types/src/hooks/index.d.ts +2 -1
  82. package/dist/types/src/hooks/index.d.ts.map +1 -1
  83. package/dist/types/src/hooks/useTextModel.d.ts +23 -8
  84. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  85. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  86. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  87. package/dist/types/src/index.d.ts +3 -0
  88. package/dist/types/src/index.d.ts.map +1 -1
  89. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  90. package/dist/types/src/styles/index.d.ts +1 -0
  91. package/dist/types/src/styles/index.d.ts.map +1 -1
  92. package/dist/types/src/styles/markdown.d.ts +14 -11
  93. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  94. package/dist/types/src/testing/replicator.d.ts +2 -2
  95. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  96. package/dist/types/src/themes/default.d.ts +17 -0
  97. package/dist/types/src/themes/default.d.ts.map +1 -0
  98. package/dist/types/src/themes/index.d.ts +2 -0
  99. package/dist/types/src/themes/index.d.ts.map +1 -0
  100. package/dist/types/src/util/cursor.d.ts +17 -0
  101. package/dist/types/src/util/cursor.d.ts.map +1 -0
  102. package/dist/types/src/util/index.d.ts +3 -0
  103. package/dist/types/src/util/index.d.ts.map +1 -0
  104. package/dist/types/src/util/util.d.ts +5 -0
  105. package/dist/types/src/util/util.d.ts.map +1 -0
  106. package/package.json +29 -33
  107. package/src/components/TextEditor/MarkdownEditor.stories.tsx +426 -0
  108. package/src/components/TextEditor/TextEditor.stories.tsx +70 -43
  109. package/src/components/TextEditor/TextEditor.tsx +98 -52
  110. package/src/{automerge → components/TextEditor}/automerge.stories.tsx +69 -7
  111. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  112. package/src/components/TextEditor/index.ts +1 -1
  113. package/src/components/TextEditor/yjs.stories.tsx +1 -0
  114. package/src/extensions/autocomplete.ts +59 -0
  115. package/src/extensions/automerge/automerge.ts +94 -0
  116. package/src/extensions/automerge/cursor.ts +51 -0
  117. package/src/extensions/automerge/defs.ts +62 -0
  118. package/src/extensions/automerge/index.ts +6 -0
  119. package/src/{automerge/automerge-plugin/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +24 -18
  120. package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → extensions/automerge/update-automerge.ts} +8 -6
  121. package/src/{automerge/automerge-plugin/amToCodemirror.ts → extensions/automerge/update-codemirror.ts} +15 -4
  122. package/src/extensions/awareness.ts +316 -0
  123. package/src/{components/TextEditor/extensions/basic/bundle.ts → extensions/basic.ts} +8 -4
  124. package/src/extensions/blast.ts +373 -0
  125. package/src/extensions/code.ts +99 -0
  126. package/src/extensions/comments.ts +421 -0
  127. package/src/extensions/hr.ts +74 -0
  128. package/src/extensions/image.ts +69 -0
  129. package/src/extensions/index.ts +22 -0
  130. package/src/extensions/link.ts +192 -0
  131. package/src/extensions/listener.ts +35 -0
  132. package/src/extensions/markdown/bundle.ts +106 -0
  133. package/src/extensions/markdown/highlight.ts +204 -0
  134. package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +1 -1
  135. package/src/extensions/mention.ts +31 -0
  136. package/src/extensions/mermaid.ts +11 -0
  137. package/src/extensions/table.ts +132 -0
  138. package/src/extensions/tasklist.ts +121 -0
  139. package/src/extensions/typewriter.ts +68 -0
  140. package/src/extensions/yjs/cursor.ts +21 -0
  141. package/src/extensions/yjs/index.ts +5 -0
  142. package/src/extensions/yjs/yjs.test.ts +35 -0
  143. package/src/extensions/yjs/yjs.ts +16 -0
  144. package/src/hooks/automerge.ts +52 -0
  145. package/src/hooks/awareness-provider.ts +120 -0
  146. package/src/hooks/index.ts +2 -1
  147. package/src/hooks/useTextModel.ts +53 -67
  148. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
  149. package/src/index.ts +3 -0
  150. package/src/styles/index.ts +1 -0
  151. package/src/styles/markdown.ts +33 -24
  152. package/src/testing/replicator.ts +16 -21
  153. package/src/themes/default.ts +302 -0
  154. package/src/themes/index.ts +5 -0
  155. package/src/util/cursor.ts +29 -0
  156. package/src/util/index.ts +6 -0
  157. package/src/util/util.ts +18 -0
  158. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  159. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
  160. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  161. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +0 -7
  162. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  163. package/dist/types/src/automerge/automerge-plugin/handle.d.ts +0 -9
  164. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  165. package/dist/types/src/automerge/automerge-plugin/index.d.ts +0 -3
  166. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  167. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts +0 -26
  168. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  169. package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
  170. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  171. package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
  172. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
  173. package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
  174. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
  175. package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
  176. package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
  177. package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
  178. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
  179. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
  180. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
  181. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
  182. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
  183. package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
  184. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
  185. package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
  186. package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -5
  187. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  188. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -8
  189. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  190. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
  191. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  192. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
  193. package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
  194. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
  195. package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
  196. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  197. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  198. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  199. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  200. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  201. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  202. package/src/automerge/automerge-plugin/handle.ts +0 -14
  203. package/src/automerge/automerge-plugin/index.ts +0 -6
  204. package/src/automerge/automerge-plugin/plugin.ts +0 -133
  205. package/src/components/TextEditor/extensions/basic/index.ts +0 -6
  206. package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
  207. package/src/components/TextEditor/extensions/change.ts +0 -22
  208. package/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts +0 -99
  209. package/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx +0 -61
  210. package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
  211. package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
  212. package/src/components/TextEditor/extensions/index.ts +0 -8
  213. package/src/components/TextEditor/extensions/markdown/bundle.ts +0 -99
  214. package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
  215. package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
  216. package/src/hooks/useCollaboration.ts +0 -45
  217. package/src/hooks/yjs/index.ts +0 -6
  218. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  219. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
@@ -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
+ };