@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.008b7c6
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 +2814 -1096
- 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 +63 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +9 -4
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +15 -4
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts +20 -0
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/index.d.ts +1 -1
- package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -0
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +10 -0
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/index.d.ts +3 -0
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
- package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → extensions/automerge/semaphore.d.ts} +8 -5
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness.d.ts +46 -0
- package/dist/types/src/extensions/awareness.d.ts.map +1 -0
- package/dist/types/src/{components/TextEditor/extensions/basic/bundle.d.ts → extensions/basic.d.ts} +3 -2
- package/dist/types/src/extensions/basic.d.ts.map +1 -0
- package/dist/types/src/extensions/blast.d.ts +25 -0
- package/dist/types/src/extensions/blast.d.ts.map +1 -0
- package/dist/types/src/extensions/code.d.ts +2 -0
- package/dist/types/src/extensions/code.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +50 -0
- package/dist/types/src/extensions/comments.d.ts.map +1 -0
- package/dist/types/src/extensions/hr.d.ts +2 -0
- package/dist/types/src/extensions/hr.d.ts.map +1 -0
- package/dist/types/src/extensions/image.d.ts +4 -0
- package/dist/types/src/extensions/image.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +19 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/extensions/link.d.ts +12 -0
- package/dist/types/src/extensions/link.d.ts.map +1 -0
- package/dist/types/src/extensions/listener.d.ts +10 -0
- package/dist/types/src/extensions/listener.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts +17 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts +36 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts +3 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
- package/dist/types/src/extensions/mention.d.ts +6 -0
- package/dist/types/src/extensions/mention.d.ts.map +1 -0
- package/dist/types/src/extensions/mermaid.d.ts +3 -0
- package/dist/types/src/extensions/mermaid.d.ts.map +1 -0
- package/dist/types/src/extensions/table.d.ts +8 -0
- package/dist/types/src/extensions/table.d.ts.map +1 -0
- package/dist/types/src/extensions/tasklist.d.ts +3 -0
- package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
- package/dist/types/src/extensions/typewriter.d.ts +10 -0
- package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
- package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/index.d.ts +2 -0
- package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
- package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/yjs.test.d.ts +2 -0
- package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge.d.ts +3 -0
- package/dist/types/src/hooks/automerge.d.ts.map +1 -0
- package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
- package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +23 -8
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
- package/dist/types/src/hooks/yjs.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/cursors.d.ts.map +1 -0
- package/dist/types/src/styles/index.d.ts +1 -0
- package/dist/types/src/styles/index.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts +14 -11
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/testing/replicator.d.ts +2 -2
- package/dist/types/src/testing/replicator.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts +17 -0
- package/dist/types/src/themes/default.d.ts.map +1 -0
- package/dist/types/src/themes/index.d.ts +2 -0
- package/dist/types/src/themes/index.d.ts.map +1 -0
- package/dist/types/src/util/cursor.d.ts +17 -0
- package/dist/types/src/util/cursor.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +3 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/src/util/util.d.ts +5 -0
- package/dist/types/src/util/util.d.ts.map +1 -0
- package/package.json +29 -33
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +426 -0
- package/src/components/TextEditor/TextEditor.stories.tsx +70 -43
- package/src/components/TextEditor/TextEditor.tsx +98 -52
- package/src/{automerge → components/TextEditor}/automerge.stories.tsx +69 -7
- package/src/components/TextEditor/codemirror.stories.ts +3 -2
- package/src/components/TextEditor/index.ts +1 -1
- package/src/components/TextEditor/yjs.stories.tsx +1 -0
- package/src/extensions/autocomplete.ts +59 -0
- package/src/extensions/automerge/automerge.ts +94 -0
- package/src/extensions/automerge/cursor.ts +51 -0
- package/src/extensions/automerge/defs.ts +62 -0
- package/src/extensions/automerge/index.ts +6 -0
- package/src/{automerge/automerge-plugin/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +24 -18
- package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → extensions/automerge/update-automerge.ts} +8 -6
- package/src/{automerge/automerge-plugin/amToCodemirror.ts → extensions/automerge/update-codemirror.ts} +15 -4
- package/src/extensions/awareness.ts +316 -0
- package/src/{components/TextEditor/extensions/basic/bundle.ts → extensions/basic.ts} +8 -4
- package/src/extensions/blast.ts +373 -0
- package/src/extensions/code.ts +99 -0
- package/src/extensions/comments.ts +421 -0
- package/src/extensions/hr.ts +74 -0
- package/src/extensions/image.ts +69 -0
- package/src/extensions/index.ts +22 -0
- package/src/extensions/link.ts +192 -0
- package/src/extensions/listener.ts +35 -0
- package/src/extensions/markdown/bundle.ts +106 -0
- package/src/extensions/markdown/highlight.ts +204 -0
- package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +1 -1
- package/src/extensions/mention.ts +31 -0
- package/src/extensions/mermaid.ts +11 -0
- package/src/extensions/table.ts +132 -0
- package/src/extensions/tasklist.ts +121 -0
- package/src/extensions/typewriter.ts +68 -0
- package/src/extensions/yjs/cursor.ts +21 -0
- package/src/extensions/yjs/index.ts +5 -0
- package/src/extensions/yjs/yjs.test.ts +35 -0
- package/src/extensions/yjs/yjs.ts +16 -0
- package/src/hooks/automerge.ts +52 -0
- package/src/hooks/awareness-provider.ts +120 -0
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useTextModel.ts +53 -67
- package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
- package/src/index.ts +3 -0
- package/src/styles/index.ts +1 -0
- package/src/styles/markdown.ts +33 -24
- package/src/testing/replicator.ts +16 -21
- package/src/themes/default.ts +302 -0
- package/src/themes/index.ts +5 -0
- package/src/util/cursor.ts +29 -0
- package/src/util/index.ts +6 -0
- package/src/util/util.ts +18 -0
- package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +0 -7
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts +0 -9
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/index.d.ts +0 -3
- package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts +0 -26
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
- package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
- package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -5
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -8
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
- package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
- package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/index.d.ts +0 -3
- package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
- package/src/automerge/automerge-plugin/handle.ts +0 -14
- package/src/automerge/automerge-plugin/index.ts +0 -6
- package/src/automerge/automerge-plugin/plugin.ts +0 -133
- package/src/components/TextEditor/extensions/basic/index.ts +0 -6
- package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
- package/src/components/TextEditor/extensions/change.ts +0 -22
- package/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts +0 -99
- package/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx +0 -61
- package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
- package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
- package/src/components/TextEditor/extensions/index.ts +0 -8
- package/src/components/TextEditor/extensions/markdown/bundle.ts +0 -99
- package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
- package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
- package/src/hooks/useCollaboration.ts +0 -45
- package/src/hooks/yjs/index.ts +0 -6
- /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
- /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
|
@@ -1,973 +1,2531 @@
|
|
|
1
1
|
// packages/ui/react-ui-editor/src/index.ts
|
|
2
2
|
import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
|
|
3
|
-
import { Doc, YText
|
|
3
|
+
import { Doc, YText, YXmlFragment } from "@dxos/text-model";
|
|
4
4
|
|
|
5
5
|
// packages/ui/react-ui-editor/src/components/TextEditor/index.ts
|
|
6
6
|
import { tags as tags2 } from "@lezer/highlight";
|
|
7
7
|
|
|
8
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// TODO(burdon): Is this required?
|
|
19
|
-
themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
|
|
20
|
-
].filter(Boolean);
|
|
21
|
-
|
|
22
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
|
|
23
|
-
import get from "lodash.get";
|
|
8
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
|
|
9
|
+
import { EditorState as EditorState2 } from "@codemirror/state";
|
|
10
|
+
import { EditorView as EditorView13 } from "@codemirror/view";
|
|
11
|
+
import { useFocusableGroup } from "@fluentui/react-tabster";
|
|
12
|
+
import { vim } from "@replit/codemirror-vim";
|
|
13
|
+
import defaultsDeep2 from "lodash.defaultsdeep";
|
|
14
|
+
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
|
|
15
|
+
import { generateName } from "@dxos/display-name";
|
|
16
|
+
import { useThemeContext } from "@dxos/react-ui";
|
|
17
|
+
import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
|
|
24
18
|
|
|
25
|
-
// packages/ui/react-ui-editor/src/
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
// packages/ui/react-ui-editor/src/extensions/autocomplete.ts
|
|
20
|
+
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
|
|
21
|
+
import { keymap } from "@codemirror/view";
|
|
22
|
+
var autocomplete = ({ onSearch }) => {
|
|
23
|
+
return [
|
|
24
|
+
// https://codemirror.net/docs/ref/#view.keymap
|
|
25
|
+
// https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
|
|
26
|
+
keymap.of(completionKeymap),
|
|
27
|
+
// https://codemirror.net/examples/autocompletion
|
|
28
|
+
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
29
|
+
autocompletion({
|
|
30
|
+
// TODO(burdon): Set custom keymap.
|
|
31
|
+
// defaultKeymap: false,
|
|
32
|
+
// Don't start unless key press.
|
|
33
|
+
activateOnTyping: false,
|
|
34
|
+
// TODO(burdon): Option to create new page?
|
|
35
|
+
// TODO(burdon): Optional decoration via addToOptions
|
|
36
|
+
override: [
|
|
37
|
+
(context) => {
|
|
38
|
+
const match = context.matchBefore(/\w*/);
|
|
39
|
+
if (!match || match.from === match.to && !context.explicit) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
from: match.from,
|
|
44
|
+
options: onSearch(match.text.toLowerCase())
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
];
|
|
34
50
|
};
|
|
35
|
-
var bold = "font-bold";
|
|
36
|
-
var code = "font-mono bg-neutral-500/10 rounded pli-0.5 plb-0.5 -mlb-0.5";
|
|
37
|
-
var codeWithoutMarks = mx(code, "pli-1.5 mli-0.5");
|
|
38
|
-
var italic = "italic";
|
|
39
|
-
var strikethrough = "line-through";
|
|
40
|
-
var mark = "!font-normal !no-underline !text-inherit opacity-40";
|
|
41
51
|
|
|
42
|
-
// packages/ui/react-ui-editor/src/
|
|
43
|
-
import {
|
|
44
|
-
|
|
52
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
|
|
53
|
+
import { Facet as Facet2, StateField } from "@codemirror/state";
|
|
54
|
+
import { ViewPlugin } from "@codemirror/view";
|
|
55
|
+
import { next as A } from "@dxos/automerge/automerge";
|
|
45
56
|
|
|
46
|
-
// packages/ui/react-ui-editor/src/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
overflow: "visible"
|
|
62
|
-
},
|
|
63
|
-
"& .cm-content": {
|
|
64
|
-
padding: 0,
|
|
65
|
-
caretColor: "black"
|
|
66
|
-
},
|
|
67
|
-
".dark & .cm-content": {
|
|
68
|
-
caretColor: "white"
|
|
69
|
-
},
|
|
70
|
-
"& .cm-tooltip": {
|
|
71
|
-
backgroundColor: "transparent",
|
|
72
|
-
border: "none"
|
|
57
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
|
|
58
|
+
import get from "lodash.get";
|
|
59
|
+
import { next as automerge } from "@dxos/automerge/automerge";
|
|
60
|
+
var cursorConverter = (handle, path) => ({
|
|
61
|
+
// TODO(burdon): Handle assoc to associate with a previous character.
|
|
62
|
+
toCursor: (pos) => {
|
|
63
|
+
const doc = handle.docSync();
|
|
64
|
+
if (!doc) {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
const value = get(doc, path);
|
|
68
|
+
if (typeof value === "string" && value.length <= pos) {
|
|
69
|
+
return "end";
|
|
70
|
+
}
|
|
71
|
+
return automerge.getCursor(doc, path.slice(), pos);
|
|
73
72
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/change.ts
|
|
81
|
-
import { StateField } from "@codemirror/state";
|
|
82
|
-
var onChangeExtension = (onChange) => StateField.define({
|
|
83
|
-
create: () => null,
|
|
84
|
-
update: (_value, transaction) => {
|
|
85
|
-
if (transaction.docChanged && onChange) {
|
|
86
|
-
onChange(transaction.newDoc.toString());
|
|
73
|
+
fromCursor: (cursor) => {
|
|
74
|
+
if (cursor === "") {
|
|
75
|
+
return 0;
|
|
87
76
|
}
|
|
88
|
-
|
|
77
|
+
const doc = handle.docSync();
|
|
78
|
+
if (!doc) {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
if (cursor === "end") {
|
|
82
|
+
const value = get(doc, path);
|
|
83
|
+
if (typeof value === "string") {
|
|
84
|
+
return value.length;
|
|
85
|
+
} else {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return automerge.getCursorPosition(doc, path.slice(), cursor);
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
|
|
92
|
-
// packages/ui/react-ui-editor/src/
|
|
93
|
-
import {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
|
|
94
|
+
import { Annotation, StateEffect } from "@codemirror/state";
|
|
95
|
+
var effectType = StateEffect.define({});
|
|
96
|
+
var updateHeads = (newHeads) => effectType.of({
|
|
97
|
+
newHeads
|
|
98
|
+
});
|
|
99
|
+
var getLastHeads = (state, field) => state.field(field).lastHeads;
|
|
100
|
+
var getPath = (state, field) => state.field(field).path;
|
|
101
|
+
var reconcileAnnotationType = Annotation.define();
|
|
102
|
+
var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
|
|
103
|
+
|
|
104
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
|
|
105
|
+
import { next as automerge2 } from "@dxos/automerge/automerge";
|
|
106
|
+
|
|
107
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
|
|
108
|
+
import { next as am } from "@dxos/automerge/automerge";
|
|
109
|
+
var updateAutomerge = (field, handle, transactions, state) => {
|
|
110
|
+
const { lastHeads, path } = state.field(field);
|
|
111
|
+
let hasChanges = false;
|
|
112
|
+
for (const tr of transactions) {
|
|
113
|
+
tr.changes.iterChanges(() => {
|
|
114
|
+
hasChanges = true;
|
|
115
|
+
});
|
|
102
116
|
}
|
|
103
|
-
|
|
104
|
-
return
|
|
117
|
+
if (!hasChanges) {
|
|
118
|
+
return void 0;
|
|
105
119
|
}
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
link.setAttribute("target", "_blank");
|
|
112
|
-
link.href = this._url;
|
|
113
|
-
} else {
|
|
114
|
-
link.onclick = () => {
|
|
115
|
-
view.dispatch({
|
|
116
|
-
selection: {
|
|
117
|
-
anchor: this._anchor
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
};
|
|
120
|
+
const newHeads = handle.changeAt(lastHeads, (doc) => {
|
|
121
|
+
for (const tr of transactions) {
|
|
122
|
+
tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
|
|
123
|
+
am.splice(doc, path, fromA, toA - fromA, inserted.toString());
|
|
124
|
+
});
|
|
121
125
|
}
|
|
122
|
-
return link;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
var hyperlinkDecoration = ({ link } = {}) => {
|
|
126
|
-
return StateField2.define({
|
|
127
|
-
create: (state) => update(state, link),
|
|
128
|
-
update: (_, tr) => update(tr.state, link),
|
|
129
|
-
provide: (field) => EditorView2.decorations.from(field)
|
|
130
126
|
});
|
|
127
|
+
return newHeads ?? void 0;
|
|
131
128
|
};
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}));
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return true;
|
|
129
|
+
|
|
130
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
|
|
131
|
+
import { ChangeSet } from "@codemirror/state";
|
|
132
|
+
var updateCodeMirror = (view, selection, target, patches) => {
|
|
133
|
+
for (const patch of patches) {
|
|
134
|
+
const changeSpec = handlePatch(patch, target, view.state);
|
|
135
|
+
if (changeSpec != null) {
|
|
136
|
+
const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
|
|
137
|
+
selection = selection.map(changeSet, 1);
|
|
138
|
+
view.dispatch({
|
|
139
|
+
changes: changeSet,
|
|
140
|
+
annotations: reconcileAnnotationType.of({})
|
|
141
|
+
});
|
|
150
142
|
}
|
|
143
|
+
}
|
|
144
|
+
view.dispatch({
|
|
145
|
+
selection,
|
|
146
|
+
annotations: reconcileAnnotationType.of({})
|
|
151
147
|
});
|
|
152
|
-
return builder.finish();
|
|
153
148
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
149
|
+
var handlePatch = (patch, target, state) => {
|
|
150
|
+
if (patch.action === "insert") {
|
|
151
|
+
return handleInsert(target, patch);
|
|
152
|
+
} else if (patch.action === "splice") {
|
|
153
|
+
return handleSplice(target, patch);
|
|
154
|
+
} else if (patch.action === "del") {
|
|
155
|
+
return handleDel(target, patch);
|
|
156
|
+
} else if (patch.action === "put") {
|
|
157
|
+
return handlePut(target, patch, state);
|
|
158
|
+
} else {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var handleInsert = (target, patch) => {
|
|
163
|
+
const index = charPath(target, patch.path);
|
|
164
|
+
if (index == null) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
const text = patch.values.map((v) => v ? v.toString() : "").join("");
|
|
168
|
+
return [
|
|
169
|
+
{
|
|
170
|
+
from: index,
|
|
171
|
+
to: index,
|
|
172
|
+
insert: text
|
|
169
173
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
];
|
|
175
|
+
};
|
|
176
|
+
var handleSplice = (target, patch) => {
|
|
177
|
+
const index = charPath(target, patch.path);
|
|
178
|
+
if (index == null) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return [
|
|
182
|
+
{
|
|
183
|
+
from: index,
|
|
184
|
+
insert: patch.value
|
|
185
|
+
}
|
|
186
|
+
];
|
|
187
|
+
};
|
|
188
|
+
var handleDel = (target, patch) => {
|
|
189
|
+
const index = charPath(target, patch.path);
|
|
190
|
+
if (index == null) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const length = patch.length || 1;
|
|
194
|
+
return [
|
|
195
|
+
{
|
|
196
|
+
from: index,
|
|
197
|
+
to: index + length
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
};
|
|
201
|
+
var handlePut = (target, patch, state) => {
|
|
202
|
+
const index = charPath(target, [
|
|
203
|
+
...patch.path,
|
|
204
|
+
0
|
|
205
|
+
]);
|
|
206
|
+
if (index == null) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
const length = state.doc.length;
|
|
210
|
+
if (typeof patch.value !== "string") {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
return [
|
|
214
|
+
{
|
|
215
|
+
from: 0,
|
|
216
|
+
to: length,
|
|
217
|
+
insert: patch.value
|
|
173
218
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
219
|
+
];
|
|
220
|
+
};
|
|
221
|
+
var charPath = (textPath, candidatePath) => {
|
|
222
|
+
if (candidatePath.length !== textPath.length + 1) {
|
|
177
223
|
return null;
|
|
178
224
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
end: idx + from + match[0].length,
|
|
183
|
-
above: true,
|
|
184
|
-
create: () => {
|
|
185
|
-
const el = document.createElement("a");
|
|
186
|
-
el.innerText = "_";
|
|
187
|
-
el.className = tooltipContent({}, "mb-2 p-1");
|
|
188
|
-
el.setAttribute("target", "_blank");
|
|
189
|
-
el.setAttribute("href", url);
|
|
190
|
-
onHover(el, url);
|
|
191
|
-
return {
|
|
192
|
-
dom: el
|
|
193
|
-
};
|
|
225
|
+
for (let i = 0; i < textPath.length; i++) {
|
|
226
|
+
if (textPath[i] !== candidatePath[i]) {
|
|
227
|
+
return null;
|
|
194
228
|
}
|
|
195
|
-
}
|
|
196
|
-
|
|
229
|
+
}
|
|
230
|
+
const index = candidatePath[candidatePath.length - 1];
|
|
231
|
+
if (typeof index === "number") {
|
|
232
|
+
return index;
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
};
|
|
197
236
|
|
|
198
|
-
// packages/ui/react-ui-editor/src/
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
end: from + 1
|
|
208
|
-
};
|
|
209
|
-
const p1 = {
|
|
210
|
-
start: from + 1,
|
|
211
|
-
end: from + 1 + label.length
|
|
212
|
-
};
|
|
213
|
-
const p2 = {
|
|
214
|
-
start: p1.end,
|
|
215
|
-
end: p1.end + 1
|
|
216
|
-
};
|
|
217
|
-
const p3 = {
|
|
218
|
-
start: p1.end + 1,
|
|
219
|
-
end: p1.end + 1 + url.length + 2
|
|
220
|
-
};
|
|
221
|
-
add(p0.start, p0.end, Decoration2.mark({
|
|
222
|
-
class: "cm-hyperlink-bracket",
|
|
223
|
-
_attributes: {
|
|
224
|
-
style: "display: none"
|
|
237
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
|
|
238
|
+
var PatchSemaphore = class {
|
|
239
|
+
constructor(_field) {
|
|
240
|
+
this._field = _field;
|
|
241
|
+
this._inReconcile = false;
|
|
242
|
+
this._queue = [];
|
|
243
|
+
this.reconcile = (handle, view) => {
|
|
244
|
+
if (this._inReconcile) {
|
|
245
|
+
return;
|
|
225
246
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
247
|
+
this._inReconcile = true;
|
|
248
|
+
const path = getPath(view.state, this._field);
|
|
249
|
+
const oldHeads = getLastHeads(view.state, this._field);
|
|
250
|
+
let selection = view.state.selection;
|
|
251
|
+
const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
|
|
252
|
+
const toInvert = transactions.slice().reverse();
|
|
253
|
+
for (const tx of toInvert) {
|
|
254
|
+
const inverted = tx.changes.invert(tx.startState.doc);
|
|
255
|
+
selection = selection.map(inverted);
|
|
256
|
+
view.dispatch({
|
|
257
|
+
changes: inverted,
|
|
258
|
+
annotations: reconcileAnnotationType.of(true)
|
|
259
|
+
});
|
|
234
260
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
_attributes: {
|
|
239
|
-
style: "display: none"
|
|
261
|
+
let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
|
|
262
|
+
if (newHeads === null || newHeads === void 0) {
|
|
263
|
+
newHeads = automerge2.getHeads(handle.docSync());
|
|
240
264
|
}
|
|
241
|
-
|
|
265
|
+
const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
|
|
266
|
+
updateCodeMirror(view, selection, path, diff);
|
|
267
|
+
view.dispatch({
|
|
268
|
+
effects: updateHeads(newHeads),
|
|
269
|
+
annotations: reconcileAnnotationType.of({})
|
|
270
|
+
});
|
|
271
|
+
this._inReconcile = false;
|
|
272
|
+
};
|
|
242
273
|
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// packages/ui/react-ui-editor/src/util/cursor.ts
|
|
277
|
+
import { Facet } from "@codemirror/state";
|
|
278
|
+
var DEFAULT_CURSOR_CONVERTER = {
|
|
279
|
+
toCursor: (position) => position.toString(),
|
|
280
|
+
fromCursor: (cursor) => parseInt(cursor)
|
|
281
|
+
};
|
|
282
|
+
var CursorConverter = Facet.define({
|
|
283
|
+
combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER
|
|
243
284
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
285
|
+
|
|
286
|
+
// packages/ui/react-ui-editor/src/util/util.ts
|
|
287
|
+
import { log } from "@dxos/log";
|
|
288
|
+
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/util.ts";
|
|
289
|
+
var callbackWrapper = (fn) => (...args) => {
|
|
290
|
+
try {
|
|
291
|
+
return fn(...args);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
log.catch(error, void 0, {
|
|
294
|
+
F: __dxlog_file,
|
|
295
|
+
L: 16,
|
|
296
|
+
S: void 0,
|
|
297
|
+
C: (f, a) => f(...a)
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
|
|
303
|
+
var semaphoreFacet = Facet2.define({
|
|
304
|
+
combine: (values) => values.at(-1)
|
|
305
|
+
});
|
|
306
|
+
var automerge3 = ({ handle, path }) => {
|
|
307
|
+
const stateField = StateField.define({
|
|
308
|
+
create: () => ({
|
|
309
|
+
lastHeads: A.getHeads(handle.docSync()),
|
|
310
|
+
unreconciledTransactions: [],
|
|
311
|
+
path: path.slice()
|
|
312
|
+
}),
|
|
313
|
+
update: (value, tr) => {
|
|
314
|
+
const result = {
|
|
315
|
+
lastHeads: value.lastHeads,
|
|
316
|
+
unreconciledTransactions: value.unreconciledTransactions.slice(),
|
|
317
|
+
path: path.slice()
|
|
318
|
+
};
|
|
319
|
+
let clearUnreconciled = false;
|
|
320
|
+
for (const effect of tr.effects) {
|
|
321
|
+
if (effect.is(effectType)) {
|
|
322
|
+
result.lastHeads = effect.value.newHeads;
|
|
323
|
+
clearUnreconciled = true;
|
|
324
|
+
}
|
|
253
325
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// https://discuss.codemirror.net/t/decorator-iteration-on-click-event/4226/2
|
|
260
|
-
mousedown: function(event, view) {
|
|
261
|
-
const pos = view.posAtDOM(event.target);
|
|
262
|
-
if (pos !== 0) {
|
|
263
|
-
console.log(this);
|
|
326
|
+
if (clearUnreconciled) {
|
|
327
|
+
result.unreconciledTransactions = [];
|
|
328
|
+
} else {
|
|
329
|
+
if (!isReconcileTx(tr)) {
|
|
330
|
+
result.unreconciledTransactions.push(tr);
|
|
264
331
|
}
|
|
265
|
-
},
|
|
266
|
-
// TODO(burdon): Hide when cursor moves away.
|
|
267
|
-
keydown: (event, view) => {
|
|
268
|
-
view.update([]);
|
|
269
332
|
}
|
|
333
|
+
return result;
|
|
270
334
|
}
|
|
271
335
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
|
|
301
|
-
import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView4, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
|
|
302
|
-
|
|
303
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/tags.ts
|
|
304
|
-
import { styleTags, Tag } from "@lezer/highlight";
|
|
305
|
-
var markdownTags = {
|
|
306
|
-
headingMark: Tag.define(),
|
|
307
|
-
quoteMark: Tag.define(),
|
|
308
|
-
listMark: Tag.define(),
|
|
309
|
-
linkMark: Tag.define(),
|
|
310
|
-
emphasisMark: Tag.define(),
|
|
311
|
-
codeMark: Tag.define(),
|
|
312
|
-
codeText: Tag.define(),
|
|
313
|
-
inlineCode: Tag.define(),
|
|
314
|
-
url: Tag.define(),
|
|
315
|
-
linkReference: Tag.define(),
|
|
316
|
-
linkLabel: Tag.define()
|
|
317
|
-
};
|
|
318
|
-
var markdownTagsExtension = {
|
|
319
|
-
props: [
|
|
320
|
-
styleTags({
|
|
321
|
-
HeaderMark: markdownTags.headingMark,
|
|
322
|
-
QuoteMark: markdownTags.quoteMark,
|
|
323
|
-
ListMark: markdownTags.listMark,
|
|
324
|
-
LinkMark: markdownTags.linkMark,
|
|
325
|
-
EmphasisMark: markdownTags.emphasisMark,
|
|
326
|
-
CodeMark: markdownTags.codeMark,
|
|
327
|
-
CodeText: markdownTags.codeText,
|
|
328
|
-
InlineCode: markdownTags.inlineCode,
|
|
329
|
-
URL: markdownTags.url,
|
|
330
|
-
LinkReference: markdownTags.linkReference,
|
|
331
|
-
LinkLabel: markdownTags.linkLabel
|
|
332
|
-
})
|
|
333
|
-
]
|
|
336
|
+
const semaphore = new PatchSemaphore(stateField);
|
|
337
|
+
const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
|
|
338
|
+
constructor(_view) {
|
|
339
|
+
this._view = _view;
|
|
340
|
+
this._handleChange = () => {
|
|
341
|
+
this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
|
|
342
|
+
};
|
|
343
|
+
handle.addListener("change", this._handleChange);
|
|
344
|
+
}
|
|
345
|
+
update(update4) {
|
|
346
|
+
if (update4.transactions.length > 0 && update4.transactions.some((tr) => !isReconcileTx(tr))) {
|
|
347
|
+
queueMicrotask(() => {
|
|
348
|
+
this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
destroy() {
|
|
353
|
+
handle.addListener("change", this._handleChange);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
return {
|
|
357
|
+
extension: [
|
|
358
|
+
CursorConverter.of(cursorConverter(handle, path)),
|
|
359
|
+
semaphoreFacet.of(semaphore),
|
|
360
|
+
stateField,
|
|
361
|
+
viewPlugin
|
|
362
|
+
]
|
|
363
|
+
};
|
|
334
364
|
};
|
|
335
365
|
|
|
336
|
-
// packages/ui/react-ui-editor/src/
|
|
337
|
-
import {
|
|
338
|
-
import {
|
|
339
|
-
import {
|
|
340
|
-
import
|
|
341
|
-
var
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
var invalid = "#ffffff";
|
|
345
|
-
var ivory = "#abb2bf";
|
|
346
|
-
var stone = "#7d8799";
|
|
347
|
-
var malibu = "#61afef";
|
|
348
|
-
var sage = "#98c379";
|
|
349
|
-
var whiskey = "#d19a66";
|
|
350
|
-
var violet = "#c678dd";
|
|
351
|
-
var highlightBackground = "#2c313a";
|
|
352
|
-
var tooltipBackground = "#353a42";
|
|
353
|
-
var monospace = get2(tokens, "fontFamily.mono", [
|
|
354
|
-
"monospace"
|
|
355
|
-
]).join(",");
|
|
356
|
-
var markdownTheme = {
|
|
357
|
-
// TODO(thure): Consider whether these commented-out rules from one-dark-theme should be integrated.
|
|
358
|
-
// '&': {
|
|
359
|
-
// color: ivory,
|
|
360
|
-
// backgroundColor: background
|
|
361
|
-
// },
|
|
362
|
-
// '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
|
363
|
-
// '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
|
|
364
|
-
// backgroundColor: selection
|
|
365
|
-
// },
|
|
366
|
-
// '.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
|
367
|
-
// '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
|
368
|
-
// '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
|
369
|
-
// '.cm-searchMatch': {
|
|
370
|
-
// backgroundColor: '#72a1ff59',
|
|
371
|
-
// outline: '1px solid #457dff'
|
|
372
|
-
// },
|
|
373
|
-
// '.cm-searchMatch.cm-searchMatch-selected': {
|
|
374
|
-
// backgroundColor: '#6199ff2f'
|
|
375
|
-
// },
|
|
376
|
-
// '.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
|
377
|
-
// '.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
|
378
|
-
// '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
|
379
|
-
// backgroundColor: '#bad0f847'
|
|
380
|
-
// },
|
|
381
|
-
// '.cm-gutters': {
|
|
382
|
-
// backgroundColor: background,
|
|
383
|
-
// color: stone,
|
|
384
|
-
// border: 'none'
|
|
385
|
-
// },
|
|
386
|
-
// '.cm-activeLineGutter': {
|
|
387
|
-
// backgroundColor: highlightBackground
|
|
388
|
-
// },
|
|
389
|
-
// '.cm-foldPlaceholder': {
|
|
390
|
-
// backgroundColor: 'transparent',
|
|
391
|
-
// border: 'none',
|
|
392
|
-
// color: '#ddd'
|
|
393
|
-
// },
|
|
394
|
-
"& .cm-tooltip": {
|
|
395
|
-
backgroundColor: "transparent",
|
|
396
|
-
border: "none"
|
|
366
|
+
// packages/ui/react-ui-editor/src/extensions/awareness.ts
|
|
367
|
+
import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
|
|
368
|
+
import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
|
|
369
|
+
import { Event } from "@dxos/async";
|
|
370
|
+
import { Context } from "@dxos/context";
|
|
371
|
+
var dummyProvider = {
|
|
372
|
+
remoteStateChange: new Event(),
|
|
373
|
+
open: () => {
|
|
397
374
|
},
|
|
398
|
-
|
|
399
|
-
// border: 'none',
|
|
400
|
-
// backgroundColor: tooltipBackground,
|
|
401
|
-
// },
|
|
402
|
-
// '.dark & .cm-tooltip .cm-tooltip-arrow:before': {
|
|
403
|
-
// borderTopColor: 'transparent',
|
|
404
|
-
// borderBottomColor: 'transparent',
|
|
405
|
-
// },
|
|
406
|
-
// '.dark & .cm-tooltip .cm-tooltip-arrow:after': {
|
|
407
|
-
// borderTopColor: tooltipBackground,
|
|
408
|
-
// borderBottomColor: tooltipBackground,
|
|
409
|
-
// },
|
|
410
|
-
// '.dark & .cm-tooltip-autocomplete': {
|
|
411
|
-
// '& > ul > li[aria-selected]': {
|
|
412
|
-
// backgroundColor: highlightBackground,
|
|
413
|
-
// color: ivory,
|
|
414
|
-
// },
|
|
415
|
-
// },
|
|
416
|
-
"&.cm-focused": {
|
|
417
|
-
outline: "none"
|
|
418
|
-
},
|
|
419
|
-
"& .cm-line": {
|
|
420
|
-
paddingInline: 0,
|
|
421
|
-
lineHeight: 1.6,
|
|
422
|
-
minBlockSize: "1.6em"
|
|
423
|
-
},
|
|
424
|
-
"& .cm-line *": {
|
|
425
|
-
lineHeight: 1.6
|
|
426
|
-
},
|
|
427
|
-
"&.cm-focused .cm-selectionBackground, & .cm-selectionBackground": {
|
|
428
|
-
background: get2(tokens, "extend.colors.primary.150", "#00ffff")
|
|
429
|
-
},
|
|
430
|
-
".dark & .cm-selectionBackground, .dark &.cm-focused .cm-selectionBackground": {
|
|
431
|
-
background: get2(tokens, "extend.colors.primary.850", "#00ffff")
|
|
432
|
-
},
|
|
433
|
-
"& .cm-selectionMatch": {
|
|
434
|
-
background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
|
|
375
|
+
close: () => {
|
|
435
376
|
},
|
|
436
|
-
|
|
437
|
-
background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
|
|
377
|
+
update: () => {
|
|
438
378
|
},
|
|
439
|
-
|
|
379
|
+
getRemoteStates: () => []
|
|
380
|
+
};
|
|
381
|
+
var AwarenessProvider = Facet3.define({
|
|
382
|
+
combine: (providers) => providers[0] ?? dummyProvider
|
|
383
|
+
});
|
|
384
|
+
var RemoteSelectionChangedAnnotation = Annotation2.define();
|
|
385
|
+
var awareness = (provider = dummyProvider) => {
|
|
386
|
+
return [
|
|
387
|
+
AwarenessProvider.of(provider),
|
|
388
|
+
ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
|
|
389
|
+
decorations: (value) => value.decorations
|
|
390
|
+
}),
|
|
391
|
+
styles
|
|
392
|
+
];
|
|
393
|
+
};
|
|
394
|
+
var RemoteSelectionsDecorator = class {
|
|
395
|
+
constructor(view) {
|
|
396
|
+
this.decorations = RangeSet.of([]);
|
|
397
|
+
this._ctx = new Context();
|
|
398
|
+
this._lastAnchor = void 0;
|
|
399
|
+
this._lastHead = void 0;
|
|
400
|
+
this._cursorConverter = view.state.facet(CursorConverter);
|
|
401
|
+
this._provider = view.state.facet(AwarenessProvider);
|
|
402
|
+
this._provider.open();
|
|
403
|
+
this._provider.remoteStateChange.on(this._ctx, () => {
|
|
404
|
+
view.dispatch({
|
|
405
|
+
annotations: [
|
|
406
|
+
RemoteSelectionChangedAnnotation.of([])
|
|
407
|
+
]
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
destroy() {
|
|
412
|
+
void this._ctx.dispose();
|
|
413
|
+
this._provider.close();
|
|
414
|
+
}
|
|
415
|
+
update(update4) {
|
|
416
|
+
this._updateLocalSelection(update4);
|
|
417
|
+
this._updateRemoteSelections(update4);
|
|
418
|
+
}
|
|
419
|
+
_updateLocalSelection(update4) {
|
|
420
|
+
const hasFocus = update4.view.hasFocus && update4.view.dom.ownerDocument.hasFocus();
|
|
421
|
+
const { anchor = void 0, head = void 0 } = hasFocus ? update4.state.selection.main : {};
|
|
422
|
+
if (this._lastAnchor === anchor && this._lastHead === head) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
this._lastAnchor = anchor;
|
|
426
|
+
this._lastHead = head;
|
|
427
|
+
this._provider.update(anchor !== void 0 && head !== void 0 ? {
|
|
428
|
+
anchor: this._cursorConverter.toCursor(anchor),
|
|
429
|
+
head: this._cursorConverter.toCursor(head)
|
|
430
|
+
} : void 0);
|
|
431
|
+
}
|
|
432
|
+
_updateRemoteSelections(update4) {
|
|
433
|
+
const decorations = [];
|
|
434
|
+
const awarenessStates = this._provider.getRemoteStates();
|
|
435
|
+
for (const state of awarenessStates) {
|
|
436
|
+
const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
|
|
437
|
+
const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
|
|
438
|
+
if (anchor == null || head == null) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const start = Math.min(Math.min(anchor, head), update4.view.state.doc.length);
|
|
442
|
+
const end = Math.min(Math.max(anchor, head), update4.view.state.doc.length);
|
|
443
|
+
const startLine = update4.view.state.doc.lineAt(start);
|
|
444
|
+
const endLine = update4.view.state.doc.lineAt(end);
|
|
445
|
+
const color = state.info.color ?? "#30bced";
|
|
446
|
+
const lightColor = state.info.lightColor ?? color + "33";
|
|
447
|
+
if (startLine.number === endLine.number) {
|
|
448
|
+
decorations.push({
|
|
449
|
+
from: start,
|
|
450
|
+
to: end,
|
|
451
|
+
value: Decoration.mark({
|
|
452
|
+
attributes: {
|
|
453
|
+
style: `background-color: ${lightColor}`
|
|
454
|
+
},
|
|
455
|
+
class: "cm-ySelection"
|
|
456
|
+
})
|
|
457
|
+
});
|
|
458
|
+
} else {
|
|
459
|
+
decorations.push({
|
|
460
|
+
from: start,
|
|
461
|
+
to: startLine.from + startLine.length,
|
|
462
|
+
value: Decoration.mark({
|
|
463
|
+
attributes: {
|
|
464
|
+
style: `background-color: ${color}`
|
|
465
|
+
},
|
|
466
|
+
class: "cm-ySelection"
|
|
467
|
+
})
|
|
468
|
+
});
|
|
469
|
+
decorations.push({
|
|
470
|
+
from: endLine.from,
|
|
471
|
+
to: end,
|
|
472
|
+
value: Decoration.mark({
|
|
473
|
+
attributes: {
|
|
474
|
+
style: `background-color: ${color}`
|
|
475
|
+
},
|
|
476
|
+
class: "cm-ySelection"
|
|
477
|
+
})
|
|
478
|
+
});
|
|
479
|
+
for (let i = startLine.number + 1; i < endLine.number; i++) {
|
|
480
|
+
const linePos = update4.view.state.doc.line(i).from;
|
|
481
|
+
decorations.push({
|
|
482
|
+
from: linePos,
|
|
483
|
+
to: linePos,
|
|
484
|
+
value: Decoration.line({
|
|
485
|
+
attributes: {
|
|
486
|
+
style: `background-color: ${color}`,
|
|
487
|
+
class: "cm-yLineSelection"
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
decorations.push({
|
|
494
|
+
from: head,
|
|
495
|
+
to: head,
|
|
496
|
+
value: Decoration.widget({
|
|
497
|
+
side: head - anchor > 0 ? -1 : 1,
|
|
498
|
+
block: false,
|
|
499
|
+
widget: new RemoteCaretWidget(state.info.displayName ?? "Anonymous", color)
|
|
500
|
+
})
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
this.decorations = Decoration.set(decorations, true);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
var RemoteCaretWidget = class extends WidgetType {
|
|
507
|
+
constructor(name, color) {
|
|
508
|
+
super();
|
|
509
|
+
this.name = name;
|
|
510
|
+
this.color = color;
|
|
511
|
+
this.name = name;
|
|
512
|
+
this.color = color;
|
|
513
|
+
}
|
|
514
|
+
toDOM() {
|
|
515
|
+
const span = document.createElement("span");
|
|
516
|
+
span.className = "cm-ySelectionCaret";
|
|
517
|
+
span.style.backgroundColor = this.color;
|
|
518
|
+
span.style.borderColor = this.color;
|
|
519
|
+
const dot = document.createElement("div");
|
|
520
|
+
dot.className = "cm-ySelectionCaretDot";
|
|
521
|
+
const info = document.createElement("div");
|
|
522
|
+
info.className = "cm-ySelectionInfo";
|
|
523
|
+
info.innerText = this.name;
|
|
524
|
+
span.appendChild(document.createTextNode("\u2060"));
|
|
525
|
+
span.appendChild(dot);
|
|
526
|
+
span.appendChild(document.createTextNode("\u2060"));
|
|
527
|
+
span.appendChild(info);
|
|
528
|
+
span.appendChild(document.createTextNode("\u2060"));
|
|
529
|
+
return span;
|
|
530
|
+
}
|
|
531
|
+
eq(widget) {
|
|
532
|
+
return widget.color === this.color;
|
|
533
|
+
}
|
|
534
|
+
compare(widget) {
|
|
535
|
+
return widget.color === this.color;
|
|
536
|
+
}
|
|
537
|
+
updateDOM() {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
get estimatedHeight() {
|
|
541
|
+
return -1;
|
|
542
|
+
}
|
|
543
|
+
ignoreEvent() {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
var styles = EditorView.baseTheme({
|
|
548
|
+
".cm-ySelection": {},
|
|
549
|
+
".cm-yLineSelection": {
|
|
440
550
|
padding: 0,
|
|
441
|
-
|
|
442
|
-
},
|
|
443
|
-
".dark & .cm-content": {
|
|
444
|
-
caretColor: "white"
|
|
551
|
+
margin: "0px 2px 0px 4px"
|
|
445
552
|
},
|
|
446
|
-
"
|
|
447
|
-
|
|
553
|
+
".cm-ySelectionCaret": {
|
|
554
|
+
position: "relative",
|
|
555
|
+
borderLeft: "1px solid black",
|
|
556
|
+
borderRight: "1px solid black",
|
|
557
|
+
marginLeft: "-1px",
|
|
558
|
+
marginRight: "-1px",
|
|
559
|
+
boxSizing: "border-box",
|
|
560
|
+
display: "inline"
|
|
448
561
|
},
|
|
449
|
-
".
|
|
450
|
-
|
|
562
|
+
".cm-ySelectionCaretDot": {
|
|
563
|
+
borderRadius: "50%",
|
|
564
|
+
position: "absolute",
|
|
565
|
+
width: ".4em",
|
|
566
|
+
height: ".4em",
|
|
567
|
+
top: "-.2em",
|
|
568
|
+
left: "-.2em",
|
|
569
|
+
backgroundColor: "inherit",
|
|
570
|
+
transition: "transform .3s ease-in-out",
|
|
571
|
+
boxSizing: "border-box"
|
|
451
572
|
},
|
|
452
|
-
".cm-
|
|
453
|
-
|
|
573
|
+
".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
|
|
574
|
+
transformOrigin: "bottom center",
|
|
575
|
+
transform: "scale(0)"
|
|
454
576
|
},
|
|
455
|
-
"
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
mixBlendMode: "multiply"
|
|
476
|
-
},
|
|
477
|
-
".dark & .cm-ySelection, .dark & .cm-yLineSelection": {
|
|
478
|
-
mixBlendMode: "screen"
|
|
577
|
+
".cm-ySelectionInfo": {
|
|
578
|
+
position: "absolute",
|
|
579
|
+
top: "-1.05em",
|
|
580
|
+
left: "-1px",
|
|
581
|
+
fontSize: ".75em",
|
|
582
|
+
fontFamily: "serif",
|
|
583
|
+
fontStyle: "normal",
|
|
584
|
+
fontWeight: "normal",
|
|
585
|
+
lineHeight: "normal",
|
|
586
|
+
userSelect: "none",
|
|
587
|
+
color: "white",
|
|
588
|
+
paddingLeft: "2px",
|
|
589
|
+
paddingRight: "2px",
|
|
590
|
+
zIndex: 101,
|
|
591
|
+
transition: "opacity .3s ease-in-out",
|
|
592
|
+
backgroundColor: "inherit",
|
|
593
|
+
// these should be separate
|
|
594
|
+
opacity: 0,
|
|
595
|
+
transitionDelay: "0s",
|
|
596
|
+
whiteSpace: "nowrap"
|
|
479
597
|
},
|
|
480
|
-
"
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
598
|
+
".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
|
|
599
|
+
opacity: 1,
|
|
600
|
+
transitionDelay: "0s"
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// packages/ui/react-ui-editor/src/extensions/basic.ts
|
|
605
|
+
import { closeBrackets } from "@codemirror/autocomplete";
|
|
606
|
+
import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
|
607
|
+
import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
|
|
608
|
+
import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
|
|
609
|
+
var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
|
|
610
|
+
EditorView2.lineWrapping,
|
|
611
|
+
// TODO(burdon): Compare with minimalSetup.
|
|
612
|
+
// https://codemirror.net/docs/ref/#codemirror.minimalSetup
|
|
613
|
+
bracketMatching(),
|
|
614
|
+
closeBrackets(),
|
|
615
|
+
drawSelection(),
|
|
616
|
+
_placeholder && placeholder(_placeholder),
|
|
617
|
+
// TODO(burdon): Is this required?
|
|
618
|
+
themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
|
|
619
|
+
].filter(Boolean);
|
|
620
|
+
|
|
621
|
+
// packages/ui/react-ui-editor/src/extensions/blast.ts
|
|
622
|
+
import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
|
|
623
|
+
import defaultsDeep from "lodash.defaultsdeep";
|
|
624
|
+
import { invariant } from "@dxos/invariant";
|
|
625
|
+
var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
|
|
626
|
+
var defaultOptions = {
|
|
627
|
+
effect: 2,
|
|
628
|
+
maxParticles: 200,
|
|
629
|
+
particleGravity: 0.08,
|
|
630
|
+
particleAlphaFadeout: 0.996,
|
|
631
|
+
particleSize: {
|
|
632
|
+
min: 2,
|
|
633
|
+
max: 8
|
|
485
634
|
},
|
|
486
|
-
|
|
487
|
-
|
|
635
|
+
particleNumRange: {
|
|
636
|
+
min: 5,
|
|
637
|
+
max: 10
|
|
488
638
|
},
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
639
|
+
particleVelocityRange: {
|
|
640
|
+
x: [
|
|
641
|
+
-1,
|
|
642
|
+
1
|
|
643
|
+
],
|
|
644
|
+
y: [
|
|
645
|
+
-3.5,
|
|
646
|
+
-1.5
|
|
647
|
+
]
|
|
495
648
|
},
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
649
|
+
particleShrinkRate: 0.95,
|
|
650
|
+
shakeIntensity: 5
|
|
651
|
+
};
|
|
652
|
+
var blast = (options = defaultOptions) => {
|
|
653
|
+
let blaster;
|
|
654
|
+
let last = 0;
|
|
655
|
+
const getPoint = (view, pos) => {
|
|
656
|
+
const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
|
|
657
|
+
const element = document.elementFromPoint(x, y);
|
|
658
|
+
const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
|
|
659
|
+
const point = {
|
|
660
|
+
x: x - left,
|
|
661
|
+
y: y - top
|
|
507
662
|
};
|
|
508
|
-
|
|
509
|
-
|
|
663
|
+
return {
|
|
664
|
+
element,
|
|
665
|
+
point
|
|
510
666
|
};
|
|
511
|
-
|
|
512
|
-
|
|
667
|
+
};
|
|
668
|
+
return [
|
|
669
|
+
// Cursor moved.
|
|
670
|
+
EditorView3.updateListener.of((update4) => {
|
|
671
|
+
if (blaster?.node !== update4.view.scrollDOM) {
|
|
672
|
+
if (blaster) {
|
|
673
|
+
blaster.destroy();
|
|
674
|
+
}
|
|
675
|
+
blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
|
|
676
|
+
particleGravity: 0.2,
|
|
677
|
+
particleShrinkRate: 0.995,
|
|
678
|
+
color: () => [
|
|
679
|
+
100 + Math.random() * 100,
|
|
680
|
+
0,
|
|
681
|
+
0
|
|
682
|
+
]
|
|
683
|
+
}, options, defaultOptions));
|
|
684
|
+
blaster.initialize();
|
|
685
|
+
blaster.start();
|
|
686
|
+
} else {
|
|
687
|
+
blaster.resize();
|
|
688
|
+
}
|
|
689
|
+
const current = update4.state.selection.main.head;
|
|
690
|
+
if (current !== last) {
|
|
691
|
+
last = current;
|
|
692
|
+
const { element, point } = getPoint(update4.view, current - 1);
|
|
693
|
+
if (element && point) {
|
|
694
|
+
blaster.spawn({
|
|
695
|
+
element,
|
|
696
|
+
point
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}),
|
|
701
|
+
keymap2.of([
|
|
702
|
+
{
|
|
703
|
+
any: (_, event) => {
|
|
704
|
+
if (blaster) {
|
|
705
|
+
if (event.key === "Enter" && event.shiftKey) {
|
|
706
|
+
blaster.shake({
|
|
707
|
+
time: 0.8
|
|
708
|
+
});
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
])
|
|
716
|
+
];
|
|
717
|
+
};
|
|
718
|
+
var Blaster = class {
|
|
719
|
+
constructor(_node, _options) {
|
|
720
|
+
this._node = _node;
|
|
721
|
+
this._options = _options;
|
|
722
|
+
this._running = false;
|
|
723
|
+
this._shakeTime = 0;
|
|
724
|
+
this._shakeTimeMax = 0;
|
|
725
|
+
this._particles = [];
|
|
726
|
+
this._particlePointer = 0;
|
|
727
|
+
this._lastPoint = {
|
|
728
|
+
x: 0,
|
|
729
|
+
y: 0
|
|
513
730
|
};
|
|
514
|
-
|
|
515
|
-
|
|
731
|
+
this.shake = throttle(({ time }) => {
|
|
732
|
+
this._shakeTime = this._shakeTimeMax || time;
|
|
733
|
+
this._shakeTimeMax = time;
|
|
734
|
+
}, 100);
|
|
735
|
+
this.spawn = throttle(({ element, point }) => {
|
|
736
|
+
const color = getRGBComponents(element, this._options.color);
|
|
737
|
+
const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
|
|
738
|
+
const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
|
|
739
|
+
this._lastPoint = point;
|
|
740
|
+
for (let i = numParticles; i--; i > 0) {
|
|
741
|
+
this._particles[this._particlePointer] = this._effect.create(point.x - dir * 16, point.y, color);
|
|
742
|
+
this._particlePointer = (this._particlePointer + 1) % this._options.maxParticles;
|
|
743
|
+
}
|
|
744
|
+
}, 100);
|
|
745
|
+
this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
|
|
746
|
+
}
|
|
747
|
+
get node() {
|
|
748
|
+
return this._node;
|
|
749
|
+
}
|
|
750
|
+
initialize() {
|
|
751
|
+
invariant(!this._canvas && !this._ctx, void 0, {
|
|
752
|
+
F: __dxlog_file2,
|
|
753
|
+
L: 138,
|
|
754
|
+
S: this,
|
|
755
|
+
A: [
|
|
756
|
+
"!this._canvas && !this._ctx",
|
|
757
|
+
""
|
|
758
|
+
]
|
|
759
|
+
});
|
|
760
|
+
this._canvas = document.createElement("canvas");
|
|
761
|
+
this._canvas.id = "code-blast-canvas";
|
|
762
|
+
this._canvas.style.position = "absolute";
|
|
763
|
+
this._canvas.style.zIndex = "0";
|
|
764
|
+
this._canvas.style.pointerEvents = "none";
|
|
765
|
+
this._ctx = this._canvas.getContext("2d");
|
|
766
|
+
const parent = this._node.parentElement?.parentElement;
|
|
767
|
+
parent?.appendChild(this._canvas);
|
|
768
|
+
this.resize();
|
|
769
|
+
}
|
|
770
|
+
destroy() {
|
|
771
|
+
this.stop();
|
|
772
|
+
if (this._canvas) {
|
|
773
|
+
this._canvas.remove();
|
|
774
|
+
this._canvas = void 0;
|
|
775
|
+
this._ctx = void 0;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
resize() {
|
|
779
|
+
if (this._node.parentElement && this._canvas) {
|
|
780
|
+
const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
|
|
781
|
+
this._canvas.style.top = `${y}px`;
|
|
782
|
+
this._canvas.style.left = `${x}px`;
|
|
783
|
+
this._canvas.width = width;
|
|
784
|
+
this._canvas.height = height;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
start() {
|
|
788
|
+
invariant(this._canvas && this._ctx, void 0, {
|
|
789
|
+
F: __dxlog_file2,
|
|
790
|
+
L: 177,
|
|
791
|
+
S: this,
|
|
792
|
+
A: [
|
|
793
|
+
"this._canvas && this._ctx",
|
|
794
|
+
""
|
|
795
|
+
]
|
|
796
|
+
});
|
|
797
|
+
this._running = true;
|
|
798
|
+
this.loop();
|
|
799
|
+
}
|
|
800
|
+
stop() {
|
|
801
|
+
this._running = false;
|
|
802
|
+
this._node.style.transform = "translate(0px, 0px)";
|
|
803
|
+
}
|
|
804
|
+
loop() {
|
|
805
|
+
if (!this._running || !this._canvas || !this._ctx) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
this._ctx.clearRect(0, 0, this._canvas.width ?? 0, this._canvas.height ?? 0);
|
|
809
|
+
const now = (/* @__PURE__ */ new Date()).getTime();
|
|
810
|
+
this._lastTime ??= now;
|
|
811
|
+
const dt = (now - this._lastTime) / 1e3;
|
|
812
|
+
this._lastTime = now;
|
|
813
|
+
if (this._shakeTime > 0) {
|
|
814
|
+
this._shakeTime -= dt;
|
|
815
|
+
const magnitude = this._shakeTime / this._shakeTimeMax * this._options.shakeIntensity;
|
|
816
|
+
const shakeX = random(-magnitude, magnitude);
|
|
817
|
+
const shakeY = random(-magnitude, magnitude);
|
|
818
|
+
this._node.style.transform = `translate(${shakeX}px,${shakeY}px)`;
|
|
819
|
+
}
|
|
820
|
+
this.drawParticles();
|
|
821
|
+
requestAnimationFrame(this.loop.bind(this));
|
|
822
|
+
}
|
|
823
|
+
drawParticles() {
|
|
824
|
+
for (let i = this._particles.length; i--; i > 0) {
|
|
825
|
+
const particle = this._particles[i];
|
|
826
|
+
if (!particle) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (particle.alpha < 0.01 || particle.size <= 0.5) {
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
this._effect.update(this._ctx, particle);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
var Effect = class {
|
|
837
|
+
constructor(_options) {
|
|
838
|
+
this._options = _options;
|
|
839
|
+
}
|
|
516
840
|
};
|
|
517
|
-
var
|
|
841
|
+
var Effect1 = class extends Effect {
|
|
842
|
+
create(x, y, color) {
|
|
843
|
+
return {
|
|
844
|
+
x,
|
|
845
|
+
y: y + 10,
|
|
846
|
+
vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
|
|
847
|
+
vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
|
|
848
|
+
size: random(this._options.particleSize.min, this._options.particleSize.max),
|
|
849
|
+
color,
|
|
850
|
+
alpha: 1
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
update(ctx, particle) {
|
|
854
|
+
particle.vy += this._options.particleGravity;
|
|
855
|
+
particle.x += particle.vx;
|
|
856
|
+
particle.y += particle.vy;
|
|
857
|
+
particle.alpha *= this._options.particleAlphaFadeout;
|
|
858
|
+
ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
|
|
859
|
+
ctx.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, particle.size);
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
var Effect2 = class extends Effect {
|
|
863
|
+
create(x, y, color) {
|
|
864
|
+
return {
|
|
865
|
+
x,
|
|
866
|
+
y: y + 10,
|
|
867
|
+
vx: random(this._options.particleVelocityRange.x[0], this._options.particleVelocityRange.x[1]),
|
|
868
|
+
vy: random(this._options.particleVelocityRange.y[0], this._options.particleVelocityRange.y[1]),
|
|
869
|
+
size: random(this._options.particleSize.min, this._options.particleSize.max),
|
|
870
|
+
color,
|
|
871
|
+
alpha: 1,
|
|
872
|
+
theta: random(0, 360) * Math.PI / 180,
|
|
873
|
+
drag: 0.92,
|
|
874
|
+
wander: 0.15
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
update(ctx, particle) {
|
|
878
|
+
particle.vy += this._options.particleGravity;
|
|
879
|
+
particle.x += particle.vx;
|
|
880
|
+
particle.y += particle.vy;
|
|
881
|
+
particle.vx *= particle.drag;
|
|
882
|
+
particle.vy *= particle.drag;
|
|
883
|
+
particle.theta += random(-0.5, 0.5);
|
|
884
|
+
particle.vx += Math.sin(particle.theta) * 0.1;
|
|
885
|
+
particle.vy += Math.cos(particle.theta) * 0.1;
|
|
886
|
+
particle.size *= this._options.particleShrinkRate;
|
|
887
|
+
particle.alpha *= this._options.particleAlphaFadeout;
|
|
888
|
+
ctx.fillStyle = `rgba(${particle.color[0]},${particle.color[1]},${particle.color[2]},${particle.alpha})`;
|
|
889
|
+
ctx.beginPath();
|
|
890
|
+
ctx.arc(Math.round(particle.x - 1), Math.round(particle.y - 1), particle.size, 0, 2 * Math.PI);
|
|
891
|
+
ctx.fill();
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
function throttle(callback, limit) {
|
|
895
|
+
let wait = false;
|
|
896
|
+
return function(...args) {
|
|
897
|
+
if (!wait) {
|
|
898
|
+
callback.apply(this, args);
|
|
899
|
+
wait = true;
|
|
900
|
+
setTimeout(() => {
|
|
901
|
+
wait = false;
|
|
902
|
+
}, limit);
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
var getRGBComponents = (node, color) => {
|
|
907
|
+
if (typeof color === "function") {
|
|
908
|
+
return color();
|
|
909
|
+
}
|
|
910
|
+
const bgColor = getComputedStyle(node).color;
|
|
911
|
+
if (bgColor) {
|
|
912
|
+
const x = bgColor.match(/(\d+), (\d+), (\d+)/)?.slice(1);
|
|
913
|
+
if (x) {
|
|
914
|
+
return x;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return [
|
|
918
|
+
50,
|
|
919
|
+
50,
|
|
920
|
+
50
|
|
921
|
+
];
|
|
922
|
+
};
|
|
923
|
+
var random = (min, max) => {
|
|
924
|
+
if (!max) {
|
|
925
|
+
max = min;
|
|
926
|
+
min = 0;
|
|
927
|
+
}
|
|
928
|
+
return min + ~~(Math.random() * (max - min + 1));
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
// packages/ui/react-ui-editor/src/extensions/code.ts
|
|
932
|
+
import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
|
|
933
|
+
import get2 from "lodash.get";
|
|
934
|
+
import { mx as mx2 } from "@dxos/react-ui-theme";
|
|
935
|
+
|
|
936
|
+
// packages/ui/react-ui-editor/src/styles/cursors.ts
|
|
937
|
+
import * as random2 from "lib0/random";
|
|
938
|
+
var cursorColors = [
|
|
518
939
|
{
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
tags.name,
|
|
522
|
-
tags.deleted,
|
|
523
|
-
tags.character,
|
|
524
|
-
tags.propertyName,
|
|
525
|
-
tags.macroName,
|
|
526
|
-
tags.color,
|
|
527
|
-
tags.constant(tags.name),
|
|
528
|
-
tags.standard(tags.name),
|
|
529
|
-
tags.definition(tags.name),
|
|
530
|
-
tags.separator,
|
|
531
|
-
tags.typeName,
|
|
532
|
-
tags.className,
|
|
533
|
-
tags.number,
|
|
534
|
-
tags.changed,
|
|
535
|
-
tags.annotation,
|
|
536
|
-
tags.modifier,
|
|
537
|
-
tags.self,
|
|
538
|
-
tags.namespace,
|
|
539
|
-
tags.operator,
|
|
540
|
-
tags.operatorKeyword,
|
|
541
|
-
tags.escape,
|
|
542
|
-
tags.regexp,
|
|
543
|
-
tags.special(tags.string),
|
|
544
|
-
tags.meta,
|
|
545
|
-
tags.comment,
|
|
546
|
-
tags.atom,
|
|
547
|
-
tags.bool,
|
|
548
|
-
tags.special(tags.variableName),
|
|
549
|
-
tags.processingInstruction,
|
|
550
|
-
tags.string,
|
|
551
|
-
tags.inserted,
|
|
552
|
-
tags.invalid
|
|
553
|
-
],
|
|
554
|
-
color: "inherit !important"
|
|
940
|
+
color: "#30bced",
|
|
941
|
+
light: "#30bced33"
|
|
555
942
|
},
|
|
556
943
|
{
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
tags.url
|
|
560
|
-
],
|
|
561
|
-
color: "inherit !important",
|
|
562
|
-
textDecoration: "none !important"
|
|
944
|
+
color: "#6eeb83",
|
|
945
|
+
light: "#6eeb8333"
|
|
563
946
|
},
|
|
564
947
|
{
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
tags.labelName
|
|
568
|
-
],
|
|
569
|
-
color: malibu,
|
|
570
|
-
fontFamily: monospace
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
tag: [
|
|
574
|
-
markdownTags.codeMark,
|
|
575
|
-
markdownTags.emphasisMark,
|
|
576
|
-
markdownTags.headingMark,
|
|
577
|
-
markdownTags.linkLabel,
|
|
578
|
-
markdownTags.linkReference,
|
|
579
|
-
markdownTags.listMark,
|
|
580
|
-
markdownTags.quoteMark,
|
|
581
|
-
markdownTags.url,
|
|
582
|
-
tags.meta,
|
|
583
|
-
tags.processingInstruction
|
|
584
|
-
],
|
|
585
|
-
class: mark
|
|
948
|
+
color: "#ffbc42",
|
|
949
|
+
light: "#ffbc4233"
|
|
586
950
|
},
|
|
587
951
|
{
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
markdownTags.inlineCode
|
|
591
|
-
],
|
|
592
|
-
class: "font-mono"
|
|
952
|
+
color: "#ecd444",
|
|
953
|
+
light: "#ecd44433"
|
|
593
954
|
},
|
|
594
955
|
{
|
|
595
|
-
|
|
596
|
-
|
|
956
|
+
color: "#ee6352",
|
|
957
|
+
light: "#ee635233"
|
|
597
958
|
},
|
|
598
959
|
{
|
|
599
|
-
|
|
600
|
-
|
|
960
|
+
color: "#9ac2c9",
|
|
961
|
+
light: "#9ac2c933"
|
|
601
962
|
},
|
|
602
963
|
{
|
|
603
|
-
|
|
604
|
-
|
|
964
|
+
color: "#8acb88",
|
|
965
|
+
light: "#8acb8833"
|
|
605
966
|
},
|
|
606
967
|
{
|
|
607
|
-
|
|
608
|
-
|
|
968
|
+
color: "#1be7ff",
|
|
969
|
+
light: "#1be7ff33"
|
|
970
|
+
}
|
|
971
|
+
];
|
|
972
|
+
var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
|
|
973
|
+
|
|
974
|
+
// packages/ui/react-ui-editor/src/styles/markdown.ts
|
|
975
|
+
import { mx } from "@dxos/react-ui-theme";
|
|
976
|
+
var headings = {
|
|
977
|
+
1: [
|
|
978
|
+
"mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
|
|
979
|
+
"-ml-[10px]"
|
|
980
|
+
],
|
|
981
|
+
2: [
|
|
982
|
+
"mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
|
|
983
|
+
"-ml-[8px]"
|
|
984
|
+
],
|
|
985
|
+
3: [
|
|
986
|
+
"mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
|
|
987
|
+
"-ml-[7px]"
|
|
988
|
+
],
|
|
989
|
+
4: [
|
|
990
|
+
"mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
|
|
991
|
+
"-ml-[6px]"
|
|
992
|
+
],
|
|
993
|
+
5: [
|
|
994
|
+
"mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
|
|
995
|
+
"-ml-[5px]"
|
|
996
|
+
],
|
|
997
|
+
6: [
|
|
998
|
+
"mbs-4 mbe-2 font-medium text-inherit no-underline",
|
|
999
|
+
"-ml-[4px]"
|
|
1000
|
+
]
|
|
1001
|
+
};
|
|
1002
|
+
var heading = (level, readonly) => {
|
|
1003
|
+
const [style, offset] = headings[level];
|
|
1004
|
+
return mx(style, readonly && offset);
|
|
1005
|
+
};
|
|
1006
|
+
var light = "text-neutral-200 dark:text-neutral-800";
|
|
1007
|
+
var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
|
|
1008
|
+
var bold = "font-bold";
|
|
1009
|
+
var italic = "italic";
|
|
1010
|
+
var strikethrough = "line-through";
|
|
1011
|
+
var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
|
|
1012
|
+
var codeMark = "font-mono text-primary-500";
|
|
1013
|
+
var inlineUrl = mx(code, "px-1");
|
|
1014
|
+
var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
|
|
1015
|
+
|
|
1016
|
+
// packages/ui/react-ui-editor/src/styles/tokens.ts
|
|
1017
|
+
import { tailwindConfig } from "@dxos/react-ui-theme";
|
|
1018
|
+
var tokens = tailwindConfig({}).theme;
|
|
1019
|
+
|
|
1020
|
+
// packages/ui/react-ui-editor/src/extensions/code.ts
|
|
1021
|
+
var CODE_REGEX = /```[\s\S]*?```/gs;
|
|
1022
|
+
var styles2 = EditorView4.baseTheme({
|
|
1023
|
+
"& .cm-codeblock": {
|
|
1024
|
+
fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
|
|
609
1025
|
},
|
|
610
|
-
{
|
|
611
|
-
|
|
612
|
-
class: heading[4]
|
|
1026
|
+
'&light [aria-readonly="true"] .cm-codeblock': {
|
|
1027
|
+
background: get2(tokens, "extend.colors.neutral.50")
|
|
613
1028
|
},
|
|
614
|
-
{
|
|
615
|
-
|
|
616
|
-
class: heading[5]
|
|
1029
|
+
'&dark [aria-readonly="true"] .cm-codeblock': {
|
|
1030
|
+
background: get2(tokens, "extend.colors.neutral.850")
|
|
617
1031
|
},
|
|
618
|
-
{
|
|
619
|
-
|
|
620
|
-
|
|
1032
|
+
'& [aria-readonly="true"] .cm-codeblock': {
|
|
1033
|
+
display: "block",
|
|
1034
|
+
paddingInline: "4px !important"
|
|
621
1035
|
},
|
|
622
|
-
{
|
|
623
|
-
|
|
624
|
-
|
|
1036
|
+
'& [aria-readonly="true"] .cm-codeblock-first': {
|
|
1037
|
+
paddingTop: "4px !important",
|
|
1038
|
+
borderTopLeftRadius: "4px",
|
|
1039
|
+
borderTopRightRadius: "4px"
|
|
625
1040
|
},
|
|
626
|
-
{
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
], {
|
|
631
|
-
scope: markdownLanguage,
|
|
632
|
-
all: {
|
|
633
|
-
fontFamily: get2(tokens, "fontFamily.body", []).join(",")
|
|
1041
|
+
'& [aria-readonly="true"] .cm-codeblock-last': {
|
|
1042
|
+
paddingBottom: "4px !important",
|
|
1043
|
+
borderBottomLeftRadius: "4px",
|
|
1044
|
+
borderBottomRightRadius: "4px"
|
|
634
1045
|
}
|
|
635
1046
|
});
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1047
|
+
var getLineRange = (lines, from, to) => {
|
|
1048
|
+
const start = lines.findIndex((line) => line.from >= from);
|
|
1049
|
+
const end = lines.findIndex((line) => line.to >= to);
|
|
639
1050
|
return [
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
// https://github.github.com/gfm
|
|
676
|
-
// GFM,
|
|
677
|
-
// Custom styling.
|
|
678
|
-
markdownTagsExtension
|
|
679
|
-
]
|
|
1051
|
+
start,
|
|
1052
|
+
end
|
|
1053
|
+
];
|
|
1054
|
+
};
|
|
1055
|
+
var code2 = () => {
|
|
1056
|
+
const getDecorations = (view) => {
|
|
1057
|
+
const decorations = [];
|
|
1058
|
+
if (view.state.readOnly) {
|
|
1059
|
+
const text = view.state.doc.sliceString(0);
|
|
1060
|
+
const matches = text.matchAll(CODE_REGEX);
|
|
1061
|
+
const blocks = view.viewportLineBlocks;
|
|
1062
|
+
for (const match of matches) {
|
|
1063
|
+
const range = getLineRange(blocks, match.index, match.index + match[0].length);
|
|
1064
|
+
for (let i = range[0]; i <= range[1]; i++) {
|
|
1065
|
+
const block = blocks[i];
|
|
1066
|
+
decorations.push(Decoration2.line({
|
|
1067
|
+
class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
|
|
1068
|
+
}).range(block.from));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return Decoration2.set(decorations);
|
|
1073
|
+
};
|
|
1074
|
+
return [
|
|
1075
|
+
ViewPlugin3.fromClass(class {
|
|
1076
|
+
constructor(view) {
|
|
1077
|
+
this.decorations = getDecorations(view);
|
|
1078
|
+
}
|
|
1079
|
+
update(update4) {
|
|
1080
|
+
if (update4.docChanged || update4.viewportChanged) {
|
|
1081
|
+
this.decorations = getDecorations(update4.view);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}, {
|
|
1085
|
+
decorations: (value) => value.decorations
|
|
680
1086
|
}),
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
|
|
684
|
-
].filter(Boolean);
|
|
1087
|
+
styles2
|
|
1088
|
+
];
|
|
685
1089
|
};
|
|
686
1090
|
|
|
687
|
-
// packages/ui/react-ui-editor/src/
|
|
688
|
-
import {
|
|
689
|
-
import { EditorView as EditorView5 } from "@codemirror/view";
|
|
690
|
-
import { useFocusableGroup } from "@fluentui/react-tabster";
|
|
691
|
-
import { vim } from "@replit/codemirror-vim";
|
|
692
|
-
import defaultsDeep from "lodash.defaultsdeep";
|
|
1091
|
+
// packages/ui/react-ui-editor/src/extensions/comments.ts
|
|
1092
|
+
import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
|
|
1093
|
+
import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
|
|
693
1094
|
import get3 from "lodash.get";
|
|
694
|
-
import
|
|
695
|
-
import {
|
|
696
|
-
import {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
import { ChangeSet } from "@codemirror/state";
|
|
716
|
-
var amToCodemirror_default = (view, selection, target, patches) => {
|
|
717
|
-
for (const patch of patches) {
|
|
718
|
-
const changeSpec = handlePatch(patch, target, view.state);
|
|
719
|
-
if (changeSpec != null) {
|
|
720
|
-
const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
|
|
721
|
-
selection = selection.map(changeSet, 1);
|
|
722
|
-
view.dispatch({
|
|
723
|
-
changes: changeSet,
|
|
724
|
-
annotations: reconcileAnnotationType.of({})
|
|
725
|
-
});
|
|
726
|
-
}
|
|
1095
|
+
import sortBy from "lodash.sortby";
|
|
1096
|
+
import { debounce } from "@dxos/async";
|
|
1097
|
+
import { invariant as invariant2 } from "@dxos/invariant";
|
|
1098
|
+
import { log as log2 } from "@dxos/log";
|
|
1099
|
+
import { nonNullable } from "@dxos/util";
|
|
1100
|
+
var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
|
|
1101
|
+
var styles3 = EditorView5.baseTheme({
|
|
1102
|
+
"& .cm-bookmark": {
|
|
1103
|
+
cursor: "pointer",
|
|
1104
|
+
margin: "4px",
|
|
1105
|
+
padding: "4px",
|
|
1106
|
+
backgroundColor: "yellow"
|
|
1107
|
+
},
|
|
1108
|
+
"& .cm-bookmark-selected": {
|
|
1109
|
+
backgroundColor: "orange"
|
|
1110
|
+
},
|
|
1111
|
+
"& .cm-comment": {
|
|
1112
|
+
backgroundColor: get3(tokens, "extend.colors.yellow.50")
|
|
1113
|
+
},
|
|
1114
|
+
"& .cm-comment-active": {
|
|
1115
|
+
backgroundColor: get3(tokens, "extend.colors.yellow.100")
|
|
727
1116
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1117
|
+
});
|
|
1118
|
+
var marks = {
|
|
1119
|
+
highlight: Decoration3.mark({
|
|
1120
|
+
class: "cm-comment"
|
|
1121
|
+
}),
|
|
1122
|
+
highlightActive: Decoration3.mark({
|
|
1123
|
+
class: "cm-comment-active"
|
|
1124
|
+
})
|
|
732
1125
|
};
|
|
733
|
-
var
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return handleSplice(target, patch);
|
|
738
|
-
} else if (patch.action === "del") {
|
|
739
|
-
return handleDel(target, patch);
|
|
740
|
-
} else if (patch.action === "put") {
|
|
741
|
-
return handlePut(target, patch, state);
|
|
742
|
-
} else {
|
|
743
|
-
return null;
|
|
1126
|
+
var setFocus = (view, thread) => {
|
|
1127
|
+
const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
|
|
1128
|
+
if (!range) {
|
|
1129
|
+
return;
|
|
744
1130
|
}
|
|
1131
|
+
view.dispatch({
|
|
1132
|
+
effects: setSelection.of({
|
|
1133
|
+
active: thread
|
|
1134
|
+
}),
|
|
1135
|
+
selection: {
|
|
1136
|
+
anchor: range.from
|
|
1137
|
+
},
|
|
1138
|
+
scrollIntoView: true
|
|
1139
|
+
});
|
|
745
1140
|
};
|
|
746
|
-
var
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1141
|
+
var setCommentRange = StateEffect2.define();
|
|
1142
|
+
var setSelection = StateEffect2.define();
|
|
1143
|
+
var setCommentState = StateEffect2.define();
|
|
1144
|
+
var commentsStateField = StateField2.define({
|
|
1145
|
+
create: () => ({
|
|
1146
|
+
ranges: []
|
|
1147
|
+
}),
|
|
1148
|
+
update: (value, tr) => {
|
|
1149
|
+
const cursorConverter3 = tr.state.facet(CursorConverter);
|
|
1150
|
+
for (const effect of tr.effects) {
|
|
1151
|
+
if (effect.is(setSelection)) {
|
|
1152
|
+
return {
|
|
1153
|
+
...value,
|
|
1154
|
+
...effect.value
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
if (effect.is(setCommentRange)) {
|
|
1158
|
+
const { comments: comments2 } = effect.value;
|
|
1159
|
+
const ranges = comments2.map((comment) => {
|
|
1160
|
+
const range = getRangeFromCursor(cursorConverter3, comment.cursor);
|
|
1161
|
+
return range && {
|
|
1162
|
+
...comment,
|
|
1163
|
+
...range
|
|
1164
|
+
};
|
|
1165
|
+
}).filter(nonNullable);
|
|
1166
|
+
return {
|
|
1167
|
+
...value,
|
|
1168
|
+
ranges
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
if (effect.is(setCommentState)) {
|
|
1172
|
+
return effect.value;
|
|
1173
|
+
}
|
|
757
1174
|
}
|
|
758
|
-
|
|
759
|
-
};
|
|
760
|
-
var handleSplice = (target, patch) => {
|
|
761
|
-
const index = charPath(target, patch.path);
|
|
762
|
-
if (index == null) {
|
|
763
|
-
return [];
|
|
1175
|
+
return value;
|
|
764
1176
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1177
|
+
});
|
|
1178
|
+
var highlightDecorations = EditorView5.decorations.compute([
|
|
1179
|
+
commentsStateField
|
|
1180
|
+
], (state) => {
|
|
1181
|
+
const { active, ranges } = state.field(commentsStateField);
|
|
1182
|
+
const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
|
|
1183
|
+
if (selection.from === selection.to) {
|
|
1184
|
+
return void 0;
|
|
769
1185
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
return [
|
|
779
|
-
{
|
|
780
|
-
from: index,
|
|
781
|
-
to: index + length
|
|
1186
|
+
const range = {
|
|
1187
|
+
from: selection.from,
|
|
1188
|
+
to: selection.to
|
|
1189
|
+
};
|
|
1190
|
+
if (selection.id === active) {
|
|
1191
|
+
return marks.highlightActive.range(range.from, range.to);
|
|
1192
|
+
} else {
|
|
1193
|
+
return marks.highlight.range(range.from, range.to);
|
|
782
1194
|
}
|
|
783
|
-
];
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1195
|
+
}).filter(nonNullable) ?? [];
|
|
1196
|
+
return Decoration3.set(decorations);
|
|
1197
|
+
});
|
|
1198
|
+
var BookmarkWidget = class extends WidgetType2 {
|
|
1199
|
+
constructor(_anchor, _id, _handleClick) {
|
|
1200
|
+
super();
|
|
1201
|
+
this._anchor = _anchor;
|
|
1202
|
+
this._id = _id;
|
|
1203
|
+
this._handleClick = _handleClick;
|
|
1204
|
+
invariant2(this._id, void 0, {
|
|
1205
|
+
F: __dxlog_file3,
|
|
1206
|
+
L: 169,
|
|
1207
|
+
S: this,
|
|
1208
|
+
A: [
|
|
1209
|
+
"this._id",
|
|
1210
|
+
""
|
|
1211
|
+
]
|
|
1212
|
+
});
|
|
796
1213
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
from: 0,
|
|
800
|
-
to: length,
|
|
801
|
-
insert: patch.value
|
|
802
|
-
}
|
|
803
|
-
];
|
|
804
|
-
};
|
|
805
|
-
var charPath = (textPath, candidatePath) => {
|
|
806
|
-
if (candidatePath.length !== textPath.length + 1) {
|
|
807
|
-
return null;
|
|
1214
|
+
eq(other) {
|
|
1215
|
+
return this._anchor === other._anchor && this._id === other._id;
|
|
808
1216
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
1217
|
+
toDOM() {
|
|
1218
|
+
const span = document.createElement("span");
|
|
1219
|
+
span.className = "cm-bookmark";
|
|
1220
|
+
span.textContent = "\u203B";
|
|
1221
|
+
if (this._handleClick) {
|
|
1222
|
+
span.onclick = () => this._handleClick();
|
|
812
1223
|
}
|
|
1224
|
+
return span;
|
|
813
1225
|
}
|
|
814
|
-
const index = candidatePath[candidatePath.length - 1];
|
|
815
|
-
if (typeof index === "number") {
|
|
816
|
-
return index;
|
|
817
|
-
}
|
|
818
|
-
return null;
|
|
819
1226
|
};
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1227
|
+
var comments = (options = {}) => {
|
|
1228
|
+
const handleSelect = debounce((state) => options.onSelect?.(state), 200);
|
|
1229
|
+
const createCommentThread = (view) => {
|
|
1230
|
+
const cursorConverter3 = view.state.facet(CursorConverter);
|
|
1231
|
+
invariant2(options.onCreate, void 0, {
|
|
1232
|
+
F: __dxlog_file3,
|
|
1233
|
+
L: 225,
|
|
1234
|
+
S: void 0,
|
|
1235
|
+
A: [
|
|
1236
|
+
"options.onCreate",
|
|
1237
|
+
""
|
|
1238
|
+
]
|
|
829
1239
|
});
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
const newHeads = handle.changeAt(lastHeads, (doc) => {
|
|
835
|
-
for (const tr of transactions) {
|
|
836
|
-
tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
|
|
837
|
-
am.splice(doc, path, fromA, toA - fromA, inserted.toString());
|
|
838
|
-
});
|
|
1240
|
+
const { head, from, to } = view.state.selection.main;
|
|
1241
|
+
if (from === to) {
|
|
1242
|
+
return false;
|
|
839
1243
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
this._inReconcile = false;
|
|
848
|
-
this._queue = [];
|
|
849
|
-
this.reconcile = (handle, view) => {
|
|
850
|
-
if (this._inReconcile) {
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
this._inReconcile = true;
|
|
854
|
-
const path = getPath(view.state, this._field);
|
|
855
|
-
const oldHeads = getLastHeads(view.state, this._field);
|
|
856
|
-
let selection = view.state.selection;
|
|
857
|
-
const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
|
|
858
|
-
const toInvert = transactions.slice().reverse();
|
|
859
|
-
for (const tx of toInvert) {
|
|
860
|
-
const inverted = tx.changes.invert(tx.startState.doc);
|
|
861
|
-
selection = selection.map(inverted);
|
|
1244
|
+
const relPos = getCursorFromRange(cursorConverter3, {
|
|
1245
|
+
from,
|
|
1246
|
+
to
|
|
1247
|
+
});
|
|
1248
|
+
if (relPos) {
|
|
1249
|
+
const id = callbackWrapper(options.onCreate)(relPos);
|
|
1250
|
+
if (id) {
|
|
862
1251
|
view.dispatch({
|
|
863
|
-
|
|
864
|
-
|
|
1252
|
+
effects: setSelection.of({
|
|
1253
|
+
active: id
|
|
1254
|
+
}),
|
|
1255
|
+
selection: {
|
|
1256
|
+
anchor: from
|
|
1257
|
+
}
|
|
865
1258
|
});
|
|
1259
|
+
if (options.footnote) {
|
|
1260
|
+
const tag = `[^${id}]`;
|
|
1261
|
+
view.dispatch({
|
|
1262
|
+
changes: {
|
|
1263
|
+
from: head,
|
|
1264
|
+
insert: tag
|
|
1265
|
+
},
|
|
1266
|
+
selection: {
|
|
1267
|
+
anchor: head + tag.length
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
return true;
|
|
866
1272
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1273
|
+
}
|
|
1274
|
+
return false;
|
|
1275
|
+
};
|
|
1276
|
+
const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
|
|
1277
|
+
static {
|
|
1278
|
+
// Match markdown footnotes (option).
|
|
1279
|
+
// https://www.markdownguide.org/extended-syntax/#footnotes
|
|
1280
|
+
this.bookmarkMatcher = new MatchDecorator({
|
|
1281
|
+
regexp: /\[\^(\w+)\]/g,
|
|
1282
|
+
decoration: (match, view, pos) => {
|
|
1283
|
+
const id = match[1];
|
|
1284
|
+
return Decoration3.replace({
|
|
1285
|
+
id,
|
|
1286
|
+
widget: new BookmarkWidget(pos, id)
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
876
1289
|
});
|
|
877
|
-
this._inReconcile = false;
|
|
878
|
-
};
|
|
879
|
-
if (field !== void 0) {
|
|
880
|
-
this._field = field;
|
|
881
1290
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1291
|
+
constructor(view) {
|
|
1292
|
+
this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
|
|
1293
|
+
}
|
|
1294
|
+
update(update4) {
|
|
1295
|
+
this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
|
|
1296
|
+
}
|
|
1297
|
+
}, {
|
|
1298
|
+
decorations: (instance) => instance.bookmarks,
|
|
1299
|
+
provide: (plugin) => EditorView5.atomicRanges.of((view) => {
|
|
1300
|
+
return view.plugin(plugin)?.bookmarks || Decoration3.none;
|
|
1301
|
+
})
|
|
1302
|
+
});
|
|
1303
|
+
return [
|
|
1304
|
+
bookmarksViewPlugin,
|
|
1305
|
+
commentsStateField,
|
|
1306
|
+
highlightDecorations,
|
|
1307
|
+
styles3,
|
|
1308
|
+
//
|
|
1309
|
+
// Keymap.
|
|
1310
|
+
//
|
|
1311
|
+
options.onCreate ? keymap3.of([
|
|
1312
|
+
{
|
|
1313
|
+
key: options?.key ?? "meta-'",
|
|
1314
|
+
run: callbackWrapper(createCommentThread)
|
|
1315
|
+
}
|
|
1316
|
+
]) : [],
|
|
1317
|
+
//
|
|
1318
|
+
// Hover tooltip (for key shortcut hints, etc.)
|
|
1319
|
+
// TODO(burdon): Factor out to generic hints extension for current selection/line.
|
|
1320
|
+
//
|
|
1321
|
+
options.onHover ? hoverTooltip((view, pos) => {
|
|
1322
|
+
const selection = view.state.selection.main;
|
|
1323
|
+
if (selection && pos >= selection.from && pos <= selection.to) {
|
|
1324
|
+
return {
|
|
1325
|
+
pos: selection.from,
|
|
1326
|
+
end: selection.to,
|
|
1327
|
+
above: true,
|
|
1328
|
+
create: () => {
|
|
1329
|
+
const el = document.createElement("div");
|
|
1330
|
+
options.onHover?.(el);
|
|
1331
|
+
return {
|
|
1332
|
+
dom: el,
|
|
1333
|
+
offset: {
|
|
1334
|
+
x: 0,
|
|
1335
|
+
y: 8
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
}, {
|
|
1343
|
+
hideOnChange: true,
|
|
1344
|
+
hoverTime: 1e3
|
|
1345
|
+
}) : [],
|
|
1346
|
+
//
|
|
1347
|
+
// Monitor cursor movement and text updates.
|
|
1348
|
+
// TODO(burdon): Is there a better (finer grained) way to do this?
|
|
1349
|
+
//
|
|
1350
|
+
EditorView5.updateListener.of(({ view, state, changes }) => {
|
|
1351
|
+
const cursorConverter3 = view.state.facet(CursorConverter);
|
|
1352
|
+
{
|
|
1353
|
+
let mod = false;
|
|
1354
|
+
const { active, ranges } = state.field(commentsStateField);
|
|
1355
|
+
changes.iterChanges((from, to, from2, to2) => {
|
|
1356
|
+
ranges.forEach((range) => {
|
|
1357
|
+
if (from2 === to2) {
|
|
1358
|
+
const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
|
|
1359
|
+
if (!newRange || newRange.to - newRange.from === 0) {
|
|
1360
|
+
log2.info("deleted comment", {
|
|
1361
|
+
thread: range.id
|
|
1362
|
+
}, {
|
|
1363
|
+
F: __dxlog_file3,
|
|
1364
|
+
L: 359,
|
|
1365
|
+
S: void 0,
|
|
1366
|
+
C: (f, a) => f(...a)
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if (from <= range.to) {
|
|
1371
|
+
const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
|
|
1372
|
+
Object.assign(range, newRange);
|
|
1373
|
+
mod = true;
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
});
|
|
1377
|
+
if (mod) {
|
|
1378
|
+
view.dispatch({
|
|
1379
|
+
effects: setCommentState.of({
|
|
1380
|
+
active,
|
|
1381
|
+
ranges
|
|
1382
|
+
})
|
|
1383
|
+
});
|
|
913
1384
|
}
|
|
914
1385
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1386
|
+
{
|
|
1387
|
+
const { head } = state.selection.main;
|
|
1388
|
+
let min = Infinity;
|
|
1389
|
+
const { active, closest, ranges } = state.field(commentsStateField);
|
|
1390
|
+
const selected = {
|
|
1391
|
+
active: void 0,
|
|
1392
|
+
closest: void 0
|
|
1393
|
+
};
|
|
1394
|
+
ranges.forEach((comment) => {
|
|
1395
|
+
const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
|
|
1396
|
+
if (head >= comment.from && head <= comment.to) {
|
|
1397
|
+
selected.active = comment.id;
|
|
1398
|
+
}
|
|
1399
|
+
if (d < min) {
|
|
1400
|
+
selected.closest = comment.id;
|
|
1401
|
+
min = d;
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
if (selected.active !== active || selected.closest !== closest) {
|
|
1405
|
+
view.dispatch({
|
|
1406
|
+
effects: setSelection.of(selected)
|
|
1407
|
+
});
|
|
1408
|
+
handleSelect({
|
|
1409
|
+
...selected,
|
|
1410
|
+
ranges: ranges.map((range) => ({
|
|
1411
|
+
...range,
|
|
1412
|
+
location: view.coordsAtPos(range.from)
|
|
1413
|
+
}))
|
|
1414
|
+
});
|
|
920
1415
|
}
|
|
921
1416
|
}
|
|
922
|
-
|
|
1417
|
+
})
|
|
1418
|
+
];
|
|
1419
|
+
};
|
|
1420
|
+
var getCursorFromRange = (cursorConverter3, range) => {
|
|
1421
|
+
const from = cursorConverter3.toCursor(range.from);
|
|
1422
|
+
const to = cursorConverter3.toCursor(range.to, -1);
|
|
1423
|
+
return [
|
|
1424
|
+
from,
|
|
1425
|
+
to
|
|
1426
|
+
].join(":");
|
|
1427
|
+
};
|
|
1428
|
+
var getRangeFromCursor = (cursorConverter3, cursor) => {
|
|
1429
|
+
const parts = cursor.split(":");
|
|
1430
|
+
const from = cursorConverter3.fromCursor(parts[0]);
|
|
1431
|
+
const to = cursorConverter3.fromCursor(parts[1]);
|
|
1432
|
+
return from && to ? {
|
|
1433
|
+
from,
|
|
1434
|
+
to
|
|
1435
|
+
} : void 0;
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
// packages/ui/react-ui-editor/src/extensions/hr.ts
|
|
1439
|
+
import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
|
|
1440
|
+
import get4 from "lodash.get";
|
|
1441
|
+
var styles4 = EditorView6.baseTheme({
|
|
1442
|
+
"& .cm-hr": {
|
|
1443
|
+
// TODO(burdon): ???
|
|
1444
|
+
// Note that block-level decorations should not have vertical margins,
|
|
1445
|
+
borderBottom: `1px solid ${get4(tokens, "extend.colors.neutral.200")}`
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
var HorizontalRuleWidget = class extends WidgetType3 {
|
|
1449
|
+
constructor(_pos) {
|
|
1450
|
+
super();
|
|
1451
|
+
this._pos = _pos;
|
|
1452
|
+
}
|
|
1453
|
+
eq(other) {
|
|
1454
|
+
return this._pos === other._pos;
|
|
1455
|
+
}
|
|
1456
|
+
toDOM(view) {
|
|
1457
|
+
const el = document.createElement("div");
|
|
1458
|
+
el.className = "cm-hr";
|
|
1459
|
+
return el;
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
var placeholderMatcher = new MatchDecorator2({
|
|
1463
|
+
regexp: /^---$/g,
|
|
1464
|
+
decoration: (match, view, pos) => Decoration4.replace({
|
|
1465
|
+
widget: new HorizontalRuleWidget(pos)
|
|
1466
|
+
})
|
|
1467
|
+
});
|
|
1468
|
+
var hr = () => [
|
|
1469
|
+
styles4,
|
|
1470
|
+
ViewPlugin5.fromClass(class {
|
|
1471
|
+
constructor(view) {
|
|
1472
|
+
this.rules = placeholderMatcher.createDeco(view);
|
|
923
1473
|
}
|
|
1474
|
+
update(update4) {
|
|
1475
|
+
this.rules = placeholderMatcher.updateDeco(update4, this.rules);
|
|
1476
|
+
}
|
|
1477
|
+
}, {
|
|
1478
|
+
decorations: (instance) => instance.rules,
|
|
1479
|
+
// TODO(burdon): Is this really atomic (cursor can move into ---).
|
|
1480
|
+
provide: (plugin) => EditorView6.atomicRanges.of((view) => {
|
|
1481
|
+
return view.plugin(plugin)?.rules || Decoration4.none;
|
|
1482
|
+
})
|
|
1483
|
+
})
|
|
1484
|
+
];
|
|
1485
|
+
|
|
1486
|
+
// packages/ui/react-ui-editor/src/extensions/image.ts
|
|
1487
|
+
import { syntaxTree } from "@codemirror/language";
|
|
1488
|
+
import { RangeSetBuilder, StateField as StateField3 } from "@codemirror/state";
|
|
1489
|
+
import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
|
|
1490
|
+
var image = (options = {}) => {
|
|
1491
|
+
return StateField3.define({
|
|
1492
|
+
create: (state) => update(state, options),
|
|
1493
|
+
update: (_, tr) => update(tr.state, options),
|
|
1494
|
+
provide: (field) => EditorView7.decorations.from(field)
|
|
924
1495
|
});
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1496
|
+
};
|
|
1497
|
+
var update = (state, options) => {
|
|
1498
|
+
const builder = new RangeSetBuilder();
|
|
1499
|
+
const cursor = state.selection.main.head;
|
|
1500
|
+
syntaxTree(state).iterate({
|
|
1501
|
+
enter: (node) => {
|
|
1502
|
+
if (node.name === "Image") {
|
|
1503
|
+
const urlNode = node.node.getChild("URL");
|
|
1504
|
+
if (urlNode) {
|
|
1505
|
+
const hide = state.readOnly || cursor < node.from || cursor > node.to;
|
|
1506
|
+
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
|
1507
|
+
builder.add(hide ? node.from : node.to, node.to, Decoration5.replace({
|
|
1508
|
+
block: true,
|
|
1509
|
+
widget: new ImageWidget(url)
|
|
1510
|
+
}));
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
933
1513
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1514
|
+
});
|
|
1515
|
+
return builder.finish();
|
|
1516
|
+
};
|
|
1517
|
+
var ImageWidget = class extends WidgetType4 {
|
|
1518
|
+
constructor(_url) {
|
|
1519
|
+
super();
|
|
1520
|
+
this._url = _url;
|
|
1521
|
+
}
|
|
1522
|
+
// TODO(burdon): Does this need to be unique for each position?
|
|
1523
|
+
eq(other) {
|
|
1524
|
+
return this._url === other._url;
|
|
1525
|
+
}
|
|
1526
|
+
toDOM(view) {
|
|
1527
|
+
const img = document.createElement("img");
|
|
1528
|
+
img.setAttribute("src", this._url);
|
|
1529
|
+
img.setAttribute("class", "cm-image");
|
|
1530
|
+
return img;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
// packages/ui/react-ui-editor/src/extensions/link.ts
|
|
1535
|
+
import { syntaxTree as syntaxTree2 } from "@codemirror/language";
|
|
1536
|
+
import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
|
|
1537
|
+
import { Decoration as Decoration6, EditorView as EditorView8, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2 } from "@codemirror/view";
|
|
1538
|
+
import { tooltipContent } from "@dxos/react-ui-theme";
|
|
1539
|
+
var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
|
|
1540
|
+
var link = (options = {}) => {
|
|
1541
|
+
const extensions = [
|
|
1542
|
+
StateField4.define({
|
|
1543
|
+
create: (state) => update2(state, options),
|
|
1544
|
+
update: (_, tr) => update2(tr.state, options),
|
|
1545
|
+
provide: (field) => EditorView8.decorations.from(field)
|
|
1546
|
+
})
|
|
1547
|
+
];
|
|
1548
|
+
if (options.onHover) {
|
|
1549
|
+
extensions.push(
|
|
1550
|
+
// https://codemirror.net/examples/tooltip
|
|
1551
|
+
// https://codemirror.net/docs/ref/#view.hoverTooltip
|
|
1552
|
+
// https://github.com/codemirror/view/blob/main/src/tooltip.ts
|
|
1553
|
+
hoverTooltip2((view, pos) => {
|
|
1554
|
+
const { from, text } = view.state.doc.lineAt(pos);
|
|
1555
|
+
const p = pos - from;
|
|
1556
|
+
let idx = 0;
|
|
1557
|
+
let match;
|
|
1558
|
+
do {
|
|
1559
|
+
match = text.substring(idx).match(markdownLinkRegexp);
|
|
1560
|
+
if (!match) {
|
|
1561
|
+
match = void 0;
|
|
1562
|
+
break;
|
|
1563
|
+
}
|
|
1564
|
+
idx = text.indexOf(match[0]);
|
|
1565
|
+
if (p >= idx && p < idx + match[0].length) {
|
|
1566
|
+
break;
|
|
1567
|
+
}
|
|
1568
|
+
idx += match[0].length;
|
|
1569
|
+
} while (true);
|
|
1570
|
+
if (!match) {
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
const [, , url] = match ?? [];
|
|
1574
|
+
return {
|
|
1575
|
+
pos: idx + from,
|
|
1576
|
+
end: idx + from + match[0].length,
|
|
1577
|
+
above: true,
|
|
1578
|
+
create: () => {
|
|
1579
|
+
const el = document.createElement("div");
|
|
1580
|
+
el.className = tooltipContent({}, "pli-2 plb-1");
|
|
1581
|
+
options.onHover(el, url);
|
|
1582
|
+
return {
|
|
1583
|
+
dom: el,
|
|
1584
|
+
offset: {
|
|
1585
|
+
x: 0,
|
|
1586
|
+
y: 4
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
})
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
return extensions;
|
|
1595
|
+
};
|
|
1596
|
+
var LinkButton = class extends WidgetType5 {
|
|
1597
|
+
constructor(_pos, _url, _onAttach) {
|
|
1598
|
+
super();
|
|
1599
|
+
this._pos = _pos;
|
|
1600
|
+
this._url = _url;
|
|
1601
|
+
this._onAttach = _onAttach;
|
|
1602
|
+
}
|
|
1603
|
+
eq(other) {
|
|
1604
|
+
return this._pos === other._pos && this._url === other._url;
|
|
1605
|
+
}
|
|
1606
|
+
toDOM(view) {
|
|
1607
|
+
const el = document.createElement("span");
|
|
1608
|
+
this._onAttach(el, this._url);
|
|
1609
|
+
return el;
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
var LinkText = class extends WidgetType5 {
|
|
1613
|
+
constructor(_text, _url) {
|
|
1614
|
+
super();
|
|
1615
|
+
this._text = _text;
|
|
1616
|
+
this._url = _url;
|
|
1617
|
+
}
|
|
1618
|
+
eq(other) {
|
|
1619
|
+
return this._url === other._url;
|
|
1620
|
+
}
|
|
1621
|
+
toDOM(view) {
|
|
1622
|
+
const link2 = document.createElement("a");
|
|
1623
|
+
link2.setAttribute("class", "cm-link");
|
|
1624
|
+
link2.textContent = this._text;
|
|
1625
|
+
if (this._url) {
|
|
1626
|
+
link2.setAttribute("rel", "noreferrer");
|
|
1627
|
+
link2.setAttribute("target", "_blank");
|
|
1628
|
+
link2.href = this._url;
|
|
1629
|
+
} else {
|
|
1630
|
+
link2.onclick = () => {
|
|
1631
|
+
view.dispatch({
|
|
1632
|
+
selection: {
|
|
1633
|
+
anchor: view.posAtDOM(link2)
|
|
1634
|
+
}
|
|
938
1635
|
});
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
return link2;
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
var update2 = (state, options) => {
|
|
1642
|
+
const builder = new RangeSetBuilder2();
|
|
1643
|
+
const cursor = state.selection.main.head;
|
|
1644
|
+
syntaxTree2(state).iterate({
|
|
1645
|
+
enter: (node) => {
|
|
1646
|
+
if (node.name === "Link") {
|
|
1647
|
+
const marks2 = node.node.getChildren("LinkMark");
|
|
1648
|
+
const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
|
|
1649
|
+
const urlNode = node.node.getChild("URL");
|
|
1650
|
+
const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
|
|
1651
|
+
if (!url) {
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
if (state.readOnly || cursor < node.from || cursor > node.to) {
|
|
1655
|
+
builder.add(node.from, node.to, Decoration6.replace({
|
|
1656
|
+
widget: new LinkText(text, options.link ? url : void 0)
|
|
1657
|
+
}));
|
|
1658
|
+
}
|
|
1659
|
+
if (options.onRender) {
|
|
1660
|
+
builder.add(node.to, node.to, Decoration6.replace({
|
|
1661
|
+
widget: new LinkButton(node.from, url, options.onRender)
|
|
1662
|
+
}));
|
|
1663
|
+
}
|
|
1664
|
+
return false;
|
|
939
1665
|
}
|
|
940
1666
|
}
|
|
941
|
-
|
|
942
|
-
|
|
1667
|
+
});
|
|
1668
|
+
return builder.finish();
|
|
1669
|
+
};
|
|
1670
|
+
|
|
1671
|
+
// packages/ui/react-ui-editor/src/extensions/listener.ts
|
|
1672
|
+
import { EditorView as EditorView9 } from "@codemirror/view";
|
|
1673
|
+
var listener = ({ onFocus, onChange }) => {
|
|
1674
|
+
const extensions = [];
|
|
1675
|
+
onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
|
|
1676
|
+
onFocus(focused);
|
|
1677
|
+
return null;
|
|
1678
|
+
}));
|
|
1679
|
+
onChange && extensions.push(EditorView9.updateListener.of((update4) => {
|
|
1680
|
+
onChange(update4.state.doc.toString());
|
|
1681
|
+
}));
|
|
1682
|
+
return extensions;
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
|
|
1686
|
+
import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
|
|
1687
|
+
import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
|
|
1688
|
+
import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
|
|
1689
|
+
import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
|
|
1690
|
+
import { languages } from "@codemirror/language-data";
|
|
1691
|
+
import { searchKeymap } from "@codemirror/search";
|
|
1692
|
+
import { EditorState } from "@codemirror/state";
|
|
1693
|
+
import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
|
|
1694
|
+
import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
|
|
1695
|
+
|
|
1696
|
+
// packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
|
|
1697
|
+
import { markdownLanguage } from "@codemirror/lang-markdown";
|
|
1698
|
+
import { HighlightStyle } from "@codemirror/language";
|
|
1699
|
+
import { tags, styleTags, Tag } from "@lezer/highlight";
|
|
1700
|
+
import { Table } from "@lezer/markdown";
|
|
1701
|
+
import get5 from "lodash.get";
|
|
1702
|
+
var markdownTags = {
|
|
1703
|
+
Blockquote: Tag.define(),
|
|
1704
|
+
CodeMark: Tag.define(),
|
|
1705
|
+
CodeText: Tag.define(),
|
|
1706
|
+
EmphasisMark: Tag.define(),
|
|
1707
|
+
HeaderMark: Tag.define(),
|
|
1708
|
+
// HorizontalRule: Tag.define(),
|
|
1709
|
+
InlineCode: Tag.define(),
|
|
1710
|
+
LinkLabel: Tag.define(),
|
|
1711
|
+
LinkReference: Tag.define(),
|
|
1712
|
+
ListMark: Tag.define(),
|
|
1713
|
+
QuoteMark: Tag.define(),
|
|
1714
|
+
URL: Tag.define(),
|
|
1715
|
+
// Custom.
|
|
1716
|
+
TableCell: Tag.define()
|
|
1717
|
+
};
|
|
1718
|
+
Table.defineNodes?.forEach((node) => {
|
|
1719
|
+
switch (node?.name) {
|
|
1720
|
+
case "TableCell": {
|
|
1721
|
+
node.style = markdownTags.TableCell;
|
|
1722
|
+
break;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
var markdownTagsExtensions = [
|
|
1727
|
+
Table,
|
|
1728
|
+
{
|
|
1729
|
+
props: [
|
|
1730
|
+
styleTags(markdownTags)
|
|
1731
|
+
]
|
|
1732
|
+
}
|
|
1733
|
+
];
|
|
1734
|
+
var markdownHighlightStyle = (readonly) => {
|
|
1735
|
+
return HighlightStyle.define([
|
|
1736
|
+
{
|
|
1737
|
+
tag: [
|
|
1738
|
+
tags.keyword,
|
|
1739
|
+
tags.name,
|
|
1740
|
+
tags.deleted,
|
|
1741
|
+
tags.character,
|
|
1742
|
+
tags.propertyName,
|
|
1743
|
+
tags.macroName,
|
|
1744
|
+
tags.color,
|
|
1745
|
+
tags.constant(tags.name),
|
|
1746
|
+
tags.standard(tags.name),
|
|
1747
|
+
tags.definition(tags.name),
|
|
1748
|
+
tags.separator,
|
|
1749
|
+
tags.typeName,
|
|
1750
|
+
tags.className,
|
|
1751
|
+
tags.number,
|
|
1752
|
+
tags.changed,
|
|
1753
|
+
tags.annotation,
|
|
1754
|
+
tags.modifier,
|
|
1755
|
+
tags.self,
|
|
1756
|
+
tags.namespace,
|
|
1757
|
+
tags.operator,
|
|
1758
|
+
tags.operatorKeyword,
|
|
1759
|
+
tags.escape,
|
|
1760
|
+
tags.regexp,
|
|
1761
|
+
tags.special(tags.string),
|
|
1762
|
+
tags.meta,
|
|
1763
|
+
tags.comment,
|
|
1764
|
+
tags.atom,
|
|
1765
|
+
tags.bool,
|
|
1766
|
+
tags.special(tags.variableName),
|
|
1767
|
+
tags.processingInstruction,
|
|
1768
|
+
tags.string,
|
|
1769
|
+
tags.inserted,
|
|
1770
|
+
tags.invalid
|
|
1771
|
+
],
|
|
1772
|
+
color: "inherit !important"
|
|
1773
|
+
},
|
|
1774
|
+
// Markdown marks.
|
|
1775
|
+
{
|
|
1776
|
+
tag: [
|
|
1777
|
+
tags.meta,
|
|
1778
|
+
tags.processingInstruction,
|
|
1779
|
+
markdownTags.LinkLabel,
|
|
1780
|
+
markdownTags.LinkReference,
|
|
1781
|
+
markdownTags.ListMark
|
|
1782
|
+
],
|
|
1783
|
+
class: mark
|
|
1784
|
+
},
|
|
1785
|
+
// Markdown marks.
|
|
1786
|
+
{
|
|
1787
|
+
tag: [
|
|
1788
|
+
markdownTags.CodeMark,
|
|
1789
|
+
markdownTags.HeaderMark,
|
|
1790
|
+
markdownTags.QuoteMark,
|
|
1791
|
+
markdownTags.EmphasisMark
|
|
1792
|
+
],
|
|
1793
|
+
class: readonly ? "hidden" : mark
|
|
1794
|
+
},
|
|
1795
|
+
// E.g., code block language (after ```).
|
|
1796
|
+
{
|
|
1797
|
+
tag: [
|
|
1798
|
+
tags.function(tags.variableName),
|
|
1799
|
+
tags.labelName
|
|
1800
|
+
],
|
|
1801
|
+
class: readonly ? "hidden" : codeMark
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
tag: [
|
|
1805
|
+
tags.monospace
|
|
1806
|
+
],
|
|
1807
|
+
class: "font-mono"
|
|
1808
|
+
},
|
|
1809
|
+
// Headings.
|
|
1810
|
+
{
|
|
1811
|
+
tag: tags.heading1,
|
|
1812
|
+
class: heading(1, readonly)
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
tag: tags.heading2,
|
|
1816
|
+
class: heading(2, readonly)
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
tag: tags.heading3,
|
|
1820
|
+
class: heading(3, readonly)
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
tag: tags.heading4,
|
|
1824
|
+
class: heading(4, readonly)
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
tag: tags.heading5,
|
|
1828
|
+
class: heading(5, readonly)
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
tag: tags.heading6,
|
|
1832
|
+
class: heading(6, readonly)
|
|
1833
|
+
},
|
|
1834
|
+
// Emphasis.
|
|
1835
|
+
{
|
|
1836
|
+
tag: tags.emphasis,
|
|
1837
|
+
class: italic
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
tag: tags.strong,
|
|
1841
|
+
class: bold
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
tag: tags.strikethrough,
|
|
1845
|
+
class: strikethrough
|
|
1846
|
+
},
|
|
1847
|
+
// Naked URLs.
|
|
1848
|
+
{
|
|
1849
|
+
tag: [
|
|
1850
|
+
markdownTags.URL
|
|
1851
|
+
],
|
|
1852
|
+
class: inlineUrl
|
|
1853
|
+
},
|
|
1854
|
+
// NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
|
|
1855
|
+
// However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
|
|
1856
|
+
// when a language is specified. In this case, the syntax highlighting extensions will colorize
|
|
1857
|
+
// the code, but all other CSS properties will be inherited.
|
|
1858
|
+
// IMPORTANT: Therefore, the fenced code block will use the base editor font.
|
|
1859
|
+
{
|
|
1860
|
+
tag: [
|
|
1861
|
+
markdownTags.CodeText,
|
|
1862
|
+
markdownTags.InlineCode
|
|
1863
|
+
],
|
|
1864
|
+
class: code
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
tag: [
|
|
1868
|
+
markdownTags.QuoteMark
|
|
1869
|
+
],
|
|
1870
|
+
class: blockquote
|
|
1871
|
+
},
|
|
1872
|
+
// TODO(burdon): Replace with extension.
|
|
1873
|
+
// {
|
|
1874
|
+
// tag: [markdownTags.HorizontalRule],
|
|
1875
|
+
// class: mx(horizontalRule, readonly && '-indent-[100rem]'),
|
|
1876
|
+
// },
|
|
1877
|
+
// TODO(burdon): Only if being edited.
|
|
1878
|
+
{
|
|
1879
|
+
tag: [
|
|
1880
|
+
markdownTags.TableCell
|
|
1881
|
+
],
|
|
1882
|
+
class: "font-mono"
|
|
1883
|
+
}
|
|
1884
|
+
], {
|
|
1885
|
+
scope: markdownLanguage,
|
|
1886
|
+
all: {
|
|
1887
|
+
fontFamily: get5(tokens, "fontFamily.body", []).join(",")
|
|
943
1888
|
}
|
|
944
1889
|
});
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1892
|
+
// packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
|
|
1893
|
+
var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
|
|
1894
|
+
return [
|
|
1895
|
+
EditorState.allowMultipleSelections.of(true),
|
|
1896
|
+
EditorState.tabSize.of(2),
|
|
1897
|
+
EditorView10.lineWrapping,
|
|
1898
|
+
customKeymap(),
|
|
1899
|
+
bracketMatching2(),
|
|
1900
|
+
closeBrackets2(),
|
|
1901
|
+
crosshairCursor(),
|
|
1902
|
+
dropCursor(),
|
|
1903
|
+
drawSelection2(),
|
|
1904
|
+
highlightActiveLine(),
|
|
1905
|
+
history(),
|
|
1906
|
+
indentOnInput(),
|
|
1907
|
+
_placeholder && placeholder2(_placeholder),
|
|
1908
|
+
// Main extension.
|
|
1909
|
+
// https://github.com/codemirror/lang-markdown
|
|
1910
|
+
// https://codemirror.net/5/mode/markdown/index.html (demo).
|
|
1911
|
+
markdown({
|
|
1912
|
+
// GRM by default (vs strict CommonMark):
|
|
1913
|
+
// Table, TaskList, Strikethrough, and Autolink.
|
|
1914
|
+
// NOTE: This extends the parser; it doesn't affect rendering.
|
|
1915
|
+
// https://github.github.com/gfm
|
|
1916
|
+
// https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
|
|
1917
|
+
base: markdownLanguage2,
|
|
1918
|
+
// Languages for syntax highlighting fenced code blocks.
|
|
1919
|
+
codeLanguages: languages,
|
|
1920
|
+
// Parser extensions.
|
|
1921
|
+
extensions: [
|
|
1922
|
+
// GFM provided by default.
|
|
1923
|
+
markdownTagsExtensions
|
|
1924
|
+
]
|
|
1925
|
+
}),
|
|
1926
|
+
// https://github.com/codemirror/theme-one-dark
|
|
1927
|
+
themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
|
|
1928
|
+
// Custom styles.
|
|
1929
|
+
syntaxHighlighting2(markdownHighlightStyle(readonly))
|
|
1930
|
+
].filter(Boolean);
|
|
1931
|
+
};
|
|
1932
|
+
var customKeymap = () => keymap4.of([
|
|
1933
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
1934
|
+
indentWithTab,
|
|
1935
|
+
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
1936
|
+
...standardKeymap,
|
|
1937
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
1938
|
+
...historyKeymap,
|
|
1939
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
1940
|
+
...closeBracketsKeymap,
|
|
1941
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
1942
|
+
...searchKeymap
|
|
1943
|
+
]);
|
|
1944
|
+
|
|
1945
|
+
// packages/ui/react-ui-editor/src/extensions/mention.ts
|
|
1946
|
+
import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
|
|
1947
|
+
var mention = ({ onSearch }) => {
|
|
1948
|
+
return autocompletion2({
|
|
1949
|
+
closeOnBlur: false,
|
|
1950
|
+
override: [
|
|
1951
|
+
(context) => {
|
|
1952
|
+
const match = context.matchBefore(/@(\w+)?/);
|
|
1953
|
+
if (!match || match.from === match.to && !context.explicit) {
|
|
1954
|
+
return null;
|
|
1955
|
+
}
|
|
1956
|
+
console.log(match);
|
|
1957
|
+
return {
|
|
1958
|
+
from: match.from,
|
|
1959
|
+
options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
|
|
1960
|
+
label: `@${value}`
|
|
1961
|
+
}))
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
950
1964
|
]
|
|
1965
|
+
});
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
// packages/ui/react-ui-editor/src/extensions/mermaid.ts
|
|
1969
|
+
var mermaid = () => {
|
|
1970
|
+
return [];
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
// packages/ui/react-ui-editor/src/extensions/table.ts
|
|
1974
|
+
import { syntaxTree as syntaxTree3 } from "@codemirror/language";
|
|
1975
|
+
import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
|
|
1976
|
+
import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
|
|
1977
|
+
var table = (options = {}) => {
|
|
1978
|
+
return StateField5.define({
|
|
1979
|
+
create: (state) => update3(state, options),
|
|
1980
|
+
update: (_, tr) => update3(tr.state, options),
|
|
1981
|
+
provide: (field) => EditorView11.decorations.from(field)
|
|
1982
|
+
});
|
|
1983
|
+
};
|
|
1984
|
+
var update3 = (state, options) => {
|
|
1985
|
+
const builder = new RangeSetBuilder3();
|
|
1986
|
+
const cursor = state.selection.main.head;
|
|
1987
|
+
const tables = [];
|
|
1988
|
+
const getTable = () => tables[tables.length - 1];
|
|
1989
|
+
const getRow = () => {
|
|
1990
|
+
const table2 = getTable();
|
|
1991
|
+
return table2.rows?.[table2.rows.length - 1];
|
|
951
1992
|
};
|
|
1993
|
+
syntaxTree3(state).iterate({
|
|
1994
|
+
enter: (node) => {
|
|
1995
|
+
switch (node.name) {
|
|
1996
|
+
case "Table": {
|
|
1997
|
+
tables.push({
|
|
1998
|
+
from: node.from,
|
|
1999
|
+
to: node.to
|
|
2000
|
+
});
|
|
2001
|
+
break;
|
|
2002
|
+
}
|
|
2003
|
+
case "TableHeader": {
|
|
2004
|
+
getTable().header = [];
|
|
2005
|
+
break;
|
|
2006
|
+
}
|
|
2007
|
+
case "TableRow": {
|
|
2008
|
+
(getTable().rows ??= []).push([]);
|
|
2009
|
+
break;
|
|
2010
|
+
}
|
|
2011
|
+
case "TableCell": {
|
|
2012
|
+
const row = getRow();
|
|
2013
|
+
if (row) {
|
|
2014
|
+
row.push(state.sliceDoc(node.from, node.to));
|
|
2015
|
+
} else {
|
|
2016
|
+
getTable().header?.push(state.sliceDoc(node.from, node.to));
|
|
2017
|
+
}
|
|
2018
|
+
break;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
tables.forEach((table2) => {
|
|
2024
|
+
const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
|
|
2025
|
+
hide && builder.add(table2.from, table2.to, Decoration7.replace({
|
|
2026
|
+
block: true,
|
|
2027
|
+
widget: new TableWidget(table2)
|
|
2028
|
+
}));
|
|
2029
|
+
builder.add(table2.from, table2.to, Decoration7.mark({
|
|
2030
|
+
class: "cm-table"
|
|
2031
|
+
}));
|
|
2032
|
+
});
|
|
2033
|
+
return builder.finish();
|
|
2034
|
+
};
|
|
2035
|
+
var TableWidget = class extends WidgetType6 {
|
|
2036
|
+
constructor(_table) {
|
|
2037
|
+
super();
|
|
2038
|
+
this._table = _table;
|
|
2039
|
+
}
|
|
2040
|
+
eq(other) {
|
|
2041
|
+
return this._table.from === other?._table?.from;
|
|
2042
|
+
}
|
|
2043
|
+
toDOM(view) {
|
|
2044
|
+
const table2 = document.createElement("table");
|
|
2045
|
+
{
|
|
2046
|
+
const header = table2.appendChild(document.createElement("thead"));
|
|
2047
|
+
const tr = header.appendChild(document.createElement("tr"));
|
|
2048
|
+
this._table.header?.forEach((cell) => {
|
|
2049
|
+
const th = document.createElement("th");
|
|
2050
|
+
th.setAttribute("class", "cm-table-head");
|
|
2051
|
+
tr.appendChild(th).textContent = cell;
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
{
|
|
2055
|
+
const body = table2.appendChild(document.createElement("tbody"));
|
|
2056
|
+
this._table.rows?.forEach((row) => {
|
|
2057
|
+
const tr = body.appendChild(document.createElement("tr"));
|
|
2058
|
+
row.forEach((cell) => {
|
|
2059
|
+
const td = document.createElement("td");
|
|
2060
|
+
td.setAttribute("class", "cm-table-cell");
|
|
2061
|
+
tr.appendChild(td).textContent = cell;
|
|
2062
|
+
});
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
return table2;
|
|
2066
|
+
}
|
|
952
2067
|
};
|
|
953
|
-
var reconcileAnnotationType = Annotation.define();
|
|
954
|
-
var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
|
|
955
2068
|
|
|
956
|
-
// packages/ui/react-ui-editor/src/
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
2069
|
+
// packages/ui/react-ui-editor/src/extensions/tasklist.ts
|
|
2070
|
+
import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
|
|
2071
|
+
var styles5 = EditorView12.baseTheme({
|
|
2072
|
+
"& .cm-task-item": {
|
|
2073
|
+
paddingLeft: "4px",
|
|
2074
|
+
paddingRight: "8px"
|
|
2075
|
+
}
|
|
2076
|
+
});
|
|
2077
|
+
var CheckboxWidget = class extends WidgetType7 {
|
|
2078
|
+
constructor(_pos, _checked, _indent, _onCheck) {
|
|
2079
|
+
super();
|
|
2080
|
+
this._pos = _pos;
|
|
2081
|
+
this._checked = _checked;
|
|
2082
|
+
this._indent = _indent;
|
|
2083
|
+
this._onCheck = _onCheck;
|
|
2084
|
+
}
|
|
2085
|
+
eq(other) {
|
|
2086
|
+
return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
|
|
2087
|
+
}
|
|
2088
|
+
toDOM(view) {
|
|
2089
|
+
const wrap = document.createElement("span");
|
|
2090
|
+
wrap.className = "cm-task-item";
|
|
2091
|
+
wrap.setAttribute("aria-hidden", "true");
|
|
2092
|
+
wrap.style.setProperty("margin-left", this._indent * 24 + "px");
|
|
2093
|
+
const input = wrap.appendChild(document.createElement("input"));
|
|
2094
|
+
input.type = "checkbox";
|
|
2095
|
+
input.checked = this._checked;
|
|
2096
|
+
if (!this._onCheck) {
|
|
2097
|
+
input.setAttribute("disabled", "true");
|
|
2098
|
+
}
|
|
2099
|
+
if (this._onCheck) {
|
|
2100
|
+
input.onchange = (event) => {
|
|
2101
|
+
this._onCheck?.(event.target.checked);
|
|
2102
|
+
return true;
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
return wrap;
|
|
2106
|
+
}
|
|
2107
|
+
ignoreEvent() {
|
|
2108
|
+
return false;
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
var tasklist = (options = {}) => {
|
|
2112
|
+
const taskMatcher = new MatchDecorator3({
|
|
2113
|
+
regexp: /^(\s*)- \[([ xX])\]\s/g,
|
|
2114
|
+
decoration: (match, view, pos) => {
|
|
2115
|
+
const indent = Math.floor(match[1].length / 2);
|
|
2116
|
+
const checked = match[2] === "x" || match[2] === "X";
|
|
2117
|
+
return Decoration8.replace({
|
|
2118
|
+
widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
|
|
2119
|
+
const idx = pos + match[0].indexOf("[") + 1;
|
|
2120
|
+
view.dispatch({
|
|
2121
|
+
changes: {
|
|
2122
|
+
from: idx,
|
|
2123
|
+
to: idx + 1,
|
|
2124
|
+
insert: checked2 ? "x" : " "
|
|
2125
|
+
},
|
|
2126
|
+
// TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
|
|
2127
|
+
selection: {
|
|
2128
|
+
anchor: idx + 3
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
})
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
const tasks = ViewPlugin6.fromClass(class {
|
|
2136
|
+
constructor(view) {
|
|
2137
|
+
this.tasks = taskMatcher.createDeco(view);
|
|
966
2138
|
}
|
|
967
|
-
|
|
2139
|
+
update(update4) {
|
|
2140
|
+
this.tasks = taskMatcher.updateDeco(update4, this.tasks);
|
|
2141
|
+
}
|
|
2142
|
+
}, {
|
|
2143
|
+
decorations: (value) => value.tasks,
|
|
2144
|
+
provide: (plugin) => EditorView12.atomicRanges.of((view) => {
|
|
2145
|
+
return view.plugin(plugin)?.tasks || Decoration8.none;
|
|
2146
|
+
})
|
|
2147
|
+
});
|
|
2148
|
+
return [
|
|
2149
|
+
tasks,
|
|
2150
|
+
styles5
|
|
2151
|
+
];
|
|
2152
|
+
};
|
|
2153
|
+
|
|
2154
|
+
// packages/ui/react-ui-editor/src/extensions/typewriter.ts
|
|
2155
|
+
import { keymap as keymap5 } from "@codemirror/view";
|
|
2156
|
+
var defaultItems = [
|
|
2157
|
+
"hello world!",
|
|
2158
|
+
"this is a test.",
|
|
2159
|
+
"this is [DXOS](https://dxos.org)"
|
|
2160
|
+
];
|
|
2161
|
+
var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
|
|
2162
|
+
let t;
|
|
2163
|
+
let idx = 0;
|
|
2164
|
+
return [
|
|
2165
|
+
keymap5.of([
|
|
2166
|
+
{
|
|
2167
|
+
// Reset.
|
|
2168
|
+
key: "alt-meta-'",
|
|
2169
|
+
run: (view) => {
|
|
2170
|
+
clearTimeout(t);
|
|
2171
|
+
idx = 0;
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2174
|
+
},
|
|
2175
|
+
{
|
|
2176
|
+
// Next prompt.
|
|
2177
|
+
// TODO(burdon): Press 1-9 to select prompt?
|
|
2178
|
+
key: "shift-meta-'",
|
|
2179
|
+
run: (view) => {
|
|
2180
|
+
clearTimeout(t);
|
|
2181
|
+
const text = items[idx++];
|
|
2182
|
+
if (idx === items?.length) {
|
|
2183
|
+
idx = 0;
|
|
2184
|
+
}
|
|
2185
|
+
let i = 0;
|
|
2186
|
+
const insert = (d = 0) => {
|
|
2187
|
+
t = setTimeout(() => {
|
|
2188
|
+
const pos = view.state.selection.main.head;
|
|
2189
|
+
view.dispatch({
|
|
2190
|
+
changes: {
|
|
2191
|
+
from: pos,
|
|
2192
|
+
insert: text[i++]
|
|
2193
|
+
},
|
|
2194
|
+
selection: {
|
|
2195
|
+
anchor: pos + 1
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
if (i < text.length) {
|
|
2199
|
+
insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
|
|
2200
|
+
}
|
|
2201
|
+
}, d);
|
|
2202
|
+
};
|
|
2203
|
+
insert();
|
|
2204
|
+
return true;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
])
|
|
2208
|
+
];
|
|
2209
|
+
};
|
|
2210
|
+
|
|
2211
|
+
// packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
|
|
2212
|
+
import { yCollab } from "y-codemirror.next";
|
|
2213
|
+
|
|
2214
|
+
// packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
|
|
2215
|
+
import * as Y from "yjs";
|
|
2216
|
+
import { arrayToString, stringToArray } from "@dxos/util";
|
|
2217
|
+
var cursorConverter2 = (text) => ({
|
|
2218
|
+
toCursor: (index, assoc) => {
|
|
2219
|
+
return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
|
|
2220
|
+
},
|
|
2221
|
+
fromCursor: (cursor) => {
|
|
2222
|
+
return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
|
|
2226
|
+
// packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
|
|
2227
|
+
var yjs = (content, awareness2) => [
|
|
2228
|
+
CursorConverter.of(cursorConverter2(content)),
|
|
2229
|
+
yCollab(content, awareness2)
|
|
2230
|
+
];
|
|
2231
|
+
|
|
2232
|
+
// packages/ui/react-ui-editor/src/themes/default.ts
|
|
2233
|
+
import get6 from "lodash.get";
|
|
2234
|
+
var defaultTheme = {
|
|
2235
|
+
//
|
|
2236
|
+
// Main layout:
|
|
2237
|
+
// https://codemirror.net/examples/styling
|
|
2238
|
+
//
|
|
2239
|
+
// <div class="cm-editor [cm-focused] [generated classes]">
|
|
2240
|
+
// <div class="cm-scroller">
|
|
2241
|
+
// <div class="cm-gutters">
|
|
2242
|
+
// <div class="cm-gutter [...]">
|
|
2243
|
+
// <div class="cm-gutterElement">...</div>
|
|
2244
|
+
// </div>
|
|
2245
|
+
// </div>
|
|
2246
|
+
// <div class="cm-content" role="textbox" contenteditable="true">
|
|
2247
|
+
// <div class="cm-line"></div>
|
|
2248
|
+
// </div>
|
|
2249
|
+
// <div class="cm-selectionLayer">
|
|
2250
|
+
// <div class="cm-selectionBackground"></div>
|
|
2251
|
+
// </div>
|
|
2252
|
+
// <div class="cm-cursorLayer">
|
|
2253
|
+
// <div class="cm-cursor"></div>
|
|
2254
|
+
// </div>
|
|
2255
|
+
// </div>
|
|
2256
|
+
// </div>
|
|
2257
|
+
//
|
|
2258
|
+
"&": {},
|
|
2259
|
+
"&.cm-focused": {
|
|
2260
|
+
outline: "none"
|
|
2261
|
+
},
|
|
2262
|
+
"& .cm-scroller": {
|
|
2263
|
+
overflow: "visible",
|
|
2264
|
+
fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
|
|
2265
|
+
},
|
|
2266
|
+
"& .cm-content": {
|
|
2267
|
+
padding: 0,
|
|
2268
|
+
// NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
|
|
2269
|
+
fontSize: "16px"
|
|
2270
|
+
},
|
|
2271
|
+
"&light .cm-content": {
|
|
2272
|
+
color: get6(tokens, "extend.colors.neutral.900", "black"),
|
|
2273
|
+
caretColor: "black"
|
|
2274
|
+
},
|
|
2275
|
+
"&dark .cm-content": {
|
|
2276
|
+
color: get6(tokens, "extend.colors.neutral.100", "white"),
|
|
2277
|
+
caretColor: "white"
|
|
2278
|
+
},
|
|
2279
|
+
//
|
|
2280
|
+
// Cursor
|
|
2281
|
+
//
|
|
2282
|
+
"&light .cm-cursor, &light .cm-dropCursor": {
|
|
2283
|
+
borderLeft: "2px solid black"
|
|
2284
|
+
},
|
|
2285
|
+
"&dark .cm-cursor, &dark .cm-dropCursor": {
|
|
2286
|
+
borderLeft: "2px solid white"
|
|
2287
|
+
},
|
|
2288
|
+
"& .cm-placeholder": {
|
|
2289
|
+
fontWeight: 100
|
|
2290
|
+
},
|
|
2291
|
+
//
|
|
2292
|
+
// line
|
|
2293
|
+
//
|
|
2294
|
+
"& .cm-line": {
|
|
2295
|
+
paddingInline: 0
|
|
2296
|
+
},
|
|
2297
|
+
"& .cm-activeLine": {
|
|
2298
|
+
background: "inherit"
|
|
2299
|
+
},
|
|
2300
|
+
//
|
|
2301
|
+
// Selection
|
|
2302
|
+
//
|
|
2303
|
+
"&light .cm-selectionBackground": {
|
|
2304
|
+
background: get6(tokens, "extend.colors.primary.100")
|
|
2305
|
+
},
|
|
2306
|
+
"&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
|
|
2307
|
+
background: get6(tokens, "extend.colors.primary.200")
|
|
2308
|
+
},
|
|
2309
|
+
"&dark .cm-selectionBackground": {
|
|
2310
|
+
background: get6(tokens, "extend.colors.primary.700")
|
|
2311
|
+
},
|
|
2312
|
+
"&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
|
|
2313
|
+
background: get6(tokens, "extend.colors.primary.600")
|
|
2314
|
+
},
|
|
2315
|
+
//
|
|
2316
|
+
// Search
|
|
2317
|
+
//
|
|
2318
|
+
"&light .cm-searchMatch": {
|
|
2319
|
+
backgroundColor: get6(tokens, "extend.colors.yellow.100")
|
|
2320
|
+
},
|
|
2321
|
+
"&dark .cm-searchMatch": {
|
|
2322
|
+
backgroundColor: get6(tokens, "extend.colors.yellow.700")
|
|
2323
|
+
},
|
|
2324
|
+
//
|
|
2325
|
+
// collaboration
|
|
2326
|
+
// TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
|
|
2327
|
+
//
|
|
2328
|
+
"&light .cm-ySelection, &light .cm-yLineSelection": {
|
|
2329
|
+
mixBlendMode: "multiply"
|
|
2330
|
+
},
|
|
2331
|
+
"&dark .cm-ySelection, &dark .cm-yLineSelection": {
|
|
2332
|
+
mixBlendMode: "screen"
|
|
2333
|
+
},
|
|
2334
|
+
"& .cm-ySelectionInfo": {
|
|
2335
|
+
padding: "2px 4px",
|
|
2336
|
+
marginBlockStart: "-4px"
|
|
2337
|
+
},
|
|
2338
|
+
"& .cm-ySelection, & .cm-selectionMatch": {
|
|
2339
|
+
paddingBlockStart: ".15em",
|
|
2340
|
+
paddingBlockEnd: ".15em"
|
|
2341
|
+
},
|
|
2342
|
+
"& .cm-ySelectionCaret": {
|
|
2343
|
+
display: "inline-block",
|
|
2344
|
+
insetBlockStart: ".1em",
|
|
2345
|
+
blockSize: "1.4em",
|
|
2346
|
+
verticalAlign: "top"
|
|
2347
|
+
},
|
|
2348
|
+
"& .cm-yLineSelection": {
|
|
2349
|
+
margin: 0
|
|
2350
|
+
},
|
|
2351
|
+
//
|
|
2352
|
+
// link
|
|
2353
|
+
//
|
|
2354
|
+
"& .cm-link": {
|
|
2355
|
+
color: get6(tokens, "extend.colors.primary.500"),
|
|
2356
|
+
textDecorationLine: "underline",
|
|
2357
|
+
textDecorationThickness: "1px",
|
|
2358
|
+
textUnderlineOffset: "2px",
|
|
2359
|
+
borderRadius: ".125rem",
|
|
2360
|
+
fontFamily: get6(tokens, "fontFamily.body", []).join(",")
|
|
2361
|
+
},
|
|
2362
|
+
//
|
|
2363
|
+
// tooltip
|
|
2364
|
+
//
|
|
2365
|
+
"& .cm-tooltip": {
|
|
2366
|
+
border: "none"
|
|
2367
|
+
},
|
|
2368
|
+
"& .cm-tooltip-below": {},
|
|
2369
|
+
//
|
|
2370
|
+
// autocomplete
|
|
2371
|
+
//
|
|
2372
|
+
"& .cm-tooltip-autocomplete": {
|
|
2373
|
+
marginTop: "4px",
|
|
2374
|
+
marginLeft: "-3px"
|
|
2375
|
+
},
|
|
2376
|
+
"& .cm-tooltip-autocomplete ul li": {},
|
|
2377
|
+
"& .cm-tooltip-autocomplete ul li[aria-selected]": {},
|
|
2378
|
+
"& .cm-completionIcon": {
|
|
2379
|
+
display: "none"
|
|
2380
|
+
},
|
|
2381
|
+
"& .cm-completionLabel": {
|
|
2382
|
+
fontFamily: get6(tokens, "fontFamily.body", []).join(",")
|
|
2383
|
+
},
|
|
2384
|
+
"& .cm-completionMatchedText": {
|
|
2385
|
+
textDecoration: "none"
|
|
2386
|
+
},
|
|
2387
|
+
//
|
|
2388
|
+
// widgets
|
|
2389
|
+
//
|
|
2390
|
+
"& .cm-widgetBuffer": {
|
|
2391
|
+
display: "none",
|
|
2392
|
+
height: 0
|
|
2393
|
+
},
|
|
2394
|
+
//
|
|
2395
|
+
// table
|
|
2396
|
+
//
|
|
2397
|
+
"& .cm-table *": {
|
|
2398
|
+
fontFamily: `${get6(tokens, "fontFamily.mono", []).join(",")} !important`,
|
|
2399
|
+
textDecoration: "none !important"
|
|
2400
|
+
},
|
|
2401
|
+
"& .cm-table-head": {
|
|
2402
|
+
padding: "2px 16px 2px 0px",
|
|
2403
|
+
borderBottom: `1px solid ${get6(tokens, "extend.colors.neutral.500")}`,
|
|
2404
|
+
fontWeight: 100,
|
|
2405
|
+
textAlign: "left",
|
|
2406
|
+
color: get6(tokens, "extend.colors.neutral.500")
|
|
2407
|
+
},
|
|
2408
|
+
"& .cm-table-cell": {
|
|
2409
|
+
padding: "2px 16px 2px 0px"
|
|
2410
|
+
},
|
|
2411
|
+
//
|
|
2412
|
+
// image
|
|
2413
|
+
//
|
|
2414
|
+
"& .cm-image": {
|
|
2415
|
+
margin: "0.5rem 0"
|
|
2416
|
+
},
|
|
2417
|
+
//
|
|
2418
|
+
// font size
|
|
2419
|
+
// 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.
|
|
2420
|
+
//
|
|
2421
|
+
...Object.keys(get6(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
|
|
2422
|
+
const height = get6(tokens, [
|
|
2423
|
+
"extend",
|
|
2424
|
+
"fontSize",
|
|
2425
|
+
fontSize,
|
|
2426
|
+
1,
|
|
2427
|
+
"lineHeight"
|
|
2428
|
+
]);
|
|
2429
|
+
acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
|
|
2430
|
+
height
|
|
2431
|
+
};
|
|
2432
|
+
acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
|
|
2433
|
+
height
|
|
2434
|
+
};
|
|
2435
|
+
acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
|
|
2436
|
+
height
|
|
2437
|
+
};
|
|
2438
|
+
return acc;
|
|
2439
|
+
}, {}),
|
|
2440
|
+
/**
|
|
2441
|
+
* Panels
|
|
2442
|
+
* TODO(burdon): Needs styling attention (esp. dark mode).
|
|
2443
|
+
* https://github.com/codemirror/search/blob/main/src/search.ts#L745
|
|
2444
|
+
*
|
|
2445
|
+
* Find/replace panel.
|
|
2446
|
+
* <div class="cm-announced">...</div>
|
|
2447
|
+
* <div class="cm-scroller">...</div>
|
|
2448
|
+
* <div class="cm-panels cm-panels-bottom">
|
|
2449
|
+
* <div class="cm-search cm-panel">
|
|
2450
|
+
* <input class="cm-textfield" />
|
|
2451
|
+
* <button class="cm-button">...</button>
|
|
2452
|
+
* <label><input type="checkbox" />...</label>
|
|
2453
|
+
* </div>
|
|
2454
|
+
* </div
|
|
2455
|
+
*/
|
|
2456
|
+
"& .cm-panels": {
|
|
2457
|
+
border: `1px solid ${get6(tokens, "extend.colors.neutral.200")}`
|
|
2458
|
+
},
|
|
2459
|
+
"& .cm-panel": {
|
|
2460
|
+
background: get6(tokens, "extend.colors.neutral.50"),
|
|
2461
|
+
fontFamily: get6(tokens, "fontFamily.body", []).join(",")
|
|
2462
|
+
},
|
|
2463
|
+
"& .cm-button": {
|
|
2464
|
+
margin: "4px",
|
|
2465
|
+
fontFamily: get6(tokens, "fontFamily.body", []).join(","),
|
|
2466
|
+
background: get6(tokens, "extend.colors.neutral.100"),
|
|
2467
|
+
backgroundImage: "none",
|
|
2468
|
+
border: "none",
|
|
2469
|
+
"&:hover": {
|
|
2470
|
+
background: get6(tokens, "extend.colors.neutral.200")
|
|
2471
|
+
},
|
|
2472
|
+
"&:active": {
|
|
2473
|
+
background: get6(tokens, "extend.colors.neutral.300"),
|
|
2474
|
+
backgroundImage: "none"
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
2477
|
+
"& .cm-panel input[type=checkbox]": {
|
|
2478
|
+
marginRight: "0.4rem !important"
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
var textTheme = {
|
|
2482
|
+
"& .cm-scroller": {
|
|
2483
|
+
fontFamily: get6(tokens, "fontFamily.body", []).join(",")
|
|
2484
|
+
},
|
|
2485
|
+
"& .cm-placeholder": {
|
|
2486
|
+
fontFamily: get6(tokens, "fontFamily.body", []).join(",")
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
var markdownTheme = {
|
|
2490
|
+
// NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
|
|
2491
|
+
"& .cm-placeholder": {
|
|
2492
|
+
fontFamily: get6(tokens, "fontFamily.body", []).join(",")
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
var codeTheme = {
|
|
2496
|
+
"& .cm-scroller": {
|
|
2497
|
+
fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
|
|
2498
|
+
},
|
|
2499
|
+
"& .cm-placeholder": {
|
|
2500
|
+
fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
|
|
2505
|
+
var EditorModes = [
|
|
2506
|
+
"default",
|
|
2507
|
+
"vim"
|
|
2508
|
+
];
|
|
2509
|
+
var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
|
|
2510
|
+
const { themeMode } = useThemeContext();
|
|
2511
|
+
const tabsterDOMAttribute = useFocusableGroup({
|
|
2512
|
+
tabBehavior: "limited"
|
|
2513
|
+
});
|
|
2514
|
+
const [root, setRoot] = useState(null);
|
|
2515
|
+
const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
|
|
2516
|
+
useImperativeHandle(forwardedRef, () => ({
|
|
2517
|
+
root,
|
|
2518
|
+
state,
|
|
2519
|
+
view
|
|
2520
|
+
}), [
|
|
2521
|
+
view,
|
|
2522
|
+
state,
|
|
2523
|
+
root
|
|
2524
|
+
]);
|
|
2525
|
+
const { awareness: awareness2, peer } = model;
|
|
968
2526
|
useEffect(() => {
|
|
969
|
-
if (
|
|
970
|
-
|
|
2527
|
+
if (awareness2 && peer) {
|
|
2528
|
+
awareness2.setLocalStateField("user", {
|
|
971
2529
|
name: peer.name ?? generateName(peer.id),
|
|
972
2530
|
color: getColorForValue({
|
|
973
2531
|
value: peer.id,
|
|
@@ -981,69 +2539,333 @@ var useCollaboration = (model, themeMode) => {
|
|
|
981
2539
|
});
|
|
982
2540
|
}
|
|
983
2541
|
}, [
|
|
984
|
-
|
|
2542
|
+
awareness2,
|
|
985
2543
|
peer,
|
|
986
2544
|
themeMode
|
|
987
2545
|
]);
|
|
988
|
-
|
|
2546
|
+
useEffect(() => {
|
|
2547
|
+
if (view && comments2?.length) {
|
|
2548
|
+
view.dispatch({
|
|
2549
|
+
effects: setCommentRange.of({
|
|
2550
|
+
model,
|
|
2551
|
+
comments: comments2
|
|
2552
|
+
})
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
}, [
|
|
2556
|
+
view,
|
|
2557
|
+
comments2
|
|
2558
|
+
]);
|
|
2559
|
+
useEffect(() => {
|
|
2560
|
+
if (!root) {
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
const state2 = EditorState2.create({
|
|
2564
|
+
doc: model.text(),
|
|
2565
|
+
extensions: [
|
|
2566
|
+
readonly && EditorState2.readOnly.of(readonly),
|
|
2567
|
+
// TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
|
|
2568
|
+
editorMode === "vim" && vim(),
|
|
2569
|
+
// Theme.
|
|
2570
|
+
// TODO(burdon): Make theme configurable.
|
|
2571
|
+
EditorView13.baseTheme(defaultTheme),
|
|
2572
|
+
EditorView13.theme(slots?.editor?.theme ?? {}),
|
|
2573
|
+
EditorView13.darkTheme.of(themeMode === "dark"),
|
|
2574
|
+
// Storage and replication.
|
|
2575
|
+
model.extension,
|
|
2576
|
+
// Custom.
|
|
2577
|
+
...extensions
|
|
2578
|
+
].filter(Boolean)
|
|
2579
|
+
});
|
|
2580
|
+
view?.destroy();
|
|
2581
|
+
setEditor({
|
|
2582
|
+
state: state2,
|
|
2583
|
+
view: new EditorView13({
|
|
2584
|
+
state: state2,
|
|
2585
|
+
parent: root
|
|
2586
|
+
})
|
|
2587
|
+
});
|
|
2588
|
+
return () => {
|
|
2589
|
+
view?.destroy();
|
|
2590
|
+
setEditor(void 0);
|
|
2591
|
+
};
|
|
2592
|
+
}, [
|
|
2593
|
+
root,
|
|
2594
|
+
model,
|
|
2595
|
+
readonly,
|
|
2596
|
+
editorMode,
|
|
2597
|
+
themeMode
|
|
2598
|
+
]);
|
|
2599
|
+
const handleKeyUp = useCallback((event) => {
|
|
2600
|
+
const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
|
|
2601
|
+
switch (key) {
|
|
2602
|
+
case "Enter": {
|
|
2603
|
+
view?.contentDOM.focus();
|
|
2604
|
+
break;
|
|
2605
|
+
}
|
|
2606
|
+
case "Escape": {
|
|
2607
|
+
editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
|
|
2608
|
+
break;
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}, [
|
|
2612
|
+
view,
|
|
2613
|
+
editorMode
|
|
2614
|
+
]);
|
|
2615
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
2616
|
+
key: model.id,
|
|
2617
|
+
ref: setRoot,
|
|
2618
|
+
tabIndex: 0,
|
|
2619
|
+
...slots?.root,
|
|
2620
|
+
...editorMode !== "vim" && tabsterDOMAttribute,
|
|
2621
|
+
onKeyUp: handleKeyUp
|
|
2622
|
+
});
|
|
2623
|
+
});
|
|
2624
|
+
var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
2625
|
+
const { themeMode } = useThemeContext();
|
|
2626
|
+
const slots = defaultsDeep2({}, _slots, defaultTextSlots);
|
|
2627
|
+
return /* @__PURE__ */ React.createElement(BaseTextEditor, {
|
|
2628
|
+
ref: forwardedRef,
|
|
2629
|
+
readonly,
|
|
2630
|
+
extensions: [
|
|
2631
|
+
basicBundle({
|
|
2632
|
+
readonly,
|
|
2633
|
+
themeMode,
|
|
2634
|
+
placeholder: slots?.editor?.placeholder
|
|
2635
|
+
}),
|
|
2636
|
+
...extensions
|
|
2637
|
+
],
|
|
2638
|
+
slots,
|
|
2639
|
+
...props
|
|
2640
|
+
});
|
|
2641
|
+
});
|
|
2642
|
+
var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
2643
|
+
const { themeMode } = useThemeContext();
|
|
2644
|
+
const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
|
|
2645
|
+
return /* @__PURE__ */ React.createElement(BaseTextEditor, {
|
|
2646
|
+
ref: forwardedRef,
|
|
2647
|
+
readonly,
|
|
2648
|
+
extensions: [
|
|
2649
|
+
markdownBundle({
|
|
2650
|
+
readonly,
|
|
2651
|
+
themeMode,
|
|
2652
|
+
placeholder: slots?.editor?.placeholder
|
|
2653
|
+
}),
|
|
2654
|
+
...extensions
|
|
2655
|
+
],
|
|
2656
|
+
slots,
|
|
2657
|
+
...props
|
|
2658
|
+
});
|
|
2659
|
+
});
|
|
2660
|
+
var defaultSlots = {
|
|
2661
|
+
root: {
|
|
2662
|
+
className: mx3("p-2", inputSurface)
|
|
2663
|
+
}
|
|
2664
|
+
};
|
|
2665
|
+
var defaultTextSlots = {
|
|
2666
|
+
...defaultSlots,
|
|
2667
|
+
editor: {
|
|
2668
|
+
theme: textTheme
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
var defaultMarkdownSlots = {
|
|
2672
|
+
...defaultSlots,
|
|
2673
|
+
editor: {
|
|
2674
|
+
theme: markdownTheme
|
|
2675
|
+
}
|
|
989
2676
|
};
|
|
990
2677
|
|
|
991
|
-
// packages/ui/react-ui-editor/src/hooks/
|
|
992
|
-
import
|
|
993
|
-
import { getRawDoc
|
|
2678
|
+
// packages/ui/react-ui-editor/src/hooks/automerge.ts
|
|
2679
|
+
import get7 from "lodash.get";
|
|
2680
|
+
import { getRawDoc } from "@dxos/echo-schema";
|
|
2681
|
+
import { isNotNullOrUndefined } from "@dxos/util";
|
|
994
2682
|
|
|
995
|
-
// packages/ui/react-ui-editor/src/hooks/
|
|
996
|
-
import
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
{
|
|
1011
|
-
color: "#ecd444",
|
|
1012
|
-
light: "#ecd44433"
|
|
1013
|
-
},
|
|
1014
|
-
{
|
|
1015
|
-
color: "#ee6352",
|
|
1016
|
-
light: "#ee635233"
|
|
1017
|
-
},
|
|
1018
|
-
{
|
|
1019
|
-
color: "#9ac2c9",
|
|
1020
|
-
light: "#9ac2c933"
|
|
1021
|
-
},
|
|
1022
|
-
{
|
|
1023
|
-
color: "#8acb88",
|
|
1024
|
-
light: "#8acb8833"
|
|
1025
|
-
},
|
|
1026
|
-
{
|
|
1027
|
-
color: "#1be7ff",
|
|
1028
|
-
light: "#1be7ff33"
|
|
2683
|
+
// packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
|
|
2684
|
+
import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
|
|
2685
|
+
import { Context as Context2 } from "@dxos/context";
|
|
2686
|
+
import { invariant as invariant3 } from "@dxos/invariant";
|
|
2687
|
+
import { log as log3 } from "@dxos/log";
|
|
2688
|
+
var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
|
|
2689
|
+
var DEBOUNCE_INTERVAL = 100;
|
|
2690
|
+
var SpaceAwarenessProvider = class {
|
|
2691
|
+
constructor(params) {
|
|
2692
|
+
this.remoteStateChange = new Event2();
|
|
2693
|
+
this._remoteStates = /* @__PURE__ */ new Map();
|
|
2694
|
+
this._space = params.space;
|
|
2695
|
+
this._channel = params.channel;
|
|
2696
|
+
this._peerId = params.peerId;
|
|
2697
|
+
this._info = params.info;
|
|
1029
2698
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
2699
|
+
open() {
|
|
2700
|
+
this._ctx = new Context2();
|
|
2701
|
+
this._postTask = new DeferredTask(this._ctx, async () => {
|
|
2702
|
+
if (this._localState) {
|
|
2703
|
+
await this._space.postMessage(this._channel, {
|
|
2704
|
+
kind: "post",
|
|
2705
|
+
state: this._localState
|
|
2706
|
+
});
|
|
2707
|
+
await sleep(DEBOUNCE_INTERVAL);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
const unsubscribe = this._space.listen(this._channel, (message) => {
|
|
2711
|
+
switch (message.payload.kind) {
|
|
2712
|
+
case "query": {
|
|
2713
|
+
this._handleQueryMessage();
|
|
2714
|
+
break;
|
|
2715
|
+
}
|
|
2716
|
+
case "post": {
|
|
2717
|
+
this._handlePostMessage(message.payload);
|
|
2718
|
+
break;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
this._ctx.onDispose(unsubscribe);
|
|
2723
|
+
void this._space.postMessage(this._channel, {
|
|
2724
|
+
kind: "query"
|
|
2725
|
+
}).catch((err) => {
|
|
2726
|
+
log3.debug("failed to query awareness", {
|
|
2727
|
+
err
|
|
2728
|
+
}, {
|
|
2729
|
+
F: __dxlog_file4,
|
|
2730
|
+
L: 85,
|
|
2731
|
+
S: this,
|
|
2732
|
+
C: (f, a) => f(...a)
|
|
2733
|
+
});
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
close() {
|
|
2737
|
+
void this._ctx?.dispose();
|
|
2738
|
+
this._ctx = void 0;
|
|
2739
|
+
this._postTask = void 0;
|
|
2740
|
+
}
|
|
2741
|
+
update(position) {
|
|
2742
|
+
invariant3(this._postTask, void 0, {
|
|
2743
|
+
F: __dxlog_file4,
|
|
2744
|
+
L: 96,
|
|
2745
|
+
S: this,
|
|
2746
|
+
A: [
|
|
2747
|
+
"this._postTask",
|
|
2748
|
+
""
|
|
2749
|
+
]
|
|
2750
|
+
});
|
|
2751
|
+
this._localState = {
|
|
2752
|
+
peerId: this._peerId,
|
|
2753
|
+
position,
|
|
2754
|
+
info: this._info
|
|
2755
|
+
};
|
|
2756
|
+
this._postTask.schedule();
|
|
2757
|
+
}
|
|
2758
|
+
getRemoteStates() {
|
|
2759
|
+
return Array.from(this._remoteStates.values());
|
|
2760
|
+
}
|
|
2761
|
+
_handleQueryMessage() {
|
|
2762
|
+
invariant3(this._postTask, void 0, {
|
|
2763
|
+
F: __dxlog_file4,
|
|
2764
|
+
L: 111,
|
|
2765
|
+
S: this,
|
|
2766
|
+
A: [
|
|
2767
|
+
"this._postTask",
|
|
2768
|
+
""
|
|
2769
|
+
]
|
|
2770
|
+
});
|
|
2771
|
+
this._postTask.schedule();
|
|
2772
|
+
}
|
|
2773
|
+
_handlePostMessage(message) {
|
|
2774
|
+
invariant3(message.kind === "post", void 0, {
|
|
2775
|
+
F: __dxlog_file4,
|
|
2776
|
+
L: 116,
|
|
2777
|
+
S: this,
|
|
2778
|
+
A: [
|
|
2779
|
+
"message.kind === 'post'",
|
|
2780
|
+
""
|
|
2781
|
+
]
|
|
2782
|
+
});
|
|
2783
|
+
this._remoteStates.set(message.state.peerId, message.state);
|
|
2784
|
+
this.remoteStateChange.emit();
|
|
2785
|
+
}
|
|
2786
|
+
};
|
|
2787
|
+
|
|
2788
|
+
// packages/ui/react-ui-editor/src/hooks/useTextModel.ts
|
|
2789
|
+
import { StateField as StateField6 } from "@codemirror/state";
|
|
2790
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
2791
|
+
import { isAutomergeObject } from "@dxos/react-client/echo";
|
|
1032
2792
|
|
|
1033
|
-
// packages/ui/react-ui-editor/src/hooks/yjs
|
|
2793
|
+
// packages/ui/react-ui-editor/src/hooks/yjs.ts
|
|
1034
2794
|
import * as decoding from "lib0/decoding";
|
|
1035
2795
|
import * as encoding from "lib0/encoding";
|
|
1036
2796
|
import { Observable } from "lib0/observable";
|
|
1037
|
-
import * as
|
|
1038
|
-
import
|
|
1039
|
-
|
|
2797
|
+
import * as YP from "y-protocols/awareness";
|
|
2798
|
+
import * as Y2 from "yjs";
|
|
2799
|
+
import { invariant as invariant4 } from "@dxos/invariant";
|
|
2800
|
+
import { log as log4 } from "@dxos/log";
|
|
2801
|
+
import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
|
|
2802
|
+
var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
|
|
2803
|
+
var createYjsModel = ({ identity, space, text }) => {
|
|
2804
|
+
invariant4(text?.doc && text?.content, void 0, {
|
|
2805
|
+
F: __dxlog_file5,
|
|
2806
|
+
L: 23,
|
|
2807
|
+
S: void 0,
|
|
2808
|
+
A: [
|
|
2809
|
+
"text?.doc && text?.content",
|
|
2810
|
+
""
|
|
2811
|
+
]
|
|
2812
|
+
});
|
|
2813
|
+
const provider = space ? new YAwarenessProvider({
|
|
2814
|
+
space,
|
|
2815
|
+
doc: text.doc,
|
|
2816
|
+
channel: `yjs.awareness.${text.id}`
|
|
2817
|
+
}) : void 0;
|
|
2818
|
+
const model = {
|
|
2819
|
+
id: text.doc.guid,
|
|
2820
|
+
content: text.content,
|
|
2821
|
+
text: () => text.content.toString(),
|
|
2822
|
+
// https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
|
|
2823
|
+
// TODO(dmaretskyi): Refactor as cursor-converter.
|
|
2824
|
+
getCursorFromRange: (range) => {
|
|
2825
|
+
const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
|
|
2826
|
+
const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
|
|
2827
|
+
return [
|
|
2828
|
+
arrayToString2(from),
|
|
2829
|
+
arrayToString2(to)
|
|
2830
|
+
].join(":");
|
|
2831
|
+
},
|
|
2832
|
+
getRangeFromCursor: (cursor) => {
|
|
2833
|
+
invariant4(text.doc, void 0, {
|
|
2834
|
+
F: __dxlog_file5,
|
|
2835
|
+
L: 40,
|
|
2836
|
+
S: void 0,
|
|
2837
|
+
A: [
|
|
2838
|
+
"text.doc",
|
|
2839
|
+
""
|
|
2840
|
+
]
|
|
2841
|
+
});
|
|
2842
|
+
const parts = cursor.split(":");
|
|
2843
|
+
const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
|
|
2844
|
+
const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
|
|
2845
|
+
return from && to ? {
|
|
2846
|
+
from: from.index,
|
|
2847
|
+
to: to.index
|
|
2848
|
+
} : void 0;
|
|
2849
|
+
},
|
|
2850
|
+
extension: [
|
|
2851
|
+
modelState.init(() => model),
|
|
2852
|
+
yjs(text.content, provider?.awareness)
|
|
2853
|
+
],
|
|
2854
|
+
awareness: provider?.awareness,
|
|
2855
|
+
peer: identity ? {
|
|
2856
|
+
id: identity.identityKey.toHex(),
|
|
2857
|
+
name: identity.profile?.displayName
|
|
2858
|
+
} : void 0
|
|
2859
|
+
};
|
|
2860
|
+
return model;
|
|
2861
|
+
};
|
|
1040
2862
|
var messageAwareness = 1;
|
|
1041
2863
|
var messageQueryAwareness = 3;
|
|
1042
|
-
var
|
|
1043
|
-
constructor({ space, doc, channel, awareness }) {
|
|
2864
|
+
var YAwarenessProvider = class extends Observable {
|
|
2865
|
+
constructor({ space, doc, channel, awareness: awareness2 }) {
|
|
1044
2866
|
super();
|
|
1045
2867
|
this._space = space;
|
|
1046
|
-
this._awareness =
|
|
2868
|
+
this._awareness = awareness2 ?? new YP.Awareness(doc);
|
|
1047
2869
|
this._channel = channel;
|
|
1048
2870
|
this._clientId = doc.clientID;
|
|
1049
2871
|
this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
|
|
@@ -1058,7 +2880,7 @@ var SpaceAwarenessProvider = class extends Observable {
|
|
|
1058
2880
|
void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
|
|
1059
2881
|
const encoderAwarenessState = encoding.createEncoder();
|
|
1060
2882
|
encoding.writeVarUint(encoderAwarenessState, messageAwareness);
|
|
1061
|
-
encoding.writeVarUint8Array(encoderAwarenessState,
|
|
2883
|
+
encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
|
|
1062
2884
|
this._clientId
|
|
1063
2885
|
]));
|
|
1064
2886
|
void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
|
|
@@ -1067,27 +2889,27 @@ var SpaceAwarenessProvider = class extends Observable {
|
|
|
1067
2889
|
return this._awareness;
|
|
1068
2890
|
}
|
|
1069
2891
|
_handleAwarenessUpdate({ added, updated, removed }, origin) {
|
|
1070
|
-
|
|
2892
|
+
log4("awareness update", {
|
|
1071
2893
|
added,
|
|
1072
2894
|
updated,
|
|
1073
2895
|
removed,
|
|
1074
2896
|
origin
|
|
1075
2897
|
}, {
|
|
1076
|
-
F:
|
|
1077
|
-
L:
|
|
2898
|
+
F: __dxlog_file5,
|
|
2899
|
+
L: 122,
|
|
1078
2900
|
S: this,
|
|
1079
2901
|
C: (f, a) => f(...a)
|
|
1080
2902
|
});
|
|
1081
2903
|
const changedClients = added.concat(updated).concat(removed);
|
|
1082
2904
|
const encoderAwareness = encoding.createEncoder();
|
|
1083
2905
|
encoding.writeVarUint(encoderAwareness, messageAwareness);
|
|
1084
|
-
encoding.writeVarUint8Array(encoderAwareness,
|
|
2906
|
+
encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
|
|
1085
2907
|
void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
|
|
1086
2908
|
}
|
|
1087
2909
|
_handleSpaceMessage({ payload }) {
|
|
1088
|
-
|
|
1089
|
-
F:
|
|
1090
|
-
L:
|
|
2910
|
+
log4("space message", payload, {
|
|
2911
|
+
F: __dxlog_file5,
|
|
2912
|
+
L: 131,
|
|
1091
2913
|
S: this,
|
|
1092
2914
|
C: (f, a) => f(...a)
|
|
1093
2915
|
});
|
|
@@ -1104,12 +2926,12 @@ var SpaceAwarenessProvider = class extends Observable {
|
|
|
1104
2926
|
let sendReply = false;
|
|
1105
2927
|
switch (messageType) {
|
|
1106
2928
|
case messageAwareness: {
|
|
1107
|
-
|
|
2929
|
+
YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
|
|
1108
2930
|
break;
|
|
1109
2931
|
}
|
|
1110
2932
|
case messageQueryAwareness: {
|
|
1111
2933
|
encoding.writeVarUint(encoder, messageAwareness);
|
|
1112
|
-
encoding.writeVarUint8Array(encoder,
|
|
2934
|
+
encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
|
|
1113
2935
|
sendReply = true;
|
|
1114
2936
|
break;
|
|
1115
2937
|
}
|
|
@@ -1124,228 +2946,124 @@ var SpaceAwarenessProvider = class extends Observable {
|
|
|
1124
2946
|
return encoder;
|
|
1125
2947
|
}
|
|
1126
2948
|
_handleBeforeUnload() {
|
|
1127
|
-
|
|
2949
|
+
YP.removeAwarenessStates(this._awareness, [
|
|
1128
2950
|
this._clientId
|
|
1129
2951
|
], "window unload");
|
|
1130
2952
|
}
|
|
1131
2953
|
};
|
|
1132
2954
|
|
|
1133
2955
|
// packages/ui/react-ui-editor/src/hooks/useTextModel.ts
|
|
1134
|
-
var
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
return new SpaceAwarenessProvider({
|
|
1143
|
-
space,
|
|
1144
|
-
doc: text.doc,
|
|
1145
|
-
channel: `yjs.awareness.${text.id}`
|
|
1146
|
-
});
|
|
1147
|
-
}, [
|
|
2956
|
+
var modelState = StateField6.define({
|
|
2957
|
+
create: () => void 0,
|
|
2958
|
+
update: (model) => model
|
|
2959
|
+
});
|
|
2960
|
+
var useTextModel = (props) => {
|
|
2961
|
+
const { identity, space, text } = props;
|
|
2962
|
+
const [model, setModel] = useState2(() => createModel(props));
|
|
2963
|
+
useEffect2(() => setModel(createModel(props)), [
|
|
1148
2964
|
identity,
|
|
1149
2965
|
space,
|
|
1150
|
-
text,
|
|
1151
|
-
text?.doc
|
|
1152
|
-
]);
|
|
1153
|
-
const content = useMemo2(() => {
|
|
1154
|
-
if (isActualAutomergeObject(text)) {
|
|
1155
|
-
const obj = text;
|
|
1156
|
-
return getRawDoc(obj, [
|
|
1157
|
-
obj.field
|
|
1158
|
-
]);
|
|
1159
|
-
} else {
|
|
1160
|
-
return text?.content;
|
|
1161
|
-
}
|
|
1162
|
-
}, [
|
|
1163
2966
|
text
|
|
1164
2967
|
]);
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
} : void 0
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
if (!text?.doc || !text?.content) {
|
|
2968
|
+
return model;
|
|
2969
|
+
};
|
|
2970
|
+
var createModel = (props) => {
|
|
2971
|
+
const { text } = props;
|
|
2972
|
+
if (isAutomergeObject(text)) {
|
|
2973
|
+
return createAutomergeModel(props);
|
|
2974
|
+
} else if (text?.doc) {
|
|
2975
|
+
return createYjsModel(props);
|
|
2976
|
+
} else {
|
|
1178
2977
|
return void 0;
|
|
1179
2978
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
2979
|
+
};
|
|
2980
|
+
|
|
2981
|
+
// packages/ui/react-ui-editor/src/hooks/automerge.ts
|
|
2982
|
+
var createAutomergeModel = ({ space, identity, text }) => {
|
|
2983
|
+
const obj = text;
|
|
2984
|
+
const doc = getRawDoc(obj, [
|
|
2985
|
+
obj.field
|
|
2986
|
+
]);
|
|
2987
|
+
const awarenessProvider = space && new SpaceAwarenessProvider({
|
|
2988
|
+
space,
|
|
2989
|
+
channel: `automerge.awareness.${obj.id}`,
|
|
2990
|
+
info: {
|
|
2991
|
+
displayName: identity?.profile?.displayName ?? "",
|
|
2992
|
+
color: cursorColor.color,
|
|
2993
|
+
lightColor: cursorColor.light
|
|
2994
|
+
},
|
|
2995
|
+
peerId: identity?.identityKey.toHex() ?? "Anonymous"
|
|
2996
|
+
});
|
|
2997
|
+
const model = {
|
|
2998
|
+
id: obj.id,
|
|
2999
|
+
content: doc,
|
|
3000
|
+
text: () => get7(doc.handle.docSync(), doc.path),
|
|
3001
|
+
// TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
|
|
3002
|
+
extension: [
|
|
3003
|
+
modelState.init(() => model),
|
|
3004
|
+
automerge3({
|
|
3005
|
+
handle: doc.handle,
|
|
3006
|
+
path: doc.path
|
|
3007
|
+
}),
|
|
3008
|
+
awarenessProvider && AwarenessProvider.of(awarenessProvider),
|
|
3009
|
+
awareness()
|
|
3010
|
+
].filter(isNotNullOrUndefined),
|
|
1184
3011
|
peer: identity ? {
|
|
1185
3012
|
id: identity.identityKey.toHex(),
|
|
1186
3013
|
name: identity.profile?.displayName
|
|
1187
3014
|
} : void 0
|
|
1188
3015
|
};
|
|
3016
|
+
return model;
|
|
1189
3017
|
};
|
|
1190
|
-
|
|
1191
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
|
|
1192
|
-
var EditorModes = [
|
|
1193
|
-
"default",
|
|
1194
|
-
"vim"
|
|
1195
|
-
];
|
|
1196
|
-
var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots, editorMode }, forwardedRef) => {
|
|
1197
|
-
const { themeMode } = useThemeContext();
|
|
1198
|
-
const tabsterDOMAttribute = useFocusableGroup({
|
|
1199
|
-
tabBehavior: "limited"
|
|
1200
|
-
});
|
|
1201
|
-
const [parent, setParent] = useState(null);
|
|
1202
|
-
const [state, setState] = useState();
|
|
1203
|
-
const [view, setView] = useState();
|
|
1204
|
-
useImperativeHandle(forwardedRef, () => ({
|
|
1205
|
-
editor: parent,
|
|
1206
|
-
state,
|
|
1207
|
-
view
|
|
1208
|
-
}), [
|
|
1209
|
-
view,
|
|
1210
|
-
state,
|
|
1211
|
-
parent
|
|
1212
|
-
]);
|
|
1213
|
-
const collaboration = useCollaboration(model, themeMode);
|
|
1214
|
-
useEffect2(() => {
|
|
1215
|
-
if (!parent) {
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
const initialText = isDocAccessor2(model.content) ? get3(model.content.handle.docSync(), model.content.path) : model.content?.toString();
|
|
1219
|
-
const state2 = EditorState2.create({
|
|
1220
|
-
doc: initialText,
|
|
1221
|
-
extensions: [
|
|
1222
|
-
// TODO(burdon): Factor out VIM mode?
|
|
1223
|
-
editorMode === "vim" && vim(),
|
|
1224
|
-
// Replication.
|
|
1225
|
-
collaboration,
|
|
1226
|
-
// Theme.
|
|
1227
|
-
slots?.editor?.theme && EditorView5.theme(slots?.editor?.theme),
|
|
1228
|
-
// Custom.
|
|
1229
|
-
...extensions
|
|
1230
|
-
].filter(Boolean)
|
|
1231
|
-
});
|
|
1232
|
-
setState(state2);
|
|
1233
|
-
view?.destroy();
|
|
1234
|
-
setView(new EditorView5({
|
|
1235
|
-
state: state2,
|
|
1236
|
-
parent
|
|
1237
|
-
}));
|
|
1238
|
-
return () => {
|
|
1239
|
-
view?.destroy();
|
|
1240
|
-
setView(void 0);
|
|
1241
|
-
setState(void 0);
|
|
1242
|
-
};
|
|
1243
|
-
}, [
|
|
1244
|
-
parent,
|
|
1245
|
-
model.content,
|
|
1246
|
-
themeMode,
|
|
1247
|
-
editorMode
|
|
1248
|
-
]);
|
|
1249
|
-
const handleKeyUp = useCallback((event) => {
|
|
1250
|
-
const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
|
|
1251
|
-
switch (key) {
|
|
1252
|
-
case "Enter": {
|
|
1253
|
-
view?.contentDOM.focus();
|
|
1254
|
-
break;
|
|
1255
|
-
}
|
|
1256
|
-
case "Escape": {
|
|
1257
|
-
editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && parent?.focus();
|
|
1258
|
-
break;
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}, [
|
|
1262
|
-
view,
|
|
1263
|
-
editorMode
|
|
1264
|
-
]);
|
|
1265
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
1266
|
-
key: model.id,
|
|
1267
|
-
ref: setParent,
|
|
1268
|
-
tabIndex: 0,
|
|
1269
|
-
...slots?.root,
|
|
1270
|
-
...editorMode !== "vim" && tabsterDOMAttribute,
|
|
1271
|
-
onKeyUp: handleKeyUp
|
|
1272
|
-
});
|
|
1273
|
-
});
|
|
1274
|
-
var TextEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
|
|
1275
|
-
const { themeMode } = useThemeContext();
|
|
1276
|
-
const extensions = [
|
|
1277
|
-
..._extensions ?? [],
|
|
1278
|
-
basicBundle({
|
|
1279
|
-
themeMode,
|
|
1280
|
-
placeholder: slots?.editor?.placeholder
|
|
1281
|
-
})
|
|
1282
|
-
];
|
|
1283
|
-
return /* @__PURE__ */ React.createElement(BaseTextEditor, {
|
|
1284
|
-
ref: forwardedRef,
|
|
1285
|
-
extensions,
|
|
1286
|
-
slots: defaultsDeep({}, slots, {
|
|
1287
|
-
editor: {
|
|
1288
|
-
theme: basicTheme
|
|
1289
|
-
}
|
|
1290
|
-
}),
|
|
1291
|
-
...props
|
|
1292
|
-
});
|
|
1293
|
-
});
|
|
1294
|
-
var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions: _extensions, slots, ...props }, forwardedRef) => {
|
|
1295
|
-
const { themeMode } = useThemeContext();
|
|
1296
|
-
const extensions = [
|
|
1297
|
-
..._extensions ?? [],
|
|
1298
|
-
markdownBundle({
|
|
1299
|
-
themeMode
|
|
1300
|
-
})
|
|
1301
|
-
];
|
|
1302
|
-
return /* @__PURE__ */ React.createElement(BaseTextEditor, {
|
|
1303
|
-
ref: forwardedRef,
|
|
1304
|
-
extensions,
|
|
1305
|
-
slots: defaultsDeep({}, slots, {
|
|
1306
|
-
editor: {
|
|
1307
|
-
theme: markdownTheme
|
|
1308
|
-
}
|
|
1309
|
-
}),
|
|
1310
|
-
...props
|
|
1311
|
-
});
|
|
1312
|
-
});
|
|
1313
3018
|
export {
|
|
3019
|
+
AwarenessProvider,
|
|
1314
3020
|
BaseTextEditor,
|
|
1315
3021
|
Doc,
|
|
1316
3022
|
EditorModes,
|
|
1317
3023
|
MarkdownEditor,
|
|
3024
|
+
RemoteSelectionsDecorator,
|
|
1318
3025
|
SpaceAwarenessProvider,
|
|
1319
3026
|
TextEditor,
|
|
1320
3027
|
TextKind,
|
|
1321
|
-
|
|
3028
|
+
YAwarenessProvider,
|
|
3029
|
+
YText,
|
|
1322
3030
|
YXmlFragment,
|
|
3031
|
+
autocomplete,
|
|
3032
|
+
automerge3 as automerge,
|
|
3033
|
+
awareness,
|
|
1323
3034
|
basicBundle,
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
3035
|
+
blast,
|
|
3036
|
+
code2 as code,
|
|
3037
|
+
codeTheme,
|
|
3038
|
+
comments,
|
|
3039
|
+
createAutomergeModel,
|
|
3040
|
+
createYjsModel,
|
|
3041
|
+
defaultMarkdownSlots,
|
|
3042
|
+
defaultOptions,
|
|
3043
|
+
defaultSlots,
|
|
3044
|
+
defaultTextSlots,
|
|
3045
|
+
defaultTheme,
|
|
3046
|
+
hr,
|
|
3047
|
+
image,
|
|
3048
|
+
link,
|
|
3049
|
+
listener,
|
|
1338
3050
|
markdownBundle,
|
|
1339
3051
|
markdownHighlightStyle,
|
|
3052
|
+
markdownTags,
|
|
3053
|
+
markdownTagsExtensions,
|
|
1340
3054
|
markdownTheme,
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
3055
|
+
mention,
|
|
3056
|
+
mermaid,
|
|
3057
|
+
modelState,
|
|
3058
|
+
setCommentRange,
|
|
3059
|
+
setFocus,
|
|
3060
|
+
setSelection,
|
|
3061
|
+
table,
|
|
1344
3062
|
tags2 as tags,
|
|
1345
|
-
|
|
1346
|
-
|
|
3063
|
+
tasklist,
|
|
3064
|
+
textTheme,
|
|
3065
|
+
typewriter,
|
|
1347
3066
|
useTextModel,
|
|
1348
|
-
|
|
1349
|
-
whiskey
|
|
3067
|
+
yjs
|
|
1350
3068
|
};
|
|
1351
3069
|
//# sourceMappingURL=index.mjs.map
|