@dxos/react-ui-editor 0.8.4-main.e098934 → 0.8.4-main.ead640a

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 (205) hide show
  1. package/dist/lib/browser/{chunk-22UMM3QJ.mjs → chunk-HL3YF6WC.mjs} +2 -2
  2. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3555 -3484
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs.map +2 -2
  7. package/dist/lib/browser/types/index.mjs +1 -1
  8. package/dist/lib/node-esm/{chunk-YXYQPV6R.mjs → chunk-YJZGD3LY.mjs} +2 -2
  9. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +3555 -3484
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  14. package/dist/lib/node-esm/types/index.mjs +1 -1
  15. package/dist/types/src/components/Editor/Editor.d.ts +24 -9
  16. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  17. package/dist/types/src/components/Editor/Editor.stories.d.ts +27 -0
  18. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorToolbar/util.d.ts +1 -1
  21. package/dist/types/src/components/index.d.ts +0 -1
  22. package/dist/types/src/components/index.d.ts.map +1 -1
  23. package/dist/types/src/extensions/{autocomplete.d.ts → autocomplete/autocomplete.d.ts} +1 -1
  24. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
  25. package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
  26. package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
  27. package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
  28. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
  29. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +20 -0
  30. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
  31. package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
  32. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  34. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  35. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
  36. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  37. package/dist/types/src/extensions/automerge/sync.d.ts +2 -2
  38. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  39. package/dist/types/src/extensions/autoscroll.d.ts +2 -2
  40. package/dist/types/src/extensions/autoscroll.d.ts.map +1 -1
  41. package/dist/types/src/extensions/factories.d.ts +7 -2
  42. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  43. package/dist/types/src/extensions/focus.d.ts.map +1 -1
  44. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  45. package/dist/types/src/extensions/index.d.ts +2 -1
  46. package/dist/types/src/extensions/index.d.ts.map +1 -1
  47. package/dist/types/src/extensions/json.d.ts +1 -1
  48. package/dist/types/src/extensions/json.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  51. package/dist/types/src/extensions/modes.d.ts +1 -1
  52. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  53. package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
  54. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
  55. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts +36 -0
  56. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts.map +1 -0
  57. package/dist/types/src/extensions/popover/index.d.ts +8 -0
  58. package/dist/types/src/extensions/popover/index.d.ts.map +1 -0
  59. package/dist/types/src/extensions/popover/menu-presets.d.ts +4 -0
  60. package/dist/types/src/extensions/popover/menu-presets.d.ts.map +1 -0
  61. package/dist/types/src/extensions/popover/menu.d.ts +24 -0
  62. package/dist/types/src/extensions/popover/menu.d.ts.map +1 -0
  63. package/dist/types/src/extensions/popover/modal.d.ts +7 -0
  64. package/dist/types/src/extensions/popover/modal.d.ts.map +1 -0
  65. package/dist/types/src/extensions/popover/popover.d.ts +47 -0
  66. package/dist/types/src/extensions/popover/popover.d.ts.map +1 -0
  67. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts +34 -0
  68. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts.map +1 -0
  69. package/dist/types/src/extensions/popover/util.d.ts +8 -0
  70. package/dist/types/src/extensions/popover/util.d.ts.map +1 -0
  71. package/dist/types/src/extensions/preview/preview.d.ts +0 -1
  72. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  73. package/dist/types/src/extensions/state.d.ts +2 -0
  74. package/dist/types/src/extensions/state.d.ts.map +1 -0
  75. package/dist/types/src/extensions/tags/streamer.d.ts.map +1 -1
  76. package/dist/types/src/extensions/tags/xml-tags.d.ts +1 -0
  77. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  78. package/dist/types/src/hooks/useTextEditor.d.ts +4 -8
  79. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  80. package/dist/types/src/stories/{Command.stories.d.ts → CommandDialog.stories.d.ts} +2 -3
  81. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +1 -0
  82. package/dist/types/src/stories/Comments.stories.d.ts +3 -4
  83. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  84. package/dist/types/src/stories/EditorToolbar.stories.d.ts +1 -2
  85. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  86. package/dist/types/src/stories/Experimental.stories.d.ts +3 -4
  87. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  88. package/dist/types/src/stories/Markdown.stories.d.ts +3 -4
  89. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
  90. package/dist/types/src/stories/Outliner.stories.d.ts +0 -1
  91. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  92. package/dist/types/src/stories/{CommandMenu.stories.d.ts → Popover.stories.d.ts} +6 -6
  93. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -0
  94. package/dist/types/src/stories/Preview.stories.d.ts +3 -4
  95. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  96. package/dist/types/src/stories/Tags.stories.d.ts +0 -1
  97. package/dist/types/src/stories/Tags.stories.d.ts.map +1 -1
  98. package/dist/types/src/stories/TextEditor.stories.d.ts +3 -5
  99. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  100. package/dist/types/src/stories/components/EditorStory.d.ts +5 -5
  101. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  102. package/dist/types/src/styles/theme.d.ts.map +1 -1
  103. package/dist/types/src/testing/PreviewPopover.d.ts.map +1 -1
  104. package/dist/types/src/types/types.d.ts +1 -1
  105. package/dist/types/src/types/types.d.ts.map +1 -1
  106. package/dist/types/src/util/index.d.ts +0 -1
  107. package/dist/types/src/util/index.d.ts.map +1 -1
  108. package/dist/types/tsconfig.tsbuildinfo +1 -1
  109. package/package.json +54 -51
  110. package/src/components/Editor/Editor.stories.tsx +69 -0
  111. package/src/components/Editor/Editor.tsx +57 -14
  112. package/src/components/EditorToolbar/EditorToolbar.tsx +1 -0
  113. package/src/components/index.ts +0 -1
  114. package/src/extensions/{autocomplete.ts → autocomplete/autocomplete.ts} +2 -1
  115. package/src/extensions/autocomplete/index.ts +8 -0
  116. package/src/extensions/autocomplete/match.ts +46 -0
  117. package/src/extensions/{command → autocomplete}/placeholder.ts +21 -17
  118. package/src/extensions/{command → autocomplete}/typeahead.ts +6 -48
  119. package/src/extensions/automerge/automerge.stories.tsx +8 -8
  120. package/src/extensions/automerge/automerge.ts +28 -9
  121. package/src/extensions/automerge/sync.ts +7 -3
  122. package/src/extensions/autoscroll.ts +29 -29
  123. package/src/extensions/factories.ts +41 -12
  124. package/src/extensions/focus.ts +5 -4
  125. package/src/extensions/folding.tsx +4 -6
  126. package/src/extensions/hashtag.tsx +2 -2
  127. package/src/extensions/index.ts +2 -1
  128. package/src/extensions/json.ts +1 -1
  129. package/src/extensions/markdown/bundle.ts +16 -4
  130. package/src/extensions/markdown/decorate.ts +1 -0
  131. package/src/extensions/modes.ts +2 -2
  132. package/src/extensions/{command/floating-menu.ts → outliner/menu.ts} +9 -9
  133. package/src/extensions/outliner/outliner.ts +2 -2
  134. package/src/extensions/popover/PopoverMenuProvider.tsx +221 -0
  135. package/src/extensions/popover/index.ts +12 -0
  136. package/src/extensions/popover/menu-presets.ts +124 -0
  137. package/src/extensions/popover/menu.ts +67 -0
  138. package/src/extensions/popover/modal.ts +24 -0
  139. package/src/extensions/popover/popover.ts +291 -0
  140. package/src/extensions/popover/usePopoverMenu.ts +173 -0
  141. package/src/extensions/popover/util.ts +29 -0
  142. package/src/extensions/preview/index.ts +1 -1
  143. package/src/extensions/preview/preview.ts +0 -2
  144. package/src/extensions/selection.ts +2 -2
  145. package/src/extensions/state.ts +7 -0
  146. package/src/extensions/tags/streamer.ts +4 -5
  147. package/src/extensions/tags/xml-tags.ts +59 -1
  148. package/src/hooks/useTextEditor.ts +27 -27
  149. package/src/stories/{Command.stories.tsx → CommandDialog.stories.tsx} +10 -22
  150. package/src/stories/Comments.stories.tsx +5 -5
  151. package/src/stories/EditorToolbar.stories.tsx +6 -5
  152. package/src/stories/Experimental.stories.tsx +6 -6
  153. package/src/stories/Markdown.stories.tsx +5 -5
  154. package/src/stories/Outliner.stories.tsx +21 -14
  155. package/src/stories/Popover.stories.tsx +163 -0
  156. package/src/stories/Preview.stories.tsx +5 -5
  157. package/src/stories/Tags.stories.tsx +5 -5
  158. package/src/stories/TextEditor.stories.tsx +7 -32
  159. package/src/stories/components/EditorStory.tsx +7 -5
  160. package/src/styles/theme.ts +12 -10
  161. package/src/testing/PreviewPopover.tsx +2 -0
  162. package/src/types/types.ts +1 -1
  163. package/src/util/index.ts +0 -1
  164. package/dist/lib/browser/chunk-22UMM3QJ.mjs.map +0 -7
  165. package/dist/lib/node-esm/chunk-YXYQPV6R.mjs.map +0 -7
  166. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts +0 -38
  167. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts.map +0 -1
  168. package/dist/types/src/components/CommandMenu/index.d.ts +0 -2
  169. package/dist/types/src/components/CommandMenu/index.d.ts.map +0 -1
  170. package/dist/types/src/extensions/autocomplete.d.ts.map +0 -1
  171. package/dist/types/src/extensions/command/action.d.ts +0 -17
  172. package/dist/types/src/extensions/command/action.d.ts.map +0 -1
  173. package/dist/types/src/extensions/command/command-menu.d.ts +0 -20
  174. package/dist/types/src/extensions/command/command-menu.d.ts.map +0 -1
  175. package/dist/types/src/extensions/command/command.d.ts +0 -6
  176. package/dist/types/src/extensions/command/command.d.ts.map +0 -1
  177. package/dist/types/src/extensions/command/floating-menu.d.ts +0 -7
  178. package/dist/types/src/extensions/command/floating-menu.d.ts.map +0 -1
  179. package/dist/types/src/extensions/command/hint.d.ts +0 -19
  180. package/dist/types/src/extensions/command/hint.d.ts.map +0 -1
  181. package/dist/types/src/extensions/command/index.d.ts +0 -7
  182. package/dist/types/src/extensions/command/index.d.ts.map +0 -1
  183. package/dist/types/src/extensions/command/placeholder.d.ts +0 -10
  184. package/dist/types/src/extensions/command/placeholder.d.ts.map +0 -1
  185. package/dist/types/src/extensions/command/state.d.ts +0 -16
  186. package/dist/types/src/extensions/command/state.d.ts.map +0 -1
  187. package/dist/types/src/extensions/command/typeahead.d.ts +0 -22
  188. package/dist/types/src/extensions/command/typeahead.d.ts.map +0 -1
  189. package/dist/types/src/extensions/command/useCommandMenu.d.ts +0 -25
  190. package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +0 -1
  191. package/dist/types/src/stories/Command.stories.d.ts.map +0 -1
  192. package/dist/types/src/stories/CommandMenu.stories.d.ts.map +0 -1
  193. package/dist/types/src/util/domino.d.ts +0 -18
  194. package/dist/types/src/util/domino.d.ts.map +0 -1
  195. package/src/components/CommandMenu/CommandMenu.tsx +0 -346
  196. package/src/components/CommandMenu/index.ts +0 -5
  197. package/src/extensions/command/action.ts +0 -55
  198. package/src/extensions/command/command-menu.ts +0 -211
  199. package/src/extensions/command/command.ts +0 -34
  200. package/src/extensions/command/hint.ts +0 -103
  201. package/src/extensions/command/index.ts +0 -10
  202. package/src/extensions/command/state.ts +0 -90
  203. package/src/extensions/command/useCommandMenu.ts +0 -115
  204. package/src/stories/CommandMenu.stories.tsx +0 -158
  205. package/src/util/domino.ts +0 -51
@@ -5,12 +5,13 @@
5
5
  //
6
6
 
7
7
  import { next as A } from '@automerge/automerge';
8
- import { type Extension, StateField } from '@codemirror/state';
8
+ import { type Extension, StateField, Transaction } from '@codemirror/state';
9
9
  import { EditorView, ViewPlugin } from '@codemirror/view';
10
10
 
11
- import { type DocAccessor } from '@dxos/react-client/echo';
11
+ import { DocAccessor } from '@dxos/react-client/echo';
12
12
 
13
13
  import { Cursor } from '../../util';
14
+ import { initialSync } from '../state';
14
15
 
15
16
  import { cursorConverter } from './cursor';
16
17
  import { type State, isReconcile, updateHeadsEffect } from './defs';
@@ -18,11 +19,13 @@ import { Syncer } from './sync';
18
19
 
19
20
  export const automerge = (accessor: DocAccessor): Extension => {
20
21
  const syncState = StateField.define<State>({
21
- create: () => ({
22
- path: accessor.path.slice(),
23
- lastHeads: A.getHeads(accessor.handle.doc()!),
24
- unreconciledTransactions: [],
25
- }),
22
+ create: () => {
23
+ return {
24
+ path: accessor.path.slice(),
25
+ lastHeads: A.getHeads(accessor.handle.doc()!),
26
+ unreconciledTransactions: [],
27
+ };
28
+ },
26
29
 
27
30
  update: (value, tr) => {
28
31
  const result: State = {
@@ -64,6 +67,18 @@ export const automerge = (accessor: DocAccessor): Extension => {
64
67
  class {
65
68
  constructor(private readonly _view: EditorView) {
66
69
  accessor.handle.addListener('change', this._handleChange);
70
+
71
+ requestAnimationFrame(() => {
72
+ const value = DocAccessor.getValue<string>(accessor);
73
+ const current = this._view.state.doc.toString();
74
+ if (value !== current) {
75
+ // TODO(burdon): This attempts to set the initial state, but creates problems.
76
+ // this._view.dispatch({
77
+ // changes: { from: 0, to: this._view.state.doc.length, insert: value },
78
+ // annotations: initialSync,
79
+ // });
80
+ }
81
+ });
67
82
  }
68
83
 
69
84
  destroy() {
@@ -77,9 +92,13 @@ export const automerge = (accessor: DocAccessor): Extension => {
77
92
  ),
78
93
 
79
94
  // Reconcile local updates.
80
- EditorView.updateListener.of(({ view, changes }) => {
95
+ EditorView.updateListener.of(({ view, changes, transactions }) => {
81
96
  if (!changes.empty) {
82
- syncer.reconcile(view, true);
97
+ // Only reconcile if it's not an initial sync (to avoid loops)
98
+ const isInitialSync = transactions.some((tr) => tr.annotation(Transaction.userEvent) === initialSync.value);
99
+ if (!isInitialSync) {
100
+ syncer.reconcile(view, true);
101
+ }
83
102
  }
84
103
  }),
85
104
  ];
@@ -8,6 +8,7 @@ import { next as A } from '@automerge/automerge';
8
8
  import { type StateField } from '@codemirror/state';
9
9
  import { type EditorView } from '@codemirror/view';
10
10
 
11
+ import { log } from '@dxos/log';
11
12
  import { type IDocHandle } from '@dxos/react-client/echo';
12
13
 
13
14
  import { type State, getLastHeads, getPath, isReconcile, reconcileAnnotation, updateHeads } from './defs';
@@ -27,7 +28,6 @@ export class Syncer {
27
28
  ) {}
28
29
 
29
30
  reconcile(view: EditorView, editor: boolean): void {
30
- // TODO(burdon): Better way to do mutex?
31
31
  if (this._pending) {
32
32
  return;
33
33
  }
@@ -41,7 +41,9 @@ export class Syncer {
41
41
  this._pending = false;
42
42
  }
43
43
 
44
- onEditorChange(view: EditorView): void {
44
+ private onEditorChange(view: EditorView): void {
45
+ log('onEditorChange');
46
+
45
47
  // Apply the unreconciled transactions to the document.
46
48
  const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
47
49
  const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
@@ -54,7 +56,9 @@ export class Syncer {
54
56
  }
55
57
  }
56
58
 
57
- onAutomergeChange(view: EditorView): void {
59
+ private onAutomergeChange(view: EditorView): void {
60
+ log('onAutomergeChange');
61
+
58
62
  // Get the diff between the updated state of the document and the heads and apply that to the codemirror doc.
59
63
  const oldHeads = getLastHeads(view.state, this._state);
60
64
  const newHeads = A.getHeads(this._handle.doc()!);
@@ -5,15 +5,15 @@
5
5
  import { StateEffect } from '@codemirror/state';
6
6
  import { EditorView, ViewPlugin } from '@codemirror/view';
7
7
 
8
- import { Domino } from '../util';
8
+ import { Domino } from '@dxos/react-ui';
9
9
 
10
10
  const lineHeight = 24;
11
11
 
12
12
  export const scrollToBottomEffect = StateEffect.define<any>();
13
13
 
14
14
  export type AutoScrollOptions = {
15
- overscroll: number;
16
- throttle: number;
15
+ overscroll?: number;
16
+ throttle?: number;
17
17
  };
18
18
 
19
19
  /**
@@ -24,7 +24,7 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
24
24
  let isThrottled = false;
25
25
  let isPinned = true;
26
26
  let timeout: NodeJS.Timeout | undefined;
27
- let buttonContainer: HTMLDivElement;
27
+ let buttonContainer: HTMLDivElement | undefined;
28
28
  let lastScrollTop = 0;
29
29
  let scrollCounter = 0;
30
30
 
@@ -50,28 +50,6 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
50
50
  };
51
51
 
52
52
  return [
53
- // Scroll button.
54
- ViewPlugin.fromClass(
55
- class {
56
- constructor(view: EditorView) {
57
- const scroller = view.scrollDOM.parentElement;
58
- buttonContainer = Domino.of('div')
59
- .classNames(true && 'cm-scroll-button transition-opacity duration-300 opacity-0')
60
- .child(
61
- Domino.of('button')
62
- .classNames('dx-button bg-accentSurface')
63
- .data('density', 'fine')
64
- .child(Domino.of<any>('dx-icon').attr('icon', 'ph--arrow-down--regular'))
65
- .on('click', () => {
66
- scrollToBottom(view);
67
- }),
68
- )
69
- .build();
70
- scroller?.appendChild(buttonContainer);
71
- }
72
- },
73
- ),
74
-
75
53
  // Update listener for logging when scrolling is needed.
76
54
  EditorView.updateListener.of((update) => {
77
55
  // Listen for effects.
@@ -83,10 +61,9 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
83
61
  }
84
62
  });
85
63
 
64
+ // Maybe scroll if doc changed and pinned.
86
65
  if (update.docChanged && isPinned && !isThrottled) {
87
66
  const distanceFromBottom = calcDistance(update.view.scrollDOM);
88
-
89
- // Keep pinned.
90
67
  if (distanceFromBottom >= overscroll) {
91
68
  isThrottled = true;
92
69
  requestAnimationFrame(() => {
@@ -96,6 +73,7 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
96
73
  // Reset throttle.
97
74
  setTimeout(() => {
98
75
  isThrottled = false;
76
+ scrollToBottom(update.view);
99
77
  }, throttle);
100
78
  }
101
79
  }
@@ -126,6 +104,29 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
126
104
  },
127
105
  }),
128
106
 
107
+ // Scroll button.
108
+ ViewPlugin.fromClass(
109
+ class {
110
+ constructor(view: EditorView) {
111
+ const scroller = view.scrollDOM.parentElement;
112
+ buttonContainer = Domino.of('div')
113
+ .classNames(true && 'cm-scroll-button transition-opacity duration-300 opacity-0')
114
+ .children(
115
+ Domino.of('button')
116
+ .classNames('dx-button bg-accentSurface')
117
+ .data('density', 'fine')
118
+ .children(Domino.of<any>('dx-icon').attributes({ icon: 'ph--arrow-down--regular' }))
119
+ .on('click', () => {
120
+ scrollToBottom(view);
121
+ }),
122
+ )
123
+ .build();
124
+ scroller?.appendChild(buttonContainer);
125
+ }
126
+ },
127
+ ),
128
+
129
+ // Styles.
129
130
  EditorView.theme({
130
131
  '.cm-scroller': {
131
132
  paddingBottom: `${overscroll}px`,
@@ -138,7 +139,6 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
138
139
  display: 'none',
139
140
  },
140
141
 
141
- // TODO(burdon): IconButton.
142
142
  '.cm-scroll-button': {
143
143
  position: 'absolute',
144
144
  bottom: '0.5rem',
@@ -4,10 +4,9 @@
4
4
 
5
5
  import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
6
6
  import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
7
- import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
7
+ import { HighlightStyle, bracketMatching, syntaxHighlighting } from '@codemirror/language';
8
8
  import { searchKeymap } from '@codemirror/search';
9
- import { EditorState, type Extension } from '@codemirror/state';
10
- import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
9
+ import { type ChangeSpec, EditorState, type Extension, type TransactionSpec } from '@codemirror/state';
11
10
  import {
12
11
  EditorView,
13
12
  type KeyBinding,
@@ -20,6 +19,7 @@ import {
20
19
  placeholder,
21
20
  scrollPastEnd,
22
21
  } from '@codemirror/view';
22
+ import { vscodeDarkStyle, vscodeLightStyle } from '@uiw/codemirror-theme-vscode';
23
23
  import defaultsDeep from 'lodash.defaultsdeep';
24
24
  import merge from 'lodash.merge';
25
25
 
@@ -29,7 +29,7 @@ import { type DocAccessor, type Space } from '@dxos/react-client/echo';
29
29
  import { type Identity } from '@dxos/react-client/halo';
30
30
  import { type ThemeMode } from '@dxos/react-ui';
31
31
  import { type HuePalette } from '@dxos/react-ui-theme';
32
- import { hexToHue, isNotFalsy } from '@dxos/util';
32
+ import { hexToHue, isTruthy } from '@dxos/util';
33
33
 
34
34
  import { editorGutter, editorMonospace } from '../defaults';
35
35
  import { type ThemeStyles, defaultTheme } from '../styles';
@@ -42,7 +42,29 @@ import { focus } from './focus';
42
42
  // Basic
43
43
  //
44
44
 
45
- export const preventNewline = EditorState.transactionFilter.of((tr) => (tr.newDoc.lines > 1 ? [] : tr));
45
+ export const filterChars = (chars: RegExp) => {
46
+ return EditorState.transactionFilter.of((transaction) => {
47
+ if (!transaction.docChanged) return transaction;
48
+
49
+ const changes: ChangeSpec[] = [];
50
+ transaction.changes.iterChanges((fromA, toA, fromB, toB, text) => {
51
+ const inserted = text.toString();
52
+ const filtered = inserted.replace(chars, '');
53
+ if (inserted !== filtered) {
54
+ changes.push({
55
+ from: fromB,
56
+ to: toB,
57
+ insert: filtered,
58
+ });
59
+ }
60
+ });
61
+
62
+ if (changes.length) {
63
+ return [transaction, { changes, sequential: true } as TransactionSpec];
64
+ }
65
+ return transaction;
66
+ });
67
+ };
46
68
 
47
69
  /**
48
70
  * https://codemirror.net/docs/extensions
@@ -70,6 +92,7 @@ export type BasicExtensionsOptions = {
70
92
  /** If true user cannot edit the text, but they can still select and copy it. */
71
93
  readOnly?: boolean;
72
94
  search?: boolean;
95
+ /** NOTE: Do not use with stack sections. */
73
96
  scrollPastEnd?: boolean;
74
97
  standardKeymap?: boolean;
75
98
  tabSize?: number;
@@ -139,9 +162,9 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
139
162
  preventDefault: true,
140
163
  run: () => true,
141
164
  },
142
- ].filter(isNotFalsy),
165
+ ].filter(isTruthy),
143
166
  ),
144
- ].filter(isNotFalsy);
167
+ ].filter(isTruthy);
145
168
  };
146
169
 
147
170
  //
@@ -179,20 +202,26 @@ export const fullWidth: ThemeExtensionsOptions['slots'] = {
179
202
 
180
203
  export const defaultThemeSlots = grow;
181
204
 
205
+ export const defaultStyles = {
206
+ dark: vscodeDarkStyle,
207
+ light: vscodeLightStyle,
208
+ };
209
+
182
210
  /**
183
211
  * https://codemirror.net/examples/styling
184
212
  */
185
213
  export const createThemeExtensions = ({
186
214
  themeMode,
187
215
  styles,
188
- syntaxHighlighting: syntaxHighlightingProps,
189
- slots: _slots,
216
+ syntaxHighlighting: syntaxHighlightingProp,
217
+ slots: slotsParam,
190
218
  }: ThemeExtensionsOptions = {}): Extension => {
191
- const slots = defaultsDeep({}, _slots, defaultThemeSlots);
219
+ const slots = defaultsDeep({}, slotsParam, defaultThemeSlots);
192
220
  return [
193
221
  EditorView.darkTheme.of(themeMode === 'dark'),
194
222
  EditorView.baseTheme(styles ? merge({}, defaultTheme, styles) : defaultTheme),
195
- syntaxHighlightingProps && syntaxHighlighting(themeMode === 'dark' ? oneDarkHighlightStyle : defaultHighlightStyle),
223
+ syntaxHighlightingProp &&
224
+ syntaxHighlighting(HighlightStyle.define(themeMode === 'dark' ? defaultStyles.dark : defaultStyles.light)),
196
225
  slots.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
197
226
  slots.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
198
227
  slots.scroll?.className &&
@@ -203,7 +232,7 @@ export const createThemeExtensions = ({
203
232
  }
204
233
  },
205
234
  ),
206
- ].filter(isNotFalsy);
235
+ ].filter(isTruthy);
207
236
  };
208
237
 
209
238
  //
@@ -15,6 +15,7 @@ export const focusField = StateField.define<boolean>({
15
15
  return effect.value;
16
16
  }
17
17
  }
18
+
18
19
  return value;
19
20
  },
20
21
  });
@@ -25,11 +26,11 @@ export const focusField = StateField.define<boolean>({
25
26
  export const focus = [
26
27
  focusField,
27
28
  EditorView.domEventHandlers({
28
- focus: (event, view) => {
29
- setTimeout(() => view.dispatch({ effects: focusEffect.of(true) }));
29
+ focus: (_event, view) => {
30
+ requestAnimationFrame(() => view.dispatch({ effects: focusEffect.of(true) }));
30
31
  },
31
- blur: (event, view) => {
32
- setTimeout(() => view.dispatch({ effects: focusEffect.of(false) }));
32
+ blur: (_event, view) => {
33
+ requestAnimationFrame(() => view.dispatch({ effects: focusEffect.of(false) }));
33
34
  },
34
35
  }),
35
36
  ];
@@ -7,16 +7,15 @@ import { type Extension } from '@codemirror/state';
7
7
  import { EditorView } from '@codemirror/view';
8
8
  import React from 'react';
9
9
 
10
- import { Icon } from '@dxos/react-ui';
10
+ import { Domino, Icon } from '@dxos/react-ui';
11
11
 
12
- import { Domino, renderRoot } from '../util';
12
+ import { renderRoot } from '../util';
13
13
 
14
14
  export type FoldingOptions = {};
15
15
 
16
16
  /**
17
17
  * https://codemirror.net/examples/gutter
18
18
  */
19
- // TODO(burdon): Remember folding state (to state).
20
19
  export const folding = (_props: FoldingOptions = {}): Extension => [
21
20
  codeFolding({
22
21
  placeholderDOM: () => {
@@ -25,10 +24,9 @@ export const folding = (_props: FoldingOptions = {}): Extension => [
25
24
  }),
26
25
  foldGutter({
27
26
  markerDOM: (open) => {
28
- // TODO(burdon): Use sprite directly.
29
- const el = Domino.of('div').classNames('flex h-full items-center').build();
30
27
  return renderRoot(
31
- el,
28
+ Domino.of('div').classNames('flex h-full items-center').build(),
29
+ // TODO(burdon): Use sprite directly.
32
30
  <Icon icon='ph--caret-right--bold' size={3} classNames={['mx-3 cursor-pointer', open && 'rotate-90']} />,
33
31
  );
34
32
  },
@@ -13,7 +13,7 @@ import {
13
13
  WidgetType,
14
14
  } from '@codemirror/view';
15
15
 
16
- import { getHashColor, mx } from '@dxos/react-ui-theme';
16
+ import { getHashStyles, mx } from '@dxos/react-ui-theme';
17
17
 
18
18
  class TagWidget extends WidgetType {
19
19
  constructor(private _text: string) {
@@ -22,7 +22,7 @@ class TagWidget extends WidgetType {
22
22
 
23
23
  toDOM(): HTMLSpanElement {
24
24
  const span = document.createElement('span');
25
- span.className = mx('cm-tag', getHashColor(this._text).tag);
25
+ span.className = mx('cm-tag', getHashStyles(this._text).surface);
26
26
  span.textContent = this._text;
27
27
  return span;
28
28
  }
@@ -8,7 +8,6 @@ export * from './autoscroll';
8
8
  export * from './automerge';
9
9
  export * from './awareness';
10
10
  export * from './blast';
11
- export * from './command';
12
11
  export * from './comments';
13
12
  export * from './debug';
14
13
  export * from './dnd';
@@ -22,7 +21,9 @@ export * from './markdown';
22
21
  export * from './mention';
23
22
  export * from './modes';
24
23
  export * from './outliner';
24
+ export * from './popover';
25
25
  export * from './preview';
26
26
  export * from './selection';
27
+ export * from './state';
27
28
  export * from './tags';
28
29
  export * from './typewriter';
@@ -7,7 +7,7 @@ import { type LintSource, linter } from '@codemirror/lint';
7
7
  import { type Extension } from '@codemirror/state';
8
8
  import Ajv, { type ValidateFunction } from 'ajv';
9
9
 
10
- import { type JsonSchemaType } from '@dxos/echo-schema';
10
+ import { type JsonSchemaType } from '@dxos/echo/internal';
11
11
 
12
12
  export type JsonExtensionsOptions = {
13
13
  schema?: JsonSchemaType;
@@ -4,14 +4,16 @@
4
4
 
5
5
  import { completionKeymap } from '@codemirror/autocomplete';
6
6
  import { defaultKeymap, indentWithTab } from '@codemirror/commands';
7
+ import { jsonLanguage } from '@codemirror/lang-json';
7
8
  import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
8
- import { syntaxHighlighting } from '@codemirror/language';
9
+ import { xml } from '@codemirror/lang-xml';
10
+ import { LanguageDescription, syntaxHighlighting } from '@codemirror/language';
9
11
  import { languages } from '@codemirror/language-data';
10
12
  import { type Extension } from '@codemirror/state';
11
13
  import { keymap } from '@codemirror/view';
12
14
  import { type MarkdownConfig } from '@lezer/markdown';
13
15
 
14
- import { isNotFalsy } from '@dxos/util';
16
+ import { isTruthy } from '@dxos/util';
15
17
 
16
18
  import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
17
19
 
@@ -43,6 +45,7 @@ export const createMarkdownExtensions = (options: MarkdownBundleOptions = {}): E
43
45
  base: markdownLanguage,
44
46
 
45
47
  // Languages for syntax highlighting fenced code blocks.
48
+ defaultCodeLanguage: jsonLanguage,
46
49
  codeLanguages: languages,
47
50
 
48
51
  // Don't complete HTML tags.
@@ -66,12 +69,21 @@ export const createMarkdownExtensions = (options: MarkdownBundleOptions = {}): E
66
69
 
67
70
  // https://codemirror.net/docs/ref/#commands.defaultKeymap
68
71
  ...defaultKeymap,
72
+
73
+ // TODO(burdon): Remove?
69
74
  ...completionKeymap,
70
- ].filter(isNotFalsy),
75
+ ].filter(isTruthy),
71
76
  ),
72
77
  ];
73
78
  };
74
79
 
80
+ const xmlLanguageDesc = LanguageDescription.of({
81
+ name: 'xml',
82
+ alias: ['html', 'xhtml'],
83
+ extensions: ['xml', 'xhtml'],
84
+ load: async () => xml(),
85
+ });
86
+
75
87
  /**
76
88
  * Default customizations.
77
89
  * https://github.com/lezer-parser/markdown/blob/main/src/markdown.ts
@@ -89,5 +101,5 @@ const noSetExtHeading: MarkdownConfig = {
89
101
  * Remove HTML and XML parsing.
90
102
  */
91
103
  const noHtml: MarkdownConfig = {
92
- remove: ['HTMLBlock', 'HTMLTag'],
104
+ // remove: ['HTMLBlock', 'HTMLTag'],
93
105
  };
@@ -447,6 +447,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
447
447
  },
448
448
  }),
449
449
  );
450
+
450
451
  if (!editing) {
451
452
  atomicDeco.add(
452
453
  marks[1].from,
@@ -11,7 +11,7 @@ import { singleValueFacet } from '../util';
11
11
 
12
12
  export type EditorInputConfig = {
13
13
  type?: string;
14
- noTabster?: boolean;
14
+ ignoreEscape?: boolean;
15
15
  };
16
16
 
17
17
  export const editorInputMode = singleValueFacet<EditorInputConfig>({});
@@ -26,7 +26,7 @@ export const InputModeExtensions: { [mode: string]: Extension } = {
26
26
  vim: [
27
27
  // https://github.com/replit/codemirror-vim
28
28
  vim(),
29
- editorInputMode.of({ type: 'vim', noTabster: true }),
29
+ editorInputMode.of({ type: 'vim', ignoreEscape: true }),
30
30
  keymap.of([
31
31
  {
32
32
  key: 'Alt-Escape',
@@ -2,19 +2,19 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type Extension } from '@codemirror/state';
5
6
  import { EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
6
7
 
7
8
  import { type CleanupFn, addEventListener } from '@dxos/async';
8
9
 
9
- import { closeEffect, openEffect } from './action';
10
-
11
- export type FloatingMenuOptions = {
10
+ export type MenuOptions = {
12
11
  icon?: string;
13
12
  height?: number;
14
13
  padding?: number;
15
14
  };
16
15
 
17
- export const floatingMenu = (options: FloatingMenuOptions = {}) => [
16
+ // TODO(burdon): Replace with popover.
17
+ export const menu = (options: MenuOptions = {}): Extension => [
18
18
  ViewPlugin.fromClass(
19
19
  class {
20
20
  view: EditorView;
@@ -36,7 +36,7 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
36
36
  icon.setAttribute('icon', options.icon ?? 'ph--dots-three-vertical--regular');
37
37
 
38
38
  this.tag = document.createElement('dx-anchor');
39
- this.tag.classList.add('cm-floating-menu-trigger');
39
+ this.tag.classList.add('cm-popover-trigger');
40
40
  this.tag.appendChild(icon);
41
41
  }
42
42
 
@@ -63,12 +63,12 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
63
63
  }
64
64
 
65
65
  // TODO(burdon): Timer to fade in/out.
66
- if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(openEffect)))) {
66
+ /*if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(openEffect)))) {
67
67
  this.tag.style.display = 'none';
68
68
  this.tag.classList.add('opacity-10');
69
69
  } else if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(closeEffect)))) {
70
70
  this.tag.style.display = '';
71
- } else if (
71
+ } else */ if (
72
72
  update.docChanged ||
73
73
  update.focusChanged ||
74
74
  update.geometryChanged ||
@@ -111,7 +111,7 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
111
111
  ),
112
112
 
113
113
  EditorView.theme({
114
- '.cm-floating-menu-trigger': {
114
+ '.cm-popover-trigger': {
115
115
  position: 'fixed',
116
116
  padding: '0',
117
117
  border: 'none',
@@ -121,7 +121,7 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
121
121
  width: '2rem',
122
122
  height: '2rem',
123
123
  },
124
- '&:focus-within .cm-floating-menu-trigger': {
124
+ '&:focus-within .cm-popover-trigger': {
125
125
  opacity: '1',
126
126
  },
127
127
  }),
@@ -7,11 +7,11 @@ import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate
7
7
 
8
8
  import { mx } from '@dxos/react-ui-theme';
9
9
 
10
- import { floatingMenu } from '../command';
11
10
  import { decorateMarkdown } from '../markdown';
12
11
 
13
12
  import { commands } from './commands';
14
13
  import { editor } from './editor';
14
+ import { menu } from './menu';
15
15
  import { selectionCompartment, selectionEquals, selectionFacet } from './selection';
16
16
  import { outlinerTree, treeFacet } from './tree';
17
17
 
@@ -52,7 +52,7 @@ export const outliner = (_options: OutlinerProps = {}): Extension => [
52
52
  editor(),
53
53
 
54
54
  // Floating menu.
55
- floatingMenu(),
55
+ menu(),
56
56
 
57
57
  // Line decorations.
58
58
  decorations(),