@dxos/react-ui-editor 0.3.11-main.3f845e1 → 0.3.11-main.463e5cd

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 (259) hide show
  1. package/dist/lib/browser/index.mjs +2578 -1622
  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 +25 -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 +23 -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 +67 -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 +13 -0
  59. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  60. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  61. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  62. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  63. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  64. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  65. package/dist/types/src/extensions/markdown/index.d.ts +11 -0
  66. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  67. package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/link.d.ts +1 -0
  68. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  69. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  70. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  71. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  72. package/dist/types/src/extensions/outliner.d.ts +4 -0
  73. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  74. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  75. package/dist/types/src/hooks/awareness-provider.d.ts +31 -0
  76. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  77. package/dist/types/src/hooks/defs.d.ts +25 -0
  78. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  79. package/dist/types/src/hooks/index.d.ts +5 -1
  80. package/dist/types/src/hooks/index.d.ts.map +1 -1
  81. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  82. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  83. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  84. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  85. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  86. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  87. package/dist/types/src/hooks/useTextModel.d.ts +14 -31
  88. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  89. package/dist/types/src/index.d.ts +5 -0
  90. package/dist/types/src/index.d.ts.map +1 -1
  91. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  92. package/dist/types/src/styles/index.d.ts +1 -0
  93. package/dist/types/src/styles/index.d.ts.map +1 -1
  94. package/dist/types/src/styles/markdown.d.ts +2 -2
  95. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  96. package/dist/types/src/styles/tokens.d.ts +1 -0
  97. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  98. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +1 -1
  99. package/dist/types/src/themes/default.d.ts.map +1 -0
  100. package/dist/types/src/themes/index.d.ts.map +1 -0
  101. package/dist/types/src/util.d.ts +7 -0
  102. package/dist/types/src/util.d.ts.map +1 -0
  103. package/package.json +48 -36
  104. package/src/components/TextEditor/MarkdownEditor.stories.tsx +93 -102
  105. package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
  106. package/src/components/TextEditor/TextEditor.tsx +137 -97
  107. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +65 -6
  108. package/src/components/TextEditor/hooks.stories.tsx +106 -0
  109. package/src/components/TextEditor/index.ts +0 -3
  110. package/src/components/Toolbar/Toolbar.stories.tsx +90 -0
  111. package/src/components/Toolbar/Toolbar.tsx +166 -0
  112. package/src/components/Toolbar/index.ts +5 -0
  113. package/src/components/index.ts +1 -0
  114. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +23 -19
  115. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  116. package/src/extensions/automerge/automerge.test.ts +13 -0
  117. package/src/extensions/automerge/automerge.ts +103 -0
  118. package/src/extensions/automerge/cursor.ts +52 -0
  119. package/src/extensions/automerge/defs.ts +41 -0
  120. package/src/extensions/automerge/index.ts +6 -0
  121. package/src/extensions/automerge/semaphore.ts +81 -0
  122. package/src/extensions/automerge/update-automerge.ts +49 -0
  123. package/src/{hooks → extensions}/automerge/update-codemirror.ts +17 -9
  124. package/src/extensions/awareness.ts +313 -0
  125. package/src/{components/TextEditor/extensions → extensions}/basic.ts +17 -10
  126. package/src/{components/TextEditor/extensions → extensions}/blast.ts +4 -1
  127. package/src/extensions/comments.ts +476 -0
  128. package/src/extensions/cursor.ts +50 -0
  129. package/src/{components/TextEditor/extensions → extensions}/index.ts +4 -8
  130. package/src/extensions/listener.ts +35 -0
  131. package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +23 -28
  132. package/src/extensions/markdown/code.ts +150 -0
  133. package/src/extensions/markdown/formatting.ts +188 -0
  134. package/src/extensions/markdown/heading.ts +59 -0
  135. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +12 -27
  136. package/src/extensions/markdown/hr.ts +71 -0
  137. package/src/extensions/markdown/image.ts +87 -0
  138. package/src/extensions/markdown/index.ts +14 -0
  139. package/src/{components/TextEditor/extensions → extensions/markdown}/link.ts +121 -58
  140. package/src/{components/TextEditor/extensions → extensions/markdown}/table.ts +10 -4
  141. package/src/extensions/markdown/tasklist.ts +110 -0
  142. package/src/{components/TextEditor/extensions → extensions}/mention.ts +9 -2
  143. package/src/extensions/outliner.ts +12 -0
  144. package/src/{components/TextEditor/extensions → extensions}/typewriter.ts +1 -1
  145. package/src/hooks/awareness-provider.ts +124 -0
  146. package/src/hooks/defs.ts +42 -0
  147. package/src/hooks/index.ts +6 -1
  148. package/src/hooks/useActionHandler.ts +62 -0
  149. package/src/hooks/useEditorView.ts +29 -0
  150. package/src/hooks/useTextEditor.ts +153 -0
  151. package/src/hooks/useTextModel.ts +48 -114
  152. package/src/index.ts +5 -0
  153. package/src/styles/index.ts +1 -0
  154. package/src/styles/markdown.ts +9 -12
  155. package/src/styles/tokens.ts +3 -0
  156. package/src/{components/TextEditor/themes → themes}/default.ts +58 -82
  157. package/src/util.ts +41 -0
  158. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  159. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  160. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  161. package/dist/types/src/components/TextEditor/extensions/basic.d.ts +0 -9
  162. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  163. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  164. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  165. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -48
  166. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  167. package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +0 -1
  168. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  169. package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -17
  170. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  171. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  172. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  173. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  174. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -17
  175. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  176. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  177. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
  178. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  179. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  180. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +0 -3
  181. package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +0 -1
  182. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  183. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  184. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  185. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  186. package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +0 -1
  187. package/dist/types/src/components/TextEditor/extensions/util.d.ts +0 -5
  188. package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +0 -1
  189. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  190. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  191. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  192. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  193. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  194. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  195. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  196. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  197. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  198. package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
  199. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
  200. package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
  201. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
  202. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
  203. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  204. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  205. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  206. package/dist/types/src/hooks/yjs/space-provider.d.ts +0 -27
  207. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  208. package/dist/types/src/hooks/yjs/yjs.stories.d.ts +0 -12
  209. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  210. package/dist/types/src/hooks/yjs/yjs.test.d.ts +0 -2
  211. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
  212. package/dist/types/src/testing/index.d.ts +0 -3
  213. package/dist/types/src/testing/index.d.ts.map +0 -1
  214. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  215. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  216. package/dist/types/src/testing/proto/index.d.ts +0 -2
  217. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  218. package/dist/types/src/testing/replicator.d.ts +0 -45
  219. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  220. package/src/components/TextEditor/codemirror.stories.ts +0 -114
  221. package/src/components/TextEditor/extensions/code.ts +0 -99
  222. package/src/components/TextEditor/extensions/comments.ts +0 -388
  223. package/src/components/TextEditor/extensions/hr.ts +0 -74
  224. package/src/components/TextEditor/extensions/image.ts +0 -69
  225. package/src/components/TextEditor/extensions/listener.ts +0 -23
  226. package/src/components/TextEditor/extensions/markdown/index.ts +0 -6
  227. package/src/components/TextEditor/extensions/mermaid.ts +0 -11
  228. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  229. package/src/components/TextEditor/extensions/tooltip.ts +0 -61
  230. package/src/components/TextEditor/extensions/util.ts +0 -18
  231. package/src/hooks/automerge/handle.ts +0 -14
  232. package/src/hooks/automerge/index.ts +0 -6
  233. package/src/hooks/automerge/plugin.ts +0 -134
  234. package/src/hooks/automerge/semaphore.ts +0 -78
  235. package/src/hooks/automerge/update-automerge.ts +0 -44
  236. package/src/hooks/yjs/index.ts +0 -6
  237. package/src/hooks/yjs/space-provider.ts +0 -126
  238. package/src/hooks/yjs/yjs.stories.tsx +0 -56
  239. package/src/hooks/yjs/yjs.test.ts +0 -59
  240. package/src/testing/index.ts +0 -6
  241. package/src/testing/proto/gen/schema.ts +0 -49
  242. package/src/testing/proto/index.ts +0 -5
  243. package/src/testing/proto/schema.proto +0 -15
  244. package/src/testing/replicator.ts +0 -189
  245. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  246. /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
  247. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  248. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/code.d.ts +0 -0
  249. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -0
  250. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/hr.d.ts +0 -0
  251. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/image.d.ts +0 -0
  252. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/table.d.ts +0 -0
  253. /package/dist/types/src/{components/TextEditor/extensions → extensions/markdown}/tasklist.d.ts +0 -0
  254. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  255. /package/dist/types/src/{components/TextEditor/extensions → extensions}/typewriter.d.ts +0 -0
  256. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  257. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  258. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  259. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -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
  }
@@ -0,0 +1,110 @@
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
+ constructor(view: EditorView) {
95
+ this.decorations = buildDecorations(view);
96
+ }
97
+
98
+ update(update: ViewUpdate) {
99
+ if (update.docChanged || update.viewportChanged || update.selectionSet) {
100
+ this.decorations = buildDecorations(update.view);
101
+ }
102
+ }
103
+ },
104
+ {
105
+ decorations: (value) => value.decorations,
106
+ },
107
+ ),
108
+ styles,
109
+ ];
110
+ };
@@ -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
+ };
@@ -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([
@@ -0,0 +1,124 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { DeferredTask, Event, sleep } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
7
+ import { invariant } from '@dxos/invariant';
8
+ import { log } from '@dxos/log';
9
+ import { type Space } from '@dxos/react-client/echo';
10
+ import { type GossipMessage } from '@dxos/react-client/mesh';
11
+
12
+ import { type AwarenessInfo, type AwarenessPosition, type AwarenessProvider, type AwarenessState } from '../extensions';
13
+
14
+ type ProtocolMessage =
15
+ | {
16
+ kind: 'query';
17
+ }
18
+ | {
19
+ kind: 'post';
20
+ state: AwarenessState;
21
+ };
22
+
23
+ const DEBOUNCE_INTERVAL = 100; // ms
24
+
25
+ export type AwarenessProviderParams = {
26
+ space: Space;
27
+ channel: string;
28
+ peerId: string;
29
+ info: AwarenessInfo;
30
+ };
31
+
32
+ /**
33
+ * Receives and broadcasts profile and cursor position.
34
+ */
35
+ export class SpaceAwarenessProvider implements AwarenessProvider {
36
+ private readonly _remoteStates = new Map<string, AwarenessState>();
37
+
38
+ private readonly _space: Space;
39
+ private readonly _channel: string;
40
+ private readonly _peerId: string;
41
+ private readonly _info: AwarenessInfo;
42
+
43
+ private _ctx?: Context;
44
+ private _postTask?: DeferredTask;
45
+ private _localState?: AwarenessState;
46
+
47
+ public readonly remoteStateChange = new Event<void>();
48
+
49
+ constructor(params: AwarenessProviderParams) {
50
+ this._space = params.space;
51
+ this._channel = params.channel;
52
+ this._peerId = params.peerId;
53
+ this._info = params.info;
54
+ }
55
+
56
+ open() {
57
+ this._ctx = new Context();
58
+ this._postTask = new DeferredTask(this._ctx, async () => {
59
+ if (this._localState) {
60
+ await this._space.postMessage(this._channel, {
61
+ kind: 'post',
62
+ state: this._localState,
63
+ } satisfies ProtocolMessage);
64
+
65
+ await sleep(DEBOUNCE_INTERVAL);
66
+ }
67
+ });
68
+
69
+ this._ctx.onDispose(
70
+ this._space.listen(this._channel, (message: GossipMessage) => {
71
+ switch (message.payload.kind) {
72
+ case 'query': {
73
+ this._handleQueryMessage();
74
+ break;
75
+ }
76
+ case 'post': {
77
+ this._handlePostMessage(message.payload);
78
+ break;
79
+ }
80
+ }
81
+ }),
82
+ );
83
+
84
+ void this._space
85
+ .postMessage(this._channel, {
86
+ kind: 'query',
87
+ } satisfies ProtocolMessage)
88
+ .catch((err) => {
89
+ log.debug('failed to query awareness', { err });
90
+ });
91
+ }
92
+
93
+ close() {
94
+ void this._ctx?.dispose();
95
+ this._ctx = undefined;
96
+ this._postTask = undefined;
97
+ }
98
+
99
+ getRemoteStates(): AwarenessState[] {
100
+ return Array.from(this._remoteStates.values());
101
+ }
102
+
103
+ update(position: AwarenessPosition | undefined): void {
104
+ invariant(this._postTask);
105
+ this._localState = {
106
+ peerId: this._peerId,
107
+ position,
108
+ info: this._info,
109
+ };
110
+
111
+ this._postTask.schedule();
112
+ }
113
+
114
+ private _handleQueryMessage() {
115
+ invariant(this._postTask);
116
+ this._postTask.schedule();
117
+ }
118
+
119
+ private _handlePostMessage(message: ProtocolMessage) {
120
+ invariant(message.kind === 'post');
121
+ this._remoteStates.set(message.state.peerId, message.state);
122
+ this.remoteStateChange.emit();
123
+ }
124
+ }
@@ -0,0 +1,42 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Extension, StateField } from '@codemirror/state';
6
+
7
+ import { type DocAccessor } from '@dxos/echo-schema';
8
+
9
+ // Runtime data structure.
10
+ export type Range = {
11
+ from: number;
12
+ to: number;
13
+ };
14
+
15
+ // Persistent data structure.
16
+ export type Comment = {
17
+ id: string;
18
+ cursor?: string;
19
+ };
20
+
21
+ export type EditorModel = {
22
+ id: string;
23
+ text: () => string;
24
+ extension?: Extension;
25
+
26
+ // TODO(burdon): Remove.
27
+ content: string | DocAccessor;
28
+
29
+ // TODO(burdon): Remove.
30
+ peer?: {
31
+ id: string;
32
+ name?: string;
33
+ };
34
+ };
35
+
36
+ /**
37
+ * State field makes the model available to other extensions.
38
+ */
39
+ export const modelState = StateField.define<EditorModel | undefined>({
40
+ create: () => undefined,
41
+ update: (model) => model,
42
+ });
@@ -2,5 +2,10 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './awareness-provider';
6
+ export * from './defs';
7
+
8
+ export * from './useActionHandler';
9
+ export * from './useEditorView';
10
+ export * from './useTextEditor';
5
11
  export * from './useTextModel';
6
- export * from './yjs';
@@ -0,0 +1,62 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type { EditorView } from '@codemirror/view';
6
+
7
+ import type { ToolbarProps } from '../components';
8
+ import {
9
+ createComment,
10
+ insertCodeblock,
11
+ insertTable,
12
+ setHeading,
13
+ toggleBold,
14
+ toggleItalic,
15
+ toggleList,
16
+ toggleStrikethrough,
17
+ } from '../extensions';
18
+
19
+ export const useActionHandler = (view?: EditorView | null): ToolbarProps['onAction'] => {
20
+ return (action) => {
21
+ if (!view) {
22
+ return;
23
+ }
24
+
25
+ switch (action.type) {
26
+ case 'heading':
27
+ setHeading(parseInt(action.data))(view);
28
+ break;
29
+
30
+ case 'bold':
31
+ toggleBold(view);
32
+ break;
33
+ case 'italic':
34
+ toggleItalic(view);
35
+ break;
36
+ case 'strikethrough':
37
+ toggleStrikethrough(view);
38
+ break;
39
+
40
+ case 'list':
41
+ toggleList(view);
42
+ break;
43
+
44
+ case 'codeblock':
45
+ insertCodeblock(view);
46
+ break;
47
+ case 'table':
48
+ insertTable(view);
49
+ break;
50
+
51
+ case 'comment':
52
+ createComment(view);
53
+ break;
54
+ }
55
+
56
+ // TODO(burdon): Hack otherwise remains on heading selector.
57
+ setTimeout(() => {
58
+ view.focus();
59
+ view.dispatch({ selection: view.state.selection });
60
+ });
61
+ };
62
+ };