@dxos/react-ui-editor 0.4.8-main.ac78619 → 0.4.8-main.beadd4b
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 +2703 -2714
- 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 +18 -34
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +80 -5
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +10 -4
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +3 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +2 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts +1 -15
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/index.d.ts +0 -1
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts +2 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
- package/dist/types/src/{hooks → extensions/awareness}/awareness-provider.d.ts +1 -1
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness/index.d.ts +3 -0
- package/dist/types/src/extensions/awareness/index.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +2 -2
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/cursor-line-margin.d.ts +6 -7
- package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -1
- package/dist/types/src/extensions/cursor.d.ts +1 -1
- package/dist/types/src/extensions/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/doc.d.ts +3 -0
- package/dist/types/src/extensions/doc.d.ts.map +1 -0
- package/dist/types/src/extensions/factories.d.ts +49 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +4 -1
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts +5 -4
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +5 -2
- 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/markdown/table.d.ts +2 -2
- package/dist/types/src/extensions/markdown/table.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/extensions/types.d.ts +9 -0
- package/dist/types/src/extensions/types.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +1 -4
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useDocAccessor.d.ts +7 -0
- package/dist/types/src/hooks/useDocAccessor.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextEditor.d.ts +3 -44
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.stories.d.ts +7 -0
- package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts +0 -3
- package/dist/types/src/themes/default.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 +520 -23
- package/src/components/TextEditor/TextEditor.tsx +44 -155
- package/src/components/Toolbar/Toolbar.stories.tsx +44 -43
- package/src/components/Toolbar/Toolbar.tsx +86 -32
- package/src/extensions/automerge/automerge.stories.tsx +16 -17
- package/src/extensions/automerge/automerge.ts +2 -1
- package/src/extensions/automerge/cursor.ts +1 -1
- package/src/extensions/automerge/defs.ts +1 -22
- package/src/extensions/automerge/index.ts +0 -1
- package/src/extensions/automerge/sync.ts +2 -9
- package/src/extensions/automerge/update-automerge.ts +2 -1
- package/src/{hooks → extensions/awareness}/awareness-provider.ts +1 -1
- package/src/extensions/{awareness.ts → awareness/awareness.ts} +1 -1
- package/src/extensions/awareness/index.ts +6 -0
- package/src/extensions/command/state.ts +1 -1
- package/src/extensions/comments.ts +9 -5
- package/src/extensions/cursor-line-margin.ts +17 -18
- package/src/extensions/cursor.ts +1 -1
- package/src/extensions/doc.ts +14 -0
- package/src/extensions/factories.ts +167 -0
- package/src/extensions/index.ts +4 -1
- package/src/extensions/markdown/bundle.ts +11 -28
- package/src/extensions/markdown/decorate.ts +2 -4
- package/src/extensions/markdown/formatting.test.ts +13 -13
- package/src/extensions/markdown/formatting.ts +95 -86
- package/src/extensions/markdown/image.ts +7 -0
- package/src/extensions/markdown/table.ts +11 -3
- package/src/extensions/modes.ts +3 -1
- package/src/extensions/state.ts +90 -0
- package/src/extensions/types.ts +16 -0
- package/src/hooks/index.ts +1 -5
- package/src/hooks/useActionHandler.ts +5 -1
- package/src/hooks/useDocAccessor.ts +21 -0
- package/src/hooks/useTextEditor.stories.tsx +40 -35
- package/src/hooks/useTextEditor.ts +11 -120
- package/src/index.ts +1 -1
- package/src/themes/default.ts +1 -28
- package/src/translations.ts +1 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +0 -103
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/awareness.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner.d.ts +0 -4
- package/dist/types/src/extensions/outliner.d.ts.map +0 -1
- package/dist/types/src/hooks/awareness-provider.d.ts.map +0 -1
- package/dist/types/src/hooks/defs.d.ts +0 -20
- package/dist/types/src/hooks/defs.d.ts.map +0 -1
- package/dist/types/src/hooks/useEditorView.d.ts +0 -20
- package/dist/types/src/hooks/useEditorView.d.ts.map +0 -1
- package/dist/types/src/hooks/useTextModel.d.ts +0 -33
- package/dist/types/src/hooks/useTextModel.d.ts.map +0 -1
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +0 -524
- package/src/extensions/outliner.ts +0 -12
- package/src/hooks/defs.ts +0 -30
- package/src/hooks/useEditorView.ts +0 -32
- package/src/hooks/useTextModel.ts +0 -121
- /package/dist/types/src/extensions/{awareness.d.ts → awareness/awareness.d.ts} +0 -0
|
@@ -9,7 +9,7 @@ import '@preact/signals-react';
|
|
|
9
9
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
10
10
|
|
|
11
11
|
import { Repo } from '@dxos/automerge/automerge-repo';
|
|
12
|
-
import { createDocAccessor, Filter } from '@dxos/echo-schema';
|
|
12
|
+
import { createDocAccessor, Filter, DocAccessor } from '@dxos/echo-schema';
|
|
13
13
|
import { type PublicKey } from '@dxos/keys';
|
|
14
14
|
import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
|
|
15
15
|
import { ClientRepeater } from '@dxos/react-client/testing';
|
|
@@ -17,9 +17,9 @@ import { useThemeContext } from '@dxos/react-ui';
|
|
|
17
17
|
import { withTheme } from '@dxos/storybook-utils';
|
|
18
18
|
|
|
19
19
|
import { automerge } from './automerge';
|
|
20
|
-
import {
|
|
21
|
-
import { createBasicExtensions, createThemeExtensions, useTextEditor } from '../../hooks';
|
|
20
|
+
import { useTextEditor } from '../../hooks';
|
|
22
21
|
import translations from '../../translations';
|
|
22
|
+
import { createBasicExtensions, createThemeExtensions } from '../factories';
|
|
23
23
|
|
|
24
24
|
const initialContent = 'Hello world!';
|
|
25
25
|
|
|
@@ -34,21 +34,19 @@ type EditorProps = {
|
|
|
34
34
|
|
|
35
35
|
const Editor = ({ source, autoFocus }: EditorProps) => {
|
|
36
36
|
const { themeMode } = useThemeContext();
|
|
37
|
-
const
|
|
38
|
-
() =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
const { parentRef } = useTextEditor(
|
|
38
|
+
() => ({
|
|
39
|
+
doc: DocAccessor.getValue(source),
|
|
40
|
+
extensions: [
|
|
41
|
+
createBasicExtensions({ placeholder: 'Type here...' }),
|
|
42
|
+
createThemeExtensions({ themeMode, slots: { editor: { className: 'p-2 bg-white dark:bg-black' } } }),
|
|
43
|
+
automerge(source),
|
|
44
|
+
],
|
|
45
|
+
autoFocus,
|
|
46
|
+
}),
|
|
47
|
+
[source, themeMode],
|
|
44
48
|
);
|
|
45
49
|
|
|
46
|
-
const { parentRef } = useTextEditor({
|
|
47
|
-
doc: DocAccessor.getValue(source),
|
|
48
|
-
extensions,
|
|
49
|
-
autoFocus,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
50
|
return <div ref={parentRef} />;
|
|
53
51
|
};
|
|
54
52
|
|
|
@@ -114,7 +112,8 @@ const EchoStory = ({ spaceKey }: { spaceKey: PublicKey }) => {
|
|
|
114
112
|
|
|
115
113
|
export const Default = {};
|
|
116
114
|
|
|
117
|
-
// TODO(burdon):
|
|
115
|
+
// TODO(burdon): Error:
|
|
116
|
+
// chunk-6NX3RPDS.mjs:2021 ControlPipeline#5 Error: invariant violation: Feed already added
|
|
118
117
|
export const WithEcho = {
|
|
119
118
|
decorators: [withTheme],
|
|
120
119
|
render: () => {
|
|
@@ -8,9 +8,10 @@ import { StateField, type Extension } from '@codemirror/state';
|
|
|
8
8
|
import { EditorView, ViewPlugin } from '@codemirror/view';
|
|
9
9
|
|
|
10
10
|
import { next as A } from '@dxos/automerge/automerge';
|
|
11
|
+
import { type DocAccessor } from '@dxos/echo-schema';
|
|
11
12
|
|
|
12
13
|
import { cursorConverter } from './cursor';
|
|
13
|
-
import { updateHeadsEffect, isReconcile, type State
|
|
14
|
+
import { updateHeadsEffect, isReconcile, type State } from './defs';
|
|
14
15
|
import { Syncer } from './sync';
|
|
15
16
|
import { Cursor } from '../cursor';
|
|
16
17
|
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import get from 'lodash.get';
|
|
6
6
|
|
|
7
7
|
import { next as A } from '@dxos/automerge/automerge';
|
|
8
|
+
import { type DocAccessor } from '@dxos/echo-schema';
|
|
8
9
|
import { log } from '@dxos/log';
|
|
9
10
|
|
|
10
|
-
import { type DocAccessor } from './defs';
|
|
11
11
|
import { type CursorConverter } from '../cursor';
|
|
12
12
|
|
|
13
13
|
export const cursorConverter = ({ handle, path }: DocAccessor): CursorConverter => ({
|
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
//
|
|
6
6
|
|
|
7
7
|
import { Annotation, StateEffect, type StateField, type EditorState, type Transaction } from '@codemirror/state';
|
|
8
|
-
import get from 'lodash.get';
|
|
9
8
|
|
|
10
|
-
import { type
|
|
9
|
+
import { type Heads, type Prop } from '@dxos/automerge/automerge';
|
|
11
10
|
|
|
12
11
|
export type State = {
|
|
13
12
|
path: Prop[];
|
|
@@ -31,23 +30,3 @@ export const reconcileAnnotation = Annotation.define<boolean>();
|
|
|
31
30
|
export const isReconcile = (tr: Transaction): boolean => {
|
|
32
31
|
return !!tr.annotation(reconcileAnnotation);
|
|
33
32
|
};
|
|
34
|
-
|
|
35
|
-
// TODO(burdon): Reconcile with echo.
|
|
36
|
-
export interface DocAccessor<T = any> {
|
|
37
|
-
handle: IDocHandle<T>;
|
|
38
|
-
get path(): string[]; // TODO(burdon): Getter or prop?
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// TODO(burdon): Reconcile with echo.
|
|
42
|
-
export const DocAccessor = {
|
|
43
|
-
getValue: (accessor: DocAccessor) => get(accessor.handle, accessor.path),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// TODO(burdon): Reconcile with echo.
|
|
47
|
-
export interface IDocHandle<T = any> {
|
|
48
|
-
docSync(): Doc<T> | undefined;
|
|
49
|
-
change(callback: ChangeFn<T>, options?: ChangeOptions<T>): void;
|
|
50
|
-
changeAt(heads: Heads, callback: ChangeFn<T>, options?: ChangeOptions<T>): string[] | undefined;
|
|
51
|
-
addListener(event: 'change', listener: () => void): void;
|
|
52
|
-
removeListener(event: 'change', listener: () => void): void;
|
|
53
|
-
}
|
|
@@ -8,16 +8,9 @@ import { type StateField } from '@codemirror/state';
|
|
|
8
8
|
import { type EditorView } from '@codemirror/view';
|
|
9
9
|
|
|
10
10
|
import { next as A } from '@dxos/automerge/automerge';
|
|
11
|
+
import { type IDocHandle } from '@dxos/echo-schema';
|
|
11
12
|
|
|
12
|
-
import {
|
|
13
|
-
getLastHeads,
|
|
14
|
-
getPath,
|
|
15
|
-
type IDocHandle,
|
|
16
|
-
type State,
|
|
17
|
-
reconcileAnnotation,
|
|
18
|
-
updateHeads,
|
|
19
|
-
isReconcile,
|
|
20
|
-
} from './defs';
|
|
13
|
+
import { getLastHeads, getPath, type State, reconcileAnnotation, updateHeads, isReconcile } from './defs';
|
|
21
14
|
import { updateAutomerge } from './update-automerge';
|
|
22
15
|
import { updateCodeMirror } from './update-codemirror';
|
|
23
16
|
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
import { type EditorState, type StateField, type Transaction, type Text } from '@codemirror/state';
|
|
8
8
|
|
|
9
9
|
import { next as A, type Heads } from '@dxos/automerge/automerge';
|
|
10
|
+
import { type IDocHandle } from '@dxos/echo-schema';
|
|
10
11
|
|
|
11
|
-
import { type
|
|
12
|
+
import { type State } from './defs';
|
|
12
13
|
|
|
13
14
|
export const updateAutomerge = (
|
|
14
15
|
field: StateField<State>,
|
|
@@ -9,7 +9,7 @@ import { log } from '@dxos/log';
|
|
|
9
9
|
import { type Space } from '@dxos/react-client/echo';
|
|
10
10
|
import { type GossipMessage } from '@dxos/react-client/mesh';
|
|
11
11
|
|
|
12
|
-
import { type AwarenessInfo, type AwarenessPosition, type AwarenessProvider, type AwarenessState } from '
|
|
12
|
+
import { type AwarenessInfo, type AwarenessPosition, type AwarenessProvider, type AwarenessState } from './awareness';
|
|
13
13
|
|
|
14
14
|
type ProtocolMessage =
|
|
15
15
|
| {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import { Event } from '@dxos/async';
|
|
17
17
|
import { Context } from '@dxos/context';
|
|
18
18
|
|
|
19
|
-
import { Cursor, type CursorConverter } from '
|
|
19
|
+
import { Cursor, type CursorConverter } from '../cursor';
|
|
20
20
|
|
|
21
21
|
export interface AwarenessProvider {
|
|
22
22
|
remoteStateChange: Event<void>;
|
|
@@ -74,7 +74,7 @@ export const commandState = StateField.define<CommandState>({
|
|
|
74
74
|
|
|
75
75
|
return state;
|
|
76
76
|
},
|
|
77
|
-
provide: (
|
|
77
|
+
provide: (field) => [showTooltip.from(field, (value) => value.tooltip ?? null)],
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
export const openEffect = StateEffect.define<{ pos: number; fullWidth?: boolean }>();
|
|
@@ -13,7 +13,7 @@ import { log } from '@dxos/log';
|
|
|
13
13
|
import { nonNullable } from '@dxos/util';
|
|
14
14
|
|
|
15
15
|
import { Cursor } from './cursor';
|
|
16
|
-
import { type Comment, type Range } from '
|
|
16
|
+
import { type Comment, type Range } from './types';
|
|
17
17
|
import { getToken } from '../styles';
|
|
18
18
|
import { callbackWrapper } from '../util';
|
|
19
19
|
|
|
@@ -508,12 +508,16 @@ export const focusComment = (view: EditorView, id: string, center = true) => {
|
|
|
508
508
|
/**
|
|
509
509
|
* Update comments state field.
|
|
510
510
|
*/
|
|
511
|
-
export const useComments = (view: EditorView | null, id: string, comments: Comment[] = []) => {
|
|
511
|
+
export const useComments = (view: EditorView | null | undefined, id: string, comments: Comment[] = []) => {
|
|
512
512
|
useEffect(() => {
|
|
513
513
|
if (view) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
514
|
+
// Check same document.
|
|
515
|
+
// NOTE: Hook might be called before editor state is updated.
|
|
516
|
+
if (id === view.state.facet(documentId)) {
|
|
517
|
+
view.dispatch({
|
|
518
|
+
effects: setComments.of({ id, comments }),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
517
521
|
}
|
|
518
522
|
}, [id, view, comments]);
|
|
519
523
|
};
|
|
@@ -5,26 +5,26 @@
|
|
|
5
5
|
// Based upon @acheronfail’s implementation, fetched 16 Feb 2024
|
|
6
6
|
// https://discuss.codemirror.net/t/cursorscrollmargin-for-v6/7448/5
|
|
7
7
|
|
|
8
|
-
import { type EditorState, type Extension,
|
|
8
|
+
import { type EditorState, type Extension, Transaction } from '@codemirror/state';
|
|
9
9
|
import { EditorView } from '@codemirror/view';
|
|
10
10
|
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Number of lines above/below the cursor to keep visible in the viewport.
|
|
15
|
-
* Defaults to 3.
|
|
16
|
-
*/
|
|
17
|
-
export const cursorLineMarginFacet = Facet.define<number, number>({
|
|
18
|
-
combine: (input) => input[0] ?? 3,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
13
|
const annotation = 'cursorLineMargin';
|
|
22
14
|
const lineAtPos = (s: EditorState, pos: number) => s.doc.lineAt(pos).number;
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
28
|
// make sure we don't trigger an infinite loop and ignore our own changes
|
|
29
29
|
if (transactions.length < 1 || transactions.some((tr) => tr.isUserEvent(annotation))) {
|
|
30
30
|
return;
|
|
@@ -32,7 +32,6 @@ export const cursorLineMargin: Extension = EditorView.updateListener.of(
|
|
|
32
32
|
|
|
33
33
|
const s = state;
|
|
34
34
|
const { main } = s.selection;
|
|
35
|
-
const cursorLineMargin = s.facet(cursorLineMarginFacet);
|
|
36
35
|
|
|
37
36
|
// editor rect
|
|
38
37
|
const rect = view.dom.getBoundingClientRect();
|
|
@@ -59,7 +58,7 @@ export const cursorLineMargin: Extension = EditorView.updateListener.of(
|
|
|
59
58
|
|
|
60
59
|
// const needsScrollTop = mainLine <= visTopLine + cursorLineMargin;
|
|
61
60
|
const needsScrollTop = false;
|
|
62
|
-
const needsScrollBot = mainLine >= visBotLine -
|
|
61
|
+
const needsScrollBot = mainLine >= visBotLine - lines;
|
|
63
62
|
|
|
64
63
|
// the scroll margins are overlapping
|
|
65
64
|
if (needsScrollTop && needsScrollBot) {
|
|
@@ -88,8 +87,8 @@ export const cursorLineMargin: Extension = EditorView.updateListener.of(
|
|
|
88
87
|
annotations: Transaction.userEvent.of(annotation),
|
|
89
88
|
effects: EditorView.scrollIntoView(s.selection.main.head, {
|
|
90
89
|
y: needsScrollTop ? 'start' : 'end',
|
|
91
|
-
yMargin: view.defaultLineHeight *
|
|
90
|
+
yMargin: view.defaultLineHeight * lines,
|
|
92
91
|
}),
|
|
93
92
|
});
|
|
94
|
-
}
|
|
95
|
-
|
|
93
|
+
});
|
|
94
|
+
};
|
package/src/extensions/cursor.ts
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Facet } from '@codemirror/state';
|
|
6
|
+
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
|
|
9
|
+
export const documentId = Facet.define<string, string>({
|
|
10
|
+
combine: (providers) => {
|
|
11
|
+
invariant(providers.length <= 1);
|
|
12
|
+
return providers[0];
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { closeBrackets } from '@codemirror/autocomplete';
|
|
6
|
+
import { history } from '@codemirror/commands';
|
|
7
|
+
import { bracketMatching } from '@codemirror/language';
|
|
8
|
+
import { EditorState, type Extension } from '@codemirror/state';
|
|
9
|
+
import {
|
|
10
|
+
EditorView,
|
|
11
|
+
crosshairCursor,
|
|
12
|
+
drawSelection,
|
|
13
|
+
dropCursor,
|
|
14
|
+
highlightActiveLine,
|
|
15
|
+
lineNumbers,
|
|
16
|
+
placeholder,
|
|
17
|
+
scrollPastEnd,
|
|
18
|
+
} from '@codemirror/view';
|
|
19
|
+
import defaultsDeep from 'lodash.defaultsdeep';
|
|
20
|
+
|
|
21
|
+
import { generateName } from '@dxos/display-name';
|
|
22
|
+
import { type DocAccessor } from '@dxos/echo-schema';
|
|
23
|
+
import { log } from '@dxos/log';
|
|
24
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
25
|
+
import { type Identity } from '@dxos/react-client/halo';
|
|
26
|
+
import { type ThemeMode } from '@dxos/react-ui';
|
|
27
|
+
import { type HuePalette, hueTokens } from '@dxos/react-ui-theme';
|
|
28
|
+
import { hexToHue, isNotFalsy } from '@dxos/util';
|
|
29
|
+
|
|
30
|
+
import { automerge } from './automerge';
|
|
31
|
+
import { awareness, SpaceAwarenessProvider } from './awareness';
|
|
32
|
+
import { type ThemeStyles } from '../styles';
|
|
33
|
+
import { defaultTheme } from '../themes';
|
|
34
|
+
|
|
35
|
+
// TODO(burdon): Move into extensions folder.
|
|
36
|
+
|
|
37
|
+
//
|
|
38
|
+
// Basic
|
|
39
|
+
//
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* https://codemirror.net/docs/extensions
|
|
43
|
+
* https://github.com/codemirror/basic-setup
|
|
44
|
+
*/
|
|
45
|
+
// TODO(burdon): Reconcile with createMarkdownExtensions.
|
|
46
|
+
export type BasicExtensionsOptions = {
|
|
47
|
+
allowMultipleSelections?: boolean;
|
|
48
|
+
bracketMatching?: boolean;
|
|
49
|
+
closeBrackets?: boolean;
|
|
50
|
+
crosshairCursor?: boolean;
|
|
51
|
+
dropCursor?: boolean;
|
|
52
|
+
drawSelection?: boolean;
|
|
53
|
+
editable?: boolean;
|
|
54
|
+
highlightActiveLine?: boolean;
|
|
55
|
+
history?: boolean;
|
|
56
|
+
lineNumbers?: boolean;
|
|
57
|
+
lineWrapping?: boolean;
|
|
58
|
+
placeholder?: string;
|
|
59
|
+
readonly?: boolean;
|
|
60
|
+
scrollPastEnd?: boolean;
|
|
61
|
+
tabSize?: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const defaults: BasicExtensionsOptions = {
|
|
65
|
+
bracketMatching: true,
|
|
66
|
+
closeBrackets: true,
|
|
67
|
+
drawSelection: true,
|
|
68
|
+
editable: true,
|
|
69
|
+
history: true,
|
|
70
|
+
lineWrapping: true,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
|
|
74
|
+
const props: BasicExtensionsOptions = defaultsDeep({}, _props, defaults);
|
|
75
|
+
return [
|
|
76
|
+
// TODO(burdon): Doesn't catch errors in keymap functions.
|
|
77
|
+
EditorView.exceptionSink.of((err) => {
|
|
78
|
+
log.catch(err);
|
|
79
|
+
}),
|
|
80
|
+
|
|
81
|
+
props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
|
|
82
|
+
props.bracketMatching && bracketMatching(),
|
|
83
|
+
props.closeBrackets && closeBrackets(),
|
|
84
|
+
props.crosshairCursor && crosshairCursor(),
|
|
85
|
+
props.dropCursor && dropCursor(),
|
|
86
|
+
props.drawSelection && drawSelection(),
|
|
87
|
+
props.highlightActiveLine && highlightActiveLine(),
|
|
88
|
+
props.history && history(),
|
|
89
|
+
props.lineNumbers && lineNumbers(),
|
|
90
|
+
props.lineWrapping && EditorView.lineWrapping,
|
|
91
|
+
props.placeholder && placeholder(props.placeholder),
|
|
92
|
+
props.readonly && [EditorState.readOnly.of(true), EditorView.editable.of(false)],
|
|
93
|
+
props.scrollPastEnd && scrollPastEnd(),
|
|
94
|
+
props.tabSize && EditorState.tabSize.of(props.tabSize),
|
|
95
|
+
].filter(isNotFalsy);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//
|
|
99
|
+
// Theme
|
|
100
|
+
//
|
|
101
|
+
|
|
102
|
+
export type ThemeExtensionsOptions = {
|
|
103
|
+
theme?: ThemeStyles;
|
|
104
|
+
themeMode?: ThemeMode;
|
|
105
|
+
slots?: {
|
|
106
|
+
editor?: {
|
|
107
|
+
className?: string;
|
|
108
|
+
};
|
|
109
|
+
content?: {
|
|
110
|
+
className?: string;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const defaultSlots = {
|
|
116
|
+
editor: {
|
|
117
|
+
className: 'w-full bs-full',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: ThemeExtensionsOptions = {}): Extension => {
|
|
122
|
+
const slots = defaultsDeep({}, _slots, defaultSlots);
|
|
123
|
+
return [
|
|
124
|
+
EditorView.baseTheme(defaultTheme),
|
|
125
|
+
EditorView.darkTheme.of(themeMode === 'dark'),
|
|
126
|
+
theme && EditorView.theme(theme),
|
|
127
|
+
slots.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
|
|
128
|
+
slots.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
|
|
129
|
+
].filter(isNotFalsy);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
//
|
|
133
|
+
// Data
|
|
134
|
+
//
|
|
135
|
+
|
|
136
|
+
export type DataExtensionsProps = {
|
|
137
|
+
id: string;
|
|
138
|
+
text: DocAccessor; // TODO(burdon): Rename content.
|
|
139
|
+
space?: Space;
|
|
140
|
+
identity?: Identity | null;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// TODO(burdon): Factor out automerge defs and extension (not hook).
|
|
144
|
+
// TODO(burdon): Move out of react-ui-editor (remove echo deps).
|
|
145
|
+
export const createDataExtensions = ({ id, text, space, identity }: DataExtensionsProps): Extension[] => {
|
|
146
|
+
const extensions: Extension[] = [automerge(text)];
|
|
147
|
+
|
|
148
|
+
if (space && identity) {
|
|
149
|
+
const peerId = identity?.identityKey.toHex();
|
|
150
|
+
const { cursorLightValue, cursorDarkValue } =
|
|
151
|
+
hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
|
|
152
|
+
const awarenessProvider = new SpaceAwarenessProvider({
|
|
153
|
+
space,
|
|
154
|
+
channel: `awareness.${id}`,
|
|
155
|
+
peerId: identity.identityKey.toHex(),
|
|
156
|
+
info: {
|
|
157
|
+
displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
|
|
158
|
+
color: cursorDarkValue,
|
|
159
|
+
lightColor: cursorLightValue,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
extensions.push(awareness(awarenessProvider));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return extensions;
|
|
167
|
+
};
|
package/src/extensions/index.ts
CHANGED
|
@@ -11,10 +11,13 @@ export * from './command';
|
|
|
11
11
|
export * from './comments';
|
|
12
12
|
export * from './cursor';
|
|
13
13
|
export * from './cursor-line-margin';
|
|
14
|
+
export * from './doc';
|
|
14
15
|
export * from './dnd';
|
|
16
|
+
export * from './factories';
|
|
15
17
|
export * from './listener';
|
|
16
18
|
export * from './markdown';
|
|
17
19
|
export * from './mention';
|
|
18
20
|
export * from './modes';
|
|
19
|
-
export * from './
|
|
21
|
+
export * from './state';
|
|
22
|
+
export * from './types';
|
|
20
23
|
export * from './typewriter';
|
|
@@ -2,51 +2,34 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { closeBracketsKeymap } from '@codemirror/autocomplete';
|
|
6
|
+
import { historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
|
|
7
7
|
import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
|
|
8
|
-
import {
|
|
8
|
+
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
9
9
|
import { languages } from '@codemirror/language-data';
|
|
10
10
|
import { searchKeymap } from '@codemirror/search';
|
|
11
|
-
import {
|
|
11
|
+
import { type Extension } from '@codemirror/state';
|
|
12
12
|
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
13
|
-
import {
|
|
13
|
+
import { keymap } from '@codemirror/view';
|
|
14
14
|
|
|
15
15
|
import { type ThemeMode } from '@dxos/react-ui';
|
|
16
|
-
import { isNotFalsy } from '@dxos/util';
|
|
17
16
|
|
|
18
17
|
import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
|
|
19
|
-
import { type TextEditorProps } from '../../components';
|
|
20
18
|
|
|
21
19
|
export type MarkdownBundleOptions = {
|
|
22
20
|
themeMode?: ThemeMode;
|
|
23
|
-
}
|
|
21
|
+
};
|
|
24
22
|
|
|
25
23
|
/**
|
|
26
|
-
*
|
|
24
|
+
* Creates markdown extensions.
|
|
25
|
+
* To be used in conjunction with createBasicExtensions.
|
|
26
|
+
*
|
|
27
27
|
* Refs:
|
|
28
28
|
* https://codemirror.net/docs/community
|
|
29
29
|
* https://codemirror.net/docs/ref/#codemirror.basicSetup
|
|
30
30
|
*/
|
|
31
|
-
export const createMarkdownExtensions = ({
|
|
32
|
-
themeMode,
|
|
33
|
-
placeholder: _placeholder,
|
|
34
|
-
lineWrapping = true,
|
|
35
|
-
}: MarkdownBundleOptions = {}): Extension[] => {
|
|
31
|
+
export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions = {}): Extension[] => {
|
|
36
32
|
return [
|
|
37
|
-
// TODO(burdon): Reconcile with createBasicExtensions.
|
|
38
|
-
lineWrapping && EditorView.lineWrapping,
|
|
39
|
-
EditorState.allowMultipleSelections.of(true),
|
|
40
|
-
EditorState.tabSize.of(2),
|
|
41
|
-
|
|
42
|
-
bracketMatching(),
|
|
43
|
-
closeBrackets(),
|
|
44
|
-
dropCursor(),
|
|
45
|
-
drawSelection(),
|
|
46
|
-
history(),
|
|
47
|
-
indentOnInput(),
|
|
48
|
-
_placeholder && placeholder(_placeholder),
|
|
49
|
-
|
|
50
33
|
// Main extension.
|
|
51
34
|
// https://github.com/codemirror/lang-markdown
|
|
52
35
|
// https://codemirror.net/5/mode/markdown/index.html (demo).
|
|
@@ -86,5 +69,5 @@ export const createMarkdownExtensions = ({
|
|
|
86
69
|
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
87
70
|
...standardKeymap,
|
|
88
71
|
]),
|
|
89
|
-
]
|
|
72
|
+
];
|
|
90
73
|
};
|
|
@@ -48,7 +48,7 @@ class CheckboxWidget extends WidgetType {
|
|
|
48
48
|
|
|
49
49
|
override toDOM(view: EditorView) {
|
|
50
50
|
const input = document.createElement('input');
|
|
51
|
-
input.className = 'cm-task-checkbox';
|
|
51
|
+
input.className = 'cm-task-checkbox ch-checkbox ch-focus-ring -mbs-0.5';
|
|
52
52
|
input.type = 'checkbox';
|
|
53
53
|
input.checked = this._checked;
|
|
54
54
|
if (view.state.readOnly) {
|
|
@@ -148,9 +148,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
|
148
148
|
marks[1].from,
|
|
149
149
|
node.to,
|
|
150
150
|
options.renderLinkButton
|
|
151
|
-
? Decoration.replace({
|
|
152
|
-
widget: new LinkButton(url, options.renderLinkButton),
|
|
153
|
-
})
|
|
151
|
+
? Decoration.replace({ widget: new LinkButton(url, options.renderLinkButton) })
|
|
154
152
|
: hide,
|
|
155
153
|
);
|
|
156
154
|
}
|
|
@@ -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
|
});
|