@marimo-team/islands 0.25.3-dev205 → 0.25.3-dev207
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/{ConnectedDataExplorerComponent-DFynurqb.js → ConnectedDataExplorerComponent-ApTYh0V3.js} +4 -4
- package/dist/{ErrorBoundary-OPdycBFt.js → ErrorBoundary-DV6q3WpD.js} +2 -2
- package/dist/{abnfDiagram-VCTEODGH-DGkUyNdz.js → abnfDiagram-VCTEODGH-uo9xhZY0.js} +2 -2
- package/dist/{architectureDiagram-5GKGNRK7-Bav9J7uF.js → architectureDiagram-5GKGNRK7-Hz6eyeOD.js} +1 -1
- package/dist/assets/__vite-browser-external-CBRgnYY0.js +1 -0
- package/dist/assets/{worker-DodjWT_Z.js → worker-DiPNP__U.js} +2 -2
- package/dist/{chat-ui-Dorq26Pp.js → chat-ui-BTA1I5i1.js} +7 -7
- package/dist/{chunk-IMKFNOWR-1UCTsTqh.js → chunk-IMKFNOWR-DBM2i23k.js} +1 -1
- package/dist/{chunk-SHT3W25Y-Bige_rTb.js → chunk-SHT3W25Y-L8KQLLBy.js} +1 -1
- package/dist/{chunk-SVP7TREG-Cmr0Anfb.js → chunk-SVP7TREG-BOUJKJEP.js} +1 -1
- package/dist/{chunk-TICWLB2K-IWykFldb.js → chunk-TICWLB2K-DY3CzFjX.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-BH73ECRQ.js → classDiagram-ZZMXUADV-s9iwW-q4.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-CCuCgq0H.js → classDiagram-v2-VYDZK3BY-2GI5CjGB.js} +2 -2
- package/dist/{code-visibility-DBpgJeRK.js → code-visibility-DLj8SFEM.js} +4 -4
- package/dist/{common-DZdZhBUN.js → common-BEiP9-YB.js} +9 -9
- package/dist/{cynefinDiagram-5FMLGOSQ-CWtL7MIJ.js → cynefinDiagram-5FMLGOSQ-DOxnhBK2.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-DsVO2pVC.js → dagre-GXQ25YYZ-D1AhEfpM.js} +1 -1
- package/dist/{data-grid-overlay-editor-aBSCgI7f.js → data-grid-overlay-editor-Bt6VgNDi.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BuAktgZv.js → diagram-S7CK7UJ4-DV_GebF_.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-DlkAXHu5.js → diagram-UQ7AKVKN-6dNzGFfD.js} +1 -1
- package/dist/{diagram-VX7I27RA-B9U9H0E8.js → diagram-VX7I27RA-DUh9jsfi.js} +1 -1
- package/dist/{diagram-Z3DM3KII-BqmfxzUL.js → diagram-Z3DM3KII-Cug-qcQ8.js} +1 -1
- package/dist/{dropdown-menu-BFQYzWmC.js → dropdown-menu-dlEm41Bt.js} +1 -1
- package/dist/{ebnfDiagram-PWID7BFC-CH5t8ATi.js → ebnfDiagram-PWID7BFC-d5LKx7Vb.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-DdIo2BIb.js → erDiagram-RLTQ6QDP-BwvlkcID.js} +1 -1
- package/dist/{error-entries-B0bcEwXm.js → error-entries-CVUHA5r_.js} +3875 -3884
- package/dist/{feedback-button-DOT-sr6d.js → feedback-button-Dd87gzc6.js} +5 -5
- package/dist/{flowDiagram-HODETNUW-BmYk_-Na.js → flowDiagram-HODETNUW-CJcSoFer.js} +2 -2
- package/dist/{formats-DyYnckF5.js → formats-Ry7c-aIV.js} +1 -1
- package/dist/{glide-data-editor-wPaUwYJ-.js → glide-data-editor-CW4LOU-D.js} +5 -5
- package/dist/{globals-iMXhFlx_.js → globals-D3AhIN1i.js} +2 -2
- package/dist/{html-to-image-BOd7ryZJ.js → html-to-image--E3uFbJ9.js} +5 -5
- package/dist/{infoDiagram-27XIBGKW-DvYh-Sbk.js → infoDiagram-27XIBGKW-Cezgppw3.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-BdHnYq8s.js → ishikawaDiagram-5VMMS53U-BahZQwJz.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-Bf8CT_tU.js → kanban-definition-UXKFOSKX-Cvfn_Obm.js} +1 -1
- package/dist/main.js +22 -22
- package/dist/{mermaid-DBpOnw8c.js → mermaid-BrdwbArF.js} +34 -34
- package/dist/{mindmap-definition-YA3MSWOX-DkYG8ltR.js → mindmap-definition-YA3MSWOX-uB4B2d-J.js} +1 -1
- package/dist/{number-overlay-editor-CmrlqczC.js → number-overlay-editor-klVnf90Q.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-DM7BQ0ip.js → pegDiagram-XKGWAZYB-CYaIwAmH.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-CPFGg4YS.js → pieDiagram-E7YTZNPT-BJ1oUxVv.js} +1 -1
- package/dist/{process-output-8BbxxGrz.js → process-output-SjJxB1wh.js} +2 -2
- package/dist/{railroadDiagram-O6MQD6OU-4rUJlIpQ.js → railroadDiagram-O6MQD6OU-BqKSaBux.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-F6ffUUQC.js → requirementDiagram-BXWQKSXE-DwFhgDhH.js} +1 -1
- package/dist/{reveal-component-W-rBMqdU.js → reveal-component-CS16WNwA.js} +6 -6
- package/dist/{sequenceDiagram-WJ2MYXX4-tNU95HVS.js → sequenceDiagram-WJ2MYXX4-L7ENjfKF.js} +1 -1
- package/dist/{spec-Alkla7dB.js → spec-BcJ9ncAY.js} +1 -1
- package/dist/{stateDiagram-D77RDMKH-BA8Modqv.js → stateDiagram-D77RDMKH-CVf88u-p.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CFmickht.js → stateDiagram-v2-MP3YSRHH-DvpkQ1V6.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-DBiYZZZc.js → swimlanes-42K2YHIH-DIuFHbVE.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-CsgR851k.js → swimlanesDiagram-VR7AAH4N-CAqHAziW.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-Dfj0CMXY.js → timeline-definition-24CTP7MA-xO4EQqBm.js} +1 -1
- package/dist/{toDate-B0-Y0SEW.js → toDate-BZZ64K_m.js} +1 -5
- package/dist/{useAsyncData-Phxf6GCd.js → useAsyncData-Dqw22wZQ.js} +1 -1
- package/dist/{useDeepCompareMemoize-BXv7DUcU.js → useDeepCompareMemoize-D3gozcjJ.js} +1 -1
- package/dist/{useLifecycle-DDDsO4O2.js → useLifecycle-CaZi4wTw.js} +1 -1
- package/dist/{useTheme-DbBkeP6Z.js → useTheme-TzPgWXwB.js} +1 -4
- package/dist/{vega-component-C_dhEynE.js → vega-component-Blvspl7v.js} +5 -5
- package/dist/{vennDiagram-4TSXK5OY-fJ83Z7FE.js → vennDiagram-4TSXK5OY-DzCi-lRh.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-DbO9cD15.js → wardleyDiagram-VM6X3IG4-CW48xa0n.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-DmsdQJG7.js → xychartDiagram-S5SC5T6Z-UPxGGI2B.js} +1 -1
- package/package.json +2 -6
- package/src/components/app-config/user-config-form.tsx +0 -26
- package/src/components/editor/cell/code/cell-editor.tsx +11 -100
- package/src/components/editor/chrome/wrapper/footer.tsx +0 -2
- package/src/core/cells/__tests__/document.test.ts +0 -18
- package/src/core/cells/document.ts +6 -9
- package/src/core/codemirror/cells/__tests__/extensions.test.ts +1 -46
- package/src/core/codemirror/cells/extensions.ts +1 -19
- package/src/core/config/config-schema.ts +0 -1
- package/src/core/config/feature-flag.tsx +0 -2
- package/src/core/runtime/__tests__/runtime.test.ts +0 -16
- package/src/core/runtime/runtime.ts +0 -8
- package/src/css/app/codemirror.css +1 -1
- package/dist/assets/__vite-browser-external-BCJsx8QS.js +0 -1
- package/src/components/editor/cell/code/__tests__/rtc-editor-config.test.ts +0 -50
- package/src/components/editor/chrome/wrapper/footer-items/rtc-status.tsx +0 -65
- package/src/core/codemirror/rtc/extension.ts +0 -464
- package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +0 -52
- package/src/core/codemirror/rtc/loro/awareness.ts +0 -546
- package/src/core/codemirror/rtc/loro/colors.ts +0 -38
- package/src/core/codemirror/rtc/loro/sync.ts +0 -169
- package/src/core/rtc/state.ts +0 -34
|
@@ -1,546 +0,0 @@
|
|
|
1
|
-
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Annotation,
|
|
5
|
-
EditorSelection,
|
|
6
|
-
type Extension,
|
|
7
|
-
type SelectionRange,
|
|
8
|
-
StateEffect,
|
|
9
|
-
StateField,
|
|
10
|
-
} from "@codemirror/state";
|
|
11
|
-
import {
|
|
12
|
-
Direction,
|
|
13
|
-
EditorView,
|
|
14
|
-
type LayerMarker,
|
|
15
|
-
layer,
|
|
16
|
-
type PluginValue,
|
|
17
|
-
type Rect,
|
|
18
|
-
RectangleMarker,
|
|
19
|
-
type ViewUpdate,
|
|
20
|
-
} from "@codemirror/view";
|
|
21
|
-
import {
|
|
22
|
-
type Awareness,
|
|
23
|
-
type AwarenessListener,
|
|
24
|
-
Cursor,
|
|
25
|
-
type LoroDoc,
|
|
26
|
-
type LoroText,
|
|
27
|
-
type PeerID,
|
|
28
|
-
type Subscription,
|
|
29
|
-
} from "loro-crdt";
|
|
30
|
-
import type { TypedString } from "@/utils/typed";
|
|
31
|
-
|
|
32
|
-
export type ScopeId = TypedString<"loro:scope">;
|
|
33
|
-
export type Uid = TypedString<"loro:uid"> | PeerID;
|
|
34
|
-
|
|
35
|
-
export const loroCursorTheme = EditorView.baseTheme({
|
|
36
|
-
".loro-cursor": {
|
|
37
|
-
position: "absolute",
|
|
38
|
-
width: "2px",
|
|
39
|
-
display: "inline-block",
|
|
40
|
-
height: "1.2em",
|
|
41
|
-
},
|
|
42
|
-
".loro-cursor::before": {
|
|
43
|
-
position: "absolute",
|
|
44
|
-
top: "1.3em",
|
|
45
|
-
left: "0",
|
|
46
|
-
content: "var(--rtc-name)",
|
|
47
|
-
padding: "2px 6px",
|
|
48
|
-
fontSize: "12px",
|
|
49
|
-
borderRadius: "3px",
|
|
50
|
-
whiteSpace: "nowrap",
|
|
51
|
-
userSelect: "none",
|
|
52
|
-
backgroundColor: "inherit",
|
|
53
|
-
opacity: "0.7",
|
|
54
|
-
},
|
|
55
|
-
".loro-selection": {
|
|
56
|
-
opacity: "0.5",
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
export type AwarenessState =
|
|
61
|
-
| {
|
|
62
|
-
type: "update";
|
|
63
|
-
uid: Uid;
|
|
64
|
-
scopeId: ScopeId;
|
|
65
|
-
cursor: { anchor: Uint8Array; head?: Uint8Array };
|
|
66
|
-
user?: {
|
|
67
|
-
name: string;
|
|
68
|
-
colorClassName: string;
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
| {
|
|
72
|
-
type: "delete";
|
|
73
|
-
uid: Uid;
|
|
74
|
-
scopeId: ScopeId;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export interface UserState {
|
|
78
|
-
name: string;
|
|
79
|
-
colorClassName: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
type CursorEffect =
|
|
83
|
-
| {
|
|
84
|
-
type: "update";
|
|
85
|
-
peer: Uid;
|
|
86
|
-
scopeId: ScopeId;
|
|
87
|
-
cursor: { anchor: number; head?: number };
|
|
88
|
-
user?: UserState;
|
|
89
|
-
}
|
|
90
|
-
| {
|
|
91
|
-
type: "delete";
|
|
92
|
-
peer: Uid;
|
|
93
|
-
scopeId: ScopeId;
|
|
94
|
-
}
|
|
95
|
-
| {
|
|
96
|
-
type: "checkout";
|
|
97
|
-
checkout: boolean;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// We should use layer https://github.com/codemirror/dev/issues/989
|
|
101
|
-
export const remoteAwarenessAnnotation = Annotation.define<undefined>();
|
|
102
|
-
export const remoteAwarenessEffect = StateEffect.define<CursorEffect>();
|
|
103
|
-
export const remoteAwarenessStateField = StateField.define<{
|
|
104
|
-
remoteCursors: Map<string, CursorPosition>;
|
|
105
|
-
isCheckout: boolean;
|
|
106
|
-
}>({
|
|
107
|
-
create() {
|
|
108
|
-
return { remoteCursors: new Map(), isCheckout: false };
|
|
109
|
-
},
|
|
110
|
-
update(value, tr) {
|
|
111
|
-
for (const effect of tr.effects) {
|
|
112
|
-
if (effect.is(remoteAwarenessEffect)) {
|
|
113
|
-
switch (effect.value.type) {
|
|
114
|
-
case "update": {
|
|
115
|
-
const { peer: uid, user, cursor } = effect.value;
|
|
116
|
-
value.remoteCursors.set(uid, {
|
|
117
|
-
uid,
|
|
118
|
-
cursor,
|
|
119
|
-
user,
|
|
120
|
-
});
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
case "delete":
|
|
124
|
-
value.remoteCursors.delete(effect.value.peer);
|
|
125
|
-
break;
|
|
126
|
-
case "checkout":
|
|
127
|
-
value.isCheckout = effect.value.checkout;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return value;
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const isRemoteCursorUpdate = (update: ViewUpdate): boolean => {
|
|
136
|
-
const effect = update.transactions
|
|
137
|
-
.flatMap((transaction) => transaction.effects)
|
|
138
|
-
.filter((effect) => effect.is(remoteAwarenessEffect));
|
|
139
|
-
return update.docChanged || update.viewportChanged || effect.length > 0;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
export const createCursorLayer = (): Extension => {
|
|
143
|
-
return layer({
|
|
144
|
-
above: true,
|
|
145
|
-
class: "loro-cursor-layer",
|
|
146
|
-
update: isRemoteCursorUpdate,
|
|
147
|
-
markers: (view) => {
|
|
148
|
-
const { remoteCursors: remoteStates, isCheckout } = view.state.field(
|
|
149
|
-
remoteAwarenessStateField,
|
|
150
|
-
);
|
|
151
|
-
if (isCheckout) {
|
|
152
|
-
return [];
|
|
153
|
-
}
|
|
154
|
-
return [...remoteStates.values()].flatMap((state) => {
|
|
155
|
-
const selectionRange = EditorSelection.cursor(state.cursor.anchor);
|
|
156
|
-
return RemoteCursorMarker.createCursor(
|
|
157
|
-
view,
|
|
158
|
-
selectionRange,
|
|
159
|
-
state.user?.name || "unknown",
|
|
160
|
-
state.user?.colorClassName || "",
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
export const createSelectionLayer = (): Extension =>
|
|
168
|
-
layer({
|
|
169
|
-
above: false,
|
|
170
|
-
class: "loro-selection-layer",
|
|
171
|
-
update: isRemoteCursorUpdate,
|
|
172
|
-
markers: (view) => {
|
|
173
|
-
const { remoteCursors: remoteStates, isCheckout } = view.state.field(
|
|
174
|
-
remoteAwarenessStateField,
|
|
175
|
-
);
|
|
176
|
-
if (isCheckout) {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
return [...remoteStates.entries()]
|
|
180
|
-
.filter(
|
|
181
|
-
([, state]) =>
|
|
182
|
-
state.cursor.head !== undefined &&
|
|
183
|
-
state.cursor.anchor !== state.cursor.head,
|
|
184
|
-
)
|
|
185
|
-
.flatMap(([, state]) => {
|
|
186
|
-
const selectionRange = EditorSelection.range(
|
|
187
|
-
state.cursor.anchor,
|
|
188
|
-
// oxlint-disable-next-line typescript/no-non-null-assertion
|
|
189
|
-
state.cursor.head!,
|
|
190
|
-
);
|
|
191
|
-
const markers = RectangleMarker.forRange(
|
|
192
|
-
view,
|
|
193
|
-
`loro-selection ${state.user?.colorClassName || ""}`,
|
|
194
|
-
selectionRange,
|
|
195
|
-
);
|
|
196
|
-
return markers;
|
|
197
|
-
});
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Renders a blinking cursor to indicate the cursor of another user.
|
|
203
|
-
*/
|
|
204
|
-
export class RemoteCursorMarker implements LayerMarker {
|
|
205
|
-
private left: number;
|
|
206
|
-
private top: number;
|
|
207
|
-
private height: number;
|
|
208
|
-
private name: string;
|
|
209
|
-
private colorClassName: string;
|
|
210
|
-
|
|
211
|
-
public constructor({
|
|
212
|
-
left,
|
|
213
|
-
top,
|
|
214
|
-
height,
|
|
215
|
-
name,
|
|
216
|
-
colorClassName,
|
|
217
|
-
}: {
|
|
218
|
-
left: number;
|
|
219
|
-
top: number;
|
|
220
|
-
height: number;
|
|
221
|
-
name: string;
|
|
222
|
-
colorClassName: string;
|
|
223
|
-
}) {
|
|
224
|
-
this.left = left;
|
|
225
|
-
this.top = top;
|
|
226
|
-
this.height = height;
|
|
227
|
-
this.name = name;
|
|
228
|
-
this.colorClassName = colorClassName;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
public draw(): HTMLElement {
|
|
232
|
-
const elt = document.createElement("div");
|
|
233
|
-
this.adjust(elt);
|
|
234
|
-
return elt;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
public update(elt: HTMLElement): boolean {
|
|
238
|
-
this.adjust(elt);
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
public adjust(element: HTMLElement) {
|
|
243
|
-
element.style.left = `${this.left}px`;
|
|
244
|
-
element.style.top = `${this.top}px`;
|
|
245
|
-
element.style.height = `${this.height}px`;
|
|
246
|
-
element.className = `loro-cursor ${this.colorClassName}`;
|
|
247
|
-
element.style.setProperty("--rtc-name", `"${this.name}"`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
public eq(other: RemoteCursorMarker): boolean {
|
|
251
|
-
return (
|
|
252
|
-
this.left === other.left &&
|
|
253
|
-
this.top === other.top &&
|
|
254
|
-
this.height === other.height &&
|
|
255
|
-
this.name === other.name
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
public static createCursor(
|
|
260
|
-
view: EditorView,
|
|
261
|
-
position: SelectionRange,
|
|
262
|
-
displayName: string,
|
|
263
|
-
colorClassName: string,
|
|
264
|
-
): RemoteCursorMarker[] {
|
|
265
|
-
const absolutePosition = RemoteCursorMarker.calculateAbsoluteCursorPosition(
|
|
266
|
-
position,
|
|
267
|
-
view,
|
|
268
|
-
);
|
|
269
|
-
if (!absolutePosition) {
|
|
270
|
-
return [];
|
|
271
|
-
}
|
|
272
|
-
const rect = view.scrollDOM.getBoundingClientRect();
|
|
273
|
-
const left =
|
|
274
|
-
view.textDirection === Direction.LTR
|
|
275
|
-
? rect.left
|
|
276
|
-
: rect.right - view.scrollDOM.clientWidth;
|
|
277
|
-
const baseLeft = left - view.scrollDOM.scrollLeft;
|
|
278
|
-
const baseTop = rect.top - view.scrollDOM.scrollTop;
|
|
279
|
-
return [
|
|
280
|
-
new RemoteCursorMarker({
|
|
281
|
-
left: absolutePosition.left - baseLeft,
|
|
282
|
-
top: absolutePosition.top - baseTop,
|
|
283
|
-
height: absolutePosition.bottom - absolutePosition.top,
|
|
284
|
-
name: displayName,
|
|
285
|
-
colorClassName,
|
|
286
|
-
}),
|
|
287
|
-
];
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private static calculateAbsoluteCursorPosition(
|
|
291
|
-
position: SelectionRange,
|
|
292
|
-
view: EditorView,
|
|
293
|
-
): Rect | null {
|
|
294
|
-
const cappedPositionHead = Math.max(
|
|
295
|
-
0,
|
|
296
|
-
Math.min(view.state.doc.length, position.anchor),
|
|
297
|
-
);
|
|
298
|
-
return view.coordsAtPos(cappedPositionHead, position.assoc || 1);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const parseAwarenessUpdate = (
|
|
303
|
-
doc: LoroDoc,
|
|
304
|
-
awareness: Awareness<AwarenessState>,
|
|
305
|
-
arg: {
|
|
306
|
-
updated: PeerID[];
|
|
307
|
-
added: PeerID[];
|
|
308
|
-
removed: PeerID[];
|
|
309
|
-
},
|
|
310
|
-
scopeId: ScopeId,
|
|
311
|
-
): StateEffect<CursorEffect>[] => {
|
|
312
|
-
const effects = [];
|
|
313
|
-
const { updated, added } = arg;
|
|
314
|
-
for (const update of [...updated, ...added]) {
|
|
315
|
-
const effect = getEffects(doc, awareness, update, scopeId);
|
|
316
|
-
if (effect) {
|
|
317
|
-
effects.push(effect);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return effects;
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
const getEffects = (
|
|
324
|
-
doc: LoroDoc,
|
|
325
|
-
awareness: Awareness<AwarenessState>,
|
|
326
|
-
peer: PeerID,
|
|
327
|
-
scopeId: ScopeId,
|
|
328
|
-
): StateEffect<CursorEffect> | undefined => {
|
|
329
|
-
const states = awareness.getAllStates();
|
|
330
|
-
const state = states[peer];
|
|
331
|
-
if (!state) {
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (peer === doc.peerIdStr) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
if (state.scopeId !== scopeId) {
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (state.type === "delete") {
|
|
342
|
-
return remoteAwarenessEffect.of({
|
|
343
|
-
type: "delete",
|
|
344
|
-
peer: state.uid,
|
|
345
|
-
scopeId: state.scopeId,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const anchor = Cursor.decode(state.cursor.anchor);
|
|
350
|
-
const anchorPos = doc.getCursorPos(anchor)?.offset ?? 0;
|
|
351
|
-
let headPos = anchorPos;
|
|
352
|
-
if (state.cursor.head) {
|
|
353
|
-
// range
|
|
354
|
-
const head = Cursor.decode(state.cursor.head);
|
|
355
|
-
headPos = doc.getCursorPos(head)?.offset ?? 0;
|
|
356
|
-
}
|
|
357
|
-
return remoteAwarenessEffect.of({
|
|
358
|
-
type: "update",
|
|
359
|
-
peer: state.uid,
|
|
360
|
-
scopeId: state.scopeId,
|
|
361
|
-
cursor: { anchor: anchorPos, head: headPos },
|
|
362
|
-
user: state.user,
|
|
363
|
-
});
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
export interface CursorPosition {
|
|
367
|
-
uid: string;
|
|
368
|
-
cursor: { anchor: number; head?: number };
|
|
369
|
-
user?: UserState;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export class AwarenessPlugin implements PluginValue {
|
|
373
|
-
public sub: Subscription;
|
|
374
|
-
public view: EditorView;
|
|
375
|
-
public doc: LoroDoc;
|
|
376
|
-
public user: UserState;
|
|
377
|
-
public awareness: Awareness<AwarenessState>;
|
|
378
|
-
private getTextFromDoc: (doc: LoroDoc) => LoroText;
|
|
379
|
-
private scopeId: ScopeId;
|
|
380
|
-
private getUserId?: () => Uid;
|
|
381
|
-
|
|
382
|
-
public constructor(
|
|
383
|
-
view: EditorView,
|
|
384
|
-
{
|
|
385
|
-
doc,
|
|
386
|
-
user,
|
|
387
|
-
awareness,
|
|
388
|
-
getTextFromDoc,
|
|
389
|
-
scopeId,
|
|
390
|
-
getUserId,
|
|
391
|
-
}: {
|
|
392
|
-
doc: LoroDoc;
|
|
393
|
-
user: UserState;
|
|
394
|
-
awareness: Awareness<AwarenessState>;
|
|
395
|
-
getTextFromDoc: (doc: LoroDoc) => LoroText;
|
|
396
|
-
scopeId: ScopeId;
|
|
397
|
-
getUserId?: () => Uid;
|
|
398
|
-
},
|
|
399
|
-
) {
|
|
400
|
-
this.view = view;
|
|
401
|
-
this.doc = doc;
|
|
402
|
-
this.user = user;
|
|
403
|
-
this.awareness = awareness;
|
|
404
|
-
this.getTextFromDoc = getTextFromDoc;
|
|
405
|
-
this.scopeId = scopeId;
|
|
406
|
-
this.getUserId = getUserId;
|
|
407
|
-
this.sub = this.doc.subscribe((e) => {
|
|
408
|
-
if (e.by === "local") {
|
|
409
|
-
// update remote cursor position
|
|
410
|
-
const effects = [];
|
|
411
|
-
for (const peer of this.awareness.peers()) {
|
|
412
|
-
const effect = getEffects(
|
|
413
|
-
this.doc,
|
|
414
|
-
this.awareness,
|
|
415
|
-
peer,
|
|
416
|
-
this.scopeId,
|
|
417
|
-
);
|
|
418
|
-
if (effect) {
|
|
419
|
-
effects.push(effect);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
this.view.dispatch({
|
|
423
|
-
effects,
|
|
424
|
-
});
|
|
425
|
-
} else if (e.by === "checkout") {
|
|
426
|
-
// TODO: better way
|
|
427
|
-
this.view.dispatch({
|
|
428
|
-
effects: [
|
|
429
|
-
remoteAwarenessEffect.of({
|
|
430
|
-
type: "checkout",
|
|
431
|
-
checkout: this.doc.isDetached(),
|
|
432
|
-
}),
|
|
433
|
-
],
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
public update(update: ViewUpdate): void {
|
|
440
|
-
if (!update.selectionSet && !update.focusChanged && !update.docChanged) {
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
const selection = update.state.selection.main;
|
|
444
|
-
if (this.view.hasFocus && !this.doc.isDetached()) {
|
|
445
|
-
const cursorState = getCursorState({
|
|
446
|
-
doc: this.doc,
|
|
447
|
-
getTextFromDoc: this.getTextFromDoc,
|
|
448
|
-
anchor: selection.anchor,
|
|
449
|
-
head: selection.head,
|
|
450
|
-
});
|
|
451
|
-
this.awareness.setLocalState({
|
|
452
|
-
type: "update",
|
|
453
|
-
uid: this.getUserId ? this.getUserId() : this.doc.peerIdStr,
|
|
454
|
-
scopeId: this.scopeId,
|
|
455
|
-
cursor: cursorState,
|
|
456
|
-
user: this.user,
|
|
457
|
-
});
|
|
458
|
-
} else {
|
|
459
|
-
// when checkout or blur
|
|
460
|
-
this.awareness.setLocalState({
|
|
461
|
-
type: "delete",
|
|
462
|
-
uid: this.getUserId ? this.getUserId() : this.doc.peerIdStr,
|
|
463
|
-
scopeId: this.scopeId,
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
public destroy(): void {
|
|
469
|
-
this.sub?.();
|
|
470
|
-
this.awareness.setLocalState({
|
|
471
|
-
type: "delete",
|
|
472
|
-
uid: this.getUserId ? this.getUserId() : this.doc.peerIdStr,
|
|
473
|
-
scopeId: this.scopeId,
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
export class RemoteAwarenessPlugin implements PluginValue {
|
|
478
|
-
public _awarenessListener?: AwarenessListener;
|
|
479
|
-
public view: EditorView;
|
|
480
|
-
public doc: LoroDoc;
|
|
481
|
-
public awareness: Awareness<AwarenessState>;
|
|
482
|
-
private scopeId: ScopeId;
|
|
483
|
-
|
|
484
|
-
public constructor(
|
|
485
|
-
view: EditorView,
|
|
486
|
-
doc: LoroDoc,
|
|
487
|
-
awareness: Awareness<AwarenessState>,
|
|
488
|
-
scopeId: ScopeId,
|
|
489
|
-
) {
|
|
490
|
-
this.view = view;
|
|
491
|
-
this.doc = doc;
|
|
492
|
-
this.awareness = awareness;
|
|
493
|
-
this.scopeId = scopeId;
|
|
494
|
-
const listener: AwarenessListener = async (arg, origin) => {
|
|
495
|
-
if (origin === "local") {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
this.view.dispatch({
|
|
499
|
-
effects: parseAwarenessUpdate(
|
|
500
|
-
this.doc,
|
|
501
|
-
this.awareness,
|
|
502
|
-
arg,
|
|
503
|
-
this.scopeId,
|
|
504
|
-
),
|
|
505
|
-
});
|
|
506
|
-
};
|
|
507
|
-
this._awarenessListener = listener;
|
|
508
|
-
this.awareness.addListener(listener);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
public destroy(): void {
|
|
512
|
-
if (this._awarenessListener) {
|
|
513
|
-
this.awareness.removeListener(this._awarenessListener);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const getCursorState = ({
|
|
519
|
-
doc,
|
|
520
|
-
getTextFromDoc,
|
|
521
|
-
anchor,
|
|
522
|
-
head,
|
|
523
|
-
}: {
|
|
524
|
-
doc: LoroDoc;
|
|
525
|
-
getTextFromDoc: (doc: LoroDoc) => LoroText;
|
|
526
|
-
anchor: number;
|
|
527
|
-
head: number | undefined;
|
|
528
|
-
}) => {
|
|
529
|
-
if (anchor === head) {
|
|
530
|
-
head = undefined;
|
|
531
|
-
}
|
|
532
|
-
const anchorCursor = getTextFromDoc(doc).getCursor(anchor)?.encode();
|
|
533
|
-
|
|
534
|
-
if (!anchorCursor) {
|
|
535
|
-
throw new Error("cursor head not found");
|
|
536
|
-
}
|
|
537
|
-
let headCursor: Uint8Array | undefined;
|
|
538
|
-
if (head !== undefined) {
|
|
539
|
-
headCursor = getTextFromDoc(doc).getCursor(head)?.encode();
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return {
|
|
543
|
-
anchor: anchorCursor,
|
|
544
|
-
head: headCursor,
|
|
545
|
-
};
|
|
546
|
-
};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
|
|
3
|
-
export const COLORS = [
|
|
4
|
-
"bg-(--amber-6)",
|
|
5
|
-
"bg-(--blue-6)",
|
|
6
|
-
"bg-(--crimson-6)",
|
|
7
|
-
"bg-(--cyan-6)",
|
|
8
|
-
"bg-(--grass-6)",
|
|
9
|
-
"bg-(--gray-6)",
|
|
10
|
-
"bg-(--green-6)",
|
|
11
|
-
"bg-(--lime-6)",
|
|
12
|
-
"bg-(--orange-6)",
|
|
13
|
-
"bg-(--purple-6)",
|
|
14
|
-
"bg-(--red-6)",
|
|
15
|
-
"bg-(--sage-6)",
|
|
16
|
-
"bg-(--sky-6)",
|
|
17
|
-
"bg-(--slate-6)",
|
|
18
|
-
] as const;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Get a deterministic color from a string or number.
|
|
22
|
-
* @param input - The input to get a color from.
|
|
23
|
-
*/
|
|
24
|
-
export const getColor = (input: string | number) => {
|
|
25
|
-
if (typeof input === "string") {
|
|
26
|
-
// Create a simple hash from the string
|
|
27
|
-
let hash = 0;
|
|
28
|
-
for (let i = 0; i < input.length; i++) {
|
|
29
|
-
hash = (hash << 5) - hash + input.charCodeAt(i);
|
|
30
|
-
hash = Math.trunc(hash); // Convert to 32bit integer
|
|
31
|
-
}
|
|
32
|
-
// Use absolute value to ensure positive index
|
|
33
|
-
return COLORS[Math.abs(hash) % COLORS.length];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Fallback for number input
|
|
37
|
-
return COLORS[input % COLORS.length];
|
|
38
|
-
};
|