Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/cursors/other-users.ts
1697 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
// Support display of other user's cursors
7
8
import { useMemo } from "react";
9
import { Map } from "immutable";
10
import { Editor, Node, Point, Text } from "slate";
11
import { getProfile } from "@cocalc/frontend/jupyter/cursors";
12
import { redux } from "@cocalc/frontend/app-framework";
13
import { markdownPositionToSlatePoint } from "../sync";
14
import { SearchHook } from "../search";
15
import { SlateEditor } from "../editable-markdown";
16
17
interface OtherCursor {
18
offset: number;
19
name: string;
20
color: string;
21
}
22
23
export const useCursorDecorate = ({
24
editor,
25
value,
26
cursors,
27
search, // passed in since can only have one decorate function.
28
}: {
29
editor: SlateEditor;
30
value: string;
31
cursors?: Map<string, any>;
32
search: SearchHook;
33
// NOTE: Passing search in is really ugly but we are doing it because slate
34
// can only have one decorate function at once and both search and cursors
35
// use decorate at once. **NOTE/TODO:** if a text node has a search result in
36
// it **and** a cursor, then only the search is shown.
37
}) => {
38
// NOTE: It is VERY important to only update this decorate function
39
// when things really change. Otherwise every Text node in the slate editor
40
// will get re-rendereded (forced by the decorate function changing identity).
41
return useMemo(() => {
42
const nodeToCursors: WeakMap<Node, OtherCursor[]> = new WeakMap();
43
44
const cursors0 = cursors?.toJS();
45
if (cursors0 != null) {
46
const user_map = redux.getStore("users").get("user_map");
47
for (const account_id in cursors0) {
48
for (const cursor of cursors0[account_id] ?? []) {
49
// TODO -- insanely inefficient!
50
const loc = markdownPositionToSlatePoint({
51
markdown: value,
52
pos: { line: cursor.y, ch: cursor.x },
53
editor,
54
});
55
if (loc == null) continue;
56
const { path, offset } = loc;
57
// TODO: for now we're ONLY implementing cursors for leafs,
58
// and ignoring everything else.
59
let leaf;
60
try {
61
leaf = Editor.leaf(editor, { path, offset })[0];
62
} catch (_err) {
63
// failing is expected since the document can change from
64
// when the cursor was reported.
65
// TODO: find nearest valid leaf?
66
continue;
67
}
68
const { name, color } = getProfile(account_id, user_map);
69
nodeToCursors.set(
70
leaf,
71
(nodeToCursors.get(leaf) ?? []).concat([{ offset, name, color }])
72
);
73
}
74
}
75
}
76
77
return ([node, path]) => {
78
// We do the search decorate and if there is no search,
79
// then we do the cursor. TODO: maybe combine, though if
80
// you are searching, seeing cursors blocking search results
81
// could be annoying.
82
const s = search.decorate([node, path]);
83
if (s.length > 0) return s;
84
const ranges: {
85
anchor: Point;
86
focus: Point;
87
cursor: { name: string; color: string; paddingText?: string };
88
}[] = [];
89
if (!Text.isText(node)) return ranges;
90
const c = nodeToCursors.get(node);
91
if (c == null) return ranges;
92
for (const cur of c) {
93
const { offset, name, color } = cur;
94
if (offset < node.text.length - 1) {
95
ranges.push({
96
anchor: { path, offset },
97
focus: { path, offset: offset + 1 },
98
cursor: { name, color },
99
});
100
} else {
101
// You can't make an *empty* decorated block, since
102
// it just gets discarded.... or does it?
103
ranges.push({
104
anchor: { path, offset: offset - 1 },
105
focus: { path, offset: offset },
106
cursor: {
107
name,
108
color,
109
paddingText: node.text[node.text.length - 1],
110
},
111
});
112
}
113
// TODO: We are just showing the first user cursor for now, even if
114
// they have multiple cursors (only happens if they are using source view)
115
// or there are multiple users editing that text node.
116
break;
117
}
118
119
return ranges;
120
};
121
}, [cursors, value, search.search]);
122
};
123
124