@korajs/react 0.5.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 +165 -522
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -162
- package/dist/index.d.ts +27 -162
- package/dist/index.js +174 -523
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
@@ -32,8 +22,10 @@ var index_exports = {};
|
|
|
32
22
|
__export(index_exports, {
|
|
33
23
|
KoraProvider: () => KoraProvider,
|
|
34
24
|
useApp: () => useApp,
|
|
25
|
+
useCollaborators: () => useCollaborators,
|
|
35
26
|
useCollection: () => useCollection,
|
|
36
27
|
useMutation: () => useMutation,
|
|
28
|
+
usePresence: () => usePresence,
|
|
37
29
|
useQuery: () => useQuery,
|
|
38
30
|
useRichText: () => useRichText,
|
|
39
31
|
useSyncStatus: () => useSyncStatus
|
|
@@ -41,6 +33,7 @@ __export(index_exports, {
|
|
|
41
33
|
module.exports = __toCommonJS(index_exports);
|
|
42
34
|
|
|
43
35
|
// src/context/kora-context.ts
|
|
36
|
+
var import_store = require("@korajs/store");
|
|
44
37
|
var import_react = require("react");
|
|
45
38
|
var KoraContext = (0, import_react.createContext)(null);
|
|
46
39
|
function KoraProvider({
|
|
@@ -54,6 +47,10 @@ function KoraProvider({
|
|
|
54
47
|
const [resolvedSync, setResolvedSync] = (0, import_react.useState)(syncEngine ?? null);
|
|
55
48
|
const [ready, setReady] = (0, import_react.useState)(!app);
|
|
56
49
|
const [initError, setInitError] = (0, import_react.useState)(null);
|
|
50
|
+
const fallbackQueryStoreCache = (0, import_react.useRef)(null);
|
|
51
|
+
if (!fallbackQueryStoreCache.current) {
|
|
52
|
+
fallbackQueryStoreCache.current = new import_store.QueryStoreCache();
|
|
53
|
+
}
|
|
57
54
|
(0, import_react.useEffect)(() => {
|
|
58
55
|
if (!app) return;
|
|
59
56
|
let cancelled = false;
|
|
@@ -72,6 +69,26 @@ function KoraProvider({
|
|
|
72
69
|
cancelled = true;
|
|
73
70
|
};
|
|
74
71
|
}, [app]);
|
|
72
|
+
(0, import_react.useEffect)(() => {
|
|
73
|
+
return () => {
|
|
74
|
+
if (!app) {
|
|
75
|
+
fallbackQueryStoreCache.current?.clear();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}, [app]);
|
|
79
|
+
const contextValue = (0, import_react.useMemo)(() => {
|
|
80
|
+
if (!resolvedStore) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
store: resolvedStore,
|
|
85
|
+
syncEngine: resolvedSync,
|
|
86
|
+
app: app ?? null,
|
|
87
|
+
events: app?.events ?? null,
|
|
88
|
+
subscribeSyncStatus: app?.sync?.subscribeStatus ?? null,
|
|
89
|
+
queryStoreCache: app && typeof app.getQueryStoreCache === "function" ? app.getQueryStoreCache() : fallbackQueryStoreCache.current
|
|
90
|
+
};
|
|
91
|
+
}, [resolvedStore, resolvedSync, app]);
|
|
75
92
|
if (initError) {
|
|
76
93
|
return (0, import_react.createElement)(
|
|
77
94
|
"div",
|
|
@@ -83,19 +100,12 @@ function KoraProvider({
|
|
|
83
100
|
if (!ready) {
|
|
84
101
|
return fallback ?? null;
|
|
85
102
|
}
|
|
86
|
-
if (!
|
|
103
|
+
if (!contextValue) {
|
|
87
104
|
throw new Error(
|
|
88
105
|
'KoraProvider requires either an "app" or "store" prop. Pass a KoraApp from createApp() or a Store instance.'
|
|
89
106
|
);
|
|
90
107
|
}
|
|
91
|
-
|
|
92
|
-
store: resolvedStore,
|
|
93
|
-
syncEngine: resolvedSync,
|
|
94
|
-
app: app ?? null,
|
|
95
|
-
events: app?.events ?? null,
|
|
96
|
-
subscribeSyncStatus: app?.sync?.subscribeStatus ?? null
|
|
97
|
-
};
|
|
98
|
-
return (0, import_react.createElement)(KoraContext.Provider, { value }, children);
|
|
108
|
+
return (0, import_react.createElement)(KoraContext.Provider, { value: contextValue }, children);
|
|
99
109
|
}
|
|
100
110
|
function useKoraContext() {
|
|
101
111
|
const context = (0, import_react.useContext)(KoraContext);
|
|
@@ -119,126 +129,35 @@ function useApp() {
|
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
// src/hooks/use-query.ts
|
|
132
|
+
var import_store2 = require("@korajs/store");
|
|
122
133
|
var import_react2 = require("react");
|
|
123
|
-
|
|
124
|
-
// src/query-store/query-store.ts
|
|
125
134
|
var EMPTY_ARRAY = Object.freeze([]);
|
|
126
|
-
var QueryStore = class {
|
|
127
|
-
snapshot = EMPTY_ARRAY;
|
|
128
|
-
listeners = /* @__PURE__ */ new Set();
|
|
129
|
-
unsubscribeQuery = null;
|
|
130
|
-
active = false;
|
|
131
|
-
queryBuilder;
|
|
132
|
-
constructor(queryBuilder) {
|
|
133
|
-
this.queryBuilder = queryBuilder;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Subscribe to snapshot changes. Compatible with useSyncExternalStore.
|
|
137
|
-
*
|
|
138
|
-
* Lazily starts the underlying query subscription when the first listener
|
|
139
|
-
* attaches, and stops it when the last listener detaches. This allows
|
|
140
|
-
* React StrictMode to unmount/remount without permanently killing the subscription.
|
|
141
|
-
*
|
|
142
|
-
* @returns Unsubscribe function
|
|
143
|
-
*/
|
|
144
|
-
subscribe = (onStoreChange) => {
|
|
145
|
-
this.listeners.add(onStoreChange);
|
|
146
|
-
if (!this.active) {
|
|
147
|
-
this.startSubscription();
|
|
148
|
-
}
|
|
149
|
-
return () => {
|
|
150
|
-
this.listeners.delete(onStoreChange);
|
|
151
|
-
if (this.listeners.size === 0) {
|
|
152
|
-
this.stopSubscription();
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
};
|
|
156
|
-
/**
|
|
157
|
-
* Get the current snapshot synchronously. Compatible with useSyncExternalStore.
|
|
158
|
-
* Returns EMPTY_ARRAY before the first async fetch completes.
|
|
159
|
-
*/
|
|
160
|
-
getSnapshot = () => {
|
|
161
|
-
return this.snapshot;
|
|
162
|
-
};
|
|
163
|
-
/**
|
|
164
|
-
* Clean up the underlying subscription and release resources.
|
|
165
|
-
* Called by useMemo when the query descriptor changes.
|
|
166
|
-
*/
|
|
167
|
-
destroy() {
|
|
168
|
-
this.stopSubscription();
|
|
169
|
-
this.listeners.clear();
|
|
170
|
-
this.snapshot = EMPTY_ARRAY;
|
|
171
|
-
}
|
|
172
|
-
startSubscription() {
|
|
173
|
-
this.active = true;
|
|
174
|
-
this.unsubscribeQuery = this.queryBuilder.subscribe((results) => {
|
|
175
|
-
if (!this.active) return;
|
|
176
|
-
const newSnapshot = Object.freeze([...results]);
|
|
177
|
-
this.snapshot = newSnapshot;
|
|
178
|
-
this.notifyListeners();
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
stopSubscription() {
|
|
182
|
-
this.active = false;
|
|
183
|
-
if (this.unsubscribeQuery) {
|
|
184
|
-
this.unsubscribeQuery();
|
|
185
|
-
this.unsubscribeQuery = null;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
notifyListeners() {
|
|
189
|
-
for (const listener of this.listeners) {
|
|
190
|
-
listener();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// src/hooks/use-query.ts
|
|
196
|
-
var APP_NOT_READY_CODE = "APP_NOT_READY";
|
|
197
|
-
function assertQueryReady(query) {
|
|
198
|
-
const descriptor = query.getDescriptor();
|
|
199
|
-
if (descriptor.collection === "__pending__") {
|
|
200
|
-
const err = new Error(
|
|
201
|
-
"Cannot use useQuery() before app.ready. Await app.ready or wrap your UI in <KoraProvider app={app}>."
|
|
202
|
-
);
|
|
203
|
-
err.name = "AppNotReadyError";
|
|
204
|
-
Object.assign(err, { code: APP_NOT_READY_CODE });
|
|
205
|
-
throw err;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
var EMPTY_ARRAY2 = Object.freeze([]);
|
|
209
135
|
var noopSubscribe = (_onStoreChange) => {
|
|
210
136
|
return () => {
|
|
211
137
|
};
|
|
212
138
|
};
|
|
213
139
|
function useQuery(query, options) {
|
|
140
|
+
const { queryStoreCache } = useKoraContext();
|
|
214
141
|
const enabled = options?.enabled !== false;
|
|
215
|
-
if (enabled) {
|
|
216
|
-
assertQueryReady(query);
|
|
217
|
-
}
|
|
218
142
|
const descriptorKey = JSON.stringify(query.getDescriptor());
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
const queryStore = (0, import_react2.
|
|
143
|
+
const queryRef = (0, import_react2.useRef)(query);
|
|
144
|
+
queryRef.current = query;
|
|
145
|
+
const [queryStore, setQueryStore] = (0, import_react2.useState)(null);
|
|
146
|
+
(0, import_react2.useEffect)(() => {
|
|
222
147
|
if (!enabled) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
queryStoreRef.current = null;
|
|
226
|
-
}
|
|
227
|
-
prevKeyRef.current = null;
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
if (prevKeyRef.current !== descriptorKey) {
|
|
231
|
-
if (queryStoreRef.current) {
|
|
232
|
-
queryStoreRef.current.destroy();
|
|
233
|
-
}
|
|
234
|
-
const newStore = new QueryStore(query);
|
|
235
|
-
queryStoreRef.current = newStore;
|
|
236
|
-
prevKeyRef.current = descriptorKey;
|
|
237
|
-
return newStore;
|
|
148
|
+
setQueryStore(null);
|
|
149
|
+
return;
|
|
238
150
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
151
|
+
const currentQuery = queryRef.current;
|
|
152
|
+
(0, import_store2.assertQueryReady)(currentQuery);
|
|
153
|
+
const store = queryStoreCache.getOrCreate(currentQuery);
|
|
154
|
+
setQueryStore(store);
|
|
155
|
+
return () => {
|
|
156
|
+
queryStoreCache.release(currentQuery);
|
|
157
|
+
setQueryStore(null);
|
|
158
|
+
};
|
|
159
|
+
}, [descriptorKey, enabled, queryStoreCache]);
|
|
160
|
+
const disabledGetSnapshot = () => EMPTY_ARRAY;
|
|
242
161
|
return (0, import_react2.useSyncExternalStore)(
|
|
243
162
|
queryStore ? queryStore.subscribe : noopSubscribe,
|
|
244
163
|
queryStore ? queryStore.getSnapshot : disabledGetSnapshot
|
|
@@ -246,144 +165,54 @@ function useQuery(query, options) {
|
|
|
246
165
|
}
|
|
247
166
|
|
|
248
167
|
// src/hooks/use-mutation.ts
|
|
168
|
+
var import_bindings = require("@korajs/core/bindings");
|
|
249
169
|
var import_react3 = require("react");
|
|
250
170
|
function useMutation(mutationFn, options) {
|
|
251
|
-
const [state, setState] = (0, import_react3.useState)({
|
|
252
|
-
isLoading: false,
|
|
253
|
-
error: null
|
|
254
|
-
});
|
|
255
|
-
const mountedRef = (0, import_react3.useRef)(true);
|
|
256
|
-
(0, import_react3.useEffect)(() => {
|
|
257
|
-
mountedRef.current = true;
|
|
258
|
-
return () => {
|
|
259
|
-
mountedRef.current = false;
|
|
260
|
-
};
|
|
261
|
-
}, []);
|
|
262
171
|
const fnRef = (0, import_react3.useRef)(mutationFn);
|
|
263
172
|
fnRef.current = mutationFn;
|
|
264
173
|
const optionsRef = (0, import_react3.useRef)(options);
|
|
265
174
|
optionsRef.current = options;
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
setState({ isLoading: false, error: null });
|
|
279
|
-
}
|
|
280
|
-
opts?.onSuccess?.(result, ...args);
|
|
281
|
-
opts?.onSettled?.(result, null, ...args);
|
|
282
|
-
return result;
|
|
283
|
-
} catch (err) {
|
|
284
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
285
|
-
if (context !== void 0 && opts?.onRollback) {
|
|
286
|
-
await opts.onRollback(context, ...args);
|
|
287
|
-
}
|
|
288
|
-
if (mountedRef.current) {
|
|
289
|
-
setState({ isLoading: false, error });
|
|
290
|
-
}
|
|
291
|
-
opts?.onError?.(error, ...args);
|
|
292
|
-
opts?.onSettled?.(void 0, error, ...args);
|
|
293
|
-
throw error;
|
|
294
|
-
}
|
|
295
|
-
}, []);
|
|
296
|
-
const mutate = (0, import_react3.useCallback)(
|
|
297
|
-
(...args) => {
|
|
298
|
-
mutateAsync(...args).catch(() => {
|
|
299
|
-
});
|
|
300
|
-
},
|
|
301
|
-
[mutateAsync]
|
|
175
|
+
const controller = (0, import_react3.useMemo)(
|
|
176
|
+
() => (0, import_bindings.createMutationController)({
|
|
177
|
+
mutationFn: (...args) => fnRef.current(...args),
|
|
178
|
+
resolveOptions: () => optionsRef.current
|
|
179
|
+
}),
|
|
180
|
+
[]
|
|
181
|
+
);
|
|
182
|
+
(0, import_react3.useEffect)(() => () => controller.destroy(), [controller]);
|
|
183
|
+
const state = (0, import_react3.useSyncExternalStore)(
|
|
184
|
+
(onStoreChange) => controller.subscribe(onStoreChange),
|
|
185
|
+
() => controller.getSnapshot(),
|
|
186
|
+
() => controller.getSnapshot()
|
|
302
187
|
);
|
|
303
|
-
const reset = (0, import_react3.useCallback)(() => {
|
|
304
|
-
if (mountedRef.current) {
|
|
305
|
-
setState({ isLoading: false, error: null });
|
|
306
|
-
}
|
|
307
|
-
}, []);
|
|
308
188
|
return {
|
|
309
|
-
mutate,
|
|
310
|
-
mutateAsync,
|
|
189
|
+
mutate: (...args) => controller.mutate(...args),
|
|
190
|
+
mutateAsync: (...args) => controller.mutateAsync(...args),
|
|
311
191
|
isLoading: state.isLoading,
|
|
312
192
|
error: state.error,
|
|
313
|
-
reset
|
|
193
|
+
reset: () => controller.reset()
|
|
314
194
|
};
|
|
315
195
|
}
|
|
316
196
|
|
|
317
197
|
// src/hooks/use-sync-status.ts
|
|
198
|
+
var import_sync = require("@korajs/sync");
|
|
318
199
|
var import_react4 = require("react");
|
|
319
|
-
var OFFLINE_STATUS = Object.freeze({
|
|
320
|
-
status: "offline",
|
|
321
|
-
pendingOperations: 0,
|
|
322
|
-
lastSyncedAt: null,
|
|
323
|
-
lastSuccessfulPush: null,
|
|
324
|
-
lastSuccessfulPull: null,
|
|
325
|
-
conflicts: 0
|
|
326
|
-
});
|
|
327
|
-
var SYNC_STATUS_EVENT_TYPES = [
|
|
328
|
-
"sync:connected",
|
|
329
|
-
"sync:disconnected",
|
|
330
|
-
"sync:schema-mismatch",
|
|
331
|
-
"sync:auth-failed",
|
|
332
|
-
"sync:sent",
|
|
333
|
-
"sync:received",
|
|
334
|
-
"sync:acknowledged",
|
|
335
|
-
"sync:apply-failed",
|
|
336
|
-
"sync:diagnostics",
|
|
337
|
-
"sync:initial-sync-progress"
|
|
338
|
-
];
|
|
339
200
|
function useSyncStatus() {
|
|
340
201
|
const { syncEngine, subscribeSyncStatus, events } = useKoraContext();
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
snapshotRef.current = status;
|
|
348
|
-
serializedRef.current = JSON.stringify(status);
|
|
349
|
-
onStoreChange();
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
if (!syncEngine) {
|
|
353
|
-
return () => {
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
const refresh = () => {
|
|
357
|
-
const newStatus = syncEngine.getStatus();
|
|
358
|
-
const newSerialized = JSON.stringify(newStatus);
|
|
359
|
-
if (newSerialized !== serializedRef.current) {
|
|
360
|
-
snapshotRef.current = newStatus;
|
|
361
|
-
serializedRef.current = newSerialized;
|
|
362
|
-
onStoreChange();
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
if (events) {
|
|
366
|
-
const unsubs = SYNC_STATUS_EVENT_TYPES.map((type) => events.on(type, refresh));
|
|
367
|
-
refresh();
|
|
368
|
-
return () => {
|
|
369
|
-
for (const unsub of unsubs) {
|
|
370
|
-
unsub();
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
refresh();
|
|
375
|
-
return () => {
|
|
376
|
-
};
|
|
377
|
-
},
|
|
202
|
+
const controller = (0, import_react4.useMemo)(
|
|
203
|
+
() => (0, import_sync.createSyncStatusController)({
|
|
204
|
+
syncEngine,
|
|
205
|
+
subscribeSyncStatus,
|
|
206
|
+
events: subscribeSyncStatus ? null : events
|
|
207
|
+
}),
|
|
378
208
|
[syncEngine, subscribeSyncStatus, events]
|
|
379
209
|
);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
|
|
210
|
+
(0, import_react4.useEffect)(() => () => controller.destroy(), [controller]);
|
|
211
|
+
return (0, import_react4.useSyncExternalStore)(
|
|
212
|
+
(onStoreChange) => controller.subscribe(onStoreChange),
|
|
213
|
+
() => controller.getSnapshot(),
|
|
214
|
+
() => controller.getSnapshot()
|
|
215
|
+
);
|
|
387
216
|
}
|
|
388
217
|
|
|
389
218
|
// src/hooks/use-collection.ts
|
|
@@ -396,304 +225,118 @@ function useCollection(name) {
|
|
|
396
225
|
}
|
|
397
226
|
|
|
398
227
|
// src/hooks/use-rich-text.ts
|
|
399
|
-
var
|
|
228
|
+
var import_store3 = require("@korajs/store");
|
|
400
229
|
var import_react6 = require("react");
|
|
401
|
-
var Y = __toESM(require("yjs"), 1);
|
|
402
|
-
var LOAD_ORIGIN = "kora-load";
|
|
403
|
-
var REMOTE_ORIGIN = "kora-remote";
|
|
404
|
-
var DOC_CHANNEL_ORIGIN = "kora-doc-channel";
|
|
405
|
-
var TEXT_KEY = "content";
|
|
406
|
-
var COMPACT_AFTER_DELTAS = 20;
|
|
407
|
-
var PERSIST_DEBOUNCE_MS = 400;
|
|
408
230
|
function useRichText(collectionName, recordId, fieldName, options) {
|
|
409
231
|
const { store, syncEngine } = useKoraContext();
|
|
410
232
|
const collection = (0, import_react6.useMemo)(
|
|
411
233
|
() => store.collection(collectionName),
|
|
412
234
|
[store, collectionName]
|
|
413
235
|
);
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
236
|
+
const controller = (0, import_react6.useMemo)(
|
|
237
|
+
() => (0, import_store3.createRichTextController)({
|
|
238
|
+
collection,
|
|
239
|
+
collectionName,
|
|
240
|
+
recordId,
|
|
241
|
+
fieldName,
|
|
242
|
+
store,
|
|
243
|
+
syncEngine: (0, import_store3.asRichTextSyncEngine)(syncEngine),
|
|
244
|
+
useDocChannel: options?.useDocChannel,
|
|
245
|
+
user: options?.user
|
|
246
|
+
}),
|
|
247
|
+
[collection, collectionName, fieldName, options?.useDocChannel, recordId, store, syncEngine]
|
|
248
|
+
);
|
|
425
249
|
(0, import_react6.useEffect)(() => {
|
|
426
|
-
|
|
427
|
-
}, [options?.user]);
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}, [undoManager]);
|
|
434
|
-
const undo = (0, import_react6.useCallback)(() => {
|
|
435
|
-
undoManager.undo();
|
|
436
|
-
syncHistoryState();
|
|
437
|
-
}, [syncHistoryState, undoManager]);
|
|
438
|
-
const redo = (0, import_react6.useCallback)(() => {
|
|
439
|
-
undoManager.redo();
|
|
440
|
-
syncHistoryState();
|
|
441
|
-
}, [syncHistoryState, undoManager]);
|
|
442
|
-
const resolveAwarenessUser = (0, import_react6.useCallback)(() => {
|
|
443
|
-
if (userRef.current) {
|
|
444
|
-
return userRef.current;
|
|
445
|
-
}
|
|
446
|
-
if (syncEngine) {
|
|
447
|
-
const existing = syncEngine.getAwarenessManager().getLocalState()?.user;
|
|
448
|
-
if (existing) {
|
|
449
|
-
return existing;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
return {
|
|
453
|
-
name: "Anonymous",
|
|
454
|
-
color: "#6366f1"
|
|
455
|
-
};
|
|
456
|
-
}, [syncEngine]);
|
|
457
|
-
const setCursor = (0, import_react6.useCallback)(
|
|
458
|
-
(anchor, head) => {
|
|
459
|
-
if (!syncEngine) {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
const awareness = syncEngine.getAwarenessManager();
|
|
463
|
-
const state = {
|
|
464
|
-
user: resolveAwarenessUser(),
|
|
465
|
-
cursor: {
|
|
466
|
-
collection: collectionName,
|
|
467
|
-
recordId,
|
|
468
|
-
field: fieldName,
|
|
469
|
-
anchor,
|
|
470
|
-
head
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
awareness.setLocalState(state);
|
|
474
|
-
},
|
|
475
|
-
[collectionName, fieldName, recordId, resolveAwarenessUser, syncEngine]
|
|
250
|
+
controller.setUser(options?.user);
|
|
251
|
+
}, [controller, options?.user]);
|
|
252
|
+
(0, import_react6.useEffect)(() => () => controller.destroy(), [controller]);
|
|
253
|
+
const snapshot = (0, import_react6.useSyncExternalStore)(
|
|
254
|
+
(onStoreChange) => controller.subscribe(onStoreChange),
|
|
255
|
+
() => controller.getSnapshot(),
|
|
256
|
+
() => controller.getSnapshot()
|
|
476
257
|
);
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const current = awareness.getLocalState();
|
|
483
|
-
if (!current?.cursor) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
if (current.cursor.collection !== collectionName || current.cursor.recordId !== recordId || current.cursor.field !== fieldName) {
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
awareness.setLocalState({ user: current.user });
|
|
490
|
-
}, [collectionName, fieldName, recordId, syncEngine]);
|
|
491
|
-
const applyRemoteSnapshot = (0, import_react6.useCallback)(
|
|
492
|
-
(value) => {
|
|
493
|
-
const encoded = encodeRichtextInput(value);
|
|
494
|
-
if (!encoded) {
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
const currentSnapshot = Y.encodeStateAsUpdate(doc);
|
|
498
|
-
if (updatesEqual(currentSnapshot, encoded)) {
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
Y.applyUpdate(doc, encoded, REMOTE_ORIGIN);
|
|
502
|
-
baseUpdateRef.current = encoded;
|
|
503
|
-
pendingDeltasRef.current = [];
|
|
504
|
-
syncHistoryState();
|
|
505
|
-
},
|
|
506
|
-
[doc, syncHistoryState]
|
|
258
|
+
const undo = (0, import_react6.useCallback)(() => controller.undo(), [controller]);
|
|
259
|
+
const redo = (0, import_react6.useCallback)(() => controller.redo(), [controller]);
|
|
260
|
+
const setCursor = (0, import_react6.useCallback)(
|
|
261
|
+
(anchor, head) => controller.setCursor(anchor, head),
|
|
262
|
+
[controller]
|
|
507
263
|
);
|
|
508
|
-
(0, import_react6.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (pendingDeltasRef.current.length >= COMPACT_AFTER_DELTAS) {
|
|
537
|
-
baseUpdateRef.current = snapshot;
|
|
538
|
-
pendingDeltasRef.current = [];
|
|
539
|
-
}
|
|
540
|
-
try {
|
|
541
|
-
await collection.update(recordId, {
|
|
542
|
-
[fieldName]: snapshot
|
|
543
|
-
});
|
|
544
|
-
} catch (cause) {
|
|
545
|
-
if (!disposed) {
|
|
546
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
const schedulePersist = () => {
|
|
551
|
-
if (persistTimerRef.current) {
|
|
552
|
-
clearTimeout(persistTimerRef.current);
|
|
553
|
-
}
|
|
554
|
-
persistTimerRef.current = setTimeout(() => {
|
|
555
|
-
persistTimerRef.current = null;
|
|
556
|
-
void flushPersist();
|
|
557
|
-
}, PERSIST_DEBOUNCE_MS);
|
|
558
|
-
};
|
|
559
|
-
const persist = (_update, origin) => {
|
|
560
|
-
syncHistoryState();
|
|
561
|
-
if (origin === LOAD_ORIGIN || origin === REMOTE_ORIGIN || origin === DOC_CHANNEL_ORIGIN) {
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
pendingDeltasRef.current.push(_update);
|
|
565
|
-
if (docChannelActiveRef.current && syncEngine) {
|
|
566
|
-
syncEngine.getRichtextDocChannel().send(collectionName, recordId, fieldName, _update);
|
|
567
|
-
schedulePersist();
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
void flushPersist();
|
|
571
|
-
};
|
|
572
|
-
doc.on("update", persist);
|
|
573
|
-
void initialize();
|
|
574
|
-
syncHistoryState();
|
|
575
|
-
return () => {
|
|
576
|
-
disposed = true;
|
|
577
|
-
if (persistTimerRef.current) {
|
|
578
|
-
clearTimeout(persistTimerRef.current);
|
|
579
|
-
persistTimerRef.current = null;
|
|
580
|
-
}
|
|
581
|
-
doc.off("update", persist);
|
|
582
|
-
undoManager.destroy();
|
|
583
|
-
baseUpdateRef.current = null;
|
|
584
|
-
pendingDeltasRef.current = [];
|
|
585
|
-
docChannelActiveRef.current = false;
|
|
586
|
-
};
|
|
587
|
-
}, [
|
|
588
|
-
collection,
|
|
589
|
-
collectionName,
|
|
590
|
-
doc,
|
|
591
|
-
fieldName,
|
|
592
|
-
options?.useDocChannel,
|
|
593
|
-
recordId,
|
|
594
|
-
syncEngine,
|
|
595
|
-
syncHistoryState,
|
|
596
|
-
undoManager
|
|
597
|
-
]);
|
|
598
|
-
(0, import_react6.useEffect)(() => {
|
|
599
|
-
if (!ready || !syncEngine || !docChannelActiveRef.current) {
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
const channel = syncEngine.getRichtextDocChannel();
|
|
603
|
-
return channel.subscribe(collectionName, recordId, fieldName, (update) => {
|
|
604
|
-
Y.applyUpdate(doc, update, DOC_CHANNEL_ORIGIN);
|
|
605
|
-
syncHistoryState();
|
|
606
|
-
});
|
|
607
|
-
}, [collectionName, doc, fieldName, ready, recordId, syncEngine, syncHistoryState]);
|
|
608
|
-
(0, import_react6.useEffect)(() => {
|
|
609
|
-
if (!ready) {
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
const unsubscribe = store.collection(collectionName).where({ id: recordId }).subscribe((results) => {
|
|
613
|
-
const record = results[0];
|
|
614
|
-
if (!record) {
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
applyRemoteSnapshot(record[fieldName]);
|
|
618
|
-
});
|
|
619
|
-
return unsubscribe;
|
|
620
|
-
}, [applyRemoteSnapshot, collectionName, fieldName, ready, recordId, store]);
|
|
621
|
-
(0, import_react6.useEffect)(() => {
|
|
622
|
-
if (!syncEngine) return;
|
|
264
|
+
const clearCursor = (0, import_react6.useCallback)(() => controller.clearCursor(), [controller]);
|
|
265
|
+
return buildResult(controller, snapshot, undo, redo, setCursor, clearCursor);
|
|
266
|
+
}
|
|
267
|
+
function buildResult(controller, snapshot, undo, redo, setCursor, clearCursor) {
|
|
268
|
+
return {
|
|
269
|
+
doc: controller.doc,
|
|
270
|
+
text: controller.text,
|
|
271
|
+
undo,
|
|
272
|
+
redo,
|
|
273
|
+
canUndo: snapshot.canUndo,
|
|
274
|
+
canRedo: snapshot.canRedo,
|
|
275
|
+
ready: snapshot.ready,
|
|
276
|
+
error: snapshot.error,
|
|
277
|
+
cursors: [...snapshot.cursors],
|
|
278
|
+
setCursor,
|
|
279
|
+
clearCursor
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/hooks/use-presence.ts
|
|
284
|
+
var import_react7 = require("react");
|
|
285
|
+
function usePresence(user) {
|
|
286
|
+
const { syncEngine } = useKoraContext();
|
|
287
|
+
const name = user?.name ?? null;
|
|
288
|
+
const color = user?.color ?? null;
|
|
289
|
+
const avatar = user?.avatar ?? null;
|
|
290
|
+
(0, import_react7.useEffect)(() => {
|
|
291
|
+
if (!syncEngine || !name || !color) return;
|
|
623
292
|
const awareness = syncEngine.getAwarenessManager();
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const states = awareness.getStates();
|
|
627
|
-
const fieldCursors = [];
|
|
628
|
-
for (const [clientId, state] of states) {
|
|
629
|
-
if (clientId === localClientId) continue;
|
|
630
|
-
if (!state.cursor) continue;
|
|
631
|
-
if (state.cursor.collection !== collectionName || state.cursor.recordId !== recordId || state.cursor.field !== fieldName) {
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
fieldCursors.push({
|
|
635
|
-
clientId,
|
|
636
|
-
userName: state.user.name,
|
|
637
|
-
color: state.user.color,
|
|
638
|
-
anchor: state.cursor.anchor,
|
|
639
|
-
head: state.cursor.head
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
setCursors(fieldCursors);
|
|
643
|
-
};
|
|
644
|
-
const unsubscribe = awareness.on("change", () => {
|
|
645
|
-
updateCursors();
|
|
293
|
+
awareness.setLocalState({
|
|
294
|
+
user: { name, color, avatar: avatar ?? void 0 }
|
|
646
295
|
});
|
|
647
|
-
updateCursors();
|
|
648
296
|
return () => {
|
|
649
|
-
|
|
650
|
-
clearCursor();
|
|
297
|
+
awareness.setLocalState(null);
|
|
651
298
|
};
|
|
652
|
-
}, [
|
|
653
|
-
return { doc, text, undo, redo, canUndo, canRedo, ready, error, cursors, setCursor, clearCursor };
|
|
654
|
-
}
|
|
655
|
-
function encodeRichtextInput(value) {
|
|
656
|
-
if (value === null || value === void 0) {
|
|
657
|
-
return null;
|
|
658
|
-
}
|
|
659
|
-
try {
|
|
660
|
-
return (0, import_store.encodeRichtext)(value);
|
|
661
|
-
} catch {
|
|
662
|
-
if (typeof value === "string") {
|
|
663
|
-
const fallbackDoc = new Y.Doc();
|
|
664
|
-
fallbackDoc.getText(TEXT_KEY).insert(0, value);
|
|
665
|
-
return Y.encodeStateAsUpdate(fallbackDoc);
|
|
666
|
-
}
|
|
667
|
-
throw new Error("Richtext record value must be a string, Uint8Array, ArrayBuffer, or null.");
|
|
668
|
-
}
|
|
299
|
+
}, [syncEngine, name, color, avatar]);
|
|
669
300
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
return
|
|
301
|
+
|
|
302
|
+
// src/hooks/use-collaborators.ts
|
|
303
|
+
var import_sync2 = require("@korajs/sync");
|
|
304
|
+
var import_react8 = require("react");
|
|
305
|
+
var EMPTY_ARRAY2 = [];
|
|
306
|
+
function useCollaborators() {
|
|
307
|
+
const { syncEngine } = useKoraContext();
|
|
308
|
+
const snapshotRef = (0, import_react8.useRef)(EMPTY_ARRAY2);
|
|
309
|
+
const subscribe = (0, import_react8.useCallback)(
|
|
310
|
+
(onStoreChange) => {
|
|
311
|
+
if (!syncEngine) {
|
|
312
|
+
snapshotRef.current = EMPTY_ARRAY2;
|
|
313
|
+
return () => {
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const awareness = syncEngine.getAwarenessManager();
|
|
317
|
+
return (0, import_sync2.subscribeRemoteAwarenessStates)(awareness, (states) => {
|
|
318
|
+
snapshotRef.current = states;
|
|
319
|
+
onStoreChange();
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
[syncEngine]
|
|
323
|
+
);
|
|
324
|
+
const getSnapshot = (0, import_react8.useCallback)(() => snapshotRef.current, []);
|
|
325
|
+
(0, import_react8.useEffect)(() => {
|
|
326
|
+
if (!syncEngine) {
|
|
327
|
+
snapshotRef.current = EMPTY_ARRAY2;
|
|
687
328
|
}
|
|
688
|
-
}
|
|
689
|
-
return
|
|
329
|
+
}, [syncEngine]);
|
|
330
|
+
return (0, import_react8.useSyncExternalStore)(subscribe, getSnapshot);
|
|
690
331
|
}
|
|
691
332
|
// Annotate the CommonJS export names for ESM import in node:
|
|
692
333
|
0 && (module.exports = {
|
|
693
334
|
KoraProvider,
|
|
694
335
|
useApp,
|
|
336
|
+
useCollaborators,
|
|
695
337
|
useCollection,
|
|
696
338
|
useMutation,
|
|
339
|
+
usePresence,
|
|
697
340
|
useQuery,
|
|
698
341
|
useRichText,
|
|
699
342
|
useSyncStatus
|