@dxos/react-ui-editor 0.4.8-main.ec0899a → 0.4.8-next.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.
- package/dist/lib/browser/index.mjs +449 -273
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -2
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +11 -5
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +4 -2
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -0
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +1 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +7 -4
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/image.d.ts +6 -0
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
- package/dist/types/src/extensions/modes.d.ts +1 -0
- package/dist/types/src/extensions/modes.d.ts.map +1 -1
- package/dist/types/src/extensions/state.d.ts +20 -0
- package/dist/types/src/extensions/state.d.ts.map +1 -0
- package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.stories.d.ts +1 -0
- package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +25 -27
- package/src/components/TextEditor/TextEditor.stories.tsx +16 -4
- package/src/components/TextEditor/TextEditor.tsx +13 -12
- package/src/components/Toolbar/Toolbar.stories.tsx +23 -24
- package/src/components/Toolbar/Toolbar.tsx +96 -35
- package/src/extensions/comments.ts +2 -2
- package/src/extensions/factories.ts +0 -3
- package/src/extensions/index.ts +1 -0
- package/src/extensions/markdown/formatting.test.ts +13 -13
- package/src/extensions/markdown/formatting.ts +91 -83
- package/src/extensions/markdown/image.ts +6 -0
- package/src/extensions/modes.ts +3 -1
- package/src/extensions/state.ts +90 -0
- package/src/hooks/useActionHandler.ts +5 -1
- package/src/hooks/useTextEditor.stories.tsx +3 -3
- package/src/hooks/useTextEditor.ts +3 -0
- package/src/translations.ts +1 -0
|
@@ -9,20 +9,20 @@ import { expect } from 'chai';
|
|
|
9
9
|
import { describe, test } from '@dxos/test';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
addBlockquote,
|
|
13
|
+
addCodeblock,
|
|
14
|
+
addLink,
|
|
15
|
+
addList,
|
|
13
16
|
addStyle,
|
|
17
|
+
getFormatting,
|
|
14
18
|
removeStyle,
|
|
15
|
-
addLink,
|
|
16
19
|
removeLink,
|
|
17
|
-
addList,
|
|
18
20
|
removeList,
|
|
19
|
-
addBlockquote,
|
|
20
21
|
removeBlockquote,
|
|
21
|
-
addCodeblock,
|
|
22
22
|
removeCodeblock,
|
|
23
|
+
setHeading,
|
|
23
24
|
Inline,
|
|
24
25
|
List,
|
|
25
|
-
getFormatting,
|
|
26
26
|
type Formatting,
|
|
27
27
|
} from './formatting';
|
|
28
28
|
|
|
@@ -204,22 +204,22 @@ describe('removeStyle', () => {
|
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
describe('addLink', () => {
|
|
207
|
-
testCommand('adds a link', '{}', addLink, '[]({})');
|
|
207
|
+
testCommand('adds a link', '{}', addLink(), '[]({})');
|
|
208
208
|
|
|
209
|
-
testCommand('adds a link around text', 'hello {world}', addLink, 'hello [world]({})');
|
|
209
|
+
testCommand('adds a link around text', 'hello {world}', addLink(), 'hello [world]({})');
|
|
210
210
|
|
|
211
|
-
testCommand('clears existing links', '[hello {world}](foo)', addLink, 'hello [world]({})');
|
|
211
|
+
testCommand('clears existing links', '[hello {world}](foo)', addLink(), 'hello [world]({})');
|
|
212
212
|
|
|
213
|
-
testCommand('does nothing across blocks', '{one\n\ntwo}', addLink, null);
|
|
213
|
+
testCommand('does nothing across blocks', '{one\n\ntwo}', addLink(), null);
|
|
214
214
|
|
|
215
|
-
testCommand('does nothing in code blocks', '```\n{one}\n```', addLink, null);
|
|
215
|
+
testCommand('does nothing in code blocks', '```\n{one}\n```', addLink(), null);
|
|
216
216
|
|
|
217
|
-
testCommand('patches up overlapping styles before', '*foo {bar* baz}', addLink, '*foo* [*bar* baz]({})');
|
|
217
|
+
testCommand('patches up overlapping styles before', '*foo {bar* baz}', addLink(), '*foo* [*bar* baz]({})');
|
|
218
218
|
|
|
219
219
|
testCommand(
|
|
220
220
|
'patches up overlapping styles after',
|
|
221
221
|
'one {two ~~three} four~~',
|
|
222
|
-
addLink,
|
|
222
|
+
addLink(),
|
|
223
223
|
'one [two ~~three~~]({}) ~~four~~',
|
|
224
224
|
);
|
|
225
225
|
});
|
|
@@ -15,7 +15,9 @@ 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 {
|
|
18
|
+
import { useMemo } from 'react';
|
|
19
|
+
|
|
20
|
+
import { useStateRef } from '@dxos/react-async';
|
|
19
21
|
|
|
20
22
|
// Markdown refs:
|
|
21
23
|
// https://github.github.com/gfm
|
|
@@ -56,7 +58,7 @@ export type Formatting = {
|
|
|
56
58
|
listStyle: null | 'ordered' | 'bullet' | 'task';
|
|
57
59
|
};
|
|
58
60
|
|
|
59
|
-
export const
|
|
61
|
+
export const formattingEquals = (a: Formatting, b: Formatting) =>
|
|
60
62
|
a.blockType === b.blockType &&
|
|
61
63
|
a.strong === b.strong &&
|
|
62
64
|
a.emphasis === b.emphasis &&
|
|
@@ -470,90 +472,94 @@ export const removeLink: StateCommand = ({ state, dispatch }) => {
|
|
|
470
472
|
};
|
|
471
473
|
|
|
472
474
|
// Add link markup around the selection
|
|
473
|
-
export const addLink
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
475
|
+
export const addLink =
|
|
476
|
+
({ url, image }: { url?: string; image?: boolean } = {}): StateCommand =>
|
|
477
|
+
({ state, dispatch }) => {
|
|
478
|
+
const changes = state.changeByRange((range) => {
|
|
479
|
+
let { from, to } = range;
|
|
480
|
+
const cutStyles: SyntaxNode[] = [];
|
|
481
|
+
let okay: boolean | null = null;
|
|
482
|
+
// Check whether this range is in a position where a link makes sense.
|
|
483
|
+
syntaxTree(state).iterate({
|
|
484
|
+
from,
|
|
485
|
+
to,
|
|
486
|
+
enter: (node) => {
|
|
487
|
+
if (Object.hasOwn(Textblocks, node.name)) {
|
|
488
|
+
// If the selection spans multiple textblocks or is in a code block, abort.
|
|
489
|
+
okay =
|
|
490
|
+
Textblocks[node.name] !== 'codeblock' &&
|
|
491
|
+
from >= blockContentStart(node) &&
|
|
492
|
+
to <= blockContentEnd(node, state.doc);
|
|
493
|
+
} else if (Object.hasOwn(InlineMarker, node.name)) {
|
|
494
|
+
// Look for inline styles that partially overlap the range.
|
|
495
|
+
// Expand the range over them if they start directly outside, otherwise mark them for later.
|
|
496
|
+
const sNode = node.node;
|
|
497
|
+
if (node.from < from && node.to <= to) {
|
|
498
|
+
if (sNode.firstChild!.to === from) {
|
|
499
|
+
from = node.from;
|
|
500
|
+
} else {
|
|
501
|
+
cutStyles.push(sNode);
|
|
502
|
+
}
|
|
503
|
+
} else if (node.from >= from && node.to > to) {
|
|
504
|
+
if (sNode.lastChild!.from === to) {
|
|
505
|
+
to = node.to;
|
|
506
|
+
} else {
|
|
507
|
+
cutStyles.push(sNode);
|
|
508
|
+
}
|
|
506
509
|
}
|
|
507
510
|
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
+
},
|
|
512
|
+
});
|
|
511
513
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
514
|
+
if (okay === null) {
|
|
515
|
+
// No textblock found around selection. Check if the rest of the line is empty.
|
|
516
|
+
const line = state.doc.lineAt(from);
|
|
517
|
+
okay = to <= line.to && !/\S/.test(line.text.slice(from - line.from));
|
|
518
|
+
}
|
|
519
|
+
if (!okay) {
|
|
520
|
+
return { range };
|
|
521
|
+
}
|
|
520
522
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
523
|
+
const changes: ChangeSpec[] = [];
|
|
524
|
+
// Some changes must be moved to end of change array so that they are applied in the right order
|
|
525
|
+
const changesAfter: ChangeSpec[] = [];
|
|
526
|
+
// Clear existing links.
|
|
527
|
+
removeLinkInner(from, to, changesAfter, state);
|
|
528
|
+
let cursorOffset = 1;
|
|
529
|
+
// Close and reopen inline styles that partially overlap the range.
|
|
530
|
+
for (const style of cutStyles) {
|
|
531
|
+
const type = InlineMarker[style.name];
|
|
532
|
+
const mark = inlineMarkerText(type);
|
|
533
|
+
if (style.from < from) {
|
|
534
|
+
// Extends before.
|
|
535
|
+
changes.push({ from: skipSpaces(from, state.doc, -1), insert: mark });
|
|
536
|
+
changesAfter.push({ from: skipSpaces(from, state.doc, 1, to), insert: mark });
|
|
537
|
+
} else {
|
|
538
|
+
changes.push({ from: skipSpaces(to, state.doc, -1, from), insert: mark });
|
|
539
|
+
const after = skipSpaces(to, state.doc, 1);
|
|
540
|
+
if (after === to) {
|
|
541
|
+
cursorOffset += mark.length;
|
|
542
|
+
}
|
|
543
|
+
changesAfter.push({ from: after, insert: mark });
|
|
540
544
|
}
|
|
541
|
-
changesAfter.push({ from: after, insert: mark });
|
|
542
545
|
}
|
|
546
|
+
|
|
547
|
+
// Add the link markup.
|
|
548
|
+
changes.push({ from, insert: image ? '` });
|
|
549
|
+
const changeSet = state.changes(changes.concat(changesAfter));
|
|
550
|
+
// Put the cursor between the title or parenthesis.
|
|
551
|
+
return {
|
|
552
|
+
changes: changeSet,
|
|
553
|
+
range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset - (url ? url.length + 2 : 0)),
|
|
554
|
+
};
|
|
555
|
+
});
|
|
556
|
+
if (changes.changes.empty) {
|
|
557
|
+
return false;
|
|
543
558
|
}
|
|
544
|
-
// Add the link markup.
|
|
545
|
-
changes.push({ from, insert: '[' }, { from: to, insert: ']()' });
|
|
546
|
-
const changeSet = state.changes(changes.concat(changesAfter));
|
|
547
|
-
// Put the cursor between the parenthesis.
|
|
548
|
-
return { changes: changeSet, range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset) };
|
|
549
|
-
});
|
|
550
|
-
if (changes.changes.empty) {
|
|
551
|
-
return false;
|
|
552
|
-
}
|
|
553
559
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
};
|
|
560
|
+
dispatch(state.update(changes, { userEvent: 'format.link.add', scrollIntoView: true }));
|
|
561
|
+
return true;
|
|
562
|
+
};
|
|
557
563
|
|
|
558
564
|
//
|
|
559
565
|
// Lists
|
|
@@ -1057,7 +1063,7 @@ export const getFormatting = (state: EditorState): Formatting => {
|
|
|
1057
1063
|
const inline: (boolean | null)[] = [null, null, null, null];
|
|
1058
1064
|
let link: boolean = false;
|
|
1059
1065
|
let blockQuote: boolean | null = null;
|
|
1060
|
-
// False indicates mixed list styles
|
|
1066
|
+
// False indicates mixed list styles.
|
|
1061
1067
|
let listStyle: Formatting['listStyle'] | null | false = null;
|
|
1062
1068
|
|
|
1063
1069
|
// Track block context for list/blockquote handling.
|
|
@@ -1216,16 +1222,18 @@ export const getFormatting = (state: EditorState): Formatting => {
|
|
|
1216
1222
|
};
|
|
1217
1223
|
|
|
1218
1224
|
/**
|
|
1219
|
-
* Hook
|
|
1225
|
+
* Hook provides an extension to compute the current formatting state.
|
|
1220
1226
|
*/
|
|
1221
|
-
export const useFormattingState = (): [Formatting |
|
|
1222
|
-
const [state, setState] =
|
|
1227
|
+
export const useFormattingState = (): [Formatting | undefined, Extension] => {
|
|
1228
|
+
const [state, setState, stateRef] = useStateRef<Formatting>();
|
|
1223
1229
|
const observer = useMemo(
|
|
1224
1230
|
() =>
|
|
1225
1231
|
EditorView.updateListener.of((update) => {
|
|
1226
1232
|
if (update.docChanged || update.selectionSet) {
|
|
1227
1233
|
const newState = getFormatting(update.state);
|
|
1228
|
-
if (!
|
|
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
|
|
1229
1237
|
setState(newState);
|
|
1230
1238
|
}
|
|
1231
1239
|
}
|
package/src/extensions/modes.ts
CHANGED
|
@@ -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:
|
|
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
|
+
};
|
|
@@ -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':
|
|
@@ -67,7 +67,7 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
|
|
|
67
67
|
<EditorModeToolbar editorMode={editorMode} setEditorMode={setEditorMode} />
|
|
68
68
|
</Toolbar.Root>
|
|
69
69
|
<div role='none' className='grow overflow-hidden'>
|
|
70
|
-
<div
|
|
70
|
+
<div className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />
|
|
71
71
|
</div>
|
|
72
72
|
</div>
|
|
73
73
|
);
|
|
@@ -117,7 +117,7 @@ export default {
|
|
|
117
117
|
export const Default = {
|
|
118
118
|
render: () => {
|
|
119
119
|
const { parentRef } = useTextEditor();
|
|
120
|
-
return <div
|
|
120
|
+
return <div className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />;
|
|
121
121
|
},
|
|
122
122
|
};
|
|
123
123
|
|
|
@@ -127,7 +127,7 @@ export const Basic = {
|
|
|
127
127
|
const { parentRef } = useTextEditor(() => ({
|
|
128
128
|
extensions: [createBasicExtensions({ placeholder: 'Enter text...' }), createThemeExtensions({ themeMode })],
|
|
129
129
|
}));
|
|
130
|
-
return <div
|
|
130
|
+
return <div className={mx(textBlockWidth, attentionSurface)} ref={parentRef} />;
|
|
131
131
|
},
|
|
132
132
|
};
|
|
133
133
|
|
|
@@ -33,6 +33,8 @@ export const useTextEditor = (cb: () => UseTextEditorProps = () => ({}), deps: D
|
|
|
33
33
|
useEffect(() => {
|
|
34
34
|
let view: EditorView;
|
|
35
35
|
if (parentRef.current) {
|
|
36
|
+
log('create', { id });
|
|
37
|
+
|
|
36
38
|
// https://codemirror.net/docs/ref/#state.EditorStateConfig
|
|
37
39
|
const state = EditorState.create({
|
|
38
40
|
doc,
|
|
@@ -69,6 +71,7 @@ export const useTextEditor = (cb: () => UseTextEditorProps = () => ({}), deps: D
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
return () => {
|
|
74
|
+
log('destroy', { id });
|
|
72
75
|
view?.destroy();
|
|
73
76
|
};
|
|
74
77
|
}, deps);
|
package/src/translations.ts
CHANGED