@korajs/vue 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/README.md +84 -10
- package/dist/index.cjs +419 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +158 -20
- package/dist/index.d.ts +158 -20
- package/dist/index.js +415 -7
- package/dist/index.js.map +1 -1
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -1,20 +1,94 @@
|
|
|
1
1
|
# @korajs/vue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vue 3 composables for [Kora.js](https://github.com/ehoneahobed/kora) offline-first applications.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add korajs @korajs/vue
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
Create the Kora app once at module scope, connect sync after `ready`, then wrap your tree with providers (matches the CLI `vue-tailwind-sync` template):
|
|
6
14
|
|
|
7
15
|
```typescript
|
|
16
|
+
import { createKoraAuthSync } from '@korajs/auth'
|
|
17
|
+
import { AuthProvider } from '@korajs/auth/vue'
|
|
18
|
+
import { KoraProvider } from '@korajs/vue'
|
|
8
19
|
import { createApp as createKoraApp } from 'korajs'
|
|
9
|
-
import { createApp
|
|
10
|
-
import
|
|
20
|
+
import { createApp, h } from 'vue'
|
|
21
|
+
import App from './App.vue'
|
|
22
|
+
import { authClient } from './auth'
|
|
11
23
|
import schema from './schema'
|
|
24
|
+
import koraWorkerUrl from './kora-worker.ts?worker&url'
|
|
25
|
+
|
|
26
|
+
const kora = createKoraApp({
|
|
27
|
+
schema,
|
|
28
|
+
sync: { url: 'ws://localhost:3000/kora-sync', authClient: createKoraAuthSync({ authClient, schema }) },
|
|
29
|
+
store: { workerUrl: koraWorkerUrl },
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
kora.ready.then(() => kora.sync?.connect())
|
|
33
|
+
|
|
34
|
+
createApp({
|
|
35
|
+
render: () =>
|
|
36
|
+
h(AuthProvider, { client: authClient }, () =>
|
|
37
|
+
h(KoraProvider, { app: kora }, () => h(App)),
|
|
38
|
+
),
|
|
39
|
+
}).mount('#app')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or use SFC wrappers:
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<AuthProvider :client="authClient">
|
|
46
|
+
<KoraProvider :app="kora">
|
|
47
|
+
<TodoList />
|
|
48
|
+
</KoraProvider>
|
|
49
|
+
</AuthProvider>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Composables
|
|
53
|
+
|
|
54
|
+
| Composable | Purpose |
|
|
55
|
+
|------------|---------|
|
|
56
|
+
| `useQuery(query)` | Reactive local query results |
|
|
57
|
+
| `useMutation(fn)` | Optimistic mutations with rollback |
|
|
58
|
+
| `useSyncStatus()` | Connection / sync state |
|
|
59
|
+
| `useApp()` | Typed `createApp()` instance |
|
|
60
|
+
| `useCollection(name)` | Collection accessor |
|
|
61
|
+
| `useRichText(name, id, field)` | Yjs richtext editor binding |
|
|
62
|
+
| `usePresence(user)` | Publish collaborative presence |
|
|
63
|
+
| `useCollaborators()` | Remote collaborator awareness states |
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<script setup lang="ts">
|
|
67
|
+
import { useApp, useQuery, useMutation } from '@korajs/vue'
|
|
68
|
+
|
|
69
|
+
const app = useApp()
|
|
70
|
+
const todos = useQuery(app.todos.where({ completed: false }))
|
|
71
|
+
const { mutate: addTodo } = useMutation(app.todos.insert)
|
|
72
|
+
</script>
|
|
12
73
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
74
|
+
<template>
|
|
75
|
+
<ul>
|
|
76
|
+
<li v-for="todo in todos" :key="todo.id">{{ todo.title }}</li>
|
|
77
|
+
</ul>
|
|
78
|
+
<button @click="addTodo({ title: 'New' })">Add</button>
|
|
79
|
+
</template>
|
|
18
80
|
```
|
|
19
81
|
|
|
20
|
-
|
|
82
|
+
## Organization auth
|
|
83
|
+
|
|
84
|
+
When using `@korajs/auth` organizations, wrap with `OrgProvider` and use org composables from `@korajs/auth/vue`:
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<OrgProvider :client="orgClient">
|
|
88
|
+
<AdminPanel />
|
|
89
|
+
</OrgProvider>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Legacy helpers
|
|
93
|
+
|
|
94
|
+
`installKora()` and `useKoraApp()` remain for app-context only. Prefer `KoraProvider` + composables for reactive data.
|
package/dist/index.cjs
CHANGED
|
@@ -20,34 +20,445 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
KoraProvider: () => KoraProvider,
|
|
23
24
|
installKora: () => installKora,
|
|
24
25
|
koraAppInjectionKey: () => koraAppInjectionKey,
|
|
25
|
-
|
|
26
|
+
koraContextKey: () => koraContextKey,
|
|
27
|
+
useApp: () => useApp,
|
|
28
|
+
useCollaborators: () => useCollaborators,
|
|
29
|
+
useCollection: () => useCollection,
|
|
30
|
+
useKoraApp: () => useKoraApp,
|
|
31
|
+
useKoraContext: () => useKoraContext,
|
|
32
|
+
useMutation: () => useMutation,
|
|
33
|
+
usePresence: () => usePresence,
|
|
34
|
+
useQuery: () => useQuery,
|
|
35
|
+
useRichText: () => useRichText,
|
|
36
|
+
useSyncStatus: () => useSyncStatus
|
|
26
37
|
});
|
|
27
38
|
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
var import_core2 = require("@korajs/core");
|
|
40
|
+
var import_vue8 = require("vue");
|
|
41
|
+
|
|
42
|
+
// src/context.ts
|
|
28
43
|
var import_core = require("@korajs/core");
|
|
29
44
|
var import_vue = require("vue");
|
|
45
|
+
var koraContextKey = /* @__PURE__ */ Symbol("korajs-context");
|
|
30
46
|
var koraAppInjectionKey = /* @__PURE__ */ Symbol("korajs-app");
|
|
47
|
+
function useKoraContext() {
|
|
48
|
+
const contextRef = (0, import_vue.inject)(koraContextKey);
|
|
49
|
+
if (!contextRef?.value) {
|
|
50
|
+
throw new import_core.KoraError(
|
|
51
|
+
"useKoraContext() requires <KoraProvider>. Wrap your app root with KoraProvider.",
|
|
52
|
+
"KORA_NOT_PROVIDED",
|
|
53
|
+
{ fix: '<KoraProvider :app="app"><App /></KoraProvider>' }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return contextRef.value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/components/kora-provider.ts
|
|
60
|
+
var import_store2 = require("@korajs/store");
|
|
61
|
+
var import_vue2 = require("vue");
|
|
62
|
+
|
|
63
|
+
// src/resolve-query-store-cache.ts
|
|
64
|
+
var import_store = require("@korajs/store");
|
|
65
|
+
function resolveQueryStoreCache(app, fallback) {
|
|
66
|
+
if (app && typeof app.getQueryStoreCache === "function") {
|
|
67
|
+
return app.getQueryStoreCache();
|
|
68
|
+
}
|
|
69
|
+
return fallback;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/components/kora-provider.ts
|
|
73
|
+
var KoraProvider = (0, import_vue2.defineComponent)({
|
|
74
|
+
name: "KoraProvider",
|
|
75
|
+
props: {
|
|
76
|
+
app: {
|
|
77
|
+
type: Object,
|
|
78
|
+
default: void 0
|
|
79
|
+
},
|
|
80
|
+
store: {
|
|
81
|
+
type: Object,
|
|
82
|
+
default: void 0
|
|
83
|
+
},
|
|
84
|
+
syncEngine: {
|
|
85
|
+
type: Object,
|
|
86
|
+
default: void 0
|
|
87
|
+
},
|
|
88
|
+
fallback: {
|
|
89
|
+
type: [Object, String],
|
|
90
|
+
default: null
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
setup(props, { slots }) {
|
|
94
|
+
const resolvedStore = (0, import_vue2.ref)(null);
|
|
95
|
+
const resolvedSync = (0, import_vue2.ref)(null);
|
|
96
|
+
const ready = (0, import_vue2.ref)(!props.app && Boolean(props.store));
|
|
97
|
+
const initError = (0, import_vue2.ref)(null);
|
|
98
|
+
const fallbackQueryStoreCache = new import_store2.QueryStoreCache();
|
|
99
|
+
const contextRef = (0, import_vue2.shallowRef)(null);
|
|
100
|
+
(0, import_vue2.provide)(koraContextKey, contextRef);
|
|
101
|
+
if (props.app) {
|
|
102
|
+
(0, import_vue2.provide)(koraAppInjectionKey, props.app);
|
|
103
|
+
}
|
|
104
|
+
(0, import_vue2.watch)(
|
|
105
|
+
() => props.app,
|
|
106
|
+
(app, _previous, onCleanup) => {
|
|
107
|
+
if (!app) {
|
|
108
|
+
if (props.store) {
|
|
109
|
+
resolvedStore.value = props.store;
|
|
110
|
+
resolvedSync.value = props.syncEngine ?? null;
|
|
111
|
+
ready.value = true;
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
ready.value = false;
|
|
116
|
+
initError.value = null;
|
|
117
|
+
let cancelled = false;
|
|
118
|
+
app.ready.then(() => {
|
|
119
|
+
if (cancelled) return;
|
|
120
|
+
resolvedStore.value = app.getStore();
|
|
121
|
+
resolvedSync.value = app.getSyncEngine();
|
|
122
|
+
ready.value = true;
|
|
123
|
+
}).catch((error) => {
|
|
124
|
+
if (cancelled) return;
|
|
125
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
126
|
+
console.error("[Kora] Initialization failed:", err);
|
|
127
|
+
initError.value = err;
|
|
128
|
+
});
|
|
129
|
+
onCleanup(() => {
|
|
130
|
+
cancelled = true;
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
{ immediate: true }
|
|
134
|
+
);
|
|
135
|
+
(0, import_vue2.watch)(
|
|
136
|
+
() => props.store,
|
|
137
|
+
(store) => {
|
|
138
|
+
if (store && !props.app) {
|
|
139
|
+
resolvedStore.value = store;
|
|
140
|
+
resolvedSync.value = props.syncEngine ?? null;
|
|
141
|
+
ready.value = true;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{ immediate: true }
|
|
145
|
+
);
|
|
146
|
+
(0, import_vue2.watch)(
|
|
147
|
+
() => [resolvedStore.value, resolvedSync.value, props.app],
|
|
148
|
+
([store, syncEngine, app]) => {
|
|
149
|
+
if (!store) {
|
|
150
|
+
contextRef.value = null;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
contextRef.value = {
|
|
154
|
+
store,
|
|
155
|
+
syncEngine: syncEngine ?? null,
|
|
156
|
+
app: app ?? null,
|
|
157
|
+
events: app?.events ?? null,
|
|
158
|
+
subscribeSyncStatus: app?.sync?.subscribeStatus ?? null,
|
|
159
|
+
queryStoreCache: resolveQueryStoreCache(app, fallbackQueryStoreCache)
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
{ immediate: true }
|
|
163
|
+
);
|
|
164
|
+
(0, import_vue2.onScopeDispose)(() => {
|
|
165
|
+
if (!props.app) {
|
|
166
|
+
fallbackQueryStoreCache.clear();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return () => {
|
|
170
|
+
if (initError.value) {
|
|
171
|
+
return (0, import_vue2.h)(
|
|
172
|
+
"div",
|
|
173
|
+
{ style: { color: "red", padding: "1rem", fontFamily: "monospace" } },
|
|
174
|
+
[(0, import_vue2.h)("strong", null, "Kora initialization error: "), initError.value.message]
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (!ready.value) {
|
|
178
|
+
return props.fallback ?? null;
|
|
179
|
+
}
|
|
180
|
+
if (!resolvedStore.value) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
'KoraProvider requires either an "app" or "store" prop. Pass createApp() or a Store instance.'
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return slots.default?.();
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// src/composables/use-query.ts
|
|
191
|
+
var import_store3 = require("@korajs/store");
|
|
192
|
+
var import_vue3 = require("vue");
|
|
193
|
+
var EMPTY_ARRAY = Object.freeze([]);
|
|
194
|
+
function useQuery(query, options) {
|
|
195
|
+
const { queryStoreCache } = useKoraContext();
|
|
196
|
+
const enabled = options?.enabled !== false;
|
|
197
|
+
const snapshot = (0, import_vue3.shallowRef)(EMPTY_ARRAY);
|
|
198
|
+
(0, import_vue3.watch)(
|
|
199
|
+
() => enabled ? JSON.stringify(query.getDescriptor()) : null,
|
|
200
|
+
(key, _previous, onCleanup) => {
|
|
201
|
+
if (!key) {
|
|
202
|
+
snapshot.value = EMPTY_ARRAY;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
(0, import_store3.assertQueryReady)(query);
|
|
206
|
+
const queryStore = queryStoreCache.getOrCreate(query);
|
|
207
|
+
const unsubscribe = queryStore.subscribe(() => {
|
|
208
|
+
snapshot.value = queryStore.getSnapshot();
|
|
209
|
+
});
|
|
210
|
+
snapshot.value = queryStore.getSnapshot();
|
|
211
|
+
onCleanup(() => {
|
|
212
|
+
unsubscribe();
|
|
213
|
+
queryStoreCache.release(query);
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
{ immediate: true }
|
|
217
|
+
);
|
|
218
|
+
return (0, import_vue3.readonly)(snapshot);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/composables/use-mutation.ts
|
|
222
|
+
var import_bindings = require("@korajs/core/bindings");
|
|
223
|
+
var import_vue4 = require("vue");
|
|
224
|
+
function useMutation(mutationFn, options) {
|
|
225
|
+
const fnRef = { current: mutationFn };
|
|
226
|
+
fnRef.current = mutationFn;
|
|
227
|
+
const optionsRef = { current: options };
|
|
228
|
+
optionsRef.current = options;
|
|
229
|
+
const isLoading = (0, import_vue4.ref)(false);
|
|
230
|
+
const error = (0, import_vue4.ref)(null);
|
|
231
|
+
let mounted = true;
|
|
232
|
+
(0, import_vue4.onScopeDispose)(() => {
|
|
233
|
+
mounted = false;
|
|
234
|
+
});
|
|
235
|
+
const controller = (0, import_bindings.createMutationController)({
|
|
236
|
+
mutationFn: (...args) => fnRef.current(...args),
|
|
237
|
+
resolveOptions: () => optionsRef.current,
|
|
238
|
+
onStateChange: (state) => {
|
|
239
|
+
if (!mounted) return;
|
|
240
|
+
isLoading.value = state.isLoading;
|
|
241
|
+
error.value = state.error;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
isLoading.value = controller.getSnapshot().isLoading;
|
|
245
|
+
error.value = controller.getSnapshot().error;
|
|
246
|
+
(0, import_vue4.onScopeDispose)(() => {
|
|
247
|
+
controller.destroy();
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
mutate: (...args) => controller.mutate(...args),
|
|
251
|
+
mutateAsync: (...args) => controller.mutateAsync(...args),
|
|
252
|
+
isLoading: (0, import_vue4.shallowReadonly)(isLoading),
|
|
253
|
+
error: (0, import_vue4.shallowReadonly)(error),
|
|
254
|
+
reset: () => controller.reset()
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/composables/use-sync-status.ts
|
|
259
|
+
var import_sync = require("@korajs/sync");
|
|
260
|
+
var import_vue5 = require("vue");
|
|
261
|
+
function useSyncStatus() {
|
|
262
|
+
const { syncEngine, subscribeSyncStatus, events } = useKoraContext();
|
|
263
|
+
const status = (0, import_vue5.shallowRef)(import_sync.OFFLINE_SYNC_STATUS);
|
|
264
|
+
(0, import_vue5.watchEffect)((onCleanup) => {
|
|
265
|
+
const controller = (0, import_sync.createSyncStatusController)({
|
|
266
|
+
syncEngine,
|
|
267
|
+
subscribeSyncStatus,
|
|
268
|
+
events: subscribeSyncStatus ? null : events
|
|
269
|
+
});
|
|
270
|
+
status.value = controller.getSnapshot();
|
|
271
|
+
const unsubscribe = controller.subscribe(() => {
|
|
272
|
+
status.value = controller.getSnapshot();
|
|
273
|
+
});
|
|
274
|
+
onCleanup(() => {
|
|
275
|
+
unsubscribe();
|
|
276
|
+
controller.destroy();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
return (0, import_vue5.readonly)(status);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/composables/use-app.ts
|
|
283
|
+
function useApp() {
|
|
284
|
+
const { app } = useKoraContext();
|
|
285
|
+
if (!app) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
'useApp() requires <KoraProvider :app="app">. Pass your createApp() result to the provider.'
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return app;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/composables/use-collection.ts
|
|
294
|
+
function useCollection(name) {
|
|
295
|
+
const { store } = useKoraContext();
|
|
296
|
+
return store.collection(name);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/composables/use-rich-text.ts
|
|
300
|
+
var import_store4 = require("@korajs/store");
|
|
301
|
+
var import_vue6 = require("vue");
|
|
302
|
+
function useRichText(collectionName, recordId, fieldName, options) {
|
|
303
|
+
const { store, syncEngine } = useKoraContext();
|
|
304
|
+
const controllerRef = (0, import_vue6.shallowRef)(null);
|
|
305
|
+
const state = (0, import_vue6.reactive)({
|
|
306
|
+
ready: false,
|
|
307
|
+
error: null,
|
|
308
|
+
canUndo: false,
|
|
309
|
+
canRedo: false,
|
|
310
|
+
cursors: []
|
|
311
|
+
});
|
|
312
|
+
(0, import_vue6.watch)(
|
|
313
|
+
() => [collectionName, recordId, fieldName, options?.useDocChannel],
|
|
314
|
+
([name, id, field, useDocChannel], _previous, onCleanup) => {
|
|
315
|
+
controllerRef.value?.destroy();
|
|
316
|
+
const controller2 = (0, import_store4.createRichTextController)({
|
|
317
|
+
collection: store.collection(name),
|
|
318
|
+
collectionName: name,
|
|
319
|
+
recordId: id,
|
|
320
|
+
fieldName: field,
|
|
321
|
+
store,
|
|
322
|
+
syncEngine: (0, import_store4.asRichTextSyncEngine)(syncEngine),
|
|
323
|
+
useDocChannel,
|
|
324
|
+
user: options?.user
|
|
325
|
+
});
|
|
326
|
+
const syncState = () => {
|
|
327
|
+
const snapshot = controller2.getSnapshot();
|
|
328
|
+
state.ready = snapshot.ready;
|
|
329
|
+
state.error = snapshot.error;
|
|
330
|
+
state.canUndo = snapshot.canUndo;
|
|
331
|
+
state.canRedo = snapshot.canRedo;
|
|
332
|
+
state.cursors = [...snapshot.cursors];
|
|
333
|
+
};
|
|
334
|
+
syncState();
|
|
335
|
+
const unsubscribe = controller2.subscribe(syncState);
|
|
336
|
+
controllerRef.value = controller2;
|
|
337
|
+
onCleanup(() => {
|
|
338
|
+
unsubscribe();
|
|
339
|
+
controller2.destroy();
|
|
340
|
+
if (controllerRef.value === controller2) {
|
|
341
|
+
controllerRef.value = null;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
{ immediate: true }
|
|
346
|
+
);
|
|
347
|
+
(0, import_vue6.watch)(
|
|
348
|
+
() => options?.user,
|
|
349
|
+
(user) => {
|
|
350
|
+
controllerRef.value?.setUser(user);
|
|
351
|
+
}
|
|
352
|
+
);
|
|
353
|
+
const controller = () => {
|
|
354
|
+
if (!controllerRef.value) {
|
|
355
|
+
throw new Error("useRichText controller is not initialized");
|
|
356
|
+
}
|
|
357
|
+
return controllerRef.value;
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
get doc() {
|
|
361
|
+
return controller().doc;
|
|
362
|
+
},
|
|
363
|
+
get text() {
|
|
364
|
+
return controller().text;
|
|
365
|
+
},
|
|
366
|
+
undo: () => controller().undo(),
|
|
367
|
+
redo: () => controller().redo(),
|
|
368
|
+
get ready() {
|
|
369
|
+
return state.ready;
|
|
370
|
+
},
|
|
371
|
+
get error() {
|
|
372
|
+
return state.error;
|
|
373
|
+
},
|
|
374
|
+
get canUndo() {
|
|
375
|
+
return state.canUndo;
|
|
376
|
+
},
|
|
377
|
+
get canRedo() {
|
|
378
|
+
return state.canRedo;
|
|
379
|
+
},
|
|
380
|
+
get cursors() {
|
|
381
|
+
return state.cursors;
|
|
382
|
+
},
|
|
383
|
+
setCursor: (anchor, head) => controller().setCursor(anchor, head),
|
|
384
|
+
clearCursor: () => controller().clearCursor()
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/composables/use-presence.ts
|
|
389
|
+
var import_sync2 = require("@korajs/sync");
|
|
390
|
+
var import_vue7 = require("vue");
|
|
391
|
+
function usePresence(user) {
|
|
392
|
+
const { syncEngine } = useKoraContext();
|
|
393
|
+
(0, import_vue7.watch)(
|
|
394
|
+
() => [syncEngine, user?.name, user?.color, user?.avatar],
|
|
395
|
+
([engine, name, color, avatar], _prev, onCleanup) => {
|
|
396
|
+
if (!engine || !name || !color) return;
|
|
397
|
+
const awareness = engine.getAwarenessManager();
|
|
398
|
+
awareness.setLocalState({
|
|
399
|
+
user: {
|
|
400
|
+
name,
|
|
401
|
+
color,
|
|
402
|
+
avatar
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
onCleanup(() => {
|
|
406
|
+
awareness.setLocalState(null);
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
{ immediate: true }
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
function useCollaborators() {
|
|
413
|
+
const { syncEngine } = useKoraContext();
|
|
414
|
+
const collaborators = (0, import_vue7.shallowRef)([]);
|
|
415
|
+
(0, import_vue7.watchEffect)((onCleanup) => {
|
|
416
|
+
if (!syncEngine) {
|
|
417
|
+
collaborators.value = [];
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const awareness = syncEngine.getAwarenessManager();
|
|
421
|
+
const unsubscribe = (0, import_sync2.subscribeRemoteAwarenessStates)(awareness, (states) => {
|
|
422
|
+
collaborators.value = states;
|
|
423
|
+
});
|
|
424
|
+
onCleanup(unsubscribe);
|
|
425
|
+
});
|
|
426
|
+
(0, import_vue7.onScopeDispose)(() => {
|
|
427
|
+
collaborators.value = [];
|
|
428
|
+
});
|
|
429
|
+
return collaborators;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/index.ts
|
|
31
433
|
function installKora(vueApp, koraApp) {
|
|
32
434
|
vueApp.provide(koraAppInjectionKey, koraApp);
|
|
33
435
|
}
|
|
34
436
|
function useKoraApp() {
|
|
35
|
-
const app = (0,
|
|
437
|
+
const app = (0, import_vue8.inject)(koraAppInjectionKey);
|
|
36
438
|
if (!app) {
|
|
37
|
-
throw new
|
|
38
|
-
|
|
439
|
+
throw new import_core2.KoraError(
|
|
440
|
+
'useKoraApp() requires installKora(vueApp, koraApp) or <KoraProvider :app="app">.',
|
|
39
441
|
"KORA_NOT_PROVIDED",
|
|
40
|
-
{
|
|
41
|
-
fix: "In main.ts: const kora = createApp({ schema }); installKora(vueApp, kora); await kora.ready"
|
|
42
|
-
}
|
|
442
|
+
{ fix: 'Use <KoraProvider :app="app"> and useApp() for full bindings.' }
|
|
43
443
|
);
|
|
44
444
|
}
|
|
45
445
|
return app;
|
|
46
446
|
}
|
|
47
447
|
// Annotate the CommonJS export names for ESM import in node:
|
|
48
448
|
0 && (module.exports = {
|
|
449
|
+
KoraProvider,
|
|
49
450
|
installKora,
|
|
50
451
|
koraAppInjectionKey,
|
|
51
|
-
|
|
452
|
+
koraContextKey,
|
|
453
|
+
useApp,
|
|
454
|
+
useCollaborators,
|
|
455
|
+
useCollection,
|
|
456
|
+
useKoraApp,
|
|
457
|
+
useKoraContext,
|
|
458
|
+
useMutation,
|
|
459
|
+
usePresence,
|
|
460
|
+
useQuery,
|
|
461
|
+
useRichText,
|
|
462
|
+
useSyncStatus
|
|
52
463
|
});
|
|
53
464
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\nimport { type App, type InjectionKey, inject } from 'vue'\nimport type { KoraAppHandle } from './types'\n\nexport type { KoraAppHandle } from './types'\n\n/** Vue injection key for the root Kora app instance. */\nexport const koraAppInjectionKey: InjectionKey<KoraAppHandle> = Symbol('korajs-app')\n\n/**\n * Register a Kora app on a Vue application instance.\n * Call once after `createApp()` from korajs, typically in `main.ts`.\n */\nexport function installKora(vueApp: App, koraApp: KoraAppHandle): void {\n\tvueApp.provide(koraAppInjectionKey, koraApp)\n}\n\n/**\n * Access the Kora app from a component setup function.\n * Pair with {@link installKora} and `app.ready` before running queries.\n */\nexport function useKoraApp(): KoraAppHandle {\n\tconst app = inject(koraAppInjectionKey)\n\tif (!app) {\n\t\tthrow new KoraError(\n\t\t\t'useKoraApp() requires installKora(vueApp, koraApp) on the Vue application.',\n\t\t\t'KORA_NOT_PROVIDED',\n\t\t\t{\n\t\t\t\tfix: 'In main.ts: const kora = createApp({ schema }); installKora(vueApp, kora); await kora.ready',\n\t\t\t},\n\t\t)\n\t}\n\treturn app\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA0B;AAC1B,iBAAoD;AAM7C,IAAM,sBAAmD,uBAAO,YAAY;AAM5E,SAAS,YAAY,QAAa,SAA8B;AACtE,SAAO,QAAQ,qBAAqB,OAAO;AAC5C;AAMO,SAAS,aAA4B;AAC3C,QAAM,UAAM,mBAAO,mBAAmB;AACtC,MAAI,CAAC,KAAK;AACT,UAAM,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,QACC,KAAK;AAAA,MACN;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/context.ts","../src/components/kora-provider.ts","../src/resolve-query-store-cache.ts","../src/composables/use-query.ts","../src/composables/use-mutation.ts","../src/composables/use-sync-status.ts","../src/composables/use-app.ts","../src/composables/use-collection.ts","../src/composables/use-rich-text.ts","../src/composables/use-presence.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\nimport { type App, inject } from 'vue'\nimport { koraAppInjectionKey } from './context'\nimport type { KoraAppLike } from './types'\n\nexport type { KoraAppHandle, KoraAppLike, KoraContextValue, KoraProviderProps } from './types'\nexport type { UseMutationOptions, UseMutationResult, UseQueryOptions, UseRichTextResult } from './types'\n\nexport { koraAppInjectionKey, koraContextKey, useKoraContext } from './context'\nexport { KoraProvider } from './components/kora-provider'\nexport { useQuery } from './composables/use-query'\nexport { useMutation } from './composables/use-mutation'\nexport { useSyncStatus } from './composables/use-sync-status'\nexport { useApp } from './composables/use-app'\nexport { useCollection } from './composables/use-collection'\nexport { useRichText } from './composables/use-rich-text'\nexport type { UseRichTextOptions } from './composables/use-rich-text'\nexport { usePresence, useCollaborators } from './composables/use-presence'\n\n/**\n * Register a Kora app on a Vue application instance.\n *\n * @deprecated Prefer {@link KoraProvider} — `installKora` does not provide reactive\n * hook context (`useQuery`, `useSyncStatus`, etc.) until you migrate to `KoraProvider`.\n */\nexport function installKora(vueApp: App, koraApp: KoraAppLike): void {\n\tvueApp.provide(koraAppInjectionKey, koraApp)\n}\n\n/**\n * Access the Kora app from a component when using {@link installKora} only.\n * For reactive hooks, use {@link useApp} inside {@link KoraProvider}.\n */\nexport function useKoraApp(): KoraAppLike {\n\tconst app = inject(koraAppInjectionKey)\n\tif (!app) {\n\t\tthrow new KoraError(\n\t\t\t'useKoraApp() requires installKora(vueApp, koraApp) or <KoraProvider :app=\"app\">.',\n\t\t\t'KORA_NOT_PROVIDED',\n\t\t\t{ fix: 'Use <KoraProvider :app=\"app\"> and useApp() for full bindings.' },\n\t\t)\n\t}\n\treturn app\n}\n","import { KoraError } from '@korajs/core'\nimport { type InjectionKey, inject, type ShallowRef } from 'vue'\nimport type { KoraAppLike, KoraContextValue } from './types'\n\nexport const koraContextKey: InjectionKey<ShallowRef<KoraContextValue | null>> =\n\tSymbol('korajs-context')\n\n/** @deprecated Use {@link koraContextKey} with {@link KoraProvider}. */\nexport const koraAppInjectionKey: InjectionKey<KoraAppLike> = Symbol('korajs-app')\n\nexport function useKoraContext(): KoraContextValue {\n\tconst contextRef = inject(koraContextKey)\n\tif (!contextRef?.value) {\n\t\tthrow new KoraError(\n\t\t\t'useKoraContext() requires <KoraProvider>. Wrap your app root with KoraProvider.',\n\t\t\t'KORA_NOT_PROVIDED',\n\t\t\t{ fix: '<KoraProvider :app=\"app\"><App /></KoraProvider>' },\n\t\t)\n\t}\n\treturn contextRef.value\n}\n","import { QueryStoreCache } from '@korajs/store'\nimport type { Store } from '@korajs/store'\nimport type { SyncEngine } from '@korajs/sync'\nimport {\n\ttype PropType,\n\ttype VNode,\n\tdefineComponent,\n\th,\n\tonScopeDispose,\n\tprovide,\n\tref,\n\tshallowRef,\n\twatch,\n} from 'vue'\nimport { koraContextKey, koraAppInjectionKey } from '../context'\nimport type { KoraAppLike, KoraContextValue } from '../types'\nimport { resolveQueryStoreCache } from '../resolve-query-store-cache'\n\nexport const KoraProvider = defineComponent({\n\tname: 'KoraProvider',\n\tprops: {\n\t\tapp: {\n\t\t\ttype: Object as PropType<KoraAppLike>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tstore: {\n\t\t\ttype: Object as PropType<Store>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tsyncEngine: {\n\t\t\ttype: Object as PropType<SyncEngine | null>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfallback: {\n\t\t\ttype: [Object, String] as PropType<VNode | string | null>,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\tconst resolvedStore = ref<Store | null>(null)\n\t\tconst resolvedSync = ref<SyncEngine | null>(null)\n\t\tconst ready = ref(!props.app && Boolean(props.store))\n\t\tconst initError = ref<Error | null>(null)\n\t\tconst fallbackQueryStoreCache = new QueryStoreCache()\n\t\tconst contextRef = shallowRef<KoraContextValue | null>(null)\n\n\t\tprovide(koraContextKey, contextRef)\n\t\tif (props.app) {\n\t\t\tprovide(koraAppInjectionKey, props.app)\n\t\t}\n\n\t\twatch(\n\t\t\t() => props.app,\n\t\t\t(app, _previous, onCleanup) => {\n\t\t\t\tif (!app) {\n\t\t\t\t\tif (props.store) {\n\t\t\t\t\t\tresolvedStore.value = props.store as Store\n\t\t\t\t\t\tresolvedSync.value = (props.syncEngine ?? null) as SyncEngine | null\n\t\t\t\t\t\tready.value = true\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tready.value = false\n\t\t\t\tinitError.value = null\n\t\t\t\tlet cancelled = false\n\n\t\t\t\tapp.ready\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tif (cancelled) return\n\t\t\t\t\t\tresolvedStore.value = app.getStore() as Store\n\t\t\t\t\t\tresolvedSync.value = app.getSyncEngine() as SyncEngine | null\n\t\t\t\t\t\tready.value = true\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\t\tif (cancelled) return\n\t\t\t\t\t\tconst err = error instanceof Error ? error : new Error(String(error))\n\t\t\t\t\t\tconsole.error('[Kora] Initialization failed:', err)\n\t\t\t\t\t\tinitError.value = err\n\t\t\t\t\t})\n\n\t\t\t\tonCleanup(() => {\n\t\t\t\t\tcancelled = true\n\t\t\t\t})\n\t\t\t},\n\t\t\t{ immediate: true },\n\t\t)\n\n\t\twatch(\n\t\t\t() => props.store,\n\t\t\t(store) => {\n\t\t\t\tif (store && !props.app) {\n\t\t\t\t\tresolvedStore.value = store as Store\n\t\t\t\t\tresolvedSync.value = (props.syncEngine ?? null) as SyncEngine | null\n\t\t\t\t\tready.value = true\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ immediate: true },\n\t\t)\n\n\t\twatch(\n\t\t\t() => [resolvedStore.value, resolvedSync.value, props.app] as const,\n\t\t\t([store, syncEngine, app]) => {\n\t\t\t\tif (!store) {\n\t\t\t\t\tcontextRef.value = null\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tcontextRef.value = {\n\t\t\t\t\tstore: store as Store,\n\t\t\t\t\tsyncEngine: (syncEngine ?? null) as SyncEngine | null,\n\t\t\t\t\tapp: app ?? null,\n\t\t\t\t\tevents: app?.events ?? null,\n\t\t\t\t\tsubscribeSyncStatus: app?.sync?.subscribeStatus ?? null,\n\t\t\t\t\tqueryStoreCache: resolveQueryStoreCache(app, fallbackQueryStoreCache),\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ immediate: true },\n\t\t)\n\n\t\tonScopeDispose(() => {\n\t\t\tif (!props.app) {\n\t\t\t\tfallbackQueryStoreCache.clear()\n\t\t\t}\n\t\t})\n\n\t\treturn () => {\n\t\t\tif (initError.value) {\n\t\t\t\treturn h(\n\t\t\t\t\t'div',\n\t\t\t\t\t{ style: { color: 'red', padding: '1rem', fontFamily: 'monospace' } },\n\t\t\t\t\t[h('strong', null, 'Kora initialization error: '), initError.value.message],\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif (!ready.value) {\n\t\t\t\treturn props.fallback ?? null\n\t\t\t}\n\n\t\t\tif (!resolvedStore.value) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'KoraProvider requires either an \"app\" or \"store\" prop. Pass createApp() or a Store instance.',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn slots.default?.()\n\t\t}\n\t},\n})\n","import { QueryStoreCache } from '@korajs/store'\nimport type { KoraAppLike } from '../types'\n\n/**\n * Resolves the per-app query cache when available, otherwise a provider-local fallback.\n */\nexport function resolveQueryStoreCache(\n\tapp: KoraAppLike | null | undefined,\n\tfallback: QueryStoreCache,\n): QueryStoreCache {\n\tif (app && typeof app.getQueryStoreCache === 'function') {\n\t\treturn app.getQueryStoreCache()\n\t}\n\treturn fallback\n}\n","import type { CollectionRecord, QueryBuilder } from '@korajs/store'\nimport { assertQueryReady } from '@korajs/store'\nimport { type DeepReadonly, readonly, shallowRef, watch } from 'vue'\nimport { useKoraContext } from '../context'\nimport type { UseQueryOptions } from '../types'\n\nconst EMPTY_ARRAY: readonly unknown[] = Object.freeze([])\n\n/**\n * Reactive query composable backed by the local Kora store.\n */\nexport function useQuery<T = CollectionRecord>(\n\tquery: QueryBuilder<T>,\n\toptions?: UseQueryOptions,\n): DeepReadonly<ReturnType<typeof shallowRef<readonly T[]>>> {\n\tconst { queryStoreCache } = useKoraContext()\n\tconst enabled = options?.enabled !== false\n\tconst snapshot = shallowRef<readonly T[]>(EMPTY_ARRAY as readonly T[])\n\n\twatch(\n\t\t() => (enabled ? JSON.stringify(query.getDescriptor()) : null),\n\t\t(key, _previous, onCleanup) => {\n\t\t\tif (!key) {\n\t\t\t\tsnapshot.value = EMPTY_ARRAY as readonly T[]\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassertQueryReady(query as QueryBuilder<unknown>)\n\t\t\tconst queryStore = queryStoreCache.getOrCreate(query)\n\t\t\tconst unsubscribe = queryStore.subscribe(() => {\n\t\t\t\tsnapshot.value = queryStore.getSnapshot()\n\t\t\t})\n\t\t\tsnapshot.value = queryStore.getSnapshot()\n\n\t\t\tonCleanup(() => {\n\t\t\t\tunsubscribe()\n\t\t\t\tqueryStoreCache.release(query as QueryBuilder<unknown>)\n\t\t\t})\n\t\t},\n\t\t{ immediate: true },\n\t)\n\n\treturn readonly(snapshot)\n}\n","import { createMutationController } from '@korajs/core/bindings'\nimport { onScopeDispose, ref, shallowReadonly } from 'vue'\nimport type { UseMutationOptions, UseMutationResult } from '../types'\n\n/**\n * Mutation composable with optional optimistic update and rollback hooks.\n */\nexport function useMutation<TData, TArgs extends unknown[], TContext = void>(\n\tmutationFn: (...args: TArgs) => Promise<TData>,\n\toptions?: UseMutationOptions<TData, TArgs, TContext>,\n): UseMutationResult<TData, TArgs> {\n\tconst fnRef = { current: mutationFn }\n\tfnRef.current = mutationFn\n\n\tconst optionsRef = { current: options }\n\toptionsRef.current = options\n\n\tconst isLoading = ref(false)\n\tconst error = ref<Error | null>(null)\n\tlet mounted = true\n\n\tonScopeDispose(() => {\n\t\tmounted = false\n\t})\n\n\tconst controller = createMutationController<TData, TArgs, TContext>({\n\t\tmutationFn: (...args) => fnRef.current(...args),\n\t\tresolveOptions: () => optionsRef.current,\n\t\tonStateChange: (state) => {\n\t\t\tif (!mounted) return\n\t\t\tisLoading.value = state.isLoading\n\t\t\terror.value = state.error\n\t\t},\n\t})\n\n\tisLoading.value = controller.getSnapshot().isLoading\n\terror.value = controller.getSnapshot().error\n\n\tonScopeDispose(() => {\n\t\tcontroller.destroy()\n\t})\n\n\treturn {\n\t\tmutate: (...args: TArgs) => controller.mutate(...args),\n\t\tmutateAsync: (...args: TArgs) => controller.mutateAsync(...args),\n\t\tisLoading: shallowReadonly(isLoading),\n\t\terror: shallowReadonly(error),\n\t\treset: () => controller.reset(),\n\t}\n}\n","import { OFFLINE_SYNC_STATUS, createSyncStatusController } from '@korajs/sync'\nimport { onScopeDispose, readonly, shallowRef, watchEffect } from 'vue'\nimport { useKoraContext } from '../context'\n\n/**\n * Reactive sync engine status. Updates only when status payload changes.\n */\nexport function useSyncStatus() {\n\tconst { syncEngine, subscribeSyncStatus, events } = useKoraContext()\n\tconst status = shallowRef(OFFLINE_SYNC_STATUS)\n\n\twatchEffect((onCleanup) => {\n\t\tconst controller = createSyncStatusController({\n\t\t\tsyncEngine,\n\t\t\tsubscribeSyncStatus,\n\t\t\tevents: subscribeSyncStatus ? null : events,\n\t\t})\n\t\tstatus.value = controller.getSnapshot()\n\t\tconst unsubscribe = controller.subscribe(() => {\n\t\t\tstatus.value = controller.getSnapshot()\n\t\t})\n\t\tonCleanup(() => {\n\t\t\tunsubscribe()\n\t\t\tcontroller.destroy()\n\t\t})\n\t})\n\n\treturn readonly(status)\n}\n","import { useKoraContext } from '../context'\nimport type { KoraAppLike } from '../types'\n\n/**\n * Returns the typed Kora app from {@link KoraProvider}'s `app` prop.\n */\nexport function useApp<T extends KoraAppLike = KoraAppLike>(): T {\n\tconst { app } = useKoraContext()\n\tif (!app) {\n\t\tthrow new Error(\n\t\t\t'useApp() requires <KoraProvider :app=\"app\">. Pass your createApp() result to the provider.',\n\t\t)\n\t}\n\treturn app as T\n}\n","import type { CollectionAccessor } from '@korajs/store'\nimport { useKoraContext } from '../context'\n\n/**\n * Returns a collection accessor for the given schema collection name.\n */\nexport function useCollection(name: string): CollectionAccessor {\n\tconst { store } = useKoraContext()\n\treturn store.collection(name)\n}\n","import { asRichTextSyncEngine, createRichTextController } from '@korajs/store'\nimport type { AwarenessUser } from '@korajs/sync'\nimport { reactive, shallowRef, watch } from 'vue'\nimport { useKoraContext } from '../context'\nimport type { UseRichTextResult } from '../types'\n\nexport interface UseRichTextOptions {\n\tuser?: AwarenessUser\n\tuseDocChannel?: boolean\n}\n\n/**\n * Binds a richtext field to a shared Yjs document for editor integration.\n */\nexport function useRichText(\n\tcollectionName: string,\n\trecordId: string,\n\tfieldName: string,\n\toptions?: UseRichTextOptions,\n): UseRichTextResult {\n\tconst { store, syncEngine } = useKoraContext()\n\tconst controllerRef = shallowRef<ReturnType<typeof createRichTextController> | null>(null)\n\n\tconst state = reactive({\n\t\tready: false,\n\t\terror: null as Error | null,\n\t\tcanUndo: false,\n\t\tcanRedo: false,\n\t\tcursors: [] as UseRichTextResult['cursors'],\n\t})\n\n\twatch(\n\t\t() => [collectionName, recordId, fieldName, options?.useDocChannel] as const,\n\t\t([name, id, field, useDocChannel], _previous, onCleanup) => {\n\t\t\tcontrollerRef.value?.destroy()\n\n\t\t\tconst controller = createRichTextController({\n\t\t\t\tcollection: store.collection(name),\n\t\t\t\tcollectionName: name,\n\t\t\t\trecordId: id,\n\t\t\t\tfieldName: field,\n\t\t\t\tstore,\n\t\t\t\tsyncEngine: asRichTextSyncEngine(syncEngine),\n\t\t\t\tuseDocChannel,\n\t\t\t\tuser: options?.user,\n\t\t\t})\n\n\t\t\tconst syncState = (): void => {\n\t\t\t\tconst snapshot = controller.getSnapshot()\n\t\t\t\tstate.ready = snapshot.ready\n\t\t\t\tstate.error = snapshot.error\n\t\t\t\tstate.canUndo = snapshot.canUndo\n\t\t\t\tstate.canRedo = snapshot.canRedo\n\t\t\t\tstate.cursors = [...snapshot.cursors]\n\t\t\t}\n\n\t\t\tsyncState()\n\t\t\tconst unsubscribe = controller.subscribe(syncState)\n\t\t\tcontrollerRef.value = controller\n\n\t\t\tonCleanup(() => {\n\t\t\t\tunsubscribe()\n\t\t\t\tcontroller.destroy()\n\t\t\t\tif (controllerRef.value === controller) {\n\t\t\t\t\tcontrollerRef.value = null\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\t{ immediate: true },\n\t)\n\n\twatch(\n\t\t() => options?.user,\n\t\t(user) => {\n\t\t\tcontrollerRef.value?.setUser(user)\n\t\t},\n\t)\n\n\tconst controller = (): NonNullable<typeof controllerRef.value> => {\n\t\tif (!controllerRef.value) {\n\t\t\tthrow new Error('useRichText controller is not initialized')\n\t\t}\n\t\treturn controllerRef.value\n\t}\n\n\treturn {\n\t\tget doc() {\n\t\t\treturn controller().doc\n\t\t},\n\t\tget text() {\n\t\t\treturn controller().text\n\t\t},\n\t\tundo: () => controller().undo(),\n\t\tredo: () => controller().redo(),\n\t\tget ready() {\n\t\t\treturn state.ready\n\t\t},\n\t\tget error() {\n\t\t\treturn state.error\n\t\t},\n\t\tget canUndo() {\n\t\t\treturn state.canUndo\n\t\t},\n\t\tget canRedo() {\n\t\t\treturn state.canRedo\n\t\t},\n\t\tget cursors() {\n\t\t\treturn state.cursors\n\t\t},\n\t\tsetCursor: (anchor: number, head: number) => controller().setCursor(anchor, head),\n\t\tclearCursor: () => controller().clearCursor(),\n\t}\n}\n","import type { AwarenessState } from '@korajs/sync'\nimport { subscribeRemoteAwarenessStates } from '@korajs/sync'\nimport { onScopeDispose, shallowRef, watch, watchEffect, type ShallowRef } from 'vue'\nimport { useKoraContext } from '../context'\n\n/**\n * Sets the local user's collaborative presence state.\n */\nexport function usePresence(user: { name: string; color: string; avatar?: string } | null): void {\n\tconst { syncEngine } = useKoraContext()\n\n\twatch(\n\t\t() => [syncEngine, user?.name, user?.color, user?.avatar] as const,\n\t\t([engine, name, color, avatar], _prev, onCleanup) => {\n\t\t\tif (!engine || !name || !color) return\n\n\t\t\tconst awareness = engine.getAwarenessManager()\n\t\t\tawareness.setLocalState({\n\t\t\t\tuser: {\n\t\t\t\t\tname,\n\t\t\t\t\tcolor,\n\t\t\t\t\tavatar,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tonCleanup(() => {\n\t\t\t\tawareness.setLocalState(null)\n\t\t\t})\n\t\t},\n\t\t{ immediate: true },\n\t)\n}\n\n/**\n * Reactive list of remote collaborators' awareness states.\n */\nexport function useCollaborators(): ShallowRef<AwarenessState[]> {\n\tconst { syncEngine } = useKoraContext()\n\tconst collaborators = shallowRef<AwarenessState[]>([])\n\n\twatchEffect((onCleanup) => {\n\t\tif (!syncEngine) {\n\t\t\tcollaborators.value = []\n\t\t\treturn\n\t\t}\n\n\t\tconst awareness = syncEngine.getAwarenessManager()\n\t\tconst unsubscribe = subscribeRemoteAwarenessStates(awareness, (states) => {\n\t\t\tcollaborators.value = states\n\t\t})\n\t\tonCleanup(unsubscribe)\n\t})\n\n\tonScopeDispose(() => {\n\t\tcollaborators.value = []\n\t})\n\n\treturn collaborators\n}\n"],"mappings":"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","names":["import_core","import_vue","import_store","import_vue","import_store","import_vue","import_vue","import_vue","import_store","import_vue","controller","import_sync","import_vue"]}
|