@dxos/react-ui-editor 0.3.10-main.ee543cf → 0.3.10-next.6fe28e7
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 +735 -731
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -4
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +4 -9
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/{basic.d.ts → basic/bundle.d.ts} +1 -1
- package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +3 -0
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +10 -0
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/change.d.ts +7 -0
- package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +11 -0
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +8 -0
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/{experimental.d.ts → hyperlink/hyperlinkWidget.d.ts} +2 -2
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +4 -0
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +2 -5
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +17 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +21 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/index.d.ts +0 -2
- package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts +0 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useCollaboration.d.ts +9 -0
- package/dist/types/src/hooks/useCollaboration.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextModel.d.ts +4 -4
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts +10 -12
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/package.json +32 -20
- package/src/{hooks/automerge → automerge}/automerge.stories.tsx +1 -2
- package/src/components/TextEditor/TextEditor.stories.tsx +43 -46
- package/src/components/TextEditor/TextEditor.tsx +45 -59
- package/src/components/TextEditor/extensions/{basic.ts → basic/bundle.ts} +2 -4
- package/src/components/TextEditor/extensions/basic/index.ts +6 -0
- package/src/components/TextEditor/extensions/basic/theme.ts +49 -0
- package/src/components/TextEditor/extensions/{listener.ts → change.ts} +3 -3
- package/src/components/TextEditor/extensions/{link.ts → hyperlink/hyperlinkDecoration.ts} +31 -68
- package/src/components/TextEditor/extensions/{tooltip.tsx → hyperlink/hyperlinkTooltip.tsx} +9 -9
- package/src/components/TextEditor/extensions/{experimental.tsx → hyperlink/hyperlinkWidget.tsx} +2 -17
- package/src/components/TextEditor/extensions/hyperlink/index.ts +7 -0
- package/src/components/TextEditor/extensions/index.ts +2 -5
- package/src/components/TextEditor/extensions/markdown/bundle.ts +11 -15
- package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
- package/src/components/TextEditor/extensions/markdown/tags.ts +38 -0
- package/src/components/TextEditor/extensions/markdown/theme.ts +265 -0
- package/src/components/TextEditor/index.ts +0 -3
- package/src/components/TextEditor/yjs.stories.tsx +0 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useCollaboration.ts +45 -0
- package/src/hooks/useTextModel.ts +40 -50
- package/src/styles/markdown.ts +24 -26
- package/src/testing/replicator.ts +2 -2
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +0 -35
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +0 -9
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/link.d.ts +0 -11
- package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -7
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +0 -31
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +0 -5
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/themes/default.d.ts +0 -17
- package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/themes/index.d.ts +0 -2
- package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +0 -242
- package/src/components/TextEditor/extensions/autocomplete.ts +0 -72
- package/src/components/TextEditor/extensions/markdown/highlight.ts +0 -166
- package/src/components/TextEditor/extensions/tasklist.ts +0 -113
- package/src/components/TextEditor/themes/default.ts +0 -170
- package/src/components/TextEditor/themes/index.ts +0 -5
- /package/dist/types/src/{hooks/automerge → automerge/automerge-plugin}/PatchSemaphore.d.ts +0 -0
- /package/dist/types/src/{hooks/automerge → automerge/automerge-plugin}/amToCodemirror.d.ts +0 -0
- /package/dist/types/src/{hooks/automerge → automerge/automerge-plugin}/codeMirrorToAm.d.ts +0 -0
- /package/dist/types/src/{hooks/automerge → automerge/automerge-plugin}/handle.d.ts +0 -0
- /package/dist/types/src/{hooks/automerge → automerge/automerge-plugin}/index.d.ts +0 -0
- /package/dist/types/src/{hooks/automerge → automerge/automerge-plugin}/plugin.d.ts +0 -0
- /package/dist/types/src/{hooks/automerge → automerge}/automerge.stories.d.ts +0 -0
- /package/src/{hooks/automerge → automerge/automerge-plugin}/PatchSemaphore.ts +0 -0
- /package/src/{hooks/automerge → automerge/automerge-plugin}/amToCodemirror.ts +0 -0
- /package/src/{hooks/automerge → automerge/automerge-plugin}/codeMirrorToAm.ts +0 -0
- /package/src/{hooks/automerge → automerge/automerge-plugin}/handle.ts +0 -0
- /package/src/{hooks/automerge → automerge/automerge-plugin}/index.ts +0 -0
- /package/src/{hooks/automerge → automerge/automerge-plugin}/plugin.ts +0 -0
|
@@ -2,24 +2,21 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Extension } from '@codemirror/state';
|
|
6
5
|
import { hoverTooltip } from '@codemirror/view';
|
|
7
6
|
|
|
8
7
|
import { tooltipContent } from '@dxos/react-ui-theme';
|
|
9
8
|
|
|
10
9
|
const markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
|
|
11
10
|
|
|
12
|
-
export type
|
|
13
|
-
onHover: (el: Element, url: string) => void;
|
|
14
|
-
regexp?: RegExp;
|
|
15
|
-
};
|
|
11
|
+
export type OnHyperlinkHover = (el: Element, url: string) => void;
|
|
16
12
|
|
|
17
13
|
/**
|
|
18
14
|
* https://codemirror.net/examples/tooltip
|
|
19
15
|
* https://codemirror.net/docs/ref/#view.hoverTooltip
|
|
20
16
|
* https://github.com/codemirror/view/blob/main/src/tooltip.ts
|
|
21
17
|
*/
|
|
22
|
-
|
|
18
|
+
// TODO(burdon): Create config object.
|
|
19
|
+
export const createHyperlinkTooltip = (onHover: OnHyperlinkHover, regexp = markdownLinkRegexp) =>
|
|
23
20
|
hoverTooltip((view, pos) => {
|
|
24
21
|
const { from, text } = view.state.doc.lineAt(pos);
|
|
25
22
|
const p = pos - from;
|
|
@@ -52,10 +49,13 @@ export const tooltip = ({ onHover, regexp = markdownLinkRegexp }: TooltipOptions
|
|
|
52
49
|
end: idx + from + match[0].length,
|
|
53
50
|
above: true,
|
|
54
51
|
create: () => {
|
|
55
|
-
const el = document.createElement('
|
|
56
|
-
el.
|
|
52
|
+
const el = document.createElement('a');
|
|
53
|
+
el.innerText = '_'; // Required so doesn't collapse.
|
|
54
|
+
el.className = tooltipContent({}, 'mb-2 p-1');
|
|
55
|
+
el.setAttribute('target', '_blank');
|
|
56
|
+
el.setAttribute('href', url);
|
|
57
57
|
onHover(el, url);
|
|
58
|
-
return { dom: el
|
|
58
|
+
return { dom: el };
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
});
|
package/src/components/TextEditor/extensions/{experimental.tsx → hyperlink/hyperlinkWidget.tsx}
RENAMED
|
@@ -13,27 +13,12 @@ import {
|
|
|
13
13
|
type ViewUpdate,
|
|
14
14
|
} from '@codemirror/view';
|
|
15
15
|
|
|
16
|
-
// TODO(burdon): Modes: parallel parsing and decoration (e.g., associated with language).
|
|
17
|
-
// TODO(burdon): Add-on: runmode: run lexer over content (with rendering codemirror).
|
|
18
|
-
// https://codemirror.net/5/doc/manual.html#addon_runmode
|
|
19
|
-
// TODO(burdon): Add-on: dialog.
|
|
20
|
-
// TODO(burdon): Comments: https://codemirror.net/5/doc/manual.html#setBookmark
|
|
21
|
-
// TODO(burdon): Split view: https://codemirror.net/examples/split
|
|
22
|
-
|
|
23
|
-
// https://codemirror.net/examples/autocompletion
|
|
24
|
-
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
25
|
-
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
|
26
|
-
|
|
27
|
-
// TODO(burdon): Hint to customize?
|
|
28
|
-
// https://codemirror.net/examples/autocompletion
|
|
29
|
-
|
|
30
16
|
// Notes: Can't edit widget content (or cursor nav through them).
|
|
31
17
|
// - https://discuss.codemirror.net/t/focusing-inputs-within-widgets/5178/7
|
|
32
18
|
// Widgets intentionally always get set to contenteditable=false,
|
|
33
19
|
// or they would become part of CodeMirror’s editable content element.
|
|
34
20
|
// You should be able to introduce new contenteditable=true child elements inside of them.
|
|
35
21
|
|
|
36
|
-
// TODO(burdon): URL match?
|
|
37
22
|
const markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
|
|
38
23
|
|
|
39
24
|
/**
|
|
@@ -117,7 +102,7 @@ export function hyperLinkExtension() {
|
|
|
117
102
|
);
|
|
118
103
|
}
|
|
119
104
|
|
|
120
|
-
export const
|
|
105
|
+
export const hyperlinkStyle = EditorView.baseTheme({
|
|
121
106
|
'.cm-hyperlink-label': {
|
|
122
107
|
cursor: 'pointer',
|
|
123
108
|
textDecoration: 'underline',
|
|
@@ -131,4 +116,4 @@ export const styles = EditorView.baseTheme({
|
|
|
131
116
|
},
|
|
132
117
|
});
|
|
133
118
|
|
|
134
|
-
export const hyperlinkWidget: Extension = [hyperLinkExtension(),
|
|
119
|
+
export const hyperlinkWidget: Extension = [hyperLinkExtension(), hyperlinkStyle];
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export * from './autocomplete';
|
|
6
5
|
export * from './basic';
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
6
|
+
export * from './change';
|
|
7
|
+
export * from './hyperlink';
|
|
9
8
|
export * from './markdown';
|
|
10
|
-
export * from './tasklist';
|
|
11
|
-
export * from './tooltip';
|
|
@@ -29,10 +29,12 @@ import {
|
|
|
29
29
|
placeholder,
|
|
30
30
|
rectangularSelection,
|
|
31
31
|
} from '@codemirror/view';
|
|
32
|
+
// import { GFM } from '@lezer/markdown';
|
|
32
33
|
|
|
33
34
|
import type { ThemeMode } from '@dxos/react-ui';
|
|
34
35
|
|
|
35
|
-
import {
|
|
36
|
+
import { markdownTagsExtension } from './tags';
|
|
37
|
+
import { markdownHighlightStyle } from './theme';
|
|
36
38
|
|
|
37
39
|
export type MarkdownBundleOptions = {
|
|
38
40
|
themeMode?: ThemeMode;
|
|
@@ -75,29 +77,23 @@ export const markdownBundle = ({ themeMode, placeholder: _placeholder }: Markdow
|
|
|
75
77
|
|
|
76
78
|
// Main extension.
|
|
77
79
|
// https://github.com/codemirror/lang-markdown
|
|
78
|
-
// https://codemirror.net/5/mode/markdown/index.html (demo).
|
|
79
80
|
markdown({
|
|
80
|
-
// GRM by default (vs strict CommonMark):
|
|
81
|
-
// Table, TaskList, Strikethrough, and Autolink.
|
|
82
|
-
// NOTE: This extends the parser; it doesn't affect rendering.
|
|
83
|
-
// https://github.github.com/gfm
|
|
84
|
-
// https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
|
|
85
81
|
base: markdownLanguage,
|
|
86
|
-
|
|
87
|
-
// Languages for syntax highlighting fenced code blocks.
|
|
88
82
|
codeLanguages: languages,
|
|
89
|
-
|
|
90
|
-
// Parser extensions.
|
|
91
83
|
extensions: [
|
|
92
|
-
//
|
|
84
|
+
// TODO(burdon): This seems to upgrade the parser.
|
|
85
|
+
// GitHub flavored markdown bundle: Table, TaskList, Strikethrough, and Autolink.
|
|
86
|
+
// https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
|
|
87
|
+
// https://github.github.com/gfm
|
|
88
|
+
// GFM,
|
|
89
|
+
|
|
90
|
+
// Custom styling.
|
|
93
91
|
markdownTagsExtension,
|
|
94
92
|
],
|
|
95
93
|
}),
|
|
96
94
|
|
|
97
|
-
// Custom styles.
|
|
98
|
-
syntaxHighlighting(markdownHighlightStyle),
|
|
99
|
-
|
|
100
95
|
// TODO(thure): All but one rule here apply to both themes; rename or refactor.
|
|
96
|
+
syntaxHighlighting(markdownHighlightStyle),
|
|
101
97
|
themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
|
|
102
98
|
].filter(Boolean) as Extension[];
|
|
103
99
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { styleTags, Tag } from '@lezer/highlight';
|
|
6
|
+
import { type MarkdownConfig } from '@lezer/markdown';
|
|
7
|
+
|
|
8
|
+
export const markdownTags = {
|
|
9
|
+
headingMark: Tag.define(),
|
|
10
|
+
quoteMark: Tag.define(),
|
|
11
|
+
listMark: Tag.define(),
|
|
12
|
+
linkMark: Tag.define(),
|
|
13
|
+
emphasisMark: Tag.define(),
|
|
14
|
+
codeMark: Tag.define(),
|
|
15
|
+
codeText: Tag.define(),
|
|
16
|
+
inlineCode: Tag.define(),
|
|
17
|
+
url: Tag.define(),
|
|
18
|
+
linkReference: Tag.define(),
|
|
19
|
+
linkLabel: Tag.define(),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const markdownTagsExtension: MarkdownConfig = {
|
|
23
|
+
props: [
|
|
24
|
+
styleTags({
|
|
25
|
+
HeaderMark: markdownTags.headingMark,
|
|
26
|
+
QuoteMark: markdownTags.quoteMark,
|
|
27
|
+
ListMark: markdownTags.listMark,
|
|
28
|
+
LinkMark: markdownTags.linkMark,
|
|
29
|
+
EmphasisMark: markdownTags.emphasisMark,
|
|
30
|
+
CodeMark: markdownTags.codeMark,
|
|
31
|
+
CodeText: markdownTags.codeText,
|
|
32
|
+
InlineCode: markdownTags.inlineCode,
|
|
33
|
+
URL: markdownTags.url,
|
|
34
|
+
LinkReference: markdownTags.linkReference,
|
|
35
|
+
LinkLabel: markdownTags.linkLabel,
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { markdownLanguage } from '@codemirror/lang-markdown';
|
|
6
|
+
import { HighlightStyle } from '@codemirror/language';
|
|
7
|
+
import { tags } from '@lezer/highlight';
|
|
8
|
+
import get from 'lodash.get';
|
|
9
|
+
|
|
10
|
+
import { markdownTags } from './tags';
|
|
11
|
+
import { bold, heading, italic, mark, strikethrough, tokens, type ThemeStyles } from '../../../../styles';
|
|
12
|
+
|
|
13
|
+
// TODO(burdon): Rationalize theme/colors. Hoist onto single property object.
|
|
14
|
+
export const chalky = '#e5c07b';
|
|
15
|
+
export const coral = '#e06c75';
|
|
16
|
+
export const cyan = '#56b6c2';
|
|
17
|
+
export const invalid = '#ffffff';
|
|
18
|
+
export const ivory = '#abb2bf';
|
|
19
|
+
export const stone = '#7d8799';
|
|
20
|
+
export const malibu = '#61afef';
|
|
21
|
+
export const sage = '#98c379';
|
|
22
|
+
export const whiskey = '#d19a66';
|
|
23
|
+
export const violet = '#c678dd';
|
|
24
|
+
const _darkBackground = '#21252b';
|
|
25
|
+
export const highlightBackground = '#2c313a';
|
|
26
|
+
const _background = '#282c34';
|
|
27
|
+
export const tooltipBackground = '#353a42';
|
|
28
|
+
const _selection = '#3E4451';
|
|
29
|
+
|
|
30
|
+
const monospace = get(tokens, 'fontFamily.mono', ['monospace']).join(',');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* NOTE: The '&' prefix denotes the CM editor root.
|
|
34
|
+
* https://codemirror.net/examples/styling
|
|
35
|
+
*/
|
|
36
|
+
export const markdownTheme: ThemeStyles = {
|
|
37
|
+
// TODO(thure): Consider whether these commented-out rules from one-dark-theme should be integrated.
|
|
38
|
+
// '&': {
|
|
39
|
+
// color: ivory,
|
|
40
|
+
// backgroundColor: background
|
|
41
|
+
// },
|
|
42
|
+
// '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
|
43
|
+
// '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
|
|
44
|
+
// backgroundColor: selection
|
|
45
|
+
// },
|
|
46
|
+
// '.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
|
47
|
+
// '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
|
48
|
+
// '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
|
49
|
+
// '.cm-searchMatch': {
|
|
50
|
+
// backgroundColor: '#72a1ff59',
|
|
51
|
+
// outline: '1px solid #457dff'
|
|
52
|
+
// },
|
|
53
|
+
// '.cm-searchMatch.cm-searchMatch-selected': {
|
|
54
|
+
// backgroundColor: '#6199ff2f'
|
|
55
|
+
// },
|
|
56
|
+
// '.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
|
57
|
+
// '.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
|
58
|
+
// '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
|
59
|
+
// backgroundColor: '#bad0f847'
|
|
60
|
+
// },
|
|
61
|
+
// '.cm-gutters': {
|
|
62
|
+
// backgroundColor: background,
|
|
63
|
+
// color: stone,
|
|
64
|
+
// border: 'none'
|
|
65
|
+
// },
|
|
66
|
+
// '.cm-activeLineGutter': {
|
|
67
|
+
// backgroundColor: highlightBackground
|
|
68
|
+
// },
|
|
69
|
+
// '.cm-foldPlaceholder': {
|
|
70
|
+
// backgroundColor: 'transparent',
|
|
71
|
+
// border: 'none',
|
|
72
|
+
// color: '#ddd'
|
|
73
|
+
// },
|
|
74
|
+
|
|
75
|
+
'& .cm-tooltip': {
|
|
76
|
+
backgroundColor: 'transparent',
|
|
77
|
+
border: 'none',
|
|
78
|
+
},
|
|
79
|
+
// '.dark & .cm-tooltip': {
|
|
80
|
+
// border: 'none',
|
|
81
|
+
// backgroundColor: tooltipBackground,
|
|
82
|
+
// },
|
|
83
|
+
// '.dark & .cm-tooltip .cm-tooltip-arrow:before': {
|
|
84
|
+
// borderTopColor: 'transparent',
|
|
85
|
+
// borderBottomColor: 'transparent',
|
|
86
|
+
// },
|
|
87
|
+
// '.dark & .cm-tooltip .cm-tooltip-arrow:after': {
|
|
88
|
+
// borderTopColor: tooltipBackground,
|
|
89
|
+
// borderBottomColor: tooltipBackground,
|
|
90
|
+
// },
|
|
91
|
+
// '.dark & .cm-tooltip-autocomplete': {
|
|
92
|
+
// '& > ul > li[aria-selected]': {
|
|
93
|
+
// backgroundColor: highlightBackground,
|
|
94
|
+
// color: ivory,
|
|
95
|
+
// },
|
|
96
|
+
// },
|
|
97
|
+
'&.cm-focused': {
|
|
98
|
+
outline: 'none',
|
|
99
|
+
},
|
|
100
|
+
'& .cm-line': {
|
|
101
|
+
paddingInline: 0,
|
|
102
|
+
lineHeight: 1.6,
|
|
103
|
+
minBlockSize: '1.6em',
|
|
104
|
+
},
|
|
105
|
+
'& .cm-line *': {
|
|
106
|
+
lineHeight: 1.6,
|
|
107
|
+
},
|
|
108
|
+
'&.cm-focused .cm-selectionBackground, & .cm-selectionBackground': {
|
|
109
|
+
background: get(tokens, 'extend.colors.primary.150', '#00ffff'),
|
|
110
|
+
},
|
|
111
|
+
'.dark & .cm-selectionBackground, .dark &.cm-focused .cm-selectionBackground': {
|
|
112
|
+
background: get(tokens, 'extend.colors.primary.850', '#00ffff'),
|
|
113
|
+
},
|
|
114
|
+
'& .cm-selectionMatch': {
|
|
115
|
+
background: get(tokens, 'extend.colors.primary.250', '#00ffff') + '44',
|
|
116
|
+
},
|
|
117
|
+
'.dark & .cm-selectionMatch': {
|
|
118
|
+
background: get(tokens, 'extend.colors.primary.600', '#00ffff') + '44',
|
|
119
|
+
},
|
|
120
|
+
'& .cm-content': {
|
|
121
|
+
padding: 0,
|
|
122
|
+
caretColor: 'black',
|
|
123
|
+
},
|
|
124
|
+
'.dark & .cm-content': {
|
|
125
|
+
caretColor: 'white',
|
|
126
|
+
},
|
|
127
|
+
'& .cm-cursor': {
|
|
128
|
+
borderLeftColor: 'black',
|
|
129
|
+
},
|
|
130
|
+
'.dark & .cm-cursor': {
|
|
131
|
+
borderLeftColor: 'white',
|
|
132
|
+
},
|
|
133
|
+
'.cm-placeholder': {
|
|
134
|
+
fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
135
|
+
},
|
|
136
|
+
'& .cm-scroller': {
|
|
137
|
+
fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
|
|
138
|
+
overflow: 'visible',
|
|
139
|
+
},
|
|
140
|
+
'& .cm-activeLine': {
|
|
141
|
+
backgroundColor: 'transparent',
|
|
142
|
+
},
|
|
143
|
+
'.dark & .cm-activeLine': {
|
|
144
|
+
backgroundColor: 'transparent',
|
|
145
|
+
},
|
|
146
|
+
'& .cm-ySelectionInfo': {
|
|
147
|
+
fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
148
|
+
padding: '2px 4px',
|
|
149
|
+
marginBlockStart: '-4px',
|
|
150
|
+
},
|
|
151
|
+
'& .cm-ySelection, & .cm-selectionMatch': {
|
|
152
|
+
paddingBlockStart: '.15em',
|
|
153
|
+
paddingBlockEnd: '.15em',
|
|
154
|
+
},
|
|
155
|
+
'& .cm-ySelection, & .cm-yLineSelection': {
|
|
156
|
+
mixBlendMode: 'multiply',
|
|
157
|
+
},
|
|
158
|
+
'.dark & .cm-ySelection, .dark & .cm-yLineSelection': {
|
|
159
|
+
mixBlendMode: 'screen',
|
|
160
|
+
},
|
|
161
|
+
'& .cm-ySelectionCaret': {
|
|
162
|
+
display: 'inline-block',
|
|
163
|
+
insetBlockStart: '.1em',
|
|
164
|
+
blockSize: '1.4em',
|
|
165
|
+
verticalAlign: 'top',
|
|
166
|
+
},
|
|
167
|
+
'& .cm-yLineSelection': {
|
|
168
|
+
margin: '0',
|
|
169
|
+
},
|
|
170
|
+
'.cm-link': {
|
|
171
|
+
color: 'rgb(20 89 208)', // TODO(burdon): Rationalize.
|
|
172
|
+
textDecorationLine: 'underline',
|
|
173
|
+
textDecorationThickness: '1px',
|
|
174
|
+
textUnderlineOffset: '2px',
|
|
175
|
+
borderRadius: '.125rem',
|
|
176
|
+
},
|
|
177
|
+
// TODO(burdon): Document.
|
|
178
|
+
...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
|
|
179
|
+
const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
|
|
180
|
+
// TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
|
|
181
|
+
acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
|
|
182
|
+
acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
|
|
183
|
+
acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
|
|
184
|
+
return acc;
|
|
185
|
+
}, {}),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const markdownHighlightStyle = HighlightStyle.define(
|
|
189
|
+
[
|
|
190
|
+
{
|
|
191
|
+
tag: [
|
|
192
|
+
tags.keyword,
|
|
193
|
+
tags.name,
|
|
194
|
+
tags.deleted,
|
|
195
|
+
tags.character,
|
|
196
|
+
tags.propertyName,
|
|
197
|
+
tags.macroName,
|
|
198
|
+
tags.color,
|
|
199
|
+
tags.constant(tags.name),
|
|
200
|
+
tags.standard(tags.name),
|
|
201
|
+
tags.definition(tags.name),
|
|
202
|
+
tags.separator,
|
|
203
|
+
tags.typeName,
|
|
204
|
+
tags.className,
|
|
205
|
+
tags.number,
|
|
206
|
+
tags.changed,
|
|
207
|
+
tags.annotation,
|
|
208
|
+
tags.modifier,
|
|
209
|
+
tags.self,
|
|
210
|
+
tags.namespace,
|
|
211
|
+
tags.operator,
|
|
212
|
+
tags.operatorKeyword,
|
|
213
|
+
tags.escape,
|
|
214
|
+
tags.regexp,
|
|
215
|
+
tags.special(tags.string),
|
|
216
|
+
tags.meta,
|
|
217
|
+
tags.comment,
|
|
218
|
+
tags.atom,
|
|
219
|
+
tags.bool,
|
|
220
|
+
tags.special(tags.variableName),
|
|
221
|
+
tags.processingInstruction,
|
|
222
|
+
tags.string,
|
|
223
|
+
tags.inserted,
|
|
224
|
+
tags.invalid,
|
|
225
|
+
],
|
|
226
|
+
color: 'inherit !important',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
tag: [tags.link, tags.url],
|
|
230
|
+
color: 'inherit !important',
|
|
231
|
+
textDecoration: 'none !important',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
tag: [tags.function(tags.variableName), tags.labelName],
|
|
235
|
+
color: malibu,
|
|
236
|
+
fontFamily: monospace,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
tag: [
|
|
240
|
+
markdownTags.codeMark,
|
|
241
|
+
markdownTags.emphasisMark,
|
|
242
|
+
markdownTags.headingMark,
|
|
243
|
+
markdownTags.linkLabel,
|
|
244
|
+
markdownTags.linkReference,
|
|
245
|
+
markdownTags.listMark,
|
|
246
|
+
markdownTags.quoteMark,
|
|
247
|
+
markdownTags.url,
|
|
248
|
+
tags.meta,
|
|
249
|
+
tags.processingInstruction,
|
|
250
|
+
],
|
|
251
|
+
class: mark,
|
|
252
|
+
},
|
|
253
|
+
{ tag: [markdownTags.codeText, markdownTags.inlineCode], class: 'font-mono' },
|
|
254
|
+
{ tag: tags.emphasis, class: italic },
|
|
255
|
+
{ tag: tags.heading1, class: heading[1] },
|
|
256
|
+
{ tag: tags.heading2, class: heading[2] },
|
|
257
|
+
{ tag: tags.heading3, class: heading[3] },
|
|
258
|
+
{ tag: tags.heading4, class: heading[4] },
|
|
259
|
+
{ tag: tags.heading5, class: heading[5] },
|
|
260
|
+
{ tag: tags.heading6, class: heading[6] },
|
|
261
|
+
{ tag: tags.strikethrough, class: strikethrough },
|
|
262
|
+
{ tag: tags.strong, class: bold },
|
|
263
|
+
],
|
|
264
|
+
{ scope: markdownLanguage, all: { fontFamily: get(tokens, 'fontFamily.body', []).join(',') } },
|
|
265
|
+
);
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { Extension } from '@codemirror/state';
|
|
6
|
+
import { useEffect, useMemo } from 'react';
|
|
7
|
+
import { yCollab } from 'y-codemirror.next';
|
|
8
|
+
|
|
9
|
+
import { generateName } from '@dxos/display-name';
|
|
10
|
+
import { isDocAccessor } from '@dxos/echo-schema';
|
|
11
|
+
import type { ThemeMode } from '@dxos/react-ui';
|
|
12
|
+
import { getColorForValue } from '@dxos/react-ui-theme';
|
|
13
|
+
import { YText } from '@dxos/text-model';
|
|
14
|
+
|
|
15
|
+
import type { EditorModel } from './useTextModel';
|
|
16
|
+
import { automergePlugin } from '../automerge/automerge-plugin';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Replication and awareness (incl. remote selection).
|
|
20
|
+
* https://codemirror.net/docs/ref/#collab
|
|
21
|
+
*/
|
|
22
|
+
export const useCollaboration = (model: EditorModel, themeMode: ThemeMode): Extension | undefined => {
|
|
23
|
+
const { provider, peer, content } = model;
|
|
24
|
+
const extension = useMemo(() => {
|
|
25
|
+
if (content instanceof YText) {
|
|
26
|
+
return yCollab(content, provider?.awareness);
|
|
27
|
+
} else if (isDocAccessor(content)) {
|
|
28
|
+
return automergePlugin(content.handle, content.path);
|
|
29
|
+
} else {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (provider && peer) {
|
|
36
|
+
provider.awareness.setLocalStateField('user', {
|
|
37
|
+
name: peer.name ?? generateName(peer.id),
|
|
38
|
+
color: getColorForValue({ value: peer.id, type: 'color' }),
|
|
39
|
+
colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}, [provider, peer, themeMode]);
|
|
43
|
+
|
|
44
|
+
return extension;
|
|
45
|
+
};
|
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import get from 'lodash.get';
|
|
7
|
-
import { useEffect, useState } from 'react';
|
|
8
|
-
import { yCollab } from 'y-codemirror.next';
|
|
5
|
+
import { useMemo } from 'react';
|
|
9
6
|
import type * as awarenessProtocol from 'y-protocols/awareness';
|
|
10
7
|
|
|
11
|
-
import { invariant } from '@dxos/invariant';
|
|
12
8
|
import {
|
|
13
9
|
type DocAccessor,
|
|
14
10
|
type Space,
|
|
@@ -20,83 +16,77 @@ import {
|
|
|
20
16
|
import { type Identity } from '@dxos/react-client/halo';
|
|
21
17
|
import type { YText, YXmlFragment } from '@dxos/text-model';
|
|
22
18
|
|
|
23
|
-
import { automergePlugin } from './automerge';
|
|
24
19
|
import { SpaceAwarenessProvider } from './yjs';
|
|
25
20
|
|
|
26
21
|
// TODO(burdon): Move.
|
|
27
22
|
type Awareness = awarenessProtocol.Awareness;
|
|
23
|
+
type Provider = { awareness: Awareness };
|
|
28
24
|
|
|
29
25
|
// TODO(wittjosiah): Factor out to common package? @dxos/react-client?
|
|
30
26
|
export type EditorModel = {
|
|
31
27
|
id: string;
|
|
32
28
|
content: string | YText | YXmlFragment | DocAccessor;
|
|
33
|
-
|
|
34
|
-
extension?: Extension;
|
|
35
|
-
awareness?: Awareness;
|
|
29
|
+
provider?: Provider;
|
|
36
30
|
peer?: {
|
|
37
31
|
id: string;
|
|
38
32
|
name?: string;
|
|
39
33
|
};
|
|
40
34
|
};
|
|
41
35
|
|
|
42
|
-
// TODO(burdon): Remove space/identity dependency. Define interface for the framework re content and presence.
|
|
43
36
|
export type UseTextModelOptions = {
|
|
44
37
|
identity?: Identity | null;
|
|
45
38
|
space?: Space;
|
|
46
39
|
text?: TextObject;
|
|
47
40
|
};
|
|
48
41
|
|
|
49
|
-
// TODO(burdon):
|
|
42
|
+
// TODO(burdon): Don't support returning undefined (and remove checks from calling code).
|
|
43
|
+
// TODO(burdon): Decouple space (make Editor less dependent on entire stack)?
|
|
50
44
|
// TODO(wittjosiah): Factor out to common package? @dxos/react-client?
|
|
51
45
|
export const useTextModel = ({ identity, space, text }: UseTextModelOptions): EditorModel | undefined => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return model;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const createModel = (options: UseTextModelOptions) => {
|
|
60
|
-
const { space, text } = options;
|
|
46
|
+
const provider = useMemo(() => {
|
|
47
|
+
if (isActualAutomergeObject(text)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
61
50
|
|
|
62
|
-
|
|
63
|
-
return createAutomergeModel(options);
|
|
64
|
-
} else {
|
|
65
|
-
if (!space || !text?.doc || !text?.content) {
|
|
51
|
+
if (!space || !text?.doc) {
|
|
66
52
|
return undefined;
|
|
67
53
|
}
|
|
68
|
-
|
|
54
|
+
|
|
55
|
+
return new SpaceAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` });
|
|
56
|
+
}, [identity, space, text, text?.doc]);
|
|
57
|
+
|
|
58
|
+
const content = useMemo(() => {
|
|
59
|
+
if (isActualAutomergeObject(text)) {
|
|
60
|
+
const obj = text as any as AutomergeTextCompat;
|
|
61
|
+
return getRawDoc(obj, [obj.field]);
|
|
62
|
+
} else {
|
|
63
|
+
return text?.content;
|
|
64
|
+
}
|
|
65
|
+
}, [text]);
|
|
66
|
+
|
|
67
|
+
if (isActualAutomergeObject(text)) {
|
|
68
|
+
const obj = text as any as AutomergeTextCompat;
|
|
69
|
+
return {
|
|
70
|
+
id: obj.id,
|
|
71
|
+
content: content!,
|
|
72
|
+
provider: undefined,
|
|
73
|
+
peer: identity
|
|
74
|
+
? {
|
|
75
|
+
id: identity.identityKey.toHex(),
|
|
76
|
+
name: identity.profile?.displayName,
|
|
77
|
+
}
|
|
78
|
+
: undefined,
|
|
79
|
+
};
|
|
69
80
|
}
|
|
70
|
-
};
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
if (!text?.doc || !text?.content) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
75
85
|
|
|
76
86
|
return {
|
|
77
87
|
id: text.doc.guid,
|
|
78
88
|
content: text.content,
|
|
79
|
-
|
|
80
|
-
extension: yCollab(text.content as YText, provider.awareness),
|
|
81
|
-
awareness: provider.awareness,
|
|
82
|
-
peer: identity
|
|
83
|
-
? {
|
|
84
|
-
id: identity.identityKey.toHex(),
|
|
85
|
-
name: identity.profile?.displayName,
|
|
86
|
-
}
|
|
87
|
-
: undefined,
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const createAutomergeModel = ({ identity, space, text }: UseTextModelOptions): EditorModel => {
|
|
92
|
-
const obj = text as any as AutomergeTextCompat;
|
|
93
|
-
const doc = getRawDoc(obj, [obj.field]);
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
id: obj.id,
|
|
97
|
-
content: doc,
|
|
98
|
-
text: () => get(doc.handle.docSync(), doc.path),
|
|
99
|
-
extension: automergePlugin(doc.handle, doc.path),
|
|
89
|
+
provider,
|
|
100
90
|
peer: identity
|
|
101
91
|
? {
|
|
102
92
|
id: identity.identityKey.toHex(),
|