@dxos/react-ui-editor 0.4.8-main.eb5ae94 → 0.4.8-main.fff1521

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 (127) hide show
  1. package/dist/lib/browser/index.mjs +2725 -2711
  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.d.ts +18 -34
  5. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +80 -5
  7. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.d.ts +11 -5
  9. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  10. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +4 -2
  11. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  12. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  13. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +2 -1
  15. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  16. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  17. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  18. package/dist/types/src/extensions/automerge/defs.d.ts +1 -15
  19. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  20. package/dist/types/src/extensions/automerge/index.d.ts +0 -1
  21. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/sync.d.ts +2 -1
  23. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -1
  25. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  26. package/dist/types/src/{hooks → extensions/awareness}/awareness-provider.d.ts +1 -1
  27. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -0
  28. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -0
  29. package/dist/types/src/extensions/awareness/index.d.ts +3 -0
  30. package/dist/types/src/extensions/awareness/index.d.ts.map +1 -0
  31. package/dist/types/src/extensions/comments.d.ts +2 -2
  32. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  33. package/dist/types/src/extensions/cursor-line-margin.d.ts +6 -7
  34. package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -1
  35. package/dist/types/src/extensions/cursor.d.ts +1 -1
  36. package/dist/types/src/extensions/cursor.d.ts.map +1 -1
  37. package/dist/types/src/extensions/doc.d.ts +3 -0
  38. package/dist/types/src/extensions/doc.d.ts.map +1 -0
  39. package/dist/types/src/extensions/factories.d.ts +49 -0
  40. package/dist/types/src/extensions/factories.d.ts.map +1 -0
  41. package/dist/types/src/extensions/index.d.ts +4 -1
  42. package/dist/types/src/extensions/index.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/bundle.d.ts +5 -4
  44. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  45. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  46. package/dist/types/src/extensions/markdown/formatting.d.ts +8 -5
  47. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/image.d.ts +6 -0
  49. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/table.d.ts +2 -2
  51. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  52. package/dist/types/src/extensions/modes.d.ts +1 -0
  53. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  54. package/dist/types/src/extensions/state.d.ts +20 -0
  55. package/dist/types/src/extensions/state.d.ts.map +1 -0
  56. package/dist/types/src/extensions/types.d.ts +9 -0
  57. package/dist/types/src/extensions/types.d.ts.map +1 -0
  58. package/dist/types/src/hooks/index.d.ts +1 -4
  59. package/dist/types/src/hooks/index.d.ts.map +1 -1
  60. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  61. package/dist/types/src/hooks/useDocAccessor.d.ts +7 -0
  62. package/dist/types/src/hooks/useDocAccessor.d.ts.map +1 -0
  63. package/dist/types/src/hooks/useTextEditor.d.ts +3 -44
  64. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useTextEditor.stories.d.ts +7 -0
  66. package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
  67. package/dist/types/src/themes/default.d.ts +0 -3
  68. package/dist/types/src/themes/default.d.ts.map +1 -1
  69. package/dist/types/src/translations.d.ts +1 -0
  70. package/dist/types/src/translations.d.ts.map +1 -1
  71. package/package.json +25 -27
  72. package/src/components/TextEditor/TextEditor.stories.tsx +520 -23
  73. package/src/components/TextEditor/TextEditor.tsx +45 -156
  74. package/src/components/Toolbar/Toolbar.stories.tsx +54 -55
  75. package/src/components/Toolbar/Toolbar.tsx +96 -35
  76. package/src/extensions/automerge/automerge.stories.tsx +16 -17
  77. package/src/extensions/automerge/automerge.ts +2 -1
  78. package/src/extensions/automerge/cursor.ts +1 -1
  79. package/src/extensions/automerge/defs.ts +1 -22
  80. package/src/extensions/automerge/index.ts +0 -1
  81. package/src/extensions/automerge/sync.ts +2 -9
  82. package/src/extensions/automerge/update-automerge.ts +2 -1
  83. package/src/{hooks → extensions/awareness}/awareness-provider.ts +1 -1
  84. package/src/extensions/{awareness.ts → awareness/awareness.ts} +1 -1
  85. package/src/extensions/awareness/index.ts +6 -0
  86. package/src/extensions/command/state.ts +1 -1
  87. package/src/extensions/comments.ts +9 -5
  88. package/src/extensions/cursor-line-margin.ts +17 -18
  89. package/src/extensions/cursor.ts +1 -1
  90. package/src/extensions/doc.ts +14 -0
  91. package/src/extensions/factories.ts +167 -0
  92. package/src/extensions/index.ts +4 -1
  93. package/src/extensions/markdown/bundle.ts +11 -28
  94. package/src/extensions/markdown/decorate.ts +2 -4
  95. package/src/extensions/markdown/formatting.test.ts +13 -13
  96. package/src/extensions/markdown/formatting.ts +106 -93
  97. package/src/extensions/markdown/image.ts +7 -0
  98. package/src/extensions/markdown/table.ts +11 -3
  99. package/src/extensions/modes.ts +3 -1
  100. package/src/extensions/state.ts +90 -0
  101. package/src/extensions/types.ts +16 -0
  102. package/src/hooks/index.ts +1 -5
  103. package/src/hooks/useActionHandler.ts +5 -1
  104. package/src/hooks/useDocAccessor.ts +21 -0
  105. package/src/hooks/useTextEditor.stories.tsx +40 -35
  106. package/src/hooks/useTextEditor.ts +14 -120
  107. package/src/index.ts +1 -1
  108. package/src/themes/default.ts +1 -28
  109. package/src/translations.ts +1 -0
  110. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +0 -103
  111. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +0 -1
  112. package/dist/types/src/extensions/awareness.d.ts.map +0 -1
  113. package/dist/types/src/extensions/outliner.d.ts +0 -4
  114. package/dist/types/src/extensions/outliner.d.ts.map +0 -1
  115. package/dist/types/src/hooks/awareness-provider.d.ts.map +0 -1
  116. package/dist/types/src/hooks/defs.d.ts +0 -20
  117. package/dist/types/src/hooks/defs.d.ts.map +0 -1
  118. package/dist/types/src/hooks/useEditorView.d.ts +0 -20
  119. package/dist/types/src/hooks/useEditorView.d.ts.map +0 -1
  120. package/dist/types/src/hooks/useTextModel.d.ts +0 -33
  121. package/dist/types/src/hooks/useTextModel.d.ts.map +0 -1
  122. package/src/components/TextEditor/MarkdownEditor.stories.tsx +0 -524
  123. package/src/extensions/outliner.ts +0 -12
  124. package/src/hooks/defs.ts +0 -30
  125. package/src/hooks/useEditorView.ts +0 -32
  126. package/src/hooks/useTextModel.ts +0 -121
  127. /package/dist/types/src/extensions/{awareness.d.ts → awareness/awareness.d.ts} +0 -0
@@ -3,7 +3,14 @@
3
3
  //
4
4
 
5
5
  import { syntaxTree } from '@codemirror/language';
6
- import { type EditorState, type RangeSet, RangeSetBuilder, StateField, type Transaction } from '@codemirror/state';
6
+ import {
7
+ type EditorState,
8
+ type Extension,
9
+ type RangeSet,
10
+ RangeSetBuilder,
11
+ StateField,
12
+ type Transaction,
13
+ } from '@codemirror/state';
7
14
  import { Decoration, EditorView, WidgetType } from '@codemirror/view';
8
15
 
9
16
  // TODO(burdon): Snippet to create basic table.
@@ -18,7 +25,7 @@ export type TableOptions = {};
18
25
  * GFM tables.
19
26
  * https://github.github.com/gfm/#tables-extension
20
27
  */
21
- export const table = (options: TableOptions = {}) => {
28
+ export const table = (options: TableOptions = {}): Extension => {
22
29
  return StateField.define<RangeSet<any>>({
23
30
  create: (state) => update(state, options),
24
31
  update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
@@ -76,7 +83,7 @@ const update = (state: EditorState, options: TableOptions) => {
76
83
 
77
84
  tables.forEach((table) => {
78
85
  const hide = state.readOnly || cursor < table.from || cursor > table.to;
79
- hide &&
86
+ if (hide) {
80
87
  builder.add(
81
88
  table.from,
82
89
  table.to,
@@ -84,6 +91,7 @@ const update = (state: EditorState, options: TableOptions) => {
84
91
  widget: new TableWidget(table),
85
92
  }),
86
93
  );
94
+ }
87
95
 
88
96
  // Add class for styling.
89
97
  builder.add(table.from, table.to, Decoration.mark({ class: 'cm-table' }));
@@ -6,6 +6,8 @@ import { type Extension, Facet } from '@codemirror/state';
6
6
  import { keymap } from '@codemirror/view';
7
7
  import { vim } from '@replit/codemirror-vim';
8
8
 
9
+ export const focusEvent = 'focus.container';
10
+
9
11
  export type EditorMode = 'default' | 'vim' | undefined;
10
12
 
11
13
  export type EditorConfig = {
@@ -27,7 +29,7 @@ export const EditorModes: { [mode: string]: Extension } = {
27
29
  key: 'Alt-Escape',
28
30
  run: (view) => {
29
31
  // Focus container for tab navigation.
30
- view.dispatch({ userEvent: 'focus.container' });
32
+ view.dispatch({ userEvent: focusEvent });
31
33
  return true;
32
34
  },
33
35
  },
@@ -0,0 +1,90 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Extension, Transaction } from '@codemirror/state';
6
+ import { EditorView, keymap } from '@codemirror/view';
7
+
8
+ import { debounce } from '@dxos/async';
9
+ import { invariant } from '@dxos/invariant';
10
+ import { isNotFalsy } from '@dxos/util';
11
+
12
+ import { documentId } from './doc';
13
+
14
+ const scrollAnnotation = 'dxos.org/cm/scrolling';
15
+
16
+ // NOTE: Serializable.
17
+ export type SelectionState = {
18
+ scrollTo: {
19
+ from: number;
20
+ };
21
+ selection: {
22
+ anchor: number;
23
+ head?: number;
24
+ };
25
+ };
26
+
27
+ export type StateOptions = {
28
+ setState: (id: string, state: SelectionState) => void;
29
+ getState: (id: string) => SelectionState | undefined;
30
+ };
31
+
32
+ const keyPrefix = 'dxos.org/react-ui-editor/state';
33
+ export const localStorageStateStoreAdapter: StateOptions = {
34
+ setState: (id, state) => {
35
+ invariant(id);
36
+ localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
37
+ },
38
+ getState: (id) => {
39
+ invariant(id);
40
+ const state = localStorage.getItem(`${keyPrefix}/${id}`);
41
+ return state ? JSON.parse(state) : undefined;
42
+ },
43
+ };
44
+
45
+ /**
46
+ * Track scrolling and selection state to be restored when switching to document.
47
+ */
48
+ export const state = ({ getState, setState }: Partial<StateOptions> = {}): Extension => {
49
+ const setStateDebounced = debounce(setState!, 1_000);
50
+
51
+ return [
52
+ // TODO(burdon): Track scrolling (currently only updates when cursor moves).
53
+ EditorView.updateListener.of(({ view, changes, transactions }) => {
54
+ // TODO(burdon): Don't react to initial scroll.
55
+ const id = view.state.facet(documentId);
56
+ if (!id || transactions.some((tr) => tr.isUserEvent(scrollAnnotation))) {
57
+ return;
58
+ }
59
+
60
+ if (setState) {
61
+ const { top } = view.dom.getBoundingClientRect();
62
+ const pos = view.posAtCoords({ x: 0, y: top });
63
+ if (pos !== null) {
64
+ const { anchor, head } = view.state.selection.main;
65
+ setStateDebounced(id, {
66
+ scrollTo: { from: pos, yMargin: 0 },
67
+ selection: { anchor, head },
68
+ });
69
+ }
70
+ }
71
+ }),
72
+ getState &&
73
+ keymap.of([
74
+ {
75
+ key: 'ctrl-r', // TODO(burdon): Setting to jump back to bookmark.
76
+ run: (view) => {
77
+ const state = getState(view.state.facet(documentId));
78
+ if (state) {
79
+ view.dispatch({
80
+ effects: EditorView.scrollIntoView(state.scrollTo.from, { yMargin: 0 }),
81
+ selection: state.selection,
82
+ annotations: Transaction.userEvent.of(scrollAnnotation),
83
+ });
84
+ }
85
+ return true;
86
+ },
87
+ },
88
+ ]),
89
+ ].filter(isNotFalsy);
90
+ };
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ // Runtime data structure.
6
+ export type Range = {
7
+ from: number;
8
+ to: number;
9
+ };
10
+
11
+ // Persistent data structure.
12
+ // TODO(burdon): Rename annotation?
13
+ export type Comment = {
14
+ id: string;
15
+ cursor?: string;
16
+ };
@@ -2,10 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './awareness-provider';
6
- export * from './defs';
7
-
8
5
  export * from './useActionHandler';
9
- export * from './useEditorView';
6
+ export * from './useDocAccessor';
10
7
  export * from './useTextEditor';
11
- export * from './useTextModel';
@@ -75,7 +75,11 @@ export const useActionHandler = (view?: EditorView | null): ToolbarProps['onActi
75
75
  break;
76
76
 
77
77
  case 'link':
78
- (action.data === false ? removeLink : addLink)(view);
78
+ (action.data === false ? removeLink : addLink())(view);
79
+ break;
80
+
81
+ case 'image':
82
+ addLink({ url: action.data, image: true })(view);
79
83
  break;
80
84
 
81
85
  case 'comment':
@@ -0,0 +1,21 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useMemo } from 'react';
6
+
7
+ import { createDocAccessor, type DocAccessor, getTextContent, type TextObject } from '@dxos/echo-schema';
8
+
9
+ // TODO(burdon): Factor out.
10
+ export const useDocAccessor = <T = any>(
11
+ text: TextObject,
12
+ ): { id: string; doc: string | undefined; accessor: DocAccessor<T> } => {
13
+ return useMemo(
14
+ () => ({
15
+ id: text.id,
16
+ doc: getTextContent(text),
17
+ accessor: createDocAccessor<T>(text),
18
+ }),
19
+ [text],
20
+ );
21
+ };
@@ -4,34 +4,28 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import React, { useMemo, useState } from 'react';
7
+ import React, { useState } from 'react';
8
8
 
9
9
  import { Toolbar as NaturalToolbar, Select, useThemeContext, Tooltip } from '@dxos/react-ui';
10
10
  import { attentionSurface, mx, textBlockWidth } from '@dxos/react-ui-theme';
11
11
  import { withTheme } from '@dxos/storybook-utils';
12
12
 
13
13
  import { useActionHandler } from './useActionHandler';
14
- import { createBasicExtensions, createThemeExtensions, useTextEditor } from './useTextEditor';
14
+ import { useTextEditor } from './useTextEditor';
15
15
  import { Toolbar } from '../components';
16
+ import { createBasicExtensions, createThemeExtensions } from '../extensions';
16
17
  import {
17
18
  type EditorMode,
18
19
  EditorModes,
19
20
  decorateMarkdown,
20
21
  createMarkdownExtensions,
21
- formatting,
22
- image,
22
+ formattingKeymap,
23
23
  table,
24
24
  useFormattingState,
25
+ image,
25
26
  } from '../extensions';
26
- import { markdownTheme } from '../themes';
27
27
  import translations from '../translations';
28
28
 
29
- // TODO(burdon): Demo toolbar with hooks.
30
- // TODO(burdon): Build components from hooks and adapters for model/extensions, etc.
31
- // TODO(burdon): Remove BaseTextEditor.
32
- // TODO(burdon): Move scrolling container layout/logic into TextEditor/MarkdownEditor components.
33
- // TODO(burdon): Define keymap in composer framework format.
34
-
35
29
  type StoryProps = {
36
30
  autoFocus?: boolean;
37
31
  placeholder?: string;
@@ -43,29 +37,24 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
43
37
  const { themeMode } = useThemeContext();
44
38
  const [formattingState, trackFormatting] = useFormattingState();
45
39
  const [editorMode, setEditorMode] = useState<EditorMode>('default');
46
- const extensions = useMemo(
47
- () => [
48
- editorMode ? EditorModes[editorMode] : [],
49
- createBasicExtensions({ readonly }),
50
- createThemeExtensions({
51
- themeMode,
52
- theme: markdownTheme,
53
- slots: {
54
- editor: { className: 'p-2' },
55
- },
56
- }),
57
- // TODO(burdon): Move lineWrapping.
58
- createMarkdownExtensions({ placeholder, lineWrapping: true }),
59
- // TODO(burdon): Move into markdown bundle (with React callbacks).
60
- decorateMarkdown(),
61
- formatting(),
62
- image(),
63
- table(),
64
- trackFormatting,
65
- ],
66
- [editorMode, themeMode, placeholder, trackFormatting, readonly],
40
+ const { parentRef, view } = useTextEditor(
41
+ () => ({
42
+ autoFocus,
43
+ doc,
44
+ extensions: [
45
+ editorMode ? EditorModes[editorMode] : [],
46
+ createBasicExtensions({ placeholder, lineWrapping: true, readonly }),
47
+ createMarkdownExtensions({ themeMode }),
48
+ createThemeExtensions({ themeMode }),
49
+ decorateMarkdown(),
50
+ formattingKeymap(),
51
+ image(),
52
+ table(),
53
+ trackFormatting,
54
+ ],
55
+ }),
56
+ [editorMode, themeMode, placeholder, readonly],
67
57
  );
68
- const { parentRef, view } = useTextEditor({ autoFocus, doc, extensions });
69
58
 
70
59
  const handleAction = useActionHandler(view);
71
60
 
@@ -78,7 +67,7 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
78
67
  <EditorModeToolbar editorMode={editorMode} setEditorMode={setEditorMode} />
79
68
  </Toolbar.Root>
80
69
  <div role='none' className='grow overflow-hidden'>
81
- <div role='textbox' className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />
70
+ <div className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />
82
71
  </div>
83
72
  </div>
84
73
  );
@@ -116,7 +105,6 @@ const EditorModeToolbar = ({
116
105
 
117
106
  export default {
118
107
  title: 'react-ui-editor/useTextEditor',
119
- // component: TextEditor,
120
108
  decorators: [withTheme],
121
109
  render: (args: StoryProps) => (
122
110
  <Tooltip.Provider>
@@ -127,6 +115,23 @@ export default {
127
115
  };
128
116
 
129
117
  export const Default = {
118
+ render: () => {
119
+ const { parentRef } = useTextEditor();
120
+ return <div className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />;
121
+ },
122
+ };
123
+
124
+ export const Basic = {
125
+ render: () => {
126
+ const { themeMode } = useThemeContext();
127
+ const { parentRef } = useTextEditor(() => ({
128
+ extensions: [createBasicExtensions({ placeholder: 'Enter text...' }), createThemeExtensions({ themeMode })],
129
+ }));
130
+ return <div className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />;
131
+ },
132
+ };
133
+
134
+ export const Markdown = {
130
135
  args: {
131
136
  autoFocus: true,
132
137
  placeholder: 'Text...',
@@ -2,57 +2,30 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { closeBrackets } from '@codemirror/autocomplete';
6
- import { history } from '@codemirror/commands';
7
- import { bracketMatching } from '@codemirror/language';
8
- import {
9
- EditorSelection,
10
- EditorState,
11
- type EditorStateConfig,
12
- type Extension,
13
- type StateEffect,
14
- } from '@codemirror/state';
15
- import {
16
- crosshairCursor,
17
- drawSelection,
18
- dropCursor,
19
- EditorView,
20
- highlightActiveLine,
21
- lineNumbers,
22
- placeholder,
23
- scrollPastEnd,
24
- } from '@codemirror/view';
25
- import defaultsDeep from 'lodash.defaultsdeep';
26
- import { type DependencyList, type RefObject, useEffect, useRef, useState } from 'react';
5
+ import { EditorSelection, EditorState } from '@codemirror/state';
6
+ import { EditorView } from '@codemirror/view';
7
+ import { type DependencyList, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
27
8
 
28
9
  import { log } from '@dxos/log';
29
- import { type ThemeMode } from '@dxos/react-ui';
30
10
  import { isNotFalsy } from '@dxos/util';
31
11
 
32
- import { type ThemeStyles } from '../styles';
33
- import { defaultTheme } from '../themes';
12
+ import { type TextEditorProps } from '../components';
13
+ import { documentId } from '../extensions';
34
14
  import { logChanges } from '../util';
35
15
 
36
- export type UseTextEditorOptions = {
37
- autoFocus?: boolean;
38
- scrollTo?: StateEffect<any>;
39
- debug?: boolean;
40
- } & EditorStateConfig;
41
-
42
- // TODO(burdon): Return tuple?
43
16
  export type UseTextEditor = {
44
17
  parentRef: RefObject<HTMLDivElement>;
45
18
  view?: EditorView;
46
19
  };
47
20
 
21
+ export type UseTextEditorProps = Omit<TextEditorProps, 'moveToEndOfLine' | 'dataTestId'>;
22
+
48
23
  /**
49
24
  * Hook for creating editor.
50
25
  */
51
- // TODO(wittjosiah): Does not work in strict mode.
52
- export const useTextEditor = (
53
- { autoFocus, scrollTo, debug, doc, selection, extensions }: UseTextEditorOptions = {},
54
- deps: DependencyList = [],
55
- ): UseTextEditor => {
26
+ export const useTextEditor = (cb: () => UseTextEditorProps = () => ({}), deps: DependencyList = []): UseTextEditor => {
27
+ let { id, doc, selection, extensions, autoFocus, scrollTo, debug } = useMemo<UseTextEditorProps>(cb, deps ?? []);
28
+
56
29
  const onUpdate = useRef<() => void>();
57
30
  const [view, setView] = useState<EditorView>();
58
31
  const parentRef = useRef<HTMLDivElement>(null);
@@ -60,11 +33,14 @@ export const useTextEditor = (
60
33
  useEffect(() => {
61
34
  let view: EditorView;
62
35
  if (parentRef.current) {
36
+ log('create', { id });
37
+
63
38
  // https://codemirror.net/docs/ref/#state.EditorStateConfig
64
39
  const state = EditorState.create({
65
40
  doc,
66
41
  selection,
67
42
  extensions: [
43
+ id && documentId.of(id),
68
44
  // TODO(burdon): Doesn't catch errors in keymap functions.
69
45
  EditorView.exceptionSink.of((err) => {
70
46
  log.catch(err);
@@ -95,6 +71,7 @@ export const useTextEditor = (
95
71
  }
96
72
 
97
73
  return () => {
74
+ log('destroy', { id });
98
75
  view?.destroy();
99
76
  };
100
77
  }, deps);
@@ -107,7 +84,6 @@ export const useTextEditor = (
107
84
  }
108
85
 
109
86
  // Set selection after first update (since content may rerender on focus).
110
- // TODO(burdon): BUG on first render may appear in middle of formatted heading.
111
87
  // TODO(burdon): Make invisible until first render?
112
88
  if (selection || scrollTo) {
113
89
  onUpdate.current = () => {
@@ -124,85 +100,3 @@ export const useTextEditor = (
124
100
 
125
101
  return { parentRef, view };
126
102
  };
127
-
128
- // TODO(burdon): Factor out extension factories.
129
-
130
- /**
131
- * https://codemirror.net/docs/extensions
132
- * https://github.com/codemirror/basic-setup
133
- */
134
- // TODO(burdon): Reconcile with createMarkdownExtensions.
135
- export type BasicExtensionsOptions = {
136
- allowMultipleSelections?: boolean;
137
- bracketMatching?: boolean;
138
- closeBrackets?: boolean;
139
- crosshairCursor?: boolean;
140
- dropCursor?: boolean;
141
- drawSelection?: boolean;
142
- editable?: boolean;
143
- highlightActiveLine?: boolean;
144
- history?: boolean;
145
- lineNumbers?: boolean;
146
- lineWrapping?: boolean;
147
- placeholder?: string;
148
- readonly?: boolean;
149
- scrollPastEnd?: boolean;
150
- tabSize?: number;
151
- };
152
-
153
- const defaults: BasicExtensionsOptions = {
154
- bracketMatching: true,
155
- closeBrackets: true,
156
- drawSelection: true,
157
- editable: true,
158
- history: true,
159
- lineWrapping: true,
160
- };
161
-
162
- export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
163
- const props: BasicExtensionsOptions = defaultsDeep({}, _props, defaults);
164
- return [
165
- // TODO(burdon): Doesn't catch errors in keymap functions.
166
- EditorView.exceptionSink.of((err) => {
167
- log.catch(err);
168
- }),
169
-
170
- props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
171
- props.bracketMatching && bracketMatching(),
172
- props.closeBrackets && closeBrackets(),
173
- props.crosshairCursor && crosshairCursor(),
174
- props.dropCursor && dropCursor(),
175
- props.drawSelection && drawSelection(),
176
- props.highlightActiveLine && highlightActiveLine(),
177
- props.history && history(),
178
- props.lineNumbers && lineNumbers(),
179
- props.lineWrapping && EditorView.lineWrapping,
180
- props.placeholder && placeholder(props.placeholder),
181
- props.readonly && [EditorState.readOnly.of(true), EditorView.editable.of(false)],
182
- props.scrollPastEnd && scrollPastEnd(),
183
- props.tabSize && EditorState.tabSize.of(props.tabSize),
184
- ].filter(isNotFalsy);
185
- };
186
-
187
- export type ThemeExtensionsOptions = {
188
- theme?: ThemeStyles;
189
- themeMode?: ThemeMode;
190
- slots?: {
191
- editor?: {
192
- className?: string;
193
- };
194
- content?: {
195
- className?: string;
196
- };
197
- };
198
- };
199
-
200
- export const createThemeExtensions = ({ theme, themeMode, slots }: ThemeExtensionsOptions = {}): Extension => {
201
- return [
202
- EditorView.baseTheme(defaultTheme),
203
- EditorView.darkTheme.of(themeMode === 'dark'),
204
- theme && EditorView.theme(theme),
205
- slots?.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
206
- slots?.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
207
- ].filter(isNotFalsy);
208
- };
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ export { type EditorView, keymap } from '@codemirror/view';
9
9
  export { tags } from '@lezer/highlight';
10
10
 
11
11
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
12
- export { Doc, YText, YXmlFragment } from '@dxos/text-model';
12
+ export { Doc, YText, YXmlFragment } from '@dxos/text-model'; // TODO(burdon): Remove.
13
13
 
14
14
  export * from './components';
15
15
  export * from './extensions';
@@ -55,7 +55,7 @@ export const defaultTheme: ThemeStyles = {
55
55
  // TODO(burdon): Reconcile with docs: https://codemirror.net/docs/guide
56
56
  // Inside of that is the scroller element. If the editor has its own scrollbar, this one should be styled with overflow: auto. But it doesn't have to—the editor also supports growing to accomodate its content, or growing up to a certain max-height and then scrolling.
57
57
  overflowY: 'auto',
58
- fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
58
+ fontFamily: get(tokens, 'fontFamily.body', []).join(','),
59
59
  lineHeight: 1.5,
60
60
  },
61
61
 
@@ -261,30 +261,3 @@ export const defaultTheme: ThemeStyles = {
261
261
  marginRight: '0.4rem !important',
262
262
  },
263
263
  };
264
-
265
- export const textTheme: ThemeStyles = {
266
- '.cm-scroller': {
267
- fontFamily: get(tokens, 'fontFamily.body', []).join(','),
268
- },
269
- '.cm-placeholder': {
270
- fontFamily: get(tokens, 'fontFamily.body', []).join(','),
271
- },
272
- };
273
-
274
- export const markdownTheme: ThemeStyles = {
275
- '.cm-scroller': {
276
- fontFamily: get(tokens, 'fontFamily.body', []).join(','),
277
- },
278
- '.cm-placeholder': {
279
- fontFamily: get(tokens, 'fontFamily.body', []).join(','),
280
- },
281
- };
282
-
283
- export const codeTheme: ThemeStyles = {
284
- '.cm-scroller': {
285
- fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
286
- },
287
- '.cm-placeholder': {
288
- fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
289
- },
290
- };
@@ -19,6 +19,7 @@ export default [
19
19
  'blockquote label': 'Block quote',
20
20
  'codeblock label': 'Code block',
21
21
  'comment label': 'Create comment',
22
+ 'image label': 'Insert image',
22
23
  'heading label': 'Heading level',
23
24
  'table label': 'Create table',
24
25
  },
@@ -1,103 +0,0 @@
1
- import '@dxosTheme';
2
- import { type EditorView } from '@codemirror/view';
3
- import React from 'react';
4
- import { type TextEditorProps } from './TextEditor';
5
- import { type Comment } from '../../hooks';
6
- type StoryProps = {
7
- id?: string;
8
- text?: string;
9
- comments?: Comment[];
10
- } & Pick<TextEditorProps, 'readonly' | 'placeholder' | 'extensions'>;
11
- declare const _default: {
12
- title: string;
13
- component: React.ForwardRefExoticComponent<TextEditorProps & React.RefAttributes<EditorView | null>>;
14
- decorators: import("@storybook/react").Decorator[];
15
- render: ({ id, text, comments, placeholder, ...props }: StoryProps) => JSX.Element | null;
16
- parameters: {
17
- translations: {
18
- 'en-US': {
19
- "react-ui-editor": {
20
- 'strong label': string;
21
- 'emphasis label': string;
22
- 'strikethrough label': string;
23
- 'code label': string;
24
- 'link label': string;
25
- 'list-bullet label': string;
26
- 'list-ordered label': string;
27
- 'list-task label': string;
28
- 'blockquote label': string;
29
- 'codeblock label': string;
30
- 'comment label': string;
31
- 'heading label': string;
32
- 'table label': string;
33
- };
34
- };
35
- }[];
36
- layout: string;
37
- };
38
- };
39
- export default _default;
40
- export declare const Default: {
41
- render: () => JSX.Element;
42
- };
43
- export declare const Readonly: {
44
- render: () => JSX.Element;
45
- };
46
- export declare const NoExtensions: {
47
- render: () => JSX.Element;
48
- };
49
- export declare const Empty: {
50
- render: () => JSX.Element;
51
- };
52
- export declare const Scrolling: {
53
- render: () => JSX.Element;
54
- };
55
- export declare const ScrollingWithImages: {
56
- render: () => JSX.Element;
57
- };
58
- export declare const Links: {
59
- render: () => JSX.Element;
60
- };
61
- export declare const Image: {
62
- render: () => JSX.Element;
63
- };
64
- export declare const Code: {
65
- render: () => JSX.Element;
66
- };
67
- export declare const Lists: {
68
- render: () => JSX.Element;
69
- };
70
- export declare const Table: {
71
- render: () => JSX.Element;
72
- };
73
- export declare const Autocomplete: {
74
- render: () => JSX.Element;
75
- };
76
- export declare const Mention: {
77
- render: () => JSX.Element;
78
- };
79
- export declare const Command: {
80
- render: () => JSX.Element;
81
- };
82
- export declare const Comments: {
83
- render: () => JSX.Element;
84
- };
85
- export declare const Vim: {
86
- render: () => JSX.Element;
87
- };
88
- export declare const Annotations: {
89
- render: () => JSX.Element;
90
- };
91
- export declare const DND: {
92
- render: () => JSX.Element;
93
- };
94
- export declare const Listener: {
95
- render: () => JSX.Element;
96
- };
97
- export declare const Typewriter: {
98
- render: () => JSX.Element;
99
- };
100
- export declare const Blast: {
101
- render: () => JSX.Element;
102
- };
103
- //# sourceMappingURL=MarkdownEditor.stories.d.ts.map