@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.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,17 +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
|
-
};
|
|
96
|
-
return (0, import_react.createElement)(KoraContext.Provider, { value }, children);
|
|
108
|
+
return (0, import_react.createElement)(KoraContext.Provider, { value: contextValue }, children);
|
|
97
109
|
}
|
|
98
110
|
function useKoraContext() {
|
|
99
111
|
const context = (0, import_react.useContext)(KoraContext);
|
|
@@ -117,111 +129,35 @@ function useApp() {
|
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
// src/hooks/use-query.ts
|
|
132
|
+
var import_store2 = require("@korajs/store");
|
|
120
133
|
var import_react2 = require("react");
|
|
121
|
-
|
|
122
|
-
// src/query-store/query-store.ts
|
|
123
134
|
var EMPTY_ARRAY = Object.freeze([]);
|
|
124
|
-
var QueryStore = class {
|
|
125
|
-
snapshot = EMPTY_ARRAY;
|
|
126
|
-
listeners = /* @__PURE__ */ new Set();
|
|
127
|
-
unsubscribeQuery = null;
|
|
128
|
-
active = false;
|
|
129
|
-
queryBuilder;
|
|
130
|
-
constructor(queryBuilder) {
|
|
131
|
-
this.queryBuilder = queryBuilder;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Subscribe to snapshot changes. Compatible with useSyncExternalStore.
|
|
135
|
-
*
|
|
136
|
-
* Lazily starts the underlying query subscription when the first listener
|
|
137
|
-
* attaches, and stops it when the last listener detaches. This allows
|
|
138
|
-
* React StrictMode to unmount/remount without permanently killing the subscription.
|
|
139
|
-
*
|
|
140
|
-
* @returns Unsubscribe function
|
|
141
|
-
*/
|
|
142
|
-
subscribe = (onStoreChange) => {
|
|
143
|
-
this.listeners.add(onStoreChange);
|
|
144
|
-
if (!this.active) {
|
|
145
|
-
this.startSubscription();
|
|
146
|
-
}
|
|
147
|
-
return () => {
|
|
148
|
-
this.listeners.delete(onStoreChange);
|
|
149
|
-
if (this.listeners.size === 0) {
|
|
150
|
-
this.stopSubscription();
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
};
|
|
154
|
-
/**
|
|
155
|
-
* Get the current snapshot synchronously. Compatible with useSyncExternalStore.
|
|
156
|
-
* Returns EMPTY_ARRAY before the first async fetch completes.
|
|
157
|
-
*/
|
|
158
|
-
getSnapshot = () => {
|
|
159
|
-
return this.snapshot;
|
|
160
|
-
};
|
|
161
|
-
/**
|
|
162
|
-
* Clean up the underlying subscription and release resources.
|
|
163
|
-
* Called by useMemo when the query descriptor changes.
|
|
164
|
-
*/
|
|
165
|
-
destroy() {
|
|
166
|
-
this.stopSubscription();
|
|
167
|
-
this.listeners.clear();
|
|
168
|
-
this.snapshot = EMPTY_ARRAY;
|
|
169
|
-
}
|
|
170
|
-
startSubscription() {
|
|
171
|
-
this.active = true;
|
|
172
|
-
this.unsubscribeQuery = this.queryBuilder.subscribe((results) => {
|
|
173
|
-
if (!this.active) return;
|
|
174
|
-
const newSnapshot = Object.freeze([...results]);
|
|
175
|
-
this.snapshot = newSnapshot;
|
|
176
|
-
this.notifyListeners();
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
stopSubscription() {
|
|
180
|
-
this.active = false;
|
|
181
|
-
if (this.unsubscribeQuery) {
|
|
182
|
-
this.unsubscribeQuery();
|
|
183
|
-
this.unsubscribeQuery = null;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
notifyListeners() {
|
|
187
|
-
for (const listener of this.listeners) {
|
|
188
|
-
listener();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// src/hooks/use-query.ts
|
|
194
|
-
var EMPTY_ARRAY2 = Object.freeze([]);
|
|
195
135
|
var noopSubscribe = (_onStoreChange) => {
|
|
196
136
|
return () => {
|
|
197
137
|
};
|
|
198
138
|
};
|
|
199
139
|
function useQuery(query, options) {
|
|
140
|
+
const { queryStoreCache } = useKoraContext();
|
|
200
141
|
const enabled = options?.enabled !== false;
|
|
201
142
|
const descriptorKey = JSON.stringify(query.getDescriptor());
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
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)(() => {
|
|
205
147
|
if (!enabled) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
queryStoreRef.current = null;
|
|
209
|
-
}
|
|
210
|
-
prevKeyRef.current = null;
|
|
211
|
-
return null;
|
|
148
|
+
setQueryStore(null);
|
|
149
|
+
return;
|
|
212
150
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}, [descriptorKey, enabled, query]);
|
|
224
|
-
const disabledGetSnapshot = () => EMPTY_ARRAY2;
|
|
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;
|
|
225
161
|
return (0, import_react2.useSyncExternalStore)(
|
|
226
162
|
queryStore ? queryStore.subscribe : noopSubscribe,
|
|
227
163
|
queryStore ? queryStore.getSnapshot : disabledGetSnapshot
|
|
@@ -229,111 +165,54 @@ function useQuery(query, options) {
|
|
|
229
165
|
}
|
|
230
166
|
|
|
231
167
|
// src/hooks/use-mutation.ts
|
|
168
|
+
var import_bindings = require("@korajs/core/bindings");
|
|
232
169
|
var import_react3 = require("react");
|
|
233
|
-
function useMutation(mutationFn) {
|
|
234
|
-
const [state, setState] = (0, import_react3.useState)({
|
|
235
|
-
isLoading: false,
|
|
236
|
-
error: null
|
|
237
|
-
});
|
|
238
|
-
const mountedRef = (0, import_react3.useRef)(true);
|
|
239
|
-
(0, import_react3.useEffect)(() => {
|
|
240
|
-
mountedRef.current = true;
|
|
241
|
-
return () => {
|
|
242
|
-
mountedRef.current = false;
|
|
243
|
-
};
|
|
244
|
-
}, []);
|
|
170
|
+
function useMutation(mutationFn, options) {
|
|
245
171
|
const fnRef = (0, import_react3.useRef)(mutationFn);
|
|
246
172
|
fnRef.current = mutationFn;
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
}, []);
|
|
265
|
-
const mutate = (0, import_react3.useCallback)(
|
|
266
|
-
(...args) => {
|
|
267
|
-
mutateAsync(...args).catch(() => {
|
|
268
|
-
});
|
|
269
|
-
},
|
|
270
|
-
[mutateAsync]
|
|
173
|
+
const optionsRef = (0, import_react3.useRef)(options);
|
|
174
|
+
optionsRef.current = options;
|
|
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()
|
|
271
187
|
);
|
|
272
|
-
const reset = (0, import_react3.useCallback)(() => {
|
|
273
|
-
if (mountedRef.current) {
|
|
274
|
-
setState({ isLoading: false, error: null });
|
|
275
|
-
}
|
|
276
|
-
}, []);
|
|
277
188
|
return {
|
|
278
|
-
mutate,
|
|
279
|
-
mutateAsync,
|
|
189
|
+
mutate: (...args) => controller.mutate(...args),
|
|
190
|
+
mutateAsync: (...args) => controller.mutateAsync(...args),
|
|
280
191
|
isLoading: state.isLoading,
|
|
281
192
|
error: state.error,
|
|
282
|
-
reset
|
|
193
|
+
reset: () => controller.reset()
|
|
283
194
|
};
|
|
284
195
|
}
|
|
285
196
|
|
|
286
197
|
// src/hooks/use-sync-status.ts
|
|
198
|
+
var import_sync = require("@korajs/sync");
|
|
287
199
|
var import_react4 = require("react");
|
|
288
|
-
var POLL_INTERVAL_MS = 500;
|
|
289
|
-
var OFFLINE_STATUS = Object.freeze({
|
|
290
|
-
status: "offline",
|
|
291
|
-
pendingOperations: 0,
|
|
292
|
-
lastSyncedAt: null,
|
|
293
|
-
lastSuccessfulPush: null,
|
|
294
|
-
lastSuccessfulPull: null,
|
|
295
|
-
conflicts: 0
|
|
296
|
-
});
|
|
297
200
|
function useSyncStatus() {
|
|
298
|
-
const { syncEngine } = useKoraContext();
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
}, POLL_INTERVAL_MS);
|
|
314
|
-
const initialStatus = syncEngine.getStatus();
|
|
315
|
-
const initialSerialized = JSON.stringify(initialStatus);
|
|
316
|
-
if (initialSerialized !== serializedRef.current) {
|
|
317
|
-
snapshotRef.current = initialStatus;
|
|
318
|
-
serializedRef.current = initialSerialized;
|
|
319
|
-
onStoreChange();
|
|
320
|
-
}
|
|
321
|
-
return () => {
|
|
322
|
-
clearInterval(intervalId);
|
|
323
|
-
};
|
|
324
|
-
},
|
|
325
|
-
[syncEngine]
|
|
201
|
+
const { syncEngine, subscribeSyncStatus, events } = useKoraContext();
|
|
202
|
+
const controller = (0, import_react4.useMemo)(
|
|
203
|
+
() => (0, import_sync.createSyncStatusController)({
|
|
204
|
+
syncEngine,
|
|
205
|
+
subscribeSyncStatus,
|
|
206
|
+
events: subscribeSyncStatus ? null : events
|
|
207
|
+
}),
|
|
208
|
+
[syncEngine, subscribeSyncStatus, events]
|
|
209
|
+
);
|
|
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()
|
|
326
215
|
);
|
|
327
|
-
const getSnapshot = (0, import_react4.useCallback)(() => {
|
|
328
|
-
return snapshotRef.current;
|
|
329
|
-
}, []);
|
|
330
|
-
(0, import_react4.useEffect)(() => {
|
|
331
|
-
if (!syncEngine) {
|
|
332
|
-
snapshotRef.current = OFFLINE_STATUS;
|
|
333
|
-
serializedRef.current = JSON.stringify(OFFLINE_STATUS);
|
|
334
|
-
}
|
|
335
|
-
}, [syncEngine]);
|
|
336
|
-
return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
|
|
337
216
|
}
|
|
338
217
|
|
|
339
218
|
// src/hooks/use-collection.ts
|
|
@@ -346,165 +225,118 @@ function useCollection(name) {
|
|
|
346
225
|
}
|
|
347
226
|
|
|
348
227
|
// src/hooks/use-rich-text.ts
|
|
228
|
+
var import_store3 = require("@korajs/store");
|
|
349
229
|
var import_react6 = require("react");
|
|
350
|
-
|
|
351
|
-
var LOAD_ORIGIN = "kora-load";
|
|
352
|
-
var TEXT_KEY = "content";
|
|
353
|
-
var COMPACT_AFTER_DELTAS = 20;
|
|
354
|
-
function useRichText(collectionName, recordId, fieldName) {
|
|
230
|
+
function useRichText(collectionName, recordId, fieldName, options) {
|
|
355
231
|
const { store, syncEngine } = useKoraContext();
|
|
356
232
|
const collection = (0, import_react6.useMemo)(
|
|
357
233
|
() => store.collection(collectionName),
|
|
358
234
|
[store, collectionName]
|
|
359
235
|
);
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}, [undoManager]);
|
|
374
|
-
const undo = (0, import_react6.useCallback)(() => {
|
|
375
|
-
undoManager.undo();
|
|
376
|
-
syncHistoryState();
|
|
377
|
-
}, [syncHistoryState, undoManager]);
|
|
378
|
-
const redo = (0, import_react6.useCallback)(() => {
|
|
379
|
-
undoManager.redo();
|
|
380
|
-
syncHistoryState();
|
|
381
|
-
}, [syncHistoryState, undoManager]);
|
|
382
|
-
(0, import_react6.useEffect)(() => {
|
|
383
|
-
let disposed = false;
|
|
384
|
-
const initialize = async () => {
|
|
385
|
-
setReady(false);
|
|
386
|
-
setError(null);
|
|
387
|
-
try {
|
|
388
|
-
const record = await collection.findById(recordId);
|
|
389
|
-
if (disposed) return;
|
|
390
|
-
doc.transact(() => {
|
|
391
|
-
const target = doc.getText(TEXT_KEY);
|
|
392
|
-
target.delete(0, target.length);
|
|
393
|
-
}, LOAD_ORIGIN);
|
|
394
|
-
const encoded = encodeRichtextInput(record?.[fieldName]);
|
|
395
|
-
baseUpdateRef.current = encoded;
|
|
396
|
-
pendingDeltasRef.current = [];
|
|
397
|
-
if (encoded) {
|
|
398
|
-
Y.applyUpdate(doc, encoded, LOAD_ORIGIN);
|
|
399
|
-
}
|
|
400
|
-
setReady(true);
|
|
401
|
-
} catch (cause) {
|
|
402
|
-
if (disposed) return;
|
|
403
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
const persist = async (_update, origin) => {
|
|
407
|
-
syncHistoryState();
|
|
408
|
-
if (origin === LOAD_ORIGIN) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
pendingDeltasRef.current.push(_update);
|
|
412
|
-
const snapshot = composeRichtextSnapshot(baseUpdateRef.current, pendingDeltasRef.current);
|
|
413
|
-
if (pendingDeltasRef.current.length >= COMPACT_AFTER_DELTAS) {
|
|
414
|
-
baseUpdateRef.current = snapshot;
|
|
415
|
-
pendingDeltasRef.current = [];
|
|
416
|
-
}
|
|
417
|
-
try {
|
|
418
|
-
await collection.update(recordId, {
|
|
419
|
-
[fieldName]: snapshot
|
|
420
|
-
});
|
|
421
|
-
} catch (cause) {
|
|
422
|
-
if (!disposed) {
|
|
423
|
-
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
doc.on("update", persist);
|
|
428
|
-
void initialize();
|
|
429
|
-
syncHistoryState();
|
|
430
|
-
return () => {
|
|
431
|
-
disposed = true;
|
|
432
|
-
doc.off("update", persist);
|
|
433
|
-
undoManager.destroy();
|
|
434
|
-
baseUpdateRef.current = null;
|
|
435
|
-
pendingDeltasRef.current = [];
|
|
436
|
-
};
|
|
437
|
-
}, [collection, doc, fieldName, recordId, syncHistoryState, undoManager]);
|
|
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
|
+
);
|
|
438
249
|
(0, import_react6.useEffect)(() => {
|
|
439
|
-
|
|
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()
|
|
257
|
+
);
|
|
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]
|
|
263
|
+
);
|
|
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;
|
|
440
292
|
const awareness = syncEngine.getAwarenessManager();
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const states = awareness.getStates();
|
|
444
|
-
const fieldCursors = [];
|
|
445
|
-
for (const [clientId, state] of states) {
|
|
446
|
-
if (clientId === localClientId) continue;
|
|
447
|
-
if (!state.cursor) continue;
|
|
448
|
-
if (state.cursor.collection !== collectionName || state.cursor.recordId !== recordId || state.cursor.field !== fieldName) {
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
fieldCursors.push({
|
|
452
|
-
clientId,
|
|
453
|
-
userName: state.user.name,
|
|
454
|
-
color: state.user.color,
|
|
455
|
-
anchor: state.cursor.anchor,
|
|
456
|
-
head: state.cursor.head
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
setCursors(fieldCursors);
|
|
460
|
-
};
|
|
461
|
-
const unsubscribe = awareness.on("change", () => {
|
|
462
|
-
updateCursors();
|
|
293
|
+
awareness.setLocalState({
|
|
294
|
+
user: { name, color, avatar: avatar ?? void 0 }
|
|
463
295
|
});
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
296
|
+
return () => {
|
|
297
|
+
awareness.setLocalState(null);
|
|
298
|
+
};
|
|
299
|
+
}, [syncEngine, name, color, avatar]);
|
|
468
300
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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;
|
|
488
328
|
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
function composeRichtextSnapshot(base, deltas) {
|
|
493
|
-
const doc = new Y.Doc();
|
|
494
|
-
if (base) {
|
|
495
|
-
Y.applyUpdate(doc, base);
|
|
496
|
-
}
|
|
497
|
-
for (const delta of deltas) {
|
|
498
|
-
Y.applyUpdate(doc, delta);
|
|
499
|
-
}
|
|
500
|
-
return Y.encodeStateAsUpdate(doc);
|
|
329
|
+
}, [syncEngine]);
|
|
330
|
+
return (0, import_react8.useSyncExternalStore)(subscribe, getSnapshot);
|
|
501
331
|
}
|
|
502
332
|
// Annotate the CommonJS export names for ESM import in node:
|
|
503
333
|
0 && (module.exports = {
|
|
504
334
|
KoraProvider,
|
|
505
335
|
useApp,
|
|
336
|
+
useCollaborators,
|
|
506
337
|
useCollection,
|
|
507
338
|
useMutation,
|
|
339
|
+
usePresence,
|
|
508
340
|
useQuery,
|
|
509
341
|
useRichText,
|
|
510
342
|
useSyncStatus
|