@dxos/react-ui-editor 0.8.2-main.f11618f → 0.8.2-staging.42af850

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 (247) hide show
  1. package/dist/lib/browser/index.mjs +4450 -3278
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/testing/index.mjs +3 -64
  5. package/dist/lib/browser/testing/index.mjs.map +4 -4
  6. package/dist/lib/node/index.cjs +2701 -1528
  7. package/dist/lib/node/index.cjs.map +4 -4
  8. package/dist/lib/node/meta.json +1 -1
  9. package/dist/lib/node/testing/index.cjs +3 -75
  10. package/dist/lib/node/testing/index.cjs.map +4 -4
  11. package/dist/lib/node-esm/index.mjs +4450 -3278
  12. package/dist/lib/node-esm/index.mjs.map +4 -4
  13. package/dist/lib/node-esm/meta.json +1 -1
  14. package/dist/lib/node-esm/testing/index.mjs +3 -64
  15. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  16. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +1 -1
  17. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  18. package/dist/types/src/components/EditorToolbar/blocks.d.ts +4 -3
  19. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorToolbar/formatting.d.ts +4 -3
  21. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorToolbar/headings.d.ts +4 -3
  23. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  24. package/dist/types/src/components/EditorToolbar/{comment.d.ts → image.d.ts} +4 -5
  25. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -0
  26. package/dist/types/src/components/EditorToolbar/index.d.ts +1 -1
  27. package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
  28. package/dist/types/src/components/EditorToolbar/lists.d.ts +4 -3
  29. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -1
  30. package/dist/types/src/components/EditorToolbar/search.d.ts +17 -0
  31. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -0
  32. package/dist/types/src/components/EditorToolbar/util.d.ts +14 -22
  33. package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
  34. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +4 -3
  35. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  36. package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +21 -0
  37. package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +1 -0
  38. package/dist/types/src/{testing → components/Popover}/RefPopover.d.ts +1 -1
  39. package/dist/types/src/components/Popover/RefPopover.d.ts.map +1 -0
  40. package/dist/types/src/components/Popover/index.d.ts +3 -0
  41. package/dist/types/src/components/Popover/index.d.ts.map +1 -0
  42. package/dist/types/src/components/index.d.ts +1 -0
  43. package/dist/types/src/components/index.d.ts.map +1 -1
  44. package/dist/types/src/defaults.d.ts +2 -5
  45. package/dist/types/src/defaults.d.ts.map +1 -1
  46. package/dist/types/src/extensions/annotations.d.ts +4 -1
  47. package/dist/types/src/extensions/annotations.d.ts.map +1 -1
  48. package/dist/types/src/extensions/autocomplete.d.ts +1 -2
  49. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  50. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  51. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  52. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  53. package/dist/types/src/extensions/automerge/defs.d.ts +1 -1
  54. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  55. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  56. package/dist/types/src/extensions/automerge/update-automerge.d.ts +1 -1
  57. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  58. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +1 -1
  59. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  60. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
  61. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  62. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  63. package/dist/types/src/extensions/command/command.d.ts +1 -2
  64. package/dist/types/src/extensions/command/command.d.ts.map +1 -1
  65. package/dist/types/src/extensions/command/hint.d.ts +14 -2
  66. package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
  67. package/dist/types/src/extensions/command/index.d.ts +2 -0
  68. package/dist/types/src/extensions/command/index.d.ts.map +1 -1
  69. package/dist/types/src/extensions/command/menu.d.ts +4 -14
  70. package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
  71. package/dist/types/src/extensions/command/state.d.ts +1 -1
  72. package/dist/types/src/extensions/command/state.d.ts.map +1 -1
  73. package/dist/types/src/extensions/command/typeahead.d.ts +17 -0
  74. package/dist/types/src/extensions/command/typeahead.d.ts.map +1 -0
  75. package/dist/types/src/extensions/comments.d.ts +2 -12
  76. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  77. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  78. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  79. package/dist/types/src/extensions/factories.d.ts +4 -0
  80. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  81. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  82. package/dist/types/src/extensions/index.d.ts +2 -0
  83. package/dist/types/src/extensions/index.d.ts.map +1 -1
  84. package/dist/types/src/extensions/json.d.ts +7 -0
  85. package/dist/types/src/extensions/json.d.ts.map +1 -0
  86. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  87. package/dist/types/src/extensions/markdown/{editorAction.d.ts → action.d.ts} +1 -1
  88. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
  89. package/dist/types/src/extensions/markdown/bundle.d.ts +2 -1
  90. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  91. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  92. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  93. package/dist/types/src/extensions/markdown/decorate.d.ts +1 -0
  94. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  95. package/dist/types/src/extensions/markdown/formatting.d.ts +1 -1
  96. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  97. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  98. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  99. package/dist/types/src/extensions/markdown/index.d.ts +1 -1
  100. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  101. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  102. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
  103. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  104. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  105. package/dist/types/src/extensions/modes.d.ts +5 -5
  106. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  107. package/dist/types/src/extensions/outliner/commands.d.ts +10 -0
  108. package/dist/types/src/extensions/outliner/commands.d.ts.map +1 -0
  109. package/dist/types/src/extensions/outliner/editor.d.ts +5 -0
  110. package/dist/types/src/extensions/outliner/editor.d.ts.map +1 -0
  111. package/dist/types/src/extensions/outliner/editor.test.d.ts +2 -0
  112. package/dist/types/src/extensions/outliner/editor.test.d.ts.map +1 -0
  113. package/dist/types/src/extensions/outliner/index.d.ts +4 -0
  114. package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
  115. package/dist/types/src/extensions/outliner/outliner.d.ts +13 -0
  116. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -0
  117. package/dist/types/src/extensions/outliner/outliner.test.d.ts +2 -0
  118. package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +1 -0
  119. package/dist/types/src/extensions/outliner/selection.d.ts +12 -0
  120. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -0
  121. package/dist/types/src/extensions/outliner/tree.d.ts +79 -0
  122. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -0
  123. package/dist/types/src/extensions/outliner/tree.test.d.ts +2 -0
  124. package/dist/types/src/extensions/outliner/tree.test.d.ts.map +1 -0
  125. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  126. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  127. package/dist/types/src/extensions/typewriter.d.ts.map +1 -1
  128. package/dist/types/src/hooks/index.d.ts +0 -1
  129. package/dist/types/src/hooks/index.d.ts.map +1 -1
  130. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  131. package/dist/types/src/stories/Command.stories.d.ts +7 -0
  132. package/dist/types/src/stories/Command.stories.d.ts.map +1 -0
  133. package/dist/types/src/stories/{TextEditorComments.stories.d.ts → Comments.stories.d.ts} +3 -3
  134. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -0
  135. package/dist/types/src/stories/EditorToolbar.stories.d.ts +12 -0
  136. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -0
  137. package/dist/types/src/stories/{TextEditorSpecial.stories.d.ts → Experimental.stories.d.ts} +3 -6
  138. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -0
  139. package/dist/types/src/stories/Markdown.stories.d.ts +46 -0
  140. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -0
  141. package/dist/types/src/stories/Outliner.stories.d.ts +26 -0
  142. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -0
  143. package/dist/types/src/stories/Preview.stories.d.ts +10 -0
  144. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -0
  145. package/dist/types/src/stories/{TextEditorBasic.stories.d.ts → TextEditor.stories.d.ts} +9 -36
  146. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -0
  147. package/dist/types/src/stories/{story-utils.d.ts → util.d.ts} +6 -6
  148. package/dist/types/src/stories/util.d.ts.map +1 -0
  149. package/dist/types/src/styles/theme.d.ts.map +1 -1
  150. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  151. package/dist/types/src/testing/index.d.ts +1 -1
  152. package/dist/types/src/testing/index.d.ts.map +1 -1
  153. package/dist/types/src/testing/util.d.ts +2 -0
  154. package/dist/types/src/testing/util.d.ts.map +1 -0
  155. package/dist/types/src/util/cursor.d.ts.map +1 -1
  156. package/dist/types/src/util/debug.d.ts.map +1 -1
  157. package/dist/types/src/util/dom.d.ts.map +1 -1
  158. package/dist/types/src/util/facet.d.ts.map +1 -1
  159. package/dist/types/src/util/react.d.ts.map +1 -1
  160. package/dist/types/tsconfig.tsbuildinfo +1 -1
  161. package/package.json +41 -31
  162. package/src/components/EditorToolbar/EditorToolbar.tsx +93 -70
  163. package/src/components/EditorToolbar/blocks.ts +27 -6
  164. package/src/components/EditorToolbar/formatting.ts +34 -7
  165. package/src/components/EditorToolbar/headings.ts +9 -8
  166. package/src/components/EditorToolbar/image.ts +16 -0
  167. package/src/components/EditorToolbar/index.ts +7 -1
  168. package/src/components/EditorToolbar/lists.ts +26 -7
  169. package/src/components/EditorToolbar/search.ts +19 -0
  170. package/src/components/EditorToolbar/util.ts +16 -17
  171. package/src/components/EditorToolbar/view-mode.ts +9 -8
  172. package/src/components/Popover/RefDropdownMenu.tsx +77 -0
  173. package/src/{testing → components/Popover}/RefPopover.tsx +5 -4
  174. package/src/components/Popover/index.ts +6 -0
  175. package/src/components/index.ts +1 -0
  176. package/src/defaults.ts +10 -13
  177. package/src/extensions/annotations.ts +41 -64
  178. package/src/extensions/autocomplete.ts +5 -6
  179. package/src/extensions/automerge/automerge.stories.tsx +11 -14
  180. package/src/extensions/automerge/automerge.test.tsx +6 -5
  181. package/src/extensions/automerge/automerge.ts +2 -2
  182. package/src/extensions/automerge/defs.ts +1 -2
  183. package/src/extensions/automerge/sync.ts +7 -7
  184. package/src/extensions/automerge/update-automerge.ts +1 -1
  185. package/src/extensions/automerge/update-codemirror.ts +3 -4
  186. package/src/extensions/awareness/awareness-provider.ts +4 -4
  187. package/src/extensions/awareness/awareness.ts +7 -7
  188. package/src/extensions/blast.ts +9 -9
  189. package/src/extensions/command/command.ts +1 -3
  190. package/src/extensions/command/hint.ts +7 -7
  191. package/src/extensions/command/index.ts +2 -0
  192. package/src/extensions/command/menu.ts +75 -50
  193. package/src/extensions/command/typeahead.ts +116 -0
  194. package/src/extensions/comments.ts +4 -69
  195. package/src/extensions/factories.ts +13 -0
  196. package/src/extensions/index.ts +2 -0
  197. package/src/extensions/json.ts +56 -0
  198. package/src/extensions/markdown/bundle.ts +13 -9
  199. package/src/extensions/markdown/changes.ts +3 -2
  200. package/src/extensions/markdown/decorate.ts +15 -14
  201. package/src/extensions/markdown/formatting.ts +4 -4
  202. package/src/extensions/markdown/image.ts +2 -2
  203. package/src/extensions/markdown/index.ts +1 -1
  204. package/src/extensions/markdown/styles.ts +4 -3
  205. package/src/extensions/markdown/table.ts +3 -3
  206. package/src/extensions/modes.ts +5 -6
  207. package/src/extensions/outliner/commands.ts +270 -0
  208. package/src/extensions/outliner/editor.test.ts +33 -0
  209. package/src/extensions/outliner/editor.ts +184 -0
  210. package/src/extensions/outliner/index.ts +7 -0
  211. package/src/extensions/outliner/outliner.test.ts +99 -0
  212. package/src/extensions/outliner/outliner.ts +168 -0
  213. package/src/extensions/outliner/selection.ts +50 -0
  214. package/src/extensions/outliner/tree.test.ts +164 -0
  215. package/src/extensions/outliner/tree.ts +315 -0
  216. package/src/extensions/preview/preview.ts +5 -5
  217. package/src/hooks/index.ts +0 -1
  218. package/src/stories/Command.stories.tsx +97 -0
  219. package/src/stories/{TextEditorComments.stories.tsx → Comments.stories.tsx} +13 -14
  220. package/src/stories/EditorToolbar.stories.tsx +96 -0
  221. package/src/stories/{TextEditorSpecial.stories.tsx → Experimental.stories.tsx} +9 -30
  222. package/src/stories/Markdown.stories.tsx +121 -0
  223. package/src/stories/Outliner.stories.tsx +108 -0
  224. package/src/stories/{TextEditorPreview.stories.tsx → Preview.stories.tsx} +46 -136
  225. package/src/stories/{TextEditorBasic.stories.tsx → TextEditor.stories.tsx} +78 -111
  226. package/src/stories/{story-utils.tsx → util.tsx} +28 -31
  227. package/src/styles/theme.ts +15 -5
  228. package/src/styles/tokens.ts +1 -2
  229. package/src/testing/index.ts +1 -1
  230. package/src/testing/util.ts +5 -0
  231. package/dist/types/src/components/EditorToolbar/comment.d.ts.map +0 -1
  232. package/dist/types/src/extensions/markdown/editorAction.d.ts.map +0 -1
  233. package/dist/types/src/hooks/useActionHandler.d.ts +0 -4
  234. package/dist/types/src/hooks/useActionHandler.d.ts.map +0 -1
  235. package/dist/types/src/stories/InputMode.stories.d.ts +0 -57
  236. package/dist/types/src/stories/InputMode.stories.d.ts.map +0 -1
  237. package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +0 -1
  238. package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +0 -1
  239. package/dist/types/src/stories/TextEditorPreview.stories.d.ts +0 -13
  240. package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +0 -1
  241. package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +0 -1
  242. package/dist/types/src/stories/story-utils.d.ts.map +0 -1
  243. package/dist/types/src/testing/RefPopover.d.ts.map +0 -1
  244. package/src/components/EditorToolbar/comment.ts +0 -23
  245. package/src/hooks/useActionHandler.ts +0 -12
  246. package/src/stories/InputMode.stories.tsx +0 -124
  247. /package/src/extensions/markdown/{editorAction.ts → action.ts} +0 -0
@@ -12,11 +12,13 @@ import { type Extension } from '@codemirror/state';
12
12
  import { keymap } from '@codemirror/view';
13
13
 
14
14
  import { type ThemeMode } from '@dxos/react-ui';
15
+ import { isNotFalsy } from '@dxos/util';
15
16
 
16
17
  import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
17
18
 
18
19
  export type MarkdownBundleOptions = {
19
20
  themeMode?: ThemeMode;
21
+ indentWithTab?: boolean;
20
22
  };
21
23
 
22
24
  /**
@@ -27,7 +29,7 @@ export type MarkdownBundleOptions = {
27
29
  * https://codemirror.net/docs/community
28
30
  * https://codemirror.net/docs/ref/#codemirror.basicSetup
29
31
  */
30
- export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions = {}): Extension[] => {
32
+ export const createMarkdownExtensions = (options: MarkdownBundleOptions = {}): Extension[] => {
31
33
  return [
32
34
  // Main extension.
33
35
  // https://github.com/codemirror/lang-markdown
@@ -56,14 +58,16 @@ export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions =
56
58
  // Custom styles.
57
59
  syntaxHighlighting(markdownHighlightStyle()),
58
60
 
59
- keymap.of([
60
- // https://codemirror.net/docs/ref/#commands.indentWithTab
61
- indentWithTab,
61
+ keymap.of(
62
+ [
63
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
64
+ options.indentWithTab !== false && indentWithTab,
62
65
 
63
- // https://codemirror.net/docs/ref/#commands.defaultKeymap
64
- ...defaultKeymap,
65
- ...completionKeymap,
66
- ...lintKeymap,
67
- ]),
66
+ // https://codemirror.net/docs/ref/#commands.defaultKeymap
67
+ ...defaultKeymap,
68
+ ...completionKeymap,
69
+ ...lintKeymap,
70
+ ].filter(isNotFalsy),
71
+ ),
68
72
  ];
69
73
  };
@@ -56,8 +56,9 @@ export const adjustChanges = () => {
56
56
  // Check for URL.
57
57
  const url = getValidUrl(update.view.state.sliceDoc(fromB, toB));
58
58
  if (url) {
59
+ // Check if pasting inside existing link.
59
60
  const node = tree.resolveInner(fromA, -1);
60
- const invalidPositions = new Set(['Link', 'LinkMark', 'Code', 'CodeText', 'FencedCode', 'URL']);
61
+ const invalidPositions = new Set(['Code', 'CodeText', 'FencedCode', 'Link', 'LinkMark', 'URL']);
61
62
  if (!invalidPositions.has(node?.name)) {
62
63
  const replacedText = tr.startState.sliceDoc(fromA, toA);
63
64
  adjustments.push({ from: fromA, to: toB, insert: createLink(url, replacedText) });
@@ -84,7 +85,7 @@ export const adjustChanges = () => {
84
85
  }
85
86
  }
86
87
 
87
- // TODO(burdon): Is this the right way to augment changes?
88
+ // TODO(burdon): Is this the right way to augment changes? Alt: EditorState.transactionFilter
88
89
  if (adjustments.length) {
89
90
  setTimeout(() => {
90
91
  update.view.dispatch(
@@ -36,7 +36,7 @@ const Unicode = {
36
36
  //
37
37
 
38
38
  class HorizontalRuleWidget extends WidgetType {
39
- override toDOM() {
39
+ override toDOM(): HTMLSpanElement {
40
40
  const el = document.createElement('span');
41
41
  el.className = 'cm-hr';
42
42
  return el;
@@ -51,12 +51,12 @@ class LinkButton extends WidgetType {
51
51
  super();
52
52
  }
53
53
 
54
- override eq(other: this) {
54
+ override eq(other: this): boolean {
55
55
  return this.url === other.url;
56
56
  }
57
57
 
58
58
  // TODO(burdon): Create icon and link directly without react?
59
- override toDOM(view: EditorView) {
59
+ override toDOM(view: EditorView): HTMLSpanElement {
60
60
  const el = document.createElement('span');
61
61
  this.render(el, { url: this.url }, view);
62
62
  return el;
@@ -68,11 +68,11 @@ class CheckboxWidget extends WidgetType {
68
68
  super();
69
69
  }
70
70
 
71
- override eq(other: this) {
71
+ override eq(other: this): boolean {
72
72
  return this._checked === other._checked;
73
73
  }
74
74
 
75
- override toDOM(view: EditorView) {
75
+ override toDOM(view: EditorView): HTMLSpanElement {
76
76
  const input = document.createElement('input');
77
77
  input.className = 'cm-task-checkbox dx-checkbox';
78
78
  input.type = 'checkbox';
@@ -105,7 +105,7 @@ class CheckboxWidget extends WidgetType {
105
105
  return span;
106
106
  }
107
107
 
108
- override ignoreEvent() {
108
+ override ignoreEvent(): boolean {
109
109
  return false;
110
110
  }
111
111
  }
@@ -118,7 +118,7 @@ class TextWidget extends WidgetType {
118
118
  super();
119
119
  }
120
120
 
121
- override toDOM() {
121
+ override toDOM(): HTMLSpanElement {
122
122
  const el = document.createElement('span');
123
123
  if (this.className) {
124
124
  el.className = this.className;
@@ -129,10 +129,10 @@ class TextWidget extends WidgetType {
129
129
  }
130
130
 
131
131
  const hide = Decoration.replace({});
132
- const blockQuote = Decoration.line({ class: mx('cm-blockquote') });
133
- const fencedCodeLine = Decoration.line({ class: mx('cm-code cm-codeblock-line') });
134
- const fencedCodeLineFirst = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-first') });
135
- const fencedCodeLineLast = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-last') });
132
+ const blockQuote = Decoration.line({ class: 'cm-blockquote' });
133
+ const fencedCodeLine = Decoration.line({ class: 'cm-code cm-codeblock-line' });
134
+ const fencedCodeLineFirst = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-start') });
135
+ const fencedCodeLineLast = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-end') });
136
136
  const commentBlockLine = fencedCodeLine;
137
137
  const commentBlockLineFirst = fencedCodeLineFirst;
138
138
  const commentBlockLineLast = fencedCodeLineLast;
@@ -277,7 +277,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
277
277
  // Set indentation.
278
278
  const list = getCurrentListLevel();
279
279
  const width = list.type === 'OrderedList' ? orderedListIndentationWidth : bulletListIndentationWidth;
280
- const offset = ((list.level ?? 0) + 1) * width;
280
+ const offset = (options?.listPaddingLeft ?? 0) + ((list.level ?? 0) + 1) * width;
281
281
  if (node.from === line.to - 1) {
282
282
  // Abort if only the hyphen is typed.
283
283
  return false;
@@ -285,7 +285,6 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
285
285
 
286
286
  // Add line decoration for the continuation indent.
287
287
  // TODO(burdon): Bug if indentation is more than one indentation unit (e.g., 4 spaces) from the previous line.
288
-
289
288
  deco.add(
290
289
  line.from,
291
290
  line.from,
@@ -406,7 +405,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
406
405
  }
407
406
 
408
407
  const first = block.from <= node.from;
409
- const last = block.to >= node.to && /^(\s>)*```$/.test(state.doc.sliceString(block.from, block.to));
408
+ const last = block.to >= node.to && /```$/.test(state.doc.sliceString(block.from, block.to));
410
409
  deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
411
410
 
412
411
  const editing = editingRange(state, node, focus);
@@ -521,6 +520,8 @@ export interface DecorateOptions {
521
520
  selectionChangeDelay?: number;
522
521
  numberedHeadings?: { from: number; to?: number };
523
522
  renderLinkButton?: RenderCallback<{ url: string }>;
523
+ // TODO(burdon): Additional padding for each line.
524
+ listPaddingLeft?: number;
524
525
  }
525
526
 
526
527
  export const decorateMarkdown = (options: DecorateOptions = {}) => {
@@ -5,13 +5,13 @@
5
5
  import { snippet } from '@codemirror/autocomplete';
6
6
  import { syntaxTree } from '@codemirror/language';
7
7
  import {
8
- type Extension,
9
- type StateCommand,
10
- type EditorState,
11
8
  type ChangeSpec,
12
- type Text,
13
9
  EditorSelection,
10
+ type Extension,
11
+ type EditorState,
14
12
  type Line,
13
+ type StateCommand,
14
+ type Text,
15
15
  } from '@codemirror/state';
16
16
  import { EditorView, keymap } from '@codemirror/view';
17
17
  import { type SyntaxNodeRef, type SyntaxNode } from '@lezer/common';
@@ -99,11 +99,11 @@ class ImageWidget extends WidgetType {
99
99
  super();
100
100
  }
101
101
 
102
- override eq(other: this) {
102
+ override eq(other: this): boolean {
103
103
  return this._url === other._url;
104
104
  }
105
105
 
106
- override toDOM(view: EditorView) {
106
+ override toDOM(view: EditorView): HTMLImageElement {
107
107
  const img = document.createElement('img');
108
108
  img.setAttribute('src', this._url);
109
109
  img.setAttribute('class', 'cm-image');
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './editorAction';
5
+ export * from './action';
6
6
  export * from './bundle';
7
7
  export * from './debug';
8
8
  export * from './decorate';
@@ -59,11 +59,11 @@ export const formattingStyles = EditorView.theme({
59
59
  background: 'var(--dx-cmCodeblock)',
60
60
  paddingInline: '1rem !important',
61
61
  },
62
- '& .cm-codeblock-first': {
62
+ '& .cm-codeblock-start': {
63
63
  borderTopLeftRadius: '.25rem',
64
64
  borderTopRightRadius: '.25rem',
65
65
  },
66
- '& .cm-codeblock-last': {
66
+ '& .cm-codeblock-end': {
67
67
  borderBottomLeftRadius: '.25rem',
68
68
  borderBottomRightRadius: '.25rem',
69
69
  },
@@ -72,8 +72,9 @@ export const formattingStyles = EditorView.theme({
72
72
  * Task list.
73
73
  */
74
74
  '& .cm-task': {
75
- display: 'inline-block',
75
+ display: 'inline-flex',
76
76
  width: `${bulletListIndentationWidth}px`,
77
+ height: '20px',
77
78
  },
78
79
  '& .cm-task-checkbox': {
79
80
  display: 'grid',
@@ -106,14 +106,14 @@ class TableWidget extends WidgetType {
106
106
  super();
107
107
  }
108
108
 
109
- override eq(other: this) {
109
+ override eq(other: this): boolean {
110
110
  return (
111
111
  this._table.header?.join() === other._table.header?.join() &&
112
112
  this._table.rows?.join() === other._table.rows?.join()
113
113
  );
114
114
  }
115
115
 
116
- override toDOM(view: EditorView) {
116
+ override toDOM(view: EditorView): HTMLDivElement {
117
117
  const div = document.createElement('div');
118
118
  const table = div.appendChild(document.createElement('table'));
119
119
 
@@ -138,7 +138,7 @@ class TableWidget extends WidgetType {
138
138
  return div;
139
139
  }
140
140
 
141
- override ignoreEvent(e: Event) {
141
+ override ignoreEvent(e: Event): boolean {
142
142
  return !/^mouse/.test(e.type);
143
143
  }
144
144
  }
@@ -6,18 +6,17 @@ import { type Extension } from '@codemirror/state';
6
6
  import { keymap } from '@codemirror/view';
7
7
  import { vim } from '@replit/codemirror-vim';
8
8
  import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
9
-
10
- import { S } from '@dxos/echo-schema';
9
+ import { Schema } from 'effect';
11
10
 
12
11
  import { singleValueFacet } from '../util';
13
12
 
14
13
  export const EditorViewModes = ['preview', 'readonly', 'source'] as const;
15
- export const EditorViewMode = S.Union(...EditorViewModes.map((mode) => S.Literal(mode)));
16
- export type EditorViewMode = S.Schema.Type<typeof EditorViewMode>;
14
+ export const EditorViewMode = Schema.Union(...EditorViewModes.map((mode) => Schema.Literal(mode)));
15
+ export type EditorViewMode = Schema.Schema.Type<typeof EditorViewMode>;
17
16
 
18
17
  export const EditorInputModes = ['default', 'vim', 'vscode'] as const;
19
- export const EditorInputMode = S.Union(...EditorInputModes.map((mode) => S.Literal(mode)));
20
- export type EditorInputMode = S.Schema.Type<typeof EditorInputMode>;
18
+ export const EditorInputMode = Schema.Union(...EditorInputModes.map((mode) => Schema.Literal(mode)));
19
+ export type EditorInputMode = Schema.Schema.Type<typeof EditorInputMode>;
21
20
 
22
21
  export type EditorInputConfig = {
23
22
  type?: string;
@@ -0,0 +1,270 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { indentMore } from '@codemirror/commands';
6
+ import { getIndentUnit } from '@codemirror/language';
7
+ import { type ChangeSpec, EditorSelection, type Extension } from '@codemirror/state';
8
+ import { type Command, type EditorView, keymap } from '@codemirror/view';
9
+
10
+ import { getSelection, selectAll, selectDown, selectNone, selectUp } from './selection';
11
+ import { getRange, treeFacet } from './tree';
12
+
13
+ //
14
+ // Indentation comnmands.
15
+ //
16
+
17
+ export const indentItemMore: Command = (view: EditorView) => {
18
+ const pos = getSelection(view.state).from;
19
+ const tree = view.state.facet(treeFacet);
20
+ const current = tree.find(pos);
21
+ if (current) {
22
+ const previous = tree.prev(current);
23
+ if (previous && current.level <= previous.level) {
24
+ // TODO(burdon): Indent descendants?
25
+ indentMore(view);
26
+ }
27
+ }
28
+
29
+ return true;
30
+ };
31
+
32
+ export const indentItemLess: Command = (view: EditorView) => {
33
+ const pos = getSelection(view.state).from;
34
+ const tree = view.state.facet(treeFacet);
35
+ const current = tree.find(pos);
36
+ if (current) {
37
+ if (current.level > 0) {
38
+ // Unindent current line and all descendants.
39
+ // NOTE: The markdown extension doesn't provide an indentation service.
40
+ const indentUnit = getIndentUnit(view.state);
41
+ const changes: ChangeSpec[] = [];
42
+ tree.traverse(current, (item) => {
43
+ const line = view.state.doc.lineAt(item.lineRange.from);
44
+ changes.push({ from: line.from, to: line.from + indentUnit });
45
+ });
46
+
47
+ if (changes.length > 0) {
48
+ view.dispatch({ changes });
49
+ }
50
+ }
51
+ }
52
+
53
+ return true;
54
+ };
55
+
56
+ //
57
+ // Moving commands.
58
+ //
59
+
60
+ export const moveItemDown: Command = (view: EditorView) => {
61
+ const pos = getSelection(view.state)?.from;
62
+ const tree = view.state.facet(treeFacet);
63
+ const current = tree.find(pos);
64
+ if (current && current.nextSibling) {
65
+ const next = current.nextSibling;
66
+ const currentContent = view.state.doc.sliceString(...getRange(tree, current));
67
+ const nextContent = view.state.doc.sliceString(...getRange(tree, next));
68
+ const changes: ChangeSpec[] = [
69
+ {
70
+ from: current.lineRange.from,
71
+ to: current.lineRange.from + currentContent.length,
72
+ insert: nextContent,
73
+ },
74
+ {
75
+ from: next.lineRange.from,
76
+ to: next.lineRange.from + nextContent.length,
77
+ insert: currentContent,
78
+ },
79
+ ];
80
+
81
+ view.dispatch({
82
+ changes,
83
+ selection: EditorSelection.cursor(pos + nextContent.length + 1),
84
+ scrollIntoView: true,
85
+ });
86
+ }
87
+
88
+ return true;
89
+ };
90
+
91
+ export const moveItemUp: Command = (view: EditorView) => {
92
+ const pos = getSelection(view.state)?.from;
93
+ const tree = view.state.facet(treeFacet);
94
+ const current = tree.find(pos);
95
+ if (current && current.prevSibling) {
96
+ const prev = current.prevSibling;
97
+ const currentContent = view.state.doc.sliceString(...getRange(tree, current));
98
+ const prevContent = view.state.doc.sliceString(...getRange(tree, prev));
99
+ const changes: ChangeSpec[] = [
100
+ {
101
+ from: prev.lineRange.from,
102
+ to: prev.lineRange.from + prevContent.length,
103
+ insert: currentContent,
104
+ },
105
+ {
106
+ from: current.lineRange.from,
107
+ to: current.lineRange.from + currentContent.length,
108
+ insert: prevContent,
109
+ },
110
+ ];
111
+
112
+ view.dispatch({
113
+ changes,
114
+ selection: EditorSelection.cursor(pos - prevContent.length - 1),
115
+ scrollIntoView: true,
116
+ });
117
+ }
118
+
119
+ return true;
120
+ };
121
+
122
+ //
123
+ // Misc commands.
124
+ //
125
+
126
+ export const deleteItem: Command = (view: EditorView) => {
127
+ const tree = view.state.facet(treeFacet);
128
+ const pos = getSelection(view.state).from;
129
+ const current = tree.find(pos);
130
+ if (current) {
131
+ view.dispatch({
132
+ selection: EditorSelection.cursor(current.lineRange.from),
133
+ changes: [
134
+ {
135
+ from: current.lineRange.from,
136
+ to: Math.min(current.lineRange.to + 1, view.state.doc.length),
137
+ },
138
+ ],
139
+ });
140
+ }
141
+
142
+ return true;
143
+ };
144
+
145
+ export const toggleTask: Command = (view: EditorView) => {
146
+ const tree = view.state.facet(treeFacet);
147
+ const pos = getSelection(view.state)?.from;
148
+ const current = tree.find(pos);
149
+ if (current) {
150
+ const type = current.type === 'task' ? 'bullet' : 'task';
151
+ const indent = ' '.repeat(getIndentUnit(view.state) * current.level);
152
+ view.dispatch({
153
+ changes: [
154
+ {
155
+ from: current.lineRange.from,
156
+ to: current.contentRange.from,
157
+ insert: indent + (type === 'task' ? '- [ ] ' : '- '),
158
+ },
159
+ ],
160
+ });
161
+ }
162
+
163
+ return true;
164
+ };
165
+
166
+ export const commands = (): Extension =>
167
+ keymap.of([
168
+ //
169
+ // Indentation.
170
+ //
171
+ {
172
+ key: 'Tab',
173
+ preventDefault: true,
174
+ run: indentItemMore,
175
+ shift: indentItemLess,
176
+ },
177
+
178
+ //
179
+ // Continuation.
180
+ //
181
+ {
182
+ key: 'Enter',
183
+ shift: (view) => {
184
+ const pos = getSelection(view.state).from;
185
+ const insert = '\n '; // TODO(burdon): Fix parsing.
186
+ view.dispatch({
187
+ changes: [{ from: pos, to: pos, insert }],
188
+ selection: EditorSelection.cursor(pos + insert.length),
189
+ });
190
+ return true;
191
+ },
192
+ },
193
+
194
+ //
195
+ // Navigation.
196
+ //
197
+ {
198
+ key: 'ArrowDown',
199
+ // Jump to next item (default moves to end of currentline).
200
+ run: (view) => {
201
+ const tree = view.state.facet(treeFacet);
202
+ const item = tree.find(getSelection(view.state).from);
203
+ if (
204
+ item &&
205
+ view.state.doc.lineAt(item.lineRange.to).number - view.state.doc.lineAt(item.lineRange.from).number === 0
206
+ ) {
207
+ const next = tree.next(item);
208
+ if (next) {
209
+ view.dispatch({ selection: EditorSelection.cursor(next.contentRange.from) });
210
+ return true;
211
+ }
212
+ }
213
+
214
+ return false;
215
+ },
216
+ },
217
+
218
+ //
219
+ // Line selection.
220
+ // TODO(burdon): Shortcut to select current item?
221
+ //
222
+ {
223
+ key: 'Mod-a',
224
+ preventDefault: true,
225
+ run: selectAll,
226
+ },
227
+ {
228
+ key: 'Escape',
229
+ preventDefault: true,
230
+ run: selectNone,
231
+ },
232
+ {
233
+ key: 'ArrowUp',
234
+ shift: selectUp,
235
+ },
236
+ {
237
+ key: 'ArrowDown',
238
+ shift: selectDown,
239
+ },
240
+
241
+ //
242
+ // Move.
243
+ //
244
+ {
245
+ key: 'Alt-ArrowDown',
246
+ preventDefault: true,
247
+ run: moveItemDown,
248
+ },
249
+ {
250
+ key: 'Alt-ArrowUp',
251
+ preventDefault: true,
252
+ run: moveItemUp,
253
+ },
254
+ //
255
+ // Delete.
256
+ //
257
+ {
258
+ key: 'Mod-Backspace',
259
+ preventDefault: true,
260
+ run: deleteItem,
261
+ },
262
+ //
263
+ // Misc.
264
+ //
265
+ {
266
+ key: 'Alt-t',
267
+ preventDefault: true,
268
+ run: toggleTask,
269
+ },
270
+ ]);
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
6
+ import { EditorSelection, EditorState } from '@codemirror/state';
7
+ import { describe, test } from 'vitest';
8
+
9
+ import { editor } from './editor';
10
+ import { outlinerTree, treeFacet } from './tree';
11
+
12
+ const extensions = [markdown({ base: markdownLanguage }), outlinerTree(), editor()];
13
+
14
+ describe('editor', () => {
15
+ test('empty', ({ expect }) => {
16
+ const state = EditorState.create({ extensions });
17
+ const tree = state.facet(treeFacet);
18
+ expect(tree).to.exist;
19
+ });
20
+
21
+ test('prevent moving out of range', ({ expect }) => {
22
+ const state = EditorState.create({ doc: '- [ ] ', extensions });
23
+ const spec = state.update({ selection: EditorSelection.cursor(1) });
24
+ expect(spec.state.selection.ranges[0].from).to.eq(6);
25
+ });
26
+
27
+ test.skip('prevent deleting task marker', ({ expect }) => {
28
+ const state = EditorState.create({ doc: '- [ ] ', extensions });
29
+ state.update({ selection: EditorSelection.cursor(6) });
30
+ const spec = state.update({ changes: { from: 5, to: 6 } });
31
+ expect(spec.state.doc.toString()).to.eq('- [ ] ');
32
+ });
33
+ });