@dxos/react-ui-editor 0.4.8-main.aa7abac → 0.4.8-main.aafbaef

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.
@@ -2,9 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { closeBrackets } from '@codemirror/autocomplete';
6
- import { history } from '@codemirror/commands';
5
+ import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
6
+ import { history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
7
7
  import { bracketMatching } from '@codemirror/language';
8
+ import { searchKeymap } from '@codemirror/search';
8
9
  import { EditorState, type Extension } from '@codemirror/state';
9
10
  import {
10
11
  EditorView,
@@ -12,6 +13,8 @@ import {
12
13
  drawSelection,
13
14
  dropCursor,
14
15
  highlightActiveLine,
16
+ highlightSpecialChars,
17
+ keymap,
15
18
  lineNumbers,
16
19
  placeholder,
17
20
  scrollPastEnd,
@@ -32,8 +35,6 @@ import { awareness, SpaceAwarenessProvider } from './awareness';
32
35
  import { type ThemeStyles } from '../styles';
33
36
  import { defaultTheme } from '../themes';
34
37
 
35
- // TODO(burdon): Move into extensions folder.
36
-
37
38
  //
38
39
  // Basic
39
40
  //
@@ -41,8 +42,8 @@ import { defaultTheme } from '../themes';
41
42
  /**
42
43
  * https://codemirror.net/docs/extensions
43
44
  * https://github.com/codemirror/basic-setup
45
+ * https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
44
46
  */
45
- // TODO(burdon): Reconcile with createMarkdownExtensions.
46
47
  export type BasicExtensionsOptions = {
47
48
  allowMultipleSelections?: boolean;
48
49
  bracketMatching?: boolean;
@@ -53,21 +54,27 @@ export type BasicExtensionsOptions = {
53
54
  editable?: boolean;
54
55
  highlightActiveLine?: boolean;
55
56
  history?: boolean;
57
+ standardKeymap?: boolean;
58
+ indentWithTab?: boolean;
56
59
  lineNumbers?: boolean;
57
60
  lineWrapping?: boolean;
58
61
  placeholder?: string;
59
62
  readonly?: boolean;
63
+ search?: boolean;
60
64
  scrollPastEnd?: boolean;
61
65
  tabSize?: number;
62
66
  };
63
67
 
64
68
  const defaults: BasicExtensionsOptions = {
69
+ allowMultipleSelections: true,
65
70
  bracketMatching: true,
66
71
  closeBrackets: true,
67
72
  drawSelection: true,
68
73
  editable: true,
69
74
  history: true,
75
+ standardKeymap: true,
70
76
  lineWrapping: true,
77
+ search: true,
71
78
  };
72
79
 
73
80
  export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
@@ -85,6 +92,7 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
85
92
  props.dropCursor && dropCursor(),
86
93
  props.drawSelection && drawSelection(),
87
94
  props.highlightActiveLine && highlightActiveLine(),
95
+ highlightSpecialChars(),
88
96
  props.history && history(),
89
97
  props.lineNumbers && lineNumbers(),
90
98
  props.lineWrapping && EditorView.lineWrapping,
@@ -92,6 +100,22 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
92
100
  props.readonly && [EditorState.readOnly.of(true), EditorView.editable.of(false)],
93
101
  props.scrollPastEnd && scrollPastEnd(),
94
102
  props.tabSize && EditorState.tabSize.of(props.tabSize),
103
+
104
+ // https://codemirror.net/docs/ref/#view.KeyBinding
105
+ keymap.of(
106
+ [
107
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
108
+ ...(props.standardKeymap ? standardKeymap : []),
109
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
110
+ ...(props.indentWithTab ? [indentWithTab] : []),
111
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
112
+ ...(props.closeBrackets ? closeBracketsKeymap : []),
113
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
114
+ ...(props.history ? historyKeymap : []),
115
+ // https://codemirror.net/docs/ref/#search.searchKeymap
116
+ ...(props.search ? searchKeymap : []),
117
+ ].filter(isNotFalsy),
118
+ ),
95
119
  ].filter(isNotFalsy);
96
120
  };
97
121
 
@@ -135,12 +159,11 @@ export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: Theme
135
159
 
136
160
  export type DataExtensionsProps = {
137
161
  id: string;
138
- text: DocAccessor; // TODO(burdon): Rename content.
162
+ text: DocAccessor;
139
163
  space?: Space;
140
164
  identity?: Identity | null;
141
165
  };
142
166
 
143
- // TODO(burdon): Factor out automerge defs and extension (not hook).
144
167
  // TODO(burdon): Move out of react-ui-editor (remove echo deps).
145
168
  export const createDataExtensions = ({ id, text, space, identity }: DataExtensionsProps): Extension[] => {
146
169
  const extensions: Extension[] = [automerge(text)];
@@ -2,12 +2,12 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { closeBracketsKeymap } from '@codemirror/autocomplete';
6
- import { historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
5
+ import { completionKeymap } from '@codemirror/autocomplete';
6
+ import { defaultKeymap, indentWithTab } from '@codemirror/commands';
7
7
  import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
8
8
  import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
9
9
  import { languages } from '@codemirror/language-data';
10
- import { searchKeymap } from '@codemirror/search';
10
+ import { lintKeymap } from '@codemirror/lint';
11
11
  import { type Extension } from '@codemirror/state';
12
12
  import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
13
13
  import { keymap } from '@codemirror/view';
@@ -60,14 +60,11 @@ export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions =
60
60
  keymap.of([
61
61
  // https://codemirror.net/docs/ref/#commands.indentWithTab
62
62
  indentWithTab,
63
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
64
- ...closeBracketsKeymap,
65
- // https://codemirror.net/docs/ref/#commands.historyKeymap
66
- ...historyKeymap,
67
- // https://codemirror.net/docs/ref/#search.searchKeymap
68
- ...searchKeymap,
69
- // https://codemirror.net/docs/ref/#commands.standardKeymap
70
- ...standardKeymap,
63
+
64
+ // https://codemirror.net/docs/ref/#commands.defaultKeymap
65
+ ...defaultKeymap,
66
+ ...completionKeymap,
67
+ ...lintKeymap,
71
68
  ]),
72
69
  ];
73
70
  };
@@ -15,9 +15,7 @@ import {
15
15
  } from '@codemirror/state';
16
16
  import { EditorView, keymap } from '@codemirror/view';
17
17
  import { type SyntaxNodeRef, type SyntaxNode } from '@lezer/common';
18
- import { useMemo } from 'react';
19
-
20
- import { useStateRef } from '@dxos/react-async';
18
+ import { useMemo, useState } from 'react';
21
19
 
22
20
  // Markdown refs:
23
21
  // https://github.github.com/gfm
@@ -1225,20 +1223,22 @@ export const getFormatting = (state: EditorState): Formatting => {
1225
1223
  * Hook provides an extension to compute the current formatting state.
1226
1224
  */
1227
1225
  export const useFormattingState = (): [Formatting | undefined, Extension] => {
1228
- const [state, setState, stateRef] = useStateRef<Formatting>();
1226
+ const [state, setState] = useState<Formatting>();
1227
+
1229
1228
  const observer = useMemo(
1230
1229
  () =>
1231
1230
  EditorView.updateListener.of((update) => {
1232
1231
  if (update.docChanged || update.selectionSet) {
1233
- const newState = getFormatting(update.state);
1234
- if (!stateRef.current || !formattingEquals(stateRef.current, newState)) {
1235
- // TODO(burdon): Error on first click on toolbar.
1236
- // Warning: React has detected a change in the order of Hooks called by ForwardRef. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
1237
- setState(newState);
1238
- }
1232
+ setState((prevState) => {
1233
+ const newState = getFormatting(update.state);
1234
+ if (!prevState || !formattingEquals(prevState, newState)) {
1235
+ return newState;
1236
+ }
1237
+ return prevState;
1238
+ });
1239
1239
  }
1240
1240
  }),
1241
- [],
1241
+ [setState],
1242
1242
  );
1243
1243
 
1244
1244
  return [state, observer];
@@ -5,10 +5,11 @@
5
5
  import { type Extension, Facet } from '@codemirror/state';
6
6
  import { keymap } from '@codemirror/view';
7
7
  import { vim } from '@replit/codemirror-vim';
8
+ import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
8
9
 
9
10
  export const focusEvent = 'focus.container';
10
11
 
11
- export type EditorMode = 'default' | 'vim' | undefined;
12
+ export type EditorMode = 'default' | 'vim' | 'vscode' | undefined;
12
13
 
13
14
  export type EditorConfig = {
14
15
  type: string;
@@ -21,7 +22,13 @@ export const editorMode = Facet.define<EditorConfig, EditorConfig>({
21
22
 
22
23
  export const EditorModes: { [mode: string]: Extension } = {
23
24
  default: [],
25
+ vscode: [
26
+ // https://github.com/replit/codemirror-vscode-keymap
27
+ editorMode.of({ type: 'vscode' }),
28
+ keymap.of(vscodeKeymap),
29
+ ],
24
30
  vim: [
31
+ // https://github.com/replit/codemirror-vim
25
32
  vim(),
26
33
  editorMode.of({ type: 'vim', noTabster: true }),
27
34
  keymap.of([
@@ -4,11 +4,17 @@
4
4
 
5
5
  import { useMemo } from 'react';
6
6
 
7
- import { createDocAccessor, type DocAccessor, getTextContent, type TextObject } from '@dxos/echo-schema';
7
+ import {
8
+ createDocAccessor,
9
+ type DocAccessor,
10
+ getTextContent,
11
+ type TextObject,
12
+ type EchoReactiveObject,
13
+ } from '@dxos/echo-schema';
8
14
 
9
15
  // TODO(burdon): Factor out.
10
16
  export const useDocAccessor = <T = any>(
11
- text: TextObject,
17
+ text: TextObject | EchoReactiveObject<{ content: string }>,
12
18
  ): { id: string; doc: string | undefined; accessor: DocAccessor<T> } => {
13
19
  return useMemo(
14
20
  () => ({
@@ -236,28 +236,44 @@ export const defaultTheme: ThemeStyles = {
236
236
  * </div>
237
237
  * </div
238
238
  */
239
- '.cm-panels': {
240
- border: `1px solid ${get(tokens, 'extend.colors.neutral.200')}`,
241
- },
239
+ '.cm-panels': {},
242
240
  '.cm-panel': {
243
- background: get(tokens, 'extend.colors.neutral.50'),
244
241
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
245
242
  },
243
+ '.cm-panel input[type=checkbox]': {
244
+ marginRight: '0.4rem !important',
245
+ },
246
+ '&light .cm-panel': {
247
+ background: get(tokens, 'extend.colors.neutral.50'),
248
+ },
249
+ '&dark .cm-panel': {
250
+ background: get(tokens, 'extend.colors.neutral.850'),
251
+ },
246
252
  '.cm-button': {
247
253
  margin: '4px',
248
254
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
249
- background: get(tokens, 'extend.colors.neutral.100'),
250
255
  backgroundImage: 'none',
251
256
  border: 'none',
257
+ '&:active': {
258
+ backgroundImage: 'none',
259
+ },
260
+ },
261
+ '&light .cm-button': {
262
+ background: get(tokens, 'extend.colors.neutral.100'),
252
263
  '&:hover': {
253
264
  background: get(tokens, 'extend.colors.neutral.200'),
254
265
  },
255
266
  '&:active': {
256
267
  background: get(tokens, 'extend.colors.neutral.300'),
257
- backgroundImage: 'none',
258
268
  },
259
269
  },
260
- '.cm-panel input[type=checkbox]': {
261
- marginRight: '0.4rem !important',
270
+ '&dark .cm-button': {
271
+ background: get(tokens, 'extend.colors.neutral.800'),
272
+ '&:hover': {
273
+ background: get(tokens, 'extend.colors.neutral.700'),
274
+ },
275
+ '&:active': {
276
+ background: get(tokens, 'extend.colors.neutral.600'),
277
+ },
262
278
  },
263
279
  };