@drakkar.software/octovault-sdk 0.3.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/chunk-RMN6N2PQ.js +1 -0
- package/dist/index.d.ts +1350 -0
- package/dist/index.js +2108 -0
- package/dist/platform/hash-wasm-shim.d.ts +1 -0
- package/dist/platform/hash-wasm-shim.js +1 -0
- package/dist/platform/index.d.ts +16 -0
- package/dist/platform/index.js +16 -0
- package/dist/platform/index.native.d.ts +17 -0
- package/dist/platform/index.native.js +16 -0
- package/package.json +77 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2108 @@
|
|
|
1
|
+
import { mutePrefsConfig, readPrefsConfig, MAX_OBJECT_BLOB_BYTES, objectBlobPaths, kvRemove, kvSet, kvGet, getSyncBase, getSyncPrefix, getSyncNamespace, keyringPull, configureDKSpaces, configureKv as configureKv$1, isMuteActive, applyMute, getSharedSpacesNamespace, pairingClientConfig, attachmentKind, linkedDeviceScope, accountScope, profilePush, profilePull } from '@drakkar.software/dk-spaces-sdk';
|
|
2
|
+
export { MAX_OBJECT_BLOB_BYTES, OBJECT_COLLECTIONS, attachmentKind, buildSignedEventsRequest, clearLiveSyncBus, clockTime, dispatchDocChange, emitSseStatus, fold, formatBytes, initialsFor, isMuteActive, isWordStart, keyringPull, keyringPush, matchTitle, objIndexPull, objIndexPush, objInvPull, objInvPush, objLogName, objPubPull, objPubPush, objectBlobName, objectDirName, onSseStatus, plural, previewInvite, rankResults, registerPull, relativeTime, relativeTimeShort, subscribeChanges, typesIndexName, typesIndexPull, typesIndexPush } from '@drakkar.software/dk-spaces-sdk';
|
|
3
|
+
import { defaultSpaceLayout, createPrefsStore, readProfile as readProfile$1, readProfiles as readProfiles$1, writeProfile as writeProfile$1, buildEncryptor as buildEncryptor$1, configureSpaces, addObject, deriveSession as deriveSession$1, buildSession as buildSession$1, buildLinkedSession as buildLinkedSession$1, sessionFromPersisted as sessionFromPersisted$1, completeDevicePairing as completeDevicePairing$1, startDevicePairing as startDevicePairing$1, getSpaceAccessEntry, readSpaces as readSpaces$1, updateSpacesDoc, removeSpaceAccessEntry, unsealFromSelf, updateSpacesExtraField } from '@drakkar.software/starfish-spaces';
|
|
4
|
+
export { SpaceAccessError, acceptSpaceInvite, activeAccountOf, addObject, ancestors, archiveObject, breadcrumbs, broadcastSpaceMeta, buildTree, capProviderFor, clearNodeAccessCache, clearSpaceAccessStore, createNode, createSpace, createSpaceInviteLink, fingerprintFromUserId, generateSeedWords, getNodeAccess, getNodeAccessEntry, getSpaceAccessEntry, getSpaceClient, hydrateSpaceAccessStore, inviteToNode, inviteToSpace, isValidSeed, joinNodeByLink, joinSpaceByLink, linkAccessFromStore, localSpaceAccessEntries, makeJoinRequest, memberCapsFromStore, nextOrder, onSpaceMeta, ownerEnsureKeyring, ownerTrustedAdders, parseObjectDirectoryDoc, patchObject, readObjectDirectory, readSpaceAccess, reconcileSpaceMeta, recoverSpaceAccess, removeSpaceAccessEntry, removeSpaceMember, reorderObjects, reorderSpaces, reparentObject, rootIdentityOf, saveNodeAccessEntry, saveSpaceAccessEntry, sealToRecipient, sealToSelf, subtreeIds, unsealFromRecipient, unsealFromSelf, updateSpacesDoc, writeSpaceAccess } from '@drakkar.software/starfish-spaces';
|
|
5
|
+
import { signRequest, stableStringify, randomId } from '@drakkar.software/starfish-protocol';
|
|
6
|
+
export { bytesToHex, randomId, slugify as roomSlug } from '@drakkar.software/starfish-protocol';
|
|
7
|
+
import { createSealedBlobStore, createKvPullCache } from '@drakkar.software/starfish-client';
|
|
8
|
+
export { FileTooLargeError } from '@drakkar.software/starfish-client';
|
|
9
|
+
import { createTimeoutFetch } from '@drakkar.software/starfish-client/fetch';
|
|
10
|
+
export { parseSseFrames } from '@drakkar.software/starfish-client/events';
|
|
11
|
+
export { WalDocument } from '@drakkar.software/starfish-wal';
|
|
12
|
+
export { createWalDocument, noopEncryptor } from '@drakkar.software/starfish-wal/client';
|
|
13
|
+
|
|
14
|
+
// src/config/config.ts
|
|
15
|
+
async function buildAuthHeaders(cap, devEdPrivHex, method, pathAndQuery) {
|
|
16
|
+
let host = "";
|
|
17
|
+
try {
|
|
18
|
+
host = new URL(getSyncBase()).host;
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
const { sig, ts, nonce } = await signRequest(
|
|
22
|
+
{ method, pathAndQuery, host },
|
|
23
|
+
devEdPrivHex
|
|
24
|
+
);
|
|
25
|
+
const capJson = stableStringify(cap);
|
|
26
|
+
const capB64 = typeof btoa === "function" ? btoa(capJson) : Buffer.from(capJson, "utf-8").toString("base64");
|
|
27
|
+
return {
|
|
28
|
+
Authorization: `Cap ${capB64}`,
|
|
29
|
+
"X-Starfish-Sig": sig,
|
|
30
|
+
"X-Starfish-Ts": String(ts),
|
|
31
|
+
"X-Starfish-Nonce": nonce
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
var _OCTOVAULT_LAYOUT = { ...defaultSpaceLayout, profilePull, profilePush, accountScope, linkedDeviceScope };
|
|
35
|
+
function octoVaultLayout() {
|
|
36
|
+
return _OCTOVAULT_LAYOUT;
|
|
37
|
+
}
|
|
38
|
+
async function readProfile(userId) {
|
|
39
|
+
return readProfile$1(userId, { baseUrl: `${getSyncBase()}${getSyncPrefix()}`, layout: octoVaultLayout() });
|
|
40
|
+
}
|
|
41
|
+
async function readProfiles(ids) {
|
|
42
|
+
return readProfiles$1(ids, {
|
|
43
|
+
baseUrl: getSyncBase(),
|
|
44
|
+
namespace: getSyncNamespace() ?? "",
|
|
45
|
+
layout: octoVaultLayout()
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function writeProfile(client, userId, patch) {
|
|
49
|
+
return writeProfile$1(client, userId, octoVaultLayout(), patch);
|
|
50
|
+
}
|
|
51
|
+
async function buildEncryptor(client, keys, spaceId, trustedAdders) {
|
|
52
|
+
return buildEncryptor$1(client, keys, keyringPull(spaceId), trustedAdders);
|
|
53
|
+
}
|
|
54
|
+
var PULL_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
55
|
+
var _cache = null;
|
|
56
|
+
function pullCache() {
|
|
57
|
+
if (!_cache) {
|
|
58
|
+
_cache = createKvPullCache(
|
|
59
|
+
{ getItem: kvGet, setItem: kvSet, removeItem: kvRemove },
|
|
60
|
+
{ prefix: "octovault.pullcache.", maxAgeMs: PULL_CACHE_MAX_AGE_MS }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return _cache;
|
|
64
|
+
}
|
|
65
|
+
function resetPullCache() {
|
|
66
|
+
_cache = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/config/config.ts
|
|
70
|
+
var _config = {
|
|
71
|
+
syncBase: "http://localhost:8787",
|
|
72
|
+
syncNamespace: void 0,
|
|
73
|
+
syncPrefix: "",
|
|
74
|
+
eventsUrl: "http://localhost:8787/events",
|
|
75
|
+
webBase: ""
|
|
76
|
+
};
|
|
77
|
+
function configureOctoVault(config) {
|
|
78
|
+
_config = { ..._config, ...config };
|
|
79
|
+
resetPullCache();
|
|
80
|
+
configureDKSpaces({
|
|
81
|
+
syncBase: _config.syncBase,
|
|
82
|
+
syncNamespace: _config.syncNamespace,
|
|
83
|
+
eventsUrl: _config.eventsUrl,
|
|
84
|
+
..._config.sharedSpacesNamespace ? { sharedSpacesNamespace: _config.sharedSpacesNamespace } : {}
|
|
85
|
+
});
|
|
86
|
+
configureSpaces({ layout: octoVaultLayout() });
|
|
87
|
+
}
|
|
88
|
+
function getSyncBase2() {
|
|
89
|
+
return _config.syncBase;
|
|
90
|
+
}
|
|
91
|
+
function getSyncNamespace2() {
|
|
92
|
+
return _config.syncNamespace;
|
|
93
|
+
}
|
|
94
|
+
function getSyncPrefix2() {
|
|
95
|
+
return _config.syncPrefix;
|
|
96
|
+
}
|
|
97
|
+
function getEventsUrl() {
|
|
98
|
+
return _config.eventsUrl;
|
|
99
|
+
}
|
|
100
|
+
var _kv = {
|
|
101
|
+
async get() {
|
|
102
|
+
return null;
|
|
103
|
+
},
|
|
104
|
+
async set() {
|
|
105
|
+
},
|
|
106
|
+
async remove() {
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
function configureKv(adapter) {
|
|
110
|
+
_kv = adapter;
|
|
111
|
+
configureKv$1({ get: adapter.get, set: adapter.set, remove: adapter.remove });
|
|
112
|
+
}
|
|
113
|
+
async function kvGet2(key) {
|
|
114
|
+
return _kv.get(key);
|
|
115
|
+
}
|
|
116
|
+
async function kvSet2(key, value) {
|
|
117
|
+
return _kv.set(key, value);
|
|
118
|
+
}
|
|
119
|
+
async function kvRemove2(key) {
|
|
120
|
+
return _kv.remove(key);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/domain/types.ts
|
|
124
|
+
var BUILTIN_OBJECT_TYPES = [
|
|
125
|
+
"folder",
|
|
126
|
+
"page",
|
|
127
|
+
"board",
|
|
128
|
+
"task",
|
|
129
|
+
"file",
|
|
130
|
+
"image",
|
|
131
|
+
"automation",
|
|
132
|
+
"note",
|
|
133
|
+
"calendar",
|
|
134
|
+
"form",
|
|
135
|
+
"feedback"
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// src/domain/capabilities.ts
|
|
139
|
+
var CAPABILITY_META = {
|
|
140
|
+
pages: { label: "Pages", description: "Rich-text pages with nested blocks", objectType: "page" },
|
|
141
|
+
boards: { label: "Boards", description: "Kanban boards with tasks", objectType: "board" },
|
|
142
|
+
notes: { label: "Notes", description: "Personal note-taking", objectType: "note" },
|
|
143
|
+
calendar: { label: "Calendar", description: "Event scheduling and agenda view", objectType: "calendar" },
|
|
144
|
+
forms: { label: "Forms", description: "Form builder and response collection", objectType: "form" },
|
|
145
|
+
feedback: { label: "Feedback", description: "Ranked feedback with voting", objectType: "feedback" }
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/domain/object-types.ts
|
|
149
|
+
var TASK_PROPS = [
|
|
150
|
+
{
|
|
151
|
+
key: "status",
|
|
152
|
+
label: "Status",
|
|
153
|
+
kind: "select",
|
|
154
|
+
options: [
|
|
155
|
+
{ id: "todo", label: "To do" },
|
|
156
|
+
{ id: "doing", label: "In progress" },
|
|
157
|
+
{ id: "done", label: "Done" }
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{ key: "columnId", label: "Column", kind: "text" },
|
|
161
|
+
{ key: "order", label: "Order", kind: "number" }
|
|
162
|
+
];
|
|
163
|
+
var BLOB_PROPS = [
|
|
164
|
+
{ key: "blobId", label: "Blob", kind: "text" },
|
|
165
|
+
{ key: "mime", label: "MIME type", kind: "text" },
|
|
166
|
+
{ key: "size", label: "Size", kind: "number" },
|
|
167
|
+
{ key: "name", label: "Filename", kind: "text" }
|
|
168
|
+
];
|
|
169
|
+
var NOTE_PROPS = [
|
|
170
|
+
{ key: "tags", label: "Tags", kind: "text" }
|
|
171
|
+
];
|
|
172
|
+
var BUILTIN_DESCRIPTORS = {
|
|
173
|
+
// OctoVault primary types
|
|
174
|
+
folder: { contentKind: "none", icon: "folder", label: "Folder", editor: "none", props: [], creatable: true, workTree: false, findable: false },
|
|
175
|
+
page: { contentKind: "append", icon: "file", label: "Page", editor: "page", props: [], creatable: true, workTree: true, findable: true, defaultTitle: "Untitled", capability: "pages" },
|
|
176
|
+
board: { contentKind: "append", icon: "work", label: "Board", editor: "board", props: [], creatable: true, workTree: true, findable: true, defaultTitle: "Untitled Board", capability: "boards", ownsScroll: true },
|
|
177
|
+
task: { contentKind: "append", icon: "check", label: "Task", editor: "page", props: TASK_PROPS, creatable: false, workTree: false, findable: false },
|
|
178
|
+
file: { contentKind: "none", icon: "file", label: "File", editor: "file", props: BLOB_PROPS, creatable: true, workTree: false, findable: false, defaultTitle: "Untitled File" },
|
|
179
|
+
image: { contentKind: "none", icon: "image", label: "Image", editor: "file", props: BLOB_PROPS, creatable: true, workTree: false, findable: false, defaultTitle: "Untitled Image" },
|
|
180
|
+
// Automation: stream-bot integration node; not yet creatable from the UI.
|
|
181
|
+
automation: { contentKind: "none", icon: "stream", label: "Automation", editor: "none", props: [], creatable: false, workTree: false, findable: false },
|
|
182
|
+
// Sub-app variant types
|
|
183
|
+
note: { contentKind: "append", icon: "file", label: "Note", editor: "page", props: NOTE_PROPS, creatable: false, workTree: false, findable: true, defaultTitle: "Untitled Note", capability: "notes" },
|
|
184
|
+
calendar: { contentKind: "append", icon: "clock", label: "Calendar", editor: "calendar", props: [], creatable: true, workTree: true, findable: true, defaultTitle: "Untitled Calendar", capability: "calendar", ownsScroll: true },
|
|
185
|
+
form: { contentKind: "append", icon: "layers", label: "Form", editor: "form", props: [], creatable: true, workTree: true, findable: true, defaultTitle: "Untitled Form", capability: "forms", ownsScroll: true },
|
|
186
|
+
feedback: { contentKind: "append", icon: "check", label: "Feedback", editor: "feedback", props: [], creatable: true, workTree: true, findable: true, defaultTitle: "Untitled Feedback", capability: "feedback", ownsScroll: true },
|
|
187
|
+
// Removed types — tombstones so legacy nodes in existing spaces stay hidden (workTree: false)
|
|
188
|
+
// rather than falling through to the generic descriptor (workTree: true). Not creatable.
|
|
189
|
+
room: { contentKind: "none", icon: "layers", label: "Channel", editor: "none", props: [], creatable: false, workTree: false, findable: false },
|
|
190
|
+
category: { contentKind: "none", icon: "folder", label: "Category", editor: "none", props: [], creatable: false, workTree: false, findable: false }
|
|
191
|
+
};
|
|
192
|
+
var GENERIC = { contentKind: "merge", icon: "layers", label: "Object", editor: "record", props: [], creatable: false, workTree: true, findable: true };
|
|
193
|
+
function objectDescriptor(type) {
|
|
194
|
+
return BUILTIN_DESCRIPTORS[type] ?? GENERIC;
|
|
195
|
+
}
|
|
196
|
+
function iconForNode(node) {
|
|
197
|
+
return objectDescriptor(node.type).icon;
|
|
198
|
+
}
|
|
199
|
+
function isContainerType(type) {
|
|
200
|
+
return objectDescriptor(type).editor === "none";
|
|
201
|
+
}
|
|
202
|
+
function showsInWorkTree(node) {
|
|
203
|
+
return objectDescriptor(node.type).workTree;
|
|
204
|
+
}
|
|
205
|
+
function isOpenableObjectType(type) {
|
|
206
|
+
return objectDescriptor(type).editor !== "none";
|
|
207
|
+
}
|
|
208
|
+
function isFindableType(type) {
|
|
209
|
+
return objectDescriptor(type).findable;
|
|
210
|
+
}
|
|
211
|
+
function contentKindOf(node) {
|
|
212
|
+
return node.contentKind ?? objectDescriptor(node.type).contentKind;
|
|
213
|
+
}
|
|
214
|
+
function creatableTypes() {
|
|
215
|
+
return Object.entries(BUILTIN_DESCRIPTORS).filter(([, d]) => d.creatable).map(([type, d]) => ({ type, ...d }));
|
|
216
|
+
}
|
|
217
|
+
function defaultProps(_type) {
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
function typeDefToDescriptor(def) {
|
|
221
|
+
return {
|
|
222
|
+
contentKind: def.contentKind,
|
|
223
|
+
icon: def.icon || "layers",
|
|
224
|
+
label: def.label,
|
|
225
|
+
editor: def.editorKind,
|
|
226
|
+
props: def.fields.map((f) => ({
|
|
227
|
+
key: f.key,
|
|
228
|
+
label: f.label,
|
|
229
|
+
kind: f.kind,
|
|
230
|
+
options: f.options,
|
|
231
|
+
required: f.required
|
|
232
|
+
})),
|
|
233
|
+
creatable: def.creatable,
|
|
234
|
+
workTree: true,
|
|
235
|
+
findable: true,
|
|
236
|
+
color: def.color
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function makeRegistry(customTypes) {
|
|
240
|
+
const customMap = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const def of customTypes) {
|
|
242
|
+
if (!BUILTIN_DESCRIPTORS[def.id]) {
|
|
243
|
+
customMap.set(def.id, typeDefToDescriptor(def));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const descriptor = (type) => BUILTIN_DESCRIPTORS[type] ?? customMap.get(type) ?? GENERIC;
|
|
247
|
+
const creatableTypes2 = () => {
|
|
248
|
+
const builtins = Object.entries(BUILTIN_DESCRIPTORS).filter(([, d]) => d.creatable).map(([type, d]) => ({ type, ...d }));
|
|
249
|
+
const customs = [];
|
|
250
|
+
for (const [type, d] of customMap) {
|
|
251
|
+
if (d.creatable) customs.push({ type, ...d });
|
|
252
|
+
}
|
|
253
|
+
return [...builtins, ...customs];
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
descriptor,
|
|
257
|
+
creatableTypes: creatableTypes2,
|
|
258
|
+
showsInWorkTree: (node) => descriptor(node.type).workTree,
|
|
259
|
+
iconForNode: (node) => descriptor(node.type).icon,
|
|
260
|
+
isFindableType: (type) => descriptor(type).findable,
|
|
261
|
+
isContainerType: (type) => descriptor(type).editor === "none",
|
|
262
|
+
isOpenableType: (type) => descriptor(type).editor !== "none",
|
|
263
|
+
allTypes: () => {
|
|
264
|
+
const builtins = Object.entries(BUILTIN_DESCRIPTORS).map(
|
|
265
|
+
([type, d]) => ({ type, ...d, isCustom: false })
|
|
266
|
+
);
|
|
267
|
+
const customs = Array.from(customMap.entries()).map(([type, d]) => ({
|
|
268
|
+
type,
|
|
269
|
+
...d,
|
|
270
|
+
isCustom: true
|
|
271
|
+
}));
|
|
272
|
+
return [...builtins, ...customs];
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
var BUILTIN_REGISTRY = makeRegistry([]);
|
|
277
|
+
function routeForNode(_node) {
|
|
278
|
+
return "/work/object/[id]";
|
|
279
|
+
}
|
|
280
|
+
function objectLink(spaceId, node) {
|
|
281
|
+
const origin = globalThis.location?.origin;
|
|
282
|
+
if (!origin) return null;
|
|
283
|
+
return `${origin}${routeForNode().replace("[id]", node.id)}?spaceId=${encodeURIComponent(spaceId)}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/domain/errors.ts
|
|
287
|
+
var GENERIC_FALLBACK = "Something went wrong. Please try again.";
|
|
288
|
+
var NETWORK_RE = /failed to fetch|network request failed|networkerror|load failed|fetch failed|socket|ECONN|timeout/i;
|
|
289
|
+
var INTERNAL_RE = /argon2|wasm|hkdf|kyber|kem\b|cbor|nacl|ciphertext|deserializ|undefined is not|cannot read|JSON parse|unexpected token/i;
|
|
290
|
+
function looksHuman(message) {
|
|
291
|
+
if (!message) return false;
|
|
292
|
+
if (message.length > 160) return false;
|
|
293
|
+
if (INTERNAL_RE.test(message)) return false;
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
function humanizeError(e, fallback = GENERIC_FALLBACK) {
|
|
297
|
+
const err = e instanceof Error ? e : null;
|
|
298
|
+
const message = err?.message ?? (typeof e === "string" ? e : "");
|
|
299
|
+
const name = err?.name ?? "";
|
|
300
|
+
if (NETWORK_RE.test(message) || NETWORK_RE.test(name)) {
|
|
301
|
+
return "Can't reach the sync server. Check your connection and try again.";
|
|
302
|
+
}
|
|
303
|
+
if (name === "NotAllowedError") {
|
|
304
|
+
return "The passkey prompt was dismissed. Try again, or use your PIN.";
|
|
305
|
+
}
|
|
306
|
+
if (name === "AbortError") {
|
|
307
|
+
return "That took too long. Try again.";
|
|
308
|
+
}
|
|
309
|
+
const status = e?.status;
|
|
310
|
+
if (typeof status === "number") {
|
|
311
|
+
if (status === 401 || status === 403) return "You don't have access to that. Check the invite or ask its owner.";
|
|
312
|
+
if (status === 404) return fallback;
|
|
313
|
+
if (status === 429) return "Too many requests \u2014 wait a moment and try again.";
|
|
314
|
+
if (status >= 500) return "The sync server hit a problem. Try again in a moment.";
|
|
315
|
+
if (status >= 400) return fallback;
|
|
316
|
+
}
|
|
317
|
+
return looksHuman(message) ? message : fallback;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/format/emoji.ts
|
|
321
|
+
var CATEGORY_TABLES = [
|
|
322
|
+
{
|
|
323
|
+
id: "smileys",
|
|
324
|
+
label: "Smileys & emotion",
|
|
325
|
+
table: {
|
|
326
|
+
smile: "\u{1F604}",
|
|
327
|
+
smiley: "\u{1F603}",
|
|
328
|
+
grin: "\u{1F601}",
|
|
329
|
+
laughing: "\u{1F606}",
|
|
330
|
+
joy: "\u{1F602}",
|
|
331
|
+
rofl: "\u{1F923}",
|
|
332
|
+
sweat_smile: "\u{1F605}",
|
|
333
|
+
wink: "\u{1F609}",
|
|
334
|
+
blush: "\u{1F60A}",
|
|
335
|
+
slight_smile: "\u{1F642}",
|
|
336
|
+
upside_down: "\u{1F643}",
|
|
337
|
+
heart_eyes: "\u{1F60D}",
|
|
338
|
+
kissing_heart: "\u{1F618}",
|
|
339
|
+
thinking: "\u{1F914}",
|
|
340
|
+
neutral_face: "\u{1F610}",
|
|
341
|
+
unamused: "\u{1F612}",
|
|
342
|
+
roll_eyes: "\u{1F644}",
|
|
343
|
+
smirk: "\u{1F60F}",
|
|
344
|
+
relieved: "\u{1F60C}",
|
|
345
|
+
pensive: "\u{1F614}",
|
|
346
|
+
confused: "\u{1F615}",
|
|
347
|
+
cry: "\u{1F622}",
|
|
348
|
+
sob: "\u{1F62D}",
|
|
349
|
+
fearful: "\u{1F628}",
|
|
350
|
+
weary: "\u{1F629}",
|
|
351
|
+
triumph: "\u{1F624}",
|
|
352
|
+
angry: "\u{1F620}",
|
|
353
|
+
rage: "\u{1F621}",
|
|
354
|
+
sleeping: "\u{1F634}",
|
|
355
|
+
sunglasses: "\u{1F60E}",
|
|
356
|
+
nerd: "\u{1F913}",
|
|
357
|
+
star_struck: "\u{1F929}",
|
|
358
|
+
partying_face: "\u{1F973}",
|
|
359
|
+
zany: "\u{1F92A}",
|
|
360
|
+
scream: "\u{1F631}",
|
|
361
|
+
exploding_head: "\u{1F92F}",
|
|
362
|
+
shushing: "\u{1F92B}"
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "gestures",
|
|
367
|
+
label: "Gestures & body",
|
|
368
|
+
table: {
|
|
369
|
+
"+1": "\u{1F44D}",
|
|
370
|
+
thumbsup: "\u{1F44D}",
|
|
371
|
+
"-1": "\u{1F44E}",
|
|
372
|
+
thumbsdown: "\u{1F44E}",
|
|
373
|
+
ok_hand: "\u{1F44C}",
|
|
374
|
+
fist: "\u{1F44A}",
|
|
375
|
+
wave: "\u{1F44B}",
|
|
376
|
+
raised_hands: "\u{1F64C}",
|
|
377
|
+
clap: "\u{1F44F}",
|
|
378
|
+
pray: "\u{1F64F}",
|
|
379
|
+
muscle: "\u{1F4AA}",
|
|
380
|
+
v: "\u270C\uFE0F",
|
|
381
|
+
crossed_fingers: "\u{1F91E}",
|
|
382
|
+
handshake: "\u{1F91D}",
|
|
383
|
+
eyes: "\u{1F440}",
|
|
384
|
+
point_up: "\u261D\uFE0F"
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "hearts",
|
|
389
|
+
label: "Hearts",
|
|
390
|
+
table: {
|
|
391
|
+
heart: "\u2764\uFE0F",
|
|
392
|
+
broken_heart: "\u{1F494}",
|
|
393
|
+
sparkling_heart: "\u{1F496}",
|
|
394
|
+
two_hearts: "\u{1F495}",
|
|
395
|
+
blue_heart: "\u{1F499}",
|
|
396
|
+
green_heart: "\u{1F49A}",
|
|
397
|
+
purple_heart: "\u{1F49C}",
|
|
398
|
+
yellow_heart: "\u{1F49B}",
|
|
399
|
+
orange_heart: "\u{1F9E1}",
|
|
400
|
+
black_heart: "\u{1F5A4}"
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "symbols",
|
|
405
|
+
label: "Symbols & marks",
|
|
406
|
+
table: {
|
|
407
|
+
fire: "\u{1F525}",
|
|
408
|
+
sparkles: "\u2728",
|
|
409
|
+
star: "\u2B50",
|
|
410
|
+
star2: "\u{1F31F}",
|
|
411
|
+
zap: "\u26A1",
|
|
412
|
+
boom: "\u{1F4A5}",
|
|
413
|
+
"100": "\u{1F4AF}",
|
|
414
|
+
tada: "\u{1F389}",
|
|
415
|
+
confetti_ball: "\u{1F38A}",
|
|
416
|
+
balloon: "\u{1F388}",
|
|
417
|
+
gift: "\u{1F381}",
|
|
418
|
+
bell: "\u{1F514}",
|
|
419
|
+
warning: "\u26A0\uFE0F",
|
|
420
|
+
check: "\u2705",
|
|
421
|
+
white_check_mark: "\u2705",
|
|
422
|
+
heavy_check_mark: "\u2714\uFE0F",
|
|
423
|
+
x: "\u274C",
|
|
424
|
+
no_entry: "\u26D4",
|
|
425
|
+
question: "\u2753",
|
|
426
|
+
exclamation: "\u2757",
|
|
427
|
+
plus: "\u2795",
|
|
428
|
+
heavy_plus_sign: "\u2795",
|
|
429
|
+
minus: "\u2796",
|
|
430
|
+
heavy_minus_sign: "\u2796",
|
|
431
|
+
bulb: "\u{1F4A1}",
|
|
432
|
+
lock: "\u{1F512}",
|
|
433
|
+
key: "\u{1F511}",
|
|
434
|
+
mag: "\u{1F50D}",
|
|
435
|
+
hourglass: "\u23F3",
|
|
436
|
+
alarm_clock: "\u23F0",
|
|
437
|
+
calendar: "\u{1F4C5}",
|
|
438
|
+
pushpin: "\u{1F4CC}",
|
|
439
|
+
paperclip: "\u{1F4CE}",
|
|
440
|
+
memo: "\u{1F4DD}",
|
|
441
|
+
link: "\u{1F517}"
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
id: "objects",
|
|
446
|
+
label: "Objects",
|
|
447
|
+
table: {
|
|
448
|
+
rocket: "\u{1F680}",
|
|
449
|
+
computer: "\u{1F4BB}",
|
|
450
|
+
iphone: "\u{1F4F1}",
|
|
451
|
+
email: "\u2709\uFE0F",
|
|
452
|
+
package: "\u{1F4E6}",
|
|
453
|
+
bug: "\u{1F41B}",
|
|
454
|
+
robot: "\u{1F916}",
|
|
455
|
+
ghost: "\u{1F47B}",
|
|
456
|
+
alien: "\u{1F47D}",
|
|
457
|
+
poop: "\u{1F4A9}",
|
|
458
|
+
skull: "\u{1F480}",
|
|
459
|
+
clown: "\u{1F921}",
|
|
460
|
+
coffee: "\u2615",
|
|
461
|
+
beer: "\u{1F37A}",
|
|
462
|
+
pizza: "\u{1F355}",
|
|
463
|
+
cake: "\u{1F382}",
|
|
464
|
+
trophy: "\u{1F3C6}",
|
|
465
|
+
medal: "\u{1F3C5}",
|
|
466
|
+
moneybag: "\u{1F4B0}",
|
|
467
|
+
chart: "\u{1F4C8}",
|
|
468
|
+
hammer: "\u{1F528}",
|
|
469
|
+
wrench: "\u{1F527}",
|
|
470
|
+
gear: "\u2699\uFE0F"
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: "marine",
|
|
475
|
+
label: "Marine & animals",
|
|
476
|
+
table: {
|
|
477
|
+
octopus: "\u{1F419}",
|
|
478
|
+
whale: "\u{1F433}",
|
|
479
|
+
fish: "\u{1F41F}",
|
|
480
|
+
tropical_fish: "\u{1F420}",
|
|
481
|
+
dolphin: "\u{1F42C}",
|
|
482
|
+
shark: "\u{1F988}",
|
|
483
|
+
crab: "\u{1F980}",
|
|
484
|
+
shell: "\u{1F41A}",
|
|
485
|
+
ocean: "\u{1F30A}",
|
|
486
|
+
anchor: "\u2693",
|
|
487
|
+
ship: "\u{1F6A2}",
|
|
488
|
+
dog: "\u{1F436}",
|
|
489
|
+
cat: "\u{1F431}",
|
|
490
|
+
unicorn: "\u{1F984}",
|
|
491
|
+
turtle: "\u{1F422}",
|
|
492
|
+
penguin: "\u{1F427}",
|
|
493
|
+
owl: "\u{1F989}",
|
|
494
|
+
snail: "\u{1F40C}"
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
id: "nature",
|
|
499
|
+
label: "Nature & weather",
|
|
500
|
+
table: {
|
|
501
|
+
sunny: "\u2600\uFE0F",
|
|
502
|
+
cloud: "\u2601\uFE0F",
|
|
503
|
+
rainbow: "\u{1F308}",
|
|
504
|
+
snowflake: "\u2744\uFE0F",
|
|
505
|
+
droplet: "\u{1F4A7}"
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
];
|
|
509
|
+
var EMOJI = Object.assign(
|
|
510
|
+
{},
|
|
511
|
+
...CATEGORY_TABLES.map((c) => c.table)
|
|
512
|
+
);
|
|
513
|
+
function dedupeByGlyph(entries) {
|
|
514
|
+
const seen = /* @__PURE__ */ new Set();
|
|
515
|
+
const out = [];
|
|
516
|
+
for (const e of entries) {
|
|
517
|
+
if (seen.has(e.glyph)) continue;
|
|
518
|
+
seen.add(e.glyph);
|
|
519
|
+
out.push(e);
|
|
520
|
+
}
|
|
521
|
+
return out;
|
|
522
|
+
}
|
|
523
|
+
var EMOJI_CATEGORIES = CATEGORY_TABLES.map((c) => ({
|
|
524
|
+
id: c.id,
|
|
525
|
+
label: c.label,
|
|
526
|
+
emoji: dedupeByGlyph(Object.entries(c.table).map(([code, glyph]) => ({ code, glyph })))
|
|
527
|
+
}));
|
|
528
|
+
var EMOJI_LIST = EMOJI_CATEGORIES.flatMap((c) => c.emoji);
|
|
529
|
+
var LIST = Object.entries(EMOJI).map(([code, glyph]) => ({ code, glyph }));
|
|
530
|
+
var TOKEN_RE = /(?:^|\s):([a-z0-9_+-]*)$/i;
|
|
531
|
+
function activeEmojiQuery(textBeforeCaret) {
|
|
532
|
+
const m = TOKEN_RE.exec(textBeforeCaret);
|
|
533
|
+
if (!m) return null;
|
|
534
|
+
const query = m[1];
|
|
535
|
+
return { query, start: textBeforeCaret.length - query.length - 1 };
|
|
536
|
+
}
|
|
537
|
+
function matchEmoji(query, limit = 8) {
|
|
538
|
+
const q = query.toLowerCase();
|
|
539
|
+
if (!q) return [];
|
|
540
|
+
const prefix = [];
|
|
541
|
+
const substr = [];
|
|
542
|
+
for (const e of LIST) {
|
|
543
|
+
const i = e.code.indexOf(q);
|
|
544
|
+
if (i === 0) prefix.push(e);
|
|
545
|
+
else if (i > 0) substr.push(e);
|
|
546
|
+
}
|
|
547
|
+
return [...prefix, ...substr].slice(0, limit);
|
|
548
|
+
}
|
|
549
|
+
function searchEmoji(query, limit = 64) {
|
|
550
|
+
const q = query.trim().toLowerCase();
|
|
551
|
+
if (!q) return [];
|
|
552
|
+
const prefix = [];
|
|
553
|
+
const substr = [];
|
|
554
|
+
for (const e of LIST) {
|
|
555
|
+
const i = e.code.indexOf(q);
|
|
556
|
+
if (i === 0) prefix.push(e);
|
|
557
|
+
else if (i > 0) substr.push(e);
|
|
558
|
+
}
|
|
559
|
+
return dedupeByGlyph([...prefix, ...substr]).slice(0, limit);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/legal.ts
|
|
563
|
+
var privacy = {
|
|
564
|
+
title: "Privacy Policy",
|
|
565
|
+
subtitle: "How OctoVault handles your data \u2014 or rather, how it doesn't.",
|
|
566
|
+
updated: "June 2026",
|
|
567
|
+
sections: [
|
|
568
|
+
{
|
|
569
|
+
title: "Overview",
|
|
570
|
+
paragraphs: [
|
|
571
|
+
"OctoVault is an end-to-end encrypted knowledge management app. Every page, block, board, and document is sealed on your device before it is transmitted or stored. Neither Drakkar Software nor any server operator can read your documents, notes, or boards.",
|
|
572
|
+
"This policy describes what minimal information is associated with your use of OctoVault and how it is handled."
|
|
573
|
+
]
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
title: "Your Identity and Keys",
|
|
577
|
+
paragraphs: [
|
|
578
|
+
"Your cryptographic identity (Ed25519 signing key, Kyber encryption key) is derived locally from a BIP-39 mnemonic seed phrase that you generate during onboarding. Your seed phrase never leaves your device and is never transmitted to any server.",
|
|
579
|
+
"Profile information such as your display name and workspace avatar is encrypted before storage. The server holds ciphertext; it has no way to associate a readable identity with your key material."
|
|
580
|
+
]
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
title: "Document Content",
|
|
584
|
+
paragraphs: [
|
|
585
|
+
"All documents, pages, blocks, and boards are encrypted client-side using per-space symmetric keys before being sent to the server. The server stores and delivers opaque encrypted blobs. It cannot read, index, or analyse your content.",
|
|
586
|
+
"Attachments (images, files) are encrypted with the same per-space keys. File content is never accessible to the server."
|
|
587
|
+
]
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
title: "Server Metadata",
|
|
591
|
+
paragraphs: [
|
|
592
|
+
"Basic connection metadata \u2014 document identifiers, timestamps, and the size of encrypted payloads \u2014 is necessarily visible to the server in order to route and deliver updates. This metadata does not include any document content.",
|
|
593
|
+
"OctoVault does not use analytics services, tracking pixels, advertising SDKs, or any third-party library that reports usage data to external parties."
|
|
594
|
+
]
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
title: "Self-Hosted Deployments",
|
|
598
|
+
paragraphs: [
|
|
599
|
+
"OctoVault is designed to be self-hosted. When you run your own Starfish server, you control all infrastructure, logs, and data retention policies.",
|
|
600
|
+
"If you use a server operated by a third party (a team admin, an employer, etc.), their privacy practices govern the infrastructure they control \u2014 including metadata, access logs, and backup retention. They cannot read your encrypted document content, but they do control server-level metadata.",
|
|
601
|
+
"Drakkar Software has no visibility into, and no responsibility for, the data handling practices of third-party server operators."
|
|
602
|
+
]
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
title: "Push Notifications",
|
|
606
|
+
paragraphs: [
|
|
607
|
+
'Push notifications, when enabled, are delivered through platform services (Apple APNs, Google FCM). By default OctoVault sends generic alerts ("New update") without decrypted content.',
|
|
608
|
+
"If you enable notification previews in Settings, a decrypted preview is generated on-device and passed to the platform notification service. Drakkar Software recommends leaving previews disabled on shared or untrusted devices."
|
|
609
|
+
]
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
title: "Data Retention",
|
|
613
|
+
paragraphs: [
|
|
614
|
+
`OctoVault is a persistent, append-only system by design. Documents, pages, and server-side records are retained per the server operator's infrastructure policy. There is no "delete account" feature \u2014 this is intentional: the append-only model is fundamental to the security and auditability of the system.`,
|
|
615
|
+
"You may remove OctoVault from your device at any time, which discards your local keys. Because documents are end-to-end encrypted, your local keys are the only way to decrypt stored ciphertext. Removing them renders your document history permanently inaccessible on that device \u2014 without recovery, by design."
|
|
616
|
+
]
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
title: "Children's Privacy",
|
|
620
|
+
paragraphs: [
|
|
621
|
+
"OctoVault is not directed at children under the age of 13. We do not knowingly process information relating to minors. If you believe a minor has created an account, please contact us at the address below."
|
|
622
|
+
]
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
title: "Changes to This Policy",
|
|
626
|
+
paragraphs: [
|
|
627
|
+
"We may update this policy to reflect changes in the software or applicable law. Material changes will be communicated through the application release notes or the official OctoVault repository.",
|
|
628
|
+
"Continued use of OctoVault after a policy update constitutes acceptance of the revised terms."
|
|
629
|
+
]
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
title: "Contact",
|
|
633
|
+
paragraphs: [
|
|
634
|
+
"Questions or concerns about this Privacy Policy? Reach us at privacy@drakkar.software or open an issue in the public OctoVault repository."
|
|
635
|
+
]
|
|
636
|
+
}
|
|
637
|
+
]
|
|
638
|
+
};
|
|
639
|
+
var terms = {
|
|
640
|
+
title: "Terms of Service",
|
|
641
|
+
subtitle: "What you can do with OctoVault, and what we ask of you.",
|
|
642
|
+
updated: "June 2026",
|
|
643
|
+
sections: [
|
|
644
|
+
{
|
|
645
|
+
title: "Acceptance",
|
|
646
|
+
paragraphs: [
|
|
647
|
+
"By installing, accessing, or using OctoVault you agree to be bound by these Terms of Service. If you do not agree with any part of these Terms, do not use the software.",
|
|
648
|
+
"These Terms apply to all users of OctoVault, whether connecting to the official hosted service, a self-hosted Starfish server, or running the software locally."
|
|
649
|
+
]
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
title: "Open-Source License",
|
|
653
|
+
paragraphs: [
|
|
654
|
+
"OctoVault client code and the OctoVault SDK are released under the MIT License. You may freely use, copy, modify, merge, publish, distribute, sublicense, and sell copies of the software, subject to the license conditions.",
|
|
655
|
+
"The Starfish server software is licensed separately; refer to its repository for applicable terms. Third-party dependencies carry their own licenses."
|
|
656
|
+
]
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
title: "Description of the Service",
|
|
660
|
+
paragraphs: [
|
|
661
|
+
"OctoVault provides end-to-end encrypted knowledge management software. Pages, boards, and documents are encrypted on your device; the server stores only ciphertext. You are solely responsible for the security of your seed phrase.",
|
|
662
|
+
"Lost or forgotten seed phrases cannot be recovered by Drakkar Software. There is no password reset, no backdoor, and no key escrow. Treat your seed phrase like a private key \u2014 back it up securely and never share it."
|
|
663
|
+
]
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
title: "Self-Hosting Responsibility",
|
|
667
|
+
paragraphs: [
|
|
668
|
+
"If you operate a Starfish server for yourself or others, you bear full responsibility for its security, availability, and legal compliance. This includes data protection obligations in your jurisdiction, secure configuration, and timely security updates.",
|
|
669
|
+
"You must not use the software to host, store, or distribute content that is illegal in your jurisdiction or in the jurisdiction of your users."
|
|
670
|
+
]
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
title: "Acceptable Use",
|
|
674
|
+
paragraphs: [
|
|
675
|
+
"You agree not to use OctoVault to: (a) violate any applicable local, national, or international law or regulation; (b) transmit or facilitate the transmission of malware, spam, or unsolicited communications; (c) harass, stalk, threaten, or harm any individual; (d) attempt to undermine the security or integrity of OctoVault or any Starfish server you do not operate; or (e) resell or white-label the service without prior written permission.",
|
|
676
|
+
"We reserve the right to terminate access to any officially operated service for users who violate these terms."
|
|
677
|
+
]
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
title: "No Warranty",
|
|
681
|
+
paragraphs: [
|
|
682
|
+
'OctoVault is provided "as is" and "as available", without warranty of any kind, express or implied. We do not warrant uninterrupted or error-free operation, security against all attack vectors, fitness for a particular purpose, or the permanent preservation of any data.',
|
|
683
|
+
"Cryptographic security relies on correct implementation of published open standards (Ed25519, Kyber, AES-GCM). We make reasonable efforts to maintain correctness and to address security disclosures promptly, but no software is free of bugs."
|
|
684
|
+
]
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
title: "Limitation of Liability",
|
|
688
|
+
paragraphs: [
|
|
689
|
+
"To the maximum extent permitted by applicable law, Drakkar Software and its contributors shall not be liable for any indirect, incidental, special, consequential, or punitive damages, including loss of data, arising from your use of or inability to use OctoVault.",
|
|
690
|
+
"Our aggregate liability for any claim arising from these Terms shall not exceed one hundred euros (\u20AC100)."
|
|
691
|
+
]
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
title: "Governing Law and Disputes",
|
|
695
|
+
paragraphs: [
|
|
696
|
+
"These Terms are governed by the laws of France, without regard to conflict-of-law provisions. Any dispute arising under these Terms shall be submitted to the exclusive jurisdiction of the competent courts of Paris, France."
|
|
697
|
+
]
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
title: "Changes to These Terms",
|
|
701
|
+
paragraphs: [
|
|
702
|
+
"We may revise these Terms from time to time. The most current version will always be available in the application and in the official OctoVault repository. Material changes will be flagged in the release notes.",
|
|
703
|
+
"Continued use of OctoVault after a revision becomes effective constitutes your acceptance of the updated Terms."
|
|
704
|
+
]
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
title: "Contact",
|
|
708
|
+
paragraphs: [
|
|
709
|
+
"Legal questions? Contact us at legal@drakkar.software or open an issue in the public OctoVault repository at github.com/drakkar-software/octovault."
|
|
710
|
+
]
|
|
711
|
+
}
|
|
712
|
+
]
|
|
713
|
+
};
|
|
714
|
+
var _store = createPrefsStore({
|
|
715
|
+
...mutePrefsConfig("octovault"),
|
|
716
|
+
client: (s) => s.accountClient
|
|
717
|
+
});
|
|
718
|
+
var getMutePrefs = () => _store.get();
|
|
719
|
+
var isRoomMuted = (roomId) => isMuteActive(_store.get().nodes[roomId]);
|
|
720
|
+
var isSpaceMuted = (spaceId) => isMuteActive(_store.get().spaces[spaceId]);
|
|
721
|
+
var isMuted = (roomId, spaceId) => isRoomMuted(roomId) || isSpaceMuted(spaceId);
|
|
722
|
+
var subscribeMutes = (listener) => _store.subscribe(listener);
|
|
723
|
+
var hydrateMutes = (userId, serverPrefs) => _store.hydrate(userId, serverPrefs);
|
|
724
|
+
var resetMutes = () => _store.reset();
|
|
725
|
+
var loadMutesFromKv = (userId) => _store.loadFromKv(userId);
|
|
726
|
+
var setRoomMute = (session, roomId, muted) => _store.mutate(session, (cur) => applyMute(cur, "nodes", roomId, muted));
|
|
727
|
+
var setSpaceMute = (session, spaceId, muted) => _store.mutate(session, (cur) => applyMute(cur, "spaces", spaceId, muted));
|
|
728
|
+
var _store2 = createPrefsStore({
|
|
729
|
+
...readPrefsConfig("octovault"),
|
|
730
|
+
client: (s) => s.accountClient
|
|
731
|
+
});
|
|
732
|
+
var getReadPrefs = () => _store2.get();
|
|
733
|
+
var getRoomReadAt = (roomId) => _store2.get().nodes[roomId];
|
|
734
|
+
var subscribeReads = (listener) => _store2.subscribe(listener);
|
|
735
|
+
var loadReadMarksFromKv = (userId) => _store2.loadFromKv(userId);
|
|
736
|
+
var hydrateReads = (userId, serverPrefs) => _store2.hydrate(userId, serverPrefs);
|
|
737
|
+
var resetReads = () => _store2.reset();
|
|
738
|
+
var flushReadsNow = () => _store2.flushNow();
|
|
739
|
+
var setRoomReadAt = (session, roomId, ts) => _store2.mutate(
|
|
740
|
+
session,
|
|
741
|
+
(cur) => ts > (cur.nodes[roomId] ?? 0) ? { nodes: { ...cur.nodes, [roomId]: ts } } : null
|
|
742
|
+
);
|
|
743
|
+
var updateQuickReactionsDoc = (session, mutator) => updateSpacesExtraField(session.spacesRegistryClient, session, "quickReactions", mutator);
|
|
744
|
+
var QUICK_REACTION_COUNT = 6;
|
|
745
|
+
var DEFAULT_QUICK_REACTIONS = ["\u{1F44D}", "\u{1F600}", "\u{1F602}", "\u2764\uFE0F", "\u{1F389}", "\u{1F419}"];
|
|
746
|
+
var snapshot = DEFAULT_QUICK_REACTIONS;
|
|
747
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
748
|
+
var pending = 0;
|
|
749
|
+
function getQuickReactions() {
|
|
750
|
+
return snapshot;
|
|
751
|
+
}
|
|
752
|
+
function subscribeQuickReactions(listener) {
|
|
753
|
+
listeners.add(listener);
|
|
754
|
+
return () => {
|
|
755
|
+
listeners.delete(listener);
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function setQuickReactions(next) {
|
|
759
|
+
snapshot = next;
|
|
760
|
+
for (const listener of listeners) listener();
|
|
761
|
+
}
|
|
762
|
+
function resetQuickReactions() {
|
|
763
|
+
setQuickReactions(DEFAULT_QUICK_REACTIONS);
|
|
764
|
+
}
|
|
765
|
+
function coerce(raw) {
|
|
766
|
+
if (!Array.isArray(raw)) return DEFAULT_QUICK_REACTIONS;
|
|
767
|
+
return DEFAULT_QUICK_REACTIONS.map((fallback, i) => {
|
|
768
|
+
const v = raw[i];
|
|
769
|
+
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
function arraysEqual(a, b) {
|
|
773
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
774
|
+
}
|
|
775
|
+
function hydrateQuickReactions(serverPalette) {
|
|
776
|
+
if (pending > 0) return;
|
|
777
|
+
const next = coerce(serverPalette);
|
|
778
|
+
if (arraysEqual(snapshot, next)) return;
|
|
779
|
+
setQuickReactions(next);
|
|
780
|
+
}
|
|
781
|
+
async function saveQuickReactions(session, emojis) {
|
|
782
|
+
const next = coerce(emojis);
|
|
783
|
+
setQuickReactions(next);
|
|
784
|
+
pending++;
|
|
785
|
+
try {
|
|
786
|
+
await updateQuickReactionsDoc(session, () => next);
|
|
787
|
+
} catch (err) {
|
|
788
|
+
console.error("[OctoVault] quick reactions: failed to sync palette change", err);
|
|
789
|
+
} finally {
|
|
790
|
+
pending--;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/ai-settings.ts
|
|
795
|
+
var DEFAULT_AI_SETTINGS = {
|
|
796
|
+
enabled: false
|
|
797
|
+
};
|
|
798
|
+
var settingsKey = (userId) => `octovault.ai.${userId}`;
|
|
799
|
+
var snapshot2 = DEFAULT_AI_SETTINGS;
|
|
800
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
801
|
+
function getAiSettings() {
|
|
802
|
+
return snapshot2;
|
|
803
|
+
}
|
|
804
|
+
function subscribeAiSettings(listener) {
|
|
805
|
+
listeners2.add(listener);
|
|
806
|
+
return () => {
|
|
807
|
+
listeners2.delete(listener);
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function setAiSettings(next) {
|
|
811
|
+
snapshot2 = next;
|
|
812
|
+
for (const listener of listeners2) listener();
|
|
813
|
+
}
|
|
814
|
+
function resetAiSettings() {
|
|
815
|
+
setAiSettings(DEFAULT_AI_SETTINGS);
|
|
816
|
+
}
|
|
817
|
+
function coerce2(raw) {
|
|
818
|
+
if (!raw || typeof raw !== "object") return DEFAULT_AI_SETTINGS;
|
|
819
|
+
const r = raw;
|
|
820
|
+
return {
|
|
821
|
+
enabled: typeof r.enabled === "boolean" ? r.enabled : DEFAULT_AI_SETTINGS.enabled
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
async function loadAiSettings(userId) {
|
|
825
|
+
const raw = await kvGet2(settingsKey(userId));
|
|
826
|
+
if (!raw) return DEFAULT_AI_SETTINGS;
|
|
827
|
+
try {
|
|
828
|
+
return coerce2(JSON.parse(raw));
|
|
829
|
+
} catch {
|
|
830
|
+
return DEFAULT_AI_SETTINGS;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async function saveAiSettings(userId, patch) {
|
|
834
|
+
const next = { ...snapshot2, ...patch };
|
|
835
|
+
setAiSettings(next);
|
|
836
|
+
await kvSet2(settingsKey(userId), JSON.stringify(next));
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/spaces-prime.ts
|
|
840
|
+
var primed = null;
|
|
841
|
+
function primeSpaces(userId, spaces) {
|
|
842
|
+
primed = { userId, spaces, at: Date.now() };
|
|
843
|
+
}
|
|
844
|
+
function consumePrimedSpaces(userId) {
|
|
845
|
+
if (!primed || primed.userId !== userId || Date.now() - primed.at > 1e4) return null;
|
|
846
|
+
const { spaces } = primed;
|
|
847
|
+
primed = null;
|
|
848
|
+
return spaces;
|
|
849
|
+
}
|
|
850
|
+
function clearPrimedSpaces() {
|
|
851
|
+
primed = null;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/blocks.ts
|
|
855
|
+
var BLOCK_SECTIONS = [
|
|
856
|
+
{ id: "basic", label: "Basic" },
|
|
857
|
+
{ id: "list", label: "Lists" },
|
|
858
|
+
{ id: "other", label: "More" }
|
|
859
|
+
];
|
|
860
|
+
var BLOCK_TYPES = [
|
|
861
|
+
{ type: "paragraph", label: "Text", icon: "text", section: "basic", keywords: ["plain", "p"], variant: "body", mono: false, multiline: true, placeholder: "Type '/' for commands" },
|
|
862
|
+
{ type: "page", label: "Page", icon: "page", section: "basic", keywords: ["sub", "child", "doc"], variant: "body", mono: false, multiline: false, placeholder: "Untitled" },
|
|
863
|
+
{ type: "heading", label: "Heading 1", icon: "h1", section: "basic", keywords: ["h1", "title", "#"], variant: "title", mono: false, multiline: false, placeholder: "Heading 1", mdPrefixes: ["# "] },
|
|
864
|
+
{ type: "subheading", label: "Heading 2", icon: "h2", section: "basic", keywords: ["h2", "h3", "sub", "##"], variant: "heading", mono: false, multiline: false, placeholder: "Heading 2", mdPrefixes: ["## ", "### "] },
|
|
865
|
+
{ type: "todo", label: "To-do list", icon: "square", section: "list", keywords: ["check", "checkbox", "task", "[]"], variant: "body", mono: false, multiline: false, placeholder: "To-do", mdPrefixes: ["[] ", "[ ] "] },
|
|
866
|
+
{ type: "bulleted", label: "Bulleted list", icon: "list-bullet", section: "list", keywords: ["ul", "bullet", "unordered", "-"], variant: "body", mono: false, multiline: false, placeholder: "List item", mdPrefixes: ["- ", "* "] },
|
|
867
|
+
{ type: "numbered", label: "Numbered list", icon: "list-number", section: "list", keywords: ["ol", "ordered", "1."], variant: "body", mono: false, multiline: false, placeholder: "List item" },
|
|
868
|
+
{ type: "toggle", label: "Toggle", icon: "toggle-chev", section: "list", keywords: ["collapse", "expand", "details"], variant: "body", mono: false, multiline: false, placeholder: "Toggle" },
|
|
869
|
+
{ type: "quote", label: "Quote", icon: "quote-mark", section: "other", keywords: ["blockquote", "cite", ">"], variant: "body", mono: false, multiline: true, placeholder: "Quote", mdPrefixes: ["> "] },
|
|
870
|
+
{ type: "code", label: "Code", icon: "code-block", section: "other", keywords: ["snippet", "mono", "```"], variant: "callout", mono: true, multiline: true, placeholder: "Code", mdPrefixes: ["``` ", "```"] },
|
|
871
|
+
{ type: "divider", label: "Divider", icon: "minus", section: "other", keywords: ["rule", "hr", "separator", "---"], variant: "body", mono: false, multiline: false, placeholder: "", mdPrefixes: ["--- ", "---"] },
|
|
872
|
+
{ type: "image", label: "Image", icon: "image", section: "other", keywords: ["picture", "photo", "img", "upload"], variant: "body", mono: false, multiline: false, placeholder: "" },
|
|
873
|
+
{ type: "file", label: "File", icon: "paperclip", section: "other", keywords: ["attachment", "upload", "pdf", "doc", "attach"], variant: "body", mono: false, multiline: false, placeholder: "" },
|
|
874
|
+
{ type: "bookmark", label: "Web bookmark", icon: "link", section: "other", keywords: ["url", "link", "bookmark", "embed", "web"], variant: "body", mono: false, multiline: false, placeholder: "Paste a link\u2026" },
|
|
875
|
+
{ type: "table", label: "Table", icon: "table", section: "other", keywords: ["table", "grid", "database", "db", "rows", "columns"], variant: "body", mono: false, multiline: false, placeholder: "" }
|
|
876
|
+
];
|
|
877
|
+
var BY_TYPE = BLOCK_TYPES.reduce(
|
|
878
|
+
(acc, def) => {
|
|
879
|
+
acc[def.type] = def;
|
|
880
|
+
return acc;
|
|
881
|
+
},
|
|
882
|
+
{}
|
|
883
|
+
);
|
|
884
|
+
function blockDef(type) {
|
|
885
|
+
return BY_TYPE[type] ?? BY_TYPE.paragraph;
|
|
886
|
+
}
|
|
887
|
+
var labelFor = (type) => blockDef(type).label;
|
|
888
|
+
var iconFor = (type) => blockDef(type).icon;
|
|
889
|
+
var variantFor = (type) => blockDef(type).variant;
|
|
890
|
+
var monoFor = (type) => blockDef(type).mono;
|
|
891
|
+
var isMultiline = (type) => blockDef(type).multiline;
|
|
892
|
+
var placeholderFor = (type) => blockDef(type).placeholder;
|
|
893
|
+
var CONTINUES = /* @__PURE__ */ new Set(["todo", "bulleted", "numbered", "toggle"]);
|
|
894
|
+
function continuationType(type) {
|
|
895
|
+
return CONTINUES.has(type) ? type : "paragraph";
|
|
896
|
+
}
|
|
897
|
+
function endsListOnEmptyEnter(type) {
|
|
898
|
+
return CONTINUES.has(type);
|
|
899
|
+
}
|
|
900
|
+
var MD_PREFIXES = [
|
|
901
|
+
...BLOCK_TYPES.flatMap((def) => (def.mdPrefixes ?? []).map((prefix) => ({ prefix, type: def.type }))),
|
|
902
|
+
{ prefix: "[x] ", type: "todo", checked: true },
|
|
903
|
+
{ prefix: "[X] ", type: "todo", checked: true }
|
|
904
|
+
].sort((a, b) => b.prefix.length - a.prefix.length);
|
|
905
|
+
var NUMBERED_MD = /^\d+[.)] /;
|
|
906
|
+
function mdShortcut(text) {
|
|
907
|
+
for (const m of MD_PREFIXES) {
|
|
908
|
+
if (text.startsWith(m.prefix)) return { type: m.type, rest: text.slice(m.prefix.length), checked: m.checked };
|
|
909
|
+
}
|
|
910
|
+
const num = NUMBERED_MD.exec(text);
|
|
911
|
+
if (num) return { type: "numbered", rest: text.slice(num[0].length) };
|
|
912
|
+
return void 0;
|
|
913
|
+
}
|
|
914
|
+
function filterBlockTypes(query) {
|
|
915
|
+
const q = query.trim().toLowerCase();
|
|
916
|
+
if (!q) return BLOCK_TYPES;
|
|
917
|
+
return BLOCK_TYPES.filter(
|
|
918
|
+
(def) => def.label.toLowerCase().includes(q) || def.keywords.some((k) => k.toLowerCase().startsWith(q))
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
function listOrdinals(blocks) {
|
|
922
|
+
const ordinals = /* @__PURE__ */ new Map();
|
|
923
|
+
const counters = [];
|
|
924
|
+
for (const b of blocks) {
|
|
925
|
+
const ind = b.indent ?? 0;
|
|
926
|
+
if (b.type === "numbered") {
|
|
927
|
+
counters[ind] = (counters[ind] ?? 0) + 1;
|
|
928
|
+
ordinals.set(b.id, counters[ind]);
|
|
929
|
+
counters.length = ind + 1;
|
|
930
|
+
} else {
|
|
931
|
+
counters.length = ind;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return ordinals;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// src/object-content-model.ts
|
|
938
|
+
var PAGE_SCHEMA = {
|
|
939
|
+
collections: [
|
|
940
|
+
{
|
|
941
|
+
listKey: "order",
|
|
942
|
+
fields: [
|
|
943
|
+
{ key: "text", kind: "charRga" },
|
|
944
|
+
{ key: "type", kind: "lww" },
|
|
945
|
+
{ key: "checked", kind: "lww" },
|
|
946
|
+
{ key: "indent", kind: "lww" },
|
|
947
|
+
{ key: "collapsed", kind: "lww" },
|
|
948
|
+
{ key: "ref", kind: "lww" }
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
]
|
|
952
|
+
};
|
|
953
|
+
var BOARD_SCHEMA = {
|
|
954
|
+
collections: [
|
|
955
|
+
{
|
|
956
|
+
listKey: "columns",
|
|
957
|
+
fields: [
|
|
958
|
+
{ key: "coltitle", kind: "lww" },
|
|
959
|
+
{ key: "coldone", kind: "lww" }
|
|
960
|
+
]
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
listKey: "tasks",
|
|
964
|
+
fields: [
|
|
965
|
+
{ key: "task", kind: "lww" }
|
|
966
|
+
// task:{id}:col / :order / :status / :title / :notes
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
]
|
|
970
|
+
};
|
|
971
|
+
var CALENDAR_SCHEMA = {
|
|
972
|
+
collections: [
|
|
973
|
+
{
|
|
974
|
+
listKey: "events",
|
|
975
|
+
fields: [
|
|
976
|
+
{ key: "etitle", kind: "charRga" },
|
|
977
|
+
{ key: "estart", kind: "lww" },
|
|
978
|
+
{ key: "eend", kind: "lww" },
|
|
979
|
+
{ key: "eallDay", kind: "lww" },
|
|
980
|
+
{ key: "ecolor", kind: "lww" },
|
|
981
|
+
{ key: "edesc", kind: "lww" }
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
]
|
|
985
|
+
};
|
|
986
|
+
var FORM_SCHEMA = {
|
|
987
|
+
collections: [
|
|
988
|
+
{
|
|
989
|
+
listKey: "fields",
|
|
990
|
+
fields: [
|
|
991
|
+
{ key: "flabel", kind: "charRga" },
|
|
992
|
+
{ key: "fkind", kind: "lww" },
|
|
993
|
+
{ key: "frequired", kind: "lww" },
|
|
994
|
+
{ key: "foptions", kind: "lww" }
|
|
995
|
+
]
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
listKey: "responses",
|
|
999
|
+
fields: [
|
|
1000
|
+
{ key: "rsubmittedAt", kind: "lww" },
|
|
1001
|
+
{ key: "rsubmitter", kind: "lww" },
|
|
1002
|
+
{ key: "rdata", kind: "lww" }
|
|
1003
|
+
]
|
|
1004
|
+
}
|
|
1005
|
+
]
|
|
1006
|
+
};
|
|
1007
|
+
var FEEDBACK_SCHEMA = {
|
|
1008
|
+
collections: [
|
|
1009
|
+
{
|
|
1010
|
+
listKey: "items",
|
|
1011
|
+
fields: [
|
|
1012
|
+
{ key: "ititle", kind: "charRga" },
|
|
1013
|
+
{ key: "istatus", kind: "lww" },
|
|
1014
|
+
{ key: "idesc", kind: "lww" },
|
|
1015
|
+
// Per-voter LWW toggle registers: `ivote:{itemId}:{userId}` (boolean true).
|
|
1016
|
+
// Cross-voter concurrent votes commute; same-voter vote vs unvote is LWW.
|
|
1017
|
+
{ key: "ivote", kind: "lww" }
|
|
1018
|
+
]
|
|
1019
|
+
}
|
|
1020
|
+
]
|
|
1021
|
+
};
|
|
1022
|
+
function schemaFor(editorKind) {
|
|
1023
|
+
switch (editorKind) {
|
|
1024
|
+
case "page":
|
|
1025
|
+
return PAGE_SCHEMA;
|
|
1026
|
+
case "board":
|
|
1027
|
+
return BOARD_SCHEMA;
|
|
1028
|
+
case "calendar":
|
|
1029
|
+
return CALENDAR_SCHEMA;
|
|
1030
|
+
case "form":
|
|
1031
|
+
return FORM_SCHEMA;
|
|
1032
|
+
case "feedback":
|
|
1033
|
+
return FEEDBACK_SCHEMA;
|
|
1034
|
+
default:
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/wal-helpers.ts
|
|
1040
|
+
function strArray(value) {
|
|
1041
|
+
return Array.isArray(value) ? value.filter((x) => typeof x === "string") : [];
|
|
1042
|
+
}
|
|
1043
|
+
function rgaList(doc, key) {
|
|
1044
|
+
return strArray(doc.materialize()[key]);
|
|
1045
|
+
}
|
|
1046
|
+
function dedupRgaList(value) {
|
|
1047
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1048
|
+
const out = [];
|
|
1049
|
+
if (Array.isArray(value)) {
|
|
1050
|
+
for (const raw of value) {
|
|
1051
|
+
if (typeof raw !== "string" || seen.has(raw)) continue;
|
|
1052
|
+
seen.add(raw);
|
|
1053
|
+
out.push(raw);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return out;
|
|
1057
|
+
}
|
|
1058
|
+
var asStr = (v) => typeof v === "string" ? v : "";
|
|
1059
|
+
var asNum = (v, fallback) => typeof v === "number" ? v : fallback;
|
|
1060
|
+
var asBool = (v) => typeof v === "boolean" ? v : void 0;
|
|
1061
|
+
var asStrOrNull = (v) => typeof v === "string" ? v : null;
|
|
1062
|
+
var asObj = (v) => v != null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
1063
|
+
function deleteFieldsByPrefix(doc, prefix) {
|
|
1064
|
+
for (const key of Object.keys(doc.materialize())) {
|
|
1065
|
+
if (key.startsWith(prefix)) doc.deleteField(key);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/page-content.ts
|
|
1070
|
+
var REF_BLOCK_TYPES = /* @__PURE__ */ new Set(["page", "image", "file"]);
|
|
1071
|
+
var ORDER = "order";
|
|
1072
|
+
var typeReg = (id) => `type:${id}`;
|
|
1073
|
+
var checkedReg = (id) => `checked:${id}`;
|
|
1074
|
+
var indentReg = (id) => `indent:${id}`;
|
|
1075
|
+
var collapsedReg = (id) => `collapsed:${id}`;
|
|
1076
|
+
var refReg = (id) => `ref:${id}`;
|
|
1077
|
+
var bookmarkReg = (id) => `bookmark:${id}`;
|
|
1078
|
+
var textList = (id) => `text:${id}`;
|
|
1079
|
+
function readBlocks(doc) {
|
|
1080
|
+
const state = doc.materialize();
|
|
1081
|
+
const blocks = [];
|
|
1082
|
+
for (const raw of dedupRgaList(state[ORDER])) {
|
|
1083
|
+
const type = state[typeReg(raw)] ?? "paragraph";
|
|
1084
|
+
const indentVal = state[indentReg(raw)];
|
|
1085
|
+
const refVal = state[refReg(raw)];
|
|
1086
|
+
blocks.push({
|
|
1087
|
+
id: raw,
|
|
1088
|
+
type,
|
|
1089
|
+
text: doc.text(textList(raw)),
|
|
1090
|
+
checked: asBool(state[checkedReg(raw)]),
|
|
1091
|
+
// Clamp a concurrent-merge artifact (negative indent) rather than render off-canvas.
|
|
1092
|
+
indent: typeof indentVal === "number" && indentVal > 0 ? Math.floor(indentVal) : void 0,
|
|
1093
|
+
collapsed: asBool(state[collapsedReg(raw)]),
|
|
1094
|
+
ref: typeof refVal === "string" ? refVal : void 0,
|
|
1095
|
+
bookmark: asObj(state[bookmarkReg(raw)])
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
return blocks;
|
|
1099
|
+
}
|
|
1100
|
+
function visibleBlocks(blocks) {
|
|
1101
|
+
const out = [];
|
|
1102
|
+
let hideDeeperThan = null;
|
|
1103
|
+
for (const b of blocks) {
|
|
1104
|
+
const ind = b.indent ?? 0;
|
|
1105
|
+
if (hideDeeperThan !== null && ind > hideDeeperThan) continue;
|
|
1106
|
+
hideDeeperThan = null;
|
|
1107
|
+
out.push(b);
|
|
1108
|
+
if (b.type === "toggle" && b.collapsed) hideDeeperThan = ind;
|
|
1109
|
+
}
|
|
1110
|
+
return out;
|
|
1111
|
+
}
|
|
1112
|
+
function insertBlock(doc, index, init = {}) {
|
|
1113
|
+
const id = randomId();
|
|
1114
|
+
doc.setField(typeReg(id), init.type ?? "paragraph");
|
|
1115
|
+
if (init.text) doc.setText(textList(id), init.text);
|
|
1116
|
+
if (init.checked !== void 0) doc.setField(checkedReg(id), init.checked);
|
|
1117
|
+
if (init.indent) doc.setField(indentReg(id), init.indent);
|
|
1118
|
+
if (init.ref) doc.setField(refReg(id), init.ref);
|
|
1119
|
+
const order = rgaList(doc, ORDER);
|
|
1120
|
+
const at = Math.max(0, Math.min(index, order.length));
|
|
1121
|
+
doc.setList(ORDER, [...order.slice(0, at), id, ...order.slice(at)]);
|
|
1122
|
+
return id;
|
|
1123
|
+
}
|
|
1124
|
+
function appendBlock(doc, init = {}) {
|
|
1125
|
+
return insertBlock(doc, rgaList(doc, ORDER).length, init);
|
|
1126
|
+
}
|
|
1127
|
+
function setBlockText(doc, id, text) {
|
|
1128
|
+
doc.setText(textList(id), text);
|
|
1129
|
+
}
|
|
1130
|
+
function setBlockType(doc, id, type) {
|
|
1131
|
+
doc.setField(typeReg(id), type);
|
|
1132
|
+
}
|
|
1133
|
+
function setBlockChecked(doc, id, checked) {
|
|
1134
|
+
doc.setField(checkedReg(id), checked);
|
|
1135
|
+
}
|
|
1136
|
+
function setBlockIndent(doc, id, indent) {
|
|
1137
|
+
const v = Math.max(0, Math.floor(indent));
|
|
1138
|
+
if (v === 0) doc.deleteField(indentReg(id));
|
|
1139
|
+
else doc.setField(indentReg(id), v);
|
|
1140
|
+
}
|
|
1141
|
+
function setBlockCollapsed(doc, id, collapsed) {
|
|
1142
|
+
doc.setField(collapsedReg(id), collapsed);
|
|
1143
|
+
}
|
|
1144
|
+
function setBlockRef(doc, id, ref) {
|
|
1145
|
+
doc.setField(refReg(id), ref);
|
|
1146
|
+
}
|
|
1147
|
+
function removeBlock(doc, id) {
|
|
1148
|
+
doc.setList(ORDER, rgaList(doc, ORDER).filter((x) => x !== id));
|
|
1149
|
+
doc.setText(textList(id), "");
|
|
1150
|
+
doc.deleteField(typeReg(id));
|
|
1151
|
+
doc.deleteField(checkedReg(id));
|
|
1152
|
+
doc.deleteField(indentReg(id));
|
|
1153
|
+
doc.deleteField(collapsedReg(id));
|
|
1154
|
+
doc.deleteField(refReg(id));
|
|
1155
|
+
doc.deleteField(bookmarkReg(id));
|
|
1156
|
+
}
|
|
1157
|
+
function moveBlock(doc, id, toIndex) {
|
|
1158
|
+
const order = rgaList(doc, ORDER).filter((x) => x !== id);
|
|
1159
|
+
const at = Math.max(0, Math.min(toIndex, order.length));
|
|
1160
|
+
order.splice(at, 0, id);
|
|
1161
|
+
doc.setList(ORDER, order);
|
|
1162
|
+
}
|
|
1163
|
+
function splitBlock(doc, id, head, init = {}) {
|
|
1164
|
+
const order = rgaList(doc, ORDER);
|
|
1165
|
+
const at = order.indexOf(id);
|
|
1166
|
+
if (at < 0) return null;
|
|
1167
|
+
doc.setText(textList(id), head);
|
|
1168
|
+
return insertBlock(doc, at + 1, init);
|
|
1169
|
+
}
|
|
1170
|
+
function mergeBlockIntoPrevious(doc, id, textOverride) {
|
|
1171
|
+
const order = rgaList(doc, ORDER);
|
|
1172
|
+
const at = order.indexOf(id);
|
|
1173
|
+
if (at <= 0) return null;
|
|
1174
|
+
const prevId = order[at - 1];
|
|
1175
|
+
const prevText = doc.text(textList(prevId));
|
|
1176
|
+
const tail = textOverride ?? doc.text(textList(id));
|
|
1177
|
+
if (tail) doc.setText(textList(prevId), prevText + tail);
|
|
1178
|
+
removeBlock(doc, id);
|
|
1179
|
+
return { prevId, offset: prevText.length };
|
|
1180
|
+
}
|
|
1181
|
+
function duplicateBlock(doc, id) {
|
|
1182
|
+
const order = rgaList(doc, ORDER);
|
|
1183
|
+
const at = order.indexOf(id);
|
|
1184
|
+
if (at < 0) return null;
|
|
1185
|
+
const state = doc.materialize();
|
|
1186
|
+
const checkedVal = state[checkedReg(id)];
|
|
1187
|
+
const indentVal = state[indentReg(id)];
|
|
1188
|
+
const refVal = state[refReg(id)];
|
|
1189
|
+
const newId = insertBlock(doc, at + 1, {
|
|
1190
|
+
type: state[typeReg(id)] ?? "paragraph",
|
|
1191
|
+
text: doc.text(textList(id)),
|
|
1192
|
+
checked: typeof checkedVal === "boolean" ? checkedVal : void 0,
|
|
1193
|
+
indent: typeof indentVal === "number" ? indentVal : void 0,
|
|
1194
|
+
ref: typeof refVal === "string" ? refVal : void 0
|
|
1195
|
+
});
|
|
1196
|
+
const bkVal = state[bookmarkReg(id)];
|
|
1197
|
+
if (bkVal && typeof bkVal === "object" && !Array.isArray(bkVal)) {
|
|
1198
|
+
doc.setField(bookmarkReg(newId), bkVal);
|
|
1199
|
+
}
|
|
1200
|
+
return newId;
|
|
1201
|
+
}
|
|
1202
|
+
function restoreBlock(doc, index, block) {
|
|
1203
|
+
doc.setField(typeReg(block.id), block.type);
|
|
1204
|
+
if (block.text) doc.setText(textList(block.id), block.text);
|
|
1205
|
+
if (block.checked !== void 0) doc.setField(checkedReg(block.id), block.checked);
|
|
1206
|
+
if (block.indent) doc.setField(indentReg(block.id), block.indent);
|
|
1207
|
+
if (block.collapsed !== void 0) doc.setField(collapsedReg(block.id), block.collapsed);
|
|
1208
|
+
if (block.ref) doc.setField(refReg(block.id), block.ref);
|
|
1209
|
+
if (block.bookmark) doc.setField(bookmarkReg(block.id), block.bookmark);
|
|
1210
|
+
const order = rgaList(doc, ORDER).filter((x) => x !== block.id);
|
|
1211
|
+
const at = Math.max(0, Math.min(index, order.length));
|
|
1212
|
+
doc.setList(ORDER, [...order.slice(0, at), block.id, ...order.slice(at)]);
|
|
1213
|
+
}
|
|
1214
|
+
function setBlockBookmark(doc, id, meta) {
|
|
1215
|
+
doc.setField(bookmarkReg(id), meta);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/comments-content.ts
|
|
1219
|
+
var COMMENTS_PREFIX = "comments:";
|
|
1220
|
+
var REACT_PREFIX = "react:";
|
|
1221
|
+
var commentsList = (blockId) => `${COMMENTS_PREFIX}${blockId}`;
|
|
1222
|
+
var authorReg = (commentId) => `author:${commentId}`;
|
|
1223
|
+
var createdReg = (commentId) => `created:${commentId}`;
|
|
1224
|
+
var bodyList = (commentId) => `body:${commentId}`;
|
|
1225
|
+
var resolvedReg = (blockId) => `resolved:${blockId}`;
|
|
1226
|
+
var reactReg = (commentId, userId) => `${REACT_PREFIX}${commentId}:${userId}`;
|
|
1227
|
+
function commentIds(state, blockId) {
|
|
1228
|
+
return dedupRgaList(state[commentsList(blockId)]);
|
|
1229
|
+
}
|
|
1230
|
+
function readReactions(state, commentId) {
|
|
1231
|
+
const prefix = `${REACT_PREFIX}${commentId}:`;
|
|
1232
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1233
|
+
for (const key of Object.keys(state)) {
|
|
1234
|
+
if (!key.startsWith(prefix)) continue;
|
|
1235
|
+
const userId = key.slice(prefix.length);
|
|
1236
|
+
for (const emoji of strArray(state[key])) {
|
|
1237
|
+
const arr = groups.get(emoji) ?? [];
|
|
1238
|
+
if (!arr.includes(userId)) arr.push(userId);
|
|
1239
|
+
groups.set(emoji, arr);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
const out = [];
|
|
1243
|
+
for (const [emoji, userIds] of groups) {
|
|
1244
|
+
if (userIds.length > 0) out.push({ emoji, userIds: userIds.sort() });
|
|
1245
|
+
}
|
|
1246
|
+
return out;
|
|
1247
|
+
}
|
|
1248
|
+
function readComment(doc, state, id) {
|
|
1249
|
+
const createdVal = state[createdReg(id)];
|
|
1250
|
+
const authorVal = state[authorReg(id)];
|
|
1251
|
+
return {
|
|
1252
|
+
id,
|
|
1253
|
+
author: typeof authorVal === "string" ? authorVal : "",
|
|
1254
|
+
createdAt: typeof createdVal === "number" ? createdVal : 0,
|
|
1255
|
+
body: doc.text(bodyList(id)),
|
|
1256
|
+
reactions: readReactions(state, id)
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
function readThread(doc, blockId) {
|
|
1260
|
+
const state = doc.materialize();
|
|
1261
|
+
return {
|
|
1262
|
+
blockId,
|
|
1263
|
+
resolved: state[resolvedReg(blockId)] === true,
|
|
1264
|
+
comments: commentIds(state, blockId).map((id) => readComment(doc, state, id))
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function readThreads(doc) {
|
|
1268
|
+
const state = doc.materialize();
|
|
1269
|
+
const threads = /* @__PURE__ */ new Map();
|
|
1270
|
+
for (const key of Object.keys(state)) {
|
|
1271
|
+
if (!key.startsWith(COMMENTS_PREFIX)) continue;
|
|
1272
|
+
const blockId = key.slice(COMMENTS_PREFIX.length);
|
|
1273
|
+
const liveIds = commentIds(state, blockId);
|
|
1274
|
+
if (liveIds.length === 0) continue;
|
|
1275
|
+
threads.set(blockId, {
|
|
1276
|
+
blockId,
|
|
1277
|
+
resolved: state[resolvedReg(blockId)] === true,
|
|
1278
|
+
comments: liveIds.map((id) => readComment(doc, state, id))
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
return threads;
|
|
1282
|
+
}
|
|
1283
|
+
function threadLastActivity(thread) {
|
|
1284
|
+
return thread.comments.reduce((max, c) => c.createdAt > max ? c.createdAt : max, 0);
|
|
1285
|
+
}
|
|
1286
|
+
function addComment(doc, blockId, authorUserId, text, createdAt = Date.now()) {
|
|
1287
|
+
const id = randomId();
|
|
1288
|
+
doc.setField(authorReg(id), authorUserId);
|
|
1289
|
+
doc.setField(createdReg(id), createdAt);
|
|
1290
|
+
if (text) doc.setText(bodyList(id), text);
|
|
1291
|
+
doc.push(commentsList(blockId), id);
|
|
1292
|
+
return id;
|
|
1293
|
+
}
|
|
1294
|
+
function setCommentBody(doc, commentId, text) {
|
|
1295
|
+
doc.setText(bodyList(commentId), text);
|
|
1296
|
+
}
|
|
1297
|
+
function removeComment(doc, blockId, commentId) {
|
|
1298
|
+
const state = doc.materialize();
|
|
1299
|
+
doc.setList(commentsList(blockId), commentIds(state, blockId).filter((x) => x !== commentId));
|
|
1300
|
+
doc.setText(bodyList(commentId), "");
|
|
1301
|
+
doc.deleteField(authorReg(commentId));
|
|
1302
|
+
doc.deleteField(createdReg(commentId));
|
|
1303
|
+
deleteFieldsByPrefix(doc, `${REACT_PREFIX}${commentId}:`);
|
|
1304
|
+
}
|
|
1305
|
+
function resolveThread(doc, blockId, resolved) {
|
|
1306
|
+
if (resolved) doc.setField(resolvedReg(blockId), true);
|
|
1307
|
+
else doc.deleteField(resolvedReg(blockId));
|
|
1308
|
+
}
|
|
1309
|
+
function toggleReaction(doc, commentId, userId, emoji) {
|
|
1310
|
+
const current = strArray(doc.materialize()[reactReg(commentId, userId)]);
|
|
1311
|
+
const next = current.includes(emoji) ? current.filter((e) => e !== emoji) : [...current, emoji];
|
|
1312
|
+
if (next.length === 0) doc.deleteField(reactReg(commentId, userId));
|
|
1313
|
+
else doc.setField(reactReg(commentId, userId), next);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// src/board-content.ts
|
|
1317
|
+
var COLS = "columns";
|
|
1318
|
+
var colTitle = (id) => `coltitle:${id}`;
|
|
1319
|
+
var colDone = (id) => `coldone:${id}`;
|
|
1320
|
+
function readColumns(doc) {
|
|
1321
|
+
const state = doc.materialize();
|
|
1322
|
+
const columns = [];
|
|
1323
|
+
for (const raw of dedupRgaList(state[COLS])) {
|
|
1324
|
+
columns.push({ id: raw, title: asStr(state[colTitle(raw)]), done: state[colDone(raw)] === true });
|
|
1325
|
+
}
|
|
1326
|
+
return columns;
|
|
1327
|
+
}
|
|
1328
|
+
function orderBetween(above, below) {
|
|
1329
|
+
if (above !== void 0 && below !== void 0) return (above + below) / 2;
|
|
1330
|
+
if (below !== void 0) return below - 1;
|
|
1331
|
+
if (above !== void 0) return above + 1;
|
|
1332
|
+
return 1;
|
|
1333
|
+
}
|
|
1334
|
+
function addColumn(doc, title) {
|
|
1335
|
+
const id = randomId();
|
|
1336
|
+
doc.setField(colTitle(id), title);
|
|
1337
|
+
doc.push(COLS, id);
|
|
1338
|
+
return id;
|
|
1339
|
+
}
|
|
1340
|
+
function renameColumn(doc, id, title) {
|
|
1341
|
+
doc.setField(colTitle(id), title);
|
|
1342
|
+
}
|
|
1343
|
+
function setColumnDone(doc, id, done) {
|
|
1344
|
+
doc.setField(colDone(id), done);
|
|
1345
|
+
}
|
|
1346
|
+
function moveColumn(doc, id, toIndex) {
|
|
1347
|
+
const cur = rgaList(doc, COLS).filter((x, i, a) => a.indexOf(x) === i);
|
|
1348
|
+
const from = cur.indexOf(id);
|
|
1349
|
+
if (from === -1) return;
|
|
1350
|
+
const next = cur.filter((x) => x !== id);
|
|
1351
|
+
next.splice(Math.max(0, Math.min(toIndex, next.length)), 0, id);
|
|
1352
|
+
doc.setList(COLS, next);
|
|
1353
|
+
}
|
|
1354
|
+
function deleteColumn(doc, id) {
|
|
1355
|
+
doc.setList(COLS, rgaList(doc, COLS).filter((x) => x !== id));
|
|
1356
|
+
doc.deleteField(colTitle(id));
|
|
1357
|
+
doc.deleteField(colDone(id));
|
|
1358
|
+
}
|
|
1359
|
+
function seedDefaultColumns(doc) {
|
|
1360
|
+
addColumn(doc, "To do");
|
|
1361
|
+
addColumn(doc, "In progress");
|
|
1362
|
+
setColumnDone(doc, addColumn(doc, "Done"), true);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/table-content.ts
|
|
1366
|
+
var tblCols = (T) => `tblcols:${T}`;
|
|
1367
|
+
var tblRows = (T) => `tblrows:${T}`;
|
|
1368
|
+
var tblSort = (T) => `tblsort:${T}`;
|
|
1369
|
+
var tblFilters = (T) => `tblfilters:${T}`;
|
|
1370
|
+
var tColTitle = (c) => `tcoltitle:${c}`;
|
|
1371
|
+
var tColType = (c) => `tcoltype:${c}`;
|
|
1372
|
+
var tColOpts = (c) => `tcolopts:${c}`;
|
|
1373
|
+
var tColWidth = (c) => `tcolwidth:${c}`;
|
|
1374
|
+
var tCell = (r, c) => `tcell:${r}:${c}`;
|
|
1375
|
+
var colIdsOf = (doc, T) => rgaList(doc, tblCols(T));
|
|
1376
|
+
var rowIdsOf = (doc, T) => rgaList(doc, tblRows(T));
|
|
1377
|
+
function readTable(doc, T) {
|
|
1378
|
+
const state = doc.materialize();
|
|
1379
|
+
const columns = [];
|
|
1380
|
+
for (const raw of dedupRgaList(state[tblCols(T)])) {
|
|
1381
|
+
const optsRaw = state[tColOpts(raw)];
|
|
1382
|
+
const options = Array.isArray(optsRaw) ? optsRaw.filter((o) => typeof o === "object" && o !== null && !Array.isArray(o)).map((o) => ({
|
|
1383
|
+
id: asStr(o["id"]),
|
|
1384
|
+
label: asStr(o["label"]),
|
|
1385
|
+
swatch: asStr(o["swatch"]) || "gray"
|
|
1386
|
+
})) : void 0;
|
|
1387
|
+
const widthRaw = state[tColWidth(raw)];
|
|
1388
|
+
const typeRaw = state[tColType(raw)];
|
|
1389
|
+
const colType = ["text", "number", "select", "checkbox"].includes(typeRaw) ? typeRaw : "text";
|
|
1390
|
+
columns.push({
|
|
1391
|
+
id: raw,
|
|
1392
|
+
title: asStr(state[tColTitle(raw)]),
|
|
1393
|
+
type: colType,
|
|
1394
|
+
options: options?.length ? options : void 0,
|
|
1395
|
+
width: typeof widthRaw === "number" ? widthRaw : void 0
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
const rowIds = dedupRgaList(state[tblRows(T)]);
|
|
1399
|
+
const cells = {};
|
|
1400
|
+
for (const r of rowIds) {
|
|
1401
|
+
for (const col of columns) {
|
|
1402
|
+
const v = state[tCell(r, col.id)];
|
|
1403
|
+
if (v !== void 0 && v !== null) {
|
|
1404
|
+
cells[`${r}:${col.id}`] = v;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const sortRaw = state[tblSort(T)];
|
|
1409
|
+
const sort = sortRaw !== null && typeof sortRaw === "object" && !Array.isArray(sortRaw) && typeof sortRaw["colId"] === "string" && (sortRaw["dir"] === "asc" || sortRaw["dir"] === "desc") ? {
|
|
1410
|
+
colId: sortRaw["colId"],
|
|
1411
|
+
dir: sortRaw["dir"]
|
|
1412
|
+
} : null;
|
|
1413
|
+
const filtersRaw = state[tblFilters(T)];
|
|
1414
|
+
const filters = filtersRaw !== null && typeof filtersRaw === "object" && !Array.isArray(filtersRaw) ? filtersRaw : {};
|
|
1415
|
+
return { columns, rowIds, cells, sort, filters };
|
|
1416
|
+
}
|
|
1417
|
+
function visibleTableRowIds(model) {
|
|
1418
|
+
const { columns, rowIds: ids, cells, sort, filters } = model;
|
|
1419
|
+
const colMap = new Map(columns.map((c) => [c.id, c]));
|
|
1420
|
+
let visible = ids;
|
|
1421
|
+
for (const [colId, filter] of Object.entries(filters)) {
|
|
1422
|
+
const col = colMap.get(colId);
|
|
1423
|
+
if (!col) continue;
|
|
1424
|
+
visible = visible.filter((r) => {
|
|
1425
|
+
const v = cells[`${r}:${colId}`] ?? null;
|
|
1426
|
+
switch (filter.kind) {
|
|
1427
|
+
case "contains":
|
|
1428
|
+
return String(v ?? "").toLowerCase().includes(filter.text.toLowerCase());
|
|
1429
|
+
case "numRange": {
|
|
1430
|
+
const n = typeof v === "number" ? v : parseFloat(String(v ?? ""));
|
|
1431
|
+
if (isNaN(n)) return false;
|
|
1432
|
+
if (filter.min !== void 0 && n < filter.min) return false;
|
|
1433
|
+
if (filter.max !== void 0 && n > filter.max) return false;
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
case "isOption":
|
|
1437
|
+
return filter.optionIds.length === 0 || filter.optionIds.includes(String(v ?? ""));
|
|
1438
|
+
case "checked":
|
|
1439
|
+
return (v === true || v === "true") === filter.value;
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
if (sort) {
|
|
1444
|
+
const col = colMap.get(sort.colId);
|
|
1445
|
+
if (col) {
|
|
1446
|
+
const optOrder = new Map(
|
|
1447
|
+
col.options ? col.options.map((o, i) => [o.id, i]) : []
|
|
1448
|
+
);
|
|
1449
|
+
visible = [...visible].sort((a, b) => {
|
|
1450
|
+
const av = cells[`${a}:${sort.colId}`] ?? null;
|
|
1451
|
+
const bv = cells[`${b}:${sort.colId}`] ?? null;
|
|
1452
|
+
let cmp = 0;
|
|
1453
|
+
switch (col.type) {
|
|
1454
|
+
case "number": {
|
|
1455
|
+
const an = typeof av === "number" ? av : parseFloat(String(av ?? ""));
|
|
1456
|
+
const bn = typeof bv === "number" ? bv : parseFloat(String(bv ?? ""));
|
|
1457
|
+
if (isNaN(an) && isNaN(bn)) {
|
|
1458
|
+
cmp = 0;
|
|
1459
|
+
break;
|
|
1460
|
+
}
|
|
1461
|
+
if (isNaN(an)) {
|
|
1462
|
+
cmp = 1;
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
if (isNaN(bn)) {
|
|
1466
|
+
cmp = -1;
|
|
1467
|
+
break;
|
|
1468
|
+
}
|
|
1469
|
+
cmp = an - bn;
|
|
1470
|
+
break;
|
|
1471
|
+
}
|
|
1472
|
+
case "select": {
|
|
1473
|
+
const ai = optOrder.get(String(av ?? "")) ?? Infinity;
|
|
1474
|
+
const bi = optOrder.get(String(bv ?? "")) ?? Infinity;
|
|
1475
|
+
cmp = ai - bi;
|
|
1476
|
+
break;
|
|
1477
|
+
}
|
|
1478
|
+
case "checkbox": {
|
|
1479
|
+
cmp = (av === true ? 1 : 0) - (bv === true ? 1 : 0);
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
default:
|
|
1483
|
+
cmp = String(av ?? "").localeCompare(String(bv ?? ""));
|
|
1484
|
+
}
|
|
1485
|
+
if (cmp !== 0) return sort.dir === "asc" ? cmp : -cmp;
|
|
1486
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return visible;
|
|
1491
|
+
}
|
|
1492
|
+
function createTable(doc, T) {
|
|
1493
|
+
const nameId = randomId();
|
|
1494
|
+
const statusId = randomId();
|
|
1495
|
+
const notesId = randomId();
|
|
1496
|
+
const statusOpts = [
|
|
1497
|
+
{ id: randomId(), label: "Not started", swatch: "gray" },
|
|
1498
|
+
{ id: randomId(), label: "In progress", swatch: "blue" },
|
|
1499
|
+
{ id: randomId(), label: "Done", swatch: "green" }
|
|
1500
|
+
];
|
|
1501
|
+
doc.setField(tColTitle(nameId), "Name");
|
|
1502
|
+
doc.setField(tColType(nameId), "text");
|
|
1503
|
+
doc.setField(tColTitle(statusId), "Status");
|
|
1504
|
+
doc.setField(tColType(statusId), "select");
|
|
1505
|
+
doc.setField(tColOpts(statusId), statusOpts);
|
|
1506
|
+
doc.setField(tColTitle(notesId), "Notes");
|
|
1507
|
+
doc.setField(tColType(notesId), "text");
|
|
1508
|
+
doc.setList(tblCols(T), [nameId, statusId, notesId]);
|
|
1509
|
+
const r1 = randomId();
|
|
1510
|
+
const r2 = randomId();
|
|
1511
|
+
doc.setList(tblRows(T), [r1, r2]);
|
|
1512
|
+
}
|
|
1513
|
+
function addTableColumn(doc, T, type, title) {
|
|
1514
|
+
const id = randomId();
|
|
1515
|
+
doc.setField(tColTitle(id), title);
|
|
1516
|
+
doc.setField(tColType(id), type);
|
|
1517
|
+
if (type === "select") {
|
|
1518
|
+
doc.setField(tColOpts(id), [{ id: randomId(), label: "Option 1", swatch: "gray" }]);
|
|
1519
|
+
}
|
|
1520
|
+
doc.push(tblCols(T), id);
|
|
1521
|
+
return id;
|
|
1522
|
+
}
|
|
1523
|
+
function renameTableColumn(doc, id, title) {
|
|
1524
|
+
doc.setField(tColTitle(id), title);
|
|
1525
|
+
}
|
|
1526
|
+
function setTableColumnType(doc, id, type) {
|
|
1527
|
+
doc.setField(tColType(id), type);
|
|
1528
|
+
if (type === "select") {
|
|
1529
|
+
const cur = doc.materialize()[tColOpts(id)];
|
|
1530
|
+
if (!Array.isArray(cur) || cur.length === 0) {
|
|
1531
|
+
doc.setField(tColOpts(id), [{ id: randomId(), label: "Option 1", swatch: "gray" }]);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function setTableColumnOptions(doc, id, options) {
|
|
1536
|
+
doc.setField(tColOpts(id), options);
|
|
1537
|
+
}
|
|
1538
|
+
function addTableSelectOption(doc, colId, label, swatch = "gray") {
|
|
1539
|
+
const state = doc.materialize();
|
|
1540
|
+
const cur = Array.isArray(state[tColOpts(colId)]) ? state[tColOpts(colId)] : [];
|
|
1541
|
+
doc.setField(tColOpts(colId), [...cur, { id: randomId(), label, swatch }]);
|
|
1542
|
+
}
|
|
1543
|
+
function removeTableSelectOption(doc, colId, optionId) {
|
|
1544
|
+
const state = doc.materialize();
|
|
1545
|
+
const cur = Array.isArray(state[tColOpts(colId)]) ? state[tColOpts(colId)] : [];
|
|
1546
|
+
doc.setField(tColOpts(colId), cur.filter((o) => o.id !== optionId));
|
|
1547
|
+
}
|
|
1548
|
+
function setTableColumnWidth(doc, colId, width) {
|
|
1549
|
+
doc.setField(tColWidth(colId), width);
|
|
1550
|
+
}
|
|
1551
|
+
function moveTableColumn(doc, T, id, toIndex) {
|
|
1552
|
+
const cur = colIdsOf(doc, T).filter((x, i, a) => a.indexOf(x) === i);
|
|
1553
|
+
if (!cur.includes(id)) return;
|
|
1554
|
+
const next = cur.filter((x) => x !== id);
|
|
1555
|
+
next.splice(Math.max(0, Math.min(toIndex, next.length)), 0, id);
|
|
1556
|
+
doc.setList(tblCols(T), next);
|
|
1557
|
+
}
|
|
1558
|
+
function deleteTableColumn(doc, T, id) {
|
|
1559
|
+
const curRowIds = rowIdsOf(doc, T);
|
|
1560
|
+
doc.setList(tblCols(T), colIdsOf(doc, T).filter((x) => x !== id));
|
|
1561
|
+
doc.deleteField(tColTitle(id));
|
|
1562
|
+
doc.deleteField(tColType(id));
|
|
1563
|
+
doc.deleteField(tColOpts(id));
|
|
1564
|
+
doc.deleteField(tColWidth(id));
|
|
1565
|
+
for (const r of curRowIds) doc.deleteField(tCell(r, id));
|
|
1566
|
+
}
|
|
1567
|
+
function addTableRow(doc, T, atIndex) {
|
|
1568
|
+
const id = randomId();
|
|
1569
|
+
const cur = rowIdsOf(doc, T);
|
|
1570
|
+
if (atIndex !== void 0) {
|
|
1571
|
+
const at = Math.max(0, Math.min(atIndex, cur.length));
|
|
1572
|
+
doc.setList(tblRows(T), [...cur.slice(0, at), id, ...cur.slice(at)]);
|
|
1573
|
+
} else {
|
|
1574
|
+
doc.push(tblRows(T), id);
|
|
1575
|
+
}
|
|
1576
|
+
return id;
|
|
1577
|
+
}
|
|
1578
|
+
function moveTableRow(doc, T, id, toIndex) {
|
|
1579
|
+
const cur = rowIdsOf(doc, T).filter((x, i, a) => a.indexOf(x) === i);
|
|
1580
|
+
if (!cur.includes(id)) return;
|
|
1581
|
+
const next = cur.filter((x) => x !== id);
|
|
1582
|
+
next.splice(Math.max(0, Math.min(toIndex, next.length)), 0, id);
|
|
1583
|
+
doc.setList(tblRows(T), next);
|
|
1584
|
+
}
|
|
1585
|
+
function duplicateTableRow(doc, T, id) {
|
|
1586
|
+
const cur = rowIdsOf(doc, T);
|
|
1587
|
+
const at = cur.indexOf(id);
|
|
1588
|
+
const insertAt = at >= 0 ? at + 1 : cur.length;
|
|
1589
|
+
const newId = randomId();
|
|
1590
|
+
doc.setList(tblRows(T), [...cur.slice(0, insertAt), newId, ...cur.slice(insertAt)]);
|
|
1591
|
+
const state = doc.materialize();
|
|
1592
|
+
for (const c of colIdsOf(doc, T)) {
|
|
1593
|
+
const v = state[tCell(id, c)];
|
|
1594
|
+
if (v !== void 0 && v !== null) doc.setField(tCell(newId, c), v);
|
|
1595
|
+
}
|
|
1596
|
+
return newId;
|
|
1597
|
+
}
|
|
1598
|
+
function deleteTableRow(doc, T, id) {
|
|
1599
|
+
const curColIds = colIdsOf(doc, T);
|
|
1600
|
+
doc.setList(tblRows(T), rowIdsOf(doc, T).filter((x) => x !== id));
|
|
1601
|
+
for (const c of curColIds) doc.deleteField(tCell(id, c));
|
|
1602
|
+
}
|
|
1603
|
+
function setTableCell(doc, r, c, value) {
|
|
1604
|
+
if (value === null || value === void 0) {
|
|
1605
|
+
doc.deleteField(tCell(r, c));
|
|
1606
|
+
} else {
|
|
1607
|
+
doc.setField(tCell(r, c), value);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
function setTableSort(doc, T, colId, dir) {
|
|
1611
|
+
doc.setField(tblSort(T), { colId, dir });
|
|
1612
|
+
}
|
|
1613
|
+
function clearTableSort(doc, T) {
|
|
1614
|
+
doc.deleteField(tblSort(T));
|
|
1615
|
+
}
|
|
1616
|
+
function setTableFilter(doc, T, colId, filter) {
|
|
1617
|
+
const state = doc.materialize();
|
|
1618
|
+
const cur = state[tblFilters(T)] !== null && typeof state[tblFilters(T)] === "object" && !Array.isArray(state[tblFilters(T)]) ? { ...state[tblFilters(T)] } : {};
|
|
1619
|
+
cur[colId] = filter;
|
|
1620
|
+
doc.setField(tblFilters(T), cur);
|
|
1621
|
+
}
|
|
1622
|
+
function clearTableFilter(doc, T, colId) {
|
|
1623
|
+
const state = doc.materialize();
|
|
1624
|
+
const cur = state[tblFilters(T)] !== null && typeof state[tblFilters(T)] === "object" && !Array.isArray(state[tblFilters(T)]) ? { ...state[tblFilters(T)] } : {};
|
|
1625
|
+
delete cur[colId];
|
|
1626
|
+
if (Object.keys(cur).length === 0) {
|
|
1627
|
+
doc.deleteField(tblFilters(T));
|
|
1628
|
+
} else {
|
|
1629
|
+
doc.setField(tblFilters(T), cur);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
function clearAllTableFilters(doc, T) {
|
|
1633
|
+
doc.deleteField(tblFilters(T));
|
|
1634
|
+
}
|
|
1635
|
+
function clearTable(doc, T) {
|
|
1636
|
+
const curColIds = colIdsOf(doc, T);
|
|
1637
|
+
const curRowIds = rowIdsOf(doc, T);
|
|
1638
|
+
for (const c of curColIds) {
|
|
1639
|
+
doc.deleteField(tColTitle(c));
|
|
1640
|
+
doc.deleteField(tColType(c));
|
|
1641
|
+
doc.deleteField(tColOpts(c));
|
|
1642
|
+
doc.deleteField(tColWidth(c));
|
|
1643
|
+
for (const r of curRowIds) doc.deleteField(tCell(r, c));
|
|
1644
|
+
}
|
|
1645
|
+
doc.setList(tblCols(T), []);
|
|
1646
|
+
doc.setList(tblRows(T), []);
|
|
1647
|
+
doc.deleteField(tblSort(T));
|
|
1648
|
+
doc.deleteField(tblFilters(T));
|
|
1649
|
+
}
|
|
1650
|
+
function propsOf(node) {
|
|
1651
|
+
const meta = node.meta;
|
|
1652
|
+
if (!meta || typeof meta.props !== "object" || meta.props === null) return {};
|
|
1653
|
+
return meta.props;
|
|
1654
|
+
}
|
|
1655
|
+
function automationOf(node) {
|
|
1656
|
+
return node.meta?.automation ?? null;
|
|
1657
|
+
}
|
|
1658
|
+
function addVaultObject(nodes, input, now) {
|
|
1659
|
+
const meta = { ...input.meta };
|
|
1660
|
+
if (input.props && Object.keys(input.props).length > 0) meta.props = input.props;
|
|
1661
|
+
if (input.automation) meta.automation = input.automation;
|
|
1662
|
+
return addObject(nodes, {
|
|
1663
|
+
type: input.type,
|
|
1664
|
+
parentId: input.parentId,
|
|
1665
|
+
title: input.title,
|
|
1666
|
+
emoji: input.emoji,
|
|
1667
|
+
id: input.id,
|
|
1668
|
+
access: input.access,
|
|
1669
|
+
enc: input.enc,
|
|
1670
|
+
...Object.keys(meta).length > 0 ? { meta } : {}
|
|
1671
|
+
}, now);
|
|
1672
|
+
}
|
|
1673
|
+
function setProps(nodes, id, patch, now) {
|
|
1674
|
+
return nodes.map((n) => {
|
|
1675
|
+
if (n.id !== id) return n;
|
|
1676
|
+
const existingProps = propsOf(n);
|
|
1677
|
+
const newMeta = { ...n.meta ?? {}, props: { ...existingProps, ...patch } };
|
|
1678
|
+
return { ...n, meta: newMeta, updatedAt: now };
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
function clearProp(nodes, id, key, now) {
|
|
1682
|
+
return nodes.map((n) => {
|
|
1683
|
+
if (n.id !== id) return n;
|
|
1684
|
+
const existingProps = { ...propsOf(n) };
|
|
1685
|
+
delete existingProps[key];
|
|
1686
|
+
const newMeta = { ...n.meta ?? {}, props: existingProps };
|
|
1687
|
+
return { ...n, meta: newMeta, updatedAt: now };
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
function patchAutomation(nodes, id, automation, now) {
|
|
1691
|
+
return nodes.map((n) => {
|
|
1692
|
+
if (n.id !== id) return n;
|
|
1693
|
+
const newMeta = { ...n.meta ?? {} };
|
|
1694
|
+
if (automation !== null) {
|
|
1695
|
+
newMeta.automation = automation;
|
|
1696
|
+
} else {
|
|
1697
|
+
delete newMeta.automation;
|
|
1698
|
+
}
|
|
1699
|
+
return { ...n, meta: newMeta, updatedAt: now };
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// src/task-model.ts
|
|
1704
|
+
function tasksForBoard(nodes, boardId, columns) {
|
|
1705
|
+
const colSet = new Set(columns.map((c) => c.id));
|
|
1706
|
+
const hasDoneCol = columns.some((c) => c.done);
|
|
1707
|
+
const doneColIds = new Set(columns.filter((c) => c.done).map((c) => c.id));
|
|
1708
|
+
const firstCol = columns[0]?.id ?? "";
|
|
1709
|
+
const tasks = [];
|
|
1710
|
+
for (const node of nodes) {
|
|
1711
|
+
if (node.archived || node.type !== "task" || node.parentId !== boardId) continue;
|
|
1712
|
+
const props = propsOf(node);
|
|
1713
|
+
let columnId = props.columnId ?? "";
|
|
1714
|
+
if (!colSet.has(columnId)) columnId = firstCol;
|
|
1715
|
+
const statusRaw = props.status;
|
|
1716
|
+
const status = statusRaw === "doing" || statusRaw === "done" ? statusRaw : "todo";
|
|
1717
|
+
const order = typeof props.order === "number" ? props.order : 0;
|
|
1718
|
+
const done = hasDoneCol ? doneColIds.has(columnId) : status === "done";
|
|
1719
|
+
tasks.push({ id: node.id, columnId, title: node.title ?? "", status, order, done });
|
|
1720
|
+
}
|
|
1721
|
+
const tasksByColumn = {};
|
|
1722
|
+
for (const col of columns) {
|
|
1723
|
+
tasksByColumn[col.id] = tasks.filter((t) => t.columnId === col.id).sort((a, b) => a.order - b.order || (a.id < b.id ? -1 : 1));
|
|
1724
|
+
}
|
|
1725
|
+
const visible = tasks.filter((t) => !!t.columnId);
|
|
1726
|
+
const doneCount = visible.filter((t) => t.done).length;
|
|
1727
|
+
return { tasksByColumn, done: doneCount, total: visible.length };
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// src/calendar-content.ts
|
|
1731
|
+
var EVENTS = "events";
|
|
1732
|
+
var startReg = (id) => `estart:${id}`;
|
|
1733
|
+
var endReg = (id) => `eend:${id}`;
|
|
1734
|
+
var allDayReg = (id) => `eallDay:${id}`;
|
|
1735
|
+
var colorReg = (id) => `ecolor:${id}`;
|
|
1736
|
+
var descReg = (id) => `edesc:${id}`;
|
|
1737
|
+
var titleList = (id) => `etitle:${id}`;
|
|
1738
|
+
function readEvents(doc) {
|
|
1739
|
+
const state = doc.materialize();
|
|
1740
|
+
const events = [];
|
|
1741
|
+
for (const raw of dedupRgaList(state[EVENTS])) {
|
|
1742
|
+
const start = asNum(state[startReg(raw)], 0);
|
|
1743
|
+
const end = asNum(state[endReg(raw)], start);
|
|
1744
|
+
events.push({
|
|
1745
|
+
id: raw,
|
|
1746
|
+
title: doc.text(titleList(raw)),
|
|
1747
|
+
start,
|
|
1748
|
+
end,
|
|
1749
|
+
allDay: state[allDayReg(raw)] === true,
|
|
1750
|
+
color: asStrOrNull(state[colorReg(raw)]),
|
|
1751
|
+
desc: asStrOrNull(state[descReg(raw)])
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
return events.sort((a, b) => a.start - b.start);
|
|
1755
|
+
}
|
|
1756
|
+
function addEvent(doc, init) {
|
|
1757
|
+
const id = randomId();
|
|
1758
|
+
const order = rgaList(doc, EVENTS);
|
|
1759
|
+
doc.setField(startReg(id), init.start);
|
|
1760
|
+
doc.setField(endReg(id), init.end ?? init.start);
|
|
1761
|
+
if (init.allDay) doc.setField(allDayReg(id), true);
|
|
1762
|
+
if (init.color) doc.setField(colorReg(id), init.color);
|
|
1763
|
+
if (init.desc) doc.setField(descReg(id), init.desc);
|
|
1764
|
+
if (init.title) doc.setText(titleList(id), init.title);
|
|
1765
|
+
doc.setList(EVENTS, [...order, id]);
|
|
1766
|
+
return id;
|
|
1767
|
+
}
|
|
1768
|
+
function deleteEvent(doc, id) {
|
|
1769
|
+
const order = rgaList(doc, EVENTS);
|
|
1770
|
+
doc.setList(EVENTS, order.filter((x) => x !== id));
|
|
1771
|
+
doc.setText(titleList(id), "");
|
|
1772
|
+
doc.deleteField(startReg(id));
|
|
1773
|
+
doc.deleteField(endReg(id));
|
|
1774
|
+
doc.deleteField(allDayReg(id));
|
|
1775
|
+
doc.deleteField(colorReg(id));
|
|
1776
|
+
doc.deleteField(descReg(id));
|
|
1777
|
+
}
|
|
1778
|
+
function patchEvent(doc, id, patch) {
|
|
1779
|
+
if (patch.start !== void 0) doc.setField(startReg(id), patch.start);
|
|
1780
|
+
if (patch.end !== void 0) doc.setField(endReg(id), patch.end);
|
|
1781
|
+
if (patch.allDay !== void 0) doc.setField(allDayReg(id), patch.allDay);
|
|
1782
|
+
if (patch.color !== void 0) {
|
|
1783
|
+
if (patch.color === null) doc.deleteField(colorReg(id));
|
|
1784
|
+
else doc.setField(colorReg(id), patch.color);
|
|
1785
|
+
}
|
|
1786
|
+
if (patch.desc !== void 0) {
|
|
1787
|
+
if (patch.desc === null) doc.deleteField(descReg(id));
|
|
1788
|
+
else doc.setField(descReg(id), patch.desc);
|
|
1789
|
+
}
|
|
1790
|
+
if (patch.title !== void 0) doc.setText(titleList(id), patch.title);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// src/form-content.ts
|
|
1794
|
+
var FIELDS = "fields";
|
|
1795
|
+
var RESPONSES = "responses";
|
|
1796
|
+
var labelList = (id) => `flabel:${id}`;
|
|
1797
|
+
var kindReg = (id) => `fkind:${id}`;
|
|
1798
|
+
var requiredReg = (id) => `frequired:${id}`;
|
|
1799
|
+
var optionsReg = (id) => `foptions:${id}`;
|
|
1800
|
+
var submittedAtReg = (id) => `rsubmittedAt:${id}`;
|
|
1801
|
+
var submitterReg = (id) => `rsubmitter:${id}`;
|
|
1802
|
+
var dataReg = (id) => `rdata:${id}`;
|
|
1803
|
+
function safeParseJson(raw) {
|
|
1804
|
+
if (typeof raw !== "string") return null;
|
|
1805
|
+
try {
|
|
1806
|
+
return JSON.parse(raw);
|
|
1807
|
+
} catch {
|
|
1808
|
+
return null;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
function readFields(doc) {
|
|
1812
|
+
const state = doc.materialize();
|
|
1813
|
+
const fields = [];
|
|
1814
|
+
for (const raw of dedupRgaList(state[FIELDS])) {
|
|
1815
|
+
fields.push({
|
|
1816
|
+
id: raw,
|
|
1817
|
+
label: doc.text(labelList(raw)),
|
|
1818
|
+
kind: typeof state[kindReg(raw)] === "string" ? state[kindReg(raw)] : "text",
|
|
1819
|
+
required: state[requiredReg(raw)] === true,
|
|
1820
|
+
options: safeParseJson(state[optionsReg(raw)]) ?? []
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
return fields;
|
|
1824
|
+
}
|
|
1825
|
+
function readResponses(doc) {
|
|
1826
|
+
const state = doc.materialize();
|
|
1827
|
+
const responses = [];
|
|
1828
|
+
for (const raw of dedupRgaList(state[RESPONSES])) {
|
|
1829
|
+
responses.push({
|
|
1830
|
+
id: raw,
|
|
1831
|
+
submittedAt: asNum(state[submittedAtReg(raw)], 0),
|
|
1832
|
+
submitter: asStr(state[submitterReg(raw)]),
|
|
1833
|
+
data: safeParseJson(state[dataReg(raw)]) ?? {}
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
return responses;
|
|
1837
|
+
}
|
|
1838
|
+
function addField(doc, init = {}) {
|
|
1839
|
+
const id = randomId();
|
|
1840
|
+
const order = rgaList(doc, FIELDS);
|
|
1841
|
+
doc.setField(kindReg(id), init.kind ?? "text");
|
|
1842
|
+
if (init.required) doc.setField(requiredReg(id), true);
|
|
1843
|
+
if (init.options?.length) doc.setField(optionsReg(id), JSON.stringify(init.options));
|
|
1844
|
+
if (init.label) doc.setText(labelList(id), init.label);
|
|
1845
|
+
doc.setList(FIELDS, [...order, id]);
|
|
1846
|
+
return id;
|
|
1847
|
+
}
|
|
1848
|
+
function deleteField(doc, id) {
|
|
1849
|
+
const order = rgaList(doc, FIELDS);
|
|
1850
|
+
doc.setList(FIELDS, order.filter((x) => x !== id));
|
|
1851
|
+
doc.setText(labelList(id), "");
|
|
1852
|
+
doc.deleteField(kindReg(id));
|
|
1853
|
+
doc.deleteField(requiredReg(id));
|
|
1854
|
+
doc.deleteField(optionsReg(id));
|
|
1855
|
+
}
|
|
1856
|
+
function moveField(doc, id, toIndex) {
|
|
1857
|
+
const order = rgaList(doc, FIELDS);
|
|
1858
|
+
const without = order.filter((x) => x !== id);
|
|
1859
|
+
const clamped = Math.max(0, Math.min(toIndex, without.length));
|
|
1860
|
+
without.splice(clamped, 0, id);
|
|
1861
|
+
doc.setList(FIELDS, without);
|
|
1862
|
+
}
|
|
1863
|
+
function patchField(doc, id, patch) {
|
|
1864
|
+
if (patch.kind !== void 0) doc.setField(kindReg(id), patch.kind);
|
|
1865
|
+
if (patch.required !== void 0) doc.setField(requiredReg(id), patch.required);
|
|
1866
|
+
if (patch.options !== void 0) doc.setField(optionsReg(id), JSON.stringify(patch.options));
|
|
1867
|
+
if (patch.label !== void 0) doc.setText(labelList(id), patch.label);
|
|
1868
|
+
}
|
|
1869
|
+
function addResponse(doc, submitter, data, now) {
|
|
1870
|
+
const id = randomId();
|
|
1871
|
+
const order = rgaList(doc, RESPONSES);
|
|
1872
|
+
doc.setField(submittedAtReg(id), now);
|
|
1873
|
+
doc.setField(submitterReg(id), submitter);
|
|
1874
|
+
doc.setField(dataReg(id), JSON.stringify(data));
|
|
1875
|
+
doc.setList(RESPONSES, [...order, id]);
|
|
1876
|
+
return id;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/feedback-content.ts
|
|
1880
|
+
var ITEMS = "items";
|
|
1881
|
+
var titleList2 = (id) => `ititle:${id}`;
|
|
1882
|
+
var statusReg = (id) => `istatus:${id}`;
|
|
1883
|
+
var descReg2 = (id) => `idesc:${id}`;
|
|
1884
|
+
var voteKey = (itemId, userId) => `ivote:${itemId}:${userId}`;
|
|
1885
|
+
var votePrefix = (itemId) => `ivote:${itemId}:`;
|
|
1886
|
+
function readVoters(state, itemId) {
|
|
1887
|
+
const prefix = votePrefix(itemId);
|
|
1888
|
+
const voters = [];
|
|
1889
|
+
for (const key of Object.keys(state)) {
|
|
1890
|
+
if (key.startsWith(prefix) && state[key] === true) {
|
|
1891
|
+
voters.push(key.slice(prefix.length));
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return voters;
|
|
1895
|
+
}
|
|
1896
|
+
function readItems(doc) {
|
|
1897
|
+
const state = doc.materialize();
|
|
1898
|
+
const items = [];
|
|
1899
|
+
for (const raw of dedupRgaList(state[ITEMS])) {
|
|
1900
|
+
items.push({
|
|
1901
|
+
id: raw,
|
|
1902
|
+
title: doc.text(titleList2(raw)),
|
|
1903
|
+
status: typeof state[statusReg(raw)] === "string" ? state[statusReg(raw)] : "open",
|
|
1904
|
+
desc: asStrOrNull(state[descReg2(raw)]),
|
|
1905
|
+
voters: readVoters(state, raw)
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
return items.sort((a, b) => b.voters.length - a.voters.length || (a.id < b.id ? -1 : 1));
|
|
1909
|
+
}
|
|
1910
|
+
function addItem(doc, title) {
|
|
1911
|
+
const id = randomId();
|
|
1912
|
+
const order = rgaList(doc, ITEMS);
|
|
1913
|
+
doc.setText(titleList2(id), title);
|
|
1914
|
+
doc.setField(statusReg(id), "open");
|
|
1915
|
+
doc.setList(ITEMS, [...order, id]);
|
|
1916
|
+
return id;
|
|
1917
|
+
}
|
|
1918
|
+
function deleteItem(doc, id) {
|
|
1919
|
+
const order = rgaList(doc, ITEMS);
|
|
1920
|
+
doc.setList(ITEMS, order.filter((x) => x !== id));
|
|
1921
|
+
doc.setText(titleList2(id), "");
|
|
1922
|
+
doc.deleteField(statusReg(id));
|
|
1923
|
+
doc.deleteField(descReg2(id));
|
|
1924
|
+
deleteFieldsByPrefix(doc, votePrefix(id));
|
|
1925
|
+
}
|
|
1926
|
+
function patchItem(doc, id, patch) {
|
|
1927
|
+
if (patch.status !== void 0) doc.setField(statusReg(id), patch.status);
|
|
1928
|
+
if (patch.desc !== void 0) {
|
|
1929
|
+
if (patch.desc === null) doc.deleteField(descReg2(id));
|
|
1930
|
+
else doc.setField(descReg2(id), patch.desc);
|
|
1931
|
+
}
|
|
1932
|
+
if (patch.title !== void 0) doc.setText(titleList2(id), patch.title);
|
|
1933
|
+
}
|
|
1934
|
+
function vote(doc, id, userId) {
|
|
1935
|
+
doc.setField(voteKey(id, userId), true);
|
|
1936
|
+
}
|
|
1937
|
+
function unvote(doc, id, userId) {
|
|
1938
|
+
doc.deleteField(voteKey(id, userId));
|
|
1939
|
+
}
|
|
1940
|
+
function clientOpts() {
|
|
1941
|
+
return { baseUrl: getSyncBase(), namespace: getSyncNamespace() ?? "" };
|
|
1942
|
+
}
|
|
1943
|
+
async function deriveSession(seedWords, name) {
|
|
1944
|
+
return deriveSession$1(seedWords, clientOpts(), { name, sharedNamespace: getSharedSpacesNamespace() ?? void 0 });
|
|
1945
|
+
}
|
|
1946
|
+
async function buildSession(opts, name) {
|
|
1947
|
+
return buildSession$1({ ...opts, name, clientOpts: clientOpts(), sharedNamespace: getSharedSpacesNamespace() ?? void 0 });
|
|
1948
|
+
}
|
|
1949
|
+
async function buildLinkedSession(linked, name) {
|
|
1950
|
+
return buildLinkedSession$1({
|
|
1951
|
+
identity: linked,
|
|
1952
|
+
name,
|
|
1953
|
+
clientOpts: clientOpts(),
|
|
1954
|
+
sharedNamespace: getSharedSpacesNamespace() ?? void 0
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
async function sessionFromPersisted(persisted) {
|
|
1958
|
+
return sessionFromPersisted$1(persisted, clientOpts(), {
|
|
1959
|
+
sharedNamespace: getSharedSpacesNamespace() ?? void 0
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
var PAIR_PREFIX = "octovault-pair:";
|
|
1963
|
+
async function completeDevicePairing(payload, pin) {
|
|
1964
|
+
return completeDevicePairing$1(payload, pin, {
|
|
1965
|
+
...pairingClientConfig(),
|
|
1966
|
+
confirmUnpinnedRoot: () => true
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
async function startDevicePairing(session, pin) {
|
|
1970
|
+
return startDevicePairing$1(session, pin, { prefix: PAIR_PREFIX });
|
|
1971
|
+
}
|
|
1972
|
+
function getMemberCap(spaceId) {
|
|
1973
|
+
const entry = getSpaceAccessEntry(spaceId);
|
|
1974
|
+
return entry?.kind === "member" ? entry.cap : null;
|
|
1975
|
+
}
|
|
1976
|
+
var asRecord = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
1977
|
+
function coerceMutePrefs(v) {
|
|
1978
|
+
const r = asRecord(v);
|
|
1979
|
+
const nodes = {};
|
|
1980
|
+
for (const [k, val] of Object.entries(asRecord(r.nodes))) {
|
|
1981
|
+
if (typeof val === "number" || val === true) nodes[k] = val;
|
|
1982
|
+
}
|
|
1983
|
+
const spaces = {};
|
|
1984
|
+
for (const [k, val] of Object.entries(asRecord(r.spaces))) {
|
|
1985
|
+
if (typeof val === "number" || val === true) spaces[k] = val;
|
|
1986
|
+
}
|
|
1987
|
+
return { nodes, spaces };
|
|
1988
|
+
}
|
|
1989
|
+
function coerceReadPrefs(v) {
|
|
1990
|
+
const r = asRecord(v);
|
|
1991
|
+
const nodes = {};
|
|
1992
|
+
for (const [k, val] of Object.entries(asRecord(r.nodes))) {
|
|
1993
|
+
if (typeof val === "number") nodes[k] = val;
|
|
1994
|
+
}
|
|
1995
|
+
return { nodes };
|
|
1996
|
+
}
|
|
1997
|
+
async function readSpaces(client, session) {
|
|
1998
|
+
const doc = await readSpaces$1(client, session);
|
|
1999
|
+
const extra = doc.extra ?? {};
|
|
2000
|
+
return {
|
|
2001
|
+
...doc,
|
|
2002
|
+
mutes: coerceMutePrefs(extra.mutes),
|
|
2003
|
+
reads: coerceReadPrefs(extra.reads)
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
async function leaveSpace(client, session, spaceId) {
|
|
2007
|
+
await updateSpacesDoc(client, session, (cur) => {
|
|
2008
|
+
if (!cur.spaces.some((s) => s.id === spaceId)) return cur;
|
|
2009
|
+
const caps = { ...cur.caps };
|
|
2010
|
+
delete caps[spaceId];
|
|
2011
|
+
const pubAccess = { ...cur.pubAccess };
|
|
2012
|
+
delete pubAccess[spaceId];
|
|
2013
|
+
return { spaces: cur.spaces.filter((s) => s.id !== spaceId), caps, pubAccess };
|
|
2014
|
+
});
|
|
2015
|
+
removeSpaceAccessEntry(spaceId);
|
|
2016
|
+
}
|
|
2017
|
+
var CategoryError = class extends Error {
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
// src/starfish/object-types-store.ts
|
|
2021
|
+
var EMPTY_TYPES_DOC = { types: [] };
|
|
2022
|
+
function addType(doc, def) {
|
|
2023
|
+
const id = randomId();
|
|
2024
|
+
const type = { id, ...def };
|
|
2025
|
+
return { doc: { ...doc, types: [...doc.types, type] }, id };
|
|
2026
|
+
}
|
|
2027
|
+
function patchType(doc, id, patch) {
|
|
2028
|
+
return { ...doc, types: doc.types.map((t) => t.id === id ? { ...t, ...patch } : t) };
|
|
2029
|
+
}
|
|
2030
|
+
function addField2(doc, typeId, field) {
|
|
2031
|
+
return {
|
|
2032
|
+
...doc,
|
|
2033
|
+
types: doc.types.map(
|
|
2034
|
+
(t) => t.id === typeId ? { ...t, fields: [...t.fields, field] } : t
|
|
2035
|
+
)
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
function patchField2(doc, typeId, fieldKey, patch) {
|
|
2039
|
+
return {
|
|
2040
|
+
...doc,
|
|
2041
|
+
types: doc.types.map(
|
|
2042
|
+
(t) => t.id === typeId ? { ...t, fields: t.fields.map((f) => f.key === fieldKey ? { ...f, ...patch } : f) } : t
|
|
2043
|
+
)
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function removeField(doc, typeId, fieldKey) {
|
|
2047
|
+
return {
|
|
2048
|
+
...doc,
|
|
2049
|
+
types: doc.types.map(
|
|
2050
|
+
(t) => t.id === typeId ? { ...t, fields: t.fields.filter((f) => f.key !== fieldKey) } : t
|
|
2051
|
+
)
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
function reorderFields(doc, typeId, keys) {
|
|
2055
|
+
return {
|
|
2056
|
+
...doc,
|
|
2057
|
+
types: doc.types.map((t) => {
|
|
2058
|
+
if (t.id !== typeId) return t;
|
|
2059
|
+
const byKey = new Map(t.fields.map((f) => [f.key, f]));
|
|
2060
|
+
const ordered = keys.map((k) => byKey.get(k)).filter((f) => !!f);
|
|
2061
|
+
const rest = t.fields.filter((f) => !keys.includes(f.key));
|
|
2062
|
+
return { ...t, fields: [...ordered, ...rest] };
|
|
2063
|
+
})
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
function archiveType(doc, id) {
|
|
2067
|
+
return patchType(doc, id, { archived: true });
|
|
2068
|
+
}
|
|
2069
|
+
var _objStore = createSealedBlobStore({
|
|
2070
|
+
paths: objectBlobPaths,
|
|
2071
|
+
maxBytes: MAX_OBJECT_BLOB_BYTES
|
|
2072
|
+
});
|
|
2073
|
+
async function uploadObjectBlob(client, enc, spaceId, bytes, name, mime) {
|
|
2074
|
+
const blobId = await _objStore.upload(client, enc, bytes, { spaceId });
|
|
2075
|
+
return { blobId, name, mime, size: bytes.length };
|
|
2076
|
+
}
|
|
2077
|
+
async function loadObjectBlob(client, enc, spaceId, blobId) {
|
|
2078
|
+
return _objStore.load(client, enc, blobId, { spaceId });
|
|
2079
|
+
}
|
|
2080
|
+
var _blobStore = createSealedBlobStore({
|
|
2081
|
+
paths: objectBlobPaths,
|
|
2082
|
+
maxBytes: MAX_OBJECT_BLOB_BYTES,
|
|
2083
|
+
kvAdapter: { getItem: kvGet, setItem: kvSet, removeItem: kvRemove },
|
|
2084
|
+
persistPrefix: "octovault.attach.blob.",
|
|
2085
|
+
persistIndexKey: "octovault.attach.index"
|
|
2086
|
+
});
|
|
2087
|
+
async function uploadAttachment(client, enc, spaceId, bytes, name, mime) {
|
|
2088
|
+
const blobId = await _blobStore.upload(client, enc, bytes, { spaceId });
|
|
2089
|
+
return { blobId, name, mime, size: bytes.length, kind: attachmentKind(mime) };
|
|
2090
|
+
}
|
|
2091
|
+
async function loadAttachment(client, enc, spaceId, ref) {
|
|
2092
|
+
return _blobStore.load(client, enc, ref.blobId, { spaceId });
|
|
2093
|
+
}
|
|
2094
|
+
function clearAttachmentCache() {
|
|
2095
|
+
_blobStore.clearCache();
|
|
2096
|
+
}
|
|
2097
|
+
var CONNECT_TIMEOUT_MS = 12e3;
|
|
2098
|
+
function fetchWithTimeout(ms = CONNECT_TIMEOUT_MS) {
|
|
2099
|
+
return createTimeoutFetch(ms);
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// src/starfish/stream-bots.ts
|
|
2103
|
+
async function openStreamBotCredential(session, stored) {
|
|
2104
|
+
if (typeof stored.token === "string") return stored;
|
|
2105
|
+
return JSON.parse(await unsealFromSelf(session, stored));
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
export { BLOCK_SECTIONS, BLOCK_TYPES, BOARD_SCHEMA, BUILTIN_OBJECT_TYPES, BUILTIN_REGISTRY, CALENDAR_SCHEMA, CAPABILITY_META, CONNECT_TIMEOUT_MS, CategoryError, DEFAULT_AI_SETTINGS, DEFAULT_QUICK_REACTIONS, EMOJI, EMOJI_CATEGORIES, EMOJI_LIST, EMPTY_TYPES_DOC, FEEDBACK_SCHEMA, FORM_SCHEMA, PAGE_SCHEMA, PAIR_PREFIX, PULL_CACHE_MAX_AGE_MS, QUICK_REACTION_COUNT, REF_BLOCK_TYPES, activeEmojiQuery, addColumn, addComment, addEvent, addField2 as addField, addField as addFormField, addItem, addResponse, addTableColumn, addTableRow, addTableSelectOption, addType, addVaultObject, appendBlock, archiveType, automationOf, blockDef, buildAuthHeaders, buildEncryptor, buildLinkedSession, buildSession, clearAllTableFilters, clearAttachmentCache, clearPrimedSpaces, clearProp, clearTable, clearTableFilter, clearTableSort, completeDevicePairing, configureKv, configureOctoVault, consumePrimedSpaces, contentKindOf, continuationType, creatableTypes, createTable, defaultProps, deleteColumn, deleteEvent, deleteField as deleteFormField, deleteItem, deleteTableColumn, deleteTableRow, deriveSession, duplicateBlock, duplicateTableRow, endsListOnEmptyEnter, fetchWithTimeout, filterBlockTypes, flushReadsNow, getAiSettings, getEventsUrl, getMemberCap, getMutePrefs, getQuickReactions, getReadPrefs, getRoomReadAt, getSyncBase2 as getSyncBase, getSyncNamespace2 as getSyncNamespace, getSyncPrefix2 as getSyncPrefix, humanizeError, hydrateMutes, hydrateQuickReactions, hydrateReads, iconFor, iconForNode, insertBlock, isContainerType, isFindableType, isMultiline, isMuted, isOpenableObjectType, isRoomMuted, isSpaceMuted, kvGet2 as kvGet, kvRemove2 as kvRemove, kvSet2 as kvSet, labelFor, leaveSpace, listOrdinals, loadAiSettings, loadAttachment, loadMutesFromKv, loadObjectBlob, loadReadMarksFromKv, makeRegistry, matchEmoji, mdShortcut, mergeBlockIntoPrevious, monoFor, moveBlock, moveColumn, moveField as moveFormField, moveTableColumn, moveTableRow, objectDescriptor, objectLink, openStreamBotCredential, orderBetween, patchAutomation, patchEvent, patchField2 as patchField, patchField as patchFormField, patchItem, patchType, placeholderFor, primeSpaces, privacy, propsOf, pullCache, readBlocks, readColumns, readEvents, readFields, readItems, readProfile, readProfiles, readResponses, readSpaces, readTable, readThread, readThreads, removeBlock, removeComment, removeField, removeTableSelectOption, renameColumn, renameTableColumn, reorderFields, resetAiSettings, resetMutes, resetQuickReactions, resetReads, resolveThread, restoreBlock, routeForNode, saveAiSettings, saveQuickReactions, schemaFor, searchEmoji, seedDefaultColumns, sessionFromPersisted, setAiSettings, setBlockBookmark, setBlockChecked, setBlockCollapsed, setBlockIndent, setBlockRef, setBlockText, setBlockType, setColumnDone, setCommentBody, setProps, setQuickReactions, setRoomMute, setRoomReadAt, setSpaceMute, setTableCell, setTableColumnOptions, setTableColumnType, setTableColumnWidth, setTableFilter, setTableSort, showsInWorkTree, splitBlock, startDevicePairing, subscribeAiSettings, subscribeMutes, subscribeQuickReactions, subscribeReads, tasksForBoard, terms, threadLastActivity, toggleReaction, unvote, uploadAttachment, uploadObjectBlob, variantFor, visibleBlocks, visibleTableRowIds, vote, writeProfile };
|