@dxos/react-ui-editor 0.3.9 → 0.3.10-main.11cdcf4
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 +722 -701
- 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 +35 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +4 -2
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +9 -4
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +9 -0
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/{basic/bundle.d.ts → basic.d.ts} +1 -1
- package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.d.ts → experimental.d.ts} +2 -2
- package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +5 -2
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/link.d.ts +11 -0
- package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts +7 -0
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts +31 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +1 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +5 -0
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +12 -0
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/index.d.ts +2 -0
- package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/themes/default.d.ts +17 -0
- package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/themes/index.d.ts +2 -0
- package/dist/types/src/components/TextEditor/themes/index.d.ts.map +1 -0
- 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/hooks/useTextModel.d.ts +1 -0
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts +12 -10
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/testing/replicator.d.ts.map +1 -1
- package/package.json +21 -32
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +242 -0
- package/src/components/TextEditor/TextEditor.stories.tsx +46 -43
- package/src/components/TextEditor/TextEditor.tsx +62 -45
- package/src/components/TextEditor/extensions/autocomplete.ts +72 -0
- package/src/components/TextEditor/extensions/{basic/bundle.ts → basic.ts} +4 -2
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkWidget.tsx → experimental.tsx} +17 -2
- package/src/components/TextEditor/extensions/index.ts +5 -2
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkDecoration.ts → link.ts} +68 -31
- package/src/components/TextEditor/extensions/{change.ts → listener.ts} +3 -3
- package/src/components/TextEditor/extensions/markdown/bundle.ts +15 -11
- package/src/components/TextEditor/extensions/markdown/highlight.ts +166 -0
- package/src/components/TextEditor/extensions/markdown/index.ts +1 -1
- package/src/components/TextEditor/extensions/tasklist.ts +113 -0
- package/src/components/TextEditor/extensions/{hyperlink/hyperlinkTooltip.tsx → tooltip.tsx} +9 -9
- package/src/components/TextEditor/index.ts +3 -0
- package/src/components/TextEditor/themes/default.ts +170 -0
- package/src/components/TextEditor/themes/index.ts +5 -0
- package/src/components/TextEditor/yjs.stories.tsx +1 -0
- package/src/hooks/useCollaboration.ts +4 -4
- package/src/hooks/useTextModel.ts +10 -5
- package/src/styles/markdown.ts +26 -24
- package/src/testing/replicator.ts +1 -0
- 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.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/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/src/components/TextEditor/extensions/basic/index.ts +0 -6
- package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
- package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
- package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
- package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
|
@@ -5,145 +5,124 @@ import { Doc, YText as YText2, YXmlFragment } from "@dxos/text-model";
|
|
|
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/extensions/
|
|
8
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
|
|
9
|
+
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
|
|
10
|
+
import { keymap } from "@codemirror/view";
|
|
11
|
+
var autocomplete = ({ getOptions }) => {
|
|
12
|
+
return [
|
|
13
|
+
// https://codemirror.net/docs/ref/#view.keymap
|
|
14
|
+
// https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
|
|
15
|
+
keymap.of(completionKeymap),
|
|
16
|
+
// https://codemirror.net/examples/autocompletion
|
|
17
|
+
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
18
|
+
autocompletion({
|
|
19
|
+
// TODO(burdon): Set custom keymap.
|
|
20
|
+
// defaultKeymap: false,
|
|
21
|
+
// Don't start unless key press.
|
|
22
|
+
activateOnTyping: false,
|
|
23
|
+
// TODO(burdon): Option to create new page?
|
|
24
|
+
// TODO(burdon): Optional decoration via addToOptions
|
|
25
|
+
override: [
|
|
26
|
+
(context) => {
|
|
27
|
+
const word = context.matchBefore(/\w*/);
|
|
28
|
+
if (!word || word.from === word.to && !context.explicit) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
from: word.from,
|
|
33
|
+
options: getOptions(word.text.toLowerCase())
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic.ts
|
|
9
42
|
import { closeBrackets } from "@codemirror/autocomplete";
|
|
10
43
|
import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
|
11
44
|
import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
|
|
12
45
|
import { EditorView, placeholder } from "@codemirror/view";
|
|
13
46
|
var basicBundle = ({ themeMode, placeholder: _placeholder }) => [
|
|
47
|
+
// TODO(burdon): Compare with minimalSetup.
|
|
48
|
+
// https://codemirror.net/docs/ref/#codemirror.minimalSetup
|
|
14
49
|
bracketMatching(),
|
|
15
50
|
closeBrackets(),
|
|
16
|
-
_placeholder && placeholder(_placeholder),
|
|
17
51
|
EditorView.lineWrapping,
|
|
52
|
+
_placeholder && placeholder(_placeholder),
|
|
18
53
|
// TODO(burdon): Is this required?
|
|
19
54
|
themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
|
|
20
55
|
].filter(Boolean);
|
|
21
56
|
|
|
22
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/
|
|
23
|
-
import get from "lodash.get";
|
|
24
|
-
|
|
25
|
-
// packages/ui/react-ui-editor/src/styles/markdown.ts
|
|
26
|
-
import { mx } from "@dxos/react-ui-theme";
|
|
27
|
-
var heading = {
|
|
28
|
-
1: "mbs-4 mbe-2 text-4xl font-semibold text-inherit no-underline",
|
|
29
|
-
2: "mbs-4 mbe-2 text-3xl font-bold text-inherit no-underline",
|
|
30
|
-
3: "mbs-4 mbe-2 text-2xl font-bold text-inherit no-underline",
|
|
31
|
-
4: "mbs-4 mbe-2 text-xl font-extrabold text-inherit no-underline",
|
|
32
|
-
5: "mbs-4 mbe-2 text-lg font-extrabold text-inherit no-underline",
|
|
33
|
-
6: "mbs-4 mbe-2 font-black text-inherit no-underline"
|
|
34
|
-
};
|
|
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
|
-
|
|
42
|
-
// packages/ui/react-ui-editor/src/styles/tokens.ts
|
|
43
|
-
import { tailwindConfig } from "@dxos/react-ui-theme";
|
|
44
|
-
var tokens = tailwindConfig({}).theme;
|
|
45
|
-
|
|
46
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic/theme.ts
|
|
47
|
-
var basicTheme = {
|
|
48
|
-
"&.cm-focused": {
|
|
49
|
-
outline: "none"
|
|
50
|
-
},
|
|
51
|
-
"& .cm-line": {
|
|
52
|
-
paddingInline: 0,
|
|
53
|
-
lineHeight: 1.6,
|
|
54
|
-
minBlockSize: "1.6em"
|
|
55
|
-
},
|
|
56
|
-
"& .cm-line *": {
|
|
57
|
-
lineHeight: 1.6
|
|
58
|
-
},
|
|
59
|
-
"& .cm-scroller": {
|
|
60
|
-
fontFamily: get(tokens, "fontFamily.body", []).join(","),
|
|
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"
|
|
73
|
-
},
|
|
74
|
-
"& .cm-link": {
|
|
75
|
-
color: get(tokens, "extend.colors.primary.500"),
|
|
76
|
-
textDecoration: "underline"
|
|
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());
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts
|
|
57
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
|
|
93
58
|
import { syntaxTree } from "@codemirror/language";
|
|
94
|
-
import { RangeSetBuilder, StateField
|
|
59
|
+
import { RangeSetBuilder, StateField } from "@codemirror/state";
|
|
95
60
|
import { Decoration, EditorView as EditorView2, WidgetType } from "@codemirror/view";
|
|
96
|
-
var
|
|
97
|
-
constructor(
|
|
61
|
+
var LinkButton = class extends WidgetType {
|
|
62
|
+
constructor(_pos, _url, _onAttach) {
|
|
63
|
+
super();
|
|
64
|
+
this._pos = _pos;
|
|
65
|
+
this._url = _url;
|
|
66
|
+
this._onAttach = _onAttach;
|
|
67
|
+
}
|
|
68
|
+
eq(other) {
|
|
69
|
+
return this._pos === other._pos && this._url === other._url;
|
|
70
|
+
}
|
|
71
|
+
toDOM(view) {
|
|
72
|
+
const el = document.createElement("span");
|
|
73
|
+
this._onAttach(el, this._url);
|
|
74
|
+
return el;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var LinkText = class extends WidgetType {
|
|
78
|
+
constructor(_pos, _text, _url) {
|
|
98
79
|
super();
|
|
99
|
-
this.
|
|
80
|
+
this._pos = _pos;
|
|
100
81
|
this._text = _text;
|
|
101
82
|
this._url = _url;
|
|
102
83
|
}
|
|
103
84
|
eq(other) {
|
|
104
|
-
return this.
|
|
85
|
+
return this._pos === other._pos && this._url === other._url;
|
|
105
86
|
}
|
|
106
87
|
toDOM(view) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
88
|
+
const link2 = document.createElement("a");
|
|
89
|
+
link2.setAttribute("class", "cm-link");
|
|
90
|
+
link2.textContent = this._text;
|
|
110
91
|
if (this._url) {
|
|
111
|
-
|
|
112
|
-
|
|
92
|
+
link2.setAttribute("rel", "noreferrer");
|
|
93
|
+
link2.setAttribute("target", "_blank");
|
|
94
|
+
link2.href = this._url;
|
|
113
95
|
} else {
|
|
114
|
-
|
|
96
|
+
link2.onclick = () => {
|
|
115
97
|
view.dispatch({
|
|
116
98
|
selection: {
|
|
117
|
-
anchor: this.
|
|
99
|
+
anchor: this._pos
|
|
118
100
|
}
|
|
119
101
|
});
|
|
120
102
|
};
|
|
121
103
|
}
|
|
122
|
-
return
|
|
104
|
+
return link2;
|
|
123
105
|
}
|
|
124
106
|
};
|
|
125
|
-
var
|
|
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
|
-
});
|
|
131
|
-
};
|
|
132
|
-
var update = (state, link) => {
|
|
107
|
+
var update = (state, options) => {
|
|
133
108
|
const builder = new RangeSetBuilder();
|
|
134
109
|
const cursor = state.selection.main.head;
|
|
135
110
|
syntaxTree(state).iterate({
|
|
136
111
|
enter: (node) => {
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
112
|
+
if (node.name === "Link") {
|
|
113
|
+
const marks = node.node.getChildren("LinkMark");
|
|
114
|
+
const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
|
|
115
|
+
const urlNode = node.node.getChild("URL");
|
|
116
|
+
const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
|
|
117
|
+
if (cursor < node.from || cursor > node.to) {
|
|
143
118
|
builder.add(node.from, node.to, Decoration.replace({
|
|
144
|
-
widget: new
|
|
119
|
+
widget: new LinkText(node.from, text, options.link ? url : void 0)
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
if (options.onRender) {
|
|
123
|
+
builder.add(node.to, node.to, Decoration.replace({
|
|
124
|
+
widget: new LinkButton(node.from, url, options.onRender)
|
|
145
125
|
}));
|
|
146
|
-
return false;
|
|
147
126
|
}
|
|
148
127
|
}
|
|
149
128
|
return true;
|
|
@@ -151,142 +130,25 @@ var update = (state, link) => {
|
|
|
151
130
|
});
|
|
152
131
|
return builder.finish();
|
|
153
132
|
};
|
|
133
|
+
var link = (options = {}) => {
|
|
134
|
+
return StateField.define({
|
|
135
|
+
create: (state) => update(state, options),
|
|
136
|
+
update: (_, tr) => update(tr.state, options),
|
|
137
|
+
provide: (field) => EditorView2.decorations.from(field)
|
|
138
|
+
});
|
|
139
|
+
};
|
|
154
140
|
|
|
155
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/
|
|
156
|
-
import {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
let idx = 0;
|
|
163
|
-
let match;
|
|
164
|
-
do {
|
|
165
|
-
match = text.substring(idx).match(regexp);
|
|
166
|
-
if (!match) {
|
|
167
|
-
match = void 0;
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
idx = text.indexOf(match[0]);
|
|
171
|
-
if (p >= idx && p < idx + match[0].length) {
|
|
172
|
-
break;
|
|
141
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
|
|
142
|
+
import { StateField as StateField2 } from "@codemirror/state";
|
|
143
|
+
var listener = (onChange) => StateField2.define({
|
|
144
|
+
create: () => null,
|
|
145
|
+
update: (_value, transaction) => {
|
|
146
|
+
if (transaction.docChanged && onChange) {
|
|
147
|
+
onChange(transaction.newDoc.toString());
|
|
173
148
|
}
|
|
174
|
-
idx += match[0].length;
|
|
175
|
-
} while (true);
|
|
176
|
-
if (!match) {
|
|
177
149
|
return null;
|
|
178
150
|
}
|
|
179
|
-
const [, , url] = match ?? [];
|
|
180
|
-
return {
|
|
181
|
-
pos: idx + from,
|
|
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
|
-
};
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx
|
|
199
|
-
import { Decoration as Decoration2, EditorView as EditorView3, MatchDecorator, ViewPlugin } from "@codemirror/view";
|
|
200
|
-
var markdownLinkRegexp2 = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
|
|
201
|
-
var linkDecorator = () => new MatchDecorator({
|
|
202
|
-
regexp: markdownLinkRegexp2,
|
|
203
|
-
decorate: (add, from, to, match, view) => {
|
|
204
|
-
const [_, label, url] = match;
|
|
205
|
-
const p0 = {
|
|
206
|
-
start: from,
|
|
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"
|
|
225
|
-
}
|
|
226
|
-
}));
|
|
227
|
-
add(p1.start, p1.end, Decoration2.mark({
|
|
228
|
-
class: "cm-hyperlink-label"
|
|
229
|
-
}));
|
|
230
|
-
add(p2.start, p2.end, Decoration2.mark({
|
|
231
|
-
class: "cm-hyperlink-bracket",
|
|
232
|
-
_attributes: {
|
|
233
|
-
style: "display: none"
|
|
234
|
-
}
|
|
235
|
-
}));
|
|
236
|
-
add(p3.start, p3.end, Decoration2.mark({
|
|
237
|
-
class: "cm-hyperlink-url",
|
|
238
|
-
_attributes: {
|
|
239
|
-
style: "display: none"
|
|
240
|
-
}
|
|
241
|
-
}));
|
|
242
|
-
}
|
|
243
151
|
});
|
|
244
|
-
function hyperLinkExtension() {
|
|
245
|
-
return ViewPlugin.fromClass(class HyperLinkView {
|
|
246
|
-
constructor(view) {
|
|
247
|
-
this.decorator = linkDecorator();
|
|
248
|
-
this.decorations = this.decorator.createDeco(view);
|
|
249
|
-
}
|
|
250
|
-
update(update2) {
|
|
251
|
-
if (update2.docChanged || update2.viewportChanged) {
|
|
252
|
-
this.decorations = this.decorator.updateDeco(update2, this.decorations);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}, {
|
|
256
|
-
decorations: (view) => view.decorations,
|
|
257
|
-
eventHandlers: {
|
|
258
|
-
// TODO(burdon): CLick to expand link.
|
|
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);
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
// TODO(burdon): Hide when cursor moves away.
|
|
267
|
-
keydown: (event, view) => {
|
|
268
|
-
view.update([]);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
var hyperlinkStyle = EditorView3.baseTheme({
|
|
274
|
-
".cm-hyperlink-label": {
|
|
275
|
-
cursor: "pointer",
|
|
276
|
-
textDecoration: "underline"
|
|
277
|
-
},
|
|
278
|
-
".cm-hyperlink-bracket": {
|
|
279
|
-
opacity: 0.2
|
|
280
|
-
},
|
|
281
|
-
".cm-hyperlink-url": {
|
|
282
|
-
opacity: 0.5,
|
|
283
|
-
fontFamily: "monospace"
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
var hyperlinkWidget = [
|
|
287
|
-
hyperLinkExtension(),
|
|
288
|
-
hyperlinkStyle
|
|
289
|
-
];
|
|
290
152
|
|
|
291
153
|
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
|
|
292
154
|
import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
|
|
@@ -298,222 +160,59 @@ import { lintKeymap } from "@codemirror/lint";
|
|
|
298
160
|
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
|
|
299
161
|
import { EditorState } from "@codemirror/state";
|
|
300
162
|
import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
|
|
301
|
-
import { crosshairCursor, drawSelection, dropCursor, EditorView as
|
|
163
|
+
import { crosshairCursor, drawSelection, dropCursor, EditorView as EditorView3, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap as keymap2, placeholder as placeholder2, rectangularSelection } from "@codemirror/view";
|
|
302
164
|
|
|
303
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/
|
|
304
|
-
import {
|
|
165
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
|
|
166
|
+
import { markdownLanguage } from "@codemirror/lang-markdown";
|
|
167
|
+
import { HighlightStyle } from "@codemirror/language";
|
|
168
|
+
import { tags, styleTags, Tag } from "@lezer/highlight";
|
|
169
|
+
import get from "lodash.get";
|
|
170
|
+
|
|
171
|
+
// packages/ui/react-ui-editor/src/styles/markdown.ts
|
|
172
|
+
import { mx } from "@dxos/react-ui-theme";
|
|
173
|
+
var heading = {
|
|
174
|
+
1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
|
|
175
|
+
2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
|
|
176
|
+
3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
|
|
177
|
+
4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
|
|
178
|
+
5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
|
|
179
|
+
6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
|
|
180
|
+
};
|
|
181
|
+
var light = "text-neutral-200 dark:text-neutral-800";
|
|
182
|
+
var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
|
|
183
|
+
var bold = "font-bold";
|
|
184
|
+
var italic = "italic";
|
|
185
|
+
var strikethrough = "line-through";
|
|
186
|
+
var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
|
|
187
|
+
var codeMark = "font-mono text-primary-500";
|
|
188
|
+
var inlineUrl = mx(code, "px-1");
|
|
189
|
+
var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
|
|
190
|
+
var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-500/50";
|
|
191
|
+
|
|
192
|
+
// packages/ui/react-ui-editor/src/styles/tokens.ts
|
|
193
|
+
import { tailwindConfig } from "@dxos/react-ui-theme";
|
|
194
|
+
var tokens = tailwindConfig({}).theme;
|
|
195
|
+
|
|
196
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
|
|
305
197
|
var markdownTags = {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
198
|
+
Blockquote: Tag.define(),
|
|
199
|
+
CodeMark: Tag.define(),
|
|
200
|
+
CodeText: Tag.define(),
|
|
201
|
+
EmphasisMark: Tag.define(),
|
|
202
|
+
HeaderMark: Tag.define(),
|
|
203
|
+
HorizontalRule: Tag.define(),
|
|
204
|
+
InlineCode: Tag.define(),
|
|
205
|
+
LinkLabel: Tag.define(),
|
|
206
|
+
LinkReference: Tag.define(),
|
|
207
|
+
ListMark: Tag.define(),
|
|
208
|
+
QuoteMark: Tag.define(),
|
|
209
|
+
URL: Tag.define()
|
|
317
210
|
};
|
|
318
211
|
var markdownTagsExtension = {
|
|
319
212
|
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
|
-
})
|
|
213
|
+
styleTags(markdownTags)
|
|
333
214
|
]
|
|
334
215
|
};
|
|
335
|
-
|
|
336
|
-
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/theme.ts
|
|
337
|
-
import { markdownLanguage } from "@codemirror/lang-markdown";
|
|
338
|
-
import { HighlightStyle } from "@codemirror/language";
|
|
339
|
-
import { tags } from "@lezer/highlight";
|
|
340
|
-
import get2 from "lodash.get";
|
|
341
|
-
var chalky = "#e5c07b";
|
|
342
|
-
var coral = "#e06c75";
|
|
343
|
-
var cyan = "#56b6c2";
|
|
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"
|
|
397
|
-
},
|
|
398
|
-
// '.dark & .cm-tooltip': {
|
|
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"
|
|
435
|
-
},
|
|
436
|
-
".dark & .cm-selectionMatch": {
|
|
437
|
-
background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
|
|
438
|
-
},
|
|
439
|
-
"& .cm-content": {
|
|
440
|
-
padding: 0,
|
|
441
|
-
caretColor: "black"
|
|
442
|
-
},
|
|
443
|
-
".dark & .cm-content": {
|
|
444
|
-
caretColor: "white"
|
|
445
|
-
},
|
|
446
|
-
"& .cm-cursor": {
|
|
447
|
-
borderLeftColor: "black"
|
|
448
|
-
},
|
|
449
|
-
".dark & .cm-cursor": {
|
|
450
|
-
borderLeftColor: "white"
|
|
451
|
-
},
|
|
452
|
-
".cm-placeholder": {
|
|
453
|
-
fontFamily: get2(tokens, "fontFamily.body", []).join(",")
|
|
454
|
-
},
|
|
455
|
-
"& .cm-scroller": {
|
|
456
|
-
fontFamily: get2(tokens, "fontFamily.mono", []).join(","),
|
|
457
|
-
overflow: "visible"
|
|
458
|
-
},
|
|
459
|
-
"& .cm-activeLine": {
|
|
460
|
-
backgroundColor: "transparent"
|
|
461
|
-
},
|
|
462
|
-
".dark & .cm-activeLine": {
|
|
463
|
-
backgroundColor: "transparent"
|
|
464
|
-
},
|
|
465
|
-
"& .cm-ySelectionInfo": {
|
|
466
|
-
fontFamily: get2(tokens, "fontFamily.body", []).join(","),
|
|
467
|
-
padding: "2px 4px",
|
|
468
|
-
marginBlockStart: "-4px"
|
|
469
|
-
},
|
|
470
|
-
"& .cm-ySelection, & .cm-selectionMatch": {
|
|
471
|
-
paddingBlockStart: ".15em",
|
|
472
|
-
paddingBlockEnd: ".15em"
|
|
473
|
-
},
|
|
474
|
-
"& .cm-ySelection, & .cm-yLineSelection": {
|
|
475
|
-
mixBlendMode: "multiply"
|
|
476
|
-
},
|
|
477
|
-
".dark & .cm-ySelection, .dark & .cm-yLineSelection": {
|
|
478
|
-
mixBlendMode: "screen"
|
|
479
|
-
},
|
|
480
|
-
"& .cm-ySelectionCaret": {
|
|
481
|
-
display: "inline-block",
|
|
482
|
-
insetBlockStart: ".1em",
|
|
483
|
-
blockSize: "1.4em",
|
|
484
|
-
verticalAlign: "top"
|
|
485
|
-
},
|
|
486
|
-
"& .cm-yLineSelection": {
|
|
487
|
-
margin: "0"
|
|
488
|
-
},
|
|
489
|
-
".cm-link": {
|
|
490
|
-
color: "rgb(20 89 208)",
|
|
491
|
-
textDecorationLine: "underline",
|
|
492
|
-
textDecorationThickness: "1px",
|
|
493
|
-
textUnderlineOffset: "2px",
|
|
494
|
-
borderRadius: ".125rem"
|
|
495
|
-
},
|
|
496
|
-
// TODO(burdon): Document.
|
|
497
|
-
...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
|
|
498
|
-
const height = get2(tokens, [
|
|
499
|
-
"extend",
|
|
500
|
-
"fontSize",
|
|
501
|
-
fontSize,
|
|
502
|
-
1,
|
|
503
|
-
"lineHeight"
|
|
504
|
-
]);
|
|
505
|
-
acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
|
|
506
|
-
height
|
|
507
|
-
};
|
|
508
|
-
acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
|
|
509
|
-
height
|
|
510
|
-
};
|
|
511
|
-
acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
|
|
512
|
-
height
|
|
513
|
-
};
|
|
514
|
-
return acc;
|
|
515
|
-
}, {})
|
|
516
|
-
};
|
|
517
216
|
var markdownHighlightStyle = HighlightStyle.define([
|
|
518
217
|
{
|
|
519
218
|
tag: [
|
|
@@ -553,48 +252,22 @@ var markdownHighlightStyle = HighlightStyle.define([
|
|
|
553
252
|
],
|
|
554
253
|
color: "inherit !important"
|
|
555
254
|
},
|
|
255
|
+
// Markdown marks.
|
|
556
256
|
{
|
|
557
257
|
tag: [
|
|
558
|
-
tags.link,
|
|
559
|
-
tags.url
|
|
560
|
-
],
|
|
561
|
-
color: "inherit !important",
|
|
562
|
-
textDecoration: "none !important"
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
tag: [
|
|
566
|
-
tags.function(tags.variableName),
|
|
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
258
|
tags.meta,
|
|
583
|
-
tags.processingInstruction
|
|
259
|
+
tags.processingInstruction,
|
|
260
|
+
markdownTags.CodeMark,
|
|
261
|
+
markdownTags.EmphasisMark,
|
|
262
|
+
markdownTags.HeaderMark,
|
|
263
|
+
markdownTags.LinkLabel,
|
|
264
|
+
markdownTags.LinkReference,
|
|
265
|
+
markdownTags.ListMark,
|
|
266
|
+
markdownTags.QuoteMark
|
|
584
267
|
],
|
|
585
268
|
class: mark
|
|
586
269
|
},
|
|
587
|
-
|
|
588
|
-
tag: [
|
|
589
|
-
markdownTags.codeText,
|
|
590
|
-
markdownTags.inlineCode
|
|
591
|
-
],
|
|
592
|
-
class: "font-mono"
|
|
593
|
-
},
|
|
594
|
-
{
|
|
595
|
-
tag: tags.emphasis,
|
|
596
|
-
class: italic
|
|
597
|
-
},
|
|
270
|
+
// Headings.
|
|
598
271
|
{
|
|
599
272
|
tag: tags.heading1,
|
|
600
273
|
class: heading[1]
|
|
@@ -619,69 +292,397 @@ var markdownHighlightStyle = HighlightStyle.define([
|
|
|
619
292
|
tag: tags.heading6,
|
|
620
293
|
class: heading[6]
|
|
621
294
|
},
|
|
295
|
+
// Emphasis.
|
|
622
296
|
{
|
|
623
|
-
tag: tags.
|
|
624
|
-
class:
|
|
297
|
+
tag: tags.emphasis,
|
|
298
|
+
class: italic
|
|
625
299
|
},
|
|
626
300
|
{
|
|
627
301
|
tag: tags.strong,
|
|
628
302
|
class: bold
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
tag: tags.strikethrough,
|
|
306
|
+
class: strikethrough
|
|
307
|
+
},
|
|
308
|
+
// Naked URLs.
|
|
309
|
+
{
|
|
310
|
+
tag: [
|
|
311
|
+
markdownTags.URL
|
|
312
|
+
],
|
|
313
|
+
class: inlineUrl
|
|
314
|
+
},
|
|
315
|
+
// E.g., code block language (after ```).
|
|
316
|
+
{
|
|
317
|
+
tag: [
|
|
318
|
+
tags.function(tags.variableName),
|
|
319
|
+
tags.labelName
|
|
320
|
+
],
|
|
321
|
+
class: codeMark
|
|
322
|
+
},
|
|
323
|
+
// The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
|
|
324
|
+
// However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
|
|
325
|
+
// when a language is specified. In this case, the syntax highlighting extensions will colorize
|
|
326
|
+
// the code, but all other CSS properties will be inherited.
|
|
327
|
+
{
|
|
328
|
+
tag: [
|
|
329
|
+
markdownTags.CodeText,
|
|
330
|
+
markdownTags.InlineCode
|
|
331
|
+
],
|
|
332
|
+
class: code
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
tag: [
|
|
336
|
+
markdownTags.QuoteMark
|
|
337
|
+
],
|
|
338
|
+
class: blockquote
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
tag: [
|
|
342
|
+
markdownTags.HorizontalRule
|
|
343
|
+
],
|
|
344
|
+
class: horizontalRule
|
|
629
345
|
}
|
|
630
346
|
], {
|
|
631
347
|
scope: markdownLanguage,
|
|
632
348
|
all: {
|
|
349
|
+
fontFamily: get(tokens, "fontFamily.body", []).join(",")
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
|
|
354
|
+
var markdownBundle = ({ themeMode, placeholder: _placeholder }) => {
|
|
355
|
+
return [
|
|
356
|
+
bracketMatching2(),
|
|
357
|
+
closeBrackets2(),
|
|
358
|
+
_placeholder && placeholder2(_placeholder),
|
|
359
|
+
EditorState.allowMultipleSelections.of(true),
|
|
360
|
+
EditorView3.lineWrapping,
|
|
361
|
+
// autocompletion(),
|
|
362
|
+
crosshairCursor(),
|
|
363
|
+
dropCursor(),
|
|
364
|
+
drawSelection(),
|
|
365
|
+
highlightActiveLine(),
|
|
366
|
+
highlightActiveLineGutter(),
|
|
367
|
+
highlightSelectionMatches(),
|
|
368
|
+
highlightSpecialChars(),
|
|
369
|
+
history(),
|
|
370
|
+
indentOnInput(),
|
|
371
|
+
rectangularSelection(),
|
|
372
|
+
keymap2.of([
|
|
373
|
+
...closeBracketsKeymap,
|
|
374
|
+
// ...completionKeymap,
|
|
375
|
+
...defaultKeymap,
|
|
376
|
+
...foldKeymap,
|
|
377
|
+
...historyKeymap,
|
|
378
|
+
...lintKeymap,
|
|
379
|
+
...searchKeymap,
|
|
380
|
+
indentWithTab
|
|
381
|
+
]),
|
|
382
|
+
// Main extension.
|
|
383
|
+
// https://github.com/codemirror/lang-markdown
|
|
384
|
+
// https://codemirror.net/5/mode/markdown/index.html (demo).
|
|
385
|
+
markdown({
|
|
386
|
+
// GRM by default (vs strict CommonMark):
|
|
387
|
+
// Table, TaskList, Strikethrough, and Autolink.
|
|
388
|
+
// NOTE: This extends the parser; it doesn't affect rendering.
|
|
389
|
+
// https://github.github.com/gfm
|
|
390
|
+
// https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
|
|
391
|
+
base: markdownLanguage2,
|
|
392
|
+
// Languages for syntax highlighting fenced code blocks.
|
|
393
|
+
codeLanguages: languages,
|
|
394
|
+
// Parser extensions.
|
|
395
|
+
extensions: [
|
|
396
|
+
// GFM, // TODO(burdon): Provided by default?
|
|
397
|
+
markdownTagsExtension
|
|
398
|
+
]
|
|
399
|
+
}),
|
|
400
|
+
// Custom styles.
|
|
401
|
+
syntaxHighlighting2(markdownHighlightStyle),
|
|
402
|
+
// TODO(thure): All but one rule here apply to both themes; rename or refactor.
|
|
403
|
+
themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
|
|
404
|
+
].filter(Boolean);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
|
|
408
|
+
import { syntaxTree as syntaxTree2 } from "@codemirror/language";
|
|
409
|
+
import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
|
|
410
|
+
import { EditorView as EditorView4, Decoration as Decoration2, WidgetType as WidgetType2 } from "@codemirror/view";
|
|
411
|
+
var CheckboxWidget = class extends WidgetType2 {
|
|
412
|
+
constructor(_pos, _getCursor, _checked) {
|
|
413
|
+
super();
|
|
414
|
+
this._pos = _pos;
|
|
415
|
+
this._getCursor = _getCursor;
|
|
416
|
+
this._checked = _checked;
|
|
417
|
+
}
|
|
418
|
+
eq(other) {
|
|
419
|
+
return this._pos === other._pos && this._checked === other._checked;
|
|
420
|
+
}
|
|
421
|
+
toDOM(view) {
|
|
422
|
+
const wrap = document.createElement("span");
|
|
423
|
+
wrap.setAttribute("aria-hidden", "true");
|
|
424
|
+
wrap.className = "cm-task-item";
|
|
425
|
+
const box = wrap.appendChild(document.createElement("input"));
|
|
426
|
+
box.type = "checkbox";
|
|
427
|
+
box.checked = this._checked;
|
|
428
|
+
box.onclick = (event) => {
|
|
429
|
+
this._checked = !isChecked(view.state, this._pos);
|
|
430
|
+
view.dispatch({
|
|
431
|
+
changes: {
|
|
432
|
+
from: this._pos + 1,
|
|
433
|
+
to: this._pos + 2,
|
|
434
|
+
insert: this._checked ? "x" : " "
|
|
435
|
+
},
|
|
436
|
+
// TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
|
|
437
|
+
selection: {
|
|
438
|
+
anchor: this._pos + 4
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
return true;
|
|
442
|
+
};
|
|
443
|
+
return wrap;
|
|
444
|
+
}
|
|
445
|
+
// TODO(burdon): Remove listener?
|
|
446
|
+
// override destroy() {
|
|
447
|
+
// console.log('destroy', this._pos);
|
|
448
|
+
// }
|
|
449
|
+
ignoreEvent() {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var isChecked = (state, pos) => {
|
|
454
|
+
return state.sliceDoc(pos, pos + 3).toLowerCase() === "[x]";
|
|
455
|
+
};
|
|
456
|
+
var statefield = () => {
|
|
457
|
+
let lastPosition = 0;
|
|
458
|
+
const listener2 = EditorView4.updateListener.of((update2) => {
|
|
459
|
+
lastPosition = update2.startState.selection.main.head;
|
|
460
|
+
});
|
|
461
|
+
const checkbox = (state) => {
|
|
462
|
+
const builder = new RangeSetBuilder2();
|
|
463
|
+
syntaxTree2(state).iterate({
|
|
464
|
+
enter: (node) => {
|
|
465
|
+
if (node.name === "TaskMarker") {
|
|
466
|
+
builder.add(node.from, node.to, Decoration2.replace({
|
|
467
|
+
widget: new CheckboxWidget(node.from, () => lastPosition, isChecked(state, node.from))
|
|
468
|
+
}));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
return builder.finish();
|
|
473
|
+
};
|
|
474
|
+
return [
|
|
475
|
+
listener2,
|
|
476
|
+
StateField3.define({
|
|
477
|
+
create: (state) => checkbox(state),
|
|
478
|
+
update: (_, tr) => checkbox(tr.state),
|
|
479
|
+
provide: (field) => EditorView4.decorations.from(field)
|
|
480
|
+
})
|
|
481
|
+
];
|
|
482
|
+
};
|
|
483
|
+
var styles = EditorView4.baseTheme({
|
|
484
|
+
"& .cm-task-item": {
|
|
485
|
+
padding: "0 4px"
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
var tasklist = () => [
|
|
489
|
+
statefield(),
|
|
490
|
+
styles
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
|
|
494
|
+
import { hoverTooltip } from "@codemirror/view";
|
|
495
|
+
import { tooltipContent } from "@dxos/react-ui-theme";
|
|
496
|
+
var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
|
|
497
|
+
var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
|
|
498
|
+
const { from, text } = view.state.doc.lineAt(pos);
|
|
499
|
+
const p = pos - from;
|
|
500
|
+
let idx = 0;
|
|
501
|
+
let match;
|
|
502
|
+
do {
|
|
503
|
+
match = text.substring(idx).match(regexp);
|
|
504
|
+
if (!match) {
|
|
505
|
+
match = void 0;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
idx = text.indexOf(match[0]);
|
|
509
|
+
if (p >= idx && p < idx + match[0].length) {
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
idx += match[0].length;
|
|
513
|
+
} while (true);
|
|
514
|
+
if (!match) {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
const [, , url] = match ?? [];
|
|
518
|
+
return {
|
|
519
|
+
pos: idx + from,
|
|
520
|
+
end: idx + from + match[0].length,
|
|
521
|
+
above: true,
|
|
522
|
+
create: () => {
|
|
523
|
+
const el = document.createElement("div");
|
|
524
|
+
el.className = tooltipContent({}, "pli-2 plb-1");
|
|
525
|
+
onHover(el, url);
|
|
526
|
+
return {
|
|
527
|
+
dom: el,
|
|
528
|
+
offset: {
|
|
529
|
+
x: 0,
|
|
530
|
+
y: 12
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
|
|
538
|
+
import get2 from "lodash.get";
|
|
539
|
+
var defaultTheme = {
|
|
540
|
+
//
|
|
541
|
+
// Main layout:
|
|
542
|
+
// <div class=".cm-editor .cm-focused">
|
|
543
|
+
// <div class=".cm-scroller">
|
|
544
|
+
// <div class=".cm-content" role="textbox" contenteditable="true">
|
|
545
|
+
// <div class=".cm-line" />
|
|
546
|
+
// </div>
|
|
547
|
+
// </div>
|
|
548
|
+
// </div>
|
|
549
|
+
//
|
|
550
|
+
"&": {},
|
|
551
|
+
"&.cm-focused": {
|
|
552
|
+
outline: "none"
|
|
553
|
+
},
|
|
554
|
+
"& .cm-scroller": {
|
|
555
|
+
overflow: "visible"
|
|
556
|
+
},
|
|
557
|
+
"& .cm-content": {
|
|
558
|
+
padding: 0,
|
|
559
|
+
// Base font size (otherwise defined by HTML tag, which might be different for storybook).
|
|
560
|
+
fontSize: "16px"
|
|
561
|
+
},
|
|
562
|
+
"&light .cm-content": {
|
|
563
|
+
color: get2(tokens, "extend.colors.neutral.900", "black"),
|
|
564
|
+
caretColor: "black"
|
|
565
|
+
},
|
|
566
|
+
"&dark .cm-content": {
|
|
567
|
+
color: get2(tokens, "extend.colors.neutral.100", "white"),
|
|
568
|
+
caretColor: "white"
|
|
569
|
+
},
|
|
570
|
+
//
|
|
571
|
+
// Cursor
|
|
572
|
+
//
|
|
573
|
+
"&light .cm-cursor": {
|
|
574
|
+
borderLeft: "2px solid black"
|
|
575
|
+
},
|
|
576
|
+
"&dark .cm-cursor": {
|
|
577
|
+
borderLeft: "2px solid white"
|
|
578
|
+
},
|
|
579
|
+
"& .cm-placeholder": {
|
|
580
|
+
fontWeight: 100
|
|
581
|
+
},
|
|
582
|
+
//
|
|
583
|
+
// line
|
|
584
|
+
//
|
|
585
|
+
"& .cm-line": {
|
|
586
|
+
paddingInline: 0
|
|
587
|
+
},
|
|
588
|
+
"& .cm-line *": {},
|
|
589
|
+
"& .cm-activeLine": {
|
|
590
|
+
backgroundColor: "transparent"
|
|
591
|
+
},
|
|
592
|
+
//
|
|
593
|
+
// selection
|
|
594
|
+
//
|
|
595
|
+
"&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
|
|
596
|
+
background: get2(tokens, "extend.colors.primary.150", "#00ffff")
|
|
597
|
+
},
|
|
598
|
+
"&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
|
|
599
|
+
background: get2(tokens, "extend.colors.primary.850", "#00ffff")
|
|
600
|
+
},
|
|
601
|
+
"&light .cm-selectionMatch": {
|
|
602
|
+
background: get2(tokens, "extend.colors.primary.250", "#00ffff") + "44"
|
|
603
|
+
},
|
|
604
|
+
"&dark .cm-selectionMatch": {
|
|
605
|
+
background: get2(tokens, "extend.colors.primary.600", "#00ffff") + "44"
|
|
606
|
+
},
|
|
607
|
+
//
|
|
608
|
+
// collaboration
|
|
609
|
+
// TODO(burdon): Review classnames (YJS dependent?)
|
|
610
|
+
//
|
|
611
|
+
"&light .cm-ySelection, &light .cm-yLineSelection": {
|
|
612
|
+
mixBlendMode: "multiply"
|
|
613
|
+
},
|
|
614
|
+
"&dark .cm-ySelection, &dark .cm-yLineSelection": {
|
|
615
|
+
mixBlendMode: "screen"
|
|
616
|
+
},
|
|
617
|
+
"& .cm-ySelectionInfo": {
|
|
618
|
+
padding: "2px 4px",
|
|
619
|
+
marginBlockStart: "-4px"
|
|
620
|
+
},
|
|
621
|
+
"& .cm-ySelection, & .cm-selectionMatch": {
|
|
622
|
+
paddingBlockStart: ".15em",
|
|
623
|
+
paddingBlockEnd: ".15em"
|
|
624
|
+
},
|
|
625
|
+
"& .cm-ySelectionCaret": {
|
|
626
|
+
display: "inline-block",
|
|
627
|
+
insetBlockStart: ".1em",
|
|
628
|
+
blockSize: "1.4em",
|
|
629
|
+
verticalAlign: "top"
|
|
630
|
+
},
|
|
631
|
+
"& .cm-yLineSelection": {
|
|
632
|
+
margin: 0
|
|
633
|
+
},
|
|
634
|
+
//
|
|
635
|
+
// link
|
|
636
|
+
//
|
|
637
|
+
"& .cm-link": {
|
|
638
|
+
color: get2(tokens, "extend.colors.primary.500"),
|
|
639
|
+
textDecorationLine: "underline",
|
|
640
|
+
textDecorationThickness: "1px",
|
|
641
|
+
textUnderlineOffset: "2px",
|
|
642
|
+
borderRadius: ".125rem",
|
|
643
|
+
fontFamily: get2(tokens, "fontFamily.body", []).join(",")
|
|
644
|
+
},
|
|
645
|
+
//
|
|
646
|
+
// tooltip
|
|
647
|
+
//
|
|
648
|
+
"& .cm-tooltip": {
|
|
649
|
+
border: "none"
|
|
650
|
+
},
|
|
651
|
+
// '& .cm-tooltip-below': {},
|
|
652
|
+
// '& .cm-tooltip-autocomplete': {},
|
|
653
|
+
//
|
|
654
|
+
// font size
|
|
655
|
+
// 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.
|
|
656
|
+
//
|
|
657
|
+
...Object.keys(get2(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
|
|
658
|
+
const height = get2(tokens, [
|
|
659
|
+
"extend",
|
|
660
|
+
"fontSize",
|
|
661
|
+
fontSize,
|
|
662
|
+
1,
|
|
663
|
+
"lineHeight"
|
|
664
|
+
]);
|
|
665
|
+
acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
|
|
666
|
+
height
|
|
667
|
+
};
|
|
668
|
+
acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
|
|
669
|
+
height
|
|
670
|
+
};
|
|
671
|
+
acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
|
|
672
|
+
height
|
|
673
|
+
};
|
|
674
|
+
return acc;
|
|
675
|
+
}, {})
|
|
676
|
+
};
|
|
677
|
+
var textTheme = {
|
|
678
|
+
"& .cm-scroller": {
|
|
633
679
|
fontFamily: get2(tokens, "fontFamily.body", []).join(",")
|
|
634
680
|
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
bracketMatching2(),
|
|
641
|
-
closeBrackets2(),
|
|
642
|
-
_placeholder && placeholder2(_placeholder),
|
|
643
|
-
EditorState.allowMultipleSelections.of(true),
|
|
644
|
-
EditorView4.lineWrapping,
|
|
645
|
-
// autocompletion(),
|
|
646
|
-
crosshairCursor(),
|
|
647
|
-
dropCursor(),
|
|
648
|
-
drawSelection(),
|
|
649
|
-
highlightActiveLine(),
|
|
650
|
-
highlightActiveLineGutter(),
|
|
651
|
-
highlightSelectionMatches(),
|
|
652
|
-
highlightSpecialChars(),
|
|
653
|
-
history(),
|
|
654
|
-
indentOnInput(),
|
|
655
|
-
rectangularSelection(),
|
|
656
|
-
keymap.of([
|
|
657
|
-
...closeBracketsKeymap,
|
|
658
|
-
// ...completionKeymap,
|
|
659
|
-
...defaultKeymap,
|
|
660
|
-
...foldKeymap,
|
|
661
|
-
...historyKeymap,
|
|
662
|
-
...lintKeymap,
|
|
663
|
-
...searchKeymap,
|
|
664
|
-
indentWithTab
|
|
665
|
-
]),
|
|
666
|
-
// Main extension.
|
|
667
|
-
// https://github.com/codemirror/lang-markdown
|
|
668
|
-
markdown({
|
|
669
|
-
base: markdownLanguage2,
|
|
670
|
-
codeLanguages: languages,
|
|
671
|
-
extensions: [
|
|
672
|
-
// TODO(burdon): This seems to upgrade the parser.
|
|
673
|
-
// GitHub flavored markdown bundle: Table, TaskList, Strikethrough, and Autolink.
|
|
674
|
-
// https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
|
|
675
|
-
// https://github.github.com/gfm
|
|
676
|
-
// GFM,
|
|
677
|
-
// Custom styling.
|
|
678
|
-
markdownTagsExtension
|
|
679
|
-
]
|
|
680
|
-
}),
|
|
681
|
-
// TODO(thure): All but one rule here apply to both themes; rename or refactor.
|
|
682
|
-
syntaxHighlighting2(markdownHighlightStyle),
|
|
683
|
-
themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2)
|
|
684
|
-
].filter(Boolean);
|
|
681
|
+
};
|
|
682
|
+
var codeTheme = {
|
|
683
|
+
"& .cm-scroller": {
|
|
684
|
+
fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
|
|
685
|
+
}
|
|
685
686
|
};
|
|
686
687
|
|
|
687
688
|
// packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
|
|
@@ -690,22 +691,170 @@ import { EditorView as EditorView5 } from "@codemirror/view";
|
|
|
690
691
|
import { useFocusableGroup } from "@fluentui/react-tabster";
|
|
691
692
|
import { vim } from "@replit/codemirror-vim";
|
|
692
693
|
import defaultsDeep from "lodash.defaultsdeep";
|
|
693
|
-
import
|
|
694
|
-
import
|
|
695
|
-
import {
|
|
694
|
+
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
|
|
695
|
+
import { yCollab } from "y-codemirror.next";
|
|
696
|
+
import { generateName } from "@dxos/display-name";
|
|
696
697
|
import { useThemeContext } from "@dxos/react-ui";
|
|
698
|
+
import { getColorForValue, inputSurface, mx as mx2 } from "@dxos/react-ui-theme";
|
|
699
|
+
var EditorModes = [
|
|
700
|
+
"default",
|
|
701
|
+
"vim"
|
|
702
|
+
];
|
|
703
|
+
var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, extensions = [], slots = defaultSlots, editorMode }, forwardedRef) => {
|
|
704
|
+
const { themeMode } = useThemeContext();
|
|
705
|
+
const tabsterDOMAttribute = useFocusableGroup({
|
|
706
|
+
tabBehavior: "limited"
|
|
707
|
+
});
|
|
708
|
+
const { provider, peer } = model;
|
|
709
|
+
useEffect(() => {
|
|
710
|
+
if (provider && peer) {
|
|
711
|
+
provider.awareness.setLocalStateField("user", {
|
|
712
|
+
name: peer.name ?? generateName(peer.id),
|
|
713
|
+
color: getColorForValue({
|
|
714
|
+
value: peer.id,
|
|
715
|
+
type: "color"
|
|
716
|
+
}),
|
|
717
|
+
colorLight: getColorForValue({
|
|
718
|
+
value: peer.id,
|
|
719
|
+
themeMode,
|
|
720
|
+
type: "highlight"
|
|
721
|
+
})
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
}, [
|
|
725
|
+
provider,
|
|
726
|
+
peer,
|
|
727
|
+
themeMode
|
|
728
|
+
]);
|
|
729
|
+
const [root, setRoot] = useState(null);
|
|
730
|
+
const [state, setState] = useState();
|
|
731
|
+
const [view, setView] = useState();
|
|
732
|
+
useImperativeHandle(forwardedRef, () => ({
|
|
733
|
+
root,
|
|
734
|
+
state,
|
|
735
|
+
view
|
|
736
|
+
}), [
|
|
737
|
+
view,
|
|
738
|
+
state,
|
|
739
|
+
root
|
|
740
|
+
]);
|
|
741
|
+
useEffect(() => {
|
|
742
|
+
if (!root) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const state2 = EditorState2.create({
|
|
746
|
+
doc: model.text(),
|
|
747
|
+
extensions: [
|
|
748
|
+
// TODO(burdon): Factor out VIM mode?
|
|
749
|
+
editorMode === "vim" && vim(),
|
|
750
|
+
// Theme.
|
|
751
|
+
EditorView5.baseTheme(defaultTheme),
|
|
752
|
+
EditorView5.theme(slots?.editor?.theme ?? {}),
|
|
753
|
+
// TODO(burdon): themeMode doesn't change in storybooks.
|
|
754
|
+
EditorView5.darkTheme.of(themeMode === "dark"),
|
|
755
|
+
// Storage and replication.
|
|
756
|
+
yCollab(model.content, model.provider?.awareness),
|
|
757
|
+
// Custom.
|
|
758
|
+
...extensions
|
|
759
|
+
].filter(Boolean)
|
|
760
|
+
});
|
|
761
|
+
view?.destroy();
|
|
762
|
+
setView(new EditorView5({
|
|
763
|
+
state: state2,
|
|
764
|
+
parent: root
|
|
765
|
+
}));
|
|
766
|
+
setState(state2);
|
|
767
|
+
return () => {
|
|
768
|
+
view?.destroy();
|
|
769
|
+
setView(void 0);
|
|
770
|
+
setState(void 0);
|
|
771
|
+
};
|
|
772
|
+
}, [
|
|
773
|
+
root,
|
|
774
|
+
model.content,
|
|
775
|
+
themeMode,
|
|
776
|
+
editorMode
|
|
777
|
+
]);
|
|
778
|
+
const handleKeyUp = useCallback((event) => {
|
|
779
|
+
const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
|
|
780
|
+
switch (key) {
|
|
781
|
+
case "Enter": {
|
|
782
|
+
view?.contentDOM.focus();
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
case "Escape": {
|
|
786
|
+
editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}, [
|
|
791
|
+
view,
|
|
792
|
+
editorMode
|
|
793
|
+
]);
|
|
794
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
795
|
+
key: model.id,
|
|
796
|
+
ref: setRoot,
|
|
797
|
+
tabIndex: 0,
|
|
798
|
+
...slots?.root,
|
|
799
|
+
...editorMode !== "vim" && tabsterDOMAttribute,
|
|
800
|
+
onKeyUp: handleKeyUp
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
var TextEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
804
|
+
const { themeMode } = useThemeContext();
|
|
805
|
+
const slots = defaultsDeep({}, _slots, defaultTextSlots);
|
|
806
|
+
return /* @__PURE__ */ React.createElement(BaseTextEditor, {
|
|
807
|
+
ref: forwardedRef,
|
|
808
|
+
extensions: [
|
|
809
|
+
basicBundle({
|
|
810
|
+
themeMode,
|
|
811
|
+
placeholder: slots?.editor?.placeholder
|
|
812
|
+
}),
|
|
813
|
+
...extensions
|
|
814
|
+
],
|
|
815
|
+
slots,
|
|
816
|
+
...props
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
var MarkdownEditor = /* @__PURE__ */ forwardRef(({ extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
820
|
+
const { themeMode } = useThemeContext();
|
|
821
|
+
const slots = defaultsDeep({}, _slots, defaultSlots);
|
|
822
|
+
return /* @__PURE__ */ React.createElement(BaseTextEditor, {
|
|
823
|
+
ref: forwardedRef,
|
|
824
|
+
extensions: [
|
|
825
|
+
markdownBundle({
|
|
826
|
+
themeMode,
|
|
827
|
+
placeholder: slots?.editor?.placeholder
|
|
828
|
+
}),
|
|
829
|
+
...extensions
|
|
830
|
+
],
|
|
831
|
+
slots,
|
|
832
|
+
...props
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
var defaultSlots = {
|
|
836
|
+
root: {
|
|
837
|
+
className: mx2("p-2", inputSurface)
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
var defaultTextSlots = {
|
|
841
|
+
...defaultSlots,
|
|
842
|
+
editor: {
|
|
843
|
+
theme: textTheme
|
|
844
|
+
}
|
|
845
|
+
};
|
|
697
846
|
|
|
698
847
|
// packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
|
|
699
|
-
import { useEffect,
|
|
700
|
-
import { yCollab } from "y-codemirror.next";
|
|
701
|
-
import { generateName } from "@dxos/display-name";
|
|
848
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
849
|
+
import { yCollab as yCollab2 } from "y-codemirror.next";
|
|
850
|
+
import { generateName as generateName2 } from "@dxos/display-name";
|
|
702
851
|
import { isDocAccessor } from "@dxos/echo-schema";
|
|
703
|
-
import { getColorForValue } from "@dxos/react-ui-theme";
|
|
852
|
+
import { getColorForValue as getColorForValue2 } from "@dxos/react-ui-theme";
|
|
704
853
|
import { YText } from "@dxos/text-model";
|
|
705
854
|
|
|
706
855
|
// packages/ui/react-ui-editor/src/automerge/automerge-plugin/plugin.ts
|
|
707
|
-
import { Annotation, Facet, StateEffect, StateField as
|
|
708
|
-
import { ViewPlugin
|
|
856
|
+
import { Annotation, Facet, StateEffect, StateField as StateField4 } from "@codemirror/state";
|
|
857
|
+
import { ViewPlugin } from "@codemirror/view";
|
|
709
858
|
import * as automerge2 from "@dxos/automerge/automerge";
|
|
710
859
|
|
|
711
860
|
// packages/ui/react-ui-editor/src/automerge/automerge-plugin/PatchSemaphore.ts
|
|
@@ -893,7 +1042,7 @@ var semaphoreFacet = Facet.define({
|
|
|
893
1042
|
combine: (values) => values.at(-1)
|
|
894
1043
|
});
|
|
895
1044
|
var automergePlugin = (handle, path) => {
|
|
896
|
-
const stateField =
|
|
1045
|
+
const stateField = StateField4.define({
|
|
897
1046
|
create: () => ({
|
|
898
1047
|
lastHeads: automerge2.getHeads(handle.docSync()),
|
|
899
1048
|
unreconciledTransactions: [],
|
|
@@ -923,7 +1072,7 @@ var automergePlugin = (handle, path) => {
|
|
|
923
1072
|
}
|
|
924
1073
|
});
|
|
925
1074
|
const semaphore = new PatchSemaphore(stateField);
|
|
926
|
-
const viewPlugin =
|
|
1075
|
+
const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
|
|
927
1076
|
constructor(view) {
|
|
928
1077
|
this._handleChange = () => {
|
|
929
1078
|
this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
|
|
@@ -955,25 +1104,25 @@ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
|
|
|
955
1104
|
|
|
956
1105
|
// packages/ui/react-ui-editor/src/hooks/useCollaboration.ts
|
|
957
1106
|
var useCollaboration = (model, themeMode) => {
|
|
958
|
-
const { provider, peer
|
|
959
|
-
const extension =
|
|
1107
|
+
const { content, provider, peer } = model;
|
|
1108
|
+
const [extension] = useState2(() => {
|
|
960
1109
|
if (content instanceof YText) {
|
|
961
|
-
return
|
|
1110
|
+
return yCollab2(content, provider?.awareness);
|
|
962
1111
|
} else if (isDocAccessor(content)) {
|
|
963
1112
|
return automergePlugin(content.handle, content.path);
|
|
964
1113
|
} else {
|
|
965
1114
|
return void 0;
|
|
966
1115
|
}
|
|
967
|
-
}
|
|
968
|
-
|
|
1116
|
+
});
|
|
1117
|
+
useEffect2(() => {
|
|
969
1118
|
if (provider && peer) {
|
|
970
1119
|
provider.awareness.setLocalStateField("user", {
|
|
971
|
-
name: peer.name ??
|
|
972
|
-
color:
|
|
1120
|
+
name: peer.name ?? generateName2(peer.id),
|
|
1121
|
+
color: getColorForValue2({
|
|
973
1122
|
value: peer.id,
|
|
974
1123
|
type: "color"
|
|
975
1124
|
}),
|
|
976
|
-
colorLight:
|
|
1125
|
+
colorLight: getColorForValue2({
|
|
977
1126
|
value: peer.id,
|
|
978
1127
|
themeMode,
|
|
979
1128
|
type: "highlight"
|
|
@@ -989,7 +1138,7 @@ var useCollaboration = (model, themeMode) => {
|
|
|
989
1138
|
};
|
|
990
1139
|
|
|
991
1140
|
// packages/ui/react-ui-editor/src/hooks/useTextModel.ts
|
|
992
|
-
import { useMemo
|
|
1141
|
+
import { useMemo } from "react";
|
|
993
1142
|
import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
|
|
994
1143
|
|
|
995
1144
|
// packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
|
|
@@ -1036,7 +1185,7 @@ import * as encoding from "lib0/encoding";
|
|
|
1036
1185
|
import { Observable } from "lib0/observable";
|
|
1037
1186
|
import * as awarenessProtocol from "y-protocols/awareness";
|
|
1038
1187
|
import { log } from "@dxos/log";
|
|
1039
|
-
var __dxlog_file = "/home/
|
|
1188
|
+
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
|
|
1040
1189
|
var messageAwareness = 1;
|
|
1041
1190
|
var messageQueryAwareness = 3;
|
|
1042
1191
|
var SpaceAwarenessProvider = class extends Observable {
|
|
@@ -1132,7 +1281,7 @@ var SpaceAwarenessProvider = class extends Observable {
|
|
|
1132
1281
|
|
|
1133
1282
|
// packages/ui/react-ui-editor/src/hooks/useTextModel.ts
|
|
1134
1283
|
var useTextModel = ({ identity, space, text }) => {
|
|
1135
|
-
const provider =
|
|
1284
|
+
const provider = useMemo(() => {
|
|
1136
1285
|
if (isActualAutomergeObject(text)) {
|
|
1137
1286
|
return void 0;
|
|
1138
1287
|
}
|
|
@@ -1150,7 +1299,7 @@ var useTextModel = ({ identity, space, text }) => {
|
|
|
1150
1299
|
text,
|
|
1151
1300
|
text?.doc
|
|
1152
1301
|
]);
|
|
1153
|
-
const content =
|
|
1302
|
+
const content = useMemo(() => {
|
|
1154
1303
|
if (isActualAutomergeObject(text)) {
|
|
1155
1304
|
const obj = text;
|
|
1156
1305
|
return getRawDoc(obj, [
|
|
@@ -1162,11 +1311,15 @@ var useTextModel = ({ identity, space, text }) => {
|
|
|
1162
1311
|
}, [
|
|
1163
1312
|
text
|
|
1164
1313
|
]);
|
|
1314
|
+
if (!text?.doc || !text?.content) {
|
|
1315
|
+
return void 0;
|
|
1316
|
+
}
|
|
1165
1317
|
if (isActualAutomergeObject(text)) {
|
|
1166
1318
|
const obj = text;
|
|
1167
1319
|
return {
|
|
1168
1320
|
id: obj.id,
|
|
1169
1321
|
content,
|
|
1322
|
+
text: () => content.toString(),
|
|
1170
1323
|
provider: void 0,
|
|
1171
1324
|
peer: identity ? {
|
|
1172
1325
|
id: identity.identityKey.toHex(),
|
|
@@ -1174,142 +1327,16 @@ var useTextModel = ({ identity, space, text }) => {
|
|
|
1174
1327
|
} : void 0
|
|
1175
1328
|
};
|
|
1176
1329
|
}
|
|
1177
|
-
if (!text?.doc || !text?.content) {
|
|
1178
|
-
return void 0;
|
|
1179
|
-
}
|
|
1180
1330
|
return {
|
|
1181
1331
|
id: text.doc.guid,
|
|
1182
1332
|
content: text.content,
|
|
1183
|
-
|
|
1333
|
+
text: () => text.content.toString(),
|
|
1184
1334
|
peer: identity ? {
|
|
1185
1335
|
id: identity.identityKey.toHex(),
|
|
1186
1336
|
name: identity.profile?.displayName
|
|
1187
1337
|
} : void 0
|
|
1188
1338
|
};
|
|
1189
1339
|
};
|
|
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
1340
|
export {
|
|
1314
1341
|
BaseTextEditor,
|
|
1315
1342
|
Doc,
|
|
@@ -1320,32 +1347,26 @@ export {
|
|
|
1320
1347
|
TextKind,
|
|
1321
1348
|
YText2 as YText,
|
|
1322
1349
|
YXmlFragment,
|
|
1350
|
+
autocomplete,
|
|
1323
1351
|
basicBundle,
|
|
1324
|
-
|
|
1325
|
-
chalky,
|
|
1326
|
-
coral,
|
|
1327
|
-
createHyperlinkTooltip,
|
|
1352
|
+
codeTheme,
|
|
1328
1353
|
cursorColor,
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
hyperlinkWidget,
|
|
1335
|
-
invalid,
|
|
1336
|
-
ivory,
|
|
1337
|
-
malibu,
|
|
1354
|
+
defaultSlots,
|
|
1355
|
+
defaultTextSlots,
|
|
1356
|
+
defaultTheme,
|
|
1357
|
+
link,
|
|
1358
|
+
listener,
|
|
1338
1359
|
markdownBundle,
|
|
1339
1360
|
markdownHighlightStyle,
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1361
|
+
markdownTags,
|
|
1362
|
+
markdownTagsExtension,
|
|
1363
|
+
statefield,
|
|
1364
|
+
styles,
|
|
1344
1365
|
tags2 as tags,
|
|
1345
|
-
|
|
1366
|
+
tasklist,
|
|
1367
|
+
textTheme,
|
|
1368
|
+
tooltip,
|
|
1346
1369
|
useCollaboration,
|
|
1347
|
-
useTextModel
|
|
1348
|
-
violet,
|
|
1349
|
-
whiskey
|
|
1370
|
+
useTextModel
|
|
1350
1371
|
};
|
|
1351
1372
|
//# sourceMappingURL=index.mjs.map
|