@keepkit/core 0.28.3 → 0.28.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -2
- package/dist/{chunk-IIGN4U7N.js → chunk-AUNFZU3A.js} +61 -6
- package/dist/chunk-AUNFZU3A.js.map +1 -0
- package/dist/{chunk-MZFZQIM5.js → chunk-NZLAW4LY.js} +467 -27
- package/dist/chunk-NZLAW4LY.js.map +1 -0
- package/dist/core.d.ts +164 -7
- package/dist/core.js +483 -7
- package/dist/core.js.map +1 -1
- package/dist/react.d.ts +5 -5
- package/dist/react.js +58 -22
- package/dist/react.js.map +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/{scope-BsU6xvN9.d.ts → scope-BteyH-b9.d.ts} +9 -3
- package/dist/storage.d.ts +47 -9
- package/dist/storage.js +1 -1
- package/dist/{store-C7pEDMLm.d.ts → store-DVP3QyVp.d.ts} +13 -4
- package/dist/{types-CJIbuqTO.d.ts → types-SKIL2p2c.d.ts} +43 -3
- package/package.json +1 -1
- package/dist/chunk-IIGN4U7N.js.map +0 -1
- package/dist/chunk-MZFZQIM5.js.map +0 -1
package/dist/core.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
reconcileKeepItems,
|
|
20
20
|
reorderKeepItems,
|
|
21
21
|
revalidateKeepItems
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-AUNFZU3A.js";
|
|
23
23
|
import {
|
|
24
24
|
KeepSchemaValidationError,
|
|
25
25
|
parseKeepMeta,
|
|
@@ -53,7 +53,273 @@ import {
|
|
|
53
53
|
isKeepSyncAuthError,
|
|
54
54
|
isSameKeepScope,
|
|
55
55
|
normalizeKeepTags
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-NZLAW4LY.js";
|
|
57
|
+
|
|
58
|
+
// src/features/items/activity.ts
|
|
59
|
+
var DEFAULT_KEEP_HISTORY_KEY = "keepkit:view-history";
|
|
60
|
+
var DEFAULT_KEEP_VIEWING_RECORDS_KEY = "keepkit:viewing-records";
|
|
61
|
+
var DEFAULT_KEEP_HISTORY_LIMIT = 100;
|
|
62
|
+
var LocalStorageKeepHistoryStorage = class {
|
|
63
|
+
constructor(options = {}) {
|
|
64
|
+
this.key = options.key ?? DEFAULT_KEEP_HISTORY_KEY;
|
|
65
|
+
this.limitKey = `${this.key}:limit`;
|
|
66
|
+
this.storage = options.storage ?? getBrowserStorage();
|
|
67
|
+
this.initialLimit = normalizeLimit(options.maxEntries ?? DEFAULT_KEEP_HISTORY_LIMIT);
|
|
68
|
+
this.now = options.now ?? Date.now;
|
|
69
|
+
}
|
|
70
|
+
async getAll() {
|
|
71
|
+
return (await this.read(this.key, isHistoryEntryArray, "history")).sort(
|
|
72
|
+
(left, right) => right.lastViewedAt - left.lastViewedAt
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
async getLimit() {
|
|
76
|
+
if (!this.storage) return this.initialLimit;
|
|
77
|
+
const raw = this.readRaw(this.limitKey, "history-limit");
|
|
78
|
+
if (raw === null) return this.initialLimit;
|
|
79
|
+
const parsed = parseJson(raw, this.limitKey, "history-limit");
|
|
80
|
+
if (typeof parsed !== "number" || !Number.isInteger(parsed) || parsed < 1) {
|
|
81
|
+
throw new KeepActivityStorageError("KeepKit history limit is invalid.", this.limitKey);
|
|
82
|
+
}
|
|
83
|
+
return parsed;
|
|
84
|
+
}
|
|
85
|
+
async setLimit(limit) {
|
|
86
|
+
const normalized = normalizeLimit(limit);
|
|
87
|
+
this.writeRaw(this.limitKey, JSON.stringify(normalized), "history-limit");
|
|
88
|
+
const entries = await this.getAll();
|
|
89
|
+
await this.writeEntries(entries.slice(0, normalized));
|
|
90
|
+
}
|
|
91
|
+
async record(itemId, viewedAt = this.now()) {
|
|
92
|
+
const normalizedId = normalizeId(itemId);
|
|
93
|
+
assertTimestamp(viewedAt);
|
|
94
|
+
const entries = await this.getAll();
|
|
95
|
+
const previous = entries.find((entry) => entry.itemId === normalizedId);
|
|
96
|
+
const next = [
|
|
97
|
+
{ itemId: normalizedId, lastViewedAt: Math.max(previous?.lastViewedAt ?? 0, viewedAt) },
|
|
98
|
+
...entries.filter((entry) => entry.itemId !== normalizedId)
|
|
99
|
+
].sort((left, right) => right.lastViewedAt - left.lastViewedAt).slice(0, await this.getLimit());
|
|
100
|
+
await this.writeEntries(next);
|
|
101
|
+
return cloneHistoryEntries(next);
|
|
102
|
+
}
|
|
103
|
+
async remove(itemId) {
|
|
104
|
+
const normalizedId = normalizeId(itemId);
|
|
105
|
+
await this.writeEntries((await this.getAll()).filter((entry) => entry.itemId !== normalizedId));
|
|
106
|
+
}
|
|
107
|
+
async clear() {
|
|
108
|
+
this.writeRaw(this.key, "[]", "history");
|
|
109
|
+
}
|
|
110
|
+
async writeEntries(entries) {
|
|
111
|
+
this.writeRaw(this.key, JSON.stringify(entries), "history");
|
|
112
|
+
}
|
|
113
|
+
async read(key, validate, label) {
|
|
114
|
+
const raw = this.readRaw(key, label);
|
|
115
|
+
if (raw === null) return [];
|
|
116
|
+
const value = parseJson(raw, key, label);
|
|
117
|
+
if (!validate(value)) throw new KeepActivityStorageError(`KeepKit ${label} data is invalid.`, key);
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
readRaw(key, label) {
|
|
121
|
+
try {
|
|
122
|
+
return this.storage?.getItem(key) ?? null;
|
|
123
|
+
} catch (cause) {
|
|
124
|
+
throw new KeepActivityStorageError(`KeepKit could not read ${label} data.`, key, cause);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
writeRaw(key, value, label) {
|
|
128
|
+
try {
|
|
129
|
+
this.storage?.setItem(key, value);
|
|
130
|
+
} catch (cause) {
|
|
131
|
+
throw new KeepActivityStorageError(`KeepKit could not write ${label} data.`, key, cause);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var LocalStorageKeepViewingRecordStorage = class {
|
|
136
|
+
constructor(options = {}) {
|
|
137
|
+
this.key = options.key ?? DEFAULT_KEEP_VIEWING_RECORDS_KEY;
|
|
138
|
+
this.storage = options.storage ?? getBrowserStorage();
|
|
139
|
+
this.now = options.now ?? Date.now;
|
|
140
|
+
this.createId = options.createId ?? createRecordId;
|
|
141
|
+
}
|
|
142
|
+
async getAll(itemId) {
|
|
143
|
+
const records = await this.readRecords();
|
|
144
|
+
const filtered = itemId === void 0 ? records : records.filter((record) => record.itemId === itemId);
|
|
145
|
+
return filtered.sort((left, right) => right.viewedAt - left.viewedAt).map((record) => ({ ...record }));
|
|
146
|
+
}
|
|
147
|
+
async set(record) {
|
|
148
|
+
const normalized = normalizeViewingRecord(record);
|
|
149
|
+
const records = await this.readRecords();
|
|
150
|
+
this.writeRecords([...records.filter((entry) => entry.id !== normalized.id), normalized]);
|
|
151
|
+
}
|
|
152
|
+
async add(itemId, options = {}) {
|
|
153
|
+
const record = {
|
|
154
|
+
id: this.createId(),
|
|
155
|
+
itemId: normalizeId(itemId),
|
|
156
|
+
viewedAt: options.viewedAt ?? this.now(),
|
|
157
|
+
...options.note?.trim() ? { note: options.note.trim() } : {}
|
|
158
|
+
};
|
|
159
|
+
await this.set(record);
|
|
160
|
+
return { ...record };
|
|
161
|
+
}
|
|
162
|
+
async remove(id) {
|
|
163
|
+
this.writeRecords((await this.readRecords()).filter((record) => record.id !== id));
|
|
164
|
+
}
|
|
165
|
+
async removeForItem(itemId) {
|
|
166
|
+
const normalizedId = normalizeId(itemId);
|
|
167
|
+
this.writeRecords((await this.readRecords()).filter((record) => record.itemId !== normalizedId));
|
|
168
|
+
}
|
|
169
|
+
async clear() {
|
|
170
|
+
this.writeRecords([]);
|
|
171
|
+
}
|
|
172
|
+
async readRecords() {
|
|
173
|
+
const raw = this.readRaw();
|
|
174
|
+
if (raw === null) return [];
|
|
175
|
+
const value = parseJson(raw, this.key, "viewing records");
|
|
176
|
+
if (!isViewingRecordArray(value)) {
|
|
177
|
+
throw new KeepActivityStorageError("KeepKit viewing record data is invalid.", this.key);
|
|
178
|
+
}
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
readRaw() {
|
|
182
|
+
try {
|
|
183
|
+
return this.storage?.getItem(this.key) ?? null;
|
|
184
|
+
} catch (cause) {
|
|
185
|
+
throw new KeepActivityStorageError("KeepKit could not read viewing records.", this.key, cause);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
writeRecords(records) {
|
|
189
|
+
try {
|
|
190
|
+
this.storage?.setItem(this.key, JSON.stringify(records));
|
|
191
|
+
} catch (cause) {
|
|
192
|
+
throw new KeepActivityStorageError("KeepKit could not write viewing records.", this.key, cause);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var KeepActivityStorageError = class extends Error {
|
|
197
|
+
constructor(message, storageKey, cause) {
|
|
198
|
+
super(message);
|
|
199
|
+
this.name = "KeepActivityStorageError";
|
|
200
|
+
this.storageKey = storageKey;
|
|
201
|
+
this.cause = cause;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
function isHistoryEntryArray(value) {
|
|
205
|
+
return Array.isArray(value) && value.every(
|
|
206
|
+
(entry) => isRecord(entry) && typeof entry.itemId === "string" && entry.itemId.length > 0 && typeof entry.lastViewedAt === "number" && Number.isFinite(entry.lastViewedAt)
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
function isViewingRecordArray(value) {
|
|
210
|
+
return Array.isArray(value) && value.every(isViewingRecord);
|
|
211
|
+
}
|
|
212
|
+
function isViewingRecord(value) {
|
|
213
|
+
return isRecord(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.itemId === "string" && value.itemId.length > 0 && typeof value.viewedAt === "number" && Number.isFinite(value.viewedAt) && value.viewedAt >= 0 && (value.note === void 0 || typeof value.note === "string");
|
|
214
|
+
}
|
|
215
|
+
function normalizeViewingRecord(record) {
|
|
216
|
+
const normalized = {
|
|
217
|
+
id: normalizeId(record.id),
|
|
218
|
+
itemId: normalizeId(record.itemId),
|
|
219
|
+
viewedAt: record.viewedAt,
|
|
220
|
+
...record.note?.trim() ? { note: record.note.trim() } : {}
|
|
221
|
+
};
|
|
222
|
+
assertTimestamp(normalized.viewedAt);
|
|
223
|
+
return normalized;
|
|
224
|
+
}
|
|
225
|
+
function normalizeId(value) {
|
|
226
|
+
const normalized = value.trim();
|
|
227
|
+
if (!normalized) throw new TypeError("KeepKit item ids must not be empty.");
|
|
228
|
+
return normalized;
|
|
229
|
+
}
|
|
230
|
+
function normalizeLimit(value) {
|
|
231
|
+
if (!Number.isInteger(value) || value < 1) throw new RangeError("KeepKit history limit must be a positive integer.");
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
function assertTimestamp(value) {
|
|
235
|
+
if (!Number.isFinite(value) || value < 0) throw new TypeError("KeepKit timestamps must be non-negative numbers.");
|
|
236
|
+
}
|
|
237
|
+
function cloneHistoryEntries(entries) {
|
|
238
|
+
return entries.map((entry) => ({ ...entry }));
|
|
239
|
+
}
|
|
240
|
+
function parseJson(raw, key, label) {
|
|
241
|
+
try {
|
|
242
|
+
return JSON.parse(raw);
|
|
243
|
+
} catch (cause) {
|
|
244
|
+
throw new KeepActivityStorageError(`KeepKit ${label} data is not valid JSON.`, key, cause);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function isRecord(value) {
|
|
248
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
249
|
+
}
|
|
250
|
+
function getBrowserStorage() {
|
|
251
|
+
if (typeof window === "undefined") return void 0;
|
|
252
|
+
try {
|
|
253
|
+
return window.localStorage;
|
|
254
|
+
} catch {
|
|
255
|
+
return void 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function createRecordId() {
|
|
259
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
260
|
+
return `keepkit-view-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/features/items/collections.ts
|
|
264
|
+
async function getKeepCollectionItems(storage, collectionId) {
|
|
265
|
+
const memberships = await requireMemberships(storage);
|
|
266
|
+
const items = await storage.getAll();
|
|
267
|
+
const itemsById = new Map(items.map((item) => [item.id, item]));
|
|
268
|
+
return memberships.filter((membership) => membership.collectionId === collectionId).sort((left, right) => left.order - right.order).flatMap((membership) => {
|
|
269
|
+
const item = itemsById.get(membership.itemId);
|
|
270
|
+
return item ? [item] : [];
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
async function addKeepItemToCollection(storage, collectionId, itemId, order) {
|
|
274
|
+
if (!(await storage.getAll()).some((item) => item.id === itemId)) {
|
|
275
|
+
throw new Error(`KeepKit cannot add missing item "${itemId}" to a collection.`);
|
|
276
|
+
}
|
|
277
|
+
const collectionMemberships = (await requireMemberships(storage)).filter((membership) => membership.collectionId === collectionId).filter((membership) => membership.itemId !== itemId).sort((left, right) => left.order - right.order);
|
|
278
|
+
const insertionIndex = order ?? collectionMemberships.length;
|
|
279
|
+
if (!Number.isInteger(insertionIndex) || insertionIndex < 0) {
|
|
280
|
+
throw new RangeError("Collection order must be a non-negative integer.");
|
|
281
|
+
}
|
|
282
|
+
collectionMemberships.splice(Math.min(insertionIndex, collectionMemberships.length), 0, {
|
|
283
|
+
collectionId,
|
|
284
|
+
itemId,
|
|
285
|
+
order: insertionIndex
|
|
286
|
+
});
|
|
287
|
+
const reordered = collectionMemberships.map((membership, index) => ({ ...membership, order: index }));
|
|
288
|
+
for (const membership of reordered) await storage.setCollectionMembership?.(membership);
|
|
289
|
+
const added = reordered.find((membership) => membership.itemId === itemId);
|
|
290
|
+
if (!added) throw new Error("KeepKit failed to add the collection membership.");
|
|
291
|
+
return added;
|
|
292
|
+
}
|
|
293
|
+
async function removeKeepItemFromCollection(storage, collectionId, itemId) {
|
|
294
|
+
const memberships = await requireMemberships(storage);
|
|
295
|
+
await storage.removeCollectionMembership?.(collectionId, itemId);
|
|
296
|
+
const remaining = memberships.filter((membership) => membership.collectionId === collectionId && membership.itemId !== itemId).sort((left, right) => left.order - right.order).map((membership, order) => ({ ...membership, order }));
|
|
297
|
+
for (const membership of remaining) await storage.setCollectionMembership?.(membership);
|
|
298
|
+
}
|
|
299
|
+
async function reorderKeepCollectionItems(storage, collectionId, itemIds) {
|
|
300
|
+
const allMemberships = await requireMemberships(storage);
|
|
301
|
+
const current = allMemberships.filter((membership) => membership.collectionId === collectionId).sort((left, right) => left.order - right.order);
|
|
302
|
+
const currentIds = new Set(current.map((membership) => membership.itemId));
|
|
303
|
+
const orderedIds = [...itemIds.filter((id, index) => currentIds.has(id) && itemIds.indexOf(id) === index)];
|
|
304
|
+
for (const membership of current) {
|
|
305
|
+
if (!orderedIds.includes(membership.itemId)) orderedIds.push(membership.itemId);
|
|
306
|
+
}
|
|
307
|
+
const currentByItemId = new Map(current.map((membership) => [membership.itemId, membership]));
|
|
308
|
+
const reordered = orderedIds.map((itemId, order) => ({
|
|
309
|
+
...currentByItemId.get(itemId) ?? {},
|
|
310
|
+
collectionId,
|
|
311
|
+
itemId,
|
|
312
|
+
order
|
|
313
|
+
}));
|
|
314
|
+
for (const membership of reordered) await storage.setCollectionMembership?.(membership);
|
|
315
|
+
return reordered;
|
|
316
|
+
}
|
|
317
|
+
async function requireMemberships(storage) {
|
|
318
|
+
if (!storage.getCollectionMemberships || !storage.setCollectionMembership || !storage.removeCollectionMembership) {
|
|
319
|
+
throw new Error("KeepKit storage adapter does not support collection memberships.");
|
|
320
|
+
}
|
|
321
|
+
return storage.getCollectionMemberships();
|
|
322
|
+
}
|
|
57
323
|
|
|
58
324
|
// src/features/items/presets.ts
|
|
59
325
|
function createKeepKitPreset(options = {}) {
|
|
@@ -82,6 +348,157 @@ function createKeepKitPreset(options = {}) {
|
|
|
82
348
|
}
|
|
83
349
|
var createKeepKitSetup = createKeepKitPreset;
|
|
84
350
|
|
|
351
|
+
// src/features/items/progress.ts
|
|
352
|
+
var DEFAULT_KEEP_PROGRESS_KEY = "keepkit:progress";
|
|
353
|
+
var LocalStorageKeepProgressStorage = class {
|
|
354
|
+
constructor(options = {}) {
|
|
355
|
+
this.key = options.key ?? DEFAULT_KEEP_PROGRESS_KEY;
|
|
356
|
+
this.storage = options.storage ?? getBrowserStorage2();
|
|
357
|
+
this.now = options.now ?? Date.now;
|
|
358
|
+
}
|
|
359
|
+
async getAll() {
|
|
360
|
+
const raw = this.readRaw();
|
|
361
|
+
if (raw === null) return [];
|
|
362
|
+
let value;
|
|
363
|
+
try {
|
|
364
|
+
value = JSON.parse(raw);
|
|
365
|
+
} catch (cause) {
|
|
366
|
+
throw new KeepProgressStorageError("KeepKit progress data is not valid JSON.", this.key, cause);
|
|
367
|
+
}
|
|
368
|
+
if (!Array.isArray(value) || !value.every(isProgressRecord)) {
|
|
369
|
+
throw new KeepProgressStorageError("KeepKit progress data is invalid.", this.key);
|
|
370
|
+
}
|
|
371
|
+
return value.map((record) => ({ ...record }));
|
|
372
|
+
}
|
|
373
|
+
async getCourse(courseId) {
|
|
374
|
+
const id = normalizeId2(courseId, "course");
|
|
375
|
+
const record = (await this.getAll()).find((entry) => entry.kind === "course" && entry.courseId === id);
|
|
376
|
+
return record?.kind === "course" ? record : void 0;
|
|
377
|
+
}
|
|
378
|
+
async getItem(itemId, compatibility) {
|
|
379
|
+
const id = normalizeId2(itemId, "item");
|
|
380
|
+
const record = (await this.getAll()).find((entry) => entry.kind === "item" && entry.itemId === id);
|
|
381
|
+
if (record?.kind !== "item") return void 0;
|
|
382
|
+
if (!isProgressCompatible(record, compatibility)) return void 0;
|
|
383
|
+
return record;
|
|
384
|
+
}
|
|
385
|
+
async saveCourse(courseId, currentItemId) {
|
|
386
|
+
const record = {
|
|
387
|
+
kind: "course",
|
|
388
|
+
courseId: normalizeId2(courseId, "course"),
|
|
389
|
+
...currentItemId?.trim() ? { currentItemId: currentItemId.trim() } : {},
|
|
390
|
+
updatedAt: this.now()
|
|
391
|
+
};
|
|
392
|
+
await this.set(record);
|
|
393
|
+
return record;
|
|
394
|
+
}
|
|
395
|
+
async saveItem(itemId, progress) {
|
|
396
|
+
const record = {
|
|
397
|
+
kind: "item",
|
|
398
|
+
itemId: normalizeId2(itemId, "item"),
|
|
399
|
+
...progress,
|
|
400
|
+
updatedAt: this.now()
|
|
401
|
+
};
|
|
402
|
+
await this.set(record);
|
|
403
|
+
return record;
|
|
404
|
+
}
|
|
405
|
+
async set(record) {
|
|
406
|
+
const normalized = normalizeProgressRecord(record);
|
|
407
|
+
const records = await this.getAll();
|
|
408
|
+
const identity = getProgressIdentity(normalized);
|
|
409
|
+
this.writeRecords([...records.filter((entry) => getProgressIdentity(entry) !== identity), normalized]);
|
|
410
|
+
}
|
|
411
|
+
async resetCourse(courseId) {
|
|
412
|
+
const id = normalizeId2(courseId, "course");
|
|
413
|
+
this.writeRecords((await this.getAll()).filter((entry) => entry.kind !== "course" || entry.courseId !== id));
|
|
414
|
+
}
|
|
415
|
+
async resetItem(itemId) {
|
|
416
|
+
const id = normalizeId2(itemId, "item");
|
|
417
|
+
this.writeRecords((await this.getAll()).filter((entry) => entry.kind !== "item" || entry.itemId !== id));
|
|
418
|
+
}
|
|
419
|
+
async clear() {
|
|
420
|
+
this.writeRecords([]);
|
|
421
|
+
}
|
|
422
|
+
readRaw() {
|
|
423
|
+
try {
|
|
424
|
+
return this.storage?.getItem(this.key) ?? null;
|
|
425
|
+
} catch (cause) {
|
|
426
|
+
throw new KeepProgressStorageError("KeepKit could not read progress data.", this.key, cause);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
writeRecords(records) {
|
|
430
|
+
try {
|
|
431
|
+
this.storage?.setItem(this.key, JSON.stringify(records));
|
|
432
|
+
} catch (cause) {
|
|
433
|
+
throw new KeepProgressStorageError("KeepKit could not write progress data.", this.key, cause);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
function isProgressCompatible(record, compatibility) {
|
|
438
|
+
if (record.language !== void 0 && record.language !== compatibility?.language) return false;
|
|
439
|
+
if (record.contentVersion !== void 0 && record.contentVersion !== compatibility?.contentVersion) return false;
|
|
440
|
+
if (compatibility?.language !== void 0 && record.language !== compatibility.language) return false;
|
|
441
|
+
if (compatibility?.contentVersion !== void 0 && record.contentVersion !== compatibility.contentVersion)
|
|
442
|
+
return false;
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
var KeepProgressStorageError = class extends Error {
|
|
446
|
+
constructor(message, storageKey, cause) {
|
|
447
|
+
super(message);
|
|
448
|
+
this.name = "KeepProgressStorageError";
|
|
449
|
+
this.storageKey = storageKey;
|
|
450
|
+
this.cause = cause;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
function isProgressRecord(value) {
|
|
454
|
+
if (!isRecord2(value) || typeof value.updatedAt !== "number" || !Number.isFinite(value.updatedAt) || value.updatedAt < 0)
|
|
455
|
+
return false;
|
|
456
|
+
if (value.kind === "course") {
|
|
457
|
+
return typeof value.courseId === "string" && value.courseId.length > 0 && (value.currentItemId === void 0 || typeof value.currentItemId === "string");
|
|
458
|
+
}
|
|
459
|
+
if (value.kind !== "item") return false;
|
|
460
|
+
return typeof value.itemId === "string" && value.itemId.length > 0 && (value.audioPositionMs === void 0 || typeof value.audioPositionMs === "number" && Number.isFinite(value.audioPositionMs) && value.audioPositionMs >= 0) && (value.readingPosition === void 0 || typeof value.readingPosition === "number" && Number.isFinite(value.readingPosition) && value.readingPosition >= 0) && (value.language === void 0 || typeof value.language === "string") && (value.contentVersion === void 0 || typeof value.contentVersion === "string");
|
|
461
|
+
}
|
|
462
|
+
function normalizeProgressRecord(record) {
|
|
463
|
+
if (!isProgressRecord(record)) throw new TypeError("KeepKit progress record is invalid.");
|
|
464
|
+
if (record.kind === "course") {
|
|
465
|
+
return {
|
|
466
|
+
kind: "course",
|
|
467
|
+
courseId: normalizeId2(record.courseId, "course"),
|
|
468
|
+
...record.currentItemId?.trim() ? { currentItemId: record.currentItemId.trim() } : {},
|
|
469
|
+
updatedAt: record.updatedAt
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
kind: "item",
|
|
474
|
+
itemId: normalizeId2(record.itemId, "item"),
|
|
475
|
+
...record.audioPositionMs !== void 0 ? { audioPositionMs: record.audioPositionMs } : {},
|
|
476
|
+
...record.readingPosition !== void 0 ? { readingPosition: record.readingPosition } : {},
|
|
477
|
+
...record.language !== void 0 ? { language: record.language } : {},
|
|
478
|
+
...record.contentVersion !== void 0 ? { contentVersion: record.contentVersion } : {},
|
|
479
|
+
updatedAt: record.updatedAt
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function getProgressIdentity(record) {
|
|
483
|
+
return record.kind === "course" ? `course:${record.courseId}` : `item:${record.itemId}`;
|
|
484
|
+
}
|
|
485
|
+
function normalizeId2(value, kind) {
|
|
486
|
+
const normalized = value.trim();
|
|
487
|
+
if (!normalized) throw new TypeError(`KeepKit ${kind} ids must not be empty.`);
|
|
488
|
+
return normalized;
|
|
489
|
+
}
|
|
490
|
+
function isRecord2(value) {
|
|
491
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
492
|
+
}
|
|
493
|
+
function getBrowserStorage2() {
|
|
494
|
+
if (typeof window === "undefined") return void 0;
|
|
495
|
+
try {
|
|
496
|
+
return window.localStorage;
|
|
497
|
+
} catch {
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
85
502
|
// src/features/items/saved-views.ts
|
|
86
503
|
var DEFAULT_SAVED_VIEWS_STORAGE_KEY = "keepkit:saved-views";
|
|
87
504
|
var LocalStorageKeepSavedViewStorage = class {
|
|
@@ -149,13 +566,13 @@ function isKeepListQuery(value) {
|
|
|
149
566
|
}
|
|
150
567
|
if (query.pinnedFirst !== void 0 && typeof query.pinnedFirst !== "boolean") return false;
|
|
151
568
|
if (query.sort !== void 0) {
|
|
152
|
-
if (!
|
|
569
|
+
if (!isRecord3(query.sort) || !["savedAt", "updatedAt", "lastOpenedAt"].includes(String(query.sort.by)))
|
|
153
570
|
return false;
|
|
154
571
|
if (query.sort.direction !== void 0 && query.sort.direction !== "asc" && query.sort.direction !== "desc")
|
|
155
572
|
return false;
|
|
156
573
|
}
|
|
157
574
|
if (query.search !== void 0) {
|
|
158
|
-
if (!
|
|
575
|
+
if (!isRecord3(query.search)) return false;
|
|
159
576
|
if (query.search.query !== void 0 && typeof query.search.query !== "string") return false;
|
|
160
577
|
if (query.search.mode !== void 0 && query.search.mode !== "and" && query.search.mode !== "or") return false;
|
|
161
578
|
if (query.search.tokenize !== void 0 && typeof query.search.tokenize !== "boolean") return false;
|
|
@@ -164,7 +581,7 @@ function isKeepListQuery(value) {
|
|
|
164
581
|
}
|
|
165
582
|
}
|
|
166
583
|
if (query.activity !== void 0) {
|
|
167
|
-
if (!
|
|
584
|
+
if (!isRecord3(query.activity)) return false;
|
|
168
585
|
if (query.activity.opened !== void 0 && query.activity.opened !== "ever" && query.activity.opened !== "never") {
|
|
169
586
|
return false;
|
|
170
587
|
}
|
|
@@ -178,7 +595,7 @@ function isKeepListQuery(value) {
|
|
|
178
595
|
return false;
|
|
179
596
|
}
|
|
180
597
|
if (query.pagination !== void 0) {
|
|
181
|
-
if (!
|
|
598
|
+
if (!isRecord3(query.pagination) || [query.pagination.page, query.pagination.pageSize].some(isInvalidNumber))
|
|
182
599
|
return false;
|
|
183
600
|
}
|
|
184
601
|
const organization = query.organization;
|
|
@@ -193,7 +610,7 @@ function isKeepListQuery(value) {
|
|
|
193
610
|
}
|
|
194
611
|
return true;
|
|
195
612
|
}
|
|
196
|
-
function
|
|
613
|
+
function isRecord3(value) {
|
|
197
614
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
198
615
|
}
|
|
199
616
|
function isInvalidNumber(value) {
|
|
@@ -397,6 +814,34 @@ var AuthenticatedSyncStorageController = class {
|
|
|
397
814
|
this.currentScope = options.scope;
|
|
398
815
|
this.current = this.createAdapter(this.currentScope);
|
|
399
816
|
this.attach(this.current);
|
|
817
|
+
if (this.current.getCollections && this.current.setCollection && this.current.removeCollection) {
|
|
818
|
+
this.getCollections = async () => {
|
|
819
|
+
await this.ensureScope();
|
|
820
|
+
return this.current.getCollections?.() ?? [];
|
|
821
|
+
};
|
|
822
|
+
this.setCollection = async (collection) => {
|
|
823
|
+
await this.ensureScope();
|
|
824
|
+
await this.current.setCollection?.(collection);
|
|
825
|
+
};
|
|
826
|
+
this.removeCollection = async (id) => {
|
|
827
|
+
await this.ensureScope();
|
|
828
|
+
await this.current.removeCollection?.(id);
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
if (this.current.getCollectionMemberships && this.current.setCollectionMembership && this.current.removeCollectionMembership) {
|
|
832
|
+
this.getCollectionMemberships = async () => {
|
|
833
|
+
await this.ensureScope();
|
|
834
|
+
return this.current.getCollectionMemberships?.() ?? [];
|
|
835
|
+
};
|
|
836
|
+
this.setCollectionMembership = async (membership) => {
|
|
837
|
+
await this.ensureScope();
|
|
838
|
+
await this.current.setCollectionMembership?.(membership);
|
|
839
|
+
};
|
|
840
|
+
this.removeCollectionMembership = async (collectionId, itemId) => {
|
|
841
|
+
await this.ensureScope();
|
|
842
|
+
await this.current.removeCollectionMembership?.(collectionId, itemId);
|
|
843
|
+
};
|
|
844
|
+
}
|
|
400
845
|
}
|
|
401
846
|
get storageKey() {
|
|
402
847
|
return this.current.storageKey;
|
|
@@ -523,6 +968,7 @@ var AuthenticatedSyncStorageController = class {
|
|
|
523
968
|
};
|
|
524
969
|
function createAuthenticatedRemote(options, scope) {
|
|
525
970
|
const pull = options.transport.pull;
|
|
971
|
+
const pullCollections = options.transport.pullCollections;
|
|
526
972
|
return {
|
|
527
973
|
push: async (operation) => {
|
|
528
974
|
try {
|
|
@@ -539,6 +985,22 @@ function createAuthenticatedRemote(options, scope) {
|
|
|
539
985
|
} catch (cause) {
|
|
540
986
|
return handleAuthFailure(cause, options, { scope });
|
|
541
987
|
}
|
|
988
|
+
} : void 0,
|
|
989
|
+
pushCollection: options.transport.pushCollection ? async (collectionOperation) => {
|
|
990
|
+
try {
|
|
991
|
+
const token = await options.getAuthToken();
|
|
992
|
+
await options.transport.pushCollection?.(collectionOperation, { token, scope, collectionOperation });
|
|
993
|
+
} catch (cause) {
|
|
994
|
+
return handleAuthFailure(cause, options, { collectionOperation, scope });
|
|
995
|
+
}
|
|
996
|
+
} : void 0,
|
|
997
|
+
pullCollections: pullCollections ? async () => {
|
|
998
|
+
try {
|
|
999
|
+
const token = await options.getAuthToken();
|
|
1000
|
+
return await pullCollections({ token, scope });
|
|
1001
|
+
} catch (cause) {
|
|
1002
|
+
return handleAuthFailure(cause, options, { scope });
|
|
1003
|
+
}
|
|
542
1004
|
} : void 0
|
|
543
1005
|
};
|
|
544
1006
|
}
|
|
@@ -561,7 +1023,11 @@ function getAuthStatus(error) {
|
|
|
561
1023
|
export {
|
|
562
1024
|
DEFAULT_INDEXEDDB_DATABASE,
|
|
563
1025
|
DEFAULT_INDEXEDDB_STORE,
|
|
1026
|
+
DEFAULT_KEEP_HISTORY_KEY,
|
|
1027
|
+
DEFAULT_KEEP_HISTORY_LIMIT,
|
|
1028
|
+
DEFAULT_KEEP_PROGRESS_KEY,
|
|
564
1029
|
DEFAULT_KEEP_URL_PARAMS,
|
|
1030
|
+
DEFAULT_KEEP_VIEWING_RECORDS_KEY,
|
|
565
1031
|
DEFAULT_SAVED_VIEWS_STORAGE_KEY,
|
|
566
1032
|
DEFAULT_STORAGE_KEY,
|
|
567
1033
|
DEFAULT_SYNC_QUEUE_DATABASE,
|
|
@@ -573,8 +1039,10 @@ export {
|
|
|
573
1039
|
IndexedDBSyncQueueAdapter,
|
|
574
1040
|
KEEP_BACKUP_FORMAT,
|
|
575
1041
|
KEEP_BACKUP_VERSION,
|
|
1042
|
+
KeepActivityStorageError,
|
|
576
1043
|
KeepBackupImportError,
|
|
577
1044
|
KeepBackupParseError,
|
|
1045
|
+
KeepProgressStorageError,
|
|
578
1046
|
KeepSchemaValidationError,
|
|
579
1047
|
KeepStorageAccessError,
|
|
580
1048
|
KeepStorageError,
|
|
@@ -583,11 +1051,15 @@ export {
|
|
|
583
1051
|
KeepStore,
|
|
584
1052
|
KeepSyncAuthError,
|
|
585
1053
|
LocalStorageAdapter,
|
|
1054
|
+
LocalStorageKeepHistoryStorage,
|
|
1055
|
+
LocalStorageKeepProgressStorage,
|
|
586
1056
|
LocalStorageKeepSavedViewStorage,
|
|
1057
|
+
LocalStorageKeepViewingRecordStorage,
|
|
587
1058
|
LocalStorageSyncQueueAdapter,
|
|
588
1059
|
ScopedStorageAdapter,
|
|
589
1060
|
ScopedSyncQueueAdapter,
|
|
590
1061
|
SyncStorageAdapter,
|
|
1062
|
+
addKeepItemToCollection,
|
|
591
1063
|
createAuthenticatedSyncKit,
|
|
592
1064
|
createBrowserStorageAdapter,
|
|
593
1065
|
createInboxQuery,
|
|
@@ -600,12 +1072,14 @@ export {
|
|
|
600
1072
|
decodeKeepListQuery,
|
|
601
1073
|
encodeKeepListQuery,
|
|
602
1074
|
exportItems,
|
|
1075
|
+
getKeepCollectionItems,
|
|
603
1076
|
getKeepNavigationState,
|
|
604
1077
|
getKeepScopeKey,
|
|
605
1078
|
getTagCounts,
|
|
606
1079
|
importItems,
|
|
607
1080
|
isKeepItemMetadataStale,
|
|
608
1081
|
isKeepSyncAuthError,
|
|
1082
|
+
isProgressCompatible,
|
|
609
1083
|
isSameKeepScope,
|
|
610
1084
|
mergeKeepItems,
|
|
611
1085
|
mergeKeepListQueryFromUrl,
|
|
@@ -616,6 +1090,8 @@ export {
|
|
|
616
1090
|
parseKeepMeta,
|
|
617
1091
|
queryKeepItems,
|
|
618
1092
|
reconcileKeepItems,
|
|
1093
|
+
removeKeepItemFromCollection,
|
|
1094
|
+
reorderKeepCollectionItems,
|
|
619
1095
|
reorderKeepItems,
|
|
620
1096
|
revalidateKeepItems,
|
|
621
1097
|
serializeKeepListQuery,
|