@dxos/react-ui-editor 0.4.8-next.fff1521 → 0.4.9-main.2d20855

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 (55) hide show
  1. package/dist/lib/browser/index.mjs +1687 -1690
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +3 -0
  5. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/Toolbar/Toolbar.d.ts +1 -6
  7. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +3 -0
  9. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  10. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +3 -0
  11. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  12. package/dist/types/src/extensions/dnd.d.ts +7 -1
  13. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  14. package/dist/types/src/extensions/factories.d.ts +5 -1
  15. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  16. package/dist/types/src/extensions/index.d.ts +0 -1
  17. package/dist/types/src/extensions/index.d.ts.map +1 -1
  18. package/dist/types/src/extensions/markdown/action.d.ts +9 -0
  19. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
  20. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  21. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  22. package/dist/types/src/extensions/markdown/index.d.ts +1 -0
  23. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  24. package/dist/types/src/extensions/modes.d.ts +1 -1
  25. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useActionHandler.d.ts +2 -2
  27. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  28. package/dist/types/src/hooks/useDocAccessor.d.ts +4 -2
  29. package/dist/types/src/hooks/useDocAccessor.d.ts.map +1 -1
  30. package/dist/types/src/hooks/useTextEditor.stories.d.ts +3 -0
  31. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  32. package/dist/types/src/themes/default.d.ts.map +1 -1
  33. package/dist/types/src/translations.d.ts +3 -0
  34. package/dist/types/src/translations.d.ts.map +1 -1
  35. package/package.json +25 -24
  36. package/src/components/TextEditor/TextEditor.stories.tsx +14 -2
  37. package/src/components/Toolbar/Toolbar.stories.tsx +0 -1
  38. package/src/components/Toolbar/Toolbar.tsx +46 -59
  39. package/src/extensions/automerge/automerge.ts +4 -4
  40. package/src/extensions/command/state.ts +2 -2
  41. package/src/extensions/dnd.ts +31 -9
  42. package/src/extensions/factories.ts +41 -15
  43. package/src/extensions/index.ts +0 -1
  44. package/src/extensions/markdown/action.ts +112 -0
  45. package/src/extensions/markdown/bundle.ts +8 -11
  46. package/src/extensions/markdown/formatting.ts +11 -11
  47. package/src/extensions/markdown/index.ts +1 -0
  48. package/src/extensions/modes.ts +8 -1
  49. package/src/hooks/useActionHandler.ts +4 -89
  50. package/src/hooks/useDocAccessor.ts +8 -2
  51. package/src/themes/default.ts +24 -8
  52. package/src/translations.ts +3 -0
  53. package/dist/types/src/extensions/cursor-line-margin.d.ts +0 -7
  54. package/dist/types/src/extensions/cursor-line-margin.d.ts.map +0 -1
  55. package/src/extensions/cursor-line-margin.ts +0 -94
@@ -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];
@@ -2,6 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './action';
5
6
  export * from './bundle';
6
7
  export * from './decorate';
7
8
  export * from './formatting';
@@ -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([
@@ -2,96 +2,11 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import type { EditorView } from '@codemirror/view';
5
+ import { type EditorView } from '@codemirror/view';
6
6
 
7
- import type { ToolbarProps } from '../components';
8
- import {
9
- Inline,
10
- List,
11
- addBlockquote,
12
- addCodeblock,
13
- addLink,
14
- addList,
15
- createComment,
16
- insertTable,
17
- removeBlockquote,
18
- removeCodeblock,
19
- removeLink,
20
- removeList,
21
- setHeading,
22
- setStyle,
23
- toggleBlockquote,
24
- toggleList,
25
- toggleStyle,
26
- } from '../extensions';
7
+ import { type ToolbarProps } from '../components';
8
+ import { processAction } from '../extensions';
27
9
 
28
10
  export const useActionHandler = (view?: EditorView | null): ToolbarProps['onAction'] => {
29
- return (action) => {
30
- if (!view) {
31
- return;
32
- }
33
-
34
- let inlineType, listType;
35
- switch (action.type) {
36
- case 'heading':
37
- setHeading(parseInt(action.data))(view);
38
- break;
39
-
40
- case 'strong':
41
- case 'emphasis':
42
- case 'strikethrough':
43
- case 'code':
44
- inlineType =
45
- action.type === 'strong'
46
- ? Inline.Strong
47
- : action.type === 'emphasis'
48
- ? Inline.Emphasis
49
- : action.type === 'strikethrough'
50
- ? Inline.Strikethrough
51
- : Inline.Code;
52
- (typeof action.data === 'boolean' ? setStyle(inlineType, action.data) : toggleStyle(inlineType))(view);
53
- break;
54
-
55
- case 'list-ordered':
56
- case 'list-bullet':
57
- case 'list-task':
58
- listType =
59
- action.type === 'list-ordered' ? List.Ordered : action.type === 'list-bullet' ? List.Bullet : List.Task;
60
- (action.data === false
61
- ? removeList(listType)
62
- : action.data === true
63
- ? addList(listType)
64
- : toggleList(listType))(view);
65
- break;
66
-
67
- case 'blockquote':
68
- (action.data === false ? removeBlockquote : action.data === true ? addBlockquote : toggleBlockquote)(view);
69
- break;
70
- case 'codeblock':
71
- (action.data === false ? removeCodeblock : addCodeblock)(view);
72
- break;
73
- case 'table':
74
- insertTable(view);
75
- break;
76
-
77
- case 'link':
78
- (action.data === false ? removeLink : addLink())(view);
79
- break;
80
-
81
- case 'image':
82
- addLink({ url: action.data, image: true })(view);
83
- break;
84
-
85
- case 'comment':
86
- createComment(view);
87
- break;
88
- }
89
-
90
- // TODO(burdon): Hack otherwise remains on heading selector.
91
- setTimeout(() => {
92
- if (!view.hasFocus) {
93
- view.focus();
94
- }
95
- });
96
- };
11
+ return (action) => view && processAction(view, action);
97
12
  };
@@ -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
  };
@@ -22,6 +22,9 @@ export default [
22
22
  'image label': 'Insert image',
23
23
  'heading label': 'Heading level',
24
24
  'table label': 'Create table',
25
+ 'heading level label_zero': 'Paragraph',
26
+ 'heading level label_one': 'Heading level {{count}}',
27
+ 'heading level label_other': 'Heading level {{count}}',
25
28
  },
26
29
  },
27
30
  },
@@ -1,7 +0,0 @@
1
- import { type Extension } from '@codemirror/state';
2
- type Options = {
3
- lines?: number;
4
- };
5
- export declare const cursorLineMargin: (options?: Options) => Extension;
6
- export {};
7
- //# sourceMappingURL=cursor-line-margin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cursor-line-margin.d.ts","sourceRoot":"","sources":["../../../../src/extensions/cursor-line-margin.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,SAAS,EAAe,MAAM,mBAAmB,CAAC;AAQlF,KAAK,OAAO,GAAG;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF,eAAO,MAAM,gBAAgB,aAAa,OAAO,KAAkB,SAyElE,CAAC"}
@@ -1,94 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- // Based upon @acheronfail’s implementation, fetched 16 Feb 2024
6
- // https://discuss.codemirror.net/t/cursorscrollmargin-for-v6/7448/5
7
-
8
- import { type EditorState, type Extension, Transaction } from '@codemirror/state';
9
- import { EditorView } from '@codemirror/view';
10
-
11
- import { log } from '@dxos/log';
12
-
13
- const annotation = 'cursorLineMargin';
14
- const lineAtPos = (s: EditorState, pos: number) => s.doc.lineAt(pos).number;
15
-
16
- type Options = {
17
- lines?: number;
18
- };
19
-
20
- // TODO(burdon): Remove. Buggy and creates scrolling problems. Find better (more general) way to deal with HALO notch.
21
- export const cursorLineMargin = (options: Options = { lines: 3 }): Extension => {
22
- const { lines } = options;
23
- if (!lines || lines <= 0) {
24
- return [];
25
- }
26
-
27
- return EditorView.updateListener.of(({ transactions, state, selectionSet, startState, view }) => {
28
- // make sure we don't trigger an infinite loop and ignore our own changes
29
- if (transactions.length < 1 || transactions.some((tr) => tr.isUserEvent(annotation))) {
30
- return;
31
- }
32
-
33
- const s = state;
34
- const { main } = s.selection;
35
-
36
- // editor rect
37
- const rect = view.dom.getBoundingClientRect();
38
- // top and bottom pixel positions of the visible region of the editor
39
- const viewportTop = rect.top - view.documentTop;
40
- const viewportBottom = rect.bottom - view.documentTop;
41
-
42
- // line with main selection
43
- const mainLine = lineAtPos(s, main.head);
44
- // top most visible line
45
- const _visTopLine = lineAtPos(s, view.lineBlockAtHeight(viewportTop).from);
46
-
47
- // bottom most visible line
48
- // NOTE: calculating this is slightly more complex - if the editor is
49
- // larger than all the lines in it, and the `scrollPastEnd()` extension is
50
- // enabled, then we need to calculate where the bottom most visible line
51
- // should be if it extended all the way to the bottom of the editor
52
- const botLineBlk = view.lineBlockAtHeight(viewportBottom);
53
- const visBotLineDoc = lineAtPos(s, Math.min(botLineBlk.to, s.doc.length));
54
- const visBotLine =
55
- botLineBlk.bottom >= viewportBottom
56
- ? visBotLineDoc
57
- : visBotLineDoc + Math.floor((viewportBottom - botLineBlk.bottom) / view.defaultLineHeight);
58
-
59
- // const needsScrollTop = mainLine <= visTopLine + cursorLineMargin;
60
- const needsScrollTop = false;
61
- const needsScrollBot = mainLine >= visBotLine - lines;
62
-
63
- // the scroll margins are overlapping
64
- if (needsScrollTop && needsScrollBot) {
65
- log.warn('is cursorScrollMargin too large for the editor size?');
66
- return;
67
- }
68
-
69
- // no need to scroll, we're in between scroll regions
70
- if (!needsScrollTop && !needsScrollBot) {
71
- return;
72
- }
73
-
74
- // if we're within the margin, but moving the other way, then don't scroll
75
- if (selectionSet) {
76
- const { head } = startState.selection.main;
77
- const oldMainLine = lineAtPos(startState, head);
78
- if (needsScrollTop && oldMainLine < mainLine) {
79
- return;
80
- }
81
- if (needsScrollBot && oldMainLine > mainLine) {
82
- return;
83
- }
84
- }
85
-
86
- view.dispatch({
87
- annotations: Transaction.userEvent.of(annotation),
88
- effects: EditorView.scrollIntoView(s.selection.main.head, {
89
- y: needsScrollTop ? 'start' : 'end',
90
- yMargin: view.defaultLineHeight * lines,
91
- }),
92
- });
93
- });
94
- };