@korajs/react 0.3.2 → 0.5.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 +296 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -13
- package/dist/index.d.ts +88 -13
- package/dist/index.js +298 -64
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -8,12 +8,8 @@ function KoraProvider({
|
|
|
8
8
|
fallback,
|
|
9
9
|
children
|
|
10
10
|
}) {
|
|
11
|
-
const [resolvedStore, setResolvedStore] = useState(
|
|
12
|
-
|
|
13
|
-
);
|
|
14
|
-
const [resolvedSync, setResolvedSync] = useState(
|
|
15
|
-
syncEngine ?? null
|
|
16
|
-
);
|
|
11
|
+
const [resolvedStore, setResolvedStore] = useState(store ?? null);
|
|
12
|
+
const [resolvedSync, setResolvedSync] = useState(syncEngine ?? null);
|
|
17
13
|
const [ready, setReady] = useState(!app);
|
|
18
14
|
const [initError, setInitError] = useState(null);
|
|
19
15
|
useEffect(() => {
|
|
@@ -52,7 +48,10 @@ function KoraProvider({
|
|
|
52
48
|
}
|
|
53
49
|
const value = {
|
|
54
50
|
store: resolvedStore,
|
|
55
|
-
syncEngine: resolvedSync
|
|
51
|
+
syncEngine: resolvedSync,
|
|
52
|
+
app: app ?? null,
|
|
53
|
+
events: app?.events ?? null,
|
|
54
|
+
subscribeSyncStatus: app?.sync?.subscribeStatus ?? null
|
|
56
55
|
};
|
|
57
56
|
return createElement(KoraContext.Provider, { value }, children);
|
|
58
57
|
}
|
|
@@ -66,6 +65,17 @@ function useKoraContext() {
|
|
|
66
65
|
return context;
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
// src/hooks/use-app.ts
|
|
69
|
+
function useApp() {
|
|
70
|
+
const { app } = useKoraContext();
|
|
71
|
+
if (!app) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'useApp() requires KoraProvider to be initialized with an "app" prop. Pass your createApp() result to <KoraProvider app={app}>.'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return app;
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
// src/hooks/use-query.ts
|
|
70
80
|
import { useMemo, useRef, useSyncExternalStore } from "react";
|
|
71
81
|
|
|
@@ -141,6 +151,18 @@ var QueryStore = class {
|
|
|
141
151
|
};
|
|
142
152
|
|
|
143
153
|
// src/hooks/use-query.ts
|
|
154
|
+
var APP_NOT_READY_CODE = "APP_NOT_READY";
|
|
155
|
+
function assertQueryReady(query) {
|
|
156
|
+
const descriptor = query.getDescriptor();
|
|
157
|
+
if (descriptor.collection === "__pending__") {
|
|
158
|
+
const err = new Error(
|
|
159
|
+
"Cannot use useQuery() before app.ready. Await app.ready or wrap your UI in <KoraProvider app={app}>."
|
|
160
|
+
);
|
|
161
|
+
err.name = "AppNotReadyError";
|
|
162
|
+
Object.assign(err, { code: APP_NOT_READY_CODE });
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
144
166
|
var EMPTY_ARRAY2 = Object.freeze([]);
|
|
145
167
|
var noopSubscribe = (_onStoreChange) => {
|
|
146
168
|
return () => {
|
|
@@ -148,6 +170,9 @@ var noopSubscribe = (_onStoreChange) => {
|
|
|
148
170
|
};
|
|
149
171
|
function useQuery(query, options) {
|
|
150
172
|
const enabled = options?.enabled !== false;
|
|
173
|
+
if (enabled) {
|
|
174
|
+
assertQueryReady(query);
|
|
175
|
+
}
|
|
151
176
|
const descriptorKey = JSON.stringify(query.getDescriptor());
|
|
152
177
|
const queryStoreRef = useRef(null);
|
|
153
178
|
const prevKeyRef = useRef(null);
|
|
@@ -180,7 +205,7 @@ function useQuery(query, options) {
|
|
|
180
205
|
|
|
181
206
|
// src/hooks/use-mutation.ts
|
|
182
207
|
import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
183
|
-
function useMutation(mutationFn) {
|
|
208
|
+
function useMutation(mutationFn, options) {
|
|
184
209
|
const [state, setState] = useState2({
|
|
185
210
|
isLoading: false,
|
|
186
211
|
error: null
|
|
@@ -194,21 +219,35 @@ function useMutation(mutationFn) {
|
|
|
194
219
|
}, []);
|
|
195
220
|
const fnRef = useRef2(mutationFn);
|
|
196
221
|
fnRef.current = mutationFn;
|
|
222
|
+
const optionsRef = useRef2(options);
|
|
223
|
+
optionsRef.current = options;
|
|
197
224
|
const mutateAsync = useCallback(async (...args) => {
|
|
225
|
+
const opts = optionsRef.current;
|
|
226
|
+
let context;
|
|
198
227
|
if (mountedRef.current) {
|
|
199
228
|
setState({ isLoading: true, error: null });
|
|
200
229
|
}
|
|
201
230
|
try {
|
|
231
|
+
if (opts?.onMutate) {
|
|
232
|
+
context = await opts.onMutate(...args);
|
|
233
|
+
}
|
|
202
234
|
const result = await fnRef.current(...args);
|
|
203
235
|
if (mountedRef.current) {
|
|
204
236
|
setState({ isLoading: false, error: null });
|
|
205
237
|
}
|
|
238
|
+
opts?.onSuccess?.(result, ...args);
|
|
239
|
+
opts?.onSettled?.(result, null, ...args);
|
|
206
240
|
return result;
|
|
207
241
|
} catch (err) {
|
|
208
242
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
243
|
+
if (context !== void 0 && opts?.onRollback) {
|
|
244
|
+
await opts.onRollback(context, ...args);
|
|
245
|
+
}
|
|
209
246
|
if (mountedRef.current) {
|
|
210
247
|
setState({ isLoading: false, error });
|
|
211
248
|
}
|
|
249
|
+
opts?.onError?.(error, ...args);
|
|
250
|
+
opts?.onSettled?.(void 0, error, ...args);
|
|
212
251
|
throw error;
|
|
213
252
|
}
|
|
214
253
|
}, []);
|
|
@@ -234,22 +273,45 @@ function useMutation(mutationFn) {
|
|
|
234
273
|
}
|
|
235
274
|
|
|
236
275
|
// src/hooks/use-sync-status.ts
|
|
237
|
-
import { useCallback as useCallback2,
|
|
238
|
-
var POLL_INTERVAL_MS = 500;
|
|
276
|
+
import { useCallback as useCallback2, useRef as useRef3, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
239
277
|
var OFFLINE_STATUS = Object.freeze({
|
|
240
278
|
status: "offline",
|
|
241
279
|
pendingOperations: 0,
|
|
242
|
-
lastSyncedAt: null
|
|
280
|
+
lastSyncedAt: null,
|
|
281
|
+
lastSuccessfulPush: null,
|
|
282
|
+
lastSuccessfulPull: null,
|
|
283
|
+
conflicts: 0
|
|
243
284
|
});
|
|
285
|
+
var SYNC_STATUS_EVENT_TYPES = [
|
|
286
|
+
"sync:connected",
|
|
287
|
+
"sync:disconnected",
|
|
288
|
+
"sync:schema-mismatch",
|
|
289
|
+
"sync:auth-failed",
|
|
290
|
+
"sync:sent",
|
|
291
|
+
"sync:received",
|
|
292
|
+
"sync:acknowledged",
|
|
293
|
+
"sync:apply-failed",
|
|
294
|
+
"sync:diagnostics",
|
|
295
|
+
"sync:initial-sync-progress"
|
|
296
|
+
];
|
|
244
297
|
function useSyncStatus() {
|
|
245
|
-
const { syncEngine } = useKoraContext();
|
|
298
|
+
const { syncEngine, subscribeSyncStatus, events } = useKoraContext();
|
|
246
299
|
const snapshotRef = useRef3(OFFLINE_STATUS);
|
|
247
300
|
const serializedRef = useRef3(JSON.stringify(OFFLINE_STATUS));
|
|
248
301
|
const subscribe = useCallback2(
|
|
249
302
|
(onStoreChange) => {
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
303
|
+
if (subscribeSyncStatus) {
|
|
304
|
+
return subscribeSyncStatus((status) => {
|
|
305
|
+
snapshotRef.current = status;
|
|
306
|
+
serializedRef.current = JSON.stringify(status);
|
|
307
|
+
onStoreChange();
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (!syncEngine) {
|
|
311
|
+
return () => {
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const refresh = () => {
|
|
253
315
|
const newStatus = syncEngine.getStatus();
|
|
254
316
|
const newSerialized = JSON.stringify(newStatus);
|
|
255
317
|
if (newSerialized !== serializedRef.current) {
|
|
@@ -257,29 +319,28 @@ function useSyncStatus() {
|
|
|
257
319
|
serializedRef.current = newSerialized;
|
|
258
320
|
onStoreChange();
|
|
259
321
|
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
322
|
+
};
|
|
323
|
+
if (events) {
|
|
324
|
+
const unsubs = SYNC_STATUS_EVENT_TYPES.map((type) => events.on(type, refresh));
|
|
325
|
+
refresh();
|
|
326
|
+
return () => {
|
|
327
|
+
for (const unsub of unsubs) {
|
|
328
|
+
unsub();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
267
331
|
}
|
|
332
|
+
refresh();
|
|
268
333
|
return () => {
|
|
269
|
-
clearInterval(intervalId);
|
|
270
334
|
};
|
|
271
335
|
},
|
|
272
|
-
[syncEngine]
|
|
336
|
+
[syncEngine, subscribeSyncStatus, events]
|
|
273
337
|
);
|
|
274
338
|
const getSnapshot = useCallback2(() => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
useEffect3(() => {
|
|
278
|
-
if (!syncEngine) {
|
|
279
|
-
snapshotRef.current = OFFLINE_STATUS;
|
|
280
|
-
serializedRef.current = JSON.stringify(OFFLINE_STATUS);
|
|
339
|
+
if (subscribeSyncStatus) {
|
|
340
|
+
return snapshotRef.current;
|
|
281
341
|
}
|
|
282
|
-
|
|
342
|
+
return syncEngine ? syncEngine.getStatus() : OFFLINE_STATUS;
|
|
343
|
+
}, [syncEngine, subscribeSyncStatus]);
|
|
283
344
|
return useSyncExternalStore2(subscribe, getSnapshot);
|
|
284
345
|
}
|
|
285
346
|
|
|
@@ -293,21 +354,35 @@ function useCollection(name) {
|
|
|
293
354
|
}
|
|
294
355
|
|
|
295
356
|
// src/hooks/use-rich-text.ts
|
|
296
|
-
import {
|
|
357
|
+
import { encodeRichtext } from "@korajs/store";
|
|
358
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
|
|
297
359
|
import * as Y from "yjs";
|
|
298
360
|
var LOAD_ORIGIN = "kora-load";
|
|
361
|
+
var REMOTE_ORIGIN = "kora-remote";
|
|
362
|
+
var DOC_CHANNEL_ORIGIN = "kora-doc-channel";
|
|
299
363
|
var TEXT_KEY = "content";
|
|
300
364
|
var COMPACT_AFTER_DELTAS = 20;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
365
|
+
var PERSIST_DEBOUNCE_MS = 400;
|
|
366
|
+
function useRichText(collectionName, recordId, fieldName, options) {
|
|
367
|
+
const { store, syncEngine } = useKoraContext();
|
|
368
|
+
const collection = useMemo3(
|
|
369
|
+
() => store.collection(collectionName),
|
|
370
|
+
[store, collectionName]
|
|
371
|
+
);
|
|
304
372
|
const [doc] = useState3(() => new Y.Doc());
|
|
305
373
|
const [ready, setReady] = useState3(false);
|
|
306
374
|
const [error, setError] = useState3(null);
|
|
307
375
|
const [canUndo, setCanUndo] = useState3(false);
|
|
308
376
|
const [canRedo, setCanRedo] = useState3(false);
|
|
377
|
+
const [cursors, setCursors] = useState3([]);
|
|
309
378
|
const baseUpdateRef = useRef4(null);
|
|
310
379
|
const pendingDeltasRef = useRef4([]);
|
|
380
|
+
const docChannelActiveRef = useRef4(false);
|
|
381
|
+
const persistTimerRef = useRef4(null);
|
|
382
|
+
const userRef = useRef4(options?.user);
|
|
383
|
+
useEffect3(() => {
|
|
384
|
+
userRef.current = options?.user;
|
|
385
|
+
}, [options?.user]);
|
|
311
386
|
const text = useMemo3(() => doc.getText(TEXT_KEY), [doc]);
|
|
312
387
|
const undoManager = useMemo3(() => new Y.UndoManager(text), [text]);
|
|
313
388
|
const syncHistoryState = useCallback3(() => {
|
|
@@ -322,7 +397,73 @@ function useRichText(collectionName, recordId, fieldName) {
|
|
|
322
397
|
undoManager.redo();
|
|
323
398
|
syncHistoryState();
|
|
324
399
|
}, [syncHistoryState, undoManager]);
|
|
325
|
-
|
|
400
|
+
const resolveAwarenessUser = useCallback3(() => {
|
|
401
|
+
if (userRef.current) {
|
|
402
|
+
return userRef.current;
|
|
403
|
+
}
|
|
404
|
+
if (syncEngine) {
|
|
405
|
+
const existing = syncEngine.getAwarenessManager().getLocalState()?.user;
|
|
406
|
+
if (existing) {
|
|
407
|
+
return existing;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
name: "Anonymous",
|
|
412
|
+
color: "#6366f1"
|
|
413
|
+
};
|
|
414
|
+
}, [syncEngine]);
|
|
415
|
+
const setCursor = useCallback3(
|
|
416
|
+
(anchor, head) => {
|
|
417
|
+
if (!syncEngine) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const awareness = syncEngine.getAwarenessManager();
|
|
421
|
+
const state = {
|
|
422
|
+
user: resolveAwarenessUser(),
|
|
423
|
+
cursor: {
|
|
424
|
+
collection: collectionName,
|
|
425
|
+
recordId,
|
|
426
|
+
field: fieldName,
|
|
427
|
+
anchor,
|
|
428
|
+
head
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
awareness.setLocalState(state);
|
|
432
|
+
},
|
|
433
|
+
[collectionName, fieldName, recordId, resolveAwarenessUser, syncEngine]
|
|
434
|
+
);
|
|
435
|
+
const clearCursor = useCallback3(() => {
|
|
436
|
+
if (!syncEngine) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const awareness = syncEngine.getAwarenessManager();
|
|
440
|
+
const current = awareness.getLocalState();
|
|
441
|
+
if (!current?.cursor) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (current.cursor.collection !== collectionName || current.cursor.recordId !== recordId || current.cursor.field !== fieldName) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
awareness.setLocalState({ user: current.user });
|
|
448
|
+
}, [collectionName, fieldName, recordId, syncEngine]);
|
|
449
|
+
const applyRemoteSnapshot = useCallback3(
|
|
450
|
+
(value) => {
|
|
451
|
+
const encoded = encodeRichtextInput(value);
|
|
452
|
+
if (!encoded) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const currentSnapshot = Y.encodeStateAsUpdate(doc);
|
|
456
|
+
if (updatesEqual(currentSnapshot, encoded)) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
Y.applyUpdate(doc, encoded, REMOTE_ORIGIN);
|
|
460
|
+
baseUpdateRef.current = encoded;
|
|
461
|
+
pendingDeltasRef.current = [];
|
|
462
|
+
syncHistoryState();
|
|
463
|
+
},
|
|
464
|
+
[doc, syncHistoryState]
|
|
465
|
+
);
|
|
466
|
+
useEffect3(() => {
|
|
326
467
|
let disposed = false;
|
|
327
468
|
const initialize = async () => {
|
|
328
469
|
setReady(false);
|
|
@@ -340,18 +481,15 @@ function useRichText(collectionName, recordId, fieldName) {
|
|
|
340
481
|
if (encoded) {
|
|
341
482
|
Y.applyUpdate(doc, encoded, LOAD_ORIGIN);
|
|
342
483
|
}
|
|
484
|
+
const channel = syncEngine?.getRichtextDocChannel?.();
|
|
485
|
+
docChannelActiveRef.current = channel?.shouldUseChannel(encoded?.length ?? 0, options?.useDocChannel) ?? false;
|
|
343
486
|
setReady(true);
|
|
344
487
|
} catch (cause) {
|
|
345
488
|
if (disposed) return;
|
|
346
489
|
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
347
490
|
}
|
|
348
491
|
};
|
|
349
|
-
const
|
|
350
|
-
syncHistoryState();
|
|
351
|
-
if (origin === LOAD_ORIGIN) {
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
pendingDeltasRef.current.push(_update);
|
|
492
|
+
const flushPersist = async () => {
|
|
355
493
|
const snapshot = composeRichtextSnapshot(baseUpdateRef.current, pendingDeltasRef.current);
|
|
356
494
|
if (pendingDeltasRef.current.length >= COMPACT_AFTER_DELTAS) {
|
|
357
495
|
baseUpdateRef.current = snapshot;
|
|
@@ -367,54 +505,150 @@ function useRichText(collectionName, recordId, fieldName) {
|
|
|
367
505
|
}
|
|
368
506
|
}
|
|
369
507
|
};
|
|
508
|
+
const schedulePersist = () => {
|
|
509
|
+
if (persistTimerRef.current) {
|
|
510
|
+
clearTimeout(persistTimerRef.current);
|
|
511
|
+
}
|
|
512
|
+
persistTimerRef.current = setTimeout(() => {
|
|
513
|
+
persistTimerRef.current = null;
|
|
514
|
+
void flushPersist();
|
|
515
|
+
}, PERSIST_DEBOUNCE_MS);
|
|
516
|
+
};
|
|
517
|
+
const persist = (_update, origin) => {
|
|
518
|
+
syncHistoryState();
|
|
519
|
+
if (origin === LOAD_ORIGIN || origin === REMOTE_ORIGIN || origin === DOC_CHANNEL_ORIGIN) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
pendingDeltasRef.current.push(_update);
|
|
523
|
+
if (docChannelActiveRef.current && syncEngine) {
|
|
524
|
+
syncEngine.getRichtextDocChannel().send(collectionName, recordId, fieldName, _update);
|
|
525
|
+
schedulePersist();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
void flushPersist();
|
|
529
|
+
};
|
|
370
530
|
doc.on("update", persist);
|
|
371
531
|
void initialize();
|
|
372
532
|
syncHistoryState();
|
|
373
533
|
return () => {
|
|
374
534
|
disposed = true;
|
|
535
|
+
if (persistTimerRef.current) {
|
|
536
|
+
clearTimeout(persistTimerRef.current);
|
|
537
|
+
persistTimerRef.current = null;
|
|
538
|
+
}
|
|
375
539
|
doc.off("update", persist);
|
|
376
540
|
undoManager.destroy();
|
|
377
541
|
baseUpdateRef.current = null;
|
|
378
542
|
pendingDeltasRef.current = [];
|
|
543
|
+
docChannelActiveRef.current = false;
|
|
544
|
+
};
|
|
545
|
+
}, [
|
|
546
|
+
collection,
|
|
547
|
+
collectionName,
|
|
548
|
+
doc,
|
|
549
|
+
fieldName,
|
|
550
|
+
options?.useDocChannel,
|
|
551
|
+
recordId,
|
|
552
|
+
syncEngine,
|
|
553
|
+
syncHistoryState,
|
|
554
|
+
undoManager
|
|
555
|
+
]);
|
|
556
|
+
useEffect3(() => {
|
|
557
|
+
if (!ready || !syncEngine || !docChannelActiveRef.current) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const channel = syncEngine.getRichtextDocChannel();
|
|
561
|
+
return channel.subscribe(collectionName, recordId, fieldName, (update) => {
|
|
562
|
+
Y.applyUpdate(doc, update, DOC_CHANNEL_ORIGIN);
|
|
563
|
+
syncHistoryState();
|
|
564
|
+
});
|
|
565
|
+
}, [collectionName, doc, fieldName, ready, recordId, syncEngine, syncHistoryState]);
|
|
566
|
+
useEffect3(() => {
|
|
567
|
+
if (!ready) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const unsubscribe = store.collection(collectionName).where({ id: recordId }).subscribe((results) => {
|
|
571
|
+
const record = results[0];
|
|
572
|
+
if (!record) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
applyRemoteSnapshot(record[fieldName]);
|
|
576
|
+
});
|
|
577
|
+
return unsubscribe;
|
|
578
|
+
}, [applyRemoteSnapshot, collectionName, fieldName, ready, recordId, store]);
|
|
579
|
+
useEffect3(() => {
|
|
580
|
+
if (!syncEngine) return;
|
|
581
|
+
const awareness = syncEngine.getAwarenessManager();
|
|
582
|
+
const localClientId = awareness.clientId;
|
|
583
|
+
const updateCursors = () => {
|
|
584
|
+
const states = awareness.getStates();
|
|
585
|
+
const fieldCursors = [];
|
|
586
|
+
for (const [clientId, state] of states) {
|
|
587
|
+
if (clientId === localClientId) continue;
|
|
588
|
+
if (!state.cursor) continue;
|
|
589
|
+
if (state.cursor.collection !== collectionName || state.cursor.recordId !== recordId || state.cursor.field !== fieldName) {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
fieldCursors.push({
|
|
593
|
+
clientId,
|
|
594
|
+
userName: state.user.name,
|
|
595
|
+
color: state.user.color,
|
|
596
|
+
anchor: state.cursor.anchor,
|
|
597
|
+
head: state.cursor.head
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
setCursors(fieldCursors);
|
|
601
|
+
};
|
|
602
|
+
const unsubscribe = awareness.on("change", () => {
|
|
603
|
+
updateCursors();
|
|
604
|
+
});
|
|
605
|
+
updateCursors();
|
|
606
|
+
return () => {
|
|
607
|
+
unsubscribe();
|
|
608
|
+
clearCursor();
|
|
379
609
|
};
|
|
380
|
-
}, [
|
|
381
|
-
return { doc, text, undo, redo, canUndo, canRedo, ready, error };
|
|
610
|
+
}, [clearCursor, collectionName, fieldName, recordId, syncEngine]);
|
|
611
|
+
return { doc, text, undo, redo, canUndo, canRedo, ready, error, cursors, setCursor, clearCursor };
|
|
382
612
|
}
|
|
383
613
|
function encodeRichtextInput(value) {
|
|
384
614
|
if (value === null || value === void 0) {
|
|
385
615
|
return null;
|
|
386
616
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
if (value instanceof ArrayBuffer) {
|
|
396
|
-
return new Uint8Array(value);
|
|
397
|
-
}
|
|
398
|
-
if (typeof globalThis !== "undefined" && "Buffer" in globalThis) {
|
|
399
|
-
const BufferCtor = globalThis.Buffer;
|
|
400
|
-
if (BufferCtor.isBuffer(value)) {
|
|
401
|
-
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
617
|
+
try {
|
|
618
|
+
return encodeRichtext(value);
|
|
619
|
+
} catch {
|
|
620
|
+
if (typeof value === "string") {
|
|
621
|
+
const fallbackDoc = new Y.Doc();
|
|
622
|
+
fallbackDoc.getText(TEXT_KEY).insert(0, value);
|
|
623
|
+
return Y.encodeStateAsUpdate(fallbackDoc);
|
|
402
624
|
}
|
|
625
|
+
throw new Error("Richtext record value must be a string, Uint8Array, ArrayBuffer, or null.");
|
|
403
626
|
}
|
|
404
|
-
throw new Error("Richtext record value must be a string, Uint8Array, ArrayBuffer, or null.");
|
|
405
627
|
}
|
|
406
628
|
function composeRichtextSnapshot(base, deltas) {
|
|
407
|
-
const
|
|
629
|
+
const mergedDoc = new Y.Doc();
|
|
408
630
|
if (base) {
|
|
409
|
-
Y.applyUpdate(
|
|
631
|
+
Y.applyUpdate(mergedDoc, base);
|
|
410
632
|
}
|
|
411
633
|
for (const delta of deltas) {
|
|
412
|
-
Y.applyUpdate(
|
|
634
|
+
Y.applyUpdate(mergedDoc, delta);
|
|
635
|
+
}
|
|
636
|
+
return Y.encodeStateAsUpdate(mergedDoc);
|
|
637
|
+
}
|
|
638
|
+
function updatesEqual(left, right) {
|
|
639
|
+
if (left.length !== right.length) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
for (let index = 0; index < left.length; index++) {
|
|
643
|
+
if (left[index] !== right[index]) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
413
646
|
}
|
|
414
|
-
return
|
|
647
|
+
return true;
|
|
415
648
|
}
|
|
416
649
|
export {
|
|
417
650
|
KoraProvider,
|
|
651
|
+
useApp,
|
|
418
652
|
useCollection,
|
|
419
653
|
useMutation,
|
|
420
654
|
useQuery,
|