@keepkit/ui 0.14.0 → 0.16.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 +59 -7
- package/dist/index.d.ts +390 -42
- package/dist/index.js +1606 -866
- package/dist/index.js.map +1 -1
- package/dist/styles/base.css +248 -17
- package/dist/styles/button.css +19 -2
- package/dist/styles/collection.css +420 -6
- package/dist/styles/sync.css +103 -11
- package/dist/tailwind.css +2 -0
- package/dist/tailwind.d.ts +4 -0
- package/dist/tailwind.js +5 -1
- package/dist/tailwind.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6,12 +6,32 @@ import {
|
|
|
6
6
|
createKeepKit as createCoreKeepKit
|
|
7
7
|
} from "@keepkit/core/react";
|
|
8
8
|
|
|
9
|
-
// src/
|
|
9
|
+
// src/hooks/useKeepToastFeedback.ts
|
|
10
|
+
import { useCallback } from "react";
|
|
11
|
+
function useKeepToastFeedback(showToast) {
|
|
12
|
+
return useCallback(
|
|
13
|
+
(event) => {
|
|
14
|
+
if (!("undo" in event)) {
|
|
15
|
+
showToast(event.message);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
showToast(event.message, {
|
|
19
|
+
action: {
|
|
20
|
+
label: event.undoLabel,
|
|
21
|
+
onClick: () => void event.undo()
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
[showToast]
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/hooks/useKeepBackup.ts
|
|
10
30
|
import { useKeepContext } from "@keepkit/core/react";
|
|
11
31
|
import { useRef, useState } from "react";
|
|
12
32
|
|
|
13
33
|
// src/ui-context.tsx
|
|
14
|
-
import { createContext, useContext, useMemo } from "react";
|
|
34
|
+
import { createContext, useCallback as useCallback2, useContext, useMemo } from "react";
|
|
15
35
|
|
|
16
36
|
// src/locales/de.ts
|
|
17
37
|
var DE_LABELS = {
|
|
@@ -49,6 +69,9 @@ var DE_LABELS = {
|
|
|
49
69
|
applyTags: "Tags anwenden",
|
|
50
70
|
savedMessage: "Element gespeichert.",
|
|
51
71
|
removedMessage: "Element entfernt.",
|
|
72
|
+
restoredMessage: "Element wiederhergestellt.",
|
|
73
|
+
syncFailedMessage: "Synchronisierung fehlgeschlagen.",
|
|
74
|
+
stalePrunedMessage: "Nicht verf\xFCgbare Elemente entfernt.",
|
|
52
75
|
undo: "R\xFCckg\xE4ngig",
|
|
53
76
|
undoAvailable: "Element entfernt. R\xFCckg\xE4ngig machen?",
|
|
54
77
|
selectionScope: "Auswahlbereich",
|
|
@@ -125,6 +148,9 @@ var EN_LABELS = {
|
|
|
125
148
|
applyTags: "Apply tags",
|
|
126
149
|
savedMessage: "Item saved.",
|
|
127
150
|
removedMessage: "Item removed.",
|
|
151
|
+
restoredMessage: "Item restored.",
|
|
152
|
+
syncFailedMessage: "Sync failed.",
|
|
153
|
+
stalePrunedMessage: "Unavailable items removed.",
|
|
128
154
|
undo: "Undo",
|
|
129
155
|
undoAvailable: "Item removed. Undo?",
|
|
130
156
|
selectionScope: "Selection scope",
|
|
@@ -201,6 +227,9 @@ var ES_LABELS = {
|
|
|
201
227
|
applyTags: "Aplicar etiquetas",
|
|
202
228
|
savedMessage: "Elemento guardado.",
|
|
203
229
|
removedMessage: "Elemento eliminado.",
|
|
230
|
+
restoredMessage: "Elemento restaurado.",
|
|
231
|
+
syncFailedMessage: "Error de sincronizaci\xF3n.",
|
|
232
|
+
stalePrunedMessage: "Elementos no disponibles eliminados.",
|
|
204
233
|
undo: "Deshacer",
|
|
205
234
|
undoAvailable: "Elemento eliminado. \xBFDeshacer?",
|
|
206
235
|
selectionScope: "\xC1mbito de selecci\xF3n",
|
|
@@ -277,6 +306,9 @@ var FIL_LABELS = {
|
|
|
277
306
|
applyTags: "Ilapat ang mga tag",
|
|
278
307
|
savedMessage: "Na-save ang item.",
|
|
279
308
|
removedMessage: "Inalis ang item.",
|
|
309
|
+
restoredMessage: "Naibalik ang item.",
|
|
310
|
+
syncFailedMessage: "Hindi nagtagumpay ang pag-sync.",
|
|
311
|
+
stalePrunedMessage: "Inalis ang mga hindi available na item.",
|
|
280
312
|
undo: "I-undo",
|
|
281
313
|
undoAvailable: "Inalis ang item. I-undo?",
|
|
282
314
|
selectionScope: "Saklaw ng pagpili",
|
|
@@ -353,6 +385,9 @@ var FR_LABELS = {
|
|
|
353
385
|
applyTags: "Appliquer les \xE9tiquettes",
|
|
354
386
|
savedMessage: "\xC9l\xE9ment enregistr\xE9.",
|
|
355
387
|
removedMessage: "\xC9l\xE9ment supprim\xE9.",
|
|
388
|
+
restoredMessage: "\xC9l\xE9ment restaur\xE9.",
|
|
389
|
+
syncFailedMessage: "\xC9chec de la synchronisation.",
|
|
390
|
+
stalePrunedMessage: "\xC9l\xE9ments indisponibles supprim\xE9s.",
|
|
356
391
|
undo: "Annuler",
|
|
357
392
|
undoAvailable: "\xC9l\xE9ment supprim\xE9. Annuler ?",
|
|
358
393
|
selectionScope: "Port\xE9e de la s\xE9lection",
|
|
@@ -429,6 +464,9 @@ var ID_LABELS = {
|
|
|
429
464
|
applyTags: "Terapkan tag",
|
|
430
465
|
savedMessage: "Item tersimpan.",
|
|
431
466
|
removedMessage: "Item dihapus.",
|
|
467
|
+
restoredMessage: "Item dipulihkan.",
|
|
468
|
+
syncFailedMessage: "Sinkronisasi gagal.",
|
|
469
|
+
stalePrunedMessage: "Item yang tidak tersedia dihapus.",
|
|
432
470
|
undo: "Urungkan",
|
|
433
471
|
undoAvailable: "Item dihapus. Urungkan?",
|
|
434
472
|
selectionScope: "Cakupan pilihan",
|
|
@@ -505,6 +543,9 @@ var IT_LABELS = {
|
|
|
505
543
|
applyTags: "Applica tag",
|
|
506
544
|
savedMessage: "Elemento salvato.",
|
|
507
545
|
removedMessage: "Elemento rimosso.",
|
|
546
|
+
restoredMessage: "Elemento ripristinato.",
|
|
547
|
+
syncFailedMessage: "Sincronizzazione non riuscita.",
|
|
548
|
+
stalePrunedMessage: "Elementi non disponibili rimossi.",
|
|
508
549
|
undo: "Annulla",
|
|
509
550
|
undoAvailable: "Elemento rimosso. Annullare?",
|
|
510
551
|
selectionScope: "Ambito della selezione",
|
|
@@ -581,6 +622,9 @@ var JA_LABELS = {
|
|
|
581
622
|
applyTags: "\u30BF\u30B0\u3092\u9069\u7528",
|
|
582
623
|
savedMessage: "\u30A2\u30A4\u30C6\u30E0\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002",
|
|
583
624
|
removedMessage: "\u30A2\u30A4\u30C6\u30E0\u3092\u524A\u9664\u3057\u307E\u3057\u305F\u3002",
|
|
625
|
+
restoredMessage: "\u30A2\u30A4\u30C6\u30E0\u3092\u5FA9\u5143\u3057\u307E\u3057\u305F\u3002",
|
|
626
|
+
syncFailedMessage: "\u540C\u671F\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002",
|
|
627
|
+
stalePrunedMessage: "\u5229\u7528\u3067\u304D\u306A\u3044\u30A2\u30A4\u30C6\u30E0\u3092\u524A\u9664\u3057\u307E\u3057\u305F\u3002",
|
|
584
628
|
undo: "\u5143\u306B\u623B\u3059",
|
|
585
629
|
undoAvailable: "\u30A2\u30A4\u30C6\u30E0\u3092\u524A\u9664\u3057\u307E\u3057\u305F\u3002\u5143\u306B\u623B\u3057\u307E\u3059\u304B\uFF1F",
|
|
586
630
|
selectionScope: "\u9078\u629E\u7BC4\u56F2",
|
|
@@ -657,6 +701,9 @@ var KO_LABELS = {
|
|
|
657
701
|
applyTags: "\uD0DC\uADF8 \uC801\uC6A9",
|
|
658
702
|
savedMessage: "\uD56D\uBAA9\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.",
|
|
659
703
|
removedMessage: "\uD56D\uBAA9\uC744 \uC0AD\uC81C\uD588\uC2B5\uB2C8\uB2E4.",
|
|
704
|
+
restoredMessage: "\uD56D\uBAA9\uC744 \uBCF5\uC6D0\uD588\uC2B5\uB2C8\uB2E4.",
|
|
705
|
+
syncFailedMessage: "\uB3D9\uAE30\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.",
|
|
706
|
+
stalePrunedMessage: "\uC0AC\uC6A9\uD560 \uC218 \uC5C6\uB294 \uD56D\uBAA9\uC744 \uC0AD\uC81C\uD588\uC2B5\uB2C8\uB2E4.",
|
|
660
707
|
undo: "\uC2E4\uD589 \uCDE8\uC18C",
|
|
661
708
|
undoAvailable: "\uD56D\uBAA9\uC744 \uC0AD\uC81C\uD588\uC2B5\uB2C8\uB2E4. \uC2E4\uD589 \uCDE8\uC18C\uD560\uAE4C\uC694?",
|
|
662
709
|
selectionScope: "\uC120\uD0DD \uBC94\uC704",
|
|
@@ -733,6 +780,9 @@ var MS_LABELS = {
|
|
|
733
780
|
applyTags: "Gunakan tag",
|
|
734
781
|
savedMessage: "Item disimpan.",
|
|
735
782
|
removedMessage: "Item dibuang.",
|
|
783
|
+
restoredMessage: "Item dipulihkan.",
|
|
784
|
+
syncFailedMessage: "Penyegerakan gagal.",
|
|
785
|
+
stalePrunedMessage: "Item yang tidak tersedia dibuang.",
|
|
736
786
|
undo: "Buat asal",
|
|
737
787
|
undoAvailable: "Item dibuang. Buat asal?",
|
|
738
788
|
selectionScope: "Skop pilihan",
|
|
@@ -809,6 +859,9 @@ var PT_BR_LABELS = {
|
|
|
809
859
|
applyTags: "Aplicar tags",
|
|
810
860
|
savedMessage: "Item salvo.",
|
|
811
861
|
removedMessage: "Item removido.",
|
|
862
|
+
restoredMessage: "Item restaurado.",
|
|
863
|
+
syncFailedMessage: "Falha na sincroniza\xE7\xE3o.",
|
|
864
|
+
stalePrunedMessage: "Itens indispon\xEDveis removidos.",
|
|
812
865
|
undo: "Desfazer",
|
|
813
866
|
undoAvailable: "Item removido. Desfazer?",
|
|
814
867
|
selectionScope: "Escopo da sele\xE7\xE3o",
|
|
@@ -885,6 +938,9 @@ var RU_LABELS = {
|
|
|
885
938
|
applyTags: "\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0442\u0435\u0433\u0438",
|
|
886
939
|
savedMessage: "\u042D\u043B\u0435\u043C\u0435\u043D\u0442 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D.",
|
|
887
940
|
removedMessage: "\u042D\u043B\u0435\u043C\u0435\u043D\u0442 \u0443\u0434\u0430\u043B\u0451\u043D.",
|
|
941
|
+
restoredMessage: "\u042D\u043B\u0435\u043C\u0435\u043D\u0442 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D.",
|
|
942
|
+
syncFailedMessage: "\u0421\u0438\u043D\u0445\u0440\u043E\u043D\u0438\u0437\u0430\u0446\u0438\u044F \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0430.",
|
|
943
|
+
stalePrunedMessage: "\u041D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u0443\u0434\u0430\u043B\u0435\u043D\u044B.",
|
|
888
944
|
undo: "\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C",
|
|
889
945
|
undoAvailable: "\u042D\u043B\u0435\u043C\u0435\u043D\u0442 \u0443\u0434\u0430\u043B\u0451\u043D. \u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C?",
|
|
890
946
|
selectionScope: "\u041E\u0431\u043B\u0430\u0441\u0442\u044C \u0432\u044B\u0431\u043E\u0440\u0430",
|
|
@@ -961,6 +1017,9 @@ var TH_LABELS = {
|
|
|
961
1017
|
applyTags: "\u0E43\u0E0A\u0E49\u0E41\u0E17\u0E47\u0E01",
|
|
962
1018
|
savedMessage: "\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E41\u0E25\u0E49\u0E27",
|
|
963
1019
|
removedMessage: "\u0E25\u0E1A\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E41\u0E25\u0E49\u0E27",
|
|
1020
|
+
restoredMessage: "\u0E01\u0E39\u0E49\u0E04\u0E37\u0E19\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E41\u0E25\u0E49\u0E27",
|
|
1021
|
+
syncFailedMessage: "\u0E0B\u0E34\u0E07\u0E04\u0E4C\u0E44\u0E21\u0E48\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08",
|
|
1022
|
+
stalePrunedMessage: "\u0E25\u0E1A\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E17\u0E35\u0E48\u0E43\u0E0A\u0E49\u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E41\u0E25\u0E49\u0E27",
|
|
964
1023
|
undo: "\u0E40\u0E25\u0E34\u0E01\u0E17\u0E33",
|
|
965
1024
|
undoAvailable: "\u0E25\u0E1A\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E41\u0E25\u0E49\u0E27 \u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23\u0E40\u0E25\u0E34\u0E01\u0E17\u0E33\u0E2B\u0E23\u0E37\u0E2D\u0E44\u0E21\u0E48",
|
|
966
1025
|
selectionScope: "\u0E02\u0E2D\u0E1A\u0E40\u0E02\u0E15\u0E01\u0E32\u0E23\u0E40\u0E25\u0E37\u0E2D\u0E01",
|
|
@@ -1037,6 +1096,9 @@ var VI_LABELS = {
|
|
|
1037
1096
|
applyTags: "\xC1p d\u1EE5ng th\u1EBB",
|
|
1038
1097
|
savedMessage: "\u0110\xE3 l\u01B0u m\u1EE5c.",
|
|
1039
1098
|
removedMessage: "\u0110\xE3 x\xF3a m\u1EE5c.",
|
|
1099
|
+
restoredMessage: "\u0110\xE3 kh\xF4i ph\u1EE5c m\u1EE5c.",
|
|
1100
|
+
syncFailedMessage: "\u0110\u1ED3ng b\u1ED9 kh\xF4ng th\xE0nh c\xF4ng.",
|
|
1101
|
+
stalePrunedMessage: "\u0110\xE3 x\xF3a c\xE1c m\u1EE5c kh\xF4ng kh\u1EA3 d\u1EE5ng.",
|
|
1040
1102
|
undo: "Ho\xE0n t\xE1c",
|
|
1041
1103
|
undoAvailable: "\u0110\xE3 x\xF3a m\u1EE5c. Ho\xE0n t\xE1c?",
|
|
1042
1104
|
selectionScope: "Ph\u1EA1m vi l\u1EF1a ch\u1ECDn",
|
|
@@ -1113,6 +1175,9 @@ var ZH_HANS_LABELS = {
|
|
|
1113
1175
|
applyTags: "\u5E94\u7528\u6807\u7B7E",
|
|
1114
1176
|
savedMessage: "\u9879\u76EE\u5DF2\u4FDD\u5B58\u3002",
|
|
1115
1177
|
removedMessage: "\u9879\u76EE\u5DF2\u5220\u9664\u3002",
|
|
1178
|
+
restoredMessage: "\u9879\u76EE\u5DF2\u6062\u590D\u3002",
|
|
1179
|
+
syncFailedMessage: "\u540C\u6B65\u5931\u8D25\u3002",
|
|
1180
|
+
stalePrunedMessage: "\u4E0D\u53EF\u7528\u9879\u76EE\u5DF2\u5220\u9664\u3002",
|
|
1116
1181
|
undo: "\u64A4\u9500",
|
|
1117
1182
|
undoAvailable: "\u9879\u76EE\u5DF2\u5220\u9664\u3002\u8981\u64A4\u9500\u5417\uFF1F",
|
|
1118
1183
|
selectionScope: "\u9009\u62E9\u8303\u56F4",
|
|
@@ -1189,6 +1254,9 @@ var ZH_HANT_LABELS = {
|
|
|
1189
1254
|
applyTags: "\u5957\u7528\u6A19\u7C64",
|
|
1190
1255
|
savedMessage: "\u9805\u76EE\u5DF2\u5132\u5B58\u3002",
|
|
1191
1256
|
removedMessage: "\u9805\u76EE\u5DF2\u522A\u9664\u3002",
|
|
1257
|
+
restoredMessage: "\u9805\u76EE\u5DF2\u9084\u539F\u3002",
|
|
1258
|
+
syncFailedMessage: "\u540C\u6B65\u5931\u6557\u3002",
|
|
1259
|
+
stalePrunedMessage: "\u7121\u6CD5\u4F7F\u7528\u7684\u9805\u76EE\u5DF2\u522A\u9664\u3002",
|
|
1192
1260
|
undo: "\u5FA9\u539F",
|
|
1193
1261
|
undoAvailable: "\u9805\u76EE\u5DF2\u522A\u9664\u3002\u8981\u5FA9\u539F\u55CE\uFF1F",
|
|
1194
1262
|
selectionScope: "\u9078\u53D6\u7BC4\u570D",
|
|
@@ -1291,14 +1359,29 @@ function getKeepLocaleLabels(locale) {
|
|
|
1291
1359
|
// src/ui-context.tsx
|
|
1292
1360
|
import { jsx } from "react/jsx-runtime";
|
|
1293
1361
|
var DEFAULT_LABELS = KEEP_LOCALE_LABELS.en;
|
|
1294
|
-
var KeepUiLabelsContext = createContext({
|
|
1295
|
-
|
|
1362
|
+
var KeepUiLabelsContext = createContext({
|
|
1363
|
+
labels: DEFAULT_LABELS,
|
|
1364
|
+
emitFeedback: () => void 0
|
|
1365
|
+
});
|
|
1366
|
+
function KeepUiProvider({
|
|
1367
|
+
labels,
|
|
1368
|
+
locale,
|
|
1369
|
+
labelResolver,
|
|
1370
|
+
onFeedback,
|
|
1371
|
+
children
|
|
1372
|
+
}) {
|
|
1373
|
+
const emitFeedback = useCallback2(
|
|
1374
|
+
(event) => {
|
|
1375
|
+
onFeedback?.(event);
|
|
1376
|
+
},
|
|
1377
|
+
[onFeedback]
|
|
1378
|
+
);
|
|
1296
1379
|
const value = useMemo(() => {
|
|
1297
1380
|
const resolvedLocale = normalizeKeepLocale(locale);
|
|
1298
1381
|
const dictionary = getKeepLocaleLabels(resolvedLocale);
|
|
1299
1382
|
const resolved = { ...DEFAULT_LABELS, ...dictionary, ...labels };
|
|
1300
|
-
return { locale: locale ?? resolvedLocale, labels: resolved, customLabels: labels, labelResolver };
|
|
1301
|
-
}, [labelResolver, labels, locale]);
|
|
1383
|
+
return { locale: locale ?? resolvedLocale, labels: resolved, customLabels: labels, labelResolver, emitFeedback };
|
|
1384
|
+
}, [emitFeedback, labelResolver, labels, locale]);
|
|
1302
1385
|
return /* @__PURE__ */ jsx(KeepUiLabelsContext.Provider, { value, children });
|
|
1303
1386
|
}
|
|
1304
1387
|
function useKeepUiLabels() {
|
|
@@ -1308,29 +1391,19 @@ function useUiLabel(key, override) {
|
|
|
1308
1391
|
const context = useKeepUiLabels();
|
|
1309
1392
|
return override ?? context.customLabels?.[key] ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];
|
|
1310
1393
|
}
|
|
1394
|
+
function useKeepUiFeedback() {
|
|
1395
|
+
const { emitFeedback } = useKeepUiLabels();
|
|
1396
|
+
return useCallback2((event) => emitFeedback(event), [emitFeedback]);
|
|
1397
|
+
}
|
|
1311
1398
|
|
|
1312
|
-
// src/
|
|
1313
|
-
|
|
1314
|
-
function KeepBackup({
|
|
1315
|
-
filename = "keepkit-backup.json",
|
|
1316
|
-
onExport,
|
|
1317
|
-
onImported,
|
|
1318
|
-
...props
|
|
1319
|
-
}) {
|
|
1399
|
+
// src/hooks/useKeepBackup.ts
|
|
1400
|
+
function useKeepBackup({ filename, onExport, onImported }) {
|
|
1320
1401
|
const context = useKeepContext();
|
|
1321
|
-
const exportLabel = useUiLabel("exportData");
|
|
1322
|
-
const importLabel = useUiLabel("importData");
|
|
1323
|
-
const importModeLabel = useUiLabel("importMode");
|
|
1324
|
-
const mergeLabel = useUiLabel("merge");
|
|
1325
|
-
const replaceLabel = useUiLabel("replace");
|
|
1326
|
-
const importedCountLabel = useUiLabel("importedCount");
|
|
1327
|
-
const failedCountLabel = useUiLabel("failedCount");
|
|
1328
|
-
const quotaErrorLabel = useUiLabel("storageQuotaError");
|
|
1329
1402
|
const inputRef = useRef(null);
|
|
1330
1403
|
const [mode, setMode] = useState("merge");
|
|
1331
1404
|
const [result, setResult] = useState();
|
|
1332
1405
|
const [error, setError] = useState();
|
|
1333
|
-
async function
|
|
1406
|
+
async function exportBackup() {
|
|
1334
1407
|
setError(void 0);
|
|
1335
1408
|
try {
|
|
1336
1409
|
const data = await context.exportBackup();
|
|
@@ -1346,7 +1419,7 @@ function KeepBackup({
|
|
|
1346
1419
|
setError(cause);
|
|
1347
1420
|
}
|
|
1348
1421
|
}
|
|
1349
|
-
async function
|
|
1422
|
+
async function importBackup(event) {
|
|
1350
1423
|
const file = event.currentTarget.files?.[0];
|
|
1351
1424
|
event.currentTarget.value = "";
|
|
1352
1425
|
if (!file) return;
|
|
@@ -1360,43 +1433,93 @@ function KeepBackup({
|
|
|
1360
1433
|
setError(cause);
|
|
1361
1434
|
}
|
|
1362
1435
|
}
|
|
1436
|
+
return {
|
|
1437
|
+
inputRef,
|
|
1438
|
+
mode,
|
|
1439
|
+
setMode,
|
|
1440
|
+
result,
|
|
1441
|
+
error,
|
|
1442
|
+
isMutating: context.isMutating,
|
|
1443
|
+
exportBackup,
|
|
1444
|
+
importBackup,
|
|
1445
|
+
openFilePicker: () => inputRef.current?.click(),
|
|
1446
|
+
labels: {
|
|
1447
|
+
export: useUiLabel("exportData"),
|
|
1448
|
+
import: useUiLabel("importData"),
|
|
1449
|
+
importMode: useUiLabel("importMode"),
|
|
1450
|
+
merge: useUiLabel("merge"),
|
|
1451
|
+
replace: useUiLabel("replace"),
|
|
1452
|
+
importedCount: useUiLabel("importedCount"),
|
|
1453
|
+
failedCount: useUiLabel("failedCount"),
|
|
1454
|
+
quotaError: useUiLabel("storageQuotaError")
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// src/KeepBackup.tsx
|
|
1460
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1461
|
+
function KeepBackup({
|
|
1462
|
+
filename = "keepkit-backup.json",
|
|
1463
|
+
onExport,
|
|
1464
|
+
onImported,
|
|
1465
|
+
...props
|
|
1466
|
+
}) {
|
|
1467
|
+
const view = useKeepBackup({ filename, onExport, onImported });
|
|
1363
1468
|
return /* @__PURE__ */ jsxs(
|
|
1364
1469
|
"section",
|
|
1365
1470
|
{
|
|
1366
1471
|
...props,
|
|
1367
1472
|
"data-keepkit": "backup",
|
|
1368
|
-
"data-state": error ? "error" : result ? "complete" : "idle",
|
|
1369
|
-
"data-loading":
|
|
1473
|
+
"data-state": view.error ? "error" : view.result ? "complete" : "idle",
|
|
1474
|
+
"data-loading": view.isMutating ? "true" : void 0,
|
|
1370
1475
|
children: [
|
|
1371
|
-
/* @__PURE__ */ jsx2(
|
|
1476
|
+
/* @__PURE__ */ jsx2(
|
|
1477
|
+
"button",
|
|
1478
|
+
{
|
|
1479
|
+
type: "button",
|
|
1480
|
+
"data-keep-action": "export-backup",
|
|
1481
|
+
onClick: () => void view.exportBackup(),
|
|
1482
|
+
disabled: view.isMutating,
|
|
1483
|
+
children: view.labels.export
|
|
1484
|
+
}
|
|
1485
|
+
),
|
|
1372
1486
|
/* @__PURE__ */ jsxs("label", { children: [
|
|
1373
|
-
|
|
1374
|
-
/* @__PURE__ */ jsxs(
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1487
|
+
view.labels.importMode,
|
|
1488
|
+
/* @__PURE__ */ jsxs(
|
|
1489
|
+
"select",
|
|
1490
|
+
{
|
|
1491
|
+
"data-keep-action": "select-import-mode",
|
|
1492
|
+
value: view.mode,
|
|
1493
|
+
onChange: (event) => view.setMode(event.currentTarget.value),
|
|
1494
|
+
children: [
|
|
1495
|
+
/* @__PURE__ */ jsx2("option", { value: "merge", children: view.labels.merge }),
|
|
1496
|
+
/* @__PURE__ */ jsx2("option", { value: "replace", children: view.labels.replace })
|
|
1497
|
+
]
|
|
1498
|
+
}
|
|
1499
|
+
)
|
|
1378
1500
|
] }),
|
|
1379
|
-
/* @__PURE__ */ jsx2("button", { type: "button",
|
|
1501
|
+
/* @__PURE__ */ jsx2("button", { type: "button", "data-keep-action": "import-backup", onClick: view.openFilePicker, disabled: view.isMutating, children: view.labels.import }),
|
|
1380
1502
|
/* @__PURE__ */ jsx2(
|
|
1381
1503
|
"input",
|
|
1382
1504
|
{
|
|
1383
|
-
ref: inputRef,
|
|
1505
|
+
ref: view.inputRef,
|
|
1384
1506
|
type: "file",
|
|
1507
|
+
"data-keep-action": "select-backup-file",
|
|
1385
1508
|
accept: "application/json,.json",
|
|
1386
|
-
"aria-label":
|
|
1387
|
-
onChange: (event) => void
|
|
1509
|
+
"aria-label": view.labels.import,
|
|
1510
|
+
onChange: (event) => void view.importBackup(event)
|
|
1388
1511
|
}
|
|
1389
1512
|
),
|
|
1390
|
-
result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
|
|
1391
|
-
result.imported,
|
|
1513
|
+
view.result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
|
|
1514
|
+
view.result.imported,
|
|
1392
1515
|
" ",
|
|
1393
|
-
|
|
1516
|
+
view.labels.importedCount,
|
|
1394
1517
|
"; ",
|
|
1395
|
-
result.failed,
|
|
1518
|
+
view.result.failed,
|
|
1396
1519
|
" ",
|
|
1397
|
-
|
|
1520
|
+
view.labels.failedCount
|
|
1398
1521
|
] }) : null,
|
|
1399
|
-
error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(error) ?
|
|
1522
|
+
view.error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(view.error) ? view.labels.quotaError : getErrorMessage(view.error) }) : null
|
|
1400
1523
|
]
|
|
1401
1524
|
}
|
|
1402
1525
|
);
|
|
@@ -1410,48 +1533,16 @@ function getErrorMessage(error) {
|
|
|
1410
1533
|
return error instanceof Error ? error.message : "Something went wrong.";
|
|
1411
1534
|
}
|
|
1412
1535
|
|
|
1413
|
-
// src/
|
|
1536
|
+
// src/hooks/useKeepBulkActions.ts
|
|
1414
1537
|
import { useKeepList } from "@keepkit/core/react";
|
|
1415
1538
|
import { useState as useState2 } from "react";
|
|
1416
1539
|
|
|
1417
|
-
// src/KeepItemCheckbox.tsx
|
|
1418
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1419
|
-
function KeepItemCheckbox({
|
|
1420
|
-
item,
|
|
1421
|
-
checked = false,
|
|
1422
|
-
label,
|
|
1423
|
-
onCheckedChange,
|
|
1424
|
-
"aria-label": ariaLabel,
|
|
1425
|
-
...props
|
|
1426
|
-
}) {
|
|
1427
|
-
const itemLabel = getItemLabel(item) ?? item.id;
|
|
1428
|
-
const accessibleLabel = ariaLabel ?? (typeof label === "string" ? label : itemLabel);
|
|
1429
|
-
return /* @__PURE__ */ jsx3(
|
|
1430
|
-
"input",
|
|
1431
|
-
{
|
|
1432
|
-
...props,
|
|
1433
|
-
type: "checkbox",
|
|
1434
|
-
checked,
|
|
1435
|
-
"data-keepkit": "item-checkbox",
|
|
1436
|
-
"data-state": checked ? "checked" : "unchecked",
|
|
1437
|
-
"data-disabled": props.disabled ? "true" : void 0,
|
|
1438
|
-
"aria-label": accessibleLabel,
|
|
1439
|
-
onChange: (event) => onCheckedChange?.(event.currentTarget.checked)
|
|
1440
|
-
}
|
|
1441
|
-
);
|
|
1442
|
-
}
|
|
1443
|
-
function getItemLabel(item) {
|
|
1444
|
-
if (typeof item.meta !== "object" || item.meta === null || !("title" in item.meta)) return void 0;
|
|
1445
|
-
const title = item.meta.title;
|
|
1446
|
-
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
1540
|
// src/shared.tsx
|
|
1450
1541
|
import {
|
|
1451
1542
|
cloneElement,
|
|
1452
1543
|
isValidElement
|
|
1453
1544
|
} from "react";
|
|
1454
|
-
import { jsx as
|
|
1545
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1455
1546
|
function toKeepButtonItem(item) {
|
|
1456
1547
|
return {
|
|
1457
1548
|
id: item.id,
|
|
@@ -1480,11 +1571,10 @@ function renderRoot(asChild, child, props, body, componentName) {
|
|
|
1480
1571
|
if (!isValidElement(child)) throw new Error(`${componentName} with asChild requires a single React element child.`);
|
|
1481
1572
|
return cloneElement(child, { ...props, children: body });
|
|
1482
1573
|
}
|
|
1483
|
-
return /* @__PURE__ */
|
|
1574
|
+
return /* @__PURE__ */ jsx3("div", { ...props, children: body });
|
|
1484
1575
|
}
|
|
1485
1576
|
|
|
1486
|
-
// src/
|
|
1487
|
-
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1577
|
+
// src/hooks/useKeepBulkActions.ts
|
|
1488
1578
|
function isAllSelected(items, selectedIds) {
|
|
1489
1579
|
if (items.length === 0) return false;
|
|
1490
1580
|
const selected = new Set(selectedIds);
|
|
@@ -1499,42 +1589,30 @@ function toggleSelectAll(items, selectedIds) {
|
|
|
1499
1589
|
}
|
|
1500
1590
|
return nextIds;
|
|
1501
1591
|
}
|
|
1502
|
-
function
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
children,
|
|
1513
|
-
...props
|
|
1514
|
-
}) {
|
|
1515
|
-
const selectItemsLabel = useUiLabel("selectItems");
|
|
1516
|
-
const selectedCountLabel = useUiLabel("selectedCount");
|
|
1517
|
-
const deleteSelectedLabel = useUiLabel("deleteSelected");
|
|
1518
|
-
const tagsLabel = useUiLabel("tagsToApply");
|
|
1519
|
-
const applyTagsLabel = useUiLabel("applyTags");
|
|
1520
|
-
const selectionScopeLabel = useUiLabel("selectionScope");
|
|
1521
|
-
const currentPageLabel = useUiLabel("currentPage");
|
|
1522
|
-
const searchResultsLabel = useUiLabel("searchResults");
|
|
1523
|
-
const allItemsLabel = useUiLabel("allItems");
|
|
1592
|
+
function useKeepBulkActions(options) {
|
|
1593
|
+
const {
|
|
1594
|
+
query,
|
|
1595
|
+
controlledSelectedIds,
|
|
1596
|
+
defaultSelectedIds,
|
|
1597
|
+
onSelectedIdsChange,
|
|
1598
|
+
onCompleted,
|
|
1599
|
+
controlledScope,
|
|
1600
|
+
onSelectionScopeChange
|
|
1601
|
+
} = options;
|
|
1524
1602
|
const list = useKeepList(query);
|
|
1525
1603
|
const queryList = useKeepList(query ? { ...query, pagination: void 0 } : { pagination: void 0 });
|
|
1526
1604
|
const allList = useKeepList({ pagination: void 0 });
|
|
1527
1605
|
const [uncontrolledScope, setUncontrolledScope] = useState2(controlledScope ?? "page");
|
|
1528
1606
|
const selectionScope = controlledScope ?? uncontrolledScope;
|
|
1529
|
-
const setSelectionScope = (scope) => {
|
|
1530
|
-
if (controlledScope === void 0) setUncontrolledScope(scope);
|
|
1531
|
-
onSelectionScopeChange?.(scope);
|
|
1532
|
-
};
|
|
1533
1607
|
const targetItems = selectionScope === "page" ? list.items : selectionScope === "query" ? queryList.items : allList.items;
|
|
1534
1608
|
const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState2(defaultSelectedIds);
|
|
1535
1609
|
const selectedIds = controlledSelectedIds ?? uncontrolledSelectedIds;
|
|
1536
1610
|
const selected = new Set(selectedIds);
|
|
1537
1611
|
const [tagsInput, setTagsInput] = useState2("");
|
|
1612
|
+
const setSelectionScope = (scope) => {
|
|
1613
|
+
if (controlledScope === void 0) setUncontrolledScope(scope);
|
|
1614
|
+
onSelectionScopeChange?.(scope);
|
|
1615
|
+
};
|
|
1538
1616
|
const setSelectedIds = (ids) => {
|
|
1539
1617
|
if (controlledSelectedIds === void 0) setUncontrolledSelectedIds(ids);
|
|
1540
1618
|
onSelectedIdsChange?.(ids);
|
|
@@ -1571,54 +1649,156 @@ function KeepBulkActions({
|
|
|
1571
1649
|
selectionScope,
|
|
1572
1650
|
setSelectionScope
|
|
1573
1651
|
};
|
|
1652
|
+
return {
|
|
1653
|
+
state,
|
|
1654
|
+
selected,
|
|
1655
|
+
isLoading: list.isLoading,
|
|
1656
|
+
labels: {
|
|
1657
|
+
selectItems: useUiLabel("selectItems"),
|
|
1658
|
+
selectedCount: useUiLabel("selectedCount"),
|
|
1659
|
+
deleteSelected: useUiLabel("deleteSelected"),
|
|
1660
|
+
tags: useUiLabel("tagsToApply"),
|
|
1661
|
+
applyTags: useUiLabel("applyTags"),
|
|
1662
|
+
selectionScope: useUiLabel("selectionScope"),
|
|
1663
|
+
currentPage: useUiLabel("currentPage"),
|
|
1664
|
+
searchResults: useUiLabel("searchResults"),
|
|
1665
|
+
allItems: useUiLabel("allItems")
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/KeepItemCheckbox.tsx
|
|
1671
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1672
|
+
function KeepItemCheckbox({
|
|
1673
|
+
item,
|
|
1674
|
+
checked = false,
|
|
1675
|
+
label,
|
|
1676
|
+
onCheckedChange,
|
|
1677
|
+
"aria-label": ariaLabel,
|
|
1678
|
+
...props
|
|
1679
|
+
}) {
|
|
1680
|
+
const itemLabel = getItemLabel(item) ?? item.id;
|
|
1681
|
+
const accessibleLabel = ariaLabel ?? (typeof label === "string" ? label : itemLabel);
|
|
1682
|
+
return /* @__PURE__ */ jsx4(
|
|
1683
|
+
"input",
|
|
1684
|
+
{
|
|
1685
|
+
...props,
|
|
1686
|
+
type: "checkbox",
|
|
1687
|
+
checked,
|
|
1688
|
+
"data-keepkit": "item-checkbox",
|
|
1689
|
+
"data-keep-action": "select-item",
|
|
1690
|
+
"data-state": checked ? "checked" : "unchecked",
|
|
1691
|
+
"data-disabled": props.disabled ? "true" : void 0,
|
|
1692
|
+
"aria-label": accessibleLabel,
|
|
1693
|
+
onChange: (event) => onCheckedChange?.(event.currentTarget.checked)
|
|
1694
|
+
}
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
function getItemLabel(item) {
|
|
1698
|
+
if (typeof item.meta !== "object" || item.meta === null || !("title" in item.meta)) return void 0;
|
|
1699
|
+
const title = item.meta.title;
|
|
1700
|
+
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// src/KeepBulkActions.tsx
|
|
1704
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1705
|
+
function KeepBulkActions({
|
|
1706
|
+
query,
|
|
1707
|
+
selectedIds: controlledSelectedIds,
|
|
1708
|
+
defaultSelectedIds = [],
|
|
1709
|
+
onSelectedIdsChange,
|
|
1710
|
+
renderItem,
|
|
1711
|
+
onCompleted,
|
|
1712
|
+
selectionScope: controlledScope,
|
|
1713
|
+
onSelectionScopeChange,
|
|
1714
|
+
render,
|
|
1715
|
+
children,
|
|
1716
|
+
...props
|
|
1717
|
+
}) {
|
|
1718
|
+
const { state, selected, isLoading, labels } = useKeepBulkActions({
|
|
1719
|
+
query,
|
|
1720
|
+
controlledSelectedIds,
|
|
1721
|
+
defaultSelectedIds,
|
|
1722
|
+
onSelectedIdsChange,
|
|
1723
|
+
onCompleted,
|
|
1724
|
+
controlledScope,
|
|
1725
|
+
onSelectionScopeChange
|
|
1726
|
+
});
|
|
1574
1727
|
const body = render ? render(state) : typeof children === "function" ? children(state) : children ?? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1575
1728
|
/* @__PURE__ */ jsxs2("fieldset", { children: [
|
|
1576
|
-
/* @__PURE__ */ jsx5("legend", { children:
|
|
1729
|
+
/* @__PURE__ */ jsx5("legend", { children: labels.selectItems }),
|
|
1577
1730
|
/* @__PURE__ */ jsxs2("label", { children: [
|
|
1578
|
-
|
|
1731
|
+
labels.selectionScope,
|
|
1579
1732
|
/* @__PURE__ */ jsxs2(
|
|
1580
1733
|
"select",
|
|
1581
1734
|
{
|
|
1582
|
-
|
|
1583
|
-
|
|
1735
|
+
"data-keep-action": "select-scope",
|
|
1736
|
+
value: state.selectionScope,
|
|
1737
|
+
onChange: (event) => state.setSelectionScope(event.currentTarget.value),
|
|
1584
1738
|
children: [
|
|
1585
|
-
/* @__PURE__ */ jsx5("option", { value: "page", children:
|
|
1586
|
-
/* @__PURE__ */ jsx5("option", { value: "query", children:
|
|
1587
|
-
/* @__PURE__ */ jsx5("option", { value: "all", children:
|
|
1739
|
+
/* @__PURE__ */ jsx5("option", { value: "page", children: labels.currentPage }),
|
|
1740
|
+
/* @__PURE__ */ jsx5("option", { value: "query", children: labels.searchResults }),
|
|
1741
|
+
/* @__PURE__ */ jsx5("option", { value: "all", children: labels.allItems })
|
|
1588
1742
|
]
|
|
1589
1743
|
}
|
|
1590
1744
|
)
|
|
1591
1745
|
] }),
|
|
1592
1746
|
/* @__PURE__ */ jsxs2("label", { children: [
|
|
1593
|
-
/* @__PURE__ */ jsx5(
|
|
1594
|
-
|
|
1747
|
+
/* @__PURE__ */ jsx5(
|
|
1748
|
+
"input",
|
|
1749
|
+
{
|
|
1750
|
+
type: "checkbox",
|
|
1751
|
+
"data-keep-action": "select-all",
|
|
1752
|
+
checked: state.allSelected,
|
|
1753
|
+
onChange: state.toggleAll,
|
|
1754
|
+
"aria-label": labels.selectItems
|
|
1755
|
+
}
|
|
1756
|
+
),
|
|
1757
|
+
state.selectedIds.length,
|
|
1595
1758
|
" ",
|
|
1596
|
-
|
|
1759
|
+
labels.selectedCount
|
|
1597
1760
|
] }),
|
|
1598
|
-
|
|
1761
|
+
state.items.map((item) => /* @__PURE__ */ jsxs2("span", { children: [
|
|
1599
1762
|
/* @__PURE__ */ jsx5(
|
|
1600
1763
|
KeepItemCheckbox,
|
|
1601
1764
|
{
|
|
1602
1765
|
item,
|
|
1603
1766
|
checked: selected.has(item.id),
|
|
1604
|
-
onCheckedChange: () => toggle(item.id)
|
|
1767
|
+
onCheckedChange: () => state.toggle(item.id)
|
|
1605
1768
|
}
|
|
1606
1769
|
),
|
|
1607
1770
|
renderItem ? renderItem(item, selected.has(item.id)) : getMetaTitle(item.meta) ?? item.id
|
|
1608
1771
|
] }, item.id))
|
|
1609
1772
|
] }),
|
|
1610
|
-
/* @__PURE__ */ jsx5(
|
|
1773
|
+
/* @__PURE__ */ jsx5(
|
|
1774
|
+
"button",
|
|
1775
|
+
{
|
|
1776
|
+
type: "button",
|
|
1777
|
+
"data-keep-action": "delete-selected",
|
|
1778
|
+
onClick: () => void state.remove(),
|
|
1779
|
+
disabled: state.selectedIds.length === 0 || state.isMutating,
|
|
1780
|
+
children: labels.deleteSelected
|
|
1781
|
+
}
|
|
1782
|
+
),
|
|
1611
1783
|
/* @__PURE__ */ jsxs2("label", { children: [
|
|
1612
|
-
|
|
1613
|
-
/* @__PURE__ */ jsx5(
|
|
1784
|
+
labels.tags,
|
|
1785
|
+
/* @__PURE__ */ jsx5(
|
|
1786
|
+
"input",
|
|
1787
|
+
{
|
|
1788
|
+
"data-keep-action": "edit-tags",
|
|
1789
|
+
value: state.tagsInput,
|
|
1790
|
+
onChange: (event) => state.setTagsInput(event.currentTarget.value)
|
|
1791
|
+
}
|
|
1792
|
+
)
|
|
1614
1793
|
] }),
|
|
1615
1794
|
/* @__PURE__ */ jsx5(
|
|
1616
1795
|
"button",
|
|
1617
1796
|
{
|
|
1618
1797
|
type: "button",
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1798
|
+
"data-keep-action": "apply-tags",
|
|
1799
|
+
onClick: () => void state.updateTags(),
|
|
1800
|
+
disabled: state.selectedIds.length === 0 || state.isMutating,
|
|
1801
|
+
children: labels.applyTags
|
|
1622
1802
|
}
|
|
1623
1803
|
)
|
|
1624
1804
|
] });
|
|
@@ -1627,9 +1807,9 @@ function KeepBulkActions({
|
|
|
1627
1807
|
{
|
|
1628
1808
|
...props,
|
|
1629
1809
|
"data-keepkit": "bulk-actions",
|
|
1630
|
-
"aria-busy":
|
|
1631
|
-
"data-state": selectedIds.length > 0 ? "selected" : "idle",
|
|
1632
|
-
"data-loading":
|
|
1810
|
+
"aria-busy": state.isMutating || props["aria-busy"],
|
|
1811
|
+
"data-state": state.selectedIds.length > 0 ? "selected" : "idle",
|
|
1812
|
+
"data-loading": isLoading || state.isMutating ? "true" : void 0,
|
|
1633
1813
|
children: body
|
|
1634
1814
|
}
|
|
1635
1815
|
);
|
|
@@ -1637,10 +1817,31 @@ function KeepBulkActions({
|
|
|
1637
1817
|
|
|
1638
1818
|
// src/KeepButton.tsx
|
|
1639
1819
|
import {
|
|
1640
|
-
KeepButton as CoreKeepButton
|
|
1641
|
-
useKeepItem
|
|
1820
|
+
KeepButton as CoreKeepButton
|
|
1642
1821
|
} from "@keepkit/core/react";
|
|
1643
|
-
import { createElement } from "react";
|
|
1822
|
+
import { createElement, useEffect, useRef as useRef2 } from "react";
|
|
1823
|
+
|
|
1824
|
+
// src/hooks/useKeepButton.ts
|
|
1825
|
+
import { useKeepItem } from "@keepkit/core/react";
|
|
1826
|
+
function useKeepButton({ item, labels, icons, children }) {
|
|
1827
|
+
return {
|
|
1828
|
+
buttonState: useKeepItem(item),
|
|
1829
|
+
emitFeedback: useKeepUiFeedback(),
|
|
1830
|
+
customStateLabel: labels?.loading !== void 0 || labels?.error !== void 0 || icons !== void 0 && children === void 0,
|
|
1831
|
+
labels: {
|
|
1832
|
+
save: useUiLabel("save", typeof labels?.unsaved === "string" ? labels.unsaved : void 0),
|
|
1833
|
+
saved: useUiLabel("saved", typeof labels?.saved === "string" ? labels.saved : void 0),
|
|
1834
|
+
loading: useUiLabel("loading", typeof labels?.loading === "string" ? labels.loading : void 0),
|
|
1835
|
+
error: useUiLabel("error", typeof labels?.error === "string" ? labels.error : void 0),
|
|
1836
|
+
savedMessage: useUiLabel("savedMessage"),
|
|
1837
|
+
removedMessage: useUiLabel("removedMessage"),
|
|
1838
|
+
restoredMessage: useUiLabel("restoredMessage"),
|
|
1839
|
+
undo: useUiLabel("undo")
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// src/KeepButton.tsx
|
|
1644
1845
|
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1645
1846
|
function KeepButton({
|
|
1646
1847
|
labels,
|
|
@@ -1648,18 +1849,42 @@ function KeepButton({
|
|
|
1648
1849
|
iconOnly = false,
|
|
1649
1850
|
showLabel = true,
|
|
1650
1851
|
iconClassName,
|
|
1852
|
+
onClick,
|
|
1853
|
+
onToggleError,
|
|
1651
1854
|
...props
|
|
1652
1855
|
}) {
|
|
1653
|
-
const
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1856
|
+
const view = useKeepButton({ item: props.item, labels, icons, children: props.children });
|
|
1857
|
+
const pendingToggle = useRef2(null);
|
|
1858
|
+
useEffect(() => {
|
|
1859
|
+
const pending = pendingToggle.current;
|
|
1860
|
+
if (!pending || view.buttonState.isMutating || pending.wasSaved === view.buttonState.isSaved) return;
|
|
1861
|
+
pendingToggle.current = null;
|
|
1862
|
+
if (view.buttonState.isSaved && view.buttonState.item) {
|
|
1863
|
+
view.emitFeedback({ type: "item-saved", item: view.buttonState.item, message: view.labels.savedMessage });
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
if (!pending.item) return;
|
|
1867
|
+
const removedItem = pending.item;
|
|
1868
|
+
view.emitFeedback({
|
|
1869
|
+
type: "item-removed",
|
|
1870
|
+
item: removedItem,
|
|
1871
|
+
message: view.labels.removedMessage,
|
|
1872
|
+
undoLabel: view.labels.undo,
|
|
1873
|
+
undo: async () => {
|
|
1874
|
+
await view.buttonState.save();
|
|
1875
|
+
view.emitFeedback({
|
|
1876
|
+
type: "item-restored",
|
|
1877
|
+
item: removedItem,
|
|
1878
|
+
items: [removedItem],
|
|
1879
|
+
message: view.labels.restoredMessage
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
}, [view]);
|
|
1659
1884
|
const getStateContent = (state) => {
|
|
1660
1885
|
if (typeof props.children === "function") return props.children(state);
|
|
1661
1886
|
if (props.children !== void 0) return props.children;
|
|
1662
|
-
const label = state.error ? labels?.error ??
|
|
1887
|
+
const label = state.error ? labels?.error ?? view.labels.error : state.isMutating ? labels?.loading ?? view.labels.loading : state.isSaved ? labels?.saved ?? view.labels.saved : labels?.unsaved ?? view.labels.save;
|
|
1663
1888
|
if (!icons) return label;
|
|
1664
1889
|
const icon = state.error ? icons.error : state.isMutating ? icons.loading : state.isSaved ? icons.remove ?? icons.saved : icons.save;
|
|
1665
1890
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
@@ -1669,15 +1894,27 @@ function KeepButton({
|
|
|
1669
1894
|
};
|
|
1670
1895
|
const sharedProps = {
|
|
1671
1896
|
...props,
|
|
1897
|
+
onClick: (event) => {
|
|
1898
|
+
onClick?.(event);
|
|
1899
|
+
if (!event.defaultPrevented) {
|
|
1900
|
+
pendingToggle.current = { wasSaved: view.buttonState.isSaved, item: view.buttonState.item };
|
|
1901
|
+
}
|
|
1902
|
+
},
|
|
1903
|
+
onToggleError: (error) => {
|
|
1904
|
+
pendingToggle.current = null;
|
|
1905
|
+
onToggleError?.(error);
|
|
1906
|
+
},
|
|
1672
1907
|
"data-keepkit": "button",
|
|
1908
|
+
"data-keep-action": "toggle-save",
|
|
1909
|
+
"data-has-custom-icon": icons ? "true" : void 0,
|
|
1673
1910
|
"data-icon-only": iconOnly ? "true" : void 0,
|
|
1674
|
-
"aria-busy": props["aria-busy"] ?? (buttonState.isLoading || buttonState.isMutating),
|
|
1675
|
-
savedLabel: labels?.saved ?? props.savedLabel ??
|
|
1676
|
-
unsavedLabel: labels?.unsaved ?? props.unsavedLabel ??
|
|
1911
|
+
"aria-busy": props["aria-busy"] ?? (view.buttonState.isLoading || view.buttonState.isMutating),
|
|
1912
|
+
savedLabel: labels?.saved ?? props.savedLabel ?? view.labels.saved,
|
|
1913
|
+
unsavedLabel: labels?.unsaved ?? props.unsavedLabel ?? view.labels.save,
|
|
1677
1914
|
savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,
|
|
1678
1915
|
unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
|
|
1679
1916
|
};
|
|
1680
|
-
if (!customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
|
|
1917
|
+
if (!view.customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
|
|
1681
1918
|
return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx6(Fragment2, { children: getStateContent(state) }) });
|
|
1682
1919
|
}
|
|
1683
1920
|
function renderIcon(icon, className) {
|
|
@@ -1686,88 +1923,296 @@ function renderIcon(icon, className) {
|
|
|
1686
1923
|
}
|
|
1687
1924
|
|
|
1688
1925
|
// src/KeepCollection.tsx
|
|
1689
|
-
import { KeepErrorBoundary as KeepErrorBoundary2
|
|
1690
|
-
import { useMemo as useMemo3, useState as useState6 } from "react";
|
|
1926
|
+
import { KeepErrorBoundary as KeepErrorBoundary2 } from "@keepkit/core/react";
|
|
1691
1927
|
|
|
1692
|
-
// src/
|
|
1693
|
-
import {
|
|
1694
|
-
|
|
1695
|
-
useKeepList as useKeepList2
|
|
1696
|
-
} from "@keepkit/core/react";
|
|
1697
|
-
import { isValidElement as isValidElement3 } from "react";
|
|
1928
|
+
// src/hooks/useKeepCollection.ts
|
|
1929
|
+
import { useKeepList as useKeepList2 } from "@keepkit/core/react";
|
|
1930
|
+
import { useMemo as useMemo2, useState as useState3 } from "react";
|
|
1698
1931
|
|
|
1699
|
-
// src/
|
|
1700
|
-
import { useKeepItem as useKeepItem2 } from "@keepkit/core/react";
|
|
1932
|
+
// src/url-sync.tsx
|
|
1701
1933
|
import {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
import {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
...props,
|
|
1718
|
-
className,
|
|
1719
|
-
"data-keepkit": "status-badge",
|
|
1720
|
-
"data-status": status,
|
|
1721
|
-
"data-item-status": resolvedStatus,
|
|
1722
|
-
children: label ?? statusLabel
|
|
1934
|
+
DEFAULT_KEEP_URL_PARAMS,
|
|
1935
|
+
decodeKeepListQuery,
|
|
1936
|
+
encodeKeepListQuery
|
|
1937
|
+
} from "@keepkit/core/core";
|
|
1938
|
+
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
1939
|
+
function createNextPagesRouterAdapter(router) {
|
|
1940
|
+
const getUrl = () => router.asPath ?? (typeof window === "undefined" ? "/" : window.location.href);
|
|
1941
|
+
return {
|
|
1942
|
+
getUrl,
|
|
1943
|
+
subscribe: router.events ? (listener) => {
|
|
1944
|
+
router.events?.on("routeChangeComplete", listener);
|
|
1945
|
+
return () => router.events?.off("routeChangeComplete", listener);
|
|
1946
|
+
} : void 0,
|
|
1947
|
+
navigate: (url, mode) => {
|
|
1948
|
+
void router[mode](url, void 0, { shallow: true });
|
|
1723
1949
|
}
|
|
1724
|
-
|
|
1725
|
-
}
|
|
1726
|
-
function getStatusLabelKey(status) {
|
|
1727
|
-
switch (status) {
|
|
1728
|
-
case "available":
|
|
1729
|
-
return "statusAvailable";
|
|
1730
|
-
case "expired":
|
|
1731
|
-
return "statusExpired";
|
|
1732
|
-
case "removed":
|
|
1733
|
-
return "statusRemoved";
|
|
1734
|
-
case "deleted":
|
|
1735
|
-
return "statusDeleted";
|
|
1736
|
-
case "private":
|
|
1737
|
-
return "statusPrivate";
|
|
1738
|
-
case "unknown":
|
|
1739
|
-
return "statusUnknown";
|
|
1740
|
-
case "restricted":
|
|
1741
|
-
return "statusPrivate";
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
function getDisplayStatus(status) {
|
|
1745
|
-
if (status === "available") return "available";
|
|
1746
|
-
if (status === "expired") return "expired";
|
|
1747
|
-
if (status === "removed") return "removed";
|
|
1748
|
-
return "restricted";
|
|
1950
|
+
};
|
|
1749
1951
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1952
|
+
function useKeepUrlSync({
|
|
1953
|
+
enabled = true,
|
|
1954
|
+
query,
|
|
1955
|
+
onQueryChange,
|
|
1956
|
+
options = {},
|
|
1957
|
+
adapter: providedAdapter
|
|
1958
|
+
}) {
|
|
1959
|
+
const browserAdapterRef = useRef3(getBrowserAdapter());
|
|
1960
|
+
const adapter = providedAdapter ?? browserAdapterRef.current;
|
|
1961
|
+
const onQueryChangeRef = useRef3(onQueryChange);
|
|
1962
|
+
onQueryChangeRef.current = onQueryChange;
|
|
1963
|
+
const skipWriteRef = useRef3(true);
|
|
1964
|
+
const params = options.params;
|
|
1965
|
+
useEffect2(() => {
|
|
1966
|
+
if (!enabled) return;
|
|
1967
|
+
const read = () => {
|
|
1968
|
+
const url = adapter.getUrl();
|
|
1969
|
+
const decoded = decodeKeepListQuery(url, { params });
|
|
1970
|
+
skipWriteRef.current = true;
|
|
1971
|
+
onQueryChangeRef.current((previousQuery) => ({
|
|
1972
|
+
...previousQuery,
|
|
1973
|
+
...decoded.search ? { search: decoded.search } : { search: void 0 },
|
|
1974
|
+
...decoded.tags ? { tags: decoded.tags } : { tags: void 0 },
|
|
1975
|
+
...decoded.sort ? { sort: decoded.sort } : {},
|
|
1976
|
+
...decoded.pagination ? { pagination: { ...previousQuery.pagination, ...decoded.pagination } } : {}
|
|
1977
|
+
}));
|
|
1978
|
+
};
|
|
1979
|
+
read();
|
|
1980
|
+
return adapter.subscribe?.(read);
|
|
1981
|
+
}, [adapter, enabled, params]);
|
|
1982
|
+
useEffect2(() => {
|
|
1983
|
+
if (!enabled) return;
|
|
1984
|
+
if (skipWriteRef.current) {
|
|
1985
|
+
skipWriteRef.current = false;
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
const currentUrl = new URL(adapter.getUrl(), "http://keepkit.invalid");
|
|
1989
|
+
const urlParams = { ...DEFAULT_KEEP_URL_PARAMS, ...params };
|
|
1990
|
+
for (const key of Object.values(urlParams)) currentUrl.searchParams.delete(key);
|
|
1991
|
+
const nextParams = encodeKeepListQuery(query, { params });
|
|
1992
|
+
nextParams.forEach((value, key) => {
|
|
1993
|
+
currentUrl.searchParams.append(key, value);
|
|
1994
|
+
});
|
|
1995
|
+
const nextUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
|
|
1996
|
+
adapter.navigate(nextUrl, options.history ?? "push");
|
|
1997
|
+
}, [adapter, enabled, options.history, params, query]);
|
|
1998
|
+
}
|
|
1999
|
+
function getBrowserAdapter() {
|
|
2000
|
+
return {
|
|
2001
|
+
getUrl: () => typeof window === "undefined" ? "/" : window.location.href,
|
|
2002
|
+
subscribe: (listener) => {
|
|
2003
|
+
if (typeof window === "undefined") return () => void 0;
|
|
2004
|
+
window.addEventListener("popstate", listener);
|
|
2005
|
+
return () => window.removeEventListener("popstate", listener);
|
|
2006
|
+
},
|
|
2007
|
+
navigate: (url, mode) => {
|
|
2008
|
+
if (typeof window === "undefined") return;
|
|
2009
|
+
window.history[mode === "push" ? "pushState" : "replaceState"]({}, "", url);
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// src/hooks/useKeepCollection.ts
|
|
2015
|
+
function useKeepCollection({
|
|
2016
|
+
query,
|
|
2017
|
+
pageSize,
|
|
2018
|
+
urlSync,
|
|
2019
|
+
urlAdapter,
|
|
2020
|
+
features
|
|
1762
2021
|
}) {
|
|
2022
|
+
const enabled = {
|
|
2023
|
+
search: true,
|
|
2024
|
+
sort: true,
|
|
2025
|
+
pagination: true,
|
|
2026
|
+
tagFilter: false,
|
|
2027
|
+
bulkActions: false,
|
|
2028
|
+
...features
|
|
2029
|
+
};
|
|
2030
|
+
const [searchValue, setSearchValue] = useState3(query.search?.query ?? "");
|
|
2031
|
+
const [sort, setSort] = useState3(query.sort ?? { by: "updatedAt", direction: "desc" });
|
|
2032
|
+
const [tag, setTag] = useState3(query.tags?.[0]);
|
|
2033
|
+
const [page, setPage] = useState3(query.pagination?.page ?? 1);
|
|
2034
|
+
const resolvedPageSize = query.pagination?.pageSize ?? pageSize;
|
|
2035
|
+
const resolvedQuery = useMemo2(
|
|
2036
|
+
() => ({
|
|
2037
|
+
...query,
|
|
2038
|
+
search: enabled.search ? { ...query.search, query: searchValue } : query.search,
|
|
2039
|
+
sort: enabled.sort ? sort : query.sort,
|
|
2040
|
+
tags: tag ? [.../* @__PURE__ */ new Set([...query.tags ?? [], tag])] : query.tags,
|
|
2041
|
+
pagination: enabled.pagination ? { ...query.pagination, page, pageSize: resolvedPageSize } : query.pagination
|
|
2042
|
+
}),
|
|
2043
|
+
[enabled.pagination, enabled.search, enabled.sort, page, query, resolvedPageSize, searchValue, sort, tag]
|
|
2044
|
+
);
|
|
2045
|
+
useKeepUrlSync({
|
|
2046
|
+
enabled: Boolean(urlSync),
|
|
2047
|
+
query: resolvedQuery,
|
|
2048
|
+
onQueryChange: (nextOrUpdater) => {
|
|
2049
|
+
const next = typeof nextOrUpdater === "function" ? nextOrUpdater(resolvedQuery) : nextOrUpdater;
|
|
2050
|
+
setSearchValue(next.search?.query ?? "");
|
|
2051
|
+
setSort(next.sort ?? { by: "updatedAt", direction: "desc" });
|
|
2052
|
+
setTag(next.tags?.[0]);
|
|
2053
|
+
setPage(next.pagination?.page ?? 1);
|
|
2054
|
+
},
|
|
2055
|
+
options: typeof urlSync === "object" ? urlSync : {},
|
|
2056
|
+
adapter: urlAdapter
|
|
2057
|
+
});
|
|
2058
|
+
const list = useKeepList2(resolvedQuery);
|
|
2059
|
+
return {
|
|
2060
|
+
enabled,
|
|
2061
|
+
searchValue,
|
|
2062
|
+
sortValue: sortToValue(sort),
|
|
2063
|
+
tag,
|
|
2064
|
+
resolvedPageSize,
|
|
2065
|
+
resolvedQuery,
|
|
2066
|
+
list,
|
|
2067
|
+
setSearchValue: (value) => {
|
|
2068
|
+
setSearchValue(value);
|
|
2069
|
+
setPage(1);
|
|
2070
|
+
},
|
|
2071
|
+
setSortValue: (_value, nextSort) => {
|
|
2072
|
+
setSort(nextSort);
|
|
2073
|
+
setPage(1);
|
|
2074
|
+
},
|
|
2075
|
+
setTag: (value) => {
|
|
2076
|
+
setTag(value);
|
|
2077
|
+
setPage(1);
|
|
2078
|
+
},
|
|
2079
|
+
setPage
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// src/KeepList.tsx
|
|
2084
|
+
import { KeepErrorBoundary } from "@keepkit/core/react";
|
|
2085
|
+
import { isValidElement as isValidElement3 } from "react";
|
|
2086
|
+
|
|
2087
|
+
// src/hooks/useKeepListView.ts
|
|
2088
|
+
import { useKeepList as useKeepList3 } from "@keepkit/core/react";
|
|
2089
|
+
function useKeepListView(query) {
|
|
2090
|
+
return {
|
|
2091
|
+
state: useKeepList3(query),
|
|
2092
|
+
labels: {
|
|
2093
|
+
loading: useUiLabel("loadingItems"),
|
|
2094
|
+
empty: useUiLabel("noItems"),
|
|
2095
|
+
error: useUiLabel("errorItems")
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/KeepItemCard.tsx
|
|
2101
|
+
import {
|
|
2102
|
+
createContext as createContext2,
|
|
2103
|
+
createElement as createElement2,
|
|
2104
|
+
isValidElement as isValidElement2,
|
|
2105
|
+
useContext as useContext2
|
|
2106
|
+
} from "react";
|
|
2107
|
+
|
|
2108
|
+
// src/hooks/useKeepItemCard.ts
|
|
2109
|
+
import { useKeepItem as useKeepItem2 } from "@keepkit/core/react";
|
|
2110
|
+
function useKeepItemCard(options) {
|
|
2111
|
+
const {
|
|
2112
|
+
item,
|
|
2113
|
+
title,
|
|
2114
|
+
getTitle,
|
|
2115
|
+
getImageProps,
|
|
2116
|
+
href: hrefOption,
|
|
2117
|
+
linkTargetAttribute,
|
|
2118
|
+
linkRel,
|
|
2119
|
+
onRemoveError,
|
|
2120
|
+
onRemoved
|
|
2121
|
+
} = options;
|
|
2122
|
+
const itemState = useKeepItem2(item);
|
|
2123
|
+
const emitFeedback = useKeepUiFeedback();
|
|
2124
|
+
const removedMessage = useUiLabel("removedMessage");
|
|
2125
|
+
const restoredMessage = useUiLabel("restoredMessage");
|
|
2126
|
+
const undoLabel = useUiLabel("undo");
|
|
2127
|
+
const resolvedTitle = typeof title === "function" ? title(item) : title ?? getTitle?.(item) ?? getMetaTitle(item.meta) ?? item.id;
|
|
2128
|
+
const imageProps = getImageProps?.(item, resolvedTitle);
|
|
2129
|
+
const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
|
|
2130
|
+
const isAvailable = item.status === void 0 || item.status === "available";
|
|
2131
|
+
const isExternalLink = href ? /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href) : false;
|
|
2132
|
+
const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey(item.status) : "statusUnknown";
|
|
2133
|
+
const unavailableLabel = useUiLabel(statusLabelKey);
|
|
2134
|
+
const remove = async () => {
|
|
2135
|
+
const wasSaved = itemState.isSaved;
|
|
2136
|
+
try {
|
|
2137
|
+
await itemState.removeWithUndo();
|
|
2138
|
+
onRemoved?.(item);
|
|
2139
|
+
if (!wasSaved) return;
|
|
2140
|
+
emitFeedback({
|
|
2141
|
+
type: "item-removed",
|
|
2142
|
+
item,
|
|
2143
|
+
message: removedMessage,
|
|
2144
|
+
undoLabel,
|
|
2145
|
+
undo: async () => {
|
|
2146
|
+
await itemState.undo();
|
|
2147
|
+
emitFeedback({ type: "item-restored", item, items: [item], message: restoredMessage });
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
} catch (cause) {
|
|
2151
|
+
onRemoveError?.(cause);
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
const state = {
|
|
2155
|
+
item,
|
|
2156
|
+
isSaved: itemState.isSaved,
|
|
2157
|
+
isMutating: itemState.isMutating,
|
|
2158
|
+
error: itemState.error,
|
|
2159
|
+
remove,
|
|
2160
|
+
status: item.status
|
|
2161
|
+
};
|
|
2162
|
+
return {
|
|
2163
|
+
itemState,
|
|
2164
|
+
state,
|
|
2165
|
+
resolvedTitle,
|
|
2166
|
+
imageProps,
|
|
2167
|
+
href,
|
|
2168
|
+
isAvailable,
|
|
2169
|
+
displayStatus: getDisplayStatus(item.status),
|
|
2170
|
+
resolvedLinkTarget: linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0),
|
|
2171
|
+
resolvedLinkRel: linkRel ?? (isExternalLink ? "noreferrer" : void 0),
|
|
2172
|
+
statusLabel: item.status && item.status !== "available" ? unavailableLabel : void 0,
|
|
2173
|
+
remove,
|
|
2174
|
+
labels: {
|
|
2175
|
+
save: useUiLabel("save"),
|
|
2176
|
+
savedAt: useUiLabel("saved"),
|
|
2177
|
+
error: useUiLabel("error"),
|
|
2178
|
+
remove: useUiLabel("remove"),
|
|
2179
|
+
tags: useUiLabel("tags")
|
|
2180
|
+
}
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
function getStatusLabelKey(status) {
|
|
2184
|
+
switch (status) {
|
|
2185
|
+
case "expired":
|
|
2186
|
+
return "statusExpired";
|
|
2187
|
+
case "removed":
|
|
2188
|
+
return "statusRemoved";
|
|
2189
|
+
case "deleted":
|
|
2190
|
+
return "statusDeleted";
|
|
2191
|
+
case "private":
|
|
2192
|
+
return "statusPrivate";
|
|
2193
|
+
default:
|
|
2194
|
+
return "statusUnknown";
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
function getDisplayStatus(status) {
|
|
2198
|
+
if (status === void 0 || status === "available") return "available";
|
|
2199
|
+
if (status === "expired") return "expired";
|
|
2200
|
+
if (status === "removed") return "removed";
|
|
2201
|
+
return "restricted";
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// src/hooks/useKeepStaleNotice.ts
|
|
2205
|
+
import { useKeepContext as useKeepContext2 } from "@keepkit/core/react";
|
|
2206
|
+
import { useState as useState4 } from "react";
|
|
2207
|
+
function useKeepStaleNotice({ item, onRetry, onRemoved }) {
|
|
1763
2208
|
const context = useKeepContext2();
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
const
|
|
1767
|
-
const
|
|
1768
|
-
const [
|
|
1769
|
-
const
|
|
1770
|
-
async function
|
|
2209
|
+
const emitFeedback = useKeepUiFeedback();
|
|
2210
|
+
const removedMessage = useUiLabel("removedMessage");
|
|
2211
|
+
const restoredMessage = useUiLabel("restoredMessage");
|
|
2212
|
+
const undoLabel = useUiLabel("undo");
|
|
2213
|
+
const [isRetrying, setIsRetrying] = useState4(false);
|
|
2214
|
+
const [error, setError] = useState4(null);
|
|
2215
|
+
async function retry() {
|
|
1771
2216
|
setError(null);
|
|
1772
2217
|
setIsRetrying(true);
|
|
1773
2218
|
try {
|
|
@@ -1779,22 +2224,157 @@ function KeepStaleNotice({
|
|
|
1779
2224
|
setIsRetrying(false);
|
|
1780
2225
|
}
|
|
1781
2226
|
}
|
|
1782
|
-
async function
|
|
2227
|
+
async function remove() {
|
|
1783
2228
|
try {
|
|
1784
2229
|
await context.removeItemWithUndo(item.id);
|
|
1785
2230
|
onRemoved?.(item);
|
|
2231
|
+
emitFeedback({
|
|
2232
|
+
type: "item-removed",
|
|
2233
|
+
item,
|
|
2234
|
+
message: removedMessage,
|
|
2235
|
+
undoLabel,
|
|
2236
|
+
undo: async () => {
|
|
2237
|
+
await context.undoLastRemoval();
|
|
2238
|
+
emitFeedback({ type: "item-restored", item, items: [item], message: restoredMessage });
|
|
2239
|
+
}
|
|
2240
|
+
});
|
|
1786
2241
|
} catch (cause) {
|
|
1787
2242
|
setError(cause);
|
|
1788
2243
|
}
|
|
1789
2244
|
}
|
|
1790
|
-
return
|
|
1791
|
-
|
|
2245
|
+
return {
|
|
2246
|
+
status: item.status && item.status !== "available" ? item.status : "unknown",
|
|
2247
|
+
error,
|
|
2248
|
+
isRetrying,
|
|
2249
|
+
isMutating: context.isMutating,
|
|
2250
|
+
retry,
|
|
2251
|
+
remove,
|
|
2252
|
+
labels: {
|
|
2253
|
+
retry: useUiLabel("retry"),
|
|
2254
|
+
remove: useUiLabel("removeFromList"),
|
|
2255
|
+
error: useUiLabel("error")
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
function useKeepPruneStale({ statuses, onPruned }) {
|
|
2260
|
+
const context = useKeepContext2();
|
|
2261
|
+
const emitFeedback = useKeepUiFeedback();
|
|
2262
|
+
const staleItems = context.items.filter((item) => item.status && statuses.includes(item.status));
|
|
2263
|
+
const staleIds = staleItems.map((item) => item.id);
|
|
2264
|
+
const restoredMessage = useUiLabel("restoredMessage");
|
|
2265
|
+
const prunedMessage = useUiLabel("stalePrunedMessage");
|
|
2266
|
+
const undoLabel = useUiLabel("undo");
|
|
2267
|
+
return {
|
|
2268
|
+
staleIds,
|
|
2269
|
+
isMutating: context.isMutating,
|
|
2270
|
+
label: useUiLabel("pruneStale"),
|
|
2271
|
+
prune: async () => {
|
|
2272
|
+
const ids = [...staleIds];
|
|
2273
|
+
const items = [...staleItems];
|
|
2274
|
+
await context.removeItemsWithUndo(ids);
|
|
2275
|
+
onPruned?.(ids);
|
|
2276
|
+
if (items.length > 0) {
|
|
2277
|
+
emitFeedback({
|
|
2278
|
+
type: "stale-pruned",
|
|
2279
|
+
item: items[0],
|
|
2280
|
+
items,
|
|
2281
|
+
message: prunedMessage,
|
|
2282
|
+
undoLabel,
|
|
2283
|
+
undo: async () => {
|
|
2284
|
+
await context.undoLastRemoval();
|
|
2285
|
+
emitFeedback({ type: "item-restored", item: items[0], items, message: restoredMessage });
|
|
2286
|
+
}
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
// src/hooks/useKeepItemStatusBadge.ts
|
|
2294
|
+
function useKeepItemStatusBadge(status) {
|
|
2295
|
+
return { resolvedStatus: getDisplayStatus2(status), statusLabel: useUiLabel(getStatusLabelKey2(status)) };
|
|
2296
|
+
}
|
|
2297
|
+
function getStatusLabelKey2(status) {
|
|
2298
|
+
switch (status) {
|
|
2299
|
+
case "available":
|
|
2300
|
+
return "statusAvailable";
|
|
2301
|
+
case "expired":
|
|
2302
|
+
return "statusExpired";
|
|
2303
|
+
case "removed":
|
|
2304
|
+
return "statusRemoved";
|
|
2305
|
+
case "deleted":
|
|
2306
|
+
return "statusDeleted";
|
|
2307
|
+
case "private":
|
|
2308
|
+
return "statusPrivate";
|
|
2309
|
+
case "unknown":
|
|
2310
|
+
return "statusUnknown";
|
|
2311
|
+
case "restricted":
|
|
2312
|
+
return "statusPrivate";
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
function getDisplayStatus2(status) {
|
|
2316
|
+
if (status === "available") return "available";
|
|
2317
|
+
if (status === "expired") return "expired";
|
|
2318
|
+
if (status === "removed") return "removed";
|
|
2319
|
+
return "restricted";
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
// src/KeepItemStatusBadge.tsx
|
|
2323
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2324
|
+
function KeepItemStatusBadge({ status = "available", label, className, ...props }) {
|
|
2325
|
+
const view = useKeepItemStatusBadge(status);
|
|
2326
|
+
return /* @__PURE__ */ jsx7(
|
|
2327
|
+
"span",
|
|
2328
|
+
{
|
|
2329
|
+
...props,
|
|
2330
|
+
className,
|
|
2331
|
+
"data-keepkit": "status-badge",
|
|
2332
|
+
"data-status": status,
|
|
2333
|
+
"data-item-status": view.resolvedStatus,
|
|
2334
|
+
children: label ?? view.statusLabel
|
|
2335
|
+
}
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/KeepStaleNotice.tsx
|
|
2340
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2341
|
+
function KeepStaleNotice({
|
|
2342
|
+
item,
|
|
2343
|
+
onRetry,
|
|
2344
|
+
onRemoved,
|
|
2345
|
+
retryLabel,
|
|
2346
|
+
removeLabel,
|
|
2347
|
+
children,
|
|
2348
|
+
className,
|
|
2349
|
+
...props
|
|
2350
|
+
}) {
|
|
2351
|
+
const view = useKeepStaleNotice({ item, onRetry, onRemoved });
|
|
2352
|
+
return /* @__PURE__ */ jsxs4("aside", { ...props, className, "data-keepkit": "stale-notice", "data-state": view.error ? "error" : "stale", children: [
|
|
2353
|
+
/* @__PURE__ */ jsx8(KeepItemStatusBadge, { status: view.status }),
|
|
1792
2354
|
children ?? (item.statusReason ? /* @__PURE__ */ jsx8("p", { children: item.statusReason }) : null),
|
|
1793
2355
|
/* @__PURE__ */ jsxs4("div", { children: [
|
|
1794
|
-
/* @__PURE__ */ jsx8(
|
|
1795
|
-
|
|
2356
|
+
/* @__PURE__ */ jsx8(
|
|
2357
|
+
"button",
|
|
2358
|
+
{
|
|
2359
|
+
type: "button",
|
|
2360
|
+
"data-keep-action": "retry-item",
|
|
2361
|
+
onClick: () => void view.retry(),
|
|
2362
|
+
disabled: view.isRetrying || view.isMutating,
|
|
2363
|
+
children: retryLabel ?? view.labels.retry
|
|
2364
|
+
}
|
|
2365
|
+
),
|
|
2366
|
+
/* @__PURE__ */ jsx8(
|
|
2367
|
+
"button",
|
|
2368
|
+
{
|
|
2369
|
+
type: "button",
|
|
2370
|
+
"data-keep-action": "remove-item",
|
|
2371
|
+
onClick: () => void view.remove(),
|
|
2372
|
+
disabled: view.isMutating,
|
|
2373
|
+
children: removeLabel ?? view.labels.remove
|
|
2374
|
+
}
|
|
2375
|
+
)
|
|
1796
2376
|
] }),
|
|
1797
|
-
error ? /* @__PURE__ */ jsx8("p", { role: "alert", children: error instanceof Error ? error.message :
|
|
2377
|
+
view.error ? /* @__PURE__ */ jsx8("p", { role: "alert", children: view.error instanceof Error ? view.error.message : view.labels.error }) : null
|
|
1798
2378
|
] });
|
|
1799
2379
|
}
|
|
1800
2380
|
function KeepPruneStaleButton({
|
|
@@ -1805,14 +2385,7 @@ function KeepPruneStaleButton({
|
|
|
1805
2385
|
disabled,
|
|
1806
2386
|
...props
|
|
1807
2387
|
}) {
|
|
1808
|
-
const
|
|
1809
|
-
const defaultLabel = useUiLabel("pruneStale");
|
|
1810
|
-
const staleIds = context.items.filter((item) => item.status && statuses.includes(item.status)).map((item) => item.id);
|
|
1811
|
-
async function handlePrune() {
|
|
1812
|
-
const ids = [...staleIds];
|
|
1813
|
-
await context.removeItemsWithUndo(ids);
|
|
1814
|
-
onPruned?.(ids);
|
|
1815
|
-
}
|
|
2388
|
+
const view = useKeepPruneStale({ statuses, onPruned });
|
|
1816
2389
|
return /* @__PURE__ */ jsx8(
|
|
1817
2390
|
"button",
|
|
1818
2391
|
{
|
|
@@ -1820,17 +2393,24 @@ function KeepPruneStaleButton({
|
|
|
1820
2393
|
type: "button",
|
|
1821
2394
|
className: props.className,
|
|
1822
2395
|
"data-keepkit": "prune-stale",
|
|
1823
|
-
"data-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2396
|
+
"data-keep-action": "prune-stale",
|
|
2397
|
+
"data-state": view.staleIds.length > 0 ? "available" : "empty",
|
|
2398
|
+
disabled: view.staleIds.length === 0 || view.isMutating || disabled,
|
|
2399
|
+
onClick: () => void view.prune(),
|
|
2400
|
+
children: children ?? label ?? `${view.label} (${view.staleIds.length})`
|
|
1827
2401
|
}
|
|
1828
2402
|
);
|
|
1829
2403
|
}
|
|
1830
2404
|
|
|
1831
2405
|
// src/KeepItemCard.tsx
|
|
1832
2406
|
import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1833
|
-
|
|
2407
|
+
var KeepItemCardContext = createContext2(null);
|
|
2408
|
+
function useKeepItemCardCompound(part) {
|
|
2409
|
+
const context = useContext2(KeepItemCardContext);
|
|
2410
|
+
if (!context) throw new Error(`KeepItemCard.${part} must be rendered inside KeepItemCard.`);
|
|
2411
|
+
return context;
|
|
2412
|
+
}
|
|
2413
|
+
function KeepItemCardRoot({
|
|
1834
2414
|
item,
|
|
1835
2415
|
title,
|
|
1836
2416
|
getTitle,
|
|
@@ -1859,116 +2439,151 @@ function KeepItemCard({
|
|
|
1859
2439
|
className,
|
|
1860
2440
|
...rootProps
|
|
1861
2441
|
}) {
|
|
1862
|
-
const
|
|
1863
|
-
const savedAtLabel = useUiLabel("saved");
|
|
1864
|
-
const errorLabel = useUiLabel("error");
|
|
1865
|
-
const removeActionLabel = useUiLabel("remove");
|
|
1866
|
-
const itemState = useKeepItem2(item);
|
|
1867
|
-
const contentChildren = asChild && isValidElement2(children) ? void 0 : children;
|
|
1868
|
-
const state = {
|
|
2442
|
+
const view = useKeepItemCard({
|
|
1869
2443
|
item,
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
const
|
|
1880
|
-
const displayStatus = getDisplayStatus2(item.status);
|
|
1881
|
-
const isExternalLink = href ? isExternalHref(href) : false;
|
|
1882
|
-
const resolvedLinkTarget = linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0);
|
|
1883
|
-
const resolvedLinkRel = linkRel ?? (isExternalLink ? "noreferrer" : void 0);
|
|
1884
|
-
const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey2(item.status) : "statusUnknown";
|
|
1885
|
-
const unavailableLabel = useUiLabel(statusLabelKey);
|
|
1886
|
-
const tagsLabel = useUiLabel("tags");
|
|
1887
|
-
const statusLabel = item.status && item.status !== "available" ? unavailableLabel : void 0;
|
|
2444
|
+
title,
|
|
2445
|
+
getTitle,
|
|
2446
|
+
getImageProps,
|
|
2447
|
+
href: hrefOption,
|
|
2448
|
+
linkTargetAttribute,
|
|
2449
|
+
linkRel,
|
|
2450
|
+
onRemoveError,
|
|
2451
|
+
onRemoved
|
|
2452
|
+
});
|
|
2453
|
+
const contentChildren = asChild && isValidElement2(children) ? void 0 : children;
|
|
1888
2454
|
function renderLink(content) {
|
|
1889
|
-
if (!href || !isAvailable) return content;
|
|
2455
|
+
if (!view.href || !view.isAvailable) return content;
|
|
1890
2456
|
const linkProps = {
|
|
1891
|
-
href,
|
|
1892
|
-
target: resolvedLinkTarget,
|
|
1893
|
-
rel: resolvedLinkRel,
|
|
2457
|
+
href: view.href,
|
|
2458
|
+
target: view.resolvedLinkTarget,
|
|
2459
|
+
rel: view.resolvedLinkRel,
|
|
1894
2460
|
onClick: (event) => onOpen?.(item, event),
|
|
1895
2461
|
children: content
|
|
1896
2462
|
};
|
|
1897
2463
|
return LinkComponent ? /* @__PURE__ */ jsx9(LinkComponent, { ...linkProps }) : /* @__PURE__ */ jsx9("a", { ...linkProps });
|
|
1898
2464
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
} catch (error) {
|
|
1904
|
-
onRemoveError?.(error);
|
|
1905
|
-
}
|
|
2465
|
+
function renderTitle(content) {
|
|
2466
|
+
if (linkTarget !== "title") return content;
|
|
2467
|
+
if (view.isAvailable) return renderLink(content);
|
|
2468
|
+
return view.href ? /* @__PURE__ */ jsx9("span", { "aria-disabled": "true", "data-link-disabled": "true", children: content }) : content;
|
|
1906
2469
|
}
|
|
1907
|
-
const
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
showSaveButton
|
|
2470
|
+
const resolvedImageProps = view.imageProps ? { ...view.imageProps, alt: imageAlt ?? view.imageProps.alt } : void 0;
|
|
2471
|
+
const image = resolvedImageProps ? renderImage?.(resolvedImageProps, item) ?? (ImageComponent ? /* @__PURE__ */ jsx9(ImageComponent, { ...resolvedImageProps }) : /* @__PURE__ */ jsx9("img", { ...resolvedImageProps, alt: resolvedImageProps.alt })) : null;
|
|
2472
|
+
const tags = showTags ? item.tags ?? [] : [];
|
|
2473
|
+
const renderedTags = showTags && tags.length > 0 ? renderTags?.(tags, item) ?? null : null;
|
|
2474
|
+
const meta = showSavedAt ? /* @__PURE__ */ jsxs5("div", { "data-card-meta": true, children: [
|
|
2475
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
2476
|
+
view.labels.savedAt,
|
|
2477
|
+
":"
|
|
2478
|
+
] }),
|
|
2479
|
+
" ",
|
|
2480
|
+
/* @__PURE__ */ jsx9("time", { dateTime: new Date(item.savedAt).toISOString(), children: formatSavedAt(item.savedAt) })
|
|
2481
|
+
] }) : null;
|
|
2482
|
+
const error = view.itemState.error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: getErrorMessage2(view.itemState.error, view.labels.error) }) : null;
|
|
2483
|
+
const actions = view.statusLabel ? /* @__PURE__ */ jsx9(KeepStaleNotice, { item, onRetry, onRemoved }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2484
|
+
showSaveButton ? /* @__PURE__ */ jsx9(
|
|
1922
2485
|
KeepButton,
|
|
1923
2486
|
{
|
|
1924
2487
|
item: toKeepButtonItem(item),
|
|
1925
2488
|
labels: saveButtonLabels,
|
|
1926
|
-
getAriaLabel: (buttonState) => `${buttonState.isSaved ?
|
|
2489
|
+
getAriaLabel: (buttonState) => `${buttonState.isSaved ? view.labels.remove : view.labels.save} ${String(view.resolvedTitle)}`
|
|
1927
2490
|
}
|
|
1928
2491
|
) : null,
|
|
1929
|
-
|
|
2492
|
+
/* @__PURE__ */ jsx9(
|
|
2493
|
+
"button",
|
|
2494
|
+
{
|
|
2495
|
+
type: "button",
|
|
2496
|
+
"data-keep-action": "remove-item",
|
|
2497
|
+
onClick: () => void view.remove(),
|
|
2498
|
+
disabled: view.itemState.isMutating,
|
|
2499
|
+
children: removeLabel ?? view.labels.remove
|
|
2500
|
+
}
|
|
2501
|
+
)
|
|
1930
2502
|
] });
|
|
1931
|
-
const
|
|
1932
|
-
|
|
2503
|
+
const compoundValue = {
|
|
2504
|
+
resolvedTitle: view.resolvedTitle,
|
|
2505
|
+
renderTitle,
|
|
2506
|
+
image,
|
|
2507
|
+
fallbackLabel: String(view.resolvedTitle),
|
|
2508
|
+
tags,
|
|
2509
|
+
tagsLabel: view.labels.tags,
|
|
2510
|
+
renderedTags,
|
|
2511
|
+
meta,
|
|
2512
|
+
error,
|
|
2513
|
+
actions
|
|
2514
|
+
};
|
|
2515
|
+
const defaultBody = /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2516
|
+
/* @__PURE__ */ jsx9(KeepItemCardMedia, {}),
|
|
2517
|
+
/* @__PURE__ */ jsx9(KeepItemCardContent, {}),
|
|
2518
|
+
/* @__PURE__ */ jsx9(KeepItemCardActions, {})
|
|
2519
|
+
] });
|
|
2520
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? defaultBody;
|
|
2521
|
+
const linkedBody = linkTarget === "card" && view.isAvailable ? renderLink(body) : body;
|
|
2522
|
+
const root = renderRoot(
|
|
1933
2523
|
asChild,
|
|
1934
2524
|
isValidElement2(children) ? children : void 0,
|
|
1935
2525
|
{
|
|
1936
2526
|
...rootProps,
|
|
1937
2527
|
className,
|
|
1938
2528
|
"data-keepkit": "card",
|
|
1939
|
-
"aria-busy": itemState.isMutating || rootProps["aria-busy"],
|
|
1940
|
-
"aria-disabled": rootProps["aria-disabled"] ?? (!isAvailable ? "true" : void 0),
|
|
1941
|
-
"data-state": itemState.error ? "error" : itemState.isSaved ? "saved" : "unsaved",
|
|
2529
|
+
"aria-busy": view.itemState.isMutating || rootProps["aria-busy"],
|
|
2530
|
+
"aria-disabled": rootProps["aria-disabled"] ?? (!view.isAvailable ? "true" : void 0),
|
|
2531
|
+
"data-state": view.itemState.error ? "error" : view.itemState.isSaved ? "saved" : "unsaved",
|
|
1942
2532
|
"data-status": item.status ?? "available",
|
|
1943
|
-
"data-item-status": displayStatus,
|
|
1944
|
-
"data-loading": itemState.isMutating ? "true" : void 0
|
|
2533
|
+
"data-item-status": view.displayStatus,
|
|
2534
|
+
"data-loading": view.itemState.isMutating ? "true" : void 0
|
|
1945
2535
|
},
|
|
1946
2536
|
linkedBody,
|
|
1947
2537
|
"KeepItemCard"
|
|
1948
2538
|
);
|
|
2539
|
+
return /* @__PURE__ */ jsx9(KeepItemCardContext.Provider, { value: compoundValue, children: root });
|
|
2540
|
+
}
|
|
2541
|
+
function KeepItemCardMedia({ children, fallback, ...props }) {
|
|
2542
|
+
const context = useKeepItemCardCompound("Media");
|
|
2543
|
+
return /* @__PURE__ */ jsx9("div", { ...props, "data-keep-card-part": "media", children: children ?? context.image ?? /* @__PURE__ */ jsx9("span", { role: "img", "aria-label": context.fallbackLabel, "data-keep-card-fallback": "true", children: fallback ?? context.fallbackLabel.slice(0, 1) }) });
|
|
2544
|
+
}
|
|
2545
|
+
function KeepItemCardContent({ children, ...props }) {
|
|
2546
|
+
const context = useKeepItemCardCompound("Content");
|
|
2547
|
+
return /* @__PURE__ */ jsx9("div", { ...props, "data-keep-card-part": "content", children: children ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2548
|
+
/* @__PURE__ */ jsx9(KeepItemCardTitle, {}),
|
|
2549
|
+
context.meta,
|
|
2550
|
+
/* @__PURE__ */ jsx9(KeepItemCardTags, {}),
|
|
2551
|
+
context.error
|
|
2552
|
+
] }) });
|
|
2553
|
+
}
|
|
2554
|
+
function KeepItemCardTitle({ as = "h3", children, ...props }) {
|
|
2555
|
+
const context = useKeepItemCardCompound("Title");
|
|
2556
|
+
return createElement2(
|
|
2557
|
+
as,
|
|
2558
|
+
{ ...props, "data-keep-card-part": "title" },
|
|
2559
|
+
context.renderTitle(children ?? context.resolvedTitle)
|
|
2560
|
+
);
|
|
1949
2561
|
}
|
|
1950
|
-
function
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
return "
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
|
|
2562
|
+
function KeepItemCardTags({ children, ...props }) {
|
|
2563
|
+
const context = useKeepItemCardCompound("Tags");
|
|
2564
|
+
if (children === void 0 && context.renderedTags) return context.renderedTags;
|
|
2565
|
+
if (children === void 0 && context.tags.length === 0) return null;
|
|
2566
|
+
return /* @__PURE__ */ jsx9("ul", { ...props, "aria-label": props["aria-label"] ?? context.tagsLabel, "data-keep-card-part": "tags", children: children ?? context.tags.map((tag) => /* @__PURE__ */ jsx9("li", { children: tag }, tag)) });
|
|
2567
|
+
}
|
|
2568
|
+
function KeepItemCardActions({ children, ...props }) {
|
|
2569
|
+
const context = useKeepItemCardCompound("Actions");
|
|
2570
|
+
return /* @__PURE__ */ jsx9("div", { ...props, "data-keep-card-part": "actions", children: children ?? context.actions });
|
|
2571
|
+
}
|
|
2572
|
+
var KeepItemCard = Object.assign(KeepItemCardRoot, {
|
|
2573
|
+
Media: KeepItemCardMedia,
|
|
2574
|
+
Content: KeepItemCardContent,
|
|
2575
|
+
Title: KeepItemCardTitle,
|
|
2576
|
+
Tags: KeepItemCardTags,
|
|
2577
|
+
Actions: KeepItemCardActions
|
|
2578
|
+
});
|
|
2579
|
+
function KeepItemCardSkeleton({ layout = "list", ...props }) {
|
|
2580
|
+
return /* @__PURE__ */ jsxs5("article", { ...props, "aria-hidden": "true", "data-keepkit": "card-skeleton", "data-layout": layout, "data-state": "loading", children: [
|
|
2581
|
+
/* @__PURE__ */ jsx9("span", { "data-skeleton-part": "media" }),
|
|
2582
|
+
/* @__PURE__ */ jsx9("span", { "data-skeleton-part": "title" }),
|
|
2583
|
+
/* @__PURE__ */ jsx9("span", { "data-skeleton-part": "meta" }),
|
|
2584
|
+
/* @__PURE__ */ jsx9("span", { "data-skeleton-part": "tag" }),
|
|
2585
|
+
/* @__PURE__ */ jsx9("span", { "data-skeleton-part": "tag" })
|
|
2586
|
+
] });
|
|
1972
2587
|
}
|
|
1973
2588
|
function formatSavedAt(timestamp) {
|
|
1974
2589
|
return new Date(timestamp).toISOString().slice(0, 10);
|
|
@@ -1978,7 +2593,7 @@ function getErrorMessage2(error, fallback) {
|
|
|
1978
2593
|
}
|
|
1979
2594
|
|
|
1980
2595
|
// src/KeepList.tsx
|
|
1981
|
-
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2596
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1982
2597
|
function KeepList(props) {
|
|
1983
2598
|
const { fallback, onBoundaryError, boundaryResetKey, ...listProps } = props;
|
|
1984
2599
|
const content = /* @__PURE__ */ jsx10(KeepListContent, { ...listProps });
|
|
@@ -1990,6 +2605,8 @@ function KeepListContent({
|
|
|
1990
2605
|
children,
|
|
1991
2606
|
renderItem,
|
|
1992
2607
|
loading,
|
|
2608
|
+
renderLoading,
|
|
2609
|
+
loadingCount = 6,
|
|
1993
2610
|
empty,
|
|
1994
2611
|
error: errorContent,
|
|
1995
2612
|
itemCardProps,
|
|
@@ -1998,16 +2615,16 @@ function KeepListContent({
|
|
|
1998
2615
|
className,
|
|
1999
2616
|
...rootProps
|
|
2000
2617
|
}) {
|
|
2001
|
-
const
|
|
2002
|
-
const
|
|
2003
|
-
const defaultError = useUiLabel("errorItems");
|
|
2004
|
-
const state = useKeepList2(query);
|
|
2618
|
+
const view = useKeepListView(query);
|
|
2619
|
+
const { state } = view;
|
|
2005
2620
|
const body = getListBody(state, {
|
|
2006
2621
|
children,
|
|
2007
2622
|
renderItem,
|
|
2008
|
-
loading:
|
|
2009
|
-
|
|
2010
|
-
|
|
2623
|
+
loading: renderLoading !== void 0 ? renderLoading : loading,
|
|
2624
|
+
loadingCount,
|
|
2625
|
+
loadingLabel: view.labels.loading,
|
|
2626
|
+
empty: empty ?? view.labels.empty,
|
|
2627
|
+
error: errorContent ?? view.labels.error,
|
|
2011
2628
|
itemCardProps,
|
|
2012
2629
|
layout
|
|
2013
2630
|
});
|
|
@@ -2035,7 +2652,17 @@ function getListState(state) {
|
|
|
2035
2652
|
}
|
|
2036
2653
|
function getListBody(state, options) {
|
|
2037
2654
|
if (state.error && state.items.length === 0) return resolveContent(options.error, state);
|
|
2038
|
-
if (state.isLoading && !state.isHydrated)
|
|
2655
|
+
if (state.isLoading && !state.isHydrated) {
|
|
2656
|
+
if (options.loading !== void 0) return resolveContent(options.loading, state);
|
|
2657
|
+
const count = Number.isFinite(options.loadingCount) ? Math.max(0, Math.floor(options.loadingCount)) : 6;
|
|
2658
|
+
return /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
2659
|
+
/* @__PURE__ */ jsx10("span", { role: "status", "data-keepkit": "loading-label", children: options.loadingLabel }),
|
|
2660
|
+
/* @__PURE__ */ jsx10("ul", { "data-keepkit": "skeleton-list", "data-layout": options.layout, children: Array.from({ length: count }, (_, index) => (
|
|
2661
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: Static loading placeholders never reorder.
|
|
2662
|
+
/* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(KeepItemCardSkeleton, { layout: options.layout }) }, index)
|
|
2663
|
+
)) })
|
|
2664
|
+
] });
|
|
2665
|
+
}
|
|
2039
2666
|
if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);
|
|
2040
2667
|
if (typeof options.children === "function") return options.children(state);
|
|
2041
2668
|
if (options.children !== void 0 && !isValidElement3(options.children)) return options.children;
|
|
@@ -2045,9 +2672,40 @@ function getListBody(state, options) {
|
|
|
2045
2672
|
}
|
|
2046
2673
|
|
|
2047
2674
|
// src/KeepTagFilter.tsx
|
|
2048
|
-
import {
|
|
2049
|
-
|
|
2050
|
-
|
|
2675
|
+
import { isValidElement as isValidElement4 } from "react";
|
|
2676
|
+
|
|
2677
|
+
// src/hooks/useKeepTagFilter.ts
|
|
2678
|
+
import { useKeepList as useKeepList4 } from "@keepkit/core/react";
|
|
2679
|
+
import { useCallback as useCallback3, useMemo as useMemo3, useState as useState5 } from "react";
|
|
2680
|
+
function useKeepTagFilter(options) {
|
|
2681
|
+
const { query, controlledValue, defaultValue, onChange, onValueChange } = options;
|
|
2682
|
+
const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
|
|
2683
|
+
const resolvedValue = controlledValue ?? uncontrolledValue;
|
|
2684
|
+
const list = useKeepList4({
|
|
2685
|
+
...query,
|
|
2686
|
+
tags: resolvedValue ? [...query?.tags ?? [], resolvedValue] : query?.tags
|
|
2687
|
+
});
|
|
2688
|
+
const select = useCallback3(
|
|
2689
|
+
(tag) => {
|
|
2690
|
+
if (controlledValue === void 0) setUncontrolledValue(tag);
|
|
2691
|
+
onChange?.(tag);
|
|
2692
|
+
onValueChange?.(tag);
|
|
2693
|
+
},
|
|
2694
|
+
[controlledValue, onChange, onValueChange]
|
|
2695
|
+
);
|
|
2696
|
+
const state = useMemo3(
|
|
2697
|
+
() => ({ tags: list.tags, tagCounts: list.tagCounts, value: resolvedValue, select }),
|
|
2698
|
+
[list.tagCounts, list.tags, resolvedValue, select]
|
|
2699
|
+
);
|
|
2700
|
+
return {
|
|
2701
|
+
state,
|
|
2702
|
+
isLoading: list.isLoading,
|
|
2703
|
+
labels: { all: useUiLabel("allTags"), aria: useUiLabel("filterTags") }
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// src/KeepTagFilter.tsx
|
|
2708
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2051
2709
|
function KeepTagFilter({
|
|
2052
2710
|
query,
|
|
2053
2711
|
value: controlledValue,
|
|
@@ -2063,38 +2721,38 @@ function KeepTagFilter({
|
|
|
2063
2721
|
className,
|
|
2064
2722
|
...rootProps
|
|
2065
2723
|
}) {
|
|
2066
|
-
const
|
|
2067
|
-
const uiAriaLabel = useUiLabel("filterTags");
|
|
2068
|
-
const [uncontrolledValue, setUncontrolledValue] = useState4(defaultValue);
|
|
2069
|
-
const resolvedValue = controlledValue ?? uncontrolledValue;
|
|
2070
|
-
const list = useKeepList3({
|
|
2071
|
-
...query,
|
|
2072
|
-
tags: resolvedValue ? [...query?.tags ?? [], resolvedValue] : query?.tags
|
|
2073
|
-
});
|
|
2074
|
-
const select = useCallback(
|
|
2075
|
-
(tag) => {
|
|
2076
|
-
if (controlledValue === void 0) setUncontrolledValue(tag);
|
|
2077
|
-
onChange?.(tag);
|
|
2078
|
-
onValueChange?.(tag);
|
|
2079
|
-
},
|
|
2080
|
-
[controlledValue, onChange, onValueChange]
|
|
2081
|
-
);
|
|
2082
|
-
const state = useMemo2(
|
|
2083
|
-
() => ({ tags: list.tags, tagCounts: list.tagCounts, value: resolvedValue, select }),
|
|
2084
|
-
[list.tagCounts, list.tags, resolvedValue, select]
|
|
2085
|
-
);
|
|
2724
|
+
const view = useKeepTagFilter({ query, controlledValue, defaultValue, onChange, onValueChange });
|
|
2086
2725
|
const contentChildren = asChild && isValidElement4(children) ? void 0 : children;
|
|
2087
|
-
const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */
|
|
2088
|
-
/* @__PURE__ */ jsx11("legend", { children: ariaLabel ??
|
|
2089
|
-
/* @__PURE__ */ jsx11(
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
"
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2726
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs7("fieldset", { children: [
|
|
2727
|
+
/* @__PURE__ */ jsx11("legend", { children: ariaLabel ?? view.labels.aria }),
|
|
2728
|
+
/* @__PURE__ */ jsx11(
|
|
2729
|
+
"button",
|
|
2730
|
+
{
|
|
2731
|
+
type: "button",
|
|
2732
|
+
"data-keep-action": "filter-all-tags",
|
|
2733
|
+
"aria-pressed": view.state.value === void 0,
|
|
2734
|
+
onClick: () => view.state.select(),
|
|
2735
|
+
children: allLabel ?? view.labels.all
|
|
2736
|
+
}
|
|
2737
|
+
),
|
|
2738
|
+
view.state.tags.map((tag) => /* @__PURE__ */ jsxs7(
|
|
2739
|
+
"button",
|
|
2740
|
+
{
|
|
2741
|
+
type: "button",
|
|
2742
|
+
"data-keep-action": "filter-tag",
|
|
2743
|
+
"aria-pressed": view.state.value === tag,
|
|
2744
|
+
onClick: () => view.state.select(tag),
|
|
2745
|
+
children: [
|
|
2746
|
+
renderTag ? renderTag(tag, view.state.tagCounts[tag] ?? 0, view.state.value === tag) : tag,
|
|
2747
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
2748
|
+
" (",
|
|
2749
|
+
view.state.tagCounts[tag] ?? 0,
|
|
2750
|
+
")"
|
|
2751
|
+
] })
|
|
2752
|
+
]
|
|
2753
|
+
},
|
|
2754
|
+
tag
|
|
2755
|
+
))
|
|
2098
2756
|
] });
|
|
2099
2757
|
return renderRoot(
|
|
2100
2758
|
asChild,
|
|
@@ -2103,17 +2761,88 @@ function KeepTagFilter({
|
|
|
2103
2761
|
...rootProps,
|
|
2104
2762
|
className,
|
|
2105
2763
|
"data-keepkit": "tag-filter",
|
|
2106
|
-
"data-state":
|
|
2107
|
-
"data-loading":
|
|
2764
|
+
"data-state": view.state.value === void 0 ? "all" : "filtered",
|
|
2765
|
+
"data-loading": view.isLoading ? "true" : void 0
|
|
2108
2766
|
},
|
|
2109
2767
|
body,
|
|
2110
2768
|
"KeepTagFilter"
|
|
2111
2769
|
);
|
|
2112
2770
|
}
|
|
2113
2771
|
|
|
2772
|
+
// src/hooks/useQueryControls.ts
|
|
2773
|
+
import { useEffect as useEffect3, useState as useState6 } from "react";
|
|
2774
|
+
function useKeepSearchInput(options) {
|
|
2775
|
+
const { controlledValue, defaultValue, debounceMs, onValueChange } = options;
|
|
2776
|
+
const [uncontrolledValue, setUncontrolledValue] = useState6(defaultValue);
|
|
2777
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
2778
|
+
useEffect3(() => {
|
|
2779
|
+
if (!onValueChange) return;
|
|
2780
|
+
if (debounceMs <= 0) {
|
|
2781
|
+
onValueChange(value);
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
const timer = window.setTimeout(() => onValueChange(value), debounceMs);
|
|
2785
|
+
return () => window.clearTimeout(timer);
|
|
2786
|
+
}, [debounceMs, onValueChange, value]);
|
|
2787
|
+
return {
|
|
2788
|
+
value,
|
|
2789
|
+
label: useUiLabel("search"),
|
|
2790
|
+
change: (event) => {
|
|
2791
|
+
if (controlledValue === void 0) setUncontrolledValue(event.currentTarget.value);
|
|
2792
|
+
}
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
function useKeepSortSelect(options) {
|
|
2796
|
+
const { controlledValue, defaultValue, onValueChange } = options;
|
|
2797
|
+
const [uncontrolledValue, setUncontrolledValue] = useState6(defaultValue);
|
|
2798
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
2799
|
+
return {
|
|
2800
|
+
value,
|
|
2801
|
+
change: (event) => {
|
|
2802
|
+
const nextValue = event.currentTarget.value;
|
|
2803
|
+
if (controlledValue === void 0) setUncontrolledValue(nextValue);
|
|
2804
|
+
const [by, direction] = nextValue.split(":");
|
|
2805
|
+
onValueChange?.(nextValue, { by, direction });
|
|
2806
|
+
},
|
|
2807
|
+
labels: {
|
|
2808
|
+
sort: useUiLabel("sort"),
|
|
2809
|
+
updatedNewest: useUiLabel("updatedNewest"),
|
|
2810
|
+
updatedOldest: useUiLabel("updatedOldest"),
|
|
2811
|
+
savedNewest: useUiLabel("savedNewest"),
|
|
2812
|
+
savedOldest: useUiLabel("savedOldest")
|
|
2813
|
+
}
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function useKeepPagination(options) {
|
|
2817
|
+
const { totalCount, pageSize, page, maxPageButtons, onPageChange } = options;
|
|
2818
|
+
const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
|
|
2819
|
+
const currentPage = Math.min(Math.max(1, page), pageCount);
|
|
2820
|
+
const goToPage = (nextPage) => {
|
|
2821
|
+
const next = Math.min(Math.max(1, nextPage), pageCount);
|
|
2822
|
+
onPageChange?.(next, (next - 1) * pageSize);
|
|
2823
|
+
};
|
|
2824
|
+
return {
|
|
2825
|
+
pageCount,
|
|
2826
|
+
currentPage,
|
|
2827
|
+
goToPage,
|
|
2828
|
+
visiblePages: getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons)),
|
|
2829
|
+
labels: {
|
|
2830
|
+
previous: useUiLabel("previousPage"),
|
|
2831
|
+
next: useUiLabel("nextPage"),
|
|
2832
|
+
page: useUiLabel("page"),
|
|
2833
|
+
pagination: useUiLabel("pagination")
|
|
2834
|
+
}
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
function getVisiblePages(currentPage, pageCount, maxPageButtons) {
|
|
2838
|
+
if (pageCount <= maxPageButtons) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
2839
|
+
const half = Math.floor(maxPageButtons / 2);
|
|
2840
|
+
const start = Math.min(Math.max(1, currentPage - half), pageCount - maxPageButtons + 1);
|
|
2841
|
+
return Array.from({ length: maxPageButtons }, (_, index) => start + index);
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2114
2844
|
// src/query-controls.tsx
|
|
2115
|
-
import {
|
|
2116
|
-
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2845
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2117
2846
|
function KeepSearchInput({
|
|
2118
2847
|
value: controlledValue,
|
|
2119
2848
|
defaultValue = "",
|
|
@@ -2123,33 +2852,20 @@ function KeepSearchInput({
|
|
|
2123
2852
|
placeholder,
|
|
2124
2853
|
...props
|
|
2125
2854
|
}) {
|
|
2126
|
-
const
|
|
2127
|
-
const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
|
|
2128
|
-
const value = controlledValue ?? uncontrolledValue;
|
|
2129
|
-
useEffect(() => {
|
|
2130
|
-
if (!onValueChange) return;
|
|
2131
|
-
if (debounceMs <= 0) {
|
|
2132
|
-
onValueChange(value);
|
|
2133
|
-
return;
|
|
2134
|
-
}
|
|
2135
|
-
const timer = window.setTimeout(() => onValueChange(value), debounceMs);
|
|
2136
|
-
return () => window.clearTimeout(timer);
|
|
2137
|
-
}, [debounceMs, onValueChange, value]);
|
|
2855
|
+
const view = useKeepSearchInput({ controlledValue, defaultValue, debounceMs, onValueChange });
|
|
2138
2856
|
return /* @__PURE__ */ jsx12(
|
|
2139
2857
|
"input",
|
|
2140
2858
|
{
|
|
2141
2859
|
...props,
|
|
2142
2860
|
"data-keepkit": "search-input",
|
|
2861
|
+
"data-keep-action": "search",
|
|
2143
2862
|
type: "search",
|
|
2144
|
-
value,
|
|
2145
|
-
"data-state": value ? "active" : "idle",
|
|
2863
|
+
value: view.value,
|
|
2864
|
+
"data-state": view.value ? "active" : "idle",
|
|
2146
2865
|
"data-disabled": props.disabled ? "true" : void 0,
|
|
2147
|
-
"aria-label": ariaLabel ?? label,
|
|
2148
|
-
placeholder: placeholder ?? label,
|
|
2149
|
-
onChange:
|
|
2150
|
-
const nextValue = event.currentTarget.value;
|
|
2151
|
-
if (controlledValue === void 0) setUncontrolledValue(nextValue);
|
|
2152
|
-
}
|
|
2866
|
+
"aria-label": ariaLabel ?? view.label,
|
|
2867
|
+
placeholder: placeholder ?? view.label,
|
|
2868
|
+
onChange: view.change
|
|
2153
2869
|
}
|
|
2154
2870
|
);
|
|
2155
2871
|
}
|
|
@@ -2161,34 +2877,24 @@ function KeepSortSelect({
|
|
|
2161
2877
|
children,
|
|
2162
2878
|
...props
|
|
2163
2879
|
}) {
|
|
2164
|
-
const
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
const value = controlledValue ?? uncontrolledValue;
|
|
2171
|
-
const options = children ?? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2172
|
-
/* @__PURE__ */ jsx12("option", { value: "updatedAt:desc", children: updatedNewestLabel }),
|
|
2173
|
-
/* @__PURE__ */ jsx12("option", { value: "updatedAt:asc", children: updatedOldestLabel }),
|
|
2174
|
-
/* @__PURE__ */ jsx12("option", { value: "savedAt:desc", children: savedNewestLabel }),
|
|
2175
|
-
/* @__PURE__ */ jsx12("option", { value: "savedAt:asc", children: savedOldestLabel })
|
|
2880
|
+
const view = useKeepSortSelect({ controlledValue, defaultValue, onValueChange });
|
|
2881
|
+
const options = children ?? /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
2882
|
+
/* @__PURE__ */ jsx12("option", { value: "updatedAt:desc", children: view.labels.updatedNewest }),
|
|
2883
|
+
/* @__PURE__ */ jsx12("option", { value: "updatedAt:asc", children: view.labels.updatedOldest }),
|
|
2884
|
+
/* @__PURE__ */ jsx12("option", { value: "savedAt:desc", children: view.labels.savedNewest }),
|
|
2885
|
+
/* @__PURE__ */ jsx12("option", { value: "savedAt:asc", children: view.labels.savedOldest })
|
|
2176
2886
|
] });
|
|
2177
2887
|
return /* @__PURE__ */ jsx12(
|
|
2178
2888
|
"select",
|
|
2179
2889
|
{
|
|
2180
2890
|
...props,
|
|
2181
2891
|
"data-keepkit": "sort-select",
|
|
2182
|
-
|
|
2892
|
+
"data-keep-action": "sort",
|
|
2893
|
+
value: view.value,
|
|
2183
2894
|
"data-state": "selected",
|
|
2184
2895
|
"data-disabled": props.disabled ? "true" : void 0,
|
|
2185
|
-
"aria-label": ariaLabel ??
|
|
2186
|
-
onChange:
|
|
2187
|
-
const nextValue = event.currentTarget.value;
|
|
2188
|
-
if (controlledValue === void 0) setUncontrolledValue(nextValue);
|
|
2189
|
-
const [by, direction] = nextValue.split(":");
|
|
2190
|
-
onValueChange?.(nextValue, { by, direction });
|
|
2191
|
-
},
|
|
2896
|
+
"aria-label": ariaLabel ?? view.labels.sort,
|
|
2897
|
+
onChange: view.change,
|
|
2192
2898
|
children: options
|
|
2193
2899
|
}
|
|
2194
2900
|
);
|
|
@@ -2202,131 +2908,53 @@ function KeepPagination({
|
|
|
2202
2908
|
render,
|
|
2203
2909
|
...props
|
|
2204
2910
|
}) {
|
|
2205
|
-
const
|
|
2206
|
-
const nextPageLabel = useUiLabel("nextPage");
|
|
2207
|
-
const pageLabel = useUiLabel("page");
|
|
2208
|
-
const paginationLabel = useUiLabel("pagination");
|
|
2209
|
-
const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
|
|
2210
|
-
const currentPage = Math.min(Math.max(1, page), pageCount);
|
|
2211
|
-
const goToPage = (nextPage) => {
|
|
2212
|
-
const next = Math.min(Math.max(1, nextPage), pageCount);
|
|
2213
|
-
onPageChange?.(next, (next - 1) * pageSize);
|
|
2214
|
-
};
|
|
2911
|
+
const view = useKeepPagination({ totalCount, pageSize, page, maxPageButtons, onPageChange });
|
|
2215
2912
|
const navProps = {
|
|
2216
2913
|
...props,
|
|
2217
2914
|
"data-keepkit": "pagination",
|
|
2218
|
-
"aria-label": props["aria-label"] ??
|
|
2219
|
-
"data-state": pageCount > 1 ? "active" : "idle"
|
|
2915
|
+
"aria-label": props["aria-label"] ?? view.labels.pagination,
|
|
2916
|
+
"data-state": view.pageCount > 1 ? "active" : "idle"
|
|
2220
2917
|
};
|
|
2221
|
-
if (render)
|
|
2222
|
-
|
|
2223
|
-
return /* @__PURE__ */
|
|
2224
|
-
/* @__PURE__ */ jsx12(
|
|
2225
|
-
|
|
2918
|
+
if (render)
|
|
2919
|
+
return /* @__PURE__ */ jsx12("nav", { ...navProps, children: render({ page: view.currentPage, pageCount: view.pageCount, goToPage: view.goToPage }) });
|
|
2920
|
+
return /* @__PURE__ */ jsxs8("nav", { ...navProps, children: [
|
|
2921
|
+
/* @__PURE__ */ jsx12(
|
|
2922
|
+
"button",
|
|
2923
|
+
{
|
|
2924
|
+
type: "button",
|
|
2925
|
+
"data-keep-action": "previous-page",
|
|
2926
|
+
onClick: () => view.goToPage(view.currentPage - 1),
|
|
2927
|
+
disabled: view.currentPage <= 1,
|
|
2928
|
+
children: view.labels.previous
|
|
2929
|
+
}
|
|
2930
|
+
),
|
|
2931
|
+
view.visiblePages.map((nextPage) => /* @__PURE__ */ jsx12(
|
|
2226
2932
|
"button",
|
|
2227
2933
|
{
|
|
2228
2934
|
type: "button",
|
|
2229
|
-
"
|
|
2230
|
-
"aria-
|
|
2231
|
-
|
|
2935
|
+
"data-keep-action": "select-page",
|
|
2936
|
+
"aria-current": nextPage === view.currentPage ? "page" : void 0,
|
|
2937
|
+
"aria-label": `${view.labels.page} ${nextPage}`,
|
|
2938
|
+
onClick: () => view.goToPage(nextPage),
|
|
2232
2939
|
children: nextPage
|
|
2233
2940
|
},
|
|
2234
2941
|
nextPage
|
|
2235
2942
|
)),
|
|
2236
|
-
/* @__PURE__ */ jsx12(
|
|
2943
|
+
/* @__PURE__ */ jsx12(
|
|
2944
|
+
"button",
|
|
2945
|
+
{
|
|
2946
|
+
type: "button",
|
|
2947
|
+
"data-keep-action": "next-page",
|
|
2948
|
+
onClick: () => view.goToPage(view.currentPage + 1),
|
|
2949
|
+
disabled: view.currentPage >= view.pageCount,
|
|
2950
|
+
children: view.labels.next
|
|
2951
|
+
}
|
|
2952
|
+
)
|
|
2237
2953
|
] });
|
|
2238
2954
|
}
|
|
2239
|
-
function getVisiblePages(currentPage, pageCount, maxPageButtons) {
|
|
2240
|
-
if (pageCount <= maxPageButtons) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
2241
|
-
const half = Math.floor(maxPageButtons / 2);
|
|
2242
|
-
const start = Math.min(Math.max(1, currentPage - half), pageCount - maxPageButtons + 1);
|
|
2243
|
-
return Array.from({ length: maxPageButtons }, (_, index) => start + index);
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
// src/url-sync.tsx
|
|
2247
|
-
import {
|
|
2248
|
-
DEFAULT_KEEP_URL_PARAMS,
|
|
2249
|
-
decodeKeepListQuery,
|
|
2250
|
-
encodeKeepListQuery
|
|
2251
|
-
} from "@keepkit/core/core";
|
|
2252
|
-
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
2253
|
-
function createNextPagesRouterAdapter(router) {
|
|
2254
|
-
const getUrl = () => router.asPath ?? (typeof window === "undefined" ? "/" : window.location.href);
|
|
2255
|
-
return {
|
|
2256
|
-
getUrl,
|
|
2257
|
-
subscribe: router.events ? (listener) => {
|
|
2258
|
-
router.events?.on("routeChangeComplete", listener);
|
|
2259
|
-
return () => router.events?.off("routeChangeComplete", listener);
|
|
2260
|
-
} : void 0,
|
|
2261
|
-
navigate: (url, mode) => {
|
|
2262
|
-
void router[mode](url, void 0, { shallow: true });
|
|
2263
|
-
}
|
|
2264
|
-
};
|
|
2265
|
-
}
|
|
2266
|
-
function useKeepUrlSync({
|
|
2267
|
-
enabled = true,
|
|
2268
|
-
query,
|
|
2269
|
-
onQueryChange,
|
|
2270
|
-
options = {},
|
|
2271
|
-
adapter: providedAdapter
|
|
2272
|
-
}) {
|
|
2273
|
-
const browserAdapterRef = useRef2(getBrowserAdapter());
|
|
2274
|
-
const adapter = providedAdapter ?? browserAdapterRef.current;
|
|
2275
|
-
const onQueryChangeRef = useRef2(onQueryChange);
|
|
2276
|
-
onQueryChangeRef.current = onQueryChange;
|
|
2277
|
-
const skipWriteRef = useRef2(true);
|
|
2278
|
-
const params = options.params;
|
|
2279
|
-
useEffect2(() => {
|
|
2280
|
-
if (!enabled) return;
|
|
2281
|
-
const read = () => {
|
|
2282
|
-
const url = adapter.getUrl();
|
|
2283
|
-
const decoded = decodeKeepListQuery(url, { params });
|
|
2284
|
-
skipWriteRef.current = true;
|
|
2285
|
-
onQueryChangeRef.current((previousQuery) => ({
|
|
2286
|
-
...previousQuery,
|
|
2287
|
-
...decoded.search ? { search: decoded.search } : { search: void 0 },
|
|
2288
|
-
...decoded.tags ? { tags: decoded.tags } : { tags: void 0 },
|
|
2289
|
-
...decoded.sort ? { sort: decoded.sort } : {},
|
|
2290
|
-
...decoded.pagination ? { pagination: { ...previousQuery.pagination, ...decoded.pagination } } : {}
|
|
2291
|
-
}));
|
|
2292
|
-
};
|
|
2293
|
-
read();
|
|
2294
|
-
return adapter.subscribe?.(read);
|
|
2295
|
-
}, [adapter, enabled, params]);
|
|
2296
|
-
useEffect2(() => {
|
|
2297
|
-
if (!enabled) return;
|
|
2298
|
-
if (skipWriteRef.current) {
|
|
2299
|
-
skipWriteRef.current = false;
|
|
2300
|
-
return;
|
|
2301
|
-
}
|
|
2302
|
-
const currentUrl = new URL(adapter.getUrl(), "http://keepkit.invalid");
|
|
2303
|
-
const urlParams = { ...DEFAULT_KEEP_URL_PARAMS, ...params };
|
|
2304
|
-
for (const key of Object.values(urlParams)) currentUrl.searchParams.delete(key);
|
|
2305
|
-
const nextParams = encodeKeepListQuery(query, { params });
|
|
2306
|
-
nextParams.forEach((value, key) => {
|
|
2307
|
-
currentUrl.searchParams.append(key, value);
|
|
2308
|
-
});
|
|
2309
|
-
const nextUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
|
|
2310
|
-
adapter.navigate(nextUrl, options.history ?? "push");
|
|
2311
|
-
}, [adapter, enabled, options.history, params, query]);
|
|
2312
|
-
}
|
|
2313
|
-
function getBrowserAdapter() {
|
|
2314
|
-
return {
|
|
2315
|
-
getUrl: () => typeof window === "undefined" ? "/" : window.location.href,
|
|
2316
|
-
subscribe: (listener) => {
|
|
2317
|
-
if (typeof window === "undefined") return () => void 0;
|
|
2318
|
-
window.addEventListener("popstate", listener);
|
|
2319
|
-
return () => window.removeEventListener("popstate", listener);
|
|
2320
|
-
},
|
|
2321
|
-
navigate: (url, mode) => {
|
|
2322
|
-
if (typeof window === "undefined") return;
|
|
2323
|
-
window.history[mode === "push" ? "pushState" : "replaceState"]({}, "", url);
|
|
2324
|
-
}
|
|
2325
|
-
};
|
|
2326
|
-
}
|
|
2327
2955
|
|
|
2328
2956
|
// src/KeepCollection.tsx
|
|
2329
|
-
import { jsx as jsx13, jsxs as
|
|
2957
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2330
2958
|
function KeepCollection(props) {
|
|
2331
2959
|
const { fallback, onBoundaryError, boundaryResetKey, ...collectionProps } = props;
|
|
2332
2960
|
const content = /* @__PURE__ */ jsx13(KeepCollectionContent, { ...collectionProps });
|
|
@@ -2343,114 +2971,54 @@ function KeepCollectionContent({
|
|
|
2343
2971
|
renderItem,
|
|
2344
2972
|
itemCardProps,
|
|
2345
2973
|
loading,
|
|
2974
|
+
renderLoading,
|
|
2975
|
+
loadingCount,
|
|
2346
2976
|
empty,
|
|
2347
2977
|
error,
|
|
2348
2978
|
className,
|
|
2349
2979
|
...rootProps
|
|
2350
2980
|
}) {
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
sort: true,
|
|
2354
|
-
pagination: true,
|
|
2355
|
-
tagFilter: false,
|
|
2356
|
-
bulkActions: false,
|
|
2357
|
-
...features
|
|
2358
|
-
};
|
|
2359
|
-
const [searchValue, setSearchValue] = useState6(query.search?.query ?? "");
|
|
2360
|
-
const [sort, setSort] = useState6(query.sort ?? { by: "updatedAt", direction: "desc" });
|
|
2361
|
-
const [tag, setTag] = useState6(query.tags?.[0]);
|
|
2362
|
-
const [page, setPage] = useState6(query.pagination?.page ?? 1);
|
|
2363
|
-
const resolvedPageSize = query.pagination?.pageSize ?? pageSize;
|
|
2364
|
-
const resolvedQuery = useMemo3(
|
|
2365
|
-
() => ({
|
|
2366
|
-
...query,
|
|
2367
|
-
search: enabled.search ? { ...query.search, query: searchValue } : query.search,
|
|
2368
|
-
sort: enabled.sort ? sort : query.sort,
|
|
2369
|
-
tags: tag ? [.../* @__PURE__ */ new Set([...query.tags ?? [], tag])] : query.tags,
|
|
2370
|
-
pagination: enabled.pagination ? { ...query.pagination, page, pageSize: resolvedPageSize } : query.pagination
|
|
2371
|
-
}),
|
|
2372
|
-
[enabled.pagination, enabled.search, enabled.sort, page, query, resolvedPageSize, searchValue, sort, tag]
|
|
2373
|
-
);
|
|
2374
|
-
useKeepUrlSync({
|
|
2375
|
-
enabled: Boolean(urlSync),
|
|
2376
|
-
query: resolvedQuery,
|
|
2377
|
-
onQueryChange: (nextOrUpdater) => {
|
|
2378
|
-
const next = typeof nextOrUpdater === "function" ? nextOrUpdater(resolvedQuery) : nextOrUpdater;
|
|
2379
|
-
setSearchValue(next.search?.query ?? "");
|
|
2380
|
-
setSort(next.sort ?? { by: "updatedAt", direction: "desc" });
|
|
2381
|
-
setTag(next.tags?.[0]);
|
|
2382
|
-
setPage(next.pagination?.page ?? 1);
|
|
2383
|
-
},
|
|
2384
|
-
options: typeof urlSync === "object" ? urlSync : {},
|
|
2385
|
-
adapter: urlAdapter
|
|
2386
|
-
});
|
|
2387
|
-
const list = useKeepList4(resolvedQuery);
|
|
2388
|
-
return /* @__PURE__ */ jsxs8(
|
|
2981
|
+
const view = useKeepCollection({ query, pageSize, urlSync, urlAdapter, features });
|
|
2982
|
+
return /* @__PURE__ */ jsxs9(
|
|
2389
2983
|
"section",
|
|
2390
2984
|
{
|
|
2391
2985
|
...rootProps,
|
|
2392
2986
|
className,
|
|
2393
2987
|
"data-keepkit": "collection",
|
|
2394
2988
|
"data-layout": layout,
|
|
2395
|
-
"aria-busy": list.isLoading || list.isMutating || rootProps["aria-busy"],
|
|
2396
|
-
"data-state": getCollectionState(list),
|
|
2397
|
-
"data-loading": list.isLoading || list.isMutating ? "true" : void 0,
|
|
2989
|
+
"aria-busy": view.list.isLoading || view.list.isMutating || rootProps["aria-busy"],
|
|
2990
|
+
"data-state": getCollectionState(view.list),
|
|
2991
|
+
"data-loading": view.list.isLoading || view.list.isMutating ? "true" : void 0,
|
|
2398
2992
|
children: [
|
|
2399
|
-
/* @__PURE__ */
|
|
2400
|
-
enabled.search ? /* @__PURE__ */ jsx13(
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
value: searchValue,
|
|
2404
|
-
onValueChange: (value) => {
|
|
2405
|
-
setSearchValue(value);
|
|
2406
|
-
setPage(1);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
) : null,
|
|
2410
|
-
enabled.sort ? /* @__PURE__ */ jsx13(
|
|
2411
|
-
KeepSortSelect,
|
|
2412
|
-
{
|
|
2413
|
-
value: sortToValue(sort),
|
|
2414
|
-
onValueChange: (_value, nextSort) => {
|
|
2415
|
-
setSort(nextSort);
|
|
2416
|
-
setPage(1);
|
|
2417
|
-
}
|
|
2418
|
-
}
|
|
2419
|
-
) : null,
|
|
2420
|
-
enabled.tagFilter ? /* @__PURE__ */ jsx13(
|
|
2421
|
-
KeepTagFilter,
|
|
2422
|
-
{
|
|
2423
|
-
query,
|
|
2424
|
-
value: tag,
|
|
2425
|
-
onValueChange: (value) => {
|
|
2426
|
-
setTag(value);
|
|
2427
|
-
setPage(1);
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
) : null
|
|
2993
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
2994
|
+
view.enabled.search ? /* @__PURE__ */ jsx13(KeepSearchInput, { value: view.searchValue, onValueChange: view.setSearchValue }) : null,
|
|
2995
|
+
view.enabled.sort ? /* @__PURE__ */ jsx13(KeepSortSelect, { value: view.sortValue, onValueChange: view.setSortValue }) : null,
|
|
2996
|
+
view.enabled.tagFilter ? /* @__PURE__ */ jsx13(KeepTagFilter, { query, value: view.tag, onValueChange: view.setTag }) : null
|
|
2431
2997
|
] }),
|
|
2432
2998
|
/* @__PURE__ */ jsx13(
|
|
2433
2999
|
KeepList,
|
|
2434
3000
|
{
|
|
2435
|
-
query: resolvedQuery,
|
|
3001
|
+
query: view.resolvedQuery,
|
|
2436
3002
|
renderItem,
|
|
2437
3003
|
itemCardProps,
|
|
2438
3004
|
layout,
|
|
2439
3005
|
loading,
|
|
3006
|
+
renderLoading,
|
|
3007
|
+
loadingCount,
|
|
2440
3008
|
empty,
|
|
2441
3009
|
error
|
|
2442
3010
|
}
|
|
2443
3011
|
),
|
|
2444
|
-
enabled.pagination ? /* @__PURE__ */ jsx13(
|
|
3012
|
+
view.enabled.pagination ? /* @__PURE__ */ jsx13(
|
|
2445
3013
|
KeepPagination,
|
|
2446
3014
|
{
|
|
2447
|
-
totalCount: list.totalCount,
|
|
2448
|
-
pageSize: resolvedPageSize,
|
|
2449
|
-
page: list.page,
|
|
2450
|
-
onPageChange:
|
|
3015
|
+
totalCount: view.list.totalCount,
|
|
3016
|
+
pageSize: view.resolvedPageSize,
|
|
3017
|
+
page: view.list.page,
|
|
3018
|
+
onPageChange: view.setPage
|
|
2451
3019
|
}
|
|
2452
3020
|
) : null,
|
|
2453
|
-
enabled.bulkActions ? /* @__PURE__ */ jsx13(KeepBulkActions, { query: resolvedQuery }) : null
|
|
3021
|
+
view.enabled.bulkActions ? /* @__PURE__ */ jsx13(KeepBulkActions, { query: view.resolvedQuery }) : null
|
|
2454
3022
|
]
|
|
2455
3023
|
}
|
|
2456
3024
|
);
|
|
@@ -2469,52 +3037,31 @@ function KeepLayout({ layout = "list", children, ...props }) {
|
|
|
2469
3037
|
}
|
|
2470
3038
|
|
|
2471
3039
|
// src/KeepNoteEditor.tsx
|
|
2472
|
-
import {
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
useState as useState7
|
|
2479
|
-
} from "react";
|
|
2480
|
-
import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2481
|
-
function KeepNoteEditor({
|
|
2482
|
-
item,
|
|
2483
|
-
label,
|
|
2484
|
-
saveLabel,
|
|
2485
|
-
placeholder,
|
|
2486
|
-
debounceMs = 300,
|
|
2487
|
-
onSaved,
|
|
2488
|
-
onSaveError,
|
|
2489
|
-
render,
|
|
2490
|
-
children,
|
|
2491
|
-
asChild = false,
|
|
2492
|
-
className,
|
|
2493
|
-
...formProps
|
|
2494
|
-
}) {
|
|
2495
|
-
const defaultLabel = useUiLabel("note");
|
|
2496
|
-
const defaultSaveLabel = useUiLabel("saveNote");
|
|
2497
|
-
const errorLabel = useUiLabel("error");
|
|
3040
|
+
import { isValidElement as isValidElement5 } from "react";
|
|
3041
|
+
|
|
3042
|
+
// src/hooks/useKeepNoteEditor.ts
|
|
3043
|
+
import { useKeepItem as useKeepItem3 } from "@keepkit/core/react";
|
|
3044
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
|
|
3045
|
+
function useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError }) {
|
|
2498
3046
|
const itemState = useKeepItem3(item);
|
|
2499
3047
|
const { error, isMutating, item: savedItem, updateNote } = itemState;
|
|
2500
|
-
const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
|
|
2501
3048
|
const [note, setNote] = useState7(item.note ?? "");
|
|
2502
3049
|
const baselineNote = savedItem?.note ?? item.note ?? "";
|
|
2503
3050
|
const isDirty = note !== baselineNote;
|
|
2504
|
-
const lastSavedNoteRef =
|
|
2505
|
-
|
|
2506
|
-
const save =
|
|
3051
|
+
const lastSavedNoteRef = useRef4(void 0);
|
|
3052
|
+
useEffect4(() => setNote(baselineNote), [baselineNote]);
|
|
3053
|
+
const save = useCallback4(async () => {
|
|
2507
3054
|
const nextNote = note.trim() || void 0;
|
|
2508
3055
|
try {
|
|
2509
3056
|
await updateNote(nextNote);
|
|
2510
3057
|
lastSavedNoteRef.current = note;
|
|
2511
3058
|
onSaved?.(nextNote);
|
|
2512
|
-
} catch (
|
|
2513
|
-
onSaveError?.(
|
|
2514
|
-
throw
|
|
3059
|
+
} catch (cause) {
|
|
3060
|
+
onSaveError?.(cause);
|
|
3061
|
+
throw cause;
|
|
2515
3062
|
}
|
|
2516
3063
|
}, [note, onSaveError, onSaved, updateNote]);
|
|
2517
|
-
|
|
3064
|
+
useEffect4(() => {
|
|
2518
3065
|
if (!isDirty || debounceMs <= 0 || lastSavedNoteRef.current === note) return;
|
|
2519
3066
|
const timer = window.setTimeout(() => void save().catch(() => void 0), debounceMs);
|
|
2520
3067
|
return () => window.clearTimeout(timer);
|
|
@@ -2528,46 +3075,77 @@ function KeepNoteEditor({
|
|
|
2528
3075
|
error,
|
|
2529
3076
|
save
|
|
2530
3077
|
};
|
|
2531
|
-
const
|
|
2532
|
-
|
|
2533
|
-
|
|
3078
|
+
const submit = (event) => {
|
|
3079
|
+
event.preventDefault();
|
|
3080
|
+
void save().catch(() => void 0);
|
|
3081
|
+
};
|
|
3082
|
+
return {
|
|
3083
|
+
state,
|
|
3084
|
+
submit,
|
|
3085
|
+
handleKeyDown: (event) => {
|
|
3086
|
+
if (event.key !== "Enter" || !event.ctrlKey && !event.metaKey) return;
|
|
3087
|
+
event.preventDefault();
|
|
3088
|
+
void save().catch(() => void 0);
|
|
3089
|
+
},
|
|
3090
|
+
labels: {
|
|
3091
|
+
note: useUiLabel("note"),
|
|
3092
|
+
save: useUiLabel("saveNote"),
|
|
3093
|
+
error: useUiLabel("error")
|
|
3094
|
+
}
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
// src/KeepNoteEditor.tsx
|
|
3099
|
+
import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3100
|
+
function KeepNoteEditor({
|
|
3101
|
+
item,
|
|
3102
|
+
label,
|
|
3103
|
+
saveLabel,
|
|
3104
|
+
placeholder,
|
|
3105
|
+
debounceMs = 300,
|
|
3106
|
+
onSaved,
|
|
3107
|
+
onSaveError,
|
|
3108
|
+
render,
|
|
3109
|
+
children,
|
|
3110
|
+
asChild = false,
|
|
3111
|
+
className,
|
|
3112
|
+
...formProps
|
|
3113
|
+
}) {
|
|
3114
|
+
const view = useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError });
|
|
3115
|
+
const { error, isDirty, isSaving, note, setNote } = view.state;
|
|
3116
|
+
const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
|
|
3117
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs10(Fragment6, { children: [
|
|
3118
|
+
/* @__PURE__ */ jsxs10("label", { children: [
|
|
3119
|
+
label ?? view.labels.note,
|
|
2534
3120
|
/* @__PURE__ */ jsx15(
|
|
2535
3121
|
"textarea",
|
|
2536
3122
|
{
|
|
3123
|
+
"data-keep-action": "edit-note",
|
|
2537
3124
|
value: note,
|
|
2538
3125
|
onChange: (event) => setNote(event.currentTarget.value),
|
|
2539
3126
|
placeholder,
|
|
2540
|
-
disabled:
|
|
2541
|
-
onKeyDown:
|
|
2542
|
-
if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
|
|
2543
|
-
event.preventDefault();
|
|
2544
|
-
void save().catch(() => void 0);
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
3127
|
+
disabled: isSaving,
|
|
3128
|
+
onKeyDown: view.handleKeyDown
|
|
2547
3129
|
}
|
|
2548
3130
|
)
|
|
2549
3131
|
] }),
|
|
2550
|
-
/* @__PURE__ */ jsx15("button", { type: "submit", disabled:
|
|
3132
|
+
/* @__PURE__ */ jsx15("button", { type: "submit", "data-keep-action": "save-note", disabled: isSaving, "aria-busy": isSaving, children: saveLabel ?? view.labels.save })
|
|
2551
3133
|
] });
|
|
2552
|
-
const handleSubmit = (event) => {
|
|
2553
|
-
event.preventDefault();
|
|
2554
|
-
void save().catch(() => void 0);
|
|
2555
|
-
};
|
|
2556
3134
|
if (!asChild) {
|
|
2557
|
-
return /* @__PURE__ */
|
|
3135
|
+
return /* @__PURE__ */ jsxs10(
|
|
2558
3136
|
"form",
|
|
2559
3137
|
{
|
|
2560
3138
|
...formProps,
|
|
2561
3139
|
className,
|
|
2562
3140
|
"data-keepkit": "note-editor",
|
|
2563
|
-
onSubmit:
|
|
2564
|
-
"aria-busy":
|
|
3141
|
+
onSubmit: view.submit,
|
|
3142
|
+
"aria-busy": isSaving || formProps["aria-busy"],
|
|
2565
3143
|
"data-state": error ? "error" : isDirty ? "dirty" : "clean",
|
|
2566
|
-
"data-loading":
|
|
2567
|
-
"data-disabled":
|
|
3144
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
3145
|
+
"data-disabled": isSaving ? "true" : void 0,
|
|
2568
3146
|
children: [
|
|
2569
3147
|
body,
|
|
2570
|
-
error ? /* @__PURE__ */ jsx15("p", { role: "alert", children: getErrorMessage3(error,
|
|
3148
|
+
error ? /* @__PURE__ */ jsx15("p", { role: "alert", children: getErrorMessage3(error, view.labels.error) }) : null
|
|
2571
3149
|
]
|
|
2572
3150
|
}
|
|
2573
3151
|
);
|
|
@@ -2579,11 +3157,11 @@ function KeepNoteEditor({
|
|
|
2579
3157
|
...formProps,
|
|
2580
3158
|
className,
|
|
2581
3159
|
"data-keepkit": "note-editor",
|
|
2582
|
-
onSubmit:
|
|
2583
|
-
"aria-busy":
|
|
3160
|
+
onSubmit: view.submit,
|
|
3161
|
+
"aria-busy": isSaving || formProps["aria-busy"],
|
|
2584
3162
|
"data-state": error ? "error" : isDirty ? "dirty" : "clean",
|
|
2585
|
-
"data-loading":
|
|
2586
|
-
"data-disabled":
|
|
3163
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
3164
|
+
"data-disabled": isSaving ? "true" : void 0
|
|
2587
3165
|
},
|
|
2588
3166
|
body,
|
|
2589
3167
|
"KeepNoteEditor"
|
|
@@ -2593,10 +3171,91 @@ function getErrorMessage3(error, fallback) {
|
|
|
2593
3171
|
return error instanceof Error ? error.message : fallback;
|
|
2594
3172
|
}
|
|
2595
3173
|
|
|
2596
|
-
// src/
|
|
3174
|
+
// src/hooks/useKeepSyncFeedback.ts
|
|
2597
3175
|
import { useKeepContext as useKeepContext3 } from "@keepkit/core/react";
|
|
2598
|
-
import { useEffect as
|
|
2599
|
-
|
|
3176
|
+
import { useEffect as useEffect5, useRef as useRef5 } from "react";
|
|
3177
|
+
function useKeepSyncFeedback() {
|
|
3178
|
+
const { syncState } = useKeepContext3();
|
|
3179
|
+
const emitFeedback = useKeepUiFeedback();
|
|
3180
|
+
const completedMessage = useUiLabel("syncSynced");
|
|
3181
|
+
const failedMessage = useUiLabel("syncFailedMessage");
|
|
3182
|
+
const previousStatus = useRef5("idle");
|
|
3183
|
+
useEffect5(() => {
|
|
3184
|
+
const previous = previousStatus.current;
|
|
3185
|
+
previousStatus.current = syncState.status;
|
|
3186
|
+
if (syncState.status === "error" && previous !== "error") {
|
|
3187
|
+
emitFeedback({ type: "sync-failed", error: syncState.error, message: failedMessage });
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
if (syncState.status === "synced" && (previous === "pending" || previous === "syncing" || previous === "conflict" || previous === "error")) {
|
|
3191
|
+
emitFeedback({ type: "sync-completed", message: completedMessage });
|
|
3192
|
+
}
|
|
3193
|
+
}, [completedMessage, emitFeedback, failedMessage, syncState.error, syncState.status]);
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
// src/KeepSyncFeedbackObserver.tsx
|
|
3197
|
+
function KeepSyncFeedbackObserver() {
|
|
3198
|
+
useKeepSyncFeedback();
|
|
3199
|
+
return null;
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// src/hooks/useKeepSyncRecoveryDialog.ts
|
|
3203
|
+
import { useKeepContext as useKeepContext4 } from "@keepkit/core/react";
|
|
3204
|
+
import { useEffect as useEffect6, useState as useState8 } from "react";
|
|
3205
|
+
function useKeepSyncRecoveryDialog(options) {
|
|
3206
|
+
const { open, onOpenChange, conflicts, onManualMerge } = options;
|
|
3207
|
+
const context = useKeepContext4();
|
|
3208
|
+
const conflictList = conflicts ?? context.syncState.conflicts ?? [];
|
|
3209
|
+
const hasRecovery = conflictList.length > 0 || context.syncState.status === "error" || Boolean(context.error);
|
|
3210
|
+
const [dismissed, setDismissed] = useState8(false);
|
|
3211
|
+
const [busyId, setBusyId] = useState8();
|
|
3212
|
+
const [error, setError] = useState8();
|
|
3213
|
+
useEffect6(() => {
|
|
3214
|
+
if (hasRecovery) setDismissed(false);
|
|
3215
|
+
}, [hasRecovery]);
|
|
3216
|
+
return {
|
|
3217
|
+
conflictList,
|
|
3218
|
+
isOpen: open ?? (hasRecovery && !dismissed),
|
|
3219
|
+
busyId,
|
|
3220
|
+
error,
|
|
3221
|
+
showBackupRecovery: context.syncState.status === "error" || Boolean(context.error),
|
|
3222
|
+
close: () => {
|
|
3223
|
+
setDismissed(true);
|
|
3224
|
+
onOpenChange?.(false);
|
|
3225
|
+
},
|
|
3226
|
+
resolve: async (conflict, resolution) => {
|
|
3227
|
+
setError(void 0);
|
|
3228
|
+
setBusyId(conflict.id);
|
|
3229
|
+
try {
|
|
3230
|
+
const merged = resolution === "manual" ? await onManualMerge?.(conflict) : void 0;
|
|
3231
|
+
if (resolution === "manual" && !merged) throw new Error("A manual merge result is required.");
|
|
3232
|
+
await context.resolveSyncConflict(conflict.id, resolution, merged);
|
|
3233
|
+
} catch (cause) {
|
|
3234
|
+
setError(cause);
|
|
3235
|
+
} finally {
|
|
3236
|
+
setBusyId(void 0);
|
|
3237
|
+
}
|
|
3238
|
+
},
|
|
3239
|
+
labels: {
|
|
3240
|
+
close: useUiLabel("close"),
|
|
3241
|
+
title: useUiLabel("resolveSync"),
|
|
3242
|
+
conflict: useUiLabel("syncConflict"),
|
|
3243
|
+
keepLocal: useUiLabel("keepLocal"),
|
|
3244
|
+
useServer: useUiLabel("useServer"),
|
|
3245
|
+
manualMerge: useUiLabel("manualMerge"),
|
|
3246
|
+
localVersion: useUiLabel("localVersion"),
|
|
3247
|
+
remoteVersion: useUiLabel("remoteVersion"),
|
|
3248
|
+
updatedAt: useUiLabel("updatedAt"),
|
|
3249
|
+
note: useUiLabel("note"),
|
|
3250
|
+
backupRecovery: useUiLabel("backupRecovery"),
|
|
3251
|
+
backupRecoveryDescription: useUiLabel("backupRecoveryDescription"),
|
|
3252
|
+
error: useUiLabel("error")
|
|
3253
|
+
}
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
// src/KeepSyncRecoveryDialog.tsx
|
|
3258
|
+
import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2600
3259
|
function KeepSyncRecoveryDialog({
|
|
2601
3260
|
open,
|
|
2602
3261
|
onOpenChange,
|
|
@@ -2609,49 +3268,9 @@ function KeepSyncRecoveryDialog({
|
|
|
2609
3268
|
className,
|
|
2610
3269
|
...props
|
|
2611
3270
|
}) {
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
const dialogTitle = title ?? defaultDialogTitle;
|
|
2616
|
-
const conflictLabel = useUiLabel("syncConflict");
|
|
2617
|
-
const keepLocalLabel = useUiLabel("keepLocal");
|
|
2618
|
-
const useServerLabel = useUiLabel("useServer");
|
|
2619
|
-
const manualMergeLabel = useUiLabel("manualMerge");
|
|
2620
|
-
const localVersionLabel = useUiLabel("localVersion");
|
|
2621
|
-
const remoteVersionLabel = useUiLabel("remoteVersion");
|
|
2622
|
-
const updatedAtLabel = useUiLabel("updatedAt");
|
|
2623
|
-
const noteLabel = useUiLabel("note");
|
|
2624
|
-
const backupRecoveryLabel = useUiLabel("backupRecovery");
|
|
2625
|
-
const backupRecoveryDescription = useUiLabel("backupRecoveryDescription");
|
|
2626
|
-
const errorLabel = useUiLabel("error");
|
|
2627
|
-
const conflictList = conflicts ?? context.syncState.conflicts ?? [];
|
|
2628
|
-
const hasRecovery = conflictList.length > 0 || context.syncState.status === "error" || Boolean(context.error);
|
|
2629
|
-
const [dismissed, setDismissed] = useState8(false);
|
|
2630
|
-
const [busyId, setBusyId] = useState8();
|
|
2631
|
-
const [error, setError] = useState8();
|
|
2632
|
-
useEffect4(() => {
|
|
2633
|
-
if (hasRecovery) setDismissed(false);
|
|
2634
|
-
}, [hasRecovery]);
|
|
2635
|
-
const isOpen = open ?? (hasRecovery && !dismissed);
|
|
2636
|
-
if (!isOpen) return null;
|
|
2637
|
-
const close = () => {
|
|
2638
|
-
setDismissed(true);
|
|
2639
|
-
onOpenChange?.(false);
|
|
2640
|
-
};
|
|
2641
|
-
async function resolve(conflict, resolution) {
|
|
2642
|
-
setError(void 0);
|
|
2643
|
-
setBusyId(conflict.id);
|
|
2644
|
-
try {
|
|
2645
|
-
const merged = resolution === "manual" ? await onManualMerge?.(conflict) : void 0;
|
|
2646
|
-
if (resolution === "manual" && !merged) throw new Error("A manual merge result is required.");
|
|
2647
|
-
await context.resolveSyncConflict(conflict.id, resolution, merged);
|
|
2648
|
-
} catch (cause) {
|
|
2649
|
-
setError(cause);
|
|
2650
|
-
} finally {
|
|
2651
|
-
setBusyId(void 0);
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
return /* @__PURE__ */ jsxs10(
|
|
3271
|
+
const view = useKeepSyncRecoveryDialog({ open, onOpenChange, conflicts, onManualMerge });
|
|
3272
|
+
if (!view.isOpen) return null;
|
|
3273
|
+
return /* @__PURE__ */ jsxs11(
|
|
2655
3274
|
"section",
|
|
2656
3275
|
{
|
|
2657
3276
|
...props,
|
|
@@ -2659,29 +3278,29 @@ function KeepSyncRecoveryDialog({
|
|
|
2659
3278
|
role: "dialog",
|
|
2660
3279
|
"aria-modal": "true",
|
|
2661
3280
|
"aria-labelledby": "keepkit-sync-recovery-title",
|
|
2662
|
-
"aria-describedby": error ? "keepkit-sync-recovery-error" : void 0,
|
|
2663
|
-
"aria-busy": busyId !== void 0,
|
|
3281
|
+
"aria-describedby": view.error ? "keepkit-sync-recovery-error" : void 0,
|
|
3282
|
+
"aria-busy": view.busyId !== void 0,
|
|
2664
3283
|
"data-keepkit": "sync-recovery",
|
|
2665
|
-
"data-state": conflictList.length > 0 ? "conflict" : "error",
|
|
2666
|
-
"data-loading": busyId !== void 0 ? "true" : void 0,
|
|
3284
|
+
"data-state": view.conflictList.length > 0 ? "conflict" : "error",
|
|
3285
|
+
"data-loading": view.busyId !== void 0 ? "true" : void 0,
|
|
2667
3286
|
children: [
|
|
2668
|
-
/* @__PURE__ */
|
|
2669
|
-
/* @__PURE__ */ jsx16("h2", { id: "keepkit-sync-recovery-title", children:
|
|
2670
|
-
/* @__PURE__ */ jsx16("button", { type: "button", onClick: close, "aria-label":
|
|
3287
|
+
/* @__PURE__ */ jsxs11("header", { children: [
|
|
3288
|
+
/* @__PURE__ */ jsx16("h2", { id: "keepkit-sync-recovery-title", children: title ?? view.labels.title }),
|
|
3289
|
+
/* @__PURE__ */ jsx16("button", { type: "button", "data-keep-action": "close-dialog", onClick: view.close, "aria-label": view.labels.close, children: view.labels.close })
|
|
2671
3290
|
] }),
|
|
2672
3291
|
children,
|
|
2673
|
-
conflictList.length > 0 ? /* @__PURE__ */
|
|
2674
|
-
/* @__PURE__ */ jsx16("p", { children:
|
|
2675
|
-
conflictList.map((conflict) => /* @__PURE__ */
|
|
3292
|
+
view.conflictList.length > 0 ? /* @__PURE__ */ jsxs11("div", { children: [
|
|
3293
|
+
/* @__PURE__ */ jsx16("p", { children: view.labels.conflict }),
|
|
3294
|
+
view.conflictList.map((conflict) => /* @__PURE__ */ jsxs11("article", { "data-conflict-id": conflict.id, children: [
|
|
2676
3295
|
/* @__PURE__ */ jsx16("h3", { children: getMetaTitle(conflict.operation.item?.meta) ?? conflict.id }),
|
|
2677
|
-
/* @__PURE__ */
|
|
3296
|
+
/* @__PURE__ */ jsxs11("div", { "data-conflict-preview": true, children: [
|
|
2678
3297
|
/* @__PURE__ */ jsx16(
|
|
2679
3298
|
ConflictPreview,
|
|
2680
3299
|
{
|
|
2681
3300
|
item: conflict.operation.item,
|
|
2682
|
-
heading:
|
|
2683
|
-
updatedAtLabel,
|
|
2684
|
-
noteLabel,
|
|
3301
|
+
heading: view.labels.localVersion,
|
|
3302
|
+
updatedAtLabel: view.labels.updatedAt,
|
|
3303
|
+
noteLabel: view.labels.note,
|
|
2685
3304
|
side: "local"
|
|
2686
3305
|
}
|
|
2687
3306
|
),
|
|
@@ -2689,34 +3308,53 @@ function KeepSyncRecoveryDialog({
|
|
|
2689
3308
|
ConflictPreview,
|
|
2690
3309
|
{
|
|
2691
3310
|
item: conflict.remote,
|
|
2692
|
-
heading:
|
|
2693
|
-
updatedAtLabel,
|
|
2694
|
-
noteLabel,
|
|
3311
|
+
heading: view.labels.remoteVersion,
|
|
3312
|
+
updatedAtLabel: view.labels.updatedAt,
|
|
3313
|
+
noteLabel: view.labels.note,
|
|
2695
3314
|
side: "remote"
|
|
2696
3315
|
}
|
|
2697
3316
|
)
|
|
2698
3317
|
] }),
|
|
2699
|
-
/* @__PURE__ */
|
|
2700
|
-
/* @__PURE__ */ jsx16(
|
|
2701
|
-
|
|
3318
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
3319
|
+
/* @__PURE__ */ jsx16(
|
|
3320
|
+
"button",
|
|
3321
|
+
{
|
|
3322
|
+
type: "button",
|
|
3323
|
+
"data-keep-action": "keep-local",
|
|
3324
|
+
onClick: () => void view.resolve(conflict, "local"),
|
|
3325
|
+
disabled: view.busyId !== void 0,
|
|
3326
|
+
children: view.labels.keepLocal
|
|
3327
|
+
}
|
|
3328
|
+
),
|
|
2702
3329
|
/* @__PURE__ */ jsx16(
|
|
2703
3330
|
"button",
|
|
2704
3331
|
{
|
|
2705
3332
|
type: "button",
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3333
|
+
"data-keep-action": "use-server",
|
|
3334
|
+
onClick: () => void view.resolve(conflict, "remote"),
|
|
3335
|
+
disabled: view.busyId !== void 0,
|
|
3336
|
+
children: view.labels.useServer
|
|
3337
|
+
}
|
|
3338
|
+
),
|
|
3339
|
+
/* @__PURE__ */ jsx16(
|
|
3340
|
+
"button",
|
|
3341
|
+
{
|
|
3342
|
+
type: "button",
|
|
3343
|
+
"data-keep-action": "manual-merge",
|
|
3344
|
+
onClick: () => void view.resolve(conflict, "manual"),
|
|
3345
|
+
disabled: view.busyId !== void 0 || !onManualMerge,
|
|
3346
|
+
children: view.labels.manualMerge
|
|
2709
3347
|
}
|
|
2710
3348
|
)
|
|
2711
3349
|
] })
|
|
2712
3350
|
] }, conflict.id))
|
|
2713
3351
|
] }) : null,
|
|
2714
|
-
|
|
2715
|
-
/* @__PURE__ */ jsx16("h3", { children:
|
|
2716
|
-
/* @__PURE__ */ jsx16("p", { children: backupRecoveryDescription }),
|
|
3352
|
+
view.showBackupRecovery ? /* @__PURE__ */ jsxs11("section", { "data-recovery": "backup", children: [
|
|
3353
|
+
/* @__PURE__ */ jsx16("h3", { children: view.labels.backupRecovery }),
|
|
3354
|
+
/* @__PURE__ */ jsx16("p", { children: view.labels.backupRecoveryDescription }),
|
|
2717
3355
|
backup ?? (showBackupControls ? /* @__PURE__ */ jsx16(KeepBackup, {}) : null)
|
|
2718
3356
|
] }) : null,
|
|
2719
|
-
error ? /* @__PURE__ */ jsx16("p", { id: "keepkit-sync-recovery-error", role: "alert", "aria-live": "assertive", children: error instanceof Error ? error.message :
|
|
3357
|
+
view.error ? /* @__PURE__ */ jsx16("p", { id: "keepkit-sync-recovery-error", role: "alert", "aria-live": "assertive", children: view.error instanceof Error ? view.error.message : view.labels.error }) : null
|
|
2720
3358
|
]
|
|
2721
3359
|
}
|
|
2722
3360
|
);
|
|
@@ -2728,14 +3366,14 @@ function ConflictPreview({
|
|
|
2728
3366
|
noteLabel,
|
|
2729
3367
|
side
|
|
2730
3368
|
}) {
|
|
2731
|
-
return /* @__PURE__ */
|
|
3369
|
+
return /* @__PURE__ */ jsxs11("article", { "data-conflict-version": side, "aria-label": heading, children: [
|
|
2732
3370
|
/* @__PURE__ */ jsx16("h4", { children: heading }),
|
|
2733
|
-
/* @__PURE__ */
|
|
2734
|
-
/* @__PURE__ */
|
|
3371
|
+
/* @__PURE__ */ jsxs11("dl", { children: [
|
|
3372
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
2735
3373
|
/* @__PURE__ */ jsx16("dt", { children: updatedAtLabel }),
|
|
2736
3374
|
/* @__PURE__ */ jsx16("dd", { children: item ? /* @__PURE__ */ jsx16("time", { dateTime: new Date(item.updatedAt).toISOString(), children: formatConflictDate(item.updatedAt) }) : "\u2014" })
|
|
2737
3375
|
] }),
|
|
2738
|
-
/* @__PURE__ */
|
|
3376
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
2739
3377
|
/* @__PURE__ */ jsx16("dt", { children: noteLabel }),
|
|
2740
3378
|
/* @__PURE__ */ jsx16("dd", { children: item?.note || "\u2014" })
|
|
2741
3379
|
] })
|
|
@@ -2746,17 +3384,10 @@ function formatConflictDate(timestamp) {
|
|
|
2746
3384
|
return new Date(timestamp).toISOString().slice(0, 10);
|
|
2747
3385
|
}
|
|
2748
3386
|
|
|
2749
|
-
// src/
|
|
2750
|
-
import { useKeepContext as
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
onRetry,
|
|
2754
|
-
onResolveConflicts,
|
|
2755
|
-
children,
|
|
2756
|
-
className,
|
|
2757
|
-
...props
|
|
2758
|
-
}) {
|
|
2759
|
-
const context = useKeepContext4();
|
|
3387
|
+
// src/hooks/useKeepSyncStatusBanner.ts
|
|
3388
|
+
import { useKeepContext as useKeepContext5 } from "@keepkit/core/react";
|
|
3389
|
+
function useKeepSyncStatusBanner({ onRetry, children }) {
|
|
3390
|
+
const context = useKeepContext5();
|
|
2760
3391
|
const retryLabel = useUiLabel("retrySync");
|
|
2761
3392
|
const resolveLabel = useUiLabel("resolveSync");
|
|
2762
3393
|
const conflictLabel = useUiLabel("syncConflict");
|
|
@@ -2764,117 +3395,175 @@ function KeepSyncStatusBanner({
|
|
|
2764
3395
|
const syncedLabel = useUiLabel("syncSynced");
|
|
2765
3396
|
const status = context.syncState.status;
|
|
2766
3397
|
const hasConflicts = (context.syncState.conflicts?.length ?? 0) > 0 || context.syncState.conflictIds.length > 0;
|
|
2767
|
-
if (status === "idle" && !hasConflicts) return null;
|
|
2768
3398
|
const message = children ?? (status === "error" ? getErrorMessage4(context.syncState.error) : status === "conflict" || hasConflicts ? conflictLabel : status === "pending" || status === "syncing" ? pendingLabel : syncedLabel);
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3399
|
+
return {
|
|
3400
|
+
status,
|
|
3401
|
+
hasConflicts,
|
|
3402
|
+
message,
|
|
3403
|
+
isMutating: context.isMutating,
|
|
3404
|
+
role: status === "error" || status === "conflict" || hasConflicts ? "alert" : "status",
|
|
3405
|
+
showRetry: status === "error" || status === "pending" || status === "syncing",
|
|
3406
|
+
retryLabel,
|
|
3407
|
+
resolveLabel,
|
|
3408
|
+
retry: async () => {
|
|
3409
|
+
if (onRetry) {
|
|
3410
|
+
await onRetry();
|
|
3411
|
+
return;
|
|
3412
|
+
}
|
|
3413
|
+
await context.flushSync();
|
|
2774
3414
|
}
|
|
2775
|
-
await context.flushSync();
|
|
2776
3415
|
};
|
|
2777
|
-
|
|
3416
|
+
}
|
|
3417
|
+
function getErrorMessage4(error) {
|
|
3418
|
+
return error instanceof Error ? error.message : "Sync failed.";
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
// src/KeepSyncStatusBanner.tsx
|
|
3422
|
+
import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3423
|
+
function KeepSyncStatusBanner({
|
|
3424
|
+
onRetry,
|
|
3425
|
+
onResolveConflicts,
|
|
3426
|
+
children,
|
|
3427
|
+
className,
|
|
3428
|
+
...props
|
|
3429
|
+
}) {
|
|
3430
|
+
const view = useKeepSyncStatusBanner({ onRetry, children });
|
|
3431
|
+
if (view.status === "idle" && !view.hasConflicts) return null;
|
|
3432
|
+
return /* @__PURE__ */ jsxs12(
|
|
2778
3433
|
"aside",
|
|
2779
3434
|
{
|
|
2780
3435
|
...props,
|
|
2781
3436
|
className,
|
|
2782
|
-
role: props.role ?? role,
|
|
3437
|
+
role: props.role ?? view.role,
|
|
2783
3438
|
"aria-live": props["aria-live"] ?? "polite",
|
|
2784
3439
|
"data-keepkit": "sync-status",
|
|
2785
|
-
"data-state": status,
|
|
3440
|
+
"data-state": view.status,
|
|
2786
3441
|
children: [
|
|
2787
|
-
/* @__PURE__ */ jsx17("p", { children: message }),
|
|
2788
|
-
|
|
2789
|
-
|
|
3442
|
+
/* @__PURE__ */ jsx17("p", { children: view.message }),
|
|
3443
|
+
view.showRetry ? /* @__PURE__ */ jsx17(
|
|
3444
|
+
"button",
|
|
3445
|
+
{
|
|
3446
|
+
type: "button",
|
|
3447
|
+
"data-keep-action": "retry-sync",
|
|
3448
|
+
onClick: () => void view.retry(),
|
|
3449
|
+
disabled: view.isMutating,
|
|
3450
|
+
children: view.retryLabel
|
|
3451
|
+
}
|
|
3452
|
+
) : null,
|
|
3453
|
+
view.hasConflicts ? /* @__PURE__ */ jsx17(
|
|
3454
|
+
"button",
|
|
3455
|
+
{
|
|
3456
|
+
type: "button",
|
|
3457
|
+
"data-keep-action": "resolve-conflicts",
|
|
3458
|
+
onClick: onResolveConflicts,
|
|
3459
|
+
disabled: !onResolveConflicts,
|
|
3460
|
+
children: view.resolveLabel
|
|
3461
|
+
}
|
|
3462
|
+
) : null
|
|
2790
3463
|
]
|
|
2791
3464
|
}
|
|
2792
3465
|
);
|
|
2793
3466
|
}
|
|
2794
|
-
function getErrorMessage4(error) {
|
|
2795
|
-
return error instanceof Error ? error.message : "Sync failed.";
|
|
2796
|
-
}
|
|
2797
3467
|
|
|
2798
|
-
// src/
|
|
3468
|
+
// src/hooks/useKeepTagEditor.ts
|
|
2799
3469
|
import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
|
|
2800
|
-
import { useCallback as
|
|
2801
|
-
|
|
2802
|
-
function KeepTagEditor({
|
|
2803
|
-
item,
|
|
2804
|
-
availableTags = [],
|
|
2805
|
-
onSaved,
|
|
2806
|
-
onSaveError,
|
|
2807
|
-
render,
|
|
2808
|
-
...props
|
|
2809
|
-
}) {
|
|
2810
|
-
const tagsLabel = useUiLabel("tagsToApply");
|
|
2811
|
-
const removeLabel = useUiLabel("remove");
|
|
2812
|
-
const applyTagsLabel = useUiLabel("applyTags");
|
|
2813
|
-
const errorLabel = useUiLabel("error");
|
|
3470
|
+
import { useCallback as useCallback5, useEffect as useEffect7, useState as useState9 } from "react";
|
|
3471
|
+
function useKeepTagEditor({ item, onSaved, onSaveError }) {
|
|
2814
3472
|
const itemState = useKeepItem4(item);
|
|
2815
3473
|
const [tags, setTags] = useState9(item.tags ?? []);
|
|
2816
3474
|
const [input, setInput] = useState9("");
|
|
2817
|
-
|
|
2818
|
-
const save =
|
|
3475
|
+
useEffect7(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
|
|
3476
|
+
const save = useCallback5(async () => {
|
|
2819
3477
|
const nextTags = normalizeUiTags(tags);
|
|
2820
3478
|
try {
|
|
2821
3479
|
await itemState.updateTags(nextTags);
|
|
2822
3480
|
setTags(nextTags);
|
|
2823
3481
|
onSaved?.(nextTags);
|
|
2824
|
-
} catch (
|
|
2825
|
-
onSaveError?.(
|
|
2826
|
-
throw
|
|
3482
|
+
} catch (cause) {
|
|
3483
|
+
onSaveError?.(cause);
|
|
3484
|
+
throw cause;
|
|
2827
3485
|
}
|
|
2828
3486
|
}, [itemState, onSaveError, onSaved, tags]);
|
|
2829
3487
|
const addTag = (tag) => {
|
|
2830
3488
|
setTags(normalizeUiTags([...tags, tag]));
|
|
2831
3489
|
setInput("");
|
|
2832
3490
|
};
|
|
2833
|
-
const
|
|
2834
|
-
|
|
2835
|
-
|
|
3491
|
+
const state = { tags, setTags, save, isSaving: itemState.isMutating };
|
|
3492
|
+
return {
|
|
3493
|
+
state,
|
|
3494
|
+
input,
|
|
3495
|
+
setInput,
|
|
3496
|
+
error: itemState.error,
|
|
3497
|
+
handleInputKeyDown: (event) => {
|
|
3498
|
+
if (event.key === "Enter") {
|
|
3499
|
+
if (event.nativeEvent.isComposing) return;
|
|
3500
|
+
event.preventDefault();
|
|
3501
|
+
if (input.trim()) addTag(input);
|
|
3502
|
+
} else if (event.key === "Backspace" && input.length === 0 && tags.length > 0) {
|
|
3503
|
+
event.preventDefault();
|
|
3504
|
+
setTags(tags.slice(0, -1));
|
|
3505
|
+
}
|
|
3506
|
+
},
|
|
3507
|
+
removeTag: (tag) => setTags(tags.filter((current) => current !== tag)),
|
|
3508
|
+
submit: (event) => {
|
|
3509
|
+
event.preventDefault();
|
|
3510
|
+
void save().catch(() => void 0);
|
|
3511
|
+
},
|
|
3512
|
+
labels: {
|
|
3513
|
+
tags: useUiLabel("tagsToApply"),
|
|
3514
|
+
remove: useUiLabel("remove"),
|
|
3515
|
+
apply: useUiLabel("applyTags"),
|
|
3516
|
+
error: useUiLabel("error")
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
// src/KeepTagEditor.tsx
|
|
3522
|
+
import { Fragment as Fragment7, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3523
|
+
function KeepTagEditor({
|
|
3524
|
+
item,
|
|
3525
|
+
availableTags = [],
|
|
3526
|
+
onSaved,
|
|
3527
|
+
onSaveError,
|
|
3528
|
+
render,
|
|
3529
|
+
...props
|
|
3530
|
+
}) {
|
|
3531
|
+
const view = useKeepTagEditor({ item, onSaved, onSaveError });
|
|
3532
|
+
const { isSaving, tags } = view.state;
|
|
3533
|
+
const body = render ? render(view.state) : /* @__PURE__ */ jsxs13(Fragment7, { children: [
|
|
3534
|
+
/* @__PURE__ */ jsxs13("label", { children: [
|
|
3535
|
+
view.labels.tags,
|
|
2836
3536
|
/* @__PURE__ */ jsx18(
|
|
2837
3537
|
"input",
|
|
2838
3538
|
{
|
|
2839
|
-
|
|
3539
|
+
"data-keep-action": "edit-tags",
|
|
3540
|
+
value: view.input,
|
|
2840
3541
|
list: availableTags.length > 0 ? `keep-tags-${item.id}` : void 0,
|
|
2841
|
-
onChange: (event) => setInput(event.currentTarget.value),
|
|
2842
|
-
onKeyDown:
|
|
2843
|
-
if (event.key === "Enter") {
|
|
2844
|
-
if (event.nativeEvent.isComposing) return;
|
|
2845
|
-
event.preventDefault();
|
|
2846
|
-
if (input.trim()) addTag(input);
|
|
2847
|
-
} else if (event.key === "Backspace" && input.length === 0 && tags.length > 0) {
|
|
2848
|
-
event.preventDefault();
|
|
2849
|
-
setTags(tags.slice(0, -1));
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
3542
|
+
onChange: (event) => view.setInput(event.currentTarget.value),
|
|
3543
|
+
onKeyDown: view.handleInputKeyDown
|
|
2852
3544
|
}
|
|
2853
3545
|
)
|
|
2854
3546
|
] }),
|
|
2855
3547
|
availableTags.length > 0 ? /* @__PURE__ */ jsx18("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx18("option", { value: tag }, tag)) }) : null,
|
|
2856
|
-
/* @__PURE__ */ jsx18("ul", { "aria-label":
|
|
3548
|
+
/* @__PURE__ */ jsx18("ul", { "aria-label": view.labels.tags, children: tags.map((tag) => /* @__PURE__ */ jsxs13("li", { children: [
|
|
2857
3549
|
tag,
|
|
2858
|
-
/* @__PURE__ */ jsx18("button", { type: "button", onClick: () =>
|
|
3550
|
+
/* @__PURE__ */ jsx18("button", { type: "button", "data-keep-action": "remove-tag", onClick: () => view.removeTag(tag), children: view.labels.remove })
|
|
2859
3551
|
] }, tag)) }),
|
|
2860
|
-
/* @__PURE__ */ jsx18("button", { type: "submit", disabled:
|
|
3552
|
+
/* @__PURE__ */ jsx18("button", { type: "submit", "data-keep-action": "apply-tags", disabled: isSaving, "aria-busy": isSaving, children: view.labels.apply })
|
|
2861
3553
|
] });
|
|
2862
|
-
return /* @__PURE__ */
|
|
3554
|
+
return /* @__PURE__ */ jsxs13(
|
|
2863
3555
|
"form",
|
|
2864
3556
|
{
|
|
2865
3557
|
...props,
|
|
2866
|
-
onSubmit:
|
|
2867
|
-
|
|
2868
|
-
void save().catch(() => void 0);
|
|
2869
|
-
},
|
|
2870
|
-
"aria-busy": itemState.isMutating || props["aria-busy"],
|
|
3558
|
+
onSubmit: view.submit,
|
|
3559
|
+
"aria-busy": isSaving || props["aria-busy"],
|
|
2871
3560
|
"data-keepkit": "tag-editor",
|
|
2872
|
-
"data-state":
|
|
2873
|
-
"data-loading":
|
|
2874
|
-
"data-disabled":
|
|
3561
|
+
"data-state": view.error ? "error" : isSaving ? "saving" : "idle",
|
|
3562
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
3563
|
+
"data-disabled": isSaving ? "true" : void 0,
|
|
2875
3564
|
children: [
|
|
2876
3565
|
body,
|
|
2877
|
-
|
|
3566
|
+
view.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: getErrorMessage5(view.error, view.labels.error) }) : null
|
|
2878
3567
|
]
|
|
2879
3568
|
}
|
|
2880
3569
|
);
|
|
@@ -2883,24 +3572,93 @@ function getErrorMessage5(error, fallback) {
|
|
|
2883
3572
|
return error instanceof Error ? error.message : fallback;
|
|
2884
3573
|
}
|
|
2885
3574
|
|
|
3575
|
+
// src/hooks/useKeepUndo.ts
|
|
3576
|
+
import { useKeepContext as useKeepContext6 } from "@keepkit/core/react";
|
|
3577
|
+
function useKeepUndo() {
|
|
3578
|
+
const context = useKeepContext6();
|
|
3579
|
+
const emitFeedback = useKeepUiFeedback();
|
|
3580
|
+
const restoredMessage = useUiLabel("restoredMessage");
|
|
3581
|
+
return {
|
|
3582
|
+
canUndo: context.undo.canUndo,
|
|
3583
|
+
undo: async () => {
|
|
3584
|
+
const items = context.lastChange?.items ?? (context.lastChange?.item ? [context.lastChange.item] : []);
|
|
3585
|
+
await context.undoLastRemoval();
|
|
3586
|
+
if (items.length > 0) {
|
|
3587
|
+
emitFeedback({ type: "item-restored", item: items[0], items, message: restoredMessage });
|
|
3588
|
+
}
|
|
3589
|
+
},
|
|
3590
|
+
message: useUiLabel("undoAvailable"),
|
|
3591
|
+
label: useUiLabel("undo")
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
|
|
2886
3595
|
// src/KeepUndo.tsx
|
|
2887
|
-
import {
|
|
2888
|
-
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3596
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2889
3597
|
function KeepUndo({ children, label, ...props }) {
|
|
2890
|
-
const
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
children ?? message,
|
|
2896
|
-
/* @__PURE__ */ jsx19("button", { type: "button", onClick: () => void context.undoLastRemoval(), children: label ?? undoLabel })
|
|
3598
|
+
const view = useKeepUndo();
|
|
3599
|
+
if (!view.canUndo) return null;
|
|
3600
|
+
return /* @__PURE__ */ jsxs14("div", { ...props, role: "status", "aria-live": "polite", "data-keepkit": "undo", "data-state": "available", children: [
|
|
3601
|
+
children ?? view.message,
|
|
3602
|
+
/* @__PURE__ */ jsx19("button", { type: "button", "data-keep-action": "undo", onClick: () => void view.undo(), children: label ?? view.label })
|
|
2897
3603
|
] });
|
|
2898
3604
|
}
|
|
2899
3605
|
|
|
2900
3606
|
// src/status.tsx
|
|
2901
|
-
import {
|
|
2902
|
-
|
|
2903
|
-
|
|
3607
|
+
import { isValidElement as isValidElement6 } from "react";
|
|
3608
|
+
|
|
3609
|
+
// src/hooks/useStatusViews.ts
|
|
3610
|
+
import { useKeepContext as useKeepContext7 } from "@keepkit/core/react";
|
|
3611
|
+
import { useEffect as useEffect8, useRef as useRef6, useState as useState10 } from "react";
|
|
3612
|
+
function useKeepEmptyState() {
|
|
3613
|
+
return useUiLabel("noItems").replace(/\.$/, "");
|
|
3614
|
+
}
|
|
3615
|
+
function useKeepStatus(status) {
|
|
3616
|
+
const context = useKeepContext7();
|
|
3617
|
+
const resolvedStatus = status ?? getDerivedStatus(context);
|
|
3618
|
+
const state = {
|
|
3619
|
+
status: resolvedStatus,
|
|
3620
|
+
error: context.error,
|
|
3621
|
+
pendingCount: context.syncState.pendingCount,
|
|
3622
|
+
items: context.items
|
|
3623
|
+
};
|
|
3624
|
+
return { state, defaultLabel: useUiLabel(getStatusLabelKey3(resolvedStatus)) };
|
|
3625
|
+
}
|
|
3626
|
+
function useKeepAnnouncements(messages) {
|
|
3627
|
+
const context = useKeepContext7();
|
|
3628
|
+
const savedMessage = useUiLabel("savedMessage", messages?.save);
|
|
3629
|
+
const removedMessage = useUiLabel("removedMessage", messages?.remove);
|
|
3630
|
+
const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
|
|
3631
|
+
const [message, setMessage] = useState10("");
|
|
3632
|
+
const lastChangeRef = useRef6(void 0);
|
|
3633
|
+
useEffect8(() => {
|
|
3634
|
+
const change = context.lastChange;
|
|
3635
|
+
if (!change || change === lastChangeRef.current) return;
|
|
3636
|
+
lastChangeRef.current = change;
|
|
3637
|
+
if (change.action === "save") setMessage(savedMessage);
|
|
3638
|
+
else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
|
|
3639
|
+
else if (change.action === "updateNote") setMessage(noteSavedMessage);
|
|
3640
|
+
}, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
|
|
3641
|
+
return message;
|
|
3642
|
+
}
|
|
3643
|
+
function getDerivedStatus(context) {
|
|
3644
|
+
if (context.error) return "error";
|
|
3645
|
+
if (context.syncState.status === "pending" || context.syncState.status === "syncing") return "syncing";
|
|
3646
|
+
if (context.isMutating) return "saving";
|
|
3647
|
+
if (context.isLoading && !context.isHydrated) return "loading";
|
|
3648
|
+
if (context.isHydrated && context.items.length === 0) return "empty";
|
|
3649
|
+
return "idle";
|
|
3650
|
+
}
|
|
3651
|
+
function getStatusLabelKey3(status) {
|
|
3652
|
+
if (status === "empty") return "noItems";
|
|
3653
|
+
if (status === "loading") return "loadingItems";
|
|
3654
|
+
if (status === "error") return "error";
|
|
3655
|
+
if (status === "saving") return "saving";
|
|
3656
|
+
if (status === "syncing") return "syncing";
|
|
3657
|
+
return "saved";
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
// src/status.tsx
|
|
3661
|
+
import { Fragment as Fragment8, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2904
3662
|
function KeepEmptyState({
|
|
2905
3663
|
title,
|
|
2906
3664
|
description,
|
|
@@ -2910,9 +3668,9 @@ function KeepEmptyState({
|
|
|
2910
3668
|
className,
|
|
2911
3669
|
...rootProps
|
|
2912
3670
|
}) {
|
|
2913
|
-
const defaultTitle =
|
|
3671
|
+
const defaultTitle = useKeepEmptyState();
|
|
2914
3672
|
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
2915
|
-
const body = contentChildren ?? /* @__PURE__ */
|
|
3673
|
+
const body = contentChildren ?? /* @__PURE__ */ jsxs15(Fragment8, { children: [
|
|
2916
3674
|
/* @__PURE__ */ jsx20("h2", { children: title ?? defaultTitle }),
|
|
2917
3675
|
description ? /* @__PURE__ */ jsx20("p", { children: description }) : null,
|
|
2918
3676
|
action
|
|
@@ -2934,18 +3692,10 @@ function KeepStatus({
|
|
|
2934
3692
|
className,
|
|
2935
3693
|
...rootProps
|
|
2936
3694
|
}) {
|
|
2937
|
-
const
|
|
3695
|
+
const view = useKeepStatus(status);
|
|
2938
3696
|
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
2939
|
-
const
|
|
2940
|
-
const
|
|
2941
|
-
const state = {
|
|
2942
|
-
status: resolvedStatus,
|
|
2943
|
-
error: context.error,
|
|
2944
|
-
pendingCount: context.syncState.pendingCount,
|
|
2945
|
-
items: context.items
|
|
2946
|
-
};
|
|
2947
|
-
const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? labels?.[resolvedStatus] ?? defaultLabel;
|
|
2948
|
-
const role = rootProps.role ?? (resolvedStatus === "error" ? "alert" : "status");
|
|
3697
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? labels?.[view.state.status] ?? view.defaultLabel;
|
|
3698
|
+
const role = rootProps.role ?? (view.state.status === "error" ? "alert" : "status");
|
|
2949
3699
|
return renderRoot(
|
|
2950
3700
|
asChild,
|
|
2951
3701
|
isValidElement6(children) ? children : void 0,
|
|
@@ -2955,28 +3705,15 @@ function KeepStatus({
|
|
|
2955
3705
|
"data-keepkit": "status",
|
|
2956
3706
|
role,
|
|
2957
3707
|
"aria-live": rootProps["aria-live"] ?? "polite",
|
|
2958
|
-
"data-state":
|
|
2959
|
-
"data-loading":
|
|
3708
|
+
"data-state": view.state.status,
|
|
3709
|
+
"data-loading": view.state.status === "loading" || view.state.status === "saving" || view.state.status === "syncing" ? "true" : void 0
|
|
2960
3710
|
},
|
|
2961
3711
|
body,
|
|
2962
3712
|
"KeepStatus"
|
|
2963
3713
|
);
|
|
2964
3714
|
}
|
|
2965
3715
|
function KeepAnnouncements({ messages, ...props }) {
|
|
2966
|
-
const
|
|
2967
|
-
const savedMessage = useUiLabel("savedMessage", messages?.save);
|
|
2968
|
-
const removedMessage = useUiLabel("removedMessage", messages?.remove);
|
|
2969
|
-
const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
|
|
2970
|
-
const [message, setMessage] = useState10("");
|
|
2971
|
-
const lastChangeRef = useRef4(void 0);
|
|
2972
|
-
useEffect6(() => {
|
|
2973
|
-
const change = context.lastChange;
|
|
2974
|
-
if (!change || change === lastChangeRef.current) return;
|
|
2975
|
-
lastChangeRef.current = change;
|
|
2976
|
-
if (change.action === "save") setMessage(savedMessage);
|
|
2977
|
-
else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
|
|
2978
|
-
else if (change.action === "updateNote") setMessage(noteSavedMessage);
|
|
2979
|
-
}, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
|
|
3716
|
+
const message = useKeepAnnouncements(messages);
|
|
2980
3717
|
return /* @__PURE__ */ jsx20(
|
|
2981
3718
|
"div",
|
|
2982
3719
|
{
|
|
@@ -2991,26 +3728,22 @@ function KeepAnnouncements({ messages, ...props }) {
|
|
|
2991
3728
|
);
|
|
2992
3729
|
}
|
|
2993
3730
|
var KeepAnnouncer = KeepAnnouncements;
|
|
2994
|
-
function getDerivedStatus(context) {
|
|
2995
|
-
if (context.error) return "error";
|
|
2996
|
-
if (context.syncState.status === "pending" || context.syncState.status === "syncing") return "syncing";
|
|
2997
|
-
if (context.isMutating) return "saving";
|
|
2998
|
-
if (context.isLoading && !context.isHydrated) return "loading";
|
|
2999
|
-
if (context.isHydrated && context.items.length === 0) return "empty";
|
|
3000
|
-
return "idle";
|
|
3001
|
-
}
|
|
3002
|
-
function getStatusLabelKey3(status) {
|
|
3003
|
-
if (status === "empty") return "noItems";
|
|
3004
|
-
if (status === "loading") return "loadingItems";
|
|
3005
|
-
if (status === "error") return "error";
|
|
3006
|
-
if (status === "saving") return "saving";
|
|
3007
|
-
if (status === "syncing") return "syncing";
|
|
3008
|
-
return "saved";
|
|
3009
|
-
}
|
|
3010
3731
|
|
|
3011
3732
|
// src/theme.tsx
|
|
3012
3733
|
import { cloneElement as cloneElement2, isValidElement as isValidElement7 } from "react";
|
|
3013
3734
|
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
3735
|
+
var keepThemeNames = [
|
|
3736
|
+
"default",
|
|
3737
|
+
"ocean",
|
|
3738
|
+
"forest",
|
|
3739
|
+
"sunset",
|
|
3740
|
+
"lavender",
|
|
3741
|
+
"compact",
|
|
3742
|
+
"minimal",
|
|
3743
|
+
"rounded",
|
|
3744
|
+
"high-contrast",
|
|
3745
|
+
"dark"
|
|
3746
|
+
];
|
|
3014
3747
|
function KeepThemeProvider({
|
|
3015
3748
|
children,
|
|
3016
3749
|
theme = "default",
|
|
@@ -3065,7 +3798,7 @@ import { createAuthenticatedSyncKit } from "@keepkit/core/core";
|
|
|
3065
3798
|
import {
|
|
3066
3799
|
KeepErrorBoundary as KeepErrorBoundary3,
|
|
3067
3800
|
KeepProvider,
|
|
3068
|
-
useKeepContext as
|
|
3801
|
+
useKeepContext as useKeepContext8,
|
|
3069
3802
|
useKeepItem as useKeepItem5,
|
|
3070
3803
|
useKeepList as useKeepList5,
|
|
3071
3804
|
useKeepShortcut
|
|
@@ -3080,11 +3813,12 @@ import {
|
|
|
3080
3813
|
LocalStorageSyncQueueAdapter,
|
|
3081
3814
|
SyncStorageAdapter
|
|
3082
3815
|
} from "@keepkit/core/storage";
|
|
3083
|
-
import { jsx as jsx22, jsxs as
|
|
3816
|
+
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3084
3817
|
function KeepKitProvider({
|
|
3085
3818
|
labels,
|
|
3086
3819
|
locale,
|
|
3087
3820
|
labelResolver,
|
|
3821
|
+
onFeedback,
|
|
3088
3822
|
theme,
|
|
3089
3823
|
mode,
|
|
3090
3824
|
density,
|
|
@@ -3099,7 +3833,7 @@ function KeepKitProvider({
|
|
|
3099
3833
|
children,
|
|
3100
3834
|
...providerProps
|
|
3101
3835
|
}) {
|
|
3102
|
-
return /* @__PURE__ */ jsx22(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsx22(
|
|
3836
|
+
return /* @__PURE__ */ jsx22(KeepUiProvider, { labels, locale, labelResolver, onFeedback, children: /* @__PURE__ */ jsx22(
|
|
3103
3837
|
KeepThemeProvider,
|
|
3104
3838
|
{
|
|
3105
3839
|
theme,
|
|
@@ -3113,7 +3847,8 @@ function KeepKitProvider({
|
|
|
3113
3847
|
className: themeClassName,
|
|
3114
3848
|
style: themeStyle,
|
|
3115
3849
|
asChild: themeAsChild,
|
|
3116
|
-
children: /* @__PURE__ */
|
|
3850
|
+
children: /* @__PURE__ */ jsxs16(CoreKeepProvider, { ...providerProps, children: [
|
|
3851
|
+
/* @__PURE__ */ jsx22(KeepSyncFeedbackObserver, {}),
|
|
3117
3852
|
children,
|
|
3118
3853
|
/* @__PURE__ */ jsx22(KeepAnnouncements, {})
|
|
3119
3854
|
] })
|
|
@@ -3125,6 +3860,7 @@ function createKeepKit(options = {}) {
|
|
|
3125
3860
|
labels,
|
|
3126
3861
|
locale,
|
|
3127
3862
|
labelResolver,
|
|
3863
|
+
onFeedback,
|
|
3128
3864
|
theme,
|
|
3129
3865
|
mode,
|
|
3130
3866
|
density,
|
|
@@ -3148,6 +3884,7 @@ function createKeepKit(options = {}) {
|
|
|
3148
3884
|
labels,
|
|
3149
3885
|
locale,
|
|
3150
3886
|
labelResolver,
|
|
3887
|
+
onFeedback,
|
|
3151
3888
|
theme,
|
|
3152
3889
|
mode,
|
|
3153
3890
|
density,
|
|
@@ -3193,6 +3930,7 @@ export {
|
|
|
3193
3930
|
KeepEmptyState,
|
|
3194
3931
|
KeepErrorBoundary3 as KeepErrorBoundary,
|
|
3195
3932
|
KeepItemCard,
|
|
3933
|
+
KeepItemCardSkeleton,
|
|
3196
3934
|
KeepItemCheckbox,
|
|
3197
3935
|
KeepItemStatusBadge,
|
|
3198
3936
|
KeepKitProvider,
|
|
@@ -3223,11 +3961,13 @@ export {
|
|
|
3223
3961
|
createStorageAdapter,
|
|
3224
3962
|
getKeepLocaleLabels,
|
|
3225
3963
|
isAllSelected,
|
|
3964
|
+
keepThemeNames,
|
|
3226
3965
|
toggleSelectAll,
|
|
3227
|
-
|
|
3966
|
+
useKeepContext8 as useKeepContext,
|
|
3228
3967
|
useKeepItem5 as useKeepItem,
|
|
3229
3968
|
useKeepList5 as useKeepList,
|
|
3230
3969
|
useKeepShortcut,
|
|
3970
|
+
useKeepToastFeedback,
|
|
3231
3971
|
useKeepUiLabels,
|
|
3232
3972
|
useKeepUrlSync
|
|
3233
3973
|
};
|