@dxos/ui-editor 0.0.0 → 0.8.4-main.1c7ec43d41

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 (255) hide show
  1. package/dist/lib/browser/index.mjs +8633 -0
  2. package/dist/lib/browser/index.mjs.map +7 -0
  3. package/dist/lib/browser/meta.json +1 -0
  4. package/dist/lib/browser/types/index.mjs +33 -0
  5. package/dist/lib/browser/types/index.mjs.map +7 -0
  6. package/dist/lib/node-esm/index.mjs +8635 -0
  7. package/dist/lib/node-esm/index.mjs.map +7 -0
  8. package/dist/lib/node-esm/meta.json +1 -0
  9. package/dist/lib/node-esm/types/index.mjs +35 -0
  10. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  11. package/dist/types/src/defaults.d.ts +6 -0
  12. package/dist/types/src/defaults.d.ts.map +1 -0
  13. package/dist/types/src/extensions/annotations.d.ts +9 -0
  14. package/dist/types/src/extensions/annotations.d.ts.map +1 -0
  15. package/dist/types/src/extensions/auto-scroll.d.ts +18 -0
  16. package/dist/types/src/extensions/auto-scroll.d.ts.map +1 -0
  17. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts +17 -0
  18. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
  19. package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
  20. package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
  21. package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
  22. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
  23. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +23 -0
  24. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
  25. package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
  26. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/automerge.d.ts +4 -0
  28. package/dist/types/src/extensions/automerge/automerge.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 +4 -0
  32. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/defs.d.ts +17 -0
  34. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  35. package/dist/types/src/extensions/automerge/index.d.ts +2 -0
  36. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  37. package/dist/types/src/extensions/automerge/sync.d.ts +17 -0
  38. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -0
  39. package/dist/types/src/extensions/automerge/update-automerge.d.ts +6 -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 +5 -0
  42. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  43. package/dist/types/src/extensions/awareness/awareness-provider.d.ts +31 -0
  44. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -0
  45. package/dist/types/src/extensions/awareness/awareness.d.ts +46 -0
  46. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -0
  47. package/dist/types/src/extensions/awareness/index.d.ts +3 -0
  48. package/dist/types/src/extensions/awareness/index.d.ts.map +1 -0
  49. package/dist/types/src/extensions/blast.d.ts +25 -0
  50. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  51. package/dist/types/src/extensions/blocks.d.ts +2 -0
  52. package/dist/types/src/extensions/blocks.d.ts.map +1 -0
  53. package/dist/types/src/extensions/bookmarks.d.ts +12 -0
  54. package/dist/types/src/extensions/bookmarks.d.ts.map +1 -0
  55. package/dist/types/src/extensions/comments.d.ts +90 -0
  56. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  57. package/dist/types/src/extensions/debug.d.ts +3 -0
  58. package/dist/types/src/extensions/debug.d.ts.map +1 -0
  59. package/dist/types/src/extensions/dnd.d.ts +9 -0
  60. package/dist/types/src/extensions/dnd.d.ts.map +1 -0
  61. package/dist/types/src/extensions/factories.d.ts +88 -0
  62. package/dist/types/src/extensions/factories.d.ts.map +1 -0
  63. package/dist/types/src/extensions/factories.test.d.ts +2 -0
  64. package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
  65. package/dist/types/src/extensions/focus.d.ts +7 -0
  66. package/dist/types/src/extensions/focus.d.ts.map +1 -0
  67. package/dist/types/src/extensions/folding.d.ts +6 -0
  68. package/dist/types/src/extensions/folding.d.ts.map +1 -0
  69. package/dist/types/src/extensions/hashtag.d.ts +3 -0
  70. package/dist/types/src/extensions/hashtag.d.ts.map +1 -0
  71. package/dist/types/src/extensions/index.d.ts +32 -0
  72. package/dist/types/src/extensions/index.d.ts.map +1 -0
  73. package/dist/types/src/extensions/json.d.ts +7 -0
  74. package/dist/types/src/extensions/json.d.ts.map +1 -0
  75. package/dist/types/src/extensions/listener.d.ts +13 -0
  76. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  77. package/dist/types/src/extensions/markdown/action.d.ts +12 -0
  78. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
  79. package/dist/types/src/extensions/markdown/bundle.d.ts +25 -0
  80. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  81. package/dist/types/src/extensions/markdown/changes.d.ts +10 -0
  82. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -0
  83. package/dist/types/src/extensions/markdown/changes.test.d.ts +2 -0
  84. package/dist/types/src/extensions/markdown/changes.test.d.ts.map +1 -0
  85. package/dist/types/src/extensions/markdown/debug.d.ts +11 -0
  86. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -0
  87. package/dist/types/src/extensions/markdown/decorate.d.ts +25 -0
  88. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -0
  89. package/dist/types/src/extensions/markdown/formatting.d.ts +63 -0
  90. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  91. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  92. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  93. package/dist/types/src/extensions/markdown/highlight.d.ts +37 -0
  94. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  95. package/dist/types/src/extensions/markdown/image.d.ts +7 -0
  96. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  97. package/dist/types/src/extensions/markdown/index.d.ts +10 -0
  98. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  99. package/dist/types/src/extensions/markdown/link.d.ts +7 -0
  100. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  101. package/dist/types/src/extensions/markdown/parser.test.d.ts +2 -0
  102. package/dist/types/src/extensions/markdown/parser.test.d.ts.map +1 -0
  103. package/dist/types/src/extensions/markdown/styles.d.ts +4 -0
  104. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -0
  105. package/dist/types/src/extensions/markdown/table.d.ts +8 -0
  106. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  107. package/dist/types/src/extensions/mention.d.ts +7 -0
  108. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  109. package/dist/types/src/extensions/modal.d.ts +7 -0
  110. package/dist/types/src/extensions/modal.d.ts.map +1 -0
  111. package/dist/types/src/extensions/modes.d.ts +10 -0
  112. package/dist/types/src/extensions/modes.d.ts.map +1 -0
  113. package/dist/types/src/extensions/outliner/commands.d.ts +10 -0
  114. package/dist/types/src/extensions/outliner/commands.d.ts.map +1 -0
  115. package/dist/types/src/extensions/outliner/editor.d.ts +5 -0
  116. package/dist/types/src/extensions/outliner/editor.d.ts.map +1 -0
  117. package/dist/types/src/extensions/outliner/editor.test.d.ts +2 -0
  118. package/dist/types/src/extensions/outliner/editor.test.d.ts.map +1 -0
  119. package/dist/types/src/extensions/outliner/index.d.ts +4 -0
  120. package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
  121. package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
  122. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
  123. package/dist/types/src/extensions/outliner/outliner.d.ts +11 -0
  124. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -0
  125. package/dist/types/src/extensions/outliner/outliner.test.d.ts +2 -0
  126. package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +1 -0
  127. package/dist/types/src/extensions/outliner/selection.d.ts +12 -0
  128. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -0
  129. package/dist/types/src/extensions/outliner/tree.d.ts +79 -0
  130. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -0
  131. package/dist/types/src/extensions/outliner/tree.test.d.ts +2 -0
  132. package/dist/types/src/extensions/outliner/tree.test.d.ts.map +1 -0
  133. package/dist/types/src/extensions/preview/index.d.ts +2 -0
  134. package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
  135. package/dist/types/src/extensions/preview/preview.d.ts +34 -0
  136. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
  137. package/dist/types/src/extensions/replacer.d.ts +21 -0
  138. package/dist/types/src/extensions/replacer.d.ts.map +1 -0
  139. package/dist/types/src/extensions/replacer.test.d.ts +2 -0
  140. package/dist/types/src/extensions/replacer.test.d.ts.map +1 -0
  141. package/dist/types/src/extensions/scroll-past-end.d.ts +3 -0
  142. package/dist/types/src/extensions/scroll-past-end.d.ts.map +1 -0
  143. package/dist/types/src/extensions/scroller.d.ts +68 -0
  144. package/dist/types/src/extensions/scroller.d.ts.map +1 -0
  145. package/dist/types/src/extensions/selection.d.ts +24 -0
  146. package/dist/types/src/extensions/selection.d.ts.map +1 -0
  147. package/dist/types/src/extensions/snippets.d.ts +10 -0
  148. package/dist/types/src/extensions/snippets.d.ts.map +1 -0
  149. package/dist/types/src/extensions/state.d.ts +2 -0
  150. package/dist/types/src/extensions/state.d.ts.map +1 -0
  151. package/dist/types/src/extensions/submit.d.ts +10 -0
  152. package/dist/types/src/extensions/submit.d.ts.map +1 -0
  153. package/dist/types/src/extensions/tags/extended-markdown.d.ts +10 -0
  154. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -0
  155. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts +2 -0
  156. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts.map +1 -0
  157. package/dist/types/src/extensions/tags/fader.d.ts +12 -0
  158. package/dist/types/src/extensions/tags/fader.d.ts.map +1 -0
  159. package/dist/types/src/extensions/tags/index.d.ts +7 -0
  160. package/dist/types/src/extensions/tags/index.d.ts.map +1 -0
  161. package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
  162. package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
  163. package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
  164. package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
  165. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
  166. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
  167. package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
  168. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
  169. package/dist/types/src/extensions/tags/xml-tags.d.ts +117 -0
  170. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -0
  171. package/dist/types/src/extensions/tags/xml-util.d.ts +10 -0
  172. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -0
  173. package/dist/types/src/extensions/tags/xml-util.test.d.ts +2 -0
  174. package/dist/types/src/extensions/tags/xml-util.test.d.ts.map +1 -0
  175. package/dist/types/src/index.d.ts +8 -0
  176. package/dist/types/src/index.d.ts.map +1 -0
  177. package/dist/types/src/styles/index.d.ts +2 -0
  178. package/dist/types/src/styles/index.d.ts.map +1 -0
  179. package/dist/types/src/styles/theme.d.ts +58 -0
  180. package/dist/types/src/styles/theme.d.ts.map +1 -0
  181. package/dist/types/src/types/index.d.ts +2 -0
  182. package/dist/types/src/types/index.d.ts.map +1 -0
  183. package/dist/types/src/types/types.d.ts +21 -0
  184. package/dist/types/src/types/types.d.ts.map +1 -0
  185. package/dist/types/src/util/cursor.d.ts +31 -0
  186. package/dist/types/src/util/cursor.d.ts.map +1 -0
  187. package/dist/types/src/util/debug.d.ts +17 -0
  188. package/dist/types/src/util/debug.d.ts.map +1 -0
  189. package/dist/types/src/util/decorations.d.ts +4 -0
  190. package/dist/types/src/util/decorations.d.ts.map +1 -0
  191. package/dist/types/src/util/dom.d.ts +10 -0
  192. package/dist/types/src/util/dom.d.ts.map +1 -0
  193. package/dist/types/src/util/facet.d.ts +3 -0
  194. package/dist/types/src/util/facet.d.ts.map +1 -0
  195. package/dist/types/src/util/index.d.ts +7 -0
  196. package/dist/types/src/util/index.d.ts.map +1 -0
  197. package/dist/types/src/util/util.d.ts +8 -0
  198. package/dist/types/src/util/util.d.ts.map +1 -0
  199. package/dist/types/tsconfig.tsbuildinfo +1 -0
  200. package/package.json +42 -43
  201. package/src/defaults.ts +33 -20
  202. package/src/extensions/annotations.ts +1 -1
  203. package/src/extensions/auto-scroll.ts +234 -0
  204. package/src/extensions/autocomplete/placeholder.ts +37 -18
  205. package/src/extensions/automerge/automerge.test.tsx +37 -11
  206. package/src/extensions/automerge/automerge.ts +5 -7
  207. package/src/extensions/blocks.ts +5 -5
  208. package/src/extensions/comments.ts +5 -6
  209. package/src/extensions/dnd.ts +2 -2
  210. package/src/extensions/factories.test.ts +88 -0
  211. package/src/extensions/factories.ts +32 -15
  212. package/src/extensions/folding.ts +5 -22
  213. package/src/extensions/index.ts +4 -3
  214. package/src/extensions/markdown/action.ts +0 -1
  215. package/src/extensions/markdown/bundle.ts +23 -9
  216. package/src/extensions/markdown/decorate.ts +15 -12
  217. package/src/extensions/markdown/formatting.ts +5 -10
  218. package/src/extensions/markdown/highlight.ts +15 -7
  219. package/src/extensions/markdown/link.ts +27 -33
  220. package/src/extensions/markdown/parser.test.ts +0 -1
  221. package/src/extensions/markdown/styles.ts +42 -9
  222. package/src/extensions/markdown/table.ts +24 -2
  223. package/src/extensions/outliner/outliner.test.ts +0 -1
  224. package/src/extensions/outliner/outliner.ts +3 -4
  225. package/src/extensions/outliner/tree.test.ts +0 -1
  226. package/src/extensions/preview/preview.ts +62 -15
  227. package/src/extensions/scroll-past-end.ts +32 -0
  228. package/src/extensions/scroller.ts +256 -0
  229. package/src/extensions/selection.ts +1 -1
  230. package/src/extensions/snippets.ts +67 -0
  231. package/src/extensions/tags/extended-markdown.test.ts +120 -2
  232. package/src/extensions/tags/extended-markdown.ts +80 -1
  233. package/src/extensions/tags/fader.ts +195 -0
  234. package/src/extensions/tags/index.ts +4 -1
  235. package/src/extensions/tags/testing/text.md +36 -0
  236. package/src/extensions/tags/testing/text.txt +35 -0
  237. package/src/extensions/tags/typewriter.test.ts +65 -0
  238. package/src/extensions/tags/typewriter.ts +594 -0
  239. package/src/extensions/tags/xml-block-decoration.ts +123 -0
  240. package/src/extensions/tags/xml-formatting.ts +125 -0
  241. package/src/extensions/tags/xml-tags.ts +186 -35
  242. package/src/extensions/tags/xml-util.test.ts +199 -24
  243. package/src/extensions/tags/xml-util.ts +62 -5
  244. package/src/index.ts +0 -1
  245. package/src/styles/index.ts +0 -2
  246. package/src/styles/theme.ts +124 -33
  247. package/src/types/types.ts +10 -2
  248. package/src/typings.d.ts +8 -0
  249. package/src/util/cursor.ts +1 -2
  250. package/src/extensions/autoscroll.ts +0 -165
  251. package/src/extensions/scrolling.ts +0 -189
  252. package/src/extensions/tags/streamer.ts +0 -243
  253. package/src/extensions/typewriter.ts +0 -68
  254. package/src/styles/markdown.ts +0 -26
  255. package/src/styles/tokens.ts +0 -17
@@ -18,7 +18,7 @@ export const formattingStyles = EditorView.theme({
18
18
  width: '100%',
19
19
  height: '0',
20
20
  verticalAlign: 'middle',
21
- borderTop: '1px solid var(--dx-cmSeparator)',
21
+ borderTop: '1px solid var(--color-cm-separator)',
22
22
  opacity: 0.5,
23
23
  },
24
24
 
@@ -43,20 +43,45 @@ export const formattingStyles = EditorView.theme({
43
43
  * Blockquote.
44
44
  */
45
45
  '& .cm-blockquote': {
46
- background: 'var(--dx-cmCodeblock)',
47
- borderLeft: '2px solid var(--dx-cmSeparator)',
46
+ background: 'var(--color-cm-codeblock)',
47
+ borderLeft: '2px solid var(--color-cm-separator)',
48
48
  paddingLeft: '1rem',
49
- margin: '0',
49
+ margin: 0,
50
50
  },
51
51
 
52
52
  /**
53
53
  * Code and codeblocks.
54
54
  */
55
+ '& code': {
56
+ fontFamily: fontMono,
57
+ color: 'var(--color-cm-code)',
58
+ whiteSpace: 'nowrap',
59
+ },
55
60
  '& .cm-code': {
56
61
  fontFamily: fontMono,
62
+ color: 'var(--color-cm-code)',
63
+ },
64
+ // Inline code spans (triggered by backticks) use `cm-code-inline` + `font-mono`.
65
+ // Different monospace font metrics can slightly overflow the fixed CodeMirror line box,
66
+ // so constrain them to the target 24px height.
67
+ '& .cm-code-inline': {
68
+ fontFamily: fontMono,
69
+ height: '24px',
70
+ // display: 'inline-flex',
71
+ alignItems: 'center',
72
+ overflow: 'hidden',
73
+ whiteSpace: 'nowrap',
74
+ color: 'var(--color-cm-code-inline)',
75
+ },
76
+ '& .cm-code-mark': {
77
+ fontFamily: fontMono,
78
+ height: '24px',
79
+ display: 'inline-flex',
80
+ alignItems: 'center',
81
+ overflow: 'hidden',
57
82
  },
58
83
  '& .cm-codeblock-line': {
59
- background: 'var(--dx-cmCodeblock)',
84
+ background: 'var(--color-cm-codeblock)',
60
85
  paddingInline: '1rem !important',
61
86
  },
62
87
  '& .cm-codeblock-start': {
@@ -87,16 +112,24 @@ export const formattingStyles = EditorView.theme({
87
112
  */
88
113
  '.cm-table *': {
89
114
  fontFamily: fontMono,
115
+ lineHeight: 1.5,
90
116
  textDecoration: 'none !important',
91
117
  },
92
118
  '.cm-table-head': {
93
119
  padding: '2px 16px 2px 0px',
120
+ overflowWrap: 'break-word',
121
+ whiteSpace: 'pre-wrap',
122
+ wordBreak: 'keep-all',
94
123
  textAlign: 'left',
95
- borderBottom: '1px solid var(--dx-cmSeparator)',
96
- color: 'var(--dx-subdued)',
124
+ color: 'var(--color-subdued)',
125
+ borderBottom: '1px solid var(--color-cm-separator)',
97
126
  },
98
127
  '.cm-table-cell': {
99
128
  padding: '2px 16px 2px 0px',
129
+ overflowWrap: 'break-word',
130
+ whiteSpace: 'pre-wrap',
131
+ wordBreak: 'keep-all',
132
+ verticalAlign: 'top',
100
133
  },
101
134
 
102
135
  /**
@@ -113,12 +146,12 @@ export const formattingStyles = EditorView.theme({
113
146
  },
114
147
  '.cm-image-with-loader': {
115
148
  display: 'block',
116
- opacity: '0',
149
+ opacity: 0,
117
150
  transitionDuration: '350ms',
118
151
  transitionProperty: 'opacity',
119
152
  },
120
153
  '.cm-image-with-loader.cm-loaded-image': {
121
- opacity: '1',
154
+ opacity: 1,
122
155
  },
123
156
  '.cm-image-wrapper': {
124
157
  'grid-template-columns': '1fr',
@@ -107,6 +107,28 @@ const update = (state: EditorState, _options: TableOptions) => {
107
107
  return builder.finish();
108
108
  };
109
109
 
110
+ /** Renders cell text into el, processing inline markdown (bold, italic, code). */
111
+ const renderCellContent = (el: HTMLElement, text: string): void => {
112
+ const parts = text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*|__[^_\n]+__|\*[^*\n]+\*|_[^_\n]+_)/);
113
+ for (const part of parts) {
114
+ if (part.length > 2 && part.startsWith('`') && part.endsWith('`')) {
115
+ const code = document.createElement('code');
116
+ code.textContent = part.slice(1, -1);
117
+ el.appendChild(code);
118
+ } else if ((part.startsWith('**') && part.endsWith('**')) || (part.startsWith('__') && part.endsWith('__'))) {
119
+ const strong = document.createElement('strong');
120
+ strong.textContent = part.slice(2, -2);
121
+ el.appendChild(strong);
122
+ } else if ((part.startsWith('*') && part.endsWith('*')) || (part.startsWith('_') && part.endsWith('_'))) {
123
+ const em = document.createElement('em');
124
+ em.textContent = part.slice(1, -1);
125
+ el.appendChild(em);
126
+ } else {
127
+ el.appendChild(document.createTextNode(part));
128
+ }
129
+ }
130
+ };
131
+
110
132
  class TableWidget extends WidgetType {
111
133
  constructor(readonly _table: Table) {
112
134
  super();
@@ -132,7 +154,7 @@ class TableWidget extends WidgetType {
132
154
  this._table.header?.forEach((cell) => {
133
155
  const th = document.createElement('th');
134
156
  th.setAttribute('class', 'cm-table-head');
135
- tr.appendChild(th).textContent = cell;
157
+ renderCellContent(tr.appendChild(th), cell);
136
158
  });
137
159
 
138
160
  const body = table.appendChild(document.createElement('tbody'));
@@ -141,7 +163,7 @@ class TableWidget extends WidgetType {
141
163
  row.forEach((cell) => {
142
164
  const td = document.createElement('td');
143
165
  td.setAttribute('class', 'cm-table-cell');
144
- tr.appendChild(td).textContent = cell;
166
+ renderCellContent(tr.appendChild(td), cell);
145
167
  });
146
168
  });
147
169
 
@@ -8,7 +8,6 @@ import { describe, test } from 'vitest';
8
8
 
9
9
  import { join } from '../../util';
10
10
  import { createMarkdownExtensions } from '../markdown';
11
-
12
11
  import { indentItemLess, indentItemMore, moveItemDown, moveItemUp } from './commands';
13
12
  import { listItemToString, outlinerTree, treeFacet } from './tree';
14
13
 
@@ -8,7 +8,6 @@ import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate
8
8
  import { mx } from '@dxos/ui-theme';
9
9
 
10
10
  import { decorateMarkdown } from '../markdown';
11
-
12
11
  import { commands } from './commands';
13
12
  import { editor } from './editor';
14
13
  import { menu } from './menu';
@@ -61,7 +60,7 @@ export const outliner = (_options: OutlinerProps = {}): Extension => [
61
60
  decorateMarkdown({ listPaddingLeft: 8 }),
62
61
 
63
62
  // Researve space for menu.
64
- EditorView.contentAttributes.of({ class: 'is-full !mr-[3rem]' }),
63
+ EditorView.contentAttributes.of({ class: 'w-full !mr-[3rem]' }),
65
64
  ];
66
65
 
67
66
  /**
@@ -157,10 +156,10 @@ const decorations = () => [
157
156
  },
158
157
 
159
158
  '.cm-list-item-focused': {
160
- borderColor: 'var(--dx-neutralFocusIndicator)',
159
+ borderColor: 'var(--color-neutral-focus-indicator)',
161
160
  },
162
161
  '&:focus-within .cm-list-item-selected': {
163
- borderColor: 'var(--dx-separator)',
162
+ borderColor: 'var(--color-separator)',
164
163
  },
165
164
  }),
166
165
  ),
@@ -8,7 +8,6 @@ import { beforeEach, describe, test } from 'vitest';
8
8
 
9
9
  import { type Range } from '../../types';
10
10
  import { join } from '../../util';
11
-
12
11
  import { type Item, listItemToString, outlinerTree, treeFacet } from './tree';
13
12
 
14
13
  const lines = [
@@ -3,10 +3,12 @@
3
3
  //
4
4
 
5
5
  import { syntaxTree } from '@codemirror/language';
6
- import { type EditorState, type Extension, RangeSetBuilder, StateField } from '@codemirror/state';
7
- import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
6
+ import { type EditorState, type Extension, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';
7
+ import { Decoration, type DecorationSet, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
8
8
  import { type SyntaxNode } from '@lezer/common';
9
9
 
10
+ import { type Database, DXN, Entity } from '@dxos/echo';
11
+
10
12
  export type PreviewBlock = {
11
13
  link: PreviewLinkRef;
12
14
  el: HTMLElement;
@@ -16,7 +18,7 @@ export type PreviewLinkRef = {
16
18
  suggest?: boolean;
17
19
  block?: boolean;
18
20
  label: string;
19
- ref: string;
21
+ dxn: string;
20
22
  };
21
23
 
22
24
  export type PreviewLinkTarget = {
@@ -26,22 +28,28 @@ export type PreviewLinkTarget = {
26
28
  };
27
29
 
28
30
  export type PreviewOptions = {
31
+ db?: Database.Database;
29
32
  addBlockContainer?: (block: PreviewBlock) => void;
30
33
  removeBlockContainer?: (block: PreviewBlock) => void;
31
34
  };
32
35
 
36
+ const labelResolvedEffect = StateEffect.define<void>();
37
+
33
38
  /**
34
39
  * Create preview decorations.
35
40
  */
36
41
  export const preview = (options: PreviewOptions = {}): Extension => {
42
+ // Mutable ref so the StateField's onLoad callback can dispatch into the view.
43
+ const viewRef: { current: EditorView | undefined } = { current: undefined };
44
+
37
45
  return [
38
46
  // NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
39
47
  // "Block decorations may not be specified via plugins".
40
48
  StateField.define<DecorationSet>({
41
- create: (state) => buildDecorations(state, options),
49
+ create: (state) => buildDecorations(state, options, viewRef),
42
50
  update: (decorations, tr) => {
43
- if (tr.docChanged) {
44
- return buildDecorations(tr.state, options);
51
+ if (tr.docChanged || tr.effects.some((effect) => effect.is(labelResolvedEffect))) {
52
+ return buildDecorations(tr.state, options, viewRef);
45
53
  }
46
54
 
47
55
  return decorations.map(tr.changes);
@@ -51,14 +59,51 @@ export const preview = (options: PreviewOptions = {}): Extension => {
51
59
  EditorView.atomicRanges.of((view) => view.state.field(field)),
52
60
  ],
53
61
  }),
62
+ ViewPlugin.define((view) => {
63
+ viewRef.current = view;
64
+ return {
65
+ destroy() {
66
+ viewRef.current = undefined;
67
+ },
68
+ };
69
+ }),
54
70
  ];
55
71
  };
56
72
 
73
+ /**
74
+ * Resolve a DXN to a display label using the database.
75
+ */
76
+ const resolveLabel = (
77
+ db: Database.Database,
78
+ dxnStr: string,
79
+ viewRef: { current: EditorView | undefined },
80
+ ): string | undefined => {
81
+ const dxn = DXN.tryParse(dxnStr);
82
+ if (!dxn) {
83
+ return;
84
+ }
85
+
86
+ const ref = db.makeRef(dxn);
87
+ const target = ref.target;
88
+ if (target) {
89
+ return Entity.getLabel(target);
90
+ }
91
+
92
+ // Object not loaded yet — schedule a decoration rebuild when it resolves.
93
+ void ref.tryLoad().then(() => {
94
+ viewRef.current?.dispatch({ effects: labelResolvedEffect.of(undefined) });
95
+ });
96
+ };
97
+
57
98
  /**
58
99
  * Echo references are represented as markdown reference links.
59
100
  * https://www.markdownguide.org/basic-syntax/#reference-style-links
60
101
  */
61
- const buildDecorations = (state: EditorState, options: PreviewOptions): DecorationSet => {
102
+ const buildDecorations = (
103
+ state: EditorState,
104
+ options: PreviewOptions,
105
+ viewRef: { current: EditorView | undefined },
106
+ ): DecorationSet => {
62
107
  const builder = new RangeSetBuilder<Decoration>();
63
108
 
64
109
  syntaxTree(state).iterate({
@@ -71,11 +116,13 @@ const buildDecorations = (state: EditorState, options: PreviewOptions): Decorati
71
116
  case 'Link': {
72
117
  const link = getLinkRef(state, node.node);
73
118
  if (link) {
119
+ const resolved = options.db ? resolveLabel(options.db, link.dxn, viewRef) : undefined;
120
+ const displayLink = resolved ? { ...link, label: resolved } : link;
74
121
  builder.add(
75
122
  node.from,
76
123
  node.to,
77
124
  Decoration.replace({
78
- widget: new PreviewInlineWidget(options, link),
125
+ widget: new PreviewInlineWidget(options, displayLink),
79
126
  side: 1,
80
127
  }),
81
128
  );
@@ -119,13 +166,13 @@ export const getLinkRef = (state: EditorState, node: SyntaxNode): PreviewLinkRef
119
166
  const mark = node.getChildren('LinkMark');
120
167
  const urlNode = node.getChild('URL');
121
168
  if (mark && urlNode) {
122
- const url = state.sliceDoc(urlNode.from, urlNode.to);
123
- if (url.startsWith('dxn:')) {
169
+ const dxn = state.sliceDoc(urlNode.from, urlNode.to);
170
+ if (dxn.startsWith('dxn:')) {
124
171
  const label = state.sliceDoc(mark[0].to, mark[1].from);
125
172
  return {
126
173
  block: state.sliceDoc(mark[0].from, mark[0].from + 1) === '!',
127
174
  label,
128
- ref: url,
175
+ dxn,
129
176
  };
130
177
  }
131
178
  }
@@ -148,14 +195,14 @@ class PreviewInlineWidget extends WidgetType {
148
195
  // }
149
196
 
150
197
  override eq(other: this) {
151
- return this._link.ref === other._link.ref && this._link.label === other._link.label;
198
+ return this._link.dxn === other._link.dxn && this._link.label === other._link.label;
152
199
  }
153
200
 
154
201
  override toDOM(_view: EditorView) {
155
202
  const root = document.createElement('dx-anchor');
156
203
  root.classList.add('dx-tag--anchor');
157
204
  root.textContent = this._link.label;
158
- root.setAttribute('refId', this._link.ref);
205
+ root.setAttribute('dxn', this._link.dxn);
159
206
  return root;
160
207
  }
161
208
  }
@@ -177,12 +224,12 @@ class PreviewBlockWidget extends WidgetType {
177
224
  // }
178
225
 
179
226
  override eq(other: this) {
180
- return this._link.ref === other._link.ref;
227
+ return this._link.dxn === other._link.dxn;
181
228
  }
182
229
 
183
230
  override toDOM(_view: EditorView) {
184
231
  const root = document.createElement('div');
185
- root.classList.add('cm-preview-block', 'density-fine');
232
+ root.classList.add('cm-preview-block', 'dx-density-fine');
186
233
  this._options.addBlockContainer?.({ link: this._link, el: root });
187
234
  return root;
188
235
  }
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Extension } from '@codemirror/state';
6
+ import { EditorView, ViewPlugin } from '@codemirror/view';
7
+
8
+ /**
9
+ * Custom scroll-past-end extension that accounts for the actual height of the last line.
10
+ * The built-in CodeMirror `scrollPastEnd` uses `defaultLineHeight` which doesn't account
11
+ * for taller elements like headings, block widgets, etc.
12
+ */
13
+ const scrollPastEndPlugin = ViewPlugin.fromClass(
14
+ class {
15
+ height = 1000;
16
+ attrs: { style: string } | null = { style: 'padding-bottom: 1000px' };
17
+
18
+ update({ view }: { view: EditorView }) {
19
+ const lastLineBlock = view.lineBlockAt(view.state.doc.length);
20
+ const height = view.dom.clientHeight - lastLineBlock.height - view.documentPadding.top - 0.5;
21
+ if (height >= 0 && height !== this.height) {
22
+ this.height = height;
23
+ this.attrs = { style: `padding-bottom: ${height}px` };
24
+ }
25
+ }
26
+ },
27
+ );
28
+
29
+ export const scrollPastEnd = (): Extension => [
30
+ scrollPastEndPlugin,
31
+ EditorView.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?.attrs ?? null),
32
+ ];
@@ -0,0 +1,256 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { StateEffect } from '@codemirror/state';
6
+ import { EditorView, ViewPlugin } from '@codemirror/view';
7
+
8
+ import { log } from '@dxos/log';
9
+
10
+ /**
11
+ * Parameters for the scroll to line effect.
12
+ */
13
+ export type ScrollToProps = {
14
+ /**
15
+ * Zero-based line number; -1 for end of document.
16
+ * NOTE: view.state.doc.lineAt() uses 1-based line numbers.
17
+ */
18
+ line: number;
19
+
20
+ /**
21
+ * Additional offset from the target line in pixels.
22
+ * Positive values scroll past the line, negative values stop before it.
23
+ * @default 0
24
+ */
25
+ offset?: number;
26
+
27
+ /**
28
+ * Position of the target line in the viewport.
29
+ * - 'start': Line appears at the start (top) of the screen
30
+ * - 'end': Line appears at the end (bottom) of the screen
31
+ * @default 'start'
32
+ */
33
+ position?: 'start' | 'end';
34
+
35
+ /**
36
+ * Whether to use smooth scrolling.
37
+ * @default 'instant'
38
+ */
39
+ behavior?: ScrollBehavior;
40
+ };
41
+
42
+ /** Scroll to a specific line. */
43
+ export const scrollerLineEffect = StateEffect.define<ScrollToProps>();
44
+
45
+ /** Start/stop crawling the end of the document. */
46
+ export const scrollerCrawlEffect = StateEffect.define<boolean>();
47
+
48
+ /**
49
+ * Helper function to scroll to a specific line.
50
+ * This is a convenience function that can be used directly with an EditorView.
51
+ */
52
+ export const scrollToLine = (view: EditorView, options: ScrollToProps) => {
53
+ view.dispatch({
54
+ effects: scrollerLineEffect.of(options),
55
+ });
56
+ };
57
+
58
+ export type ScrollerOptions = {
59
+ /** Threshold in px to trigger scroll from bottom. */
60
+ overScroll?: number;
61
+ };
62
+
63
+ /**
64
+ * Extension that provides smooth scrolling to specific lines in the editor.
65
+ */
66
+ export const scroller = ({ overScroll = 0 }: ScrollerOptions = {}) => {
67
+ // ViewPlugin to manage scroll animations.
68
+ const scrollPlugin = ViewPlugin.fromClass(
69
+ class ScrollerPlugin {
70
+ private readonly crawler: ReturnType<typeof createCrawler>;
71
+ constructor(private readonly view: EditorView) {
72
+ this.crawler = createCrawler(this.view);
73
+ }
74
+
75
+ // No-op.
76
+ destroy() {
77
+ this.crawler.cancel();
78
+ }
79
+
80
+ cancel() {
81
+ this.crawler.cancel();
82
+ }
83
+
84
+ crawl(start = false) {
85
+ if (start) {
86
+ this.crawler.scroll();
87
+ } else {
88
+ this.crawler.cancel();
89
+ }
90
+ }
91
+
92
+ scroll({ line, offset = 0, position, behavior = 'instant' }: ScrollToProps) {
93
+ const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
94
+ const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
95
+ const doc = this.view.state.doc;
96
+
97
+ let targetScrollTop = scrollHeight - clientHeight + offset;
98
+ if (line >= 0 && line <= doc.lines - 1) {
99
+ const lineStart = doc.line(line + 1).from;
100
+ const coords = this.view.coordsAtPos(lineStart);
101
+ if (coords) {
102
+ // Calculate target scroll position based on position option.
103
+ const currentScrollTop = scrollTop;
104
+ const maxScrollTop = scrollHeight - clientHeight;
105
+
106
+ if (position === 'end') {
107
+ // Position line at end (bottom) of viewport.
108
+ // Calculate how far down we need to scroll so the line's bottom aligns with viewport bottom.
109
+ targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
110
+ } else {
111
+ // Default: position line at start (top) of viewport.
112
+ targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
113
+ }
114
+
115
+ // Clamp to valid scroll range.
116
+ targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
117
+ }
118
+ }
119
+
120
+ // TODO(burdon): Smooth scrolling doesn't work when the document is being streamed into.
121
+ requestAnimationFrame(() => {
122
+ this.view.scrollDOM.scrollTo({ top: targetScrollTop }); //, behavior });
123
+ });
124
+ }
125
+ },
126
+ );
127
+
128
+ return [
129
+ scrollPlugin,
130
+
131
+ // Listen for effect.
132
+ EditorView.updateListener.of((update) => {
133
+ update.transactions.forEach((transaction) => {
134
+ try {
135
+ const plugin = update.view.plugin(scrollPlugin);
136
+ if (plugin) {
137
+ for (const effect of transaction.effects) {
138
+ if (effect.is(scrollerCrawlEffect)) {
139
+ plugin.crawl(effect.value);
140
+ } else if (effect.is(scrollerLineEffect)) {
141
+ plugin.scroll(effect.value);
142
+ }
143
+ }
144
+ }
145
+ } catch (err) {
146
+ log.catch(err);
147
+ }
148
+ });
149
+ }),
150
+
151
+ // Styles.
152
+ EditorView.theme({
153
+ '.cm-scroller': {
154
+ overflowY: 'scroll',
155
+ // Browser scroll-anchoring: when widgets above the viewport resize (e.g. tool blocks
156
+ // expanding their TogglePanel), the browser picks a stable element near the viewport
157
+ // top and adjusts `scrollTop` so the user's view doesn't jump. Auto-scroll's pinning
158
+ // logic still has the final word when pinned (forces scrollTop to scrollHeight).
159
+ overflowAnchor: 'auto',
160
+ },
161
+ '.cm-scroller.cm-hide-scrollbar::-webkit-scrollbar': {
162
+ display: 'none',
163
+ },
164
+ '.cm-scroller::-webkit-scrollbar-thumb': {
165
+ background: 'transparent',
166
+ transition: 'background 0.15s',
167
+ },
168
+ '&:hover .cm-scroller::-webkit-scrollbar-thumb': {
169
+ background: 'var(--color-scrollbar-thumb)',
170
+ },
171
+ // Spacer below the last text line. Implemented as a real block pseudo-element
172
+ // (rather than `padding-bottom` on `.cm-content`) so it materializes in the
173
+ // scroller's `scrollHeight` regardless of how `padding` is reset by the base
174
+ // theme or downstream classes — this is what gives auto-scroll its head-room
175
+ // so the last line stays `overScroll` px above the viewport bottom.
176
+ '.cm-content::after': {
177
+ content: '""',
178
+ display: 'block',
179
+ height: `${overScroll}px`,
180
+ },
181
+ '.cm-scroll-button': {
182
+ position: 'absolute',
183
+ bottom: '0.5rem',
184
+ right: '1rem',
185
+ },
186
+ }),
187
+ ];
188
+ };
189
+
190
+ /**
191
+ * Creates a smooth crawler that follows the live bottom of a CodeMirror 6 EditorView.
192
+ *
193
+ * Uses a critically-damped spring: each frame applies a restoring force toward the
194
+ * target and a damping force opposing current velocity. With damping = 2·ω, the
195
+ * system is critically damped — fastest approach without overshoot. The spring
196
+ * naturally sprints when far behind and eases as it approaches, so streaming
197
+ * content is followed tightly without the jerk of explicit accel/decel state.
198
+ *
199
+ * Integration uses real elapsed wall-clock time so the perceived speed stays
200
+ * constant when requestAnimationFrame is throttled (e.g. low-power mode dropping
201
+ * from 60Hz to 30Hz).
202
+ *
203
+ * @param omega Spring stiffness in rad/s. Higher = snappier follow. ~12–18 feels good.
204
+ * @param snapThreshold Snap-to-target distance threshold in px.
205
+ * @param snapVelocity Snap-to-target velocity threshold in px/s.
206
+ */
207
+ export function createCrawler(view: EditorView, omega = 5, snapThreshold = 5, snapVelocity = 50) {
208
+ const el = view.scrollDOM;
209
+
210
+ let currentTop = 0;
211
+ let velocity = 0;
212
+ let rafId: number | null = null;
213
+ let lastTime = 0;
214
+
215
+ function frame(now: number) {
216
+ // Clamp dt to handle long pauses (tab backgrounded) and the first frame.
217
+ const dt = lastTime === 0 ? 1 / 60 : Math.min(0.1, (now - lastTime) / 1000);
218
+ lastTime = now;
219
+
220
+ const targetTop = el.scrollHeight - el.clientHeight;
221
+ const delta = targetTop - currentTop;
222
+ if (Math.abs(delta) < snapThreshold && Math.abs(velocity) < snapVelocity) {
223
+ el.scrollTop = targetTop;
224
+ currentTop = targetTop;
225
+ velocity = 0;
226
+ rafId = null;
227
+ lastTime = 0;
228
+ return;
229
+ }
230
+
231
+ // Critically-damped spring: a = ω²·delta − 2ω·v.
232
+ const accel = omega * omega * delta - 2 * omega * velocity;
233
+ velocity += accel * dt;
234
+ currentTop += velocity * dt;
235
+ el.scrollTop = currentTop;
236
+ rafId = requestAnimationFrame(frame);
237
+ }
238
+
239
+ return {
240
+ scroll: () => {
241
+ if (rafId === null) {
242
+ currentTop = el.scrollTop;
243
+ lastTime = 0;
244
+ rafId = requestAnimationFrame(frame);
245
+ }
246
+ },
247
+ cancel: () => {
248
+ if (rafId !== null) {
249
+ cancelAnimationFrame(rafId);
250
+ velocity = 0;
251
+ lastTime = 0;
252
+ rafId = null;
253
+ }
254
+ },
255
+ };
256
+ }
@@ -31,7 +31,7 @@ export type EditorStateStore = {
31
31
  getState: (id: string) => EditorSelectionState | undefined;
32
32
  };
33
33
 
34
- const stateRestoreAnnotation = 'dxos.org/cm/state-restore';
34
+ const stateRestoreAnnotation = 'org.dxos.cm.state-restore';
35
35
 
36
36
  export const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {
37
37
  return {