@dxos/react-ui-editor 0.3.11-main.fbbdc2a → 0.3.11-main.fe789c1
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 +412 -516
- 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 +9 -5
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +12 -17
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
- package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/index.d.ts +1 -0
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
- package/dist/types/src/extensions/code.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +2 -2
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/{util → extensions}/cursor.d.ts +9 -1
- package/dist/types/src/extensions/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/hr.d.ts.map +1 -1
- package/dist/types/src/extensions/image.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +1 -1
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/link.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
- package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
- package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
- package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +0 -5
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/hooks/yjs.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts +1 -0
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/dist/types/src/util.d.ts.map +1 -0
- package/package.json +23 -22
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +25 -23
- package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
- package/src/components/TextEditor/TextEditor.tsx +68 -64
- package/src/components/TextEditor/comments.stories.tsx +315 -0
- package/src/extensions/autocomplete.ts +18 -17
- package/src/extensions/automerge/automerge.ts +2 -2
- package/src/extensions/automerge/cursor.ts +2 -1
- package/src/extensions/automerge/index.ts +1 -0
- package/src/extensions/awareness.ts +25 -32
- package/src/extensions/code.ts +14 -7
- package/src/extensions/comments.ts +23 -119
- package/src/extensions/cursor.ts +46 -0
- package/src/extensions/hr.ts +4 -5
- package/src/extensions/image.ts +40 -22
- package/src/extensions/index.ts +1 -1
- package/src/extensions/link.ts +86 -88
- package/src/extensions/markdown/highlight.ts +3 -12
- package/src/extensions/table.ts +10 -4
- package/src/extensions/tasklist.ts +3 -5
- package/src/extensions/yjs/cursor.ts +2 -1
- package/src/extensions/yjs/yjs.ts +2 -2
- package/src/hooks/useTextModel.ts +4 -7
- package/src/hooks/yjs.ts +1 -23
- package/src/index.ts +3 -0
- package/src/styles/markdown.ts +0 -2
- package/src/styles/tokens.ts +3 -0
- package/src/themes/default.ts +52 -71
- package/dist/types/src/extensions/mermaid.d.ts +0 -3
- package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
- package/dist/types/src/util/cursor.d.ts.map +0 -1
- package/dist/types/src/util/index.d.ts +0 -3
- package/dist/types/src/util/index.d.ts.map +0 -1
- package/dist/types/src/util/util.d.ts.map +0 -1
- package/src/extensions/mermaid.ts +0 -11
- package/src/util/cursor.ts +0 -29
- package/src/util/index.ts +0 -6
- /package/dist/types/src/{util/util.d.ts → util.d.ts} +0 -0
- /package/src/{util/util.ts → util.ts} +0 -0
package/src/extensions/link.ts
CHANGED
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
//
|
|
5
5
|
|
|
6
6
|
import { syntaxTree } from '@codemirror/language';
|
|
7
|
+
import { type Extension, RangeSetBuilder } from '@codemirror/state';
|
|
7
8
|
import {
|
|
8
|
-
|
|
9
|
-
type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
Decoration,
|
|
10
|
+
type EditorView,
|
|
11
|
+
WidgetType,
|
|
12
|
+
hoverTooltip,
|
|
13
|
+
ViewPlugin,
|
|
14
|
+
type ViewUpdate,
|
|
15
|
+
type DecorationSet,
|
|
16
|
+
} from '@codemirror/view';
|
|
17
|
+
import { type SyntaxNode } from '@lezer/common';
|
|
16
18
|
|
|
17
19
|
import { tooltipContent } from '@dxos/react-ui-theme';
|
|
18
20
|
|
|
19
|
-
const markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
|
|
20
|
-
|
|
21
21
|
// Adapted from:
|
|
22
22
|
// https://codemirror.net/try/?c=aW1wb3J0IHsgYmFzaWNTZXR1cCwgRWRpdG9yVmlldyB9IGZyb20gImNvZGVtaXJyb3IiCmltcG9ydCB7IG1hcmtkb3duIH0gZnJvbSAiQGNvZGVtaXJyb3IvbGFuZy1tYXJrZG93biIKaW1wb3J0IHsgc3ludGF4VHJlZSB9IGZyb20gIkBjb2RlbWlycm9yL2xhbmd1YWdlIjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIFJhbmdlU2V0QnVpbGRlciwgU3RhdGVGaWVsZCB9IGZyb20gIkBjb2RlbWlycm9yL3N0YXRlIjsKaW1wb3J0IHsgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSB9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciOwoKY2xhc3MgTGluayBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKHRleHQsIHVybCkgewogICAgc3VwZXIoKQogICAgdGhpcy50ZXh0ID0gdGV4dAogICAgdGhpcy51cmwgPSB1cmwKICB9CiAgZXEob3RoZXIpIHsgcmV0dXJuIHRoaXMudGV4dCA9PSBvdGhlci50ZXh0ICYmIHRoaXMudXJsID09IG90aGVyLnVybCB9CiAgdG9ET00oKSB7CiAgICBsZXQgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKQogICAgbGluay50ZXh0Q29udGVudCA9IHRoaXMudGV4dAogICAgbGluay5ocmVmID0gdGhpcy51cmwKICAgIHJldHVybiBsaW5rOwogIH0KfQoKbGV0IGRlY29yYXRpb25zRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShfLCB0cikgewogICAgY29uc3QgYnVpbGRlciA9IG5ldyBSYW5nZVNldEJ1aWxkZXIoKTsKICAgIGxldCBjdXJzb3IgPSB0ci5zdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkOwogICAgc3ludGF4VHJlZSh0ci5zdGF0ZSkuaXRlcmF0ZSh7CiAgICAgIGVudGVyOiAobm9kZSkgPT4gewogICAgICAgIGlmICgoY3Vyc29yIDwgbm9kZS5mcm9tIHx8IGN1cnNvciA+IG5vZGUudG8pICYmIG5vZGUubmFtZSA9PSAiTGluayIpIHsKICAgICAgICAgIGxldCBtYXJrcyA9IG5vZGUubm9kZS5nZXRDaGlsZHJlbigiTGlua01hcmsiKTsKICAgICAgICAgIGxldCB0ZXh0ID0gbWFya3MubGVuZ3RoID49IDIgPyB0ci5zdGF0ZS5zbGljZURvYyhtYXJrc1swXS50bywgbWFya3NbMV0uZnJvbSkgOiAiIgoKICAgICAgICAgIGxldCB1cmxOb2RlID0gbm9kZS5ub2RlLmdldENoaWxkKCJVUkwiKTsKICAgICAgICAgIGxldCB1cmwgPSB1cmxOb2RlID8gdHIuc3RhdGUuc2xpY2VEb2ModXJsTm9kZS5mcm9tLCB1cmxOb2RlLnRvKSA6ICIiCiAgICAgICAgICBidWlsZGVyLmFkZChub2RlLmZyb20sIG5vZGUudG8sIERlY29yYXRpb24ucmVwbGFjZSh7CiAgICAgICAgICAgIHdpZGdldDogbmV3IExpbmsodGV4dCwgdXJsKSwKICAgICAgICAgIH0pLAogICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfSwKICAgIH0pOwogICAgcmV0dXJuIGJ1aWxkZXIuZmluaXNoKCk7CiAgfSwKICBwcm92aWRlOiAoZikgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGYpLAp9KTsKCmxldCB2aWV3ID0gbmV3IEVkaXRvclZpZXcoewogIGRvYzogIiMgSGVsbG8gV29ybGRcblxuW1Rlc3RdKGh0dHBzOi8vZXhhbXBsZS5jb20pXG5cblxuIiwKICBleHRlbnNpb25zOiBbZGVjb3JhdGlvbnNGaWVsZCwgbWFya2Rvd24oKV0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5LAp9KTsKCnZpZXcuZm9jdXMoKTs=
|
|
23
23
|
// https://discuss.codemirror.net/t/link-widget-is-clicked-only-if-editor-is-out-of-focus/7433
|
|
@@ -34,11 +34,24 @@ export type LinkOptions = {
|
|
|
34
34
|
*/
|
|
35
35
|
export const link = (options: LinkOptions = {}): Extension => {
|
|
36
36
|
const extensions: Extension[] = [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
ViewPlugin.fromClass(
|
|
38
|
+
class {
|
|
39
|
+
decorations: DecorationSet;
|
|
40
|
+
|
|
41
|
+
constructor(view: EditorView) {
|
|
42
|
+
this.decorations = buildDecorations(view, options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
update(update: ViewUpdate) {
|
|
46
|
+
if (update.docChanged || update.viewportChanged || update.selectionSet) {
|
|
47
|
+
this.decorations = buildDecorations(update.view, options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
decorations: (v) => v.decorations,
|
|
53
|
+
},
|
|
54
|
+
),
|
|
42
55
|
];
|
|
43
56
|
|
|
44
57
|
if (options.onHover) {
|
|
@@ -46,41 +59,25 @@ export const link = (options: LinkOptions = {}): Extension => {
|
|
|
46
59
|
// https://codemirror.net/examples/tooltip
|
|
47
60
|
// https://codemirror.net/docs/ref/#view.hoverTooltip
|
|
48
61
|
// https://github.com/codemirror/view/blob/main/src/tooltip.ts
|
|
49
|
-
hoverTooltip((view, pos) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!match) {
|
|
58
|
-
match = undefined;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
idx = text.indexOf(match[0]);
|
|
63
|
-
if (p >= idx && p < idx + match[0].length) {
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
idx += match[0].length;
|
|
68
|
-
} while (true);
|
|
69
|
-
|
|
70
|
-
if (!match) {
|
|
62
|
+
hoverTooltip((view, pos, side) => {
|
|
63
|
+
const syntax = syntaxTree(view.state).resolveInner(pos, side);
|
|
64
|
+
let link = null;
|
|
65
|
+
for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
|
|
66
|
+
link = node.name === 'Link' ? node : null;
|
|
67
|
+
}
|
|
68
|
+
const url = link && link.getChild('URL');
|
|
69
|
+
if (!url || !link) {
|
|
71
70
|
return null;
|
|
72
71
|
}
|
|
73
|
-
|
|
74
|
-
const [, , url] = match ?? [];
|
|
75
|
-
|
|
72
|
+
const urlText = view.state.sliceDoc(url.from, url.to);
|
|
76
73
|
return {
|
|
77
|
-
pos:
|
|
78
|
-
end:
|
|
74
|
+
pos: link.from,
|
|
75
|
+
end: link.to,
|
|
79
76
|
above: true,
|
|
80
77
|
create: () => {
|
|
81
78
|
const el = document.createElement('div');
|
|
82
79
|
el.className = tooltipContent({}, 'pli-2 plb-1');
|
|
83
|
-
options.onHover!(el,
|
|
80
|
+
options.onHover!(el, urlText);
|
|
84
81
|
return { dom: el, offset: { x: 0, y: 4 } };
|
|
85
82
|
},
|
|
86
83
|
};
|
|
@@ -92,19 +89,15 @@ export const link = (options: LinkOptions = {}): Extension => {
|
|
|
92
89
|
};
|
|
93
90
|
|
|
94
91
|
class LinkButton extends WidgetType {
|
|
95
|
-
constructor(
|
|
96
|
-
private readonly _pos: number,
|
|
97
|
-
private readonly _url: string,
|
|
98
|
-
private readonly _onAttach: LinkOptions['onRender'],
|
|
99
|
-
) {
|
|
92
|
+
constructor(private readonly _url: string, private readonly _onAttach: LinkOptions['onRender']) {
|
|
100
93
|
super();
|
|
101
94
|
}
|
|
102
95
|
|
|
103
|
-
override eq(other:
|
|
104
|
-
return this.
|
|
96
|
+
override eq(other: this) {
|
|
97
|
+
return this._url === other._url;
|
|
105
98
|
}
|
|
106
99
|
|
|
107
|
-
toDOM(view: EditorView) {
|
|
100
|
+
override toDOM(view: EditorView) {
|
|
108
101
|
const el = document.createElement('span');
|
|
109
102
|
this._onAttach!(el, this._url);
|
|
110
103
|
return el;
|
|
@@ -116,11 +109,11 @@ class LinkText extends WidgetType {
|
|
|
116
109
|
super();
|
|
117
110
|
}
|
|
118
111
|
|
|
119
|
-
override eq(other:
|
|
112
|
+
override eq(other: this) {
|
|
120
113
|
return this._url === other._url;
|
|
121
114
|
}
|
|
122
115
|
|
|
123
|
-
toDOM(view: EditorView) {
|
|
116
|
+
override toDOM(view: EditorView) {
|
|
124
117
|
const link = document.createElement('a');
|
|
125
118
|
link.setAttribute('class', 'cm-link');
|
|
126
119
|
link.textContent = this._text;
|
|
@@ -147,46 +140,51 @@ class LinkText extends WidgetType {
|
|
|
147
140
|
* possibly overlapping ranges in such a way that they can efficiently be mapped though document changes.
|
|
148
141
|
* https://codemirror.net/docs/ref/#state
|
|
149
142
|
*/
|
|
150
|
-
const
|
|
151
|
-
const builder = new RangeSetBuilder();
|
|
143
|
+
const buildDecorations = (view: EditorView, options: LinkOptions): DecorationSet => {
|
|
144
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
145
|
+
const { state } = view;
|
|
152
146
|
const cursor = state.selection.main.head;
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
148
|
+
for (const { from, to } of view.visibleRanges) {
|
|
149
|
+
syntaxTree(state).iterate({
|
|
150
|
+
enter: (node) => {
|
|
151
|
+
// Check if cursor is inside text.
|
|
152
|
+
if (node.name === 'Link') {
|
|
153
|
+
const marks = node.node.getChildren('LinkMark');
|
|
154
|
+
const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
|
|
155
|
+
const urlNode = node.node.getChild('URL');
|
|
156
|
+
const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
|
|
157
|
+
if (!url) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
165
160
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
161
|
+
if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
|
|
162
|
+
builder.add(
|
|
163
|
+
node.from,
|
|
164
|
+
node.to,
|
|
165
|
+
Decoration.replace({
|
|
166
|
+
widget: new LinkText(text, options.link ? url : undefined),
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
175
170
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
171
|
+
if (options.onRender) {
|
|
172
|
+
builder.add(
|
|
173
|
+
node.to,
|
|
174
|
+
node.to,
|
|
175
|
+
Decoration.replace({
|
|
176
|
+
widget: new LinkButton(url, options.onRender),
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
185
180
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
from,
|
|
185
|
+
to,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
190
188
|
|
|
191
189
|
return builder.finish();
|
|
192
190
|
};
|
|
@@ -6,19 +6,18 @@ import { markdownLanguage } from '@codemirror/lang-markdown';
|
|
|
6
6
|
import { HighlightStyle } from '@codemirror/language';
|
|
7
7
|
import { tags, styleTags, Tag } from '@lezer/highlight';
|
|
8
8
|
import { type MarkdownConfig, Table } from '@lezer/markdown';
|
|
9
|
-
import get from 'lodash.get';
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
11
|
blockquote,
|
|
13
12
|
bold,
|
|
14
13
|
code,
|
|
15
14
|
codeMark,
|
|
15
|
+
getToken,
|
|
16
16
|
heading,
|
|
17
17
|
inlineUrl,
|
|
18
18
|
italic,
|
|
19
19
|
mark,
|
|
20
20
|
strikethrough,
|
|
21
|
-
tokens,
|
|
22
21
|
} from '../../styles';
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -32,7 +31,6 @@ export const markdownTags = {
|
|
|
32
31
|
CodeText: Tag.define(),
|
|
33
32
|
EmphasisMark: Tag.define(),
|
|
34
33
|
HeaderMark: Tag.define(),
|
|
35
|
-
// HorizontalRule: Tag.define(),
|
|
36
34
|
InlineCode: Tag.define(),
|
|
37
35
|
LinkLabel: Tag.define(),
|
|
38
36
|
LinkReference: Tag.define(),
|
|
@@ -178,13 +176,6 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
|
|
|
178
176
|
class: blockquote,
|
|
179
177
|
},
|
|
180
178
|
|
|
181
|
-
// TODO(burdon): Replace with extension.
|
|
182
|
-
// {
|
|
183
|
-
// tag: [markdownTags.HorizontalRule],
|
|
184
|
-
// class: mx(horizontalRule, readonly && '-indent-[100rem]'),
|
|
185
|
-
// },
|
|
186
|
-
|
|
187
|
-
// TODO(burdon): Only if being edited.
|
|
188
179
|
{
|
|
189
180
|
tag: [markdownTags.TableCell],
|
|
190
181
|
class: 'font-mono',
|
|
@@ -193,12 +184,12 @@ export const markdownHighlightStyle = (readonly?: boolean) => {
|
|
|
193
184
|
// Main content, paragraphs, etc.
|
|
194
185
|
// {
|
|
195
186
|
// tag: [tags.content],
|
|
196
|
-
// fontFamily:
|
|
187
|
+
// fontFamily: getToken('fontFamily.body', []).join(','),
|
|
197
188
|
// },
|
|
198
189
|
],
|
|
199
190
|
{
|
|
200
191
|
scope: markdownLanguage,
|
|
201
|
-
all: { fontFamily:
|
|
192
|
+
all: { fontFamily: getToken('fontFamily.body', []).join(',') },
|
|
202
193
|
},
|
|
203
194
|
);
|
|
204
195
|
};
|
package/src/extensions/table.ts
CHANGED
|
@@ -81,7 +81,6 @@ const update = (state: EditorState, options: TableOptions) => {
|
|
|
81
81
|
table.from,
|
|
82
82
|
table.to,
|
|
83
83
|
Decoration.replace({
|
|
84
|
-
block: true,
|
|
85
84
|
widget: new TableWidget(table),
|
|
86
85
|
}),
|
|
87
86
|
);
|
|
@@ -98,11 +97,14 @@ class TableWidget extends WidgetType {
|
|
|
98
97
|
super();
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
override eq(other:
|
|
102
|
-
return
|
|
100
|
+
override eq(other: this) {
|
|
101
|
+
return (
|
|
102
|
+
this._table.header?.join() === other._table.header?.join() &&
|
|
103
|
+
this._table.rows?.join() === other._table.rows?.join()
|
|
104
|
+
);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
toDOM(view: EditorView) {
|
|
107
|
+
override toDOM(view: EditorView) {
|
|
106
108
|
const table = document.createElement('table');
|
|
107
109
|
|
|
108
110
|
{
|
|
@@ -129,4 +131,8 @@ class TableWidget extends WidgetType {
|
|
|
129
131
|
|
|
130
132
|
return table;
|
|
131
133
|
}
|
|
134
|
+
|
|
135
|
+
override ignoreEvent(e: Event) {
|
|
136
|
+
return !/^mouse/.test(e.type);
|
|
137
|
+
}
|
|
132
138
|
}
|
|
@@ -22,7 +22,6 @@ const styles = EditorView.baseTheme({
|
|
|
22
22
|
|
|
23
23
|
class CheckboxWidget extends WidgetType {
|
|
24
24
|
constructor(
|
|
25
|
-
private readonly _pos: number,
|
|
26
25
|
private _checked: boolean,
|
|
27
26
|
private readonly _indent: number,
|
|
28
27
|
private readonly _onCheck?: (check: boolean) => void,
|
|
@@ -30,11 +29,11 @@ class CheckboxWidget extends WidgetType {
|
|
|
30
29
|
super();
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
override eq(other:
|
|
34
|
-
return this.
|
|
32
|
+
override eq(other: this) {
|
|
33
|
+
return this._checked === other._checked && this._indent === other._indent;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
toDOM(view: EditorView) {
|
|
36
|
+
override toDOM(view: EditorView) {
|
|
38
37
|
const wrap = document.createElement('span');
|
|
39
38
|
wrap.className = 'cm-task-item';
|
|
40
39
|
wrap.setAttribute('aria-hidden', 'true');
|
|
@@ -73,7 +72,6 @@ export const tasklist = (options: TasklistOptions = {}) => {
|
|
|
73
72
|
const checked = match[2] === 'x' || match[2] === 'X';
|
|
74
73
|
return Decoration.replace({
|
|
75
74
|
widget: new CheckboxWidget(
|
|
76
|
-
pos,
|
|
77
75
|
checked,
|
|
78
76
|
indent,
|
|
79
77
|
view.state.readOnly
|
|
@@ -6,12 +6,13 @@ import * as Y from 'yjs';
|
|
|
6
6
|
|
|
7
7
|
import { arrayToString, stringToArray } from '@dxos/util';
|
|
8
8
|
|
|
9
|
-
import { type CursorConverter } from '
|
|
9
|
+
import { type CursorConverter } from '../cursor';
|
|
10
10
|
|
|
11
11
|
export const cursorConverter = (text: Y.Text): CursorConverter => ({
|
|
12
12
|
toCursor: (index, assoc) => {
|
|
13
13
|
return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
|
|
14
14
|
},
|
|
15
|
+
|
|
15
16
|
fromCursor: (cursor) => {
|
|
16
17
|
return (
|
|
17
18
|
Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc!)?.index ??
|
|
@@ -8,9 +8,9 @@ import type * as YP from 'y-protocols/awareness';
|
|
|
8
8
|
import type { YText } from '@dxos/text-model';
|
|
9
9
|
|
|
10
10
|
import { cursorConverter } from './cursor';
|
|
11
|
-
import {
|
|
11
|
+
import { Cursor } from '../cursor';
|
|
12
12
|
|
|
13
13
|
export const yjs = (content: YText, awareness?: YP.Awareness) => [
|
|
14
|
-
|
|
14
|
+
Cursor.converter.of(cursorConverter(content)),
|
|
15
15
|
yCollab(content, awareness),
|
|
16
16
|
];
|
|
@@ -30,7 +30,7 @@ export type Range = {
|
|
|
30
30
|
|
|
31
31
|
export type CommentRange = {
|
|
32
32
|
id: string;
|
|
33
|
-
cursor: string;
|
|
33
|
+
cursor: string; // TODO(burdon): Rename range.
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
// TODO(wittjosiah): Factor out to common package? @dxos/react-client?
|
|
@@ -41,13 +41,10 @@ export type EditorModel = {
|
|
|
41
41
|
|
|
42
42
|
// TODO(burdon): Remove.
|
|
43
43
|
content: string | YText | YXmlFragment | DocAccessor;
|
|
44
|
-
|
|
45
|
-
* @deprecated Use CursorConverter.
|
|
46
|
-
*/
|
|
47
|
-
getCursorFromRange?: (value: Range) => string;
|
|
48
|
-
getRangeFromCursor?: (cursor: string) => Range | undefined;
|
|
44
|
+
|
|
49
45
|
// TODO(burdon): Move into extension.
|
|
50
46
|
awareness?: YP.Awareness;
|
|
47
|
+
|
|
51
48
|
// TODO(burdon): Remove.
|
|
52
49
|
peer?: {
|
|
53
50
|
id: string;
|
|
@@ -66,7 +63,7 @@ export type UseTextModelProps = {
|
|
|
66
63
|
// TODO(wittjosiah): Factor out to common package? @dxos/react-client?
|
|
67
64
|
export const useTextModel = (props: UseTextModelProps): EditorModel | undefined => {
|
|
68
65
|
const { identity, space, text } = props;
|
|
69
|
-
const [model, setModel] = useState<EditorModel | undefined>(
|
|
66
|
+
const [model, setModel] = useState<EditorModel | undefined>();
|
|
70
67
|
useEffect(() => setModel(createModel(props)), [identity, space, text]);
|
|
71
68
|
return model;
|
|
72
69
|
};
|
package/src/hooks/yjs.ts
CHANGED
|
@@ -6,7 +6,6 @@ import * as decoding from 'lib0/decoding';
|
|
|
6
6
|
import * as encoding from 'lib0/encoding';
|
|
7
7
|
import { Observable } from 'lib0/observable';
|
|
8
8
|
import * as YP from 'y-protocols/awareness';
|
|
9
|
-
import * as Y from 'yjs';
|
|
10
9
|
import { type Doc } from 'yjs';
|
|
11
10
|
|
|
12
11
|
import { invariant } from '@dxos/invariant';
|
|
@@ -14,9 +13,8 @@ import { log } from '@dxos/log';
|
|
|
14
13
|
import { type Space } from '@dxos/react-client/echo';
|
|
15
14
|
import { type GossipMessage } from '@dxos/react-client/mesh';
|
|
16
15
|
import type { YText } from '@dxos/text-model';
|
|
17
|
-
import { arrayToString, stringToArray } from '@dxos/util';
|
|
18
16
|
|
|
19
|
-
import { type EditorModel, modelState, type
|
|
17
|
+
import { type EditorModel, modelState, type UseTextModelProps } from './useTextModel';
|
|
20
18
|
import { yjs } from '../extensions';
|
|
21
19
|
|
|
22
20
|
export const createYjsModel = ({ identity, space, text }: UseTextModelProps): EditorModel => {
|
|
@@ -29,26 +27,6 @@ export const createYjsModel = ({ identity, space, text }: UseTextModelProps): Ed
|
|
|
29
27
|
id: text.doc.guid,
|
|
30
28
|
content: text.content,
|
|
31
29
|
text: () => text.content!.toString(),
|
|
32
|
-
// https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
|
|
33
|
-
// TODO(dmaretskyi): Refactor as cursor-converter.
|
|
34
|
-
getCursorFromRange: (range: Range) => {
|
|
35
|
-
const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.from));
|
|
36
|
-
const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.to, -1));
|
|
37
|
-
return [arrayToString(from), arrayToString(to)].join(':');
|
|
38
|
-
},
|
|
39
|
-
getRangeFromCursor: (cursor: string) => {
|
|
40
|
-
invariant(text.doc);
|
|
41
|
-
const parts = cursor.split(':');
|
|
42
|
-
const from = Y.createAbsolutePositionFromRelativePosition(
|
|
43
|
-
Y.decodeRelativePosition(stringToArray(parts[0])),
|
|
44
|
-
text.doc,
|
|
45
|
-
);
|
|
46
|
-
const to = Y.createAbsolutePositionFromRelativePosition(
|
|
47
|
-
Y.decodeRelativePosition(stringToArray(parts[1])),
|
|
48
|
-
text.doc,
|
|
49
|
-
);
|
|
50
|
-
return from && to ? { from: from.index, to: to.index } : undefined;
|
|
51
|
-
},
|
|
52
30
|
extension: [modelState.init(() => model), yjs(text.content as YText, provider?.awareness)],
|
|
53
31
|
awareness: provider?.awareness,
|
|
54
32
|
peer: identity
|
package/src/index.ts
CHANGED
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
|
|
6
6
|
export { Doc, YText, YXmlFragment } from '@dxos/text-model';
|
|
7
7
|
export { type Extension } from '@codemirror/state';
|
|
8
|
+
export { type EditorView } from '@codemirror/view';
|
|
8
9
|
|
|
9
10
|
export * from './components';
|
|
10
11
|
export * from './extensions';
|
|
11
12
|
export * from './hooks';
|
|
13
|
+
export { getToken } from './styles';
|
|
12
14
|
export * from './themes';
|
|
15
|
+
export * from './util';
|
package/src/styles/markdown.ts
CHANGED
|
@@ -39,10 +39,8 @@ export const codeBlock = 'mlb-2 font-mono bg-neutral-500/10 p-3 rounded';
|
|
|
39
39
|
|
|
40
40
|
export const inlineUrl = mx(code, 'px-1');
|
|
41
41
|
|
|
42
|
-
// TODO(burdon): Replace with widget.
|
|
43
42
|
export const blockquote = mx('pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30', light);
|
|
44
43
|
|
|
45
|
-
// TODO(burdon): Replace with widget.
|
|
46
44
|
export const horizontalRule =
|
|
47
45
|
'flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800';
|
|
48
46
|
|
package/src/styles/tokens.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import get from 'lodash.get';
|
|
5
6
|
import type { StyleSpec } from 'style-mod';
|
|
6
7
|
|
|
7
8
|
import { tailwindConfig, type TailwindConfig } from '@dxos/react-ui-theme';
|
|
@@ -12,3 +13,5 @@ export type ThemeStyles = {
|
|
|
12
13
|
|
|
13
14
|
// TODO(thure): Why export the whole theme? Can this be done differently?
|
|
14
15
|
export const tokens: TailwindConfig['theme'] = tailwindConfig({}).theme;
|
|
16
|
+
|
|
17
|
+
export const getToken = (path: string, defaultValue: any = undefined) => get(tokens, path, defaultValue);
|