@keepkit/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/chunk-5QSZP6MT.js +1 -0
- package/dist/chunk-5QSZP6MT.js.map +1 -0
- package/dist/{chunk-H4A322SZ.js → chunk-C3SOQCVW.js} +214 -14
- package/dist/chunk-C3SOQCVW.js.map +1 -0
- package/dist/chunk-PLT2WJVM.js +79 -0
- package/dist/chunk-PLT2WJVM.js.map +1 -0
- package/dist/chunk-THZ3ACR2.js +41 -0
- package/dist/chunk-THZ3ACR2.js.map +1 -0
- package/dist/core.d.ts +66 -0
- package/dist/core.js +214 -0
- package/dist/core.js.map +1 -0
- package/dist/react.d.ts +151 -0
- package/dist/{index.js → react.js} +127 -359
- package/dist/react.js.map +1 -0
- package/dist/schema.d.ts +15 -0
- package/dist/schema.js +11 -0
- package/dist/schema.js.map +1 -0
- package/dist/storage.d.ts +227 -1
- package/dist/storage.js +8 -1
- package/dist/store-BAvx_Iaw.d.ts +63 -0
- package/dist/types-DtILwEQ0.d.ts +162 -0
- package/package.json +12 -7
- package/dist/chunk-H4A322SZ.js.map +0 -1
- package/dist/index.d.ts +0 -224
- package/dist/index.js.map +0 -1
- package/dist/storage-DPIKGno6.d.ts +0 -316
|
@@ -1,190 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
KeepStorageError,
|
|
12
|
-
KeepStorageParseError,
|
|
13
|
-
KeepStorageQuotaError,
|
|
14
|
-
LocalStorageAdapter,
|
|
15
|
-
LocalStorageSyncQueueAdapter,
|
|
16
|
-
SyncStorageAdapter,
|
|
17
|
-
createStorageAdapter,
|
|
3
|
+
KeepStore,
|
|
4
|
+
queryKeepItems
|
|
5
|
+
} from "./chunk-PLT2WJVM.js";
|
|
6
|
+
import {
|
|
7
|
+
parseKeepMeta
|
|
8
|
+
} from "./chunk-THZ3ACR2.js";
|
|
9
|
+
import "./chunk-5QSZP6MT.js";
|
|
10
|
+
import {
|
|
11
|
+
createBrowserStorageAdapter,
|
|
18
12
|
normalizeKeepTags
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
|
|
21
|
-
// src/migration.ts
|
|
22
|
-
async function mergeKeepItems(localItems, target) {
|
|
23
|
-
if (target.merge) return target.merge(localItems);
|
|
24
|
-
const remoteItems = await target.getAll();
|
|
25
|
-
const byId = new Map(remoteItems.map((item) => [item.id, item]));
|
|
26
|
-
for (const localItem of localItems) {
|
|
27
|
-
const remoteItem = byId.get(localItem.id);
|
|
28
|
-
if (!remoteItem || localItem.updatedAt > remoteItem.updatedAt) {
|
|
29
|
-
byId.set(localItem.id, localItem);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
const merged = [...byId.values()].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
33
|
-
await Promise.all(merged.map((item) => target.set(item)));
|
|
34
|
-
return merged;
|
|
35
|
-
}
|
|
36
|
-
async function migrateKeepItems(source, target) {
|
|
37
|
-
const localItems = await source.getAll();
|
|
38
|
-
const merged = await mergeKeepItems(localItems, target);
|
|
39
|
-
await source.clear();
|
|
40
|
-
return merged;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/schema.ts
|
|
44
|
-
var KeepSchemaValidationError = class extends Error {
|
|
45
|
-
constructor(message, options = {}) {
|
|
46
|
-
super(message);
|
|
47
|
-
this.name = "KeepSchemaValidationError";
|
|
48
|
-
this.cause = options.cause;
|
|
49
|
-
this.itemId = options.itemId;
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
async function parseKeepMeta(schema, value) {
|
|
53
|
-
try {
|
|
54
|
-
if ("parse" in schema) return await schema.parse(value);
|
|
55
|
-
if ("safeParse" in schema) {
|
|
56
|
-
const result2 = await schema.safeParse(value);
|
|
57
|
-
if (result2.success) return result2.data;
|
|
58
|
-
throw new KeepSchemaValidationError("KeepKit metadata did not match the configured schema.", {
|
|
59
|
-
cause: result2.error
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
const result = await schema["~standard"].validate(value);
|
|
63
|
-
if (!result.issues && "value" in result) return result.value;
|
|
64
|
-
throw new KeepSchemaValidationError("KeepKit metadata did not match the configured schema.", {
|
|
65
|
-
cause: result.issues
|
|
66
|
-
});
|
|
67
|
-
} catch (cause) {
|
|
68
|
-
if (cause instanceof KeepSchemaValidationError) throw cause;
|
|
69
|
-
throw new KeepSchemaValidationError("KeepKit metadata did not match the configured schema.", {
|
|
70
|
-
cause
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async function validateKeepItem(item, schema) {
|
|
75
|
-
return { ...item, meta: await parseKeepMeta(schema, item.meta) };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/backup.ts
|
|
79
|
-
var KEEP_BACKUP_FORMAT = "keepkit";
|
|
80
|
-
var KEEP_BACKUP_VERSION = 1;
|
|
81
|
-
var KeepBackupParseError = class extends Error {
|
|
82
|
-
constructor(message, options) {
|
|
83
|
-
super(message);
|
|
84
|
-
this.name = "KeepBackupParseError";
|
|
85
|
-
if (options?.cause !== void 0) this.cause = options.cause;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
var KeepBackupImportError = class extends Error {
|
|
89
|
-
constructor(message, options) {
|
|
90
|
-
super(message);
|
|
91
|
-
this.name = "KeepBackupImportError";
|
|
92
|
-
this.mode = options.mode;
|
|
93
|
-
this.imported = options.imported;
|
|
94
|
-
this.failed = options.failed;
|
|
95
|
-
if (options.cause !== void 0) this.cause = options.cause;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
async function exportItems(adapter) {
|
|
99
|
-
const backup = {
|
|
100
|
-
format: KEEP_BACKUP_FORMAT,
|
|
101
|
-
version: KEEP_BACKUP_VERSION,
|
|
102
|
-
exportedAt: Date.now(),
|
|
103
|
-
items: await adapter.getAll()
|
|
104
|
-
};
|
|
105
|
-
return JSON.stringify(backup, null, 2);
|
|
106
|
-
}
|
|
107
|
-
async function importItems(adapter, data, options = {}) {
|
|
108
|
-
const backup = parseBackup(data);
|
|
109
|
-
const mode = options.mode ?? "merge";
|
|
110
|
-
const validItems = [];
|
|
111
|
-
let failed = 0;
|
|
112
|
-
for (const item of backup.items) {
|
|
113
|
-
if (!options.schema) {
|
|
114
|
-
validItems.push(item);
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
validItems.push(await validateKeepItem(item, options.schema));
|
|
119
|
-
} catch (cause) {
|
|
120
|
-
options.onInvalidItem?.(cause, item);
|
|
121
|
-
if ((options.invalidItemPolicy ?? "error") === "drop") {
|
|
122
|
-
failed += 1;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
throw cause;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
let items;
|
|
129
|
-
if (mode === "merge") {
|
|
130
|
-
try {
|
|
131
|
-
items = await mergeKeepItems(validItems, adapter);
|
|
132
|
-
} catch (cause) {
|
|
133
|
-
throw new KeepBackupImportError("KeepKit could not merge the backup.", {
|
|
134
|
-
mode,
|
|
135
|
-
imported: 0,
|
|
136
|
-
failed: validItems.length + failed,
|
|
137
|
-
cause
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
let imported = 0;
|
|
142
|
-
try {
|
|
143
|
-
await adapter.clear();
|
|
144
|
-
for (const item of validItems) {
|
|
145
|
-
await adapter.set(item);
|
|
146
|
-
imported += 1;
|
|
147
|
-
}
|
|
148
|
-
items = await adapter.getAll();
|
|
149
|
-
} catch (cause) {
|
|
150
|
-
throw new KeepBackupImportError("KeepKit could not replace the stored items.", {
|
|
151
|
-
mode,
|
|
152
|
-
imported,
|
|
153
|
-
failed: validItems.length + failed - imported,
|
|
154
|
-
cause
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return { mode, imported: validItems.length, failed, total: items.length, items };
|
|
159
|
-
}
|
|
160
|
-
function parseBackup(data) {
|
|
161
|
-
let value = data;
|
|
162
|
-
if (typeof data === "string") {
|
|
163
|
-
try {
|
|
164
|
-
value = JSON.parse(data);
|
|
165
|
-
} catch (cause) {
|
|
166
|
-
throw new KeepBackupParseError("KeepKit backup is not valid JSON.", { cause });
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (!isRecord(value)) throw new KeepBackupParseError("KeepKit backup must be an object.");
|
|
170
|
-
if (value.format !== KEEP_BACKUP_FORMAT || value.version !== KEEP_BACKUP_VERSION) {
|
|
171
|
-
throw new KeepBackupParseError("KeepKit backup format or version is unsupported.");
|
|
172
|
-
}
|
|
173
|
-
if (typeof value.exportedAt !== "number" || !Number.isFinite(value.exportedAt)) {
|
|
174
|
-
throw new KeepBackupParseError("KeepKit backup has an invalid export timestamp.");
|
|
175
|
-
}
|
|
176
|
-
if (!Array.isArray(value.items) || !value.items.every(isKeepItem)) {
|
|
177
|
-
throw new KeepBackupParseError("KeepKit backup contains invalid items.");
|
|
178
|
-
}
|
|
179
|
-
return value;
|
|
180
|
-
}
|
|
181
|
-
function isKeepItem(value) {
|
|
182
|
-
if (!isRecord(value)) return false;
|
|
183
|
-
return typeof value.id === "string" && typeof value.savedAt === "number" && Number.isFinite(value.savedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && "meta" in value && (value.targetType === void 0 || typeof value.targetType === "string") && (value.note === void 0 || typeof value.note === "string") && (value.schemaVersion === void 0 || typeof value.schemaVersion === "number" && Number.isFinite(value.schemaVersion)) && (value.revision === void 0 || typeof value.revision === "string") && (value.tags === void 0 || Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === "string"));
|
|
184
|
-
}
|
|
185
|
-
function isRecord(value) {
|
|
186
|
-
return typeof value === "object" && value !== null;
|
|
187
|
-
}
|
|
13
|
+
} from "./chunk-C3SOQCVW.js";
|
|
188
14
|
|
|
189
15
|
// src/hooks/useKeepItem.ts
|
|
190
16
|
import { useCallback as useCallback3 } from "react";
|
|
@@ -199,39 +25,13 @@ import {
|
|
|
199
25
|
useRef,
|
|
200
26
|
useSyncExternalStore
|
|
201
27
|
} from "react";
|
|
202
|
-
|
|
203
|
-
// src/store.ts
|
|
204
|
-
var KeepStore = class {
|
|
205
|
-
constructor(initialState) {
|
|
206
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
207
|
-
this.getSnapshot = () => this.state;
|
|
208
|
-
this.subscribe = (listener) => {
|
|
209
|
-
this.listeners.add(listener);
|
|
210
|
-
return () => this.listeners.delete(listener);
|
|
211
|
-
};
|
|
212
|
-
this.state = initialState;
|
|
213
|
-
}
|
|
214
|
-
setState(next) {
|
|
215
|
-
let changed = false;
|
|
216
|
-
for (const key of Object.keys(next)) {
|
|
217
|
-
if (!Object.is(this.state[key], next[key])) {
|
|
218
|
-
changed = true;
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (!changed) return;
|
|
223
|
-
this.state = { ...this.state, ...next };
|
|
224
|
-
for (const listener of this.listeners) listener();
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
// src/KeepProvider.tsx
|
|
229
28
|
import { jsx } from "react/jsx-runtime";
|
|
230
|
-
var defaultStorage =
|
|
29
|
+
var defaultStorage = createBrowserStorageAdapter();
|
|
231
30
|
var KeepContext = createContext(null);
|
|
232
31
|
var KeepStoreContext = createContext(null);
|
|
233
32
|
function KeepProvider({
|
|
234
33
|
storage = defaultStorage,
|
|
34
|
+
initialItems,
|
|
235
35
|
onSave,
|
|
236
36
|
onRemove,
|
|
237
37
|
onNoteUpdate,
|
|
@@ -249,7 +49,7 @@ function KeepProvider({
|
|
|
249
49
|
const storeRef = useRef(null);
|
|
250
50
|
if (!storeRef.current) {
|
|
251
51
|
storeRef.current = new KeepStore({
|
|
252
|
-
items: [],
|
|
52
|
+
items: initialItems ? [...initialItems] : [],
|
|
253
53
|
isLoading: true,
|
|
254
54
|
isHydrated: false,
|
|
255
55
|
isMutating: false,
|
|
@@ -276,10 +76,7 @@ function KeepProvider({
|
|
|
276
76
|
const pendingRefreshesRef = useRef(0);
|
|
277
77
|
const pendingMutationsRef = useRef(0);
|
|
278
78
|
const syncStorage = isSyncCapableStorage(storage) ? storage : void 0;
|
|
279
|
-
const getSyncState = useCallback(
|
|
280
|
-
() => syncStorage?.getSyncState() ?? IDLE_SYNC_STATE,
|
|
281
|
-
[syncStorage]
|
|
282
|
-
);
|
|
79
|
+
const getSyncState = useCallback(() => syncStorage?.getSyncState() ?? IDLE_SYNC_STATE, [syncStorage]);
|
|
283
80
|
const subscribeSync = useCallback(
|
|
284
81
|
(listener) => syncStorage?.subscribeSync(listener) ?? (() => void 0),
|
|
285
82
|
[syncStorage]
|
|
@@ -300,19 +97,13 @@ function KeepProvider({
|
|
|
300
97
|
},
|
|
301
98
|
[store]
|
|
302
99
|
);
|
|
303
|
-
const runBeforePlugins = useCallback(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
);
|
|
310
|
-
const runAfterPlugins = useCallback(
|
|
311
|
-
async (context) => {
|
|
312
|
-
for (const plugin of pluginsRef.current) await plugin.after?.(context);
|
|
313
|
-
},
|
|
314
|
-
[]
|
|
315
|
-
);
|
|
100
|
+
const runBeforePlugins = useCallback(async (context) => {
|
|
101
|
+
for (const plugin of pluginsRef.current) await plugin.before?.(context);
|
|
102
|
+
return context;
|
|
103
|
+
}, []);
|
|
104
|
+
const runAfterPlugins = useCallback(async (context) => {
|
|
105
|
+
for (const plugin of pluginsRef.current) await plugin.after?.(context);
|
|
106
|
+
}, []);
|
|
316
107
|
const enqueueOperation = useCallback((operation) => {
|
|
317
108
|
const run = operationTailRef.current.then(operation, operation);
|
|
318
109
|
operationTailRef.current = run.then(
|
|
@@ -434,10 +225,9 @@ function KeepProvider({
|
|
|
434
225
|
throw cause;
|
|
435
226
|
}
|
|
436
227
|
await runMutation("save", normalizedItem.id, (previous) => ({
|
|
437
|
-
next: [
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
].sort((a, b) => b.updatedAt - a.updatedAt),
|
|
228
|
+
next: [...previous.filter((current) => current.id !== normalizedItem.id), normalizedItem].sort(
|
|
229
|
+
(a, b) => b.updatedAt - a.updatedAt
|
|
230
|
+
),
|
|
441
231
|
persist: () => storage.set(normalizedItem),
|
|
442
232
|
onSuccess: () => handlersRef.current.onSave?.(normalizedItem),
|
|
443
233
|
pluginContext: { action: "save", id: normalizedItem.id, item: normalizedItem }
|
|
@@ -533,9 +323,7 @@ function KeepProvider({
|
|
|
533
323
|
const idSet = new Set(ids);
|
|
534
324
|
const currentItems = itemsRef.current.filter((item) => idSet.has(item.id));
|
|
535
325
|
await Promise.all(
|
|
536
|
-
currentItems.map(
|
|
537
|
-
(item) => updateTags(item.id, normalizeKeepTags([...item.tags ?? [], ...additions]))
|
|
538
|
-
)
|
|
326
|
+
currentItems.map((item) => updateTags(item.id, normalizeKeepTags([...item.tags ?? [], ...additions])))
|
|
539
327
|
);
|
|
540
328
|
},
|
|
541
329
|
[updateTags]
|
|
@@ -547,10 +335,7 @@ function KeepProvider({
|
|
|
547
335
|
const currentItems = itemsRef.current.filter((item) => idSet.has(item.id));
|
|
548
336
|
await Promise.all(
|
|
549
337
|
currentItems.map(
|
|
550
|
-
(item) => updateTags(
|
|
551
|
-
item.id,
|
|
552
|
-
normalizeKeepTags((item.tags ?? []).filter((tag) => !removals.has(tag)))
|
|
553
|
-
)
|
|
338
|
+
(item) => updateTags(item.id, normalizeKeepTags((item.tags ?? []).filter((tag) => !removals.has(tag))))
|
|
554
339
|
)
|
|
555
340
|
);
|
|
556
341
|
},
|
|
@@ -614,10 +399,7 @@ function KeepProvider({
|
|
|
614
399
|
})),
|
|
615
400
|
[runMutation, storage]
|
|
616
401
|
);
|
|
617
|
-
const flushSync = useCallback(
|
|
618
|
-
() => syncStorage ? syncStorage.flushSync() : Promise.resolve(),
|
|
619
|
-
[syncStorage]
|
|
620
|
-
);
|
|
402
|
+
const flushSync = useCallback(() => syncStorage ? syncStorage.flushSync() : Promise.resolve(), [syncStorage]);
|
|
621
403
|
const value = useMemo(
|
|
622
404
|
() => ({
|
|
623
405
|
items,
|
|
@@ -873,14 +655,8 @@ function useKeepList(options = {}) {
|
|
|
873
655
|
(ids, tags2) => actions.updateTagsBatch(ids, tags2),
|
|
874
656
|
[actions]
|
|
875
657
|
);
|
|
876
|
-
const addTagsBatch = useCallback4(
|
|
877
|
-
|
|
878
|
-
[actions]
|
|
879
|
-
);
|
|
880
|
-
const removeTagsBatch = useCallback4(
|
|
881
|
-
(ids, tags2) => actions.removeTagsBatch(ids, tags2),
|
|
882
|
-
[actions]
|
|
883
|
-
);
|
|
658
|
+
const addTagsBatch = useCallback4((ids, tags2) => actions.addTagsBatch(ids, tags2), [actions]);
|
|
659
|
+
const removeTagsBatch = useCallback4((ids, tags2) => actions.removeTagsBatch(ids, tags2), [actions]);
|
|
884
660
|
return {
|
|
885
661
|
items,
|
|
886
662
|
totalCount,
|
|
@@ -899,52 +675,6 @@ function useKeepList(options = {}) {
|
|
|
899
675
|
refresh: actions.refresh
|
|
900
676
|
};
|
|
901
677
|
}
|
|
902
|
-
function queryKeepItems(source, options = {}) {
|
|
903
|
-
const filtered = source.filter((item) => {
|
|
904
|
-
const [from, to] = options.savedBetween ?? [];
|
|
905
|
-
const savedAt = item.savedAt;
|
|
906
|
-
const lowerBound = from === void 0 ? void 0 : toTimestamp(from);
|
|
907
|
-
const upperBound = to === void 0 ? void 0 : toTimestamp(to);
|
|
908
|
-
return (options.targetType === void 0 || item.targetType === options.targetType) && (options.tag === void 0 || item.tags?.includes(options.tag) === true) && (options.tags === void 0 || options.tags.every((tag) => item.tags?.includes(tag))) && (lowerBound === void 0 || savedAt >= lowerBound) && (upperBound === void 0 || savedAt <= upperBound) && matchesSearch(item, options.searchQuery, options.search) && (options.filter?.(item) ?? true) && (options.filterFn?.(item) ?? true);
|
|
909
|
-
});
|
|
910
|
-
const tagCounts = getTagCounts(filtered);
|
|
911
|
-
const sortBy = options.sortBy ?? options.sort?.by;
|
|
912
|
-
const direction = (options.order ?? options.sort?.direction) === "asc" ? 1 : -1;
|
|
913
|
-
const sorted = sortBy ? [...filtered].sort((a, b) => (a[sortBy] - b[sortBy]) * direction) : filtered;
|
|
914
|
-
const offset = Math.max(0, options.offset ?? 0);
|
|
915
|
-
const items = options.limit === void 0 ? sorted.slice(offset) : sorted.slice(offset, offset + Math.max(0, options.limit));
|
|
916
|
-
return { items, totalCount: sorted.length, tagCounts };
|
|
917
|
-
}
|
|
918
|
-
function getTagCounts(items) {
|
|
919
|
-
const counts = {};
|
|
920
|
-
for (const item of items) {
|
|
921
|
-
for (const tag of item.tags ?? []) counts[tag] = (counts[tag] ?? 0) + 1;
|
|
922
|
-
}
|
|
923
|
-
return Object.fromEntries(Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)));
|
|
924
|
-
}
|
|
925
|
-
function matchesSearch(item, searchQuery, search) {
|
|
926
|
-
const query = search?.query ?? searchQuery;
|
|
927
|
-
if (!query?.trim()) return true;
|
|
928
|
-
const fields = search?.fields ?? ["note", "meta", "tags"];
|
|
929
|
-
const values = fields.map((field) => {
|
|
930
|
-
if (field === "note") return item.note ?? "";
|
|
931
|
-
if (field === "tags") return (item.tags ?? []).join(" ");
|
|
932
|
-
try {
|
|
933
|
-
return JSON.stringify(item.meta) ?? "";
|
|
934
|
-
} catch {
|
|
935
|
-
return String(item.meta);
|
|
936
|
-
}
|
|
937
|
-
});
|
|
938
|
-
const text = values.join(" ").toLocaleLowerCase();
|
|
939
|
-
if (!search) return text.includes(query.trim().toLocaleLowerCase());
|
|
940
|
-
const normalized = query.trim().toLocaleLowerCase();
|
|
941
|
-
const needles = search.tokenize === false ? [normalized] : normalized.split(/\s+/).filter(Boolean);
|
|
942
|
-
const matches = needles.map((needle) => text.includes(needle));
|
|
943
|
-
return search.mode === "or" ? matches.some(Boolean) : matches.every(Boolean);
|
|
944
|
-
}
|
|
945
|
-
function toTimestamp(value) {
|
|
946
|
-
return value instanceof Date ? value.getTime() : value;
|
|
947
|
-
}
|
|
948
678
|
function sameItems(left, right) {
|
|
949
679
|
return left.length === right.length && left.every((item, index) => item === right[index]);
|
|
950
680
|
}
|
|
@@ -957,6 +687,62 @@ function sameCounts(left, right) {
|
|
|
957
687
|
});
|
|
958
688
|
}
|
|
959
689
|
|
|
690
|
+
// src/hooks/useKeepShortcut.ts
|
|
691
|
+
import { useEffect as useEffect2 } from "react";
|
|
692
|
+
function useKeepShortcut(options) {
|
|
693
|
+
const item = useKeepItem(options.id ?? "", options.itemPayload);
|
|
694
|
+
const {
|
|
695
|
+
action = "toggle",
|
|
696
|
+
allowInEditable = false,
|
|
697
|
+
enabled = true,
|
|
698
|
+
key,
|
|
699
|
+
modifier,
|
|
700
|
+
onError,
|
|
701
|
+
onTrigger,
|
|
702
|
+
preventDefault = true
|
|
703
|
+
} = options;
|
|
704
|
+
useEffect2(() => {
|
|
705
|
+
if (!enabled) return;
|
|
706
|
+
const handleKeyDown = (event) => {
|
|
707
|
+
if (!allowInEditable && isEditableTarget(event.target)) return;
|
|
708
|
+
if (!matchesShortcut(event, key, modifier)) return;
|
|
709
|
+
if (preventDefault) event.preventDefault();
|
|
710
|
+
const run = onTrigger ? onTrigger(event) : options.id ? action === "save" ? item.save() : action === "remove" ? item.remove() : item.toggle() : void 0;
|
|
711
|
+
if (run) void Promise.resolve(run).catch((error) => onError?.(error));
|
|
712
|
+
};
|
|
713
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
714
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
715
|
+
}, [
|
|
716
|
+
action,
|
|
717
|
+
allowInEditable,
|
|
718
|
+
enabled,
|
|
719
|
+
item.remove,
|
|
720
|
+
item.save,
|
|
721
|
+
item.toggle,
|
|
722
|
+
key,
|
|
723
|
+
modifier,
|
|
724
|
+
onError,
|
|
725
|
+
onTrigger,
|
|
726
|
+
options.id,
|
|
727
|
+
preventDefault
|
|
728
|
+
]);
|
|
729
|
+
}
|
|
730
|
+
function matchesShortcut(event, key, modifier) {
|
|
731
|
+
if (event.key.toLocaleLowerCase() !== key.toLocaleLowerCase()) return false;
|
|
732
|
+
const modifiers = {
|
|
733
|
+
meta: event.metaKey,
|
|
734
|
+
ctrl: event.ctrlKey,
|
|
735
|
+
alt: event.altKey,
|
|
736
|
+
shift: event.shiftKey
|
|
737
|
+
};
|
|
738
|
+
if (modifier ? !modifiers[modifier] : Object.values(modifiers).some(Boolean)) return false;
|
|
739
|
+
return Object.entries(modifiers).every(([name, pressed]) => name === modifier || !pressed);
|
|
740
|
+
}
|
|
741
|
+
function isEditableTarget(target) {
|
|
742
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
743
|
+
return target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
744
|
+
}
|
|
745
|
+
|
|
960
746
|
// src/KeepButton.tsx
|
|
961
747
|
import {
|
|
962
748
|
Children,
|
|
@@ -984,6 +770,7 @@ function KeepButton({
|
|
|
984
770
|
const { isSaved, toggle } = state;
|
|
985
771
|
const isDisabled = disabled ?? state.isMutating;
|
|
986
772
|
async function handleClick(event) {
|
|
773
|
+
if (isDisabled) return;
|
|
987
774
|
if (asChild) {
|
|
988
775
|
onClick?.(event);
|
|
989
776
|
} else {
|
|
@@ -1000,33 +787,53 @@ function KeepButton({
|
|
|
1000
787
|
}
|
|
1001
788
|
const content = typeof children === "function" ? children(state) : children ?? (isSaved ? savedLabel : unsavedLabel);
|
|
1002
789
|
function handleElementClick(event) {
|
|
790
|
+
if (isDisabled) return;
|
|
1003
791
|
if (asChild && isValidElement(content)) {
|
|
1004
792
|
content.props.onClick?.(event);
|
|
1005
793
|
}
|
|
1006
794
|
if (!event.defaultPrevented) void handleClick(event);
|
|
1007
795
|
}
|
|
796
|
+
function handleKeyDown(event) {
|
|
797
|
+
if (asChild && isValidElement(content)) {
|
|
798
|
+
content.props.onKeyDown?.(event);
|
|
799
|
+
}
|
|
800
|
+
buttonProps.onKeyDown?.(event);
|
|
801
|
+
if (!event.defaultPrevented && !isDisabled && asChild && (event.key === "Enter" || event.key === " ")) {
|
|
802
|
+
void handleClick(event);
|
|
803
|
+
event.preventDefault();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const child = asChild ? Children.only(content) : void 0;
|
|
807
|
+
if (asChild && !isValidElement(child)) {
|
|
808
|
+
throw new Error("KeepButton with asChild requires a single React element child.");
|
|
809
|
+
}
|
|
1008
810
|
const commonProps = {
|
|
1009
811
|
...buttonProps,
|
|
1010
812
|
"aria-pressed": isSaved,
|
|
1011
|
-
"aria-label": ("aria-label" in buttonProps ? buttonProps["aria-label"] : void 0) ?? (isSaved
|
|
1012
|
-
|
|
1013
|
-
|
|
813
|
+
"aria-label": ("aria-label" in buttonProps ? buttonProps["aria-label"] : void 0) ?? getAccessibleLabel(isSaved, item, asChild),
|
|
814
|
+
...asChild ? {
|
|
815
|
+
"aria-disabled": isDisabled,
|
|
816
|
+
role: buttonProps.role ?? "button",
|
|
817
|
+
tabIndex: isDisabled ? -1 : buttonProps.tabIndex ?? 0
|
|
818
|
+
} : { disabled: isDisabled },
|
|
819
|
+
onClick: handleElementClick,
|
|
820
|
+
onKeyDown: handleKeyDown
|
|
1014
821
|
};
|
|
1015
822
|
if (asChild) {
|
|
1016
|
-
const child = Children.only(content);
|
|
1017
|
-
if (!isValidElement(child)) {
|
|
1018
|
-
throw new Error("KeepButton with asChild requires a single React element child.");
|
|
1019
|
-
}
|
|
1020
823
|
return cloneElement(child, commonProps);
|
|
1021
824
|
}
|
|
1022
|
-
return /* @__PURE__ */ jsx2(
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
825
|
+
return /* @__PURE__ */ jsx2("button", { ...commonProps, type: "type" in buttonProps ? buttonProps.type ?? "button" : "button", children: content });
|
|
826
|
+
}
|
|
827
|
+
function getAccessibleLabel(isSaved, item, asChild) {
|
|
828
|
+
if (!asChild) return isSaved ? "Remove saved item" : "Save item";
|
|
829
|
+
const title = getMetaTitle(item.meta);
|
|
830
|
+
const subject = title ? `${item.targetType ?? "item"}: ${title}` : item.targetType ?? "item";
|
|
831
|
+
return `${isSaved ? "Remove" : "Save"} ${subject}`;
|
|
832
|
+
}
|
|
833
|
+
function getMetaTitle(meta) {
|
|
834
|
+
if (typeof meta !== "object" || meta === null || !("title" in meta)) return void 0;
|
|
835
|
+
const title = meta.title;
|
|
836
|
+
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1030
837
|
}
|
|
1031
838
|
|
|
1032
839
|
// src/createKeepKit.tsx
|
|
@@ -1037,57 +844,18 @@ function createKeepKit(options = {}) {
|
|
|
1037
844
|
KeepButton: (props) => /* @__PURE__ */ jsx3(KeepButton, { ...props }),
|
|
1038
845
|
useKeepContext: () => useKeepContext(),
|
|
1039
846
|
useKeepItem: (id, itemPayload) => useKeepItem(id, itemPayload),
|
|
1040
|
-
useKeepList: (options2) => useKeepList(options2)
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// src/integrations.ts
|
|
1045
|
-
function createKeepInvalidationPlugin(options) {
|
|
1046
|
-
return {
|
|
1047
|
-
name: options.name ?? "keepkit-cache-invalidation",
|
|
1048
|
-
after: async (context) => {
|
|
1049
|
-
const keys = typeof options.queryKeys === "function" ? options.queryKeys(context) : [options.queryKeys];
|
|
1050
|
-
await Promise.all(keys.map((queryKey) => options.invalidate(queryKey, context)));
|
|
1051
|
-
}
|
|
847
|
+
useKeepList: (options2) => useKeepList(options2),
|
|
848
|
+
useKeepShortcut: (shortcutOptions) => useKeepShortcut(shortcutOptions)
|
|
1052
849
|
};
|
|
1053
850
|
}
|
|
1054
851
|
export {
|
|
1055
|
-
DEFAULT_INDEXEDDB_DATABASE,
|
|
1056
|
-
DEFAULT_INDEXEDDB_STORE,
|
|
1057
|
-
DEFAULT_STORAGE_KEY,
|
|
1058
|
-
DEFAULT_SYNC_QUEUE_DATABASE,
|
|
1059
|
-
DEFAULT_SYNC_QUEUE_KEY,
|
|
1060
|
-
DEFAULT_SYNC_QUEUE_STORE,
|
|
1061
|
-
IndexedDBAdapter,
|
|
1062
|
-
IndexedDBSyncQueueAdapter,
|
|
1063
|
-
KEEP_BACKUP_FORMAT,
|
|
1064
|
-
KEEP_BACKUP_VERSION,
|
|
1065
|
-
KeepBackupImportError,
|
|
1066
|
-
KeepBackupParseError,
|
|
1067
852
|
KeepButton,
|
|
1068
853
|
KeepProvider,
|
|
1069
|
-
KeepSchemaValidationError,
|
|
1070
|
-
KeepStorageAccessError,
|
|
1071
|
-
KeepStorageError,
|
|
1072
|
-
KeepStorageParseError,
|
|
1073
|
-
KeepStorageQuotaError,
|
|
1074
|
-
LocalStorageAdapter,
|
|
1075
|
-
LocalStorageSyncQueueAdapter,
|
|
1076
|
-
SyncStorageAdapter,
|
|
1077
|
-
createKeepInvalidationPlugin,
|
|
1078
854
|
createKeepKit,
|
|
1079
|
-
createStorageAdapter,
|
|
1080
|
-
exportItems,
|
|
1081
|
-
getTagCounts,
|
|
1082
|
-
importItems,
|
|
1083
|
-
mergeKeepItems,
|
|
1084
|
-
migrateKeepItems,
|
|
1085
|
-
normalizeKeepTags,
|
|
1086
|
-
parseKeepMeta,
|
|
1087
|
-
queryKeepItems,
|
|
1088
855
|
useKeepContext,
|
|
1089
856
|
useKeepItem,
|
|
1090
857
|
useKeepList,
|
|
1091
|
-
|
|
858
|
+
useKeepShortcut,
|
|
859
|
+
useKeepStore
|
|
1092
860
|
};
|
|
1093
|
-
//# sourceMappingURL=
|
|
861
|
+
//# sourceMappingURL=react.js.map
|