@dxos/react-ui-editor 0.3.10 → 0.3.11-main.00a28cb

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 (222) hide show
  1. package/dist/lib/browser/index.mjs +1724 -1105
  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 +24 -13
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +15 -19
  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 -2
  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/comments.stories.d.ts +26 -0
  14. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  16. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  17. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  18. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  19. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  23. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  25. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  26. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  27. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  28. package/dist/types/src/extensions/automerge/semaphore.d.ts +14 -0
  29. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  30. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  31. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  32. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
  33. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  34. package/dist/types/src/extensions/awareness.d.ts +46 -0
  35. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  36. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  37. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  38. package/dist/types/src/extensions/code.d.ts.map +1 -0
  39. package/dist/types/src/extensions/comments.d.ts +59 -0
  40. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  41. package/dist/types/src/extensions/cursor.d.ts +25 -0
  42. package/dist/types/src/extensions/cursor.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.map +1 -0
  46. package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +7 -3
  47. package/dist/types/src/extensions/index.d.ts.map +1 -0
  48. package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
  49. package/dist/types/src/extensions/link.d.ts.map +1 -0
  50. package/dist/types/src/extensions/listener.d.ts +10 -0
  51. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  52. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  53. package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -1
  54. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  56. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  57. package/dist/types/src/extensions/outliner.d.ts +4 -0
  58. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  59. package/dist/types/src/extensions/table.d.ts.map +1 -0
  60. package/dist/types/src/extensions/tasklist.d.ts +7 -0
  61. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  62. package/dist/types/src/{components/TextEditor/extensions/demo.d.ts → extensions/typewriter.d.ts} +2 -3
  63. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  64. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  65. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  66. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  67. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  68. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  69. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  70. package/dist/types/src/extensions/yjs/yjs.test.d.ts +2 -0
  71. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  72. package/dist/types/src/hooks/automerge.d.ts +3 -0
  73. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  74. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  75. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  76. package/dist/types/src/hooks/index.d.ts +3 -1
  77. package/dist/types/src/hooks/index.d.ts.map +1 -1
  78. package/dist/types/src/hooks/useTextModel.d.ts +15 -11
  79. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  80. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  81. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  82. package/dist/types/src/index.d.ts +6 -0
  83. package/dist/types/src/index.d.ts.map +1 -1
  84. package/dist/types/src/styles/cursors.d.ts.map +1 -0
  85. package/dist/types/src/styles/index.d.ts +1 -0
  86. package/dist/types/src/styles/index.d.ts.map +1 -1
  87. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  88. package/dist/types/src/styles/tokens.d.ts +1 -0
  89. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  90. package/dist/types/src/testing/replicator.d.ts +2 -2
  91. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  92. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +5 -13
  93. package/dist/types/src/themes/default.d.ts.map +1 -0
  94. package/dist/types/src/themes/index.d.ts.map +1 -0
  95. package/dist/types/src/util.d.ts +5 -0
  96. package/dist/types/src/util.d.ts.map +1 -0
  97. package/package.json +28 -22
  98. package/src/components/TextEditor/MarkdownEditor.stories.tsx +150 -87
  99. package/src/components/TextEditor/TextEditor.stories.tsx +14 -35
  100. package/src/components/TextEditor/TextEditor.tsx +87 -79
  101. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +67 -8
  102. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  103. package/src/components/TextEditor/comments.stories.tsx +355 -0
  104. package/src/components/TextEditor/index.ts +0 -3
  105. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  106. package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +18 -17
  107. package/src/extensions/automerge/automerge.ts +92 -0
  108. package/src/extensions/automerge/cursor.ts +52 -0
  109. package/src/extensions/automerge/defs.ts +66 -0
  110. package/src/extensions/automerge/index.ts +6 -0
  111. package/src/{hooks/automerge/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +26 -25
  112. package/src/{hooks/automerge/translation/codemirror-to-automerge.ts → extensions/automerge/update-automerge.ts} +6 -7
  113. package/src/{hooks/automerge/translation/automerge-to-codemirror.ts → extensions/automerge/update-codemirror.ts} +14 -11
  114. package/src/extensions/awareness.ts +310 -0
  115. package/src/extensions/code.ts +104 -0
  116. package/src/extensions/comments.ts +408 -0
  117. package/src/extensions/cursor.ts +46 -0
  118. package/src/extensions/hr.ts +72 -0
  119. package/src/extensions/image.ts +87 -0
  120. package/src/{components/TextEditor/extensions → extensions}/index.ts +7 -3
  121. package/src/{components/TextEditor/extensions → extensions}/link.ts +116 -62
  122. package/src/extensions/listener.ts +35 -0
  123. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +4 -16
  124. package/src/extensions/outliner.ts +12 -0
  125. package/src/{components/TextEditor/extensions → extensions}/table.ts +23 -4
  126. package/src/extensions/tasklist.ts +96 -0
  127. package/src/{components/TextEditor/extensions/demo.ts → extensions/typewriter.ts} +2 -3
  128. package/src/extensions/yjs/cursor.ts +22 -0
  129. package/src/extensions/yjs/index.ts +5 -0
  130. package/src/extensions/yjs/yjs.test.ts +35 -0
  131. package/src/extensions/yjs/yjs.ts +16 -0
  132. package/src/hooks/automerge.ts +52 -0
  133. package/src/hooks/awareness-provider.ts +120 -0
  134. package/src/hooks/index.ts +4 -1
  135. package/src/hooks/useTextModel.ts +40 -79
  136. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +49 -19
  137. package/src/index.ts +6 -0
  138. package/src/styles/index.ts +1 -0
  139. package/src/styles/markdown.ts +0 -2
  140. package/src/styles/tokens.ts +3 -0
  141. package/src/testing/replicator.ts +10 -16
  142. package/src/themes/default.ts +275 -0
  143. package/src/util.ts +18 -0
  144. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  145. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  146. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  147. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -18
  149. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  150. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +0 -1
  151. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +0 -10
  152. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +0 -1
  153. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts +0 -24
  154. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts.map +0 -1
  155. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  156. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  157. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  158. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  159. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  160. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  161. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  162. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  163. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  164. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  165. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +0 -3
  166. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  167. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  168. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  169. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  170. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  171. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts +0 -19
  172. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +0 -1
  173. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  174. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  175. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  176. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  177. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  178. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  179. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  180. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +0 -5
  181. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +0 -1
  182. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +0 -6
  183. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +0 -1
  184. package/dist/types/src/hooks/automerge/translation/index.d.ts +0 -3
  185. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +0 -1
  186. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  187. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  188. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  189. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  190. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  191. package/src/components/TextEditor/extensions/code.ts +0 -99
  192. package/src/components/TextEditor/extensions/comments.ts +0 -173
  193. package/src/components/TextEditor/extensions/experimental.tsx +0 -114
  194. package/src/components/TextEditor/extensions/highlight.ts +0 -128
  195. package/src/components/TextEditor/extensions/image.ts +0 -67
  196. package/src/components/TextEditor/extensions/listener.ts +0 -23
  197. package/src/components/TextEditor/extensions/tasklist.ts +0 -121
  198. package/src/components/TextEditor/extensions/tooltip.tsx +0 -61
  199. package/src/components/TextEditor/themes/default.ts +0 -279
  200. package/src/hooks/automerge/handle.ts +0 -14
  201. package/src/hooks/automerge/index.ts +0 -6
  202. package/src/hooks/automerge/plugin.ts +0 -134
  203. package/src/hooks/automerge/translation/index.ts +0 -6
  204. package/src/hooks/yjs/index.ts +0 -6
  205. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  206. /package/dist/types/src/{components/TextEditor/extensions → extensions}/basic.d.ts +0 -0
  207. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  208. /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.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/bundle.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/{hooks/yjs → styles}/cursors.d.ts +0 -0
  215. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  216. /package/src/{components/TextEditor/extensions → extensions}/basic.ts +0 -0
  217. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  218. /package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +0 -0
  219. /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
  220. /package/src/{components/TextEditor/extensions → extensions}/mention.ts +0 -0
  221. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  222. /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
+ };
@@ -6,23 +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
-
11
- import { mx } from '@dxos/react-ui-theme';
12
9
 
13
10
  import {
14
11
  blockquote,
15
12
  bold,
16
13
  code,
17
14
  codeMark,
15
+ getToken,
18
16
  heading,
19
- horizontalRule,
20
17
  inlineUrl,
21
18
  italic,
22
19
  mark,
23
20
  strikethrough,
24
- tokens,
25
- } from '../../../../styles';
21
+ } from '../../styles';
26
22
 
27
23
  /**
28
24
  * Custom tags defined and processed by the GFM lezer extension.
@@ -35,7 +31,6 @@ export const markdownTags = {
35
31
  CodeText: Tag.define(),
36
32
  EmphasisMark: Tag.define(),
37
33
  HeaderMark: Tag.define(),
38
- HorizontalRule: Tag.define(),
39
34
  InlineCode: Tag.define(),
40
35
  LinkLabel: Tag.define(),
41
36
  LinkReference: Tag.define(),
@@ -181,13 +176,6 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
181
176
  class: blockquote,
182
177
  },
183
178
 
184
- // TODO(burdon): Replace with extension.
185
- {
186
- tag: [markdownTags.HorizontalRule],
187
- class: mx(horizontalRule, readonly && '-indent-[100rem]'),
188
- },
189
-
190
- // TODO(burdon): Only if being edited.
191
179
  {
192
180
  tag: [markdownTags.TableCell],
193
181
  class: 'font-mono',
@@ -196,12 +184,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
196
184
  // Main content, paragraphs, etc.
197
185
  // {
198
186
  // tag: [tags.content],
199
- // fontFamily: get(tokens, 'fontFamily.body', []).join(','),
187
+ // fontFamily: getToken('fontFamily.body', []).join(','),
200
188
  // },
201
189
  ],
202
190
  {
203
191
  scope: markdownLanguage,
204
- all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') },
192
+ all: { fontFamily: getToken('fontFamily.body', []).join(',') },
205
193
  },
206
194
  );
207
195
  };
@@ -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
+ };
@@ -9,6 +9,9 @@ import { Decoration, EditorView, WidgetType } from '@codemirror/view';
9
9
  // TODO(burdon): Snippet to create basic table.
10
10
  // https://codemirror.net/docs/ref/#autocomplete.snippet
11
11
 
12
+ // TODO(burdon): Advanced formatting (left/right/center).
13
+ // TODO(burdon): Editor to auto balance columns.
14
+
12
15
  export type TableOptions = {};
13
16
 
14
17
  /**
@@ -73,7 +76,16 @@ const update = (state: EditorState, options: TableOptions) => {
73
76
 
74
77
  tables.forEach((table) => {
75
78
  const hide = state.readOnly || cursor < table.from || cursor > table.to;
76
- hide && builder.add(table.from, table.to, Decoration.replace({ widget: new TableWidget(table) }));
79
+ hide &&
80
+ builder.add(
81
+ table.from,
82
+ table.to,
83
+ Decoration.replace({
84
+ widget: new TableWidget(table),
85
+ }),
86
+ );
87
+
88
+ // Add class for styling.
77
89
  builder.add(table.from, table.to, Decoration.mark({ class: 'cm-table' }));
78
90
  });
79
91
 
@@ -85,11 +97,14 @@ class TableWidget extends WidgetType {
85
97
  super();
86
98
  }
87
99
 
88
- override eq(other: WidgetType) {
89
- 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
+ );
90
105
  }
91
106
 
92
- toDOM(view: EditorView) {
107
+ override toDOM(view: EditorView) {
93
108
  const table = document.createElement('table');
94
109
 
95
110
  {
@@ -116,4 +131,8 @@ class TableWidget extends WidgetType {
116
131
 
117
132
  return table;
118
133
  }
134
+
135
+ override ignoreEvent(e: Event) {
136
+ return !/^mouse/.test(e.type);
137
+ }
119
138
  }
@@ -0,0 +1,96 @@
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
+ class CheckboxWidget extends WidgetType {
10
+ constructor(private _checked: boolean) {
11
+ super();
12
+ }
13
+
14
+ override eq(other: this) {
15
+ return this._checked === other._checked;
16
+ }
17
+
18
+ override toDOM(view: EditorView) {
19
+ const input = document.createElement('input');
20
+ input.type = 'checkbox';
21
+ input.checked = this._checked;
22
+ if (view.state.readOnly) {
23
+ input.setAttribute('disabled', 'true');
24
+ } else {
25
+ input.onmousedown = (event: Event) => {
26
+ const pos = view.posAtDOM(input);
27
+ const text = view.state.sliceDoc(pos, pos + 3);
28
+ if (text === (this._checked ? '[x]' : '[ ]')) {
29
+ view.dispatch({
30
+ changes: { from: pos + 1, to: pos + 2, insert: this._checked ? ' ' : 'x' },
31
+ });
32
+ event.preventDefault();
33
+ }
34
+ };
35
+ }
36
+
37
+ return input;
38
+ }
39
+
40
+ override ignoreEvent() {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ const checkedDeco = Decoration.replace({ widget: new CheckboxWidget(true) });
46
+ const uncheckedDeco = Decoration.replace({ widget: new CheckboxWidget(false) });
47
+
48
+ export type TasklistOptions = {};
49
+
50
+ export const tasklist = (options: TasklistOptions = {}) => {
51
+ return ViewPlugin.fromClass(
52
+ class {
53
+ decorations: DecorationSet;
54
+
55
+ constructor(view: EditorView) {
56
+ this.decorations = buildDecorations(view);
57
+ }
58
+
59
+ update(update: ViewUpdate) {
60
+ if (update.docChanged || update.viewportChanged || update.selectionSet) {
61
+ this.decorations = buildDecorations(update.view);
62
+ }
63
+ }
64
+ },
65
+ {
66
+ decorations: (v) => v.decorations,
67
+ // TODO(burdon): Is this still required?
68
+ provide: (plugin) =>
69
+ EditorView.atomicRanges.of((view) => {
70
+ return view.plugin(plugin)?.decorations || Decoration.none;
71
+ }),
72
+ },
73
+ );
74
+ };
75
+
76
+ const buildDecorations = (view: EditorView): DecorationSet => {
77
+ const builder = new RangeSetBuilder<Decoration>();
78
+ const { state } = view;
79
+ const cursor = state.selection.main.head;
80
+
81
+ for (const { from, to } of view.visibleRanges) {
82
+ syntaxTree(state).iterate({
83
+ enter: (node) => {
84
+ // Check if cursor is inside text.
85
+ if (node.name === 'TaskMarker' && (cursor < node.from || cursor > node.to)) {
86
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
87
+ builder.add(node.from, node.to, checked ? checkedDeco : uncheckedDeco);
88
+ }
89
+ },
90
+ from,
91
+ to,
92
+ });
93
+ }
94
+
95
+ return builder.finish();
96
+ };
@@ -15,12 +15,11 @@ export type DemoOptions = {
15
15
  const defaultItems = ['hello world!', 'this is a test.', 'this is [DXOS](https://dxos.org)'];
16
16
 
17
17
  /**
18
- * Demo script.
19
18
  * Configurable plugin that let's user cycle through pre-configured input script.
20
19
  */
21
- export const demo = ({ delay = 75, items = defaultItems }: DemoOptions = {}): Extension => {
20
+ export const typewriter = ({ delay = 75, items = defaultItems }: DemoOptions = {}): Extension => {
22
21
  let t: any;
23
- let idx = 0;
22
+ let idx = 0; // TODO(burdon): Make global.
24
23
 
25
24
  return [
26
25
  keymap.of([
@@ -0,0 +1,22 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Y from 'yjs';
6
+
7
+ import { arrayToString, stringToArray } from '@dxos/util';
8
+
9
+ import { type CursorConverter } from '../cursor';
10
+
11
+ export const cursorConverter = (text: Y.Text): CursorConverter => ({
12
+ toCursor: (index, assoc) => {
13
+ return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
14
+ },
15
+
16
+ fromCursor: (cursor) => {
17
+ return (
18
+ Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc!)?.index ??
19
+ 0
20
+ );
21
+ },
22
+ });
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './yjs';
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+ import * as Y from 'yjs';
7
+
8
+ import { TextObject } from '@dxos/echo-schema';
9
+ import { describe, test } from '@dxos/test';
10
+ import { type YText } from '@dxos/text-model';
11
+
12
+ describe('YJS', () => {
13
+ // https://docs.yjs.dev/api/shared-types/y.text
14
+ // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
15
+ test.skip('absolute position', () => {
16
+ const obj = new TextObject('hello world');
17
+ const index = obj.text.indexOf('world');
18
+ const relPos = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(obj.content as YText, index));
19
+
20
+ {
21
+ const cursor = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(relPos), obj.doc!);
22
+ expect(cursor!.index).to.equal(index);
23
+ expect(obj.text.substring(cursor!.index)).to.equal('world');
24
+ }
25
+
26
+ (obj.content as YText).insert(index, 'cruel ');
27
+ expect(obj.text).to.equal('hello cruel world');
28
+
29
+ {
30
+ const cursor = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(relPos), obj.doc!);
31
+ expect(cursor!.index).not.to.equal(index);
32
+ expect(obj.text.substring(cursor!.index)).to.equal('world');
33
+ }
34
+ });
35
+ });
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { yCollab } from 'y-codemirror.next';
6
+ import type * as YP from 'y-protocols/awareness';
7
+
8
+ import type { YText } from '@dxos/text-model';
9
+
10
+ import { cursorConverter } from './cursor';
11
+ import { Cursor } from '../cursor';
12
+
13
+ export const yjs = (content: YText, awareness?: YP.Awareness) => [
14
+ Cursor.converter.of(cursorConverter(content)),
15
+ yCollab(content, awareness),
16
+ ];
@@ -0,0 +1,52 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import get from 'lodash.get';
6
+
7
+ import { type AutomergeTextCompat, getRawDoc } from '@dxos/echo-schema';
8
+ import { isNotNullOrUndefined } from '@dxos/util';
9
+
10
+ import { SpaceAwarenessProvider } from './awareness-provider';
11
+ import { type EditorModel, modelState, type UseTextModelProps } from './useTextModel';
12
+ import { automerge, awareness, AwarenessProvider } from '../extensions';
13
+ import { cursorColor } from '../styles';
14
+
15
+ export const createAutomergeModel = ({ space, identity, text }: UseTextModelProps): EditorModel => {
16
+ const obj = text as any as AutomergeTextCompat;
17
+ const doc = getRawDoc(obj, [obj.field]);
18
+
19
+ const awarenessProvider =
20
+ space &&
21
+ new SpaceAwarenessProvider({
22
+ space,
23
+ channel: `automerge.awareness.${obj.id}`,
24
+ info: {
25
+ displayName: identity?.profile?.displayName ?? '',
26
+ color: cursorColor.color,
27
+ lightColor: cursorColor.light,
28
+ },
29
+ peerId: identity?.identityKey.toHex() ?? 'Anonymous',
30
+ });
31
+
32
+ const model: EditorModel = {
33
+ id: obj.id,
34
+ content: doc,
35
+ text: () => get(doc.handle.docSync(), doc.path),
36
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
37
+ extension: [
38
+ modelState.init(() => model),
39
+ automerge({ handle: doc.handle, path: doc.path }),
40
+ awarenessProvider && AwarenessProvider.of(awarenessProvider),
41
+ awareness(),
42
+ ].filter(isNotNullOrUndefined),
43
+ peer: identity
44
+ ? {
45
+ id: identity.identityKey.toHex(),
46
+ name: identity.profile?.displayName,
47
+ }
48
+ : undefined,
49
+ };
50
+
51
+ return model;
52
+ };