@korajs/react 0.4.0 → 0.6.0
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/index.cjs +175 -343
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -122
- package/dist/index.d.ts +35 -122
- package/dist/index.js +183 -343
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/context/kora-context.ts
|
|
2
|
-
import {
|
|
2
|
+
import { QueryStoreCache } from "@korajs/store";
|
|
3
|
+
import { createContext, createElement, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
3
4
|
var KoraContext = createContext(null);
|
|
4
5
|
function KoraProvider({
|
|
5
6
|
app,
|
|
@@ -12,6 +13,10 @@ function KoraProvider({
|
|
|
12
13
|
const [resolvedSync, setResolvedSync] = useState(syncEngine ?? null);
|
|
13
14
|
const [ready, setReady] = useState(!app);
|
|
14
15
|
const [initError, setInitError] = useState(null);
|
|
16
|
+
const fallbackQueryStoreCache = useRef(null);
|
|
17
|
+
if (!fallbackQueryStoreCache.current) {
|
|
18
|
+
fallbackQueryStoreCache.current = new QueryStoreCache();
|
|
19
|
+
}
|
|
15
20
|
useEffect(() => {
|
|
16
21
|
if (!app) return;
|
|
17
22
|
let cancelled = false;
|
|
@@ -30,6 +35,26 @@ function KoraProvider({
|
|
|
30
35
|
cancelled = true;
|
|
31
36
|
};
|
|
32
37
|
}, [app]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
return () => {
|
|
40
|
+
if (!app) {
|
|
41
|
+
fallbackQueryStoreCache.current?.clear();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}, [app]);
|
|
45
|
+
const contextValue = useMemo(() => {
|
|
46
|
+
if (!resolvedStore) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
store: resolvedStore,
|
|
51
|
+
syncEngine: resolvedSync,
|
|
52
|
+
app: app ?? null,
|
|
53
|
+
events: app?.events ?? null,
|
|
54
|
+
subscribeSyncStatus: app?.sync?.subscribeStatus ?? null,
|
|
55
|
+
queryStoreCache: app && typeof app.getQueryStoreCache === "function" ? app.getQueryStoreCache() : fallbackQueryStoreCache.current
|
|
56
|
+
};
|
|
57
|
+
}, [resolvedStore, resolvedSync, app]);
|
|
33
58
|
if (initError) {
|
|
34
59
|
return createElement(
|
|
35
60
|
"div",
|
|
@@ -41,17 +66,12 @@ function KoraProvider({
|
|
|
41
66
|
if (!ready) {
|
|
42
67
|
return fallback ?? null;
|
|
43
68
|
}
|
|
44
|
-
if (!
|
|
69
|
+
if (!contextValue) {
|
|
45
70
|
throw new Error(
|
|
46
71
|
'KoraProvider requires either an "app" or "store" prop. Pass a KoraApp from createApp() or a Store instance.'
|
|
47
72
|
);
|
|
48
73
|
}
|
|
49
|
-
|
|
50
|
-
store: resolvedStore,
|
|
51
|
-
syncEngine: resolvedSync,
|
|
52
|
-
app: app ?? null
|
|
53
|
-
};
|
|
54
|
-
return createElement(KoraContext.Provider, { value }, children);
|
|
74
|
+
return createElement(KoraContext.Provider, { value: contextValue }, children);
|
|
55
75
|
}
|
|
56
76
|
function useKoraContext() {
|
|
57
77
|
const context = useContext(KoraContext);
|
|
@@ -75,111 +95,35 @@ function useApp() {
|
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
// src/hooks/use-query.ts
|
|
78
|
-
import {
|
|
79
|
-
|
|
80
|
-
// src/query-store/query-store.ts
|
|
98
|
+
import { assertQueryReady } from "@korajs/store";
|
|
99
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
|
|
81
100
|
var EMPTY_ARRAY = Object.freeze([]);
|
|
82
|
-
var QueryStore = class {
|
|
83
|
-
snapshot = EMPTY_ARRAY;
|
|
84
|
-
listeners = /* @__PURE__ */ new Set();
|
|
85
|
-
unsubscribeQuery = null;
|
|
86
|
-
active = false;
|
|
87
|
-
queryBuilder;
|
|
88
|
-
constructor(queryBuilder) {
|
|
89
|
-
this.queryBuilder = queryBuilder;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Subscribe to snapshot changes. Compatible with useSyncExternalStore.
|
|
93
|
-
*
|
|
94
|
-
* Lazily starts the underlying query subscription when the first listener
|
|
95
|
-
* attaches, and stops it when the last listener detaches. This allows
|
|
96
|
-
* React StrictMode to unmount/remount without permanently killing the subscription.
|
|
97
|
-
*
|
|
98
|
-
* @returns Unsubscribe function
|
|
99
|
-
*/
|
|
100
|
-
subscribe = (onStoreChange) => {
|
|
101
|
-
this.listeners.add(onStoreChange);
|
|
102
|
-
if (!this.active) {
|
|
103
|
-
this.startSubscription();
|
|
104
|
-
}
|
|
105
|
-
return () => {
|
|
106
|
-
this.listeners.delete(onStoreChange);
|
|
107
|
-
if (this.listeners.size === 0) {
|
|
108
|
-
this.stopSubscription();
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
113
|
-
* Get the current snapshot synchronously. Compatible with useSyncExternalStore.
|
|
114
|
-
* Returns EMPTY_ARRAY before the first async fetch completes.
|
|
115
|
-
*/
|
|
116
|
-
getSnapshot = () => {
|
|
117
|
-
return this.snapshot;
|
|
118
|
-
};
|
|
119
|
-
/**
|
|
120
|
-
* Clean up the underlying subscription and release resources.
|
|
121
|
-
* Called by useMemo when the query descriptor changes.
|
|
122
|
-
*/
|
|
123
|
-
destroy() {
|
|
124
|
-
this.stopSubscription();
|
|
125
|
-
this.listeners.clear();
|
|
126
|
-
this.snapshot = EMPTY_ARRAY;
|
|
127
|
-
}
|
|
128
|
-
startSubscription() {
|
|
129
|
-
this.active = true;
|
|
130
|
-
this.unsubscribeQuery = this.queryBuilder.subscribe((results) => {
|
|
131
|
-
if (!this.active) return;
|
|
132
|
-
const newSnapshot = Object.freeze([...results]);
|
|
133
|
-
this.snapshot = newSnapshot;
|
|
134
|
-
this.notifyListeners();
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
stopSubscription() {
|
|
138
|
-
this.active = false;
|
|
139
|
-
if (this.unsubscribeQuery) {
|
|
140
|
-
this.unsubscribeQuery();
|
|
141
|
-
this.unsubscribeQuery = null;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
notifyListeners() {
|
|
145
|
-
for (const listener of this.listeners) {
|
|
146
|
-
listener();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// src/hooks/use-query.ts
|
|
152
|
-
var EMPTY_ARRAY2 = Object.freeze([]);
|
|
153
101
|
var noopSubscribe = (_onStoreChange) => {
|
|
154
102
|
return () => {
|
|
155
103
|
};
|
|
156
104
|
};
|
|
157
105
|
function useQuery(query, options) {
|
|
106
|
+
const { queryStoreCache } = useKoraContext();
|
|
158
107
|
const enabled = options?.enabled !== false;
|
|
159
108
|
const descriptorKey = JSON.stringify(query.getDescriptor());
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
const queryStore =
|
|
109
|
+
const queryRef = useRef2(query);
|
|
110
|
+
queryRef.current = query;
|
|
111
|
+
const [queryStore, setQueryStore] = useState2(null);
|
|
112
|
+
useEffect2(() => {
|
|
163
113
|
if (!enabled) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
queryStoreRef.current = null;
|
|
167
|
-
}
|
|
168
|
-
prevKeyRef.current = null;
|
|
169
|
-
return null;
|
|
114
|
+
setQueryStore(null);
|
|
115
|
+
return;
|
|
170
116
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}, [descriptorKey, enabled, query]);
|
|
182
|
-
const disabledGetSnapshot = () => EMPTY_ARRAY2;
|
|
117
|
+
const currentQuery = queryRef.current;
|
|
118
|
+
assertQueryReady(currentQuery);
|
|
119
|
+
const store = queryStoreCache.getOrCreate(currentQuery);
|
|
120
|
+
setQueryStore(store);
|
|
121
|
+
return () => {
|
|
122
|
+
queryStoreCache.release(currentQuery);
|
|
123
|
+
setQueryStore(null);
|
|
124
|
+
};
|
|
125
|
+
}, [descriptorKey, enabled, queryStoreCache]);
|
|
126
|
+
const disabledGetSnapshot = () => EMPTY_ARRAY;
|
|
183
127
|
return useSyncExternalStore(
|
|
184
128
|
queryStore ? queryStore.subscribe : noopSubscribe,
|
|
185
129
|
queryStore ? queryStore.getSnapshot : disabledGetSnapshot
|
|
@@ -187,281 +131,177 @@ function useQuery(query, options) {
|
|
|
187
131
|
}
|
|
188
132
|
|
|
189
133
|
// src/hooks/use-mutation.ts
|
|
190
|
-
import {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
error: null
|
|
195
|
-
});
|
|
196
|
-
const mountedRef = useRef2(true);
|
|
197
|
-
useEffect2(() => {
|
|
198
|
-
mountedRef.current = true;
|
|
199
|
-
return () => {
|
|
200
|
-
mountedRef.current = false;
|
|
201
|
-
};
|
|
202
|
-
}, []);
|
|
203
|
-
const fnRef = useRef2(mutationFn);
|
|
134
|
+
import { createMutationController } from "@korajs/core/bindings";
|
|
135
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
136
|
+
function useMutation(mutationFn, options) {
|
|
137
|
+
const fnRef = useRef3(mutationFn);
|
|
204
138
|
fnRef.current = mutationFn;
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
throw error;
|
|
221
|
-
}
|
|
222
|
-
}, []);
|
|
223
|
-
const mutate = useCallback(
|
|
224
|
-
(...args) => {
|
|
225
|
-
mutateAsync(...args).catch(() => {
|
|
226
|
-
});
|
|
227
|
-
},
|
|
228
|
-
[mutateAsync]
|
|
139
|
+
const optionsRef = useRef3(options);
|
|
140
|
+
optionsRef.current = options;
|
|
141
|
+
const controller = useMemo2(
|
|
142
|
+
() => createMutationController({
|
|
143
|
+
mutationFn: (...args) => fnRef.current(...args),
|
|
144
|
+
resolveOptions: () => optionsRef.current
|
|
145
|
+
}),
|
|
146
|
+
[]
|
|
147
|
+
);
|
|
148
|
+
useEffect3(() => () => controller.destroy(), [controller]);
|
|
149
|
+
const state = useSyncExternalStore2(
|
|
150
|
+
(onStoreChange) => controller.subscribe(onStoreChange),
|
|
151
|
+
() => controller.getSnapshot(),
|
|
152
|
+
() => controller.getSnapshot()
|
|
229
153
|
);
|
|
230
|
-
const reset = useCallback(() => {
|
|
231
|
-
if (mountedRef.current) {
|
|
232
|
-
setState({ isLoading: false, error: null });
|
|
233
|
-
}
|
|
234
|
-
}, []);
|
|
235
154
|
return {
|
|
236
|
-
mutate,
|
|
237
|
-
mutateAsync,
|
|
155
|
+
mutate: (...args) => controller.mutate(...args),
|
|
156
|
+
mutateAsync: (...args) => controller.mutateAsync(...args),
|
|
238
157
|
isLoading: state.isLoading,
|
|
239
158
|
error: state.error,
|
|
240
|
-
reset
|
|
159
|
+
reset: () => controller.reset()
|
|
241
160
|
};
|
|
242
161
|
}
|
|
243
162
|
|
|
244
163
|
// src/hooks/use-sync-status.ts
|
|
245
|
-
import {
|
|
246
|
-
|
|
247
|
-
var OFFLINE_STATUS = Object.freeze({
|
|
248
|
-
status: "offline",
|
|
249
|
-
pendingOperations: 0,
|
|
250
|
-
lastSyncedAt: null,
|
|
251
|
-
lastSuccessfulPush: null,
|
|
252
|
-
lastSuccessfulPull: null,
|
|
253
|
-
conflicts: 0
|
|
254
|
-
});
|
|
164
|
+
import { createSyncStatusController } from "@korajs/sync";
|
|
165
|
+
import { useEffect as useEffect4, useMemo as useMemo3, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
255
166
|
function useSyncStatus() {
|
|
256
|
-
const { syncEngine } = useKoraContext();
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
}, POLL_INTERVAL_MS);
|
|
272
|
-
const initialStatus = syncEngine.getStatus();
|
|
273
|
-
const initialSerialized = JSON.stringify(initialStatus);
|
|
274
|
-
if (initialSerialized !== serializedRef.current) {
|
|
275
|
-
snapshotRef.current = initialStatus;
|
|
276
|
-
serializedRef.current = initialSerialized;
|
|
277
|
-
onStoreChange();
|
|
278
|
-
}
|
|
279
|
-
return () => {
|
|
280
|
-
clearInterval(intervalId);
|
|
281
|
-
};
|
|
282
|
-
},
|
|
283
|
-
[syncEngine]
|
|
167
|
+
const { syncEngine, subscribeSyncStatus, events } = useKoraContext();
|
|
168
|
+
const controller = useMemo3(
|
|
169
|
+
() => createSyncStatusController({
|
|
170
|
+
syncEngine,
|
|
171
|
+
subscribeSyncStatus,
|
|
172
|
+
events: subscribeSyncStatus ? null : events
|
|
173
|
+
}),
|
|
174
|
+
[syncEngine, subscribeSyncStatus, events]
|
|
175
|
+
);
|
|
176
|
+
useEffect4(() => () => controller.destroy(), [controller]);
|
|
177
|
+
return useSyncExternalStore3(
|
|
178
|
+
(onStoreChange) => controller.subscribe(onStoreChange),
|
|
179
|
+
() => controller.getSnapshot(),
|
|
180
|
+
() => controller.getSnapshot()
|
|
284
181
|
);
|
|
285
|
-
const getSnapshot = useCallback2(() => {
|
|
286
|
-
return snapshotRef.current;
|
|
287
|
-
}, []);
|
|
288
|
-
useEffect3(() => {
|
|
289
|
-
if (!syncEngine) {
|
|
290
|
-
snapshotRef.current = OFFLINE_STATUS;
|
|
291
|
-
serializedRef.current = JSON.stringify(OFFLINE_STATUS);
|
|
292
|
-
}
|
|
293
|
-
}, [syncEngine]);
|
|
294
|
-
return useSyncExternalStore2(subscribe, getSnapshot);
|
|
295
182
|
}
|
|
296
183
|
|
|
297
184
|
// src/hooks/use-collection.ts
|
|
298
|
-
import { useMemo as
|
|
185
|
+
import { useMemo as useMemo4 } from "react";
|
|
299
186
|
function useCollection(name) {
|
|
300
187
|
const { store } = useKoraContext();
|
|
301
|
-
return
|
|
188
|
+
return useMemo4(() => {
|
|
302
189
|
return store.collection(name);
|
|
303
190
|
}, [store, name]);
|
|
304
191
|
}
|
|
305
192
|
|
|
306
193
|
// src/hooks/use-rich-text.ts
|
|
307
|
-
import {
|
|
308
|
-
import
|
|
309
|
-
|
|
310
|
-
var TEXT_KEY = "content";
|
|
311
|
-
var COMPACT_AFTER_DELTAS = 20;
|
|
312
|
-
function useRichText(collectionName, recordId, fieldName) {
|
|
194
|
+
import { asRichTextSyncEngine, createRichTextController } from "@korajs/store";
|
|
195
|
+
import { useCallback, useEffect as useEffect5, useMemo as useMemo5, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
196
|
+
function useRichText(collectionName, recordId, fieldName, options) {
|
|
313
197
|
const { store, syncEngine } = useKoraContext();
|
|
314
|
-
const collection =
|
|
198
|
+
const collection = useMemo5(
|
|
315
199
|
() => store.collection(collectionName),
|
|
316
200
|
[store, collectionName]
|
|
317
201
|
);
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
try {
|
|
376
|
-
await collection.update(recordId, {
|
|
377
|
-
[fieldName]: snapshot
|
|
378
|
-
});
|
|
379
|
-
} catch (cause) {
|
|
380
|
-
if (!disposed) {
|
|
381
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
doc.on("update", persist);
|
|
386
|
-
void initialize();
|
|
387
|
-
syncHistoryState();
|
|
388
|
-
return () => {
|
|
389
|
-
disposed = true;
|
|
390
|
-
doc.off("update", persist);
|
|
391
|
-
undoManager.destroy();
|
|
392
|
-
baseUpdateRef.current = null;
|
|
393
|
-
pendingDeltasRef.current = [];
|
|
394
|
-
};
|
|
395
|
-
}, [collection, doc, fieldName, recordId, syncHistoryState, undoManager]);
|
|
396
|
-
useEffect4(() => {
|
|
397
|
-
if (!syncEngine) return;
|
|
202
|
+
const controller = useMemo5(
|
|
203
|
+
() => createRichTextController({
|
|
204
|
+
collection,
|
|
205
|
+
collectionName,
|
|
206
|
+
recordId,
|
|
207
|
+
fieldName,
|
|
208
|
+
store,
|
|
209
|
+
syncEngine: asRichTextSyncEngine(syncEngine),
|
|
210
|
+
useDocChannel: options?.useDocChannel,
|
|
211
|
+
user: options?.user
|
|
212
|
+
}),
|
|
213
|
+
[collection, collectionName, fieldName, options?.useDocChannel, recordId, store, syncEngine]
|
|
214
|
+
);
|
|
215
|
+
useEffect5(() => {
|
|
216
|
+
controller.setUser(options?.user);
|
|
217
|
+
}, [controller, options?.user]);
|
|
218
|
+
useEffect5(() => () => controller.destroy(), [controller]);
|
|
219
|
+
const snapshot = useSyncExternalStore4(
|
|
220
|
+
(onStoreChange) => controller.subscribe(onStoreChange),
|
|
221
|
+
() => controller.getSnapshot(),
|
|
222
|
+
() => controller.getSnapshot()
|
|
223
|
+
);
|
|
224
|
+
const undo = useCallback(() => controller.undo(), [controller]);
|
|
225
|
+
const redo = useCallback(() => controller.redo(), [controller]);
|
|
226
|
+
const setCursor = useCallback(
|
|
227
|
+
(anchor, head) => controller.setCursor(anchor, head),
|
|
228
|
+
[controller]
|
|
229
|
+
);
|
|
230
|
+
const clearCursor = useCallback(() => controller.clearCursor(), [controller]);
|
|
231
|
+
return buildResult(controller, snapshot, undo, redo, setCursor, clearCursor);
|
|
232
|
+
}
|
|
233
|
+
function buildResult(controller, snapshot, undo, redo, setCursor, clearCursor) {
|
|
234
|
+
return {
|
|
235
|
+
doc: controller.doc,
|
|
236
|
+
text: controller.text,
|
|
237
|
+
undo,
|
|
238
|
+
redo,
|
|
239
|
+
canUndo: snapshot.canUndo,
|
|
240
|
+
canRedo: snapshot.canRedo,
|
|
241
|
+
ready: snapshot.ready,
|
|
242
|
+
error: snapshot.error,
|
|
243
|
+
cursors: [...snapshot.cursors],
|
|
244
|
+
setCursor,
|
|
245
|
+
clearCursor
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/hooks/use-presence.ts
|
|
250
|
+
import { useEffect as useEffect6 } from "react";
|
|
251
|
+
function usePresence(user) {
|
|
252
|
+
const { syncEngine } = useKoraContext();
|
|
253
|
+
const name = user?.name ?? null;
|
|
254
|
+
const color = user?.color ?? null;
|
|
255
|
+
const avatar = user?.avatar ?? null;
|
|
256
|
+
useEffect6(() => {
|
|
257
|
+
if (!syncEngine || !name || !color) return;
|
|
398
258
|
const awareness = syncEngine.getAwarenessManager();
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const states = awareness.getStates();
|
|
402
|
-
const fieldCursors = [];
|
|
403
|
-
for (const [clientId, state] of states) {
|
|
404
|
-
if (clientId === localClientId) continue;
|
|
405
|
-
if (!state.cursor) continue;
|
|
406
|
-
if (state.cursor.collection !== collectionName || state.cursor.recordId !== recordId || state.cursor.field !== fieldName) {
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
fieldCursors.push({
|
|
410
|
-
clientId,
|
|
411
|
-
userName: state.user.name,
|
|
412
|
-
color: state.user.color,
|
|
413
|
-
anchor: state.cursor.anchor,
|
|
414
|
-
head: state.cursor.head
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
setCursors(fieldCursors);
|
|
418
|
-
};
|
|
419
|
-
const unsubscribe = awareness.on("change", () => {
|
|
420
|
-
updateCursors();
|
|
259
|
+
awareness.setLocalState({
|
|
260
|
+
user: { name, color, avatar: avatar ?? void 0 }
|
|
421
261
|
});
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
262
|
+
return () => {
|
|
263
|
+
awareness.setLocalState(null);
|
|
264
|
+
};
|
|
265
|
+
}, [syncEngine, name, color, avatar]);
|
|
426
266
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
267
|
+
|
|
268
|
+
// src/hooks/use-collaborators.ts
|
|
269
|
+
import { subscribeRemoteAwarenessStates } from "@korajs/sync";
|
|
270
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useRef as useRef4, useSyncExternalStore as useSyncExternalStore5 } from "react";
|
|
271
|
+
var EMPTY_ARRAY2 = [];
|
|
272
|
+
function useCollaborators() {
|
|
273
|
+
const { syncEngine } = useKoraContext();
|
|
274
|
+
const snapshotRef = useRef4(EMPTY_ARRAY2);
|
|
275
|
+
const subscribe = useCallback2(
|
|
276
|
+
(onStoreChange) => {
|
|
277
|
+
if (!syncEngine) {
|
|
278
|
+
snapshotRef.current = EMPTY_ARRAY2;
|
|
279
|
+
return () => {
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const awareness = syncEngine.getAwarenessManager();
|
|
283
|
+
return subscribeRemoteAwarenessStates(awareness, (states) => {
|
|
284
|
+
snapshotRef.current = states;
|
|
285
|
+
onStoreChange();
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
[syncEngine]
|
|
289
|
+
);
|
|
290
|
+
const getSnapshot = useCallback2(() => snapshotRef.current, []);
|
|
291
|
+
useEffect7(() => {
|
|
292
|
+
if (!syncEngine) {
|
|
293
|
+
snapshotRef.current = EMPTY_ARRAY2;
|
|
446
294
|
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
function composeRichtextSnapshot(base, deltas) {
|
|
451
|
-
const doc = new Y.Doc();
|
|
452
|
-
if (base) {
|
|
453
|
-
Y.applyUpdate(doc, base);
|
|
454
|
-
}
|
|
455
|
-
for (const delta of deltas) {
|
|
456
|
-
Y.applyUpdate(doc, delta);
|
|
457
|
-
}
|
|
458
|
-
return Y.encodeStateAsUpdate(doc);
|
|
295
|
+
}, [syncEngine]);
|
|
296
|
+
return useSyncExternalStore5(subscribe, getSnapshot);
|
|
459
297
|
}
|
|
460
298
|
export {
|
|
461
299
|
KoraProvider,
|
|
462
300
|
useApp,
|
|
301
|
+
useCollaborators,
|
|
463
302
|
useCollection,
|
|
464
303
|
useMutation,
|
|
304
|
+
usePresence,
|
|
465
305
|
useQuery,
|
|
466
306
|
useRichText,
|
|
467
307
|
useSyncStatus
|