@korajs/react 0.5.0 → 0.6.1
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 +205 -530
- 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 +205 -522
- 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,454 +165,210 @@ function useQuery(query, options) {
|
|
|
246
165
|
}
|
|
247
166
|
|
|
248
167
|
// src/hooks/use-mutation.ts
|
|
168
|
+
var import_bindings = require("@korajs/core/bindings");
|
|
169
|
+
var import_react4 = require("react");
|
|
170
|
+
|
|
171
|
+
// src/hooks/use-controller.ts
|
|
249
172
|
var import_react3 = require("react");
|
|
250
|
-
function
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
173
|
+
function useController(create, destroy, deps) {
|
|
174
|
+
const controllerRef = (0, import_react3.useRef)(null);
|
|
175
|
+
const createRef = (0, import_react3.useRef)(create);
|
|
176
|
+
createRef.current = create;
|
|
177
|
+
const [, forceRender] = (0, import_react3.useReducer)((count) => count + 1, 0);
|
|
178
|
+
const getController = (0, import_react3.useRef)(() => {
|
|
179
|
+
if (controllerRef.current === null) {
|
|
180
|
+
controllerRef.current = createRef.current();
|
|
181
|
+
}
|
|
182
|
+
return controllerRef.current;
|
|
183
|
+
}).current;
|
|
256
184
|
(0, import_react3.useEffect)(() => {
|
|
257
|
-
|
|
185
|
+
if (controllerRef.current === null) {
|
|
186
|
+
controllerRef.current = createRef.current();
|
|
187
|
+
forceRender();
|
|
188
|
+
}
|
|
258
189
|
return () => {
|
|
259
|
-
|
|
190
|
+
const controller = controllerRef.current;
|
|
191
|
+
controllerRef.current = null;
|
|
192
|
+
if (controller !== null) {
|
|
193
|
+
destroy(controller);
|
|
194
|
+
}
|
|
260
195
|
};
|
|
261
|
-
},
|
|
262
|
-
|
|
196
|
+
}, deps);
|
|
197
|
+
return getController;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/hooks/use-mutation.ts
|
|
201
|
+
function useMutation(mutationFn, options) {
|
|
202
|
+
const fnRef = (0, import_react4.useRef)(mutationFn);
|
|
263
203
|
fnRef.current = mutationFn;
|
|
264
|
-
const optionsRef = (0,
|
|
204
|
+
const optionsRef = (0, import_react4.useRef)(options);
|
|
265
205
|
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]
|
|
206
|
+
const getController = useController(
|
|
207
|
+
() => (0, import_bindings.createMutationController)({
|
|
208
|
+
mutationFn: (...args) => fnRef.current(...args),
|
|
209
|
+
resolveOptions: () => optionsRef.current
|
|
210
|
+
}),
|
|
211
|
+
(controller) => controller.destroy(),
|
|
212
|
+
[]
|
|
213
|
+
);
|
|
214
|
+
const state = (0, import_react4.useSyncExternalStore)(
|
|
215
|
+
(onStoreChange) => getController().subscribe(onStoreChange),
|
|
216
|
+
() => getController().getSnapshot(),
|
|
217
|
+
() => getController().getSnapshot()
|
|
302
218
|
);
|
|
303
|
-
const reset = (0, import_react3.useCallback)(() => {
|
|
304
|
-
if (mountedRef.current) {
|
|
305
|
-
setState({ isLoading: false, error: null });
|
|
306
|
-
}
|
|
307
|
-
}, []);
|
|
308
219
|
return {
|
|
309
|
-
mutate,
|
|
310
|
-
mutateAsync,
|
|
220
|
+
mutate: (...args) => getController().mutate(...args),
|
|
221
|
+
mutateAsync: (...args) => getController().mutateAsync(...args),
|
|
311
222
|
isLoading: state.isLoading,
|
|
312
223
|
error: state.error,
|
|
313
|
-
reset
|
|
224
|
+
reset: () => getController().reset()
|
|
314
225
|
};
|
|
315
226
|
}
|
|
316
227
|
|
|
317
228
|
// src/hooks/use-sync-status.ts
|
|
318
|
-
var
|
|
319
|
-
var
|
|
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
|
-
];
|
|
229
|
+
var import_sync = require("@korajs/sync");
|
|
230
|
+
var import_react5 = require("react");
|
|
339
231
|
function useSyncStatus() {
|
|
340
232
|
const { syncEngine, subscribeSyncStatus, events } = useKoraContext();
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
},
|
|
233
|
+
const getController = useController(
|
|
234
|
+
() => (0, import_sync.createSyncStatusController)({
|
|
235
|
+
syncEngine,
|
|
236
|
+
subscribeSyncStatus,
|
|
237
|
+
events: subscribeSyncStatus ? null : events
|
|
238
|
+
}),
|
|
239
|
+
(controller) => controller.destroy(),
|
|
378
240
|
[syncEngine, subscribeSyncStatus, events]
|
|
379
241
|
);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}, [syncEngine, subscribeSyncStatus]);
|
|
386
|
-
return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
|
|
242
|
+
return (0, import_react5.useSyncExternalStore)(
|
|
243
|
+
(onStoreChange) => getController().subscribe(onStoreChange),
|
|
244
|
+
() => getController().getSnapshot(),
|
|
245
|
+
() => getController().getSnapshot()
|
|
246
|
+
);
|
|
387
247
|
}
|
|
388
248
|
|
|
389
249
|
// src/hooks/use-collection.ts
|
|
390
|
-
var
|
|
250
|
+
var import_react6 = require("react");
|
|
391
251
|
function useCollection(name) {
|
|
392
252
|
const { store } = useKoraContext();
|
|
393
|
-
return (0,
|
|
253
|
+
return (0, import_react6.useMemo)(() => {
|
|
394
254
|
return store.collection(name);
|
|
395
255
|
}, [store, name]);
|
|
396
256
|
}
|
|
397
257
|
|
|
398
258
|
// src/hooks/use-rich-text.ts
|
|
399
|
-
var
|
|
400
|
-
var
|
|
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;
|
|
259
|
+
var import_store3 = require("@korajs/store");
|
|
260
|
+
var import_react7 = require("react");
|
|
408
261
|
function useRichText(collectionName, recordId, fieldName, options) {
|
|
409
262
|
const { store, syncEngine } = useKoraContext();
|
|
410
|
-
const collection = (0,
|
|
411
|
-
|
|
412
|
-
|
|
263
|
+
const collection = (0, import_react7.useMemo)(() => store.collection(collectionName), [store, collectionName]);
|
|
264
|
+
const getController = useController(
|
|
265
|
+
() => (0, import_store3.createRichTextController)({
|
|
266
|
+
collection,
|
|
267
|
+
collectionName,
|
|
268
|
+
recordId,
|
|
269
|
+
fieldName,
|
|
270
|
+
store,
|
|
271
|
+
syncEngine: (0, import_store3.asRichTextSyncEngine)(syncEngine),
|
|
272
|
+
useDocChannel: options?.useDocChannel,
|
|
273
|
+
user: options?.user
|
|
274
|
+
}),
|
|
275
|
+
(controller) => controller.destroy(),
|
|
276
|
+
[collection, collectionName, fieldName, options?.useDocChannel, recordId, store, syncEngine]
|
|
413
277
|
);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const pendingDeltasRef = (0, import_react6.useRef)([]);
|
|
422
|
-
const docChannelActiveRef = (0, import_react6.useRef)(false);
|
|
423
|
-
const persistTimerRef = (0, import_react6.useRef)(null);
|
|
424
|
-
const userRef = (0, import_react6.useRef)(options?.user);
|
|
425
|
-
(0, import_react6.useEffect)(() => {
|
|
426
|
-
userRef.current = options?.user;
|
|
427
|
-
}, [options?.user]);
|
|
428
|
-
const text = (0, import_react6.useMemo)(() => doc.getText(TEXT_KEY), [doc]);
|
|
429
|
-
const undoManager = (0, import_react6.useMemo)(() => new Y.UndoManager(text), [text]);
|
|
430
|
-
const syncHistoryState = (0, import_react6.useCallback)(() => {
|
|
431
|
-
setCanUndo(undoManager.undoStack.length > 0);
|
|
432
|
-
setCanRedo(undoManager.redoStack.length > 0);
|
|
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]
|
|
278
|
+
(0, import_react7.useEffect)(() => {
|
|
279
|
+
getController().setUser(options?.user);
|
|
280
|
+
}, [getController, options?.user]);
|
|
281
|
+
const snapshot = (0, import_react7.useSyncExternalStore)(
|
|
282
|
+
(onStoreChange) => getController().subscribe(onStoreChange),
|
|
283
|
+
() => getController().getSnapshot(),
|
|
284
|
+
() => getController().getSnapshot()
|
|
476
285
|
);
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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]
|
|
286
|
+
const undo = (0, import_react7.useCallback)(() => {
|
|
287
|
+
getController().undo();
|
|
288
|
+
}, [getController]);
|
|
289
|
+
const redo = (0, import_react7.useCallback)(() => {
|
|
290
|
+
getController().redo();
|
|
291
|
+
}, [getController]);
|
|
292
|
+
const setCursor = (0, import_react7.useCallback)(
|
|
293
|
+
(anchor, head) => getController().setCursor(anchor, head),
|
|
294
|
+
[getController]
|
|
507
295
|
);
|
|
508
|
-
(0,
|
|
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;
|
|
296
|
+
const clearCursor = (0, import_react7.useCallback)(() => getController().clearCursor(), [getController]);
|
|
297
|
+
return buildResult(getController(), snapshot, undo, redo, setCursor, clearCursor);
|
|
298
|
+
}
|
|
299
|
+
function buildResult(controller, snapshot, undo, redo, setCursor, clearCursor) {
|
|
300
|
+
return {
|
|
301
|
+
doc: controller.doc,
|
|
302
|
+
text: controller.text,
|
|
303
|
+
undo,
|
|
304
|
+
redo,
|
|
305
|
+
canUndo: snapshot.canUndo,
|
|
306
|
+
canRedo: snapshot.canRedo,
|
|
307
|
+
ready: snapshot.ready,
|
|
308
|
+
error: snapshot.error,
|
|
309
|
+
cursors: [...snapshot.cursors],
|
|
310
|
+
setCursor,
|
|
311
|
+
clearCursor
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/hooks/use-presence.ts
|
|
316
|
+
var import_react8 = require("react");
|
|
317
|
+
function usePresence(user) {
|
|
318
|
+
const { syncEngine } = useKoraContext();
|
|
319
|
+
const name = user?.name ?? null;
|
|
320
|
+
const color = user?.color ?? null;
|
|
321
|
+
const avatar = user?.avatar ?? null;
|
|
322
|
+
(0, import_react8.useEffect)(() => {
|
|
323
|
+
if (!syncEngine || !name || !color) return;
|
|
623
324
|
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();
|
|
325
|
+
awareness.setLocalState({
|
|
326
|
+
user: { name, color, avatar: avatar ?? void 0 }
|
|
646
327
|
});
|
|
647
|
-
updateCursors();
|
|
648
328
|
return () => {
|
|
649
|
-
|
|
650
|
-
clearCursor();
|
|
329
|
+
awareness.setLocalState(null);
|
|
651
330
|
};
|
|
652
|
-
}, [
|
|
653
|
-
return { doc, text, undo, redo, canUndo, canRedo, ready, error, cursors, setCursor, clearCursor };
|
|
331
|
+
}, [syncEngine, name, color, avatar]);
|
|
654
332
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
return false;
|
|
683
|
-
}
|
|
684
|
-
for (let index = 0; index < left.length; index++) {
|
|
685
|
-
if (left[index] !== right[index]) {
|
|
686
|
-
return false;
|
|
333
|
+
|
|
334
|
+
// src/hooks/use-collaborators.ts
|
|
335
|
+
var import_sync2 = require("@korajs/sync");
|
|
336
|
+
var import_react9 = require("react");
|
|
337
|
+
var EMPTY_ARRAY2 = [];
|
|
338
|
+
function useCollaborators() {
|
|
339
|
+
const { syncEngine } = useKoraContext();
|
|
340
|
+
const snapshotRef = (0, import_react9.useRef)(EMPTY_ARRAY2);
|
|
341
|
+
const subscribe = (0, import_react9.useCallback)(
|
|
342
|
+
(onStoreChange) => {
|
|
343
|
+
if (!syncEngine) {
|
|
344
|
+
snapshotRef.current = EMPTY_ARRAY2;
|
|
345
|
+
return () => {
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const awareness = syncEngine.getAwarenessManager();
|
|
349
|
+
return (0, import_sync2.subscribeRemoteAwarenessStates)(awareness, (states) => {
|
|
350
|
+
snapshotRef.current = states;
|
|
351
|
+
onStoreChange();
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
[syncEngine]
|
|
355
|
+
);
|
|
356
|
+
const getSnapshot = (0, import_react9.useCallback)(() => snapshotRef.current, []);
|
|
357
|
+
(0, import_react9.useEffect)(() => {
|
|
358
|
+
if (!syncEngine) {
|
|
359
|
+
snapshotRef.current = EMPTY_ARRAY2;
|
|
687
360
|
}
|
|
688
|
-
}
|
|
689
|
-
return
|
|
361
|
+
}, [syncEngine]);
|
|
362
|
+
return (0, import_react9.useSyncExternalStore)(subscribe, getSnapshot);
|
|
690
363
|
}
|
|
691
364
|
// Annotate the CommonJS export names for ESM import in node:
|
|
692
365
|
0 && (module.exports = {
|
|
693
366
|
KoraProvider,
|
|
694
367
|
useApp,
|
|
368
|
+
useCollaborators,
|
|
695
369
|
useCollection,
|
|
696
370
|
useMutation,
|
|
371
|
+
usePresence,
|
|
697
372
|
useQuery,
|
|
698
373
|
useRichText,
|
|
699
374
|
useSyncStatus
|