@dxos/react-ui-editor 0.3.11-main.baa8c4d → 0.3.11-main.bc6b68b

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 (221) hide show
  1. package/dist/lib/browser/index.mjs +1882 -1531
  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 -14
  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/comments.stories.d.ts +26 -0
  13. package/dist/types/src/components/TextEditor/comments.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/extensions/autocomplete.d.ts.map +1 -0
  17. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  18. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  19. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  20. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  21. package/dist/types/src/extensions/automerge/defs.d.ts +24 -0
  22. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  23. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  24. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/semaphore.d.ts +14 -0
  26. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  28. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  30. package/dist/types/src/extensions/awareness.d.ts +46 -0
  31. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  32. package/dist/types/src/extensions/basic.d.ts +8 -0
  33. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  34. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  35. package/dist/types/src/extensions/code.d.ts.map +1 -0
  36. package/dist/types/src/{components/TextEditor/extensions → extensions}/comments.d.ts +33 -18
  37. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  38. package/dist/types/src/extensions/cursor.d.ts +25 -0
  39. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  40. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  41. package/dist/types/src/extensions/image.d.ts.map +1 -0
  42. package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +4 -2
  43. package/dist/types/src/extensions/index.d.ts.map +1 -0
  44. package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
  45. package/dist/types/src/extensions/link.d.ts.map +1 -0
  46. package/dist/types/src/extensions/listener.d.ts +10 -0
  47. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  48. package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/bundle.d.ts +2 -5
  49. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  50. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  51. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  52. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  53. package/dist/types/src/extensions/outliner.d.ts +4 -0
  54. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  55. package/dist/types/src/extensions/table.d.ts.map +1 -0
  56. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  57. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  58. package/dist/types/src/hooks/awareness-provider.d.ts +31 -0
  59. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  60. package/dist/types/src/hooks/defs.d.ts +25 -0
  61. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  62. package/dist/types/src/hooks/index.d.ts +3 -1
  63. package/dist/types/src/hooks/index.d.ts.map +1 -1
  64. package/dist/types/src/hooks/useTextEditor.d.ts +17 -0
  65. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  66. package/dist/types/src/hooks/useTextModel.d.ts +2 -31
  67. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  68. package/dist/types/src/index.d.ts +5 -0
  69. package/dist/types/src/index.d.ts.map +1 -1
  70. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  71. package/dist/types/src/styles/index.d.ts +1 -0
  72. package/dist/types/src/styles/index.d.ts.map +1 -1
  73. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  74. package/dist/types/src/styles/tokens.d.ts +1 -0
  75. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  76. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +1 -1
  77. package/dist/types/src/themes/default.d.ts.map +1 -0
  78. package/dist/types/src/themes/index.d.ts.map +1 -0
  79. package/dist/types/src/util.d.ts +7 -0
  80. package/dist/types/src/util.d.ts.map +1 -0
  81. package/package.json +33 -34
  82. package/src/components/TextEditor/MarkdownEditor.stories.tsx +86 -91
  83. package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
  84. package/src/components/TextEditor/TextEditor.tsx +124 -95
  85. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
  86. package/src/components/TextEditor/comments.stories.tsx +357 -0
  87. package/src/components/TextEditor/index.ts +0 -3
  88. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +23 -19
  89. package/src/extensions/automerge/automerge.ts +88 -0
  90. package/src/extensions/automerge/cursor.ts +52 -0
  91. package/src/extensions/automerge/defs.ts +41 -0
  92. package/src/extensions/automerge/index.ts +6 -0
  93. package/src/{hooks → extensions}/automerge/semaphore.ts +34 -29
  94. package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
  95. package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
  96. package/src/extensions/awareness.ts +310 -0
  97. package/src/extensions/basic.ts +37 -0
  98. package/src/{components/TextEditor/extensions → extensions}/code.ts +28 -23
  99. package/src/extensions/comments.ts +428 -0
  100. package/src/extensions/cursor.ts +50 -0
  101. package/src/extensions/hr.ts +72 -0
  102. package/src/extensions/image.ts +87 -0
  103. package/src/{components/TextEditor/extensions → extensions}/index.ts +4 -2
  104. package/src/{components/TextEditor/extensions → extensions}/link.ts +116 -62
  105. package/src/extensions/listener.ts +35 -0
  106. package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +18 -24
  107. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +4 -13
  108. package/src/{components/TextEditor/extensions → extensions}/mention.ts +9 -2
  109. package/src/extensions/outliner.ts +12 -0
  110. package/src/{components/TextEditor/extensions → extensions}/table.ts +10 -4
  111. package/src/extensions/tasklist.ts +111 -0
  112. package/src/{components/TextEditor/extensions → extensions}/typewriter.ts +1 -1
  113. package/src/hooks/awareness-provider.ts +124 -0
  114. package/src/hooks/defs.ts +42 -0
  115. package/src/hooks/index.ts +4 -1
  116. package/src/hooks/useTextEditor.ts +29 -0
  117. package/src/hooks/useTextModel.ts +30 -115
  118. package/src/index.ts +5 -0
  119. package/src/styles/index.ts +1 -0
  120. package/src/styles/markdown.ts +0 -2
  121. package/src/styles/tokens.ts +3 -0
  122. package/src/{components/TextEditor/themes → themes}/default.ts +52 -79
  123. package/src/util.ts +41 -0
  124. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  125. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  126. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  127. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +0 -9
  128. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  129. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  130. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  131. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  132. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +0 -1
  133. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  134. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  135. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  136. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  137. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  138. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  139. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  140. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  141. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  142. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +0 -3
  143. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +0 -1
  144. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  145. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  146. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  147. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +0 -1
  149. package/dist/types/src/components/TextEditor/extensions/util.d.ts +0 -5
  150. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +0 -1
  151. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  152. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  153. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  154. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  155. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  156. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  157. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  158. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  159. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  160. package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
  161. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  162. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  163. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  164. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  165. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  166. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  167. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  168. package/dist/types/src/hooks/yjs/space-provider.d.ts +0 -27
  169. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  170. package/dist/types/src/hooks/yjs/yjs.stories.d.ts +0 -12
  171. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  172. package/dist/types/src/hooks/yjs/yjs.test.d.ts +0 -2
  173. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  174. package/dist/types/src/testing/index.d.ts +0 -3
  175. package/dist/types/src/testing/index.d.ts.map +0 -1
  176. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  177. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  178. package/dist/types/src/testing/proto/index.d.ts +0 -2
  179. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  180. package/dist/types/src/testing/replicator.d.ts +0 -45
  181. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  182. package/src/components/TextEditor/codemirror.stories.ts +0 -114
  183. package/src/components/TextEditor/extensions/basic.ts +0 -32
  184. package/src/components/TextEditor/extensions/comments.ts +0 -388
  185. package/src/components/TextEditor/extensions/hr.ts +0 -74
  186. package/src/components/TextEditor/extensions/image.ts +0 -69
  187. package/src/components/TextEditor/extensions/listener.ts +0 -23
  188. package/src/components/TextEditor/extensions/mermaid.ts +0 -11
  189. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  190. package/src/components/TextEditor/extensions/tooltip.ts +0 -61
  191. package/src/components/TextEditor/extensions/util.ts +0 -18
  192. package/src/hooks/automerge/handle.ts +0 -14
  193. package/src/hooks/automerge/index.ts +0 -6
  194. package/src/hooks/automerge/plugin.ts +0 -134
  195. package/src/hooks/yjs/index.ts +0 -6
  196. package/src/hooks/yjs/space-provider.ts +0 -126
  197. package/src/hooks/yjs/yjs.stories.tsx +0 -56
  198. package/src/hooks/yjs/yjs.test.ts +0 -59
  199. package/src/testing/index.ts +0 -6
  200. package/src/testing/proto/gen/schema.ts +0 -49
  201. package/src/testing/proto/index.ts +0 -5
  202. package/src/testing/proto/schema.proto +0 -15
  203. package/src/testing/replicator.ts +0 -189
  204. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  205. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  206. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  207. /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.d.ts +0 -0
  208. /package/dist/types/src/{components/TextEditor/extensions → extensions}/hr.d.ts +0 -0
  209. /package/dist/types/src/{components/TextEditor/extensions → extensions}/image.d.ts +0 -0
  210. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -0
  211. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/index.d.ts +0 -0
  212. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  213. /package/dist/types/src/{components/TextEditor/extensions → extensions}/table.d.ts +0 -0
  214. /package/dist/types/src/{components/TextEditor/extensions → extensions}/tasklist.d.ts +0 -0
  215. /package/dist/types/src/{components/TextEditor/extensions → extensions}/typewriter.d.ts +0 -0
  216. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  217. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  218. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  219. /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
  220. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  221. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -4,15 +4,19 @@
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=
@@ -20,6 +24,7 @@ import { Decoration, EditorView, WidgetType } from '@codemirror/view';
20
24
 
21
25
  export type LinkOptions = {
22
26
  link?: boolean;
27
+ onHover?: (el: Element, url: string) => void;
23
28
  onRender?: (el: Element, url: string) => void;
24
29
  };
25
30
 
@@ -28,27 +33,71 @@ export type LinkOptions = {
28
33
  * https://codemirror.net/docs/ref/#state.StateField
29
34
  */
30
35
  export const link = (options: LinkOptions = {}): Extension => {
31
- return StateField.define<RangeSet<any>>({
32
- create: (state) => update(state, options),
33
- update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
34
- provide: (field) => EditorView.decorations.from(field),
35
- });
36
+ const extensions: Extension[] = [
37
+ ViewPlugin.fromClass(
38
+ class {
39
+ decorations: DecorationSet;
40
+
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: (v) => v.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
+ const url = link && link.getChild('URL');
69
+ if (!url || !link) {
70
+ return null;
71
+ }
72
+ const urlText = view.state.sliceDoc(url.from, url.to);
73
+ return {
74
+ pos: link.from,
75
+ end: link.to,
76
+ above: true,
77
+ create: () => {
78
+ const el = document.createElement('div');
79
+ el.className = tooltipContent({}, 'pli-2 plb-1');
80
+ options.onHover!(el, urlText);
81
+ return { dom: el, offset: { x: 0, y: 4 } };
82
+ },
83
+ };
84
+ }),
85
+ );
86
+ }
87
+
88
+ return extensions;
36
89
  };
37
90
 
38
91
  class LinkButton extends WidgetType {
39
- constructor(
40
- private readonly _pos: number,
41
- private readonly _url: string,
42
- private readonly _onAttach: LinkOptions['onRender'],
43
- ) {
92
+ constructor(private readonly _url: string, private readonly _onAttach: LinkOptions['onRender']) {
44
93
  super();
45
94
  }
46
95
 
47
- override eq(other: LinkButton) {
48
- return this._pos === other._pos && this._url === other._url;
96
+ override eq(other: this) {
97
+ return this._url === other._url;
49
98
  }
50
99
 
51
- toDOM(view: EditorView) {
100
+ override toDOM(view: EditorView) {
52
101
  const el = document.createElement('span');
53
102
  this._onAttach!(el, this._url);
54
103
  return el;
@@ -56,15 +105,15 @@ class LinkButton extends WidgetType {
56
105
  }
57
106
 
58
107
  class LinkText extends WidgetType {
59
- constructor(private readonly _pos: number, private readonly _text: string, private readonly _url?: string) {
108
+ constructor(private readonly _text: string, private readonly _url?: string) {
60
109
  super();
61
110
  }
62
111
 
63
- override eq(other: LinkText) {
64
- return this._pos === other._pos && this._url === other._url;
112
+ override eq(other: this) {
113
+ return this._url === other._url;
65
114
  }
66
115
 
67
- toDOM(view: EditorView) {
116
+ override toDOM(view: EditorView) {
68
117
  const link = document.createElement('a');
69
118
  link.setAttribute('class', 'cm-link');
70
119
  link.textContent = this._text;
@@ -76,7 +125,7 @@ class LinkText extends WidgetType {
76
125
  link.onclick = () => {
77
126
  view.dispatch({
78
127
  selection: {
79
- anchor: this._pos,
128
+ anchor: view.posAtDOM(link),
80
129
  },
81
130
  });
82
131
  };
@@ -91,46 +140,51 @@ class LinkText extends WidgetType {
91
140
  * possibly overlapping ranges in such a way that they can efficiently be mapped though document changes.
92
141
  * https://codemirror.net/docs/ref/#state
93
142
  */
94
- const update = (state: EditorState, options: LinkOptions) => {
95
- const builder = new RangeSetBuilder();
143
+ const buildDecorations = (view: EditorView, options: LinkOptions): DecorationSet => {
144
+ const builder = new RangeSetBuilder<Decoration>();
145
+ const { state } = view;
96
146
  const cursor = state.selection.main.head;
97
147
 
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
- }
109
-
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
- }
148
+ for (const { from, to } of view.visibleRanges) {
149
+ syntaxTree(state).iterate({
150
+ enter: (node) => {
151
+ // Check if cursor is inside text.
152
+ if (node.name === 'Link') {
153
+ const marks = node.node.getChildren('LinkMark');
154
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
155
+ const urlNode = node.node.getChild('URL');
156
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
157
+ if (!url) {
158
+ return false;
159
+ }
160
+
161
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
162
+ builder.add(
163
+ node.from,
164
+ node.to,
165
+ Decoration.replace({
166
+ widget: new LinkText(text, options.link ? url : undefined),
167
+ }),
168
+ );
169
+ }
170
+
171
+ if (options.onRender) {
172
+ builder.add(
173
+ node.to,
174
+ node.to,
175
+ Decoration.replace({
176
+ widget: new LinkButton(url, options.onRender),
177
+ }),
178
+ );
179
+ }
119
180
 
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
- );
181
+ return false;
128
182
  }
129
-
130
- return false;
131
- }
132
- },
133
- });
183
+ },
184
+ from,
185
+ to,
186
+ });
187
+ }
134
188
 
135
189
  return builder.finish();
136
190
  };
@@ -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
+ };
@@ -23,18 +23,15 @@ import {
23
23
  import type { ThemeMode } from '@dxos/react-ui';
24
24
 
25
25
  import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
26
+ import { type TextEditorProps } from '../../components';
26
27
 
27
28
  export type MarkdownBundleOptions = {
28
- readonly?: boolean;
29
29
  themeMode?: ThemeMode;
30
- placeholder?: string;
31
- };
30
+ } & Pick<TextEditorProps, 'readonly' | 'placeholder'>;
32
31
 
33
32
  /**
34
33
  * Markdown bundle.
35
34
  * Refs:
36
- * https://github.com/codemirror/basic-setup
37
- * https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts#L50
38
35
  * https://codemirror.net/docs/community
39
36
  * https://codemirror.net/docs/ref/#codemirror.basicSetup
40
37
  */
@@ -49,8 +46,7 @@ export const markdownBundle = ({
49
46
  EditorState.tabSize.of(2),
50
47
  EditorView.lineWrapping,
51
48
 
52
- customKeymap(),
53
-
49
+ // https://github.com/codemirror/basic-setup
54
50
  bracketMatching(),
55
51
  closeBrackets(),
56
52
  crosshairCursor(),
@@ -59,7 +55,7 @@ export const markdownBundle = ({
59
55
  highlightActiveLine(),
60
56
  history(),
61
57
  indentOnInput(),
62
- _placeholder && placeholder(_placeholder),
58
+ _placeholder ? placeholder(_placeholder) : [],
63
59
 
64
60
  // Main extension.
65
61
  // https://github.com/codemirror/lang-markdown
@@ -87,20 +83,18 @@ export const markdownBundle = ({
87
83
 
88
84
  // Custom styles.
89
85
  syntaxHighlighting(markdownHighlightStyle(readonly)),
90
- ].filter(Boolean) as Extension[];
91
- };
92
86
 
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
- ]);
87
+ keymap.of([
88
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
89
+ indentWithTab,
90
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
91
+ ...closeBracketsKeymap,
92
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
93
+ ...historyKeymap,
94
+ // https://codemirror.net/docs/ref/#search.searchKeymap
95
+ ...searchKeymap,
96
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
97
+ ...standardKeymap,
98
+ ]),
99
+ ];
100
+ };
@@ -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(),
@@ -178,13 +176,6 @@ 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',
@@ -193,12 +184,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
193
184
  // Main content, paragraphs, etc.
194
185
  // {
195
186
  // tag: [tags.content],
196
- // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
187
+ // fontFamily: getToken('fontFamily.body', []).join(','),
197
188
  // },
198
189
  ],
199
190
  {
200
191
  scope: markdownLanguage,
201
- all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
192
+ all: { fontFamily: getToken('fontFamily.body', []).join(',') },
202
193
  },
203
194
  );
204
195
  };
@@ -9,18 +9,25 @@ export type MentionOptions = {
9
9
  onSearch: (text: string) => string[];
10
10
  };
11
11
 
12
+ // TODO(burdon): Can only have a single autocompletion. Merge configuration with autocomplete.
12
13
  export const mention = ({ onSearch }: MentionOptions): Extension => {
13
14
  return autocompletion({
15
+ // TODO(burdon): Not working.
16
+ activateOnTyping: true,
17
+ // activateOnTypingDelay: 100,
18
+ // selectOnOpen: true,
14
19
  closeOnBlur: false, // For debugging.
20
+ // defaultKeymap: false,
21
+ icons: false,
15
22
  override: [
16
23
  (context: CompletionContext): CompletionResult | null => {
24
+ console.log(context);
25
+
17
26
  const match = context.matchBefore(/@(\w+)?/);
18
27
  if (!match || (match.from === match.to && !context.explicit)) {
19
28
  return null;
20
29
  }
21
30
 
22
- console.log(match);
23
-
24
31
  return {
25
32
  from: match.from,
26
33
  options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({ label: `@${value}` })),
@@ -0,0 +1,12 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Extension } from '@codemirror/state';
6
+
7
+ export type OutlinerOptions = {};
8
+
9
+ // TODO(burdon): Experimental component: multiple hierarchical draggable editor sections.
10
+ export const outliner = (options: OutlinerOptions = {}): Extension => {
11
+ return [];
12
+ };
@@ -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
  }
@@ -0,0 +1,111 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { syntaxTree } from '@codemirror/language';
6
+ import { RangeSetBuilder } from '@codemirror/state';
7
+ import { EditorView, Decoration, WidgetType, ViewPlugin, type DecorationSet, type ViewUpdate } from '@codemirror/view';
8
+
9
+ import { getToken } from '../styles';
10
+
11
+ // TODO(burdon): Reconcile with theme.
12
+ const styles = EditorView.baseTheme({
13
+ '& .cm-task': {
14
+ color: getToken('extend.colors.blue.500'),
15
+ },
16
+ '& .cm-task-checkbox': {
17
+ marginLeft: '4px',
18
+ marginRight: '4px',
19
+ },
20
+ });
21
+
22
+ class CheckboxWidget extends WidgetType {
23
+ constructor(private _checked: boolean) {
24
+ super();
25
+ }
26
+
27
+ override eq(other: this) {
28
+ return this._checked === other._checked;
29
+ }
30
+
31
+ override toDOM(view: EditorView) {
32
+ const input = document.createElement('input');
33
+ input.className = 'cm-task-checkbox';
34
+ input.type = 'checkbox';
35
+ input.checked = this._checked;
36
+ if (view.state.readOnly) {
37
+ input.setAttribute('disabled', 'true');
38
+ } else {
39
+ input.onmousedown = (event: Event) => {
40
+ const pos = view.posAtDOM(input);
41
+ const text = view.state.sliceDoc(pos, pos + 3);
42
+ if (text === (this._checked ? '[x]' : '[ ]')) {
43
+ view.dispatch({
44
+ changes: { from: pos + 1, to: pos + 2, insert: this._checked ? ' ' : 'x' },
45
+ });
46
+ event.preventDefault();
47
+ }
48
+ };
49
+ }
50
+
51
+ return input;
52
+ }
53
+
54
+ override ignoreEvent() {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ const checkedDecoration = Decoration.replace({ widget: new CheckboxWidget(true) });
60
+ const uncheckedDecoration = Decoration.replace({ widget: new CheckboxWidget(false) });
61
+
62
+ const buildDecorations = (view: EditorView): DecorationSet => {
63
+ const builder = new RangeSetBuilder<Decoration>();
64
+ const { state } = view;
65
+ const cursor = state.selection.main.head;
66
+
67
+ for (const { from, to } of view.visibleRanges) {
68
+ syntaxTree(state).iterate({
69
+ enter: (node) => {
70
+ if (node.name === 'TaskMarker') {
71
+ // Check if cursor is inside text.
72
+ if (cursor < node.from || cursor > node.to) {
73
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
74
+ builder.add(node.from - 2, node.from - 1, Decoration.mark({ class: 'cm-task' }));
75
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
76
+ }
77
+ }
78
+ },
79
+ from,
80
+ to,
81
+ });
82
+ }
83
+
84
+ return builder.finish();
85
+ };
86
+
87
+ export type TasklistOptions = {};
88
+
89
+ export const tasklist = (options: TasklistOptions = {}) => {
90
+ return [
91
+ ViewPlugin.fromClass(
92
+ class {
93
+ decorations: DecorationSet;
94
+
95
+ constructor(view: EditorView) {
96
+ this.decorations = buildDecorations(view);
97
+ }
98
+
99
+ update(update: ViewUpdate) {
100
+ if (update.docChanged || update.viewportChanged || update.selectionSet) {
101
+ this.decorations = buildDecorations(update.view);
102
+ }
103
+ }
104
+ },
105
+ {
106
+ decorations: (v) => v.decorations,
107
+ },
108
+ ),
109
+ styles,
110
+ ];
111
+ };
@@ -19,7 +19,7 @@ const defaultItems = ['hello world!', 'this is a test.', 'this is [DXOS](https:/
19
19
  */
20
20
  export const typewriter = ({ delay = 75, items = defaultItems }: DemoOptions = {}): Extension => {
21
21
  let t: any;
22
- let idx = 0;
22
+ let idx = 0; // TODO(burdon): Make global.
23
23
 
24
24
  return [
25
25
  keymap.of([