@keepkit/core 0.28.4 → 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 +24 -4
- package/dist/{chunk-Y4U5DEGG.js → chunk-AUNFZU3A.js} +61 -6
- package/dist/chunk-AUNFZU3A.js.map +1 -0
- package/dist/{chunk-2TENUU7B.js → chunk-NZLAW4LY.js} +294 -30
- package/dist/chunk-NZLAW4LY.js.map +1 -0
- package/dist/core.d.ts +164 -7
- package/dist/core.js +469 -7
- package/dist/core.js.map +1 -1
- package/dist/react.d.ts +5 -5
- package/dist/react.js +2 -2
- package/dist/schema.d.ts +1 -1
- package/dist/{scope-Dtkxpnof.d.ts → scope-BteyH-b9.d.ts} +6 -3
- package/dist/storage.d.ts +28 -9
- package/dist/storage.js +1 -1
- package/dist/{store-45yaDeER.d.ts → store-DVP3QyVp.d.ts} +13 -4
- package/dist/{types-C_U5Q44o.d.ts → types-SKIL2p2c.d.ts} +34 -3
- package/package.json +1 -1
- package/dist/chunk-2TENUU7B.js.map +0 -1
- package/dist/chunk-Y4U5DEGG.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) {
|
|
@@ -411,6 +828,20 @@ var AuthenticatedSyncStorageController = class {
|
|
|
411
828
|
await this.current.removeCollection?.(id);
|
|
412
829
|
};
|
|
413
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
|
+
}
|
|
414
845
|
}
|
|
415
846
|
get storageKey() {
|
|
416
847
|
return this.current.storageKey;
|
|
@@ -537,6 +968,7 @@ var AuthenticatedSyncStorageController = class {
|
|
|
537
968
|
};
|
|
538
969
|
function createAuthenticatedRemote(options, scope) {
|
|
539
970
|
const pull = options.transport.pull;
|
|
971
|
+
const pullCollections = options.transport.pullCollections;
|
|
540
972
|
return {
|
|
541
973
|
push: async (operation) => {
|
|
542
974
|
try {
|
|
@@ -553,6 +985,22 @@ function createAuthenticatedRemote(options, scope) {
|
|
|
553
985
|
} catch (cause) {
|
|
554
986
|
return handleAuthFailure(cause, options, { scope });
|
|
555
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
|
+
}
|
|
556
1004
|
} : void 0
|
|
557
1005
|
};
|
|
558
1006
|
}
|
|
@@ -575,7 +1023,11 @@ function getAuthStatus(error) {
|
|
|
575
1023
|
export {
|
|
576
1024
|
DEFAULT_INDEXEDDB_DATABASE,
|
|
577
1025
|
DEFAULT_INDEXEDDB_STORE,
|
|
1026
|
+
DEFAULT_KEEP_HISTORY_KEY,
|
|
1027
|
+
DEFAULT_KEEP_HISTORY_LIMIT,
|
|
1028
|
+
DEFAULT_KEEP_PROGRESS_KEY,
|
|
578
1029
|
DEFAULT_KEEP_URL_PARAMS,
|
|
1030
|
+
DEFAULT_KEEP_VIEWING_RECORDS_KEY,
|
|
579
1031
|
DEFAULT_SAVED_VIEWS_STORAGE_KEY,
|
|
580
1032
|
DEFAULT_STORAGE_KEY,
|
|
581
1033
|
DEFAULT_SYNC_QUEUE_DATABASE,
|
|
@@ -587,8 +1039,10 @@ export {
|
|
|
587
1039
|
IndexedDBSyncQueueAdapter,
|
|
588
1040
|
KEEP_BACKUP_FORMAT,
|
|
589
1041
|
KEEP_BACKUP_VERSION,
|
|
1042
|
+
KeepActivityStorageError,
|
|
590
1043
|
KeepBackupImportError,
|
|
591
1044
|
KeepBackupParseError,
|
|
1045
|
+
KeepProgressStorageError,
|
|
592
1046
|
KeepSchemaValidationError,
|
|
593
1047
|
KeepStorageAccessError,
|
|
594
1048
|
KeepStorageError,
|
|
@@ -597,11 +1051,15 @@ export {
|
|
|
597
1051
|
KeepStore,
|
|
598
1052
|
KeepSyncAuthError,
|
|
599
1053
|
LocalStorageAdapter,
|
|
1054
|
+
LocalStorageKeepHistoryStorage,
|
|
1055
|
+
LocalStorageKeepProgressStorage,
|
|
600
1056
|
LocalStorageKeepSavedViewStorage,
|
|
1057
|
+
LocalStorageKeepViewingRecordStorage,
|
|
601
1058
|
LocalStorageSyncQueueAdapter,
|
|
602
1059
|
ScopedStorageAdapter,
|
|
603
1060
|
ScopedSyncQueueAdapter,
|
|
604
1061
|
SyncStorageAdapter,
|
|
1062
|
+
addKeepItemToCollection,
|
|
605
1063
|
createAuthenticatedSyncKit,
|
|
606
1064
|
createBrowserStorageAdapter,
|
|
607
1065
|
createInboxQuery,
|
|
@@ -614,12 +1072,14 @@ export {
|
|
|
614
1072
|
decodeKeepListQuery,
|
|
615
1073
|
encodeKeepListQuery,
|
|
616
1074
|
exportItems,
|
|
1075
|
+
getKeepCollectionItems,
|
|
617
1076
|
getKeepNavigationState,
|
|
618
1077
|
getKeepScopeKey,
|
|
619
1078
|
getTagCounts,
|
|
620
1079
|
importItems,
|
|
621
1080
|
isKeepItemMetadataStale,
|
|
622
1081
|
isKeepSyncAuthError,
|
|
1082
|
+
isProgressCompatible,
|
|
623
1083
|
isSameKeepScope,
|
|
624
1084
|
mergeKeepItems,
|
|
625
1085
|
mergeKeepListQueryFromUrl,
|
|
@@ -630,6 +1090,8 @@ export {
|
|
|
630
1090
|
parseKeepMeta,
|
|
631
1091
|
queryKeepItems,
|
|
632
1092
|
reconcileKeepItems,
|
|
1093
|
+
removeKeepItemFromCollection,
|
|
1094
|
+
reorderKeepCollectionItems,
|
|
633
1095
|
reorderKeepItems,
|
|
634
1096
|
revalidateKeepItems,
|
|
635
1097
|
serializeKeepListQuery,
|