@dxos/react-ui-editor 0.3.11-main.dafb7f2 → 0.3.11-main.df1e30a
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 +1192 -1300
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +16 -13
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
- package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.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 +7 -9
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/semaphore.d.ts +7 -12
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
- package/dist/types/src/extensions/awareness.d.ts +1 -1
- package/dist/types/src/extensions/awareness.d.ts.map +1 -1
- package/dist/types/src/extensions/basic.d.ts +4 -5
- package/dist/types/src/extensions/basic.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +32 -19
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
- package/dist/types/src/extensions/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +2 -8
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
- package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts +7 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
- package/dist/types/src/extensions/mention.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner.d.ts +4 -0
- package/dist/types/src/extensions/outliner.d.ts.map +1 -0
- package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
- package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
- package/dist/types/src/hooks/defs.d.ts +25 -0
- package/dist/types/src/hooks/defs.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +2 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts +17 -0
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextModel.d.ts +2 -32
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts +2 -2
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts +1 -0
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +7 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/package.json +34 -36
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +82 -83
- package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
- package/src/components/TextEditor/TextEditor.tsx +122 -93
- package/src/components/TextEditor/comments.stories.tsx +357 -0
- package/src/extensions/autocomplete.ts +23 -19
- package/src/extensions/automerge/automerge.ts +40 -46
- package/src/extensions/automerge/cursor.ts +2 -1
- package/src/extensions/automerge/defs.ts +10 -31
- package/src/extensions/automerge/semaphore.ts +23 -20
- package/src/extensions/automerge/update-automerge.ts +2 -2
- package/src/extensions/awareness.ts +40 -46
- package/src/extensions/basic.ts +22 -17
- package/src/extensions/comments.ts +249 -242
- package/src/extensions/cursor.ts +50 -0
- package/src/extensions/index.ts +2 -8
- package/src/extensions/markdown/bundle.ts +20 -30
- package/src/extensions/markdown/code.ts +127 -0
- package/src/extensions/markdown/heading.ts +59 -0
- package/src/extensions/markdown/highlight.ts +11 -26
- package/src/extensions/markdown/hr.ts +71 -0
- package/src/extensions/{image.ts → markdown/image.ts} +2 -2
- package/src/extensions/markdown/index.ts +7 -0
- package/src/extensions/{link.ts → markdown/link.ts} +9 -10
- package/src/extensions/{table.ts → markdown/table.ts} +6 -3
- package/src/extensions/markdown/tasklist.ts +110 -0
- package/src/extensions/mention.ts +9 -2
- package/src/extensions/outliner.ts +12 -0
- package/src/hooks/awareness-provider.ts +24 -20
- package/src/hooks/defs.ts +42 -0
- package/src/hooks/index.ts +3 -2
- package/src/hooks/useTextEditor.ts +29 -0
- package/src/hooks/useTextModel.ts +48 -60
- package/src/index.ts +3 -0
- package/src/styles/markdown.ts +9 -12
- package/src/styles/tokens.ts +3 -0
- package/src/themes/default.ts +15 -50
- package/src/util.ts +41 -0
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts +0 -12
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/code.d.ts.map +0 -1
- package/dist/types/src/extensions/hr.d.ts.map +0 -1
- package/dist/types/src/extensions/image.d.ts.map +0 -1
- package/dist/types/src/extensions/link.d.ts.map +0 -1
- package/dist/types/src/extensions/mermaid.d.ts +0 -3
- package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
- package/dist/types/src/extensions/table.d.ts.map +0 -1
- package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
- package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/index.d.ts +0 -2
- package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
- package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
- package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge.d.ts +0 -3
- package/dist/types/src/hooks/automerge.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs.d.ts +0 -27
- package/dist/types/src/hooks/yjs.d.ts.map +0 -1
- package/dist/types/src/testing/index.d.ts +0 -3
- package/dist/types/src/testing/index.d.ts.map +0 -1
- package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
- package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
- package/dist/types/src/testing/proto/index.d.ts +0 -2
- package/dist/types/src/testing/proto/index.d.ts.map +0 -1
- package/dist/types/src/testing/replicator.d.ts +0 -45
- package/dist/types/src/testing/replicator.d.ts.map +0 -1
- package/dist/types/src/util/cursor.d.ts.map +0 -1
- package/dist/types/src/util/index.d.ts +0 -3
- package/dist/types/src/util/index.d.ts.map +0 -1
- package/dist/types/src/util/util.d.ts +0 -5
- package/dist/types/src/util/util.d.ts.map +0 -1
- package/src/components/TextEditor/codemirror.stories.ts +0 -115
- package/src/components/TextEditor/yjs.stories.tsx +0 -56
- package/src/extensions/code.ts +0 -99
- package/src/extensions/hr.ts +0 -74
- package/src/extensions/mermaid.ts +0 -11
- package/src/extensions/tasklist.ts +0 -119
- package/src/extensions/yjs/cursor.ts +0 -21
- package/src/extensions/yjs/index.ts +0 -5
- package/src/extensions/yjs/yjs.test.ts +0 -35
- package/src/extensions/yjs/yjs.ts +0 -16
- package/src/hooks/automerge.ts +0 -52
- package/src/hooks/yjs.ts +0 -178
- package/src/testing/index.ts +0 -6
- package/src/testing/proto/gen/schema.ts +0 -49
- package/src/testing/proto/index.ts +0 -5
- package/src/testing/proto/schema.proto +0 -15
- package/src/testing/replicator.ts +0 -184
- package/src/util/cursor.ts +0 -29
- package/src/util/index.ts +0 -6
- package/src/util/util.ts +0 -18
- /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
- /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
- /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
- /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
- /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
- /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type EditorState, Facet } from '@codemirror/state';
|
|
6
|
+
|
|
7
|
+
import type { Range } from '../hooks';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Converts indexes into the text document into stable peer-independent cursors.
|
|
11
|
+
*
|
|
12
|
+
* See:
|
|
13
|
+
* - https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
|
|
14
|
+
* - https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
|
|
15
|
+
*
|
|
16
|
+
* @param {assoc} number Negative values will associate the cursor with the previous character
|
|
17
|
+
* while positive - with the next one.
|
|
18
|
+
*/
|
|
19
|
+
export interface CursorConverter {
|
|
20
|
+
toCursor(position: number, assoc?: -1 | 1 | undefined): string;
|
|
21
|
+
fromCursor(cursor: string): number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultCursorConverter: CursorConverter = {
|
|
25
|
+
toCursor: (position) => position.toString(),
|
|
26
|
+
fromCursor: (cursor) => parseInt(cursor),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class Cursor {
|
|
30
|
+
static readonly converter = Facet.define<CursorConverter, CursorConverter>({
|
|
31
|
+
combine: (providers) => providers[0] ?? defaultCursorConverter,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
static readonly getCursorFromRange = (state: EditorState, range: Range) => {
|
|
35
|
+
const cursorConverter = state.facet(Cursor.converter);
|
|
36
|
+
|
|
37
|
+
const from = cursorConverter.toCursor(range.from);
|
|
38
|
+
const to = cursorConverter.toCursor(range.to, -1);
|
|
39
|
+
return [from, to].join(':');
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
static readonly getRangeFromCursor = (state: EditorState, cursor: string) => {
|
|
43
|
+
const cursorConverter = state.facet(Cursor.converter);
|
|
44
|
+
|
|
45
|
+
const parts = cursor.split(':');
|
|
46
|
+
const from = cursorConverter.fromCursor(parts[0]);
|
|
47
|
+
const to = cursorConverter.fromCursor(parts[1]);
|
|
48
|
+
return from !== undefined && to !== undefined ? { from, to } : undefined;
|
|
49
|
+
};
|
|
50
|
+
}
|
package/src/extensions/index.ts
CHANGED
|
@@ -7,16 +7,10 @@ export * from './automerge';
|
|
|
7
7
|
export * from './awareness';
|
|
8
8
|
export * from './basic';
|
|
9
9
|
export * from './blast';
|
|
10
|
-
export * from './code';
|
|
11
10
|
export * from './comments';
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './image';
|
|
14
|
-
export * from './link';
|
|
11
|
+
export * from './cursor';
|
|
15
12
|
export * from './listener';
|
|
16
13
|
export * from './markdown';
|
|
17
14
|
export * from './mention';
|
|
18
|
-
export * from './
|
|
19
|
-
export * from './table';
|
|
20
|
-
export * from './tasklist';
|
|
15
|
+
export * from './outliner';
|
|
21
16
|
export * from './typewriter';
|
|
22
|
-
export * from './yjs';
|
|
@@ -23,34 +23,26 @@ import {
|
|
|
23
23
|
import type { ThemeMode } from '@dxos/react-ui';
|
|
24
24
|
|
|
25
25
|
import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
|
|
26
|
+
import { type TextEditorProps } from '../../components';
|
|
26
27
|
|
|
27
28
|
export type MarkdownBundleOptions = {
|
|
28
|
-
readonly?: boolean;
|
|
29
29
|
themeMode?: ThemeMode;
|
|
30
|
-
|
|
31
|
-
};
|
|
30
|
+
} & Pick<TextEditorProps, 'placeholder'>;
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Markdown bundle.
|
|
35
34
|
* Refs:
|
|
36
|
-
* https://github.com/codemirror/basic-setup
|
|
37
|
-
* https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts#L50
|
|
38
35
|
* https://codemirror.net/docs/community
|
|
39
36
|
* https://codemirror.net/docs/ref/#codemirror.basicSetup
|
|
40
37
|
*/
|
|
41
38
|
// TODO(burdon): Add Composer here: https://codemirror.net/docs/community
|
|
42
|
-
export const markdownBundle = ({
|
|
43
|
-
readonly,
|
|
44
|
-
themeMode,
|
|
45
|
-
placeholder: _placeholder,
|
|
46
|
-
}: MarkdownBundleOptions): Extension[] => {
|
|
39
|
+
export const markdownBundle = ({ themeMode, placeholder: _placeholder }: MarkdownBundleOptions): Extension[] => {
|
|
47
40
|
return [
|
|
48
41
|
EditorState.allowMultipleSelections.of(true),
|
|
49
42
|
EditorState.tabSize.of(2),
|
|
50
43
|
EditorView.lineWrapping,
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
// https://github.com/codemirror/basic-setup
|
|
54
46
|
bracketMatching(),
|
|
55
47
|
closeBrackets(),
|
|
56
48
|
crosshairCursor(),
|
|
@@ -59,7 +51,7 @@ export const markdownBundle = ({
|
|
|
59
51
|
highlightActiveLine(),
|
|
60
52
|
history(),
|
|
61
53
|
indentOnInput(),
|
|
62
|
-
_placeholder
|
|
54
|
+
_placeholder ? placeholder(_placeholder) : [],
|
|
63
55
|
|
|
64
56
|
// Main extension.
|
|
65
57
|
// https://github.com/codemirror/lang-markdown
|
|
@@ -86,21 +78,19 @@ export const markdownBundle = ({
|
|
|
86
78
|
themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
|
|
87
79
|
|
|
88
80
|
// Custom styles.
|
|
89
|
-
syntaxHighlighting(markdownHighlightStyle(
|
|
90
|
-
].filter(Boolean) as Extension[];
|
|
91
|
-
};
|
|
81
|
+
syntaxHighlighting(markdownHighlightStyle()),
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
83
|
+
keymap.of([
|
|
84
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
85
|
+
indentWithTab,
|
|
86
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
87
|
+
...closeBracketsKeymap,
|
|
88
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
89
|
+
...historyKeymap,
|
|
90
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
91
|
+
...searchKeymap,
|
|
92
|
+
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
93
|
+
...standardKeymap,
|
|
94
|
+
]),
|
|
95
|
+
];
|
|
96
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import { RangeSetBuilder } from '@codemirror/state';
|
|
7
|
+
import {
|
|
8
|
+
ViewPlugin,
|
|
9
|
+
type DecorationSet,
|
|
10
|
+
EditorView,
|
|
11
|
+
type ViewUpdate,
|
|
12
|
+
Decoration,
|
|
13
|
+
type BlockInfo,
|
|
14
|
+
WidgetType,
|
|
15
|
+
} from '@codemirror/view';
|
|
16
|
+
|
|
17
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
18
|
+
|
|
19
|
+
import { getToken } from '../../styles';
|
|
20
|
+
|
|
21
|
+
// TODO(burdon): Reconcile with theme.
|
|
22
|
+
// [aria-readonly="true"]
|
|
23
|
+
const styles = EditorView.baseTheme({
|
|
24
|
+
'& .cm-code': {
|
|
25
|
+
paddingInline: '1rem !important',
|
|
26
|
+
fontFamily: getToken('fontFamily.mono', []).join(','),
|
|
27
|
+
},
|
|
28
|
+
'& .cm-codeblock': {
|
|
29
|
+
display: 'inline-block',
|
|
30
|
+
width: '100%',
|
|
31
|
+
},
|
|
32
|
+
'&light .cm-codeblock, &light .cm-codeblock.cm-activeLine': {
|
|
33
|
+
background: getToken('extend.colors.neutral.25'),
|
|
34
|
+
},
|
|
35
|
+
'&dark .cm-codeblock, &dark .cm-codeblock.cm-activeLine': {
|
|
36
|
+
background: getToken('extend.colors.neutral.850'),
|
|
37
|
+
},
|
|
38
|
+
'& .cm-codeblock-first': {
|
|
39
|
+
borderTopLeftRadius: '.5rem',
|
|
40
|
+
borderTopRightRadius: '.5rem',
|
|
41
|
+
},
|
|
42
|
+
'& .cm-codeblock-last': {
|
|
43
|
+
borderBottomLeftRadius: '.5rem',
|
|
44
|
+
borderBottomRightRadius: '.5rem',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// TODO(burdon): Start from EditorView.lineBlockAt(pos).
|
|
49
|
+
const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
|
|
50
|
+
const start = lines.findIndex((line) => line.from >= from);
|
|
51
|
+
const end = lines.findIndex((line) => line.to >= to);
|
|
52
|
+
return [start, end];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
class LineWidget extends WidgetType {
|
|
56
|
+
constructor(private readonly _className: string) {
|
|
57
|
+
super();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override toDOM() {
|
|
61
|
+
const el = document.createElement('span');
|
|
62
|
+
el.innerHTML = ' ';
|
|
63
|
+
el.className = mx('cm-code cm-codeblock', this._className);
|
|
64
|
+
return el;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const top = new LineWidget('cm-codeblock-first');
|
|
69
|
+
const bottom = new LineWidget('cm-codeblock-last');
|
|
70
|
+
|
|
71
|
+
const buildDecorations = (view: EditorView): DecorationSet => {
|
|
72
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
73
|
+
const { state } = view;
|
|
74
|
+
const blocks = view.viewportLineBlocks;
|
|
75
|
+
const cursor = state.selection.main.head;
|
|
76
|
+
|
|
77
|
+
// TODO(burdon): Add copy to clipboard widget.
|
|
78
|
+
for (const { from, to } of view.visibleRanges) {
|
|
79
|
+
syntaxTree(state).iterate({
|
|
80
|
+
enter: (node) => {
|
|
81
|
+
if (node.name === 'FencedCode') {
|
|
82
|
+
const edit = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
|
|
83
|
+
const range = getLineRange(blocks, node.from, node.to);
|
|
84
|
+
for (let i = range[0]; i <= range[1]; i++) {
|
|
85
|
+
const block = blocks[i];
|
|
86
|
+
if (!edit && (i === range[0] || i === range[1])) {
|
|
87
|
+
builder.add(block.from, block.to, Decoration.replace({ widget: i === range[0] ? top : bottom }));
|
|
88
|
+
} else {
|
|
89
|
+
builder.add(
|
|
90
|
+
block.from,
|
|
91
|
+
block.from,
|
|
92
|
+
Decoration.line({
|
|
93
|
+
class: mx('cm-code cm-codeblock'),
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
from,
|
|
101
|
+
to,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return builder.finish();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const code = () => {
|
|
109
|
+
return [
|
|
110
|
+
ViewPlugin.fromClass(
|
|
111
|
+
class {
|
|
112
|
+
decorations: DecorationSet;
|
|
113
|
+
constructor(view: EditorView) {
|
|
114
|
+
this.decorations = buildDecorations(view);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
update(update: ViewUpdate) {
|
|
118
|
+
this.decorations = buildDecorations(update.view);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
decorations: (value) => value.decorations,
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
styles,
|
|
126
|
+
];
|
|
127
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import { RangeSetBuilder } from '@codemirror/state';
|
|
7
|
+
import { Decoration, type DecorationSet, type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
const buildDecorations = (view: EditorView): DecorationSet => {
|
|
10
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
11
|
+
const { state } = view;
|
|
12
|
+
const cursor = state.selection.main.head;
|
|
13
|
+
|
|
14
|
+
for (const { from, to } of view.visibleRanges) {
|
|
15
|
+
syntaxTree(state).iterate({
|
|
16
|
+
enter: (node) => {
|
|
17
|
+
switch (node.name) {
|
|
18
|
+
case 'ATXHeading1':
|
|
19
|
+
case 'ATXHeading2':
|
|
20
|
+
case 'ATXHeading3':
|
|
21
|
+
case 'ATXHeading4':
|
|
22
|
+
case 'ATXHeading5':
|
|
23
|
+
case 'ATXHeading6': {
|
|
24
|
+
const mark = node.node.getChild('HeaderMark');
|
|
25
|
+
if (mark) {
|
|
26
|
+
if (view.state.readOnly || cursor < node.from || cursor > node.to) {
|
|
27
|
+
builder.add(mark.from, mark.to + 1, Decoration.replace({}));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
from,
|
|
34
|
+
to,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return builder.finish();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const heading = () => {
|
|
42
|
+
return [
|
|
43
|
+
ViewPlugin.fromClass(
|
|
44
|
+
class {
|
|
45
|
+
decorations: DecorationSet;
|
|
46
|
+
constructor(view: EditorView) {
|
|
47
|
+
this.decorations = buildDecorations(view);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
update(update: ViewUpdate) {
|
|
51
|
+
this.decorations = buildDecorations(update.view);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
decorations: (value) => value.decorations,
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
+
];
|
|
59
|
+
};
|
|
@@ -6,19 +6,18 @@ import { markdownLanguage } from '@codemirror/lang-markdown';
|
|
|
6
6
|
import { HighlightStyle } from '@codemirror/language';
|
|
7
7
|
import { tags, styleTags, Tag } from '@lezer/highlight';
|
|
8
8
|
import { type MarkdownConfig, Table } from '@lezer/markdown';
|
|
9
|
-
import get from 'lodash.get';
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
11
|
blockquote,
|
|
13
12
|
bold,
|
|
14
13
|
code,
|
|
15
14
|
codeMark,
|
|
15
|
+
getToken,
|
|
16
16
|
heading,
|
|
17
17
|
inlineUrl,
|
|
18
18
|
italic,
|
|
19
19
|
mark,
|
|
20
20
|
strikethrough,
|
|
21
|
-
tokens,
|
|
22
21
|
} from '../../styles';
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -32,7 +31,6 @@ export const markdownTags = {
|
|
|
32
31
|
CodeText: Tag.define(),
|
|
33
32
|
EmphasisMark: Tag.define(),
|
|
34
33
|
HeaderMark: Tag.define(),
|
|
35
|
-
// HorizontalRule: Tag.define(),
|
|
36
34
|
InlineCode: Tag.define(),
|
|
37
35
|
LinkLabel: Tag.define(),
|
|
38
36
|
LinkReference: Tag.define(),
|
|
@@ -130,13 +128,13 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
|
|
|
130
128
|
// Markdown marks.
|
|
131
129
|
{
|
|
132
130
|
tag: [markdownTags.CodeMark, markdownTags.HeaderMark, markdownTags.QuoteMark, markdownTags.EmphasisMark],
|
|
133
|
-
class:
|
|
131
|
+
class: mark,
|
|
134
132
|
},
|
|
135
133
|
|
|
136
134
|
// E.g., code block language (after ```).
|
|
137
135
|
{
|
|
138
136
|
tag: [tags.function(tags.variableName), tags.labelName],
|
|
139
|
-
class:
|
|
137
|
+
class: codeMark,
|
|
140
138
|
},
|
|
141
139
|
|
|
142
140
|
{
|
|
@@ -145,12 +143,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
|
|
|
145
143
|
},
|
|
146
144
|
|
|
147
145
|
// Headings.
|
|
148
|
-
{ tag: tags.heading1, class: heading(1
|
|
149
|
-
{ tag: tags.heading2, class: heading(2
|
|
150
|
-
{ tag: tags.heading3, class: heading(3
|
|
151
|
-
{ tag: tags.heading4, class: heading(4
|
|
152
|
-
{ tag: tags.heading5, class: heading(5
|
|
153
|
-
{ tag: tags.heading6, class: heading(6
|
|
146
|
+
{ tag: tags.heading1, class: heading(1) },
|
|
147
|
+
{ tag: tags.heading2, class: heading(2) },
|
|
148
|
+
{ tag: tags.heading3, class: heading(3) },
|
|
149
|
+
{ tag: tags.heading4, class: heading(4) },
|
|
150
|
+
{ tag: tags.heading5, class: heading(5) },
|
|
151
|
+
{ tag: tags.heading6, class: heading(6) },
|
|
154
152
|
|
|
155
153
|
// Emphasis.
|
|
156
154
|
{ tag: tags.emphasis, class: italic },
|
|
@@ -167,7 +165,7 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
|
|
|
167
165
|
// However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
|
|
168
166
|
// when a language is specified. In this case, the syntax highlighting extensions will colorize
|
|
169
167
|
// the code, but all other CSS properties will be inherited.
|
|
170
|
-
// IMPORTANT: Therefore, the fenced code block will use the base editor font.
|
|
168
|
+
// IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
|
|
171
169
|
{
|
|
172
170
|
tag: [markdownTags.CodeText, markdownTags.InlineCode],
|
|
173
171
|
class: code,
|
|
@@ -178,27 +176,14 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
|
|
|
178
176
|
class: blockquote,
|
|
179
177
|
},
|
|
180
178
|
|
|
181
|
-
// TODO(burdon): Replace with extension.
|
|
182
|
-
// {
|
|
183
|
-
// tag: [markdownTags.HorizontalRule],
|
|
184
|
-
// class: mx(horizontalRule, readonly && '-indent-[100rem]'),
|
|
185
|
-
// },
|
|
186
|
-
|
|
187
|
-
// TODO(burdon): Only if being edited.
|
|
188
179
|
{
|
|
189
180
|
tag: [markdownTags.TableCell],
|
|
190
181
|
class: 'font-mono',
|
|
191
182
|
},
|
|
192
|
-
|
|
193
|
-
// Main content, paragraphs, etc.
|
|
194
|
-
// {
|
|
195
|
-
// tag: [tags.content],
|
|
196
|
-
// fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
197
|
-
// },
|
|
198
183
|
],
|
|
199
184
|
{
|
|
200
185
|
scope: markdownLanguage,
|
|
201
|
-
all: { fontFamily:
|
|
186
|
+
all: { fontFamily: getToken('fontFamily.body', []).join(',') },
|
|
202
187
|
},
|
|
203
188
|
);
|
|
204
189
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import { RangeSetBuilder } from '@codemirror/state';
|
|
7
|
+
import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
import { getToken } from '../../styles';
|
|
10
|
+
|
|
11
|
+
// TODO(burdon): Reconcile with theme.
|
|
12
|
+
const styles = EditorView.baseTheme({
|
|
13
|
+
'& .cm-hr': {
|
|
14
|
+
// Note that block-level decorations should not have vertical margins,
|
|
15
|
+
borderTop: `1px solid ${getToken('extend.colors.neutral.200')}`,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
class HorizontalRuleWidget extends WidgetType {
|
|
20
|
+
override toDOM() {
|
|
21
|
+
const el = document.createElement('div');
|
|
22
|
+
el.className = 'cm-hr';
|
|
23
|
+
return el;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const decoration = Decoration.replace({ widget: new HorizontalRuleWidget() });
|
|
28
|
+
|
|
29
|
+
const buildDecorations = (view: EditorView): DecorationSet => {
|
|
30
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
31
|
+
const { state } = view;
|
|
32
|
+
const cursor = state.selection.main.head;
|
|
33
|
+
|
|
34
|
+
for (const { from, to } of view.visibleRanges) {
|
|
35
|
+
syntaxTree(state).iterate({
|
|
36
|
+
enter: (node) => {
|
|
37
|
+
if (node.name === 'HorizontalRule') {
|
|
38
|
+
// Check if cursor is inside text.
|
|
39
|
+
if (cursor <= node.from || cursor >= node.to) {
|
|
40
|
+
builder.add(node.from, node.to, decoration);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
from,
|
|
45
|
+
to,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return builder.finish();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const hr = () => {
|
|
53
|
+
return [
|
|
54
|
+
ViewPlugin.fromClass(
|
|
55
|
+
class {
|
|
56
|
+
decorations: DecorationSet;
|
|
57
|
+
constructor(view: EditorView) {
|
|
58
|
+
this.decorations = buildDecorations(view);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
update(update: ViewUpdate) {
|
|
62
|
+
this.decorations = buildDecorations(update.view);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
decorations: (value) => value.decorations,
|
|
67
|
+
},
|
|
68
|
+
),
|
|
69
|
+
styles,
|
|
70
|
+
];
|
|
71
|
+
};
|
|
@@ -72,11 +72,11 @@ class ImageWidget extends WidgetType {
|
|
|
72
72
|
super();
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
override eq(other:
|
|
75
|
+
override eq(other: this) {
|
|
76
76
|
return this._url === (other as any as ImageWidget)._url;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
toDOM(view: EditorView) {
|
|
79
|
+
override toDOM(view: EditorView) {
|
|
80
80
|
const img = document.createElement('img');
|
|
81
81
|
img.setAttribute('src', this._url);
|
|
82
82
|
img.setAttribute('class', 'cm-image');
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
import { syntaxTree } from '@codemirror/language';
|
|
7
7
|
import { type Extension, RangeSetBuilder } from '@codemirror/state';
|
|
8
8
|
import {
|
|
9
|
+
hoverTooltip,
|
|
9
10
|
Decoration,
|
|
11
|
+
type DecorationSet,
|
|
10
12
|
type EditorView,
|
|
11
|
-
WidgetType,
|
|
12
|
-
hoverTooltip,
|
|
13
13
|
ViewPlugin,
|
|
14
14
|
type ViewUpdate,
|
|
15
|
-
|
|
15
|
+
WidgetType,
|
|
16
16
|
} from '@codemirror/view';
|
|
17
17
|
import { type SyntaxNode } from '@lezer/common';
|
|
18
18
|
|
|
@@ -37,7 +37,6 @@ export const link = (options: LinkOptions = {}): Extension => {
|
|
|
37
37
|
ViewPlugin.fromClass(
|
|
38
38
|
class {
|
|
39
39
|
decorations: DecorationSet;
|
|
40
|
-
|
|
41
40
|
constructor(view: EditorView) {
|
|
42
41
|
this.decorations = buildDecorations(view, options);
|
|
43
42
|
}
|
|
@@ -49,7 +48,7 @@ export const link = (options: LinkOptions = {}): Extension => {
|
|
|
49
48
|
}
|
|
50
49
|
},
|
|
51
50
|
{
|
|
52
|
-
decorations: (
|
|
51
|
+
decorations: (value) => value.decorations,
|
|
53
52
|
},
|
|
54
53
|
),
|
|
55
54
|
];
|
|
@@ -93,11 +92,11 @@ class LinkButton extends WidgetType {
|
|
|
93
92
|
super();
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
override eq(other:
|
|
95
|
+
override eq(other: this) {
|
|
97
96
|
return this._url === other._url;
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
toDOM(view: EditorView) {
|
|
99
|
+
override toDOM(view: EditorView) {
|
|
101
100
|
const el = document.createElement('span');
|
|
102
101
|
this._onAttach!(el, this._url);
|
|
103
102
|
return el;
|
|
@@ -109,11 +108,11 @@ class LinkText extends WidgetType {
|
|
|
109
108
|
super();
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
override eq(other:
|
|
111
|
+
override eq(other: this) {
|
|
113
112
|
return this._url === other._url;
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
toDOM(view: EditorView) {
|
|
115
|
+
override toDOM(view: EditorView) {
|
|
117
116
|
const link = document.createElement('a');
|
|
118
117
|
link.setAttribute('class', 'cm-link');
|
|
119
118
|
link.textContent = this._text;
|
|
@@ -158,7 +157,7 @@ const buildDecorations = (view: EditorView, options: LinkOptions): DecorationSet
|
|
|
158
157
|
return false;
|
|
159
158
|
}
|
|
160
159
|
|
|
161
|
-
if (state.readOnly || cursor < node.from || cursor > node.to) {
|
|
160
|
+
if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
|
|
162
161
|
builder.add(
|
|
163
162
|
node.from,
|
|
164
163
|
node.to,
|
|
@@ -81,7 +81,6 @@ const update = (state: EditorState, options: TableOptions) => {
|
|
|
81
81
|
table.from,
|
|
82
82
|
table.to,
|
|
83
83
|
Decoration.replace({
|
|
84
|
-
block: true,
|
|
85
84
|
widget: new TableWidget(table),
|
|
86
85
|
}),
|
|
87
86
|
);
|
|
@@ -98,14 +97,14 @@ class TableWidget extends WidgetType {
|
|
|
98
97
|
super();
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
override eq(other:
|
|
100
|
+
override eq(other: this) {
|
|
102
101
|
return (
|
|
103
102
|
this._table.header?.join() === other._table.header?.join() &&
|
|
104
103
|
this._table.rows?.join() === other._table.rows?.join()
|
|
105
104
|
);
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
toDOM(view: EditorView) {
|
|
107
|
+
override toDOM(view: EditorView) {
|
|
109
108
|
const table = document.createElement('table');
|
|
110
109
|
|
|
111
110
|
{
|
|
@@ -132,4 +131,8 @@ class TableWidget extends WidgetType {
|
|
|
132
131
|
|
|
133
132
|
return table;
|
|
134
133
|
}
|
|
134
|
+
|
|
135
|
+
override ignoreEvent(e: Event) {
|
|
136
|
+
return !/^mouse/.test(e.type);
|
|
137
|
+
}
|
|
135
138
|
}
|