@keepkit/ui 0.14.0 → 0.15.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 +15 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.js +1250 -831
- 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 +244 -2
- 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,7 +6,7 @@ import {
|
|
|
6
6
|
createKeepKit as createCoreKeepKit
|
|
7
7
|
} from "@keepkit/core/react";
|
|
8
8
|
|
|
9
|
-
// src/
|
|
9
|
+
// src/hooks/useKeepBackup.ts
|
|
10
10
|
import { useKeepContext } from "@keepkit/core/react";
|
|
11
11
|
import { useRef, useState } from "react";
|
|
12
12
|
|
|
@@ -1309,28 +1309,14 @@ function useUiLabel(key, override) {
|
|
|
1309
1309
|
return override ?? context.customLabels?.[key] ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];
|
|
1310
1310
|
}
|
|
1311
1311
|
|
|
1312
|
-
// src/
|
|
1313
|
-
|
|
1314
|
-
function KeepBackup({
|
|
1315
|
-
filename = "keepkit-backup.json",
|
|
1316
|
-
onExport,
|
|
1317
|
-
onImported,
|
|
1318
|
-
...props
|
|
1319
|
-
}) {
|
|
1312
|
+
// src/hooks/useKeepBackup.ts
|
|
1313
|
+
function useKeepBackup({ filename, onExport, onImported }) {
|
|
1320
1314
|
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
1315
|
const inputRef = useRef(null);
|
|
1330
1316
|
const [mode, setMode] = useState("merge");
|
|
1331
1317
|
const [result, setResult] = useState();
|
|
1332
1318
|
const [error, setError] = useState();
|
|
1333
|
-
async function
|
|
1319
|
+
async function exportBackup() {
|
|
1334
1320
|
setError(void 0);
|
|
1335
1321
|
try {
|
|
1336
1322
|
const data = await context.exportBackup();
|
|
@@ -1346,7 +1332,7 @@ function KeepBackup({
|
|
|
1346
1332
|
setError(cause);
|
|
1347
1333
|
}
|
|
1348
1334
|
}
|
|
1349
|
-
async function
|
|
1335
|
+
async function importBackup(event) {
|
|
1350
1336
|
const file = event.currentTarget.files?.[0];
|
|
1351
1337
|
event.currentTarget.value = "";
|
|
1352
1338
|
if (!file) return;
|
|
@@ -1360,43 +1346,93 @@ function KeepBackup({
|
|
|
1360
1346
|
setError(cause);
|
|
1361
1347
|
}
|
|
1362
1348
|
}
|
|
1349
|
+
return {
|
|
1350
|
+
inputRef,
|
|
1351
|
+
mode,
|
|
1352
|
+
setMode,
|
|
1353
|
+
result,
|
|
1354
|
+
error,
|
|
1355
|
+
isMutating: context.isMutating,
|
|
1356
|
+
exportBackup,
|
|
1357
|
+
importBackup,
|
|
1358
|
+
openFilePicker: () => inputRef.current?.click(),
|
|
1359
|
+
labels: {
|
|
1360
|
+
export: useUiLabel("exportData"),
|
|
1361
|
+
import: useUiLabel("importData"),
|
|
1362
|
+
importMode: useUiLabel("importMode"),
|
|
1363
|
+
merge: useUiLabel("merge"),
|
|
1364
|
+
replace: useUiLabel("replace"),
|
|
1365
|
+
importedCount: useUiLabel("importedCount"),
|
|
1366
|
+
failedCount: useUiLabel("failedCount"),
|
|
1367
|
+
quotaError: useUiLabel("storageQuotaError")
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// src/KeepBackup.tsx
|
|
1373
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1374
|
+
function KeepBackup({
|
|
1375
|
+
filename = "keepkit-backup.json",
|
|
1376
|
+
onExport,
|
|
1377
|
+
onImported,
|
|
1378
|
+
...props
|
|
1379
|
+
}) {
|
|
1380
|
+
const view = useKeepBackup({ filename, onExport, onImported });
|
|
1363
1381
|
return /* @__PURE__ */ jsxs(
|
|
1364
1382
|
"section",
|
|
1365
1383
|
{
|
|
1366
1384
|
...props,
|
|
1367
1385
|
"data-keepkit": "backup",
|
|
1368
|
-
"data-state": error ? "error" : result ? "complete" : "idle",
|
|
1369
|
-
"data-loading":
|
|
1386
|
+
"data-state": view.error ? "error" : view.result ? "complete" : "idle",
|
|
1387
|
+
"data-loading": view.isMutating ? "true" : void 0,
|
|
1370
1388
|
children: [
|
|
1371
|
-
/* @__PURE__ */ jsx2(
|
|
1389
|
+
/* @__PURE__ */ jsx2(
|
|
1390
|
+
"button",
|
|
1391
|
+
{
|
|
1392
|
+
type: "button",
|
|
1393
|
+
"data-keep-action": "export-backup",
|
|
1394
|
+
onClick: () => void view.exportBackup(),
|
|
1395
|
+
disabled: view.isMutating,
|
|
1396
|
+
children: view.labels.export
|
|
1397
|
+
}
|
|
1398
|
+
),
|
|
1372
1399
|
/* @__PURE__ */ jsxs("label", { children: [
|
|
1373
|
-
|
|
1374
|
-
/* @__PURE__ */ jsxs(
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1400
|
+
view.labels.importMode,
|
|
1401
|
+
/* @__PURE__ */ jsxs(
|
|
1402
|
+
"select",
|
|
1403
|
+
{
|
|
1404
|
+
"data-keep-action": "select-import-mode",
|
|
1405
|
+
value: view.mode,
|
|
1406
|
+
onChange: (event) => view.setMode(event.currentTarget.value),
|
|
1407
|
+
children: [
|
|
1408
|
+
/* @__PURE__ */ jsx2("option", { value: "merge", children: view.labels.merge }),
|
|
1409
|
+
/* @__PURE__ */ jsx2("option", { value: "replace", children: view.labels.replace })
|
|
1410
|
+
]
|
|
1411
|
+
}
|
|
1412
|
+
)
|
|
1378
1413
|
] }),
|
|
1379
|
-
/* @__PURE__ */ jsx2("button", { type: "button",
|
|
1414
|
+
/* @__PURE__ */ jsx2("button", { type: "button", "data-keep-action": "import-backup", onClick: view.openFilePicker, disabled: view.isMutating, children: view.labels.import }),
|
|
1380
1415
|
/* @__PURE__ */ jsx2(
|
|
1381
1416
|
"input",
|
|
1382
1417
|
{
|
|
1383
|
-
ref: inputRef,
|
|
1418
|
+
ref: view.inputRef,
|
|
1384
1419
|
type: "file",
|
|
1420
|
+
"data-keep-action": "select-backup-file",
|
|
1385
1421
|
accept: "application/json,.json",
|
|
1386
|
-
"aria-label":
|
|
1387
|
-
onChange: (event) => void
|
|
1422
|
+
"aria-label": view.labels.import,
|
|
1423
|
+
onChange: (event) => void view.importBackup(event)
|
|
1388
1424
|
}
|
|
1389
1425
|
),
|
|
1390
|
-
result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
|
|
1391
|
-
result.imported,
|
|
1426
|
+
view.result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
|
|
1427
|
+
view.result.imported,
|
|
1392
1428
|
" ",
|
|
1393
|
-
|
|
1429
|
+
view.labels.importedCount,
|
|
1394
1430
|
"; ",
|
|
1395
|
-
result.failed,
|
|
1431
|
+
view.result.failed,
|
|
1396
1432
|
" ",
|
|
1397
|
-
|
|
1433
|
+
view.labels.failedCount
|
|
1398
1434
|
] }) : null,
|
|
1399
|
-
error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(error) ?
|
|
1435
|
+
view.error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(view.error) ? view.labels.quotaError : getErrorMessage(view.error) }) : null
|
|
1400
1436
|
]
|
|
1401
1437
|
}
|
|
1402
1438
|
);
|
|
@@ -1410,48 +1446,16 @@ function getErrorMessage(error) {
|
|
|
1410
1446
|
return error instanceof Error ? error.message : "Something went wrong.";
|
|
1411
1447
|
}
|
|
1412
1448
|
|
|
1413
|
-
// src/
|
|
1449
|
+
// src/hooks/useKeepBulkActions.ts
|
|
1414
1450
|
import { useKeepList } from "@keepkit/core/react";
|
|
1415
1451
|
import { useState as useState2 } from "react";
|
|
1416
1452
|
|
|
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
1453
|
// src/shared.tsx
|
|
1450
1454
|
import {
|
|
1451
1455
|
cloneElement,
|
|
1452
1456
|
isValidElement
|
|
1453
1457
|
} from "react";
|
|
1454
|
-
import { jsx as
|
|
1458
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1455
1459
|
function toKeepButtonItem(item) {
|
|
1456
1460
|
return {
|
|
1457
1461
|
id: item.id,
|
|
@@ -1480,11 +1484,10 @@ function renderRoot(asChild, child, props, body, componentName) {
|
|
|
1480
1484
|
if (!isValidElement(child)) throw new Error(`${componentName} with asChild requires a single React element child.`);
|
|
1481
1485
|
return cloneElement(child, { ...props, children: body });
|
|
1482
1486
|
}
|
|
1483
|
-
return /* @__PURE__ */
|
|
1487
|
+
return /* @__PURE__ */ jsx3("div", { ...props, children: body });
|
|
1484
1488
|
}
|
|
1485
1489
|
|
|
1486
|
-
// src/
|
|
1487
|
-
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1490
|
+
// src/hooks/useKeepBulkActions.ts
|
|
1488
1491
|
function isAllSelected(items, selectedIds) {
|
|
1489
1492
|
if (items.length === 0) return false;
|
|
1490
1493
|
const selected = new Set(selectedIds);
|
|
@@ -1499,42 +1502,30 @@ function toggleSelectAll(items, selectedIds) {
|
|
|
1499
1502
|
}
|
|
1500
1503
|
return nextIds;
|
|
1501
1504
|
}
|
|
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");
|
|
1505
|
+
function useKeepBulkActions(options) {
|
|
1506
|
+
const {
|
|
1507
|
+
query,
|
|
1508
|
+
controlledSelectedIds,
|
|
1509
|
+
defaultSelectedIds,
|
|
1510
|
+
onSelectedIdsChange,
|
|
1511
|
+
onCompleted,
|
|
1512
|
+
controlledScope,
|
|
1513
|
+
onSelectionScopeChange
|
|
1514
|
+
} = options;
|
|
1524
1515
|
const list = useKeepList(query);
|
|
1525
1516
|
const queryList = useKeepList(query ? { ...query, pagination: void 0 } : { pagination: void 0 });
|
|
1526
1517
|
const allList = useKeepList({ pagination: void 0 });
|
|
1527
1518
|
const [uncontrolledScope, setUncontrolledScope] = useState2(controlledScope ?? "page");
|
|
1528
1519
|
const selectionScope = controlledScope ?? uncontrolledScope;
|
|
1529
|
-
const setSelectionScope = (scope) => {
|
|
1530
|
-
if (controlledScope === void 0) setUncontrolledScope(scope);
|
|
1531
|
-
onSelectionScopeChange?.(scope);
|
|
1532
|
-
};
|
|
1533
1520
|
const targetItems = selectionScope === "page" ? list.items : selectionScope === "query" ? queryList.items : allList.items;
|
|
1534
1521
|
const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState2(defaultSelectedIds);
|
|
1535
1522
|
const selectedIds = controlledSelectedIds ?? uncontrolledSelectedIds;
|
|
1536
1523
|
const selected = new Set(selectedIds);
|
|
1537
1524
|
const [tagsInput, setTagsInput] = useState2("");
|
|
1525
|
+
const setSelectionScope = (scope) => {
|
|
1526
|
+
if (controlledScope === void 0) setUncontrolledScope(scope);
|
|
1527
|
+
onSelectionScopeChange?.(scope);
|
|
1528
|
+
};
|
|
1538
1529
|
const setSelectedIds = (ids) => {
|
|
1539
1530
|
if (controlledSelectedIds === void 0) setUncontrolledSelectedIds(ids);
|
|
1540
1531
|
onSelectedIdsChange?.(ids);
|
|
@@ -1571,54 +1562,156 @@ function KeepBulkActions({
|
|
|
1571
1562
|
selectionScope,
|
|
1572
1563
|
setSelectionScope
|
|
1573
1564
|
};
|
|
1565
|
+
return {
|
|
1566
|
+
state,
|
|
1567
|
+
selected,
|
|
1568
|
+
isLoading: list.isLoading,
|
|
1569
|
+
labels: {
|
|
1570
|
+
selectItems: useUiLabel("selectItems"),
|
|
1571
|
+
selectedCount: useUiLabel("selectedCount"),
|
|
1572
|
+
deleteSelected: useUiLabel("deleteSelected"),
|
|
1573
|
+
tags: useUiLabel("tagsToApply"),
|
|
1574
|
+
applyTags: useUiLabel("applyTags"),
|
|
1575
|
+
selectionScope: useUiLabel("selectionScope"),
|
|
1576
|
+
currentPage: useUiLabel("currentPage"),
|
|
1577
|
+
searchResults: useUiLabel("searchResults"),
|
|
1578
|
+
allItems: useUiLabel("allItems")
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/KeepItemCheckbox.tsx
|
|
1584
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1585
|
+
function KeepItemCheckbox({
|
|
1586
|
+
item,
|
|
1587
|
+
checked = false,
|
|
1588
|
+
label,
|
|
1589
|
+
onCheckedChange,
|
|
1590
|
+
"aria-label": ariaLabel,
|
|
1591
|
+
...props
|
|
1592
|
+
}) {
|
|
1593
|
+
const itemLabel = getItemLabel(item) ?? item.id;
|
|
1594
|
+
const accessibleLabel = ariaLabel ?? (typeof label === "string" ? label : itemLabel);
|
|
1595
|
+
return /* @__PURE__ */ jsx4(
|
|
1596
|
+
"input",
|
|
1597
|
+
{
|
|
1598
|
+
...props,
|
|
1599
|
+
type: "checkbox",
|
|
1600
|
+
checked,
|
|
1601
|
+
"data-keepkit": "item-checkbox",
|
|
1602
|
+
"data-keep-action": "select-item",
|
|
1603
|
+
"data-state": checked ? "checked" : "unchecked",
|
|
1604
|
+
"data-disabled": props.disabled ? "true" : void 0,
|
|
1605
|
+
"aria-label": accessibleLabel,
|
|
1606
|
+
onChange: (event) => onCheckedChange?.(event.currentTarget.checked)
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
function getItemLabel(item) {
|
|
1611
|
+
if (typeof item.meta !== "object" || item.meta === null || !("title" in item.meta)) return void 0;
|
|
1612
|
+
const title = item.meta.title;
|
|
1613
|
+
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/KeepBulkActions.tsx
|
|
1617
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1618
|
+
function KeepBulkActions({
|
|
1619
|
+
query,
|
|
1620
|
+
selectedIds: controlledSelectedIds,
|
|
1621
|
+
defaultSelectedIds = [],
|
|
1622
|
+
onSelectedIdsChange,
|
|
1623
|
+
renderItem,
|
|
1624
|
+
onCompleted,
|
|
1625
|
+
selectionScope: controlledScope,
|
|
1626
|
+
onSelectionScopeChange,
|
|
1627
|
+
render,
|
|
1628
|
+
children,
|
|
1629
|
+
...props
|
|
1630
|
+
}) {
|
|
1631
|
+
const { state, selected, isLoading, labels } = useKeepBulkActions({
|
|
1632
|
+
query,
|
|
1633
|
+
controlledSelectedIds,
|
|
1634
|
+
defaultSelectedIds,
|
|
1635
|
+
onSelectedIdsChange,
|
|
1636
|
+
onCompleted,
|
|
1637
|
+
controlledScope,
|
|
1638
|
+
onSelectionScopeChange
|
|
1639
|
+
});
|
|
1574
1640
|
const body = render ? render(state) : typeof children === "function" ? children(state) : children ?? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1575
1641
|
/* @__PURE__ */ jsxs2("fieldset", { children: [
|
|
1576
|
-
/* @__PURE__ */ jsx5("legend", { children:
|
|
1642
|
+
/* @__PURE__ */ jsx5("legend", { children: labels.selectItems }),
|
|
1577
1643
|
/* @__PURE__ */ jsxs2("label", { children: [
|
|
1578
|
-
|
|
1644
|
+
labels.selectionScope,
|
|
1579
1645
|
/* @__PURE__ */ jsxs2(
|
|
1580
1646
|
"select",
|
|
1581
1647
|
{
|
|
1582
|
-
|
|
1583
|
-
|
|
1648
|
+
"data-keep-action": "select-scope",
|
|
1649
|
+
value: state.selectionScope,
|
|
1650
|
+
onChange: (event) => state.setSelectionScope(event.currentTarget.value),
|
|
1584
1651
|
children: [
|
|
1585
|
-
/* @__PURE__ */ jsx5("option", { value: "page", children:
|
|
1586
|
-
/* @__PURE__ */ jsx5("option", { value: "query", children:
|
|
1587
|
-
/* @__PURE__ */ jsx5("option", { value: "all", children:
|
|
1652
|
+
/* @__PURE__ */ jsx5("option", { value: "page", children: labels.currentPage }),
|
|
1653
|
+
/* @__PURE__ */ jsx5("option", { value: "query", children: labels.searchResults }),
|
|
1654
|
+
/* @__PURE__ */ jsx5("option", { value: "all", children: labels.allItems })
|
|
1588
1655
|
]
|
|
1589
1656
|
}
|
|
1590
1657
|
)
|
|
1591
1658
|
] }),
|
|
1592
1659
|
/* @__PURE__ */ jsxs2("label", { children: [
|
|
1593
|
-
/* @__PURE__ */ jsx5(
|
|
1594
|
-
|
|
1660
|
+
/* @__PURE__ */ jsx5(
|
|
1661
|
+
"input",
|
|
1662
|
+
{
|
|
1663
|
+
type: "checkbox",
|
|
1664
|
+
"data-keep-action": "select-all",
|
|
1665
|
+
checked: state.allSelected,
|
|
1666
|
+
onChange: state.toggleAll,
|
|
1667
|
+
"aria-label": labels.selectItems
|
|
1668
|
+
}
|
|
1669
|
+
),
|
|
1670
|
+
state.selectedIds.length,
|
|
1595
1671
|
" ",
|
|
1596
|
-
|
|
1672
|
+
labels.selectedCount
|
|
1597
1673
|
] }),
|
|
1598
|
-
|
|
1674
|
+
state.items.map((item) => /* @__PURE__ */ jsxs2("span", { children: [
|
|
1599
1675
|
/* @__PURE__ */ jsx5(
|
|
1600
1676
|
KeepItemCheckbox,
|
|
1601
1677
|
{
|
|
1602
1678
|
item,
|
|
1603
1679
|
checked: selected.has(item.id),
|
|
1604
|
-
onCheckedChange: () => toggle(item.id)
|
|
1680
|
+
onCheckedChange: () => state.toggle(item.id)
|
|
1605
1681
|
}
|
|
1606
1682
|
),
|
|
1607
1683
|
renderItem ? renderItem(item, selected.has(item.id)) : getMetaTitle(item.meta) ?? item.id
|
|
1608
1684
|
] }, item.id))
|
|
1609
1685
|
] }),
|
|
1610
|
-
/* @__PURE__ */ jsx5(
|
|
1686
|
+
/* @__PURE__ */ jsx5(
|
|
1687
|
+
"button",
|
|
1688
|
+
{
|
|
1689
|
+
type: "button",
|
|
1690
|
+
"data-keep-action": "delete-selected",
|
|
1691
|
+
onClick: () => void state.remove(),
|
|
1692
|
+
disabled: state.selectedIds.length === 0 || state.isMutating,
|
|
1693
|
+
children: labels.deleteSelected
|
|
1694
|
+
}
|
|
1695
|
+
),
|
|
1611
1696
|
/* @__PURE__ */ jsxs2("label", { children: [
|
|
1612
|
-
|
|
1613
|
-
/* @__PURE__ */ jsx5(
|
|
1697
|
+
labels.tags,
|
|
1698
|
+
/* @__PURE__ */ jsx5(
|
|
1699
|
+
"input",
|
|
1700
|
+
{
|
|
1701
|
+
"data-keep-action": "edit-tags",
|
|
1702
|
+
value: state.tagsInput,
|
|
1703
|
+
onChange: (event) => state.setTagsInput(event.currentTarget.value)
|
|
1704
|
+
}
|
|
1705
|
+
)
|
|
1614
1706
|
] }),
|
|
1615
1707
|
/* @__PURE__ */ jsx5(
|
|
1616
1708
|
"button",
|
|
1617
1709
|
{
|
|
1618
1710
|
type: "button",
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1711
|
+
"data-keep-action": "apply-tags",
|
|
1712
|
+
onClick: () => void state.updateTags(),
|
|
1713
|
+
disabled: state.selectedIds.length === 0 || state.isMutating,
|
|
1714
|
+
children: labels.applyTags
|
|
1622
1715
|
}
|
|
1623
1716
|
)
|
|
1624
1717
|
] });
|
|
@@ -1627,9 +1720,9 @@ function KeepBulkActions({
|
|
|
1627
1720
|
{
|
|
1628
1721
|
...props,
|
|
1629
1722
|
"data-keepkit": "bulk-actions",
|
|
1630
|
-
"aria-busy":
|
|
1631
|
-
"data-state": selectedIds.length > 0 ? "selected" : "idle",
|
|
1632
|
-
"data-loading":
|
|
1723
|
+
"aria-busy": state.isMutating || props["aria-busy"],
|
|
1724
|
+
"data-state": state.selectedIds.length > 0 ? "selected" : "idle",
|
|
1725
|
+
"data-loading": isLoading || state.isMutating ? "true" : void 0,
|
|
1633
1726
|
children: body
|
|
1634
1727
|
}
|
|
1635
1728
|
);
|
|
@@ -1637,10 +1730,26 @@ function KeepBulkActions({
|
|
|
1637
1730
|
|
|
1638
1731
|
// src/KeepButton.tsx
|
|
1639
1732
|
import {
|
|
1640
|
-
KeepButton as CoreKeepButton
|
|
1641
|
-
useKeepItem
|
|
1733
|
+
KeepButton as CoreKeepButton
|
|
1642
1734
|
} from "@keepkit/core/react";
|
|
1643
1735
|
import { createElement } from "react";
|
|
1736
|
+
|
|
1737
|
+
// src/hooks/useKeepButton.ts
|
|
1738
|
+
import { useKeepItem } from "@keepkit/core/react";
|
|
1739
|
+
function useKeepButton({ item, labels, icons, children }) {
|
|
1740
|
+
return {
|
|
1741
|
+
buttonState: useKeepItem(item),
|
|
1742
|
+
customStateLabel: labels?.loading !== void 0 || labels?.error !== void 0 || icons !== void 0 && children === void 0,
|
|
1743
|
+
labels: {
|
|
1744
|
+
save: useUiLabel("save", typeof labels?.unsaved === "string" ? labels.unsaved : void 0),
|
|
1745
|
+
saved: useUiLabel("saved", typeof labels?.saved === "string" ? labels.saved : void 0),
|
|
1746
|
+
loading: useUiLabel("loading", typeof labels?.loading === "string" ? labels.loading : void 0),
|
|
1747
|
+
error: useUiLabel("error", typeof labels?.error === "string" ? labels.error : void 0)
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// src/KeepButton.tsx
|
|
1644
1753
|
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1645
1754
|
function KeepButton({
|
|
1646
1755
|
labels,
|
|
@@ -1650,16 +1759,11 @@ function KeepButton({
|
|
|
1650
1759
|
iconClassName,
|
|
1651
1760
|
...props
|
|
1652
1761
|
}) {
|
|
1653
|
-
const
|
|
1654
|
-
const savedLabel = useUiLabel("saved", typeof labels?.saved === "string" ? labels.saved : void 0);
|
|
1655
|
-
const loadingLabel = useUiLabel("loading", typeof labels?.loading === "string" ? labels.loading : void 0);
|
|
1656
|
-
const errorLabel = useUiLabel("error", typeof labels?.error === "string" ? labels.error : void 0);
|
|
1657
|
-
const buttonState = useKeepItem(props.item);
|
|
1658
|
-
const customStateLabel = labels?.loading !== void 0 || labels?.error !== void 0 || icons !== void 0 && props.children === void 0;
|
|
1762
|
+
const view = useKeepButton({ item: props.item, labels, icons, children: props.children });
|
|
1659
1763
|
const getStateContent = (state) => {
|
|
1660
1764
|
if (typeof props.children === "function") return props.children(state);
|
|
1661
1765
|
if (props.children !== void 0) return props.children;
|
|
1662
|
-
const label = state.error ? labels?.error ??
|
|
1766
|
+
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
1767
|
if (!icons) return label;
|
|
1664
1768
|
const icon = state.error ? icons.error : state.isMutating ? icons.loading : state.isSaved ? icons.remove ?? icons.saved : icons.save;
|
|
1665
1769
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
@@ -1670,14 +1774,16 @@ function KeepButton({
|
|
|
1670
1774
|
const sharedProps = {
|
|
1671
1775
|
...props,
|
|
1672
1776
|
"data-keepkit": "button",
|
|
1777
|
+
"data-keep-action": "toggle-save",
|
|
1778
|
+
"data-has-custom-icon": icons ? "true" : void 0,
|
|
1673
1779
|
"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 ??
|
|
1780
|
+
"aria-busy": props["aria-busy"] ?? (view.buttonState.isLoading || view.buttonState.isMutating),
|
|
1781
|
+
savedLabel: labels?.saved ?? props.savedLabel ?? view.labels.saved,
|
|
1782
|
+
unsavedLabel: labels?.unsaved ?? props.unsavedLabel ?? view.labels.save,
|
|
1677
1783
|
savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,
|
|
1678
1784
|
unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
|
|
1679
1785
|
};
|
|
1680
|
-
if (!customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
|
|
1786
|
+
if (!view.customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
|
|
1681
1787
|
return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx6(Fragment2, { children: getStateContent(state) }) });
|
|
1682
1788
|
}
|
|
1683
1789
|
function renderIcon(icon, className) {
|
|
@@ -1686,100 +1792,284 @@ function renderIcon(icon, className) {
|
|
|
1686
1792
|
}
|
|
1687
1793
|
|
|
1688
1794
|
// src/KeepCollection.tsx
|
|
1689
|
-
import { KeepErrorBoundary as KeepErrorBoundary2
|
|
1690
|
-
import { useMemo as useMemo3, useState as useState6 } from "react";
|
|
1795
|
+
import { KeepErrorBoundary as KeepErrorBoundary2 } from "@keepkit/core/react";
|
|
1691
1796
|
|
|
1692
|
-
// src/
|
|
1693
|
-
import {
|
|
1694
|
-
|
|
1695
|
-
useKeepList as useKeepList2
|
|
1696
|
-
} from "@keepkit/core/react";
|
|
1697
|
-
import { isValidElement as isValidElement3 } from "react";
|
|
1797
|
+
// src/hooks/useKeepCollection.ts
|
|
1798
|
+
import { useKeepList as useKeepList2 } from "@keepkit/core/react";
|
|
1799
|
+
import { useMemo as useMemo2, useState as useState3 } from "react";
|
|
1698
1800
|
|
|
1699
|
-
// src/
|
|
1700
|
-
import { useKeepItem as useKeepItem2 } from "@keepkit/core/react";
|
|
1801
|
+
// src/url-sync.tsx
|
|
1701
1802
|
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
|
|
1803
|
+
DEFAULT_KEEP_URL_PARAMS,
|
|
1804
|
+
decodeKeepListQuery,
|
|
1805
|
+
encodeKeepListQuery
|
|
1806
|
+
} from "@keepkit/core/core";
|
|
1807
|
+
import { useEffect, useRef as useRef2 } from "react";
|
|
1808
|
+
function createNextPagesRouterAdapter(router) {
|
|
1809
|
+
const getUrl = () => router.asPath ?? (typeof window === "undefined" ? "/" : window.location.href);
|
|
1810
|
+
return {
|
|
1811
|
+
getUrl,
|
|
1812
|
+
subscribe: router.events ? (listener) => {
|
|
1813
|
+
router.events?.on("routeChangeComplete", listener);
|
|
1814
|
+
return () => router.events?.off("routeChangeComplete", listener);
|
|
1815
|
+
} : void 0,
|
|
1816
|
+
navigate: (url, mode) => {
|
|
1817
|
+
void router[mode](url, void 0, { shallow: true });
|
|
1723
1818
|
}
|
|
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";
|
|
1819
|
+
};
|
|
1749
1820
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
onRemoved,
|
|
1757
|
-
retryLabel,
|
|
1758
|
-
removeLabel,
|
|
1759
|
-
children,
|
|
1760
|
-
className,
|
|
1761
|
-
...props
|
|
1821
|
+
function useKeepUrlSync({
|
|
1822
|
+
enabled = true,
|
|
1823
|
+
query,
|
|
1824
|
+
onQueryChange,
|
|
1825
|
+
options = {},
|
|
1826
|
+
adapter: providedAdapter
|
|
1762
1827
|
}) {
|
|
1763
|
-
const
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
const
|
|
1768
|
-
const
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1828
|
+
const browserAdapterRef = useRef2(getBrowserAdapter());
|
|
1829
|
+
const adapter = providedAdapter ?? browserAdapterRef.current;
|
|
1830
|
+
const onQueryChangeRef = useRef2(onQueryChange);
|
|
1831
|
+
onQueryChangeRef.current = onQueryChange;
|
|
1832
|
+
const skipWriteRef = useRef2(true);
|
|
1833
|
+
const params = options.params;
|
|
1834
|
+
useEffect(() => {
|
|
1835
|
+
if (!enabled) return;
|
|
1836
|
+
const read = () => {
|
|
1837
|
+
const url = adapter.getUrl();
|
|
1838
|
+
const decoded = decodeKeepListQuery(url, { params });
|
|
1839
|
+
skipWriteRef.current = true;
|
|
1840
|
+
onQueryChangeRef.current((previousQuery) => ({
|
|
1841
|
+
...previousQuery,
|
|
1842
|
+
...decoded.search ? { search: decoded.search } : { search: void 0 },
|
|
1843
|
+
...decoded.tags ? { tags: decoded.tags } : { tags: void 0 },
|
|
1844
|
+
...decoded.sort ? { sort: decoded.sort } : {},
|
|
1845
|
+
...decoded.pagination ? { pagination: { ...previousQuery.pagination, ...decoded.pagination } } : {}
|
|
1846
|
+
}));
|
|
1847
|
+
};
|
|
1848
|
+
read();
|
|
1849
|
+
return adapter.subscribe?.(read);
|
|
1850
|
+
}, [adapter, enabled, params]);
|
|
1851
|
+
useEffect(() => {
|
|
1852
|
+
if (!enabled) return;
|
|
1853
|
+
if (skipWriteRef.current) {
|
|
1854
|
+
skipWriteRef.current = false;
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
const currentUrl = new URL(adapter.getUrl(), "http://keepkit.invalid");
|
|
1858
|
+
const urlParams = { ...DEFAULT_KEEP_URL_PARAMS, ...params };
|
|
1859
|
+
for (const key of Object.values(urlParams)) currentUrl.searchParams.delete(key);
|
|
1860
|
+
const nextParams = encodeKeepListQuery(query, { params });
|
|
1861
|
+
nextParams.forEach((value, key) => {
|
|
1862
|
+
currentUrl.searchParams.append(key, value);
|
|
1863
|
+
});
|
|
1864
|
+
const nextUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
|
|
1865
|
+
adapter.navigate(nextUrl, options.history ?? "push");
|
|
1866
|
+
}, [adapter, enabled, options.history, params, query]);
|
|
1867
|
+
}
|
|
1868
|
+
function getBrowserAdapter() {
|
|
1869
|
+
return {
|
|
1870
|
+
getUrl: () => typeof window === "undefined" ? "/" : window.location.href,
|
|
1871
|
+
subscribe: (listener) => {
|
|
1872
|
+
if (typeof window === "undefined") return () => void 0;
|
|
1873
|
+
window.addEventListener("popstate", listener);
|
|
1874
|
+
return () => window.removeEventListener("popstate", listener);
|
|
1875
|
+
},
|
|
1876
|
+
navigate: (url, mode) => {
|
|
1877
|
+
if (typeof window === "undefined") return;
|
|
1878
|
+
window.history[mode === "push" ? "pushState" : "replaceState"]({}, "", url);
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/hooks/useKeepCollection.ts
|
|
1884
|
+
function useKeepCollection({
|
|
1885
|
+
query,
|
|
1886
|
+
pageSize,
|
|
1887
|
+
urlSync,
|
|
1888
|
+
urlAdapter,
|
|
1889
|
+
features
|
|
1890
|
+
}) {
|
|
1891
|
+
const enabled = {
|
|
1892
|
+
search: true,
|
|
1893
|
+
sort: true,
|
|
1894
|
+
pagination: true,
|
|
1895
|
+
tagFilter: false,
|
|
1896
|
+
bulkActions: false,
|
|
1897
|
+
...features
|
|
1898
|
+
};
|
|
1899
|
+
const [searchValue, setSearchValue] = useState3(query.search?.query ?? "");
|
|
1900
|
+
const [sort, setSort] = useState3(query.sort ?? { by: "updatedAt", direction: "desc" });
|
|
1901
|
+
const [tag, setTag] = useState3(query.tags?.[0]);
|
|
1902
|
+
const [page, setPage] = useState3(query.pagination?.page ?? 1);
|
|
1903
|
+
const resolvedPageSize = query.pagination?.pageSize ?? pageSize;
|
|
1904
|
+
const resolvedQuery = useMemo2(
|
|
1905
|
+
() => ({
|
|
1906
|
+
...query,
|
|
1907
|
+
search: enabled.search ? { ...query.search, query: searchValue } : query.search,
|
|
1908
|
+
sort: enabled.sort ? sort : query.sort,
|
|
1909
|
+
tags: tag ? [.../* @__PURE__ */ new Set([...query.tags ?? [], tag])] : query.tags,
|
|
1910
|
+
pagination: enabled.pagination ? { ...query.pagination, page, pageSize: resolvedPageSize } : query.pagination
|
|
1911
|
+
}),
|
|
1912
|
+
[enabled.pagination, enabled.search, enabled.sort, page, query, resolvedPageSize, searchValue, sort, tag]
|
|
1913
|
+
);
|
|
1914
|
+
useKeepUrlSync({
|
|
1915
|
+
enabled: Boolean(urlSync),
|
|
1916
|
+
query: resolvedQuery,
|
|
1917
|
+
onQueryChange: (nextOrUpdater) => {
|
|
1918
|
+
const next = typeof nextOrUpdater === "function" ? nextOrUpdater(resolvedQuery) : nextOrUpdater;
|
|
1919
|
+
setSearchValue(next.search?.query ?? "");
|
|
1920
|
+
setSort(next.sort ?? { by: "updatedAt", direction: "desc" });
|
|
1921
|
+
setTag(next.tags?.[0]);
|
|
1922
|
+
setPage(next.pagination?.page ?? 1);
|
|
1923
|
+
},
|
|
1924
|
+
options: typeof urlSync === "object" ? urlSync : {},
|
|
1925
|
+
adapter: urlAdapter
|
|
1926
|
+
});
|
|
1927
|
+
const list = useKeepList2(resolvedQuery);
|
|
1928
|
+
return {
|
|
1929
|
+
enabled,
|
|
1930
|
+
searchValue,
|
|
1931
|
+
sortValue: sortToValue(sort),
|
|
1932
|
+
tag,
|
|
1933
|
+
resolvedPageSize,
|
|
1934
|
+
resolvedQuery,
|
|
1935
|
+
list,
|
|
1936
|
+
setSearchValue: (value) => {
|
|
1937
|
+
setSearchValue(value);
|
|
1938
|
+
setPage(1);
|
|
1939
|
+
},
|
|
1940
|
+
setSortValue: (_value, nextSort) => {
|
|
1941
|
+
setSort(nextSort);
|
|
1942
|
+
setPage(1);
|
|
1943
|
+
},
|
|
1944
|
+
setTag: (value) => {
|
|
1945
|
+
setTag(value);
|
|
1946
|
+
setPage(1);
|
|
1947
|
+
},
|
|
1948
|
+
setPage
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// src/KeepList.tsx
|
|
1953
|
+
import { KeepErrorBoundary } from "@keepkit/core/react";
|
|
1954
|
+
import { isValidElement as isValidElement3 } from "react";
|
|
1955
|
+
|
|
1956
|
+
// src/hooks/useKeepListView.ts
|
|
1957
|
+
import { useKeepList as useKeepList3 } from "@keepkit/core/react";
|
|
1958
|
+
function useKeepListView(query) {
|
|
1959
|
+
return {
|
|
1960
|
+
state: useKeepList3(query),
|
|
1961
|
+
labels: {
|
|
1962
|
+
loading: useUiLabel("loadingItems"),
|
|
1963
|
+
empty: useUiLabel("noItems"),
|
|
1964
|
+
error: useUiLabel("errorItems")
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// src/KeepItemCard.tsx
|
|
1970
|
+
import {
|
|
1971
|
+
isValidElement as isValidElement2
|
|
1972
|
+
} from "react";
|
|
1973
|
+
|
|
1974
|
+
// src/hooks/useKeepItemCard.ts
|
|
1975
|
+
import { useKeepItem as useKeepItem2 } from "@keepkit/core/react";
|
|
1976
|
+
function useKeepItemCard(options) {
|
|
1977
|
+
const {
|
|
1978
|
+
item,
|
|
1979
|
+
title,
|
|
1980
|
+
getTitle,
|
|
1981
|
+
getImageProps,
|
|
1982
|
+
href: hrefOption,
|
|
1983
|
+
linkTargetAttribute,
|
|
1984
|
+
linkRel,
|
|
1985
|
+
onRemoveError,
|
|
1986
|
+
onRemoved
|
|
1987
|
+
} = options;
|
|
1988
|
+
const itemState = useKeepItem2(item);
|
|
1989
|
+
const resolvedTitle = typeof title === "function" ? title(item) : title ?? getTitle?.(item) ?? getMetaTitle(item.meta) ?? item.id;
|
|
1990
|
+
const imageProps = getImageProps?.(item, resolvedTitle);
|
|
1991
|
+
const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
|
|
1992
|
+
const isAvailable = item.status === void 0 || item.status === "available";
|
|
1993
|
+
const isExternalLink = href ? /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href) : false;
|
|
1994
|
+
const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey(item.status) : "statusUnknown";
|
|
1995
|
+
const unavailableLabel = useUiLabel(statusLabelKey);
|
|
1996
|
+
const state = {
|
|
1997
|
+
item,
|
|
1998
|
+
isSaved: itemState.isSaved,
|
|
1999
|
+
isMutating: itemState.isMutating,
|
|
2000
|
+
error: itemState.error,
|
|
2001
|
+
remove: itemState.remove,
|
|
2002
|
+
status: item.status
|
|
2003
|
+
};
|
|
2004
|
+
return {
|
|
2005
|
+
itemState,
|
|
2006
|
+
state,
|
|
2007
|
+
resolvedTitle,
|
|
2008
|
+
imageProps,
|
|
2009
|
+
href,
|
|
2010
|
+
isAvailable,
|
|
2011
|
+
displayStatus: getDisplayStatus(item.status),
|
|
2012
|
+
resolvedLinkTarget: linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0),
|
|
2013
|
+
resolvedLinkRel: linkRel ?? (isExternalLink ? "noreferrer" : void 0),
|
|
2014
|
+
statusLabel: item.status && item.status !== "available" ? unavailableLabel : void 0,
|
|
2015
|
+
remove: async () => {
|
|
2016
|
+
try {
|
|
2017
|
+
await itemState.remove();
|
|
2018
|
+
onRemoved?.(item);
|
|
2019
|
+
} catch (cause) {
|
|
2020
|
+
onRemoveError?.(cause);
|
|
2021
|
+
}
|
|
2022
|
+
},
|
|
2023
|
+
labels: {
|
|
2024
|
+
save: useUiLabel("save"),
|
|
2025
|
+
savedAt: useUiLabel("saved"),
|
|
2026
|
+
error: useUiLabel("error"),
|
|
2027
|
+
remove: useUiLabel("remove"),
|
|
2028
|
+
tags: useUiLabel("tags")
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
function getStatusLabelKey(status) {
|
|
2033
|
+
switch (status) {
|
|
2034
|
+
case "expired":
|
|
2035
|
+
return "statusExpired";
|
|
2036
|
+
case "removed":
|
|
2037
|
+
return "statusRemoved";
|
|
2038
|
+
case "deleted":
|
|
2039
|
+
return "statusDeleted";
|
|
2040
|
+
case "private":
|
|
2041
|
+
return "statusPrivate";
|
|
2042
|
+
default:
|
|
2043
|
+
return "statusUnknown";
|
|
1781
2044
|
}
|
|
1782
|
-
|
|
2045
|
+
}
|
|
2046
|
+
function getDisplayStatus(status) {
|
|
2047
|
+
if (status === void 0 || status === "available") return "available";
|
|
2048
|
+
if (status === "expired") return "expired";
|
|
2049
|
+
if (status === "removed") return "removed";
|
|
2050
|
+
return "restricted";
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
// src/hooks/useKeepStaleNotice.ts
|
|
2054
|
+
import { useKeepContext as useKeepContext2 } from "@keepkit/core/react";
|
|
2055
|
+
import { useState as useState4 } from "react";
|
|
2056
|
+
function useKeepStaleNotice({ item, onRetry, onRemoved }) {
|
|
2057
|
+
const context = useKeepContext2();
|
|
2058
|
+
const [isRetrying, setIsRetrying] = useState4(false);
|
|
2059
|
+
const [error, setError] = useState4(null);
|
|
2060
|
+
async function retry() {
|
|
2061
|
+
setError(null);
|
|
2062
|
+
setIsRetrying(true);
|
|
2063
|
+
try {
|
|
2064
|
+
if (onRetry) await onRetry(item);
|
|
2065
|
+
else await context.revalidateItems();
|
|
2066
|
+
} catch (cause) {
|
|
2067
|
+
setError(cause);
|
|
2068
|
+
} finally {
|
|
2069
|
+
setIsRetrying(false);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
async function remove() {
|
|
1783
2073
|
try {
|
|
1784
2074
|
await context.removeItemWithUndo(item.id);
|
|
1785
2075
|
onRemoved?.(item);
|
|
@@ -1787,14 +2077,120 @@ function KeepStaleNotice({
|
|
|
1787
2077
|
setError(cause);
|
|
1788
2078
|
}
|
|
1789
2079
|
}
|
|
1790
|
-
return
|
|
1791
|
-
|
|
2080
|
+
return {
|
|
2081
|
+
status: item.status && item.status !== "available" ? item.status : "unknown",
|
|
2082
|
+
error,
|
|
2083
|
+
isRetrying,
|
|
2084
|
+
isMutating: context.isMutating,
|
|
2085
|
+
retry,
|
|
2086
|
+
remove,
|
|
2087
|
+
labels: {
|
|
2088
|
+
retry: useUiLabel("retry"),
|
|
2089
|
+
remove: useUiLabel("removeFromList"),
|
|
2090
|
+
error: useUiLabel("error")
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
function useKeepPruneStale({ statuses, onPruned }) {
|
|
2095
|
+
const context = useKeepContext2();
|
|
2096
|
+
const staleIds = context.items.filter((item) => item.status && statuses.includes(item.status)).map((item) => item.id);
|
|
2097
|
+
return {
|
|
2098
|
+
staleIds,
|
|
2099
|
+
isMutating: context.isMutating,
|
|
2100
|
+
label: useUiLabel("pruneStale"),
|
|
2101
|
+
prune: async () => {
|
|
2102
|
+
const ids = [...staleIds];
|
|
2103
|
+
await context.removeItemsWithUndo(ids);
|
|
2104
|
+
onPruned?.(ids);
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/hooks/useKeepItemStatusBadge.ts
|
|
2110
|
+
function useKeepItemStatusBadge(status) {
|
|
2111
|
+
return { resolvedStatus: getDisplayStatus2(status), statusLabel: useUiLabel(getStatusLabelKey2(status)) };
|
|
2112
|
+
}
|
|
2113
|
+
function getStatusLabelKey2(status) {
|
|
2114
|
+
switch (status) {
|
|
2115
|
+
case "available":
|
|
2116
|
+
return "statusAvailable";
|
|
2117
|
+
case "expired":
|
|
2118
|
+
return "statusExpired";
|
|
2119
|
+
case "removed":
|
|
2120
|
+
return "statusRemoved";
|
|
2121
|
+
case "deleted":
|
|
2122
|
+
return "statusDeleted";
|
|
2123
|
+
case "private":
|
|
2124
|
+
return "statusPrivate";
|
|
2125
|
+
case "unknown":
|
|
2126
|
+
return "statusUnknown";
|
|
2127
|
+
case "restricted":
|
|
2128
|
+
return "statusPrivate";
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
function getDisplayStatus2(status) {
|
|
2132
|
+
if (status === "available") return "available";
|
|
2133
|
+
if (status === "expired") return "expired";
|
|
2134
|
+
if (status === "removed") return "removed";
|
|
2135
|
+
return "restricted";
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/KeepItemStatusBadge.tsx
|
|
2139
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2140
|
+
function KeepItemStatusBadge({ status = "available", label, className, ...props }) {
|
|
2141
|
+
const view = useKeepItemStatusBadge(status);
|
|
2142
|
+
return /* @__PURE__ */ jsx7(
|
|
2143
|
+
"span",
|
|
2144
|
+
{
|
|
2145
|
+
...props,
|
|
2146
|
+
className,
|
|
2147
|
+
"data-keepkit": "status-badge",
|
|
2148
|
+
"data-status": status,
|
|
2149
|
+
"data-item-status": view.resolvedStatus,
|
|
2150
|
+
children: label ?? view.statusLabel
|
|
2151
|
+
}
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// src/KeepStaleNotice.tsx
|
|
2156
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2157
|
+
function KeepStaleNotice({
|
|
2158
|
+
item,
|
|
2159
|
+
onRetry,
|
|
2160
|
+
onRemoved,
|
|
2161
|
+
retryLabel,
|
|
2162
|
+
removeLabel,
|
|
2163
|
+
children,
|
|
2164
|
+
className,
|
|
2165
|
+
...props
|
|
2166
|
+
}) {
|
|
2167
|
+
const view = useKeepStaleNotice({ item, onRetry, onRemoved });
|
|
2168
|
+
return /* @__PURE__ */ jsxs4("aside", { ...props, className, "data-keepkit": "stale-notice", "data-state": view.error ? "error" : "stale", children: [
|
|
2169
|
+
/* @__PURE__ */ jsx8(KeepItemStatusBadge, { status: view.status }),
|
|
1792
2170
|
children ?? (item.statusReason ? /* @__PURE__ */ jsx8("p", { children: item.statusReason }) : null),
|
|
1793
2171
|
/* @__PURE__ */ jsxs4("div", { children: [
|
|
1794
|
-
/* @__PURE__ */ jsx8(
|
|
1795
|
-
|
|
2172
|
+
/* @__PURE__ */ jsx8(
|
|
2173
|
+
"button",
|
|
2174
|
+
{
|
|
2175
|
+
type: "button",
|
|
2176
|
+
"data-keep-action": "retry-item",
|
|
2177
|
+
onClick: () => void view.retry(),
|
|
2178
|
+
disabled: view.isRetrying || view.isMutating,
|
|
2179
|
+
children: retryLabel ?? view.labels.retry
|
|
2180
|
+
}
|
|
2181
|
+
),
|
|
2182
|
+
/* @__PURE__ */ jsx8(
|
|
2183
|
+
"button",
|
|
2184
|
+
{
|
|
2185
|
+
type: "button",
|
|
2186
|
+
"data-keep-action": "remove-item",
|
|
2187
|
+
onClick: () => void view.remove(),
|
|
2188
|
+
disabled: view.isMutating,
|
|
2189
|
+
children: removeLabel ?? view.labels.remove
|
|
2190
|
+
}
|
|
2191
|
+
)
|
|
1796
2192
|
] }),
|
|
1797
|
-
error ? /* @__PURE__ */ jsx8("p", { role: "alert", children: error instanceof Error ? error.message :
|
|
2193
|
+
view.error ? /* @__PURE__ */ jsx8("p", { role: "alert", children: view.error instanceof Error ? view.error.message : view.labels.error }) : null
|
|
1798
2194
|
] });
|
|
1799
2195
|
}
|
|
1800
2196
|
function KeepPruneStaleButton({
|
|
@@ -1805,14 +2201,7 @@ function KeepPruneStaleButton({
|
|
|
1805
2201
|
disabled,
|
|
1806
2202
|
...props
|
|
1807
2203
|
}) {
|
|
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
|
-
}
|
|
2204
|
+
const view = useKeepPruneStale({ statuses, onPruned });
|
|
1816
2205
|
return /* @__PURE__ */ jsx8(
|
|
1817
2206
|
"button",
|
|
1818
2207
|
{
|
|
@@ -1820,10 +2209,11 @@ function KeepPruneStaleButton({
|
|
|
1820
2209
|
type: "button",
|
|
1821
2210
|
className: props.className,
|
|
1822
2211
|
"data-keepkit": "prune-stale",
|
|
1823
|
-
"data-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2212
|
+
"data-keep-action": "prune-stale",
|
|
2213
|
+
"data-state": view.staleIds.length > 0 ? "available" : "empty",
|
|
2214
|
+
disabled: view.staleIds.length === 0 || view.isMutating || disabled,
|
|
2215
|
+
onClick: () => void view.prune(),
|
|
2216
|
+
children: children ?? label ?? `${view.label} (${view.staleIds.length})`
|
|
1827
2217
|
}
|
|
1828
2218
|
);
|
|
1829
2219
|
}
|
|
@@ -1859,76 +2249,63 @@ function KeepItemCard({
|
|
|
1859
2249
|
className,
|
|
1860
2250
|
...rootProps
|
|
1861
2251
|
}) {
|
|
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 = {
|
|
2252
|
+
const view = useKeepItemCard({
|
|
1869
2253
|
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;
|
|
2254
|
+
title,
|
|
2255
|
+
getTitle,
|
|
2256
|
+
getImageProps,
|
|
2257
|
+
href: hrefOption,
|
|
2258
|
+
linkTargetAttribute,
|
|
2259
|
+
linkRel,
|
|
2260
|
+
onRemoveError,
|
|
2261
|
+
onRemoved
|
|
2262
|
+
});
|
|
2263
|
+
const contentChildren = asChild && isValidElement2(children) ? void 0 : children;
|
|
1888
2264
|
function renderLink(content) {
|
|
1889
|
-
if (!href || !isAvailable) return content;
|
|
2265
|
+
if (!view.href || !view.isAvailable) return content;
|
|
1890
2266
|
const linkProps = {
|
|
1891
|
-
href,
|
|
1892
|
-
target: resolvedLinkTarget,
|
|
1893
|
-
rel: resolvedLinkRel,
|
|
2267
|
+
href: view.href,
|
|
2268
|
+
target: view.resolvedLinkTarget,
|
|
2269
|
+
rel: view.resolvedLinkRel,
|
|
1894
2270
|
onClick: (event) => onOpen?.(item, event),
|
|
1895
2271
|
children: content
|
|
1896
2272
|
};
|
|
1897
2273
|
return LinkComponent ? /* @__PURE__ */ jsx9(LinkComponent, { ...linkProps }) : /* @__PURE__ */ jsx9("a", { ...linkProps });
|
|
1898
2274
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
onRemoved?.(item);
|
|
1903
|
-
} catch (error) {
|
|
1904
|
-
onRemoveError?.(error);
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1908
|
-
imageProps ? renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx9(ImageComponent, { ...imageProps, alt: imageAlt ?? imageProps.alt }) : /* @__PURE__ */ jsx9("img", { ...imageProps, alt: imageAlt ?? imageProps.alt })) : null,
|
|
1909
|
-
/* @__PURE__ */ jsx9("h3", { children: linkTarget === "title" ? isAvailable ? renderLink(resolvedTitle) : href ? /* @__PURE__ */ jsx9("span", { "aria-disabled": "true", "data-link-disabled": "true", children: resolvedTitle }) : resolvedTitle : resolvedTitle }),
|
|
2275
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2276
|
+
view.imageProps ? renderImage?.({ ...view.imageProps, alt: imageAlt ?? view.imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx9(ImageComponent, { ...view.imageProps, alt: imageAlt ?? view.imageProps.alt }) : /* @__PURE__ */ jsx9("img", { ...view.imageProps, alt: imageAlt ?? view.imageProps.alt })) : null,
|
|
2277
|
+
/* @__PURE__ */ jsx9("h3", { children: linkTarget === "title" ? view.isAvailable ? renderLink(view.resolvedTitle) : view.href ? /* @__PURE__ */ jsx9("span", { "aria-disabled": "true", "data-link-disabled": "true", children: view.resolvedTitle }) : view.resolvedTitle : view.resolvedTitle }),
|
|
1910
2278
|
showSavedAt ? /* @__PURE__ */ jsxs5("div", { "data-card-meta": true, children: [
|
|
1911
2279
|
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1912
|
-
|
|
2280
|
+
view.labels.savedAt,
|
|
1913
2281
|
":"
|
|
1914
2282
|
] }),
|
|
1915
2283
|
" ",
|
|
1916
2284
|
/* @__PURE__ */ jsx9("time", { dateTime: new Date(item.savedAt).toISOString(), children: formatSavedAt(item.savedAt) })
|
|
1917
2285
|
] }) : null,
|
|
1918
|
-
showTags && (item.tags?.length ?? 0) > 0 ? renderTags ? renderTags(item.tags ?? [], item) : /* @__PURE__ */ jsx9("ul", { "aria-label":
|
|
1919
|
-
itemState.error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: getErrorMessage2(itemState.error,
|
|
1920
|
-
statusLabel ? /* @__PURE__ */ jsx9(KeepStaleNotice, { item, onRetry, onRemoved }) : null,
|
|
1921
|
-
showSaveButton && !statusLabel ? /* @__PURE__ */ jsx9(
|
|
2286
|
+
showTags && (item.tags?.length ?? 0) > 0 ? renderTags ? renderTags(item.tags ?? [], item) : /* @__PURE__ */ jsx9("ul", { "aria-label": view.labels.tags, children: item.tags?.map((tag) => /* @__PURE__ */ jsx9("li", { children: tag }, tag)) }) : null,
|
|
2287
|
+
view.itemState.error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: getErrorMessage2(view.itemState.error, view.labels.error) }) : null,
|
|
2288
|
+
view.statusLabel ? /* @__PURE__ */ jsx9(KeepStaleNotice, { item, onRetry, onRemoved }) : null,
|
|
2289
|
+
showSaveButton && !view.statusLabel ? /* @__PURE__ */ jsx9(
|
|
1922
2290
|
KeepButton,
|
|
1923
2291
|
{
|
|
1924
2292
|
item: toKeepButtonItem(item),
|
|
1925
2293
|
labels: saveButtonLabels,
|
|
1926
|
-
getAriaLabel: (buttonState) => `${buttonState.isSaved ?
|
|
2294
|
+
getAriaLabel: (buttonState) => `${buttonState.isSaved ? view.labels.remove : view.labels.save} ${String(view.resolvedTitle)}`
|
|
1927
2295
|
}
|
|
1928
2296
|
) : null,
|
|
1929
|
-
!statusLabel ? /* @__PURE__ */ jsx9(
|
|
2297
|
+
!view.statusLabel ? /* @__PURE__ */ jsx9(
|
|
2298
|
+
"button",
|
|
2299
|
+
{
|
|
2300
|
+
type: "button",
|
|
2301
|
+
"data-keep-action": "remove-item",
|
|
2302
|
+
onClick: () => void view.remove(),
|
|
2303
|
+
disabled: view.itemState.isMutating,
|
|
2304
|
+
children: removeLabel ?? view.labels.remove
|
|
2305
|
+
}
|
|
2306
|
+
) : null
|
|
1930
2307
|
] });
|
|
1931
|
-
const linkedBody = linkTarget === "card" && isAvailable ? renderLink(body) : body;
|
|
2308
|
+
const linkedBody = linkTarget === "card" && view.isAvailable ? renderLink(body) : body;
|
|
1932
2309
|
return renderRoot(
|
|
1933
2310
|
asChild,
|
|
1934
2311
|
isValidElement2(children) ? children : void 0,
|
|
@@ -1936,40 +2313,17 @@ function KeepItemCard({
|
|
|
1936
2313
|
...rootProps,
|
|
1937
2314
|
className,
|
|
1938
2315
|
"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",
|
|
2316
|
+
"aria-busy": view.itemState.isMutating || rootProps["aria-busy"],
|
|
2317
|
+
"aria-disabled": rootProps["aria-disabled"] ?? (!view.isAvailable ? "true" : void 0),
|
|
2318
|
+
"data-state": view.itemState.error ? "error" : view.itemState.isSaved ? "saved" : "unsaved",
|
|
1942
2319
|
"data-status": item.status ?? "available",
|
|
1943
|
-
"data-item-status": displayStatus,
|
|
1944
|
-
"data-loading": itemState.isMutating ? "true" : void 0
|
|
2320
|
+
"data-item-status": view.displayStatus,
|
|
2321
|
+
"data-loading": view.itemState.isMutating ? "true" : void 0
|
|
1945
2322
|
},
|
|
1946
2323
|
linkedBody,
|
|
1947
2324
|
"KeepItemCard"
|
|
1948
2325
|
);
|
|
1949
2326
|
}
|
|
1950
|
-
function getStatusLabelKey2(status) {
|
|
1951
|
-
switch (status) {
|
|
1952
|
-
case "expired":
|
|
1953
|
-
return "statusExpired";
|
|
1954
|
-
case "removed":
|
|
1955
|
-
return "statusRemoved";
|
|
1956
|
-
case "deleted":
|
|
1957
|
-
return "statusDeleted";
|
|
1958
|
-
case "private":
|
|
1959
|
-
return "statusPrivate";
|
|
1960
|
-
default:
|
|
1961
|
-
return "statusUnknown";
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
function getDisplayStatus2(status) {
|
|
1965
|
-
if (status === void 0 || status === "available") return "available";
|
|
1966
|
-
if (status === "expired") return "expired";
|
|
1967
|
-
if (status === "removed") return "removed";
|
|
1968
|
-
return "restricted";
|
|
1969
|
-
}
|
|
1970
|
-
function isExternalHref(href) {
|
|
1971
|
-
return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href);
|
|
1972
|
-
}
|
|
1973
2327
|
function formatSavedAt(timestamp) {
|
|
1974
2328
|
return new Date(timestamp).toISOString().slice(0, 10);
|
|
1975
2329
|
}
|
|
@@ -1998,16 +2352,14 @@ function KeepListContent({
|
|
|
1998
2352
|
className,
|
|
1999
2353
|
...rootProps
|
|
2000
2354
|
}) {
|
|
2001
|
-
const
|
|
2002
|
-
const
|
|
2003
|
-
const defaultError = useUiLabel("errorItems");
|
|
2004
|
-
const state = useKeepList2(query);
|
|
2355
|
+
const view = useKeepListView(query);
|
|
2356
|
+
const { state } = view;
|
|
2005
2357
|
const body = getListBody(state, {
|
|
2006
2358
|
children,
|
|
2007
2359
|
renderItem,
|
|
2008
|
-
loading: loading ??
|
|
2009
|
-
empty: empty ??
|
|
2010
|
-
error: errorContent ??
|
|
2360
|
+
loading: loading ?? view.labels.loading,
|
|
2361
|
+
empty: empty ?? view.labels.empty,
|
|
2362
|
+
error: errorContent ?? view.labels.error,
|
|
2011
2363
|
itemCardProps,
|
|
2012
2364
|
layout
|
|
2013
2365
|
});
|
|
@@ -2045,8 +2397,39 @@ function getListBody(state, options) {
|
|
|
2045
2397
|
}
|
|
2046
2398
|
|
|
2047
2399
|
// src/KeepTagFilter.tsx
|
|
2048
|
-
import {
|
|
2049
|
-
|
|
2400
|
+
import { isValidElement as isValidElement4 } from "react";
|
|
2401
|
+
|
|
2402
|
+
// src/hooks/useKeepTagFilter.ts
|
|
2403
|
+
import { useKeepList as useKeepList4 } from "@keepkit/core/react";
|
|
2404
|
+
import { useCallback, useMemo as useMemo3, useState as useState5 } from "react";
|
|
2405
|
+
function useKeepTagFilter(options) {
|
|
2406
|
+
const { query, controlledValue, defaultValue, onChange, onValueChange } = options;
|
|
2407
|
+
const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
|
|
2408
|
+
const resolvedValue = controlledValue ?? uncontrolledValue;
|
|
2409
|
+
const list = useKeepList4({
|
|
2410
|
+
...query,
|
|
2411
|
+
tags: resolvedValue ? [...query?.tags ?? [], resolvedValue] : query?.tags
|
|
2412
|
+
});
|
|
2413
|
+
const select = useCallback(
|
|
2414
|
+
(tag) => {
|
|
2415
|
+
if (controlledValue === void 0) setUncontrolledValue(tag);
|
|
2416
|
+
onChange?.(tag);
|
|
2417
|
+
onValueChange?.(tag);
|
|
2418
|
+
},
|
|
2419
|
+
[controlledValue, onChange, onValueChange]
|
|
2420
|
+
);
|
|
2421
|
+
const state = useMemo3(
|
|
2422
|
+
() => ({ tags: list.tags, tagCounts: list.tagCounts, value: resolvedValue, select }),
|
|
2423
|
+
[list.tagCounts, list.tags, resolvedValue, select]
|
|
2424
|
+
);
|
|
2425
|
+
return {
|
|
2426
|
+
state,
|
|
2427
|
+
isLoading: list.isLoading,
|
|
2428
|
+
labels: { all: useUiLabel("allTags"), aria: useUiLabel("filterTags") }
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// src/KeepTagFilter.tsx
|
|
2050
2433
|
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2051
2434
|
function KeepTagFilter({
|
|
2052
2435
|
query,
|
|
@@ -2063,38 +2446,38 @@ function KeepTagFilter({
|
|
|
2063
2446
|
className,
|
|
2064
2447
|
...rootProps
|
|
2065
2448
|
}) {
|
|
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
|
-
);
|
|
2449
|
+
const view = useKeepTagFilter({ query, controlledValue, defaultValue, onChange, onValueChange });
|
|
2086
2450
|
const contentChildren = asChild && isValidElement4(children) ? void 0 : children;
|
|
2087
|
-
const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs6("fieldset", { children: [
|
|
2088
|
-
/* @__PURE__ */ jsx11("legend", { children: ariaLabel ??
|
|
2089
|
-
/* @__PURE__ */ jsx11(
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
"
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2451
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs6("fieldset", { children: [
|
|
2452
|
+
/* @__PURE__ */ jsx11("legend", { children: ariaLabel ?? view.labels.aria }),
|
|
2453
|
+
/* @__PURE__ */ jsx11(
|
|
2454
|
+
"button",
|
|
2455
|
+
{
|
|
2456
|
+
type: "button",
|
|
2457
|
+
"data-keep-action": "filter-all-tags",
|
|
2458
|
+
"aria-pressed": view.state.value === void 0,
|
|
2459
|
+
onClick: () => view.state.select(),
|
|
2460
|
+
children: allLabel ?? view.labels.all
|
|
2461
|
+
}
|
|
2462
|
+
),
|
|
2463
|
+
view.state.tags.map((tag) => /* @__PURE__ */ jsxs6(
|
|
2464
|
+
"button",
|
|
2465
|
+
{
|
|
2466
|
+
type: "button",
|
|
2467
|
+
"data-keep-action": "filter-tag",
|
|
2468
|
+
"aria-pressed": view.state.value === tag,
|
|
2469
|
+
onClick: () => view.state.select(tag),
|
|
2470
|
+
children: [
|
|
2471
|
+
renderTag ? renderTag(tag, view.state.tagCounts[tag] ?? 0, view.state.value === tag) : tag,
|
|
2472
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
2473
|
+
" (",
|
|
2474
|
+
view.state.tagCounts[tag] ?? 0,
|
|
2475
|
+
")"
|
|
2476
|
+
] })
|
|
2477
|
+
]
|
|
2478
|
+
},
|
|
2479
|
+
tag
|
|
2480
|
+
))
|
|
2098
2481
|
] });
|
|
2099
2482
|
return renderRoot(
|
|
2100
2483
|
asChild,
|
|
@@ -2103,16 +2486,87 @@ function KeepTagFilter({
|
|
|
2103
2486
|
...rootProps,
|
|
2104
2487
|
className,
|
|
2105
2488
|
"data-keepkit": "tag-filter",
|
|
2106
|
-
"data-state":
|
|
2107
|
-
"data-loading":
|
|
2489
|
+
"data-state": view.state.value === void 0 ? "all" : "filtered",
|
|
2490
|
+
"data-loading": view.isLoading ? "true" : void 0
|
|
2108
2491
|
},
|
|
2109
2492
|
body,
|
|
2110
2493
|
"KeepTagFilter"
|
|
2111
2494
|
);
|
|
2112
2495
|
}
|
|
2113
2496
|
|
|
2497
|
+
// src/hooks/useQueryControls.ts
|
|
2498
|
+
import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
2499
|
+
function useKeepSearchInput(options) {
|
|
2500
|
+
const { controlledValue, defaultValue, debounceMs, onValueChange } = options;
|
|
2501
|
+
const [uncontrolledValue, setUncontrolledValue] = useState6(defaultValue);
|
|
2502
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
2503
|
+
useEffect2(() => {
|
|
2504
|
+
if (!onValueChange) return;
|
|
2505
|
+
if (debounceMs <= 0) {
|
|
2506
|
+
onValueChange(value);
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
const timer = window.setTimeout(() => onValueChange(value), debounceMs);
|
|
2510
|
+
return () => window.clearTimeout(timer);
|
|
2511
|
+
}, [debounceMs, onValueChange, value]);
|
|
2512
|
+
return {
|
|
2513
|
+
value,
|
|
2514
|
+
label: useUiLabel("search"),
|
|
2515
|
+
change: (event) => {
|
|
2516
|
+
if (controlledValue === void 0) setUncontrolledValue(event.currentTarget.value);
|
|
2517
|
+
}
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
function useKeepSortSelect(options) {
|
|
2521
|
+
const { controlledValue, defaultValue, onValueChange } = options;
|
|
2522
|
+
const [uncontrolledValue, setUncontrolledValue] = useState6(defaultValue);
|
|
2523
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
2524
|
+
return {
|
|
2525
|
+
value,
|
|
2526
|
+
change: (event) => {
|
|
2527
|
+
const nextValue = event.currentTarget.value;
|
|
2528
|
+
if (controlledValue === void 0) setUncontrolledValue(nextValue);
|
|
2529
|
+
const [by, direction] = nextValue.split(":");
|
|
2530
|
+
onValueChange?.(nextValue, { by, direction });
|
|
2531
|
+
},
|
|
2532
|
+
labels: {
|
|
2533
|
+
sort: useUiLabel("sort"),
|
|
2534
|
+
updatedNewest: useUiLabel("updatedNewest"),
|
|
2535
|
+
updatedOldest: useUiLabel("updatedOldest"),
|
|
2536
|
+
savedNewest: useUiLabel("savedNewest"),
|
|
2537
|
+
savedOldest: useUiLabel("savedOldest")
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
function useKeepPagination(options) {
|
|
2542
|
+
const { totalCount, pageSize, page, maxPageButtons, onPageChange } = options;
|
|
2543
|
+
const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
|
|
2544
|
+
const currentPage = Math.min(Math.max(1, page), pageCount);
|
|
2545
|
+
const goToPage = (nextPage) => {
|
|
2546
|
+
const next = Math.min(Math.max(1, nextPage), pageCount);
|
|
2547
|
+
onPageChange?.(next, (next - 1) * pageSize);
|
|
2548
|
+
};
|
|
2549
|
+
return {
|
|
2550
|
+
pageCount,
|
|
2551
|
+
currentPage,
|
|
2552
|
+
goToPage,
|
|
2553
|
+
visiblePages: getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons)),
|
|
2554
|
+
labels: {
|
|
2555
|
+
previous: useUiLabel("previousPage"),
|
|
2556
|
+
next: useUiLabel("nextPage"),
|
|
2557
|
+
page: useUiLabel("page"),
|
|
2558
|
+
pagination: useUiLabel("pagination")
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
function getVisiblePages(currentPage, pageCount, maxPageButtons) {
|
|
2563
|
+
if (pageCount <= maxPageButtons) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
2564
|
+
const half = Math.floor(maxPageButtons / 2);
|
|
2565
|
+
const start = Math.min(Math.max(1, currentPage - half), pageCount - maxPageButtons + 1);
|
|
2566
|
+
return Array.from({ length: maxPageButtons }, (_, index) => start + index);
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2114
2569
|
// src/query-controls.tsx
|
|
2115
|
-
import { useEffect, useState as useState5 } from "react";
|
|
2116
2570
|
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2117
2571
|
function KeepSearchInput({
|
|
2118
2572
|
value: controlledValue,
|
|
@@ -2123,33 +2577,20 @@ function KeepSearchInput({
|
|
|
2123
2577
|
placeholder,
|
|
2124
2578
|
...props
|
|
2125
2579
|
}) {
|
|
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]);
|
|
2580
|
+
const view = useKeepSearchInput({ controlledValue, defaultValue, debounceMs, onValueChange });
|
|
2138
2581
|
return /* @__PURE__ */ jsx12(
|
|
2139
2582
|
"input",
|
|
2140
2583
|
{
|
|
2141
2584
|
...props,
|
|
2142
2585
|
"data-keepkit": "search-input",
|
|
2586
|
+
"data-keep-action": "search",
|
|
2143
2587
|
type: "search",
|
|
2144
|
-
value,
|
|
2145
|
-
"data-state": value ? "active" : "idle",
|
|
2588
|
+
value: view.value,
|
|
2589
|
+
"data-state": view.value ? "active" : "idle",
|
|
2146
2590
|
"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
|
-
}
|
|
2591
|
+
"aria-label": ariaLabel ?? view.label,
|
|
2592
|
+
placeholder: placeholder ?? view.label,
|
|
2593
|
+
onChange: view.change
|
|
2153
2594
|
}
|
|
2154
2595
|
);
|
|
2155
2596
|
}
|
|
@@ -2161,34 +2602,24 @@ function KeepSortSelect({
|
|
|
2161
2602
|
children,
|
|
2162
2603
|
...props
|
|
2163
2604
|
}) {
|
|
2164
|
-
const
|
|
2165
|
-
const updatedNewestLabel = useUiLabel("updatedNewest");
|
|
2166
|
-
const updatedOldestLabel = useUiLabel("updatedOldest");
|
|
2167
|
-
const savedNewestLabel = useUiLabel("savedNewest");
|
|
2168
|
-
const savedOldestLabel = useUiLabel("savedOldest");
|
|
2169
|
-
const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
|
|
2170
|
-
const value = controlledValue ?? uncontrolledValue;
|
|
2605
|
+
const view = useKeepSortSelect({ controlledValue, defaultValue, onValueChange });
|
|
2171
2606
|
const options = children ?? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
2172
|
-
/* @__PURE__ */ jsx12("option", { value: "updatedAt:desc", children:
|
|
2173
|
-
/* @__PURE__ */ jsx12("option", { value: "updatedAt:asc", children:
|
|
2174
|
-
/* @__PURE__ */ jsx12("option", { value: "savedAt:desc", children:
|
|
2175
|
-
/* @__PURE__ */ jsx12("option", { value: "savedAt:asc", children:
|
|
2607
|
+
/* @__PURE__ */ jsx12("option", { value: "updatedAt:desc", children: view.labels.updatedNewest }),
|
|
2608
|
+
/* @__PURE__ */ jsx12("option", { value: "updatedAt:asc", children: view.labels.updatedOldest }),
|
|
2609
|
+
/* @__PURE__ */ jsx12("option", { value: "savedAt:desc", children: view.labels.savedNewest }),
|
|
2610
|
+
/* @__PURE__ */ jsx12("option", { value: "savedAt:asc", children: view.labels.savedOldest })
|
|
2176
2611
|
] });
|
|
2177
2612
|
return /* @__PURE__ */ jsx12(
|
|
2178
2613
|
"select",
|
|
2179
2614
|
{
|
|
2180
2615
|
...props,
|
|
2181
2616
|
"data-keepkit": "sort-select",
|
|
2182
|
-
|
|
2617
|
+
"data-keep-action": "sort",
|
|
2618
|
+
value: view.value,
|
|
2183
2619
|
"data-state": "selected",
|
|
2184
2620
|
"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
|
-
},
|
|
2621
|
+
"aria-label": ariaLabel ?? view.labels.sort,
|
|
2622
|
+
onChange: view.change,
|
|
2192
2623
|
children: options
|
|
2193
2624
|
}
|
|
2194
2625
|
);
|
|
@@ -2202,128 +2633,50 @@ function KeepPagination({
|
|
|
2202
2633
|
render,
|
|
2203
2634
|
...props
|
|
2204
2635
|
}) {
|
|
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
|
-
};
|
|
2636
|
+
const view = useKeepPagination({ totalCount, pageSize, page, maxPageButtons, onPageChange });
|
|
2215
2637
|
const navProps = {
|
|
2216
2638
|
...props,
|
|
2217
2639
|
"data-keepkit": "pagination",
|
|
2218
|
-
"aria-label": props["aria-label"] ??
|
|
2219
|
-
"data-state": pageCount > 1 ? "active" : "idle"
|
|
2640
|
+
"aria-label": props["aria-label"] ?? view.labels.pagination,
|
|
2641
|
+
"data-state": view.pageCount > 1 ? "active" : "idle"
|
|
2220
2642
|
};
|
|
2221
|
-
if (render)
|
|
2222
|
-
|
|
2643
|
+
if (render)
|
|
2644
|
+
return /* @__PURE__ */ jsx12("nav", { ...navProps, children: render({ page: view.currentPage, pageCount: view.pageCount, goToPage: view.goToPage }) });
|
|
2223
2645
|
return /* @__PURE__ */ jsxs7("nav", { ...navProps, children: [
|
|
2224
|
-
/* @__PURE__ */ jsx12(
|
|
2225
|
-
|
|
2646
|
+
/* @__PURE__ */ jsx12(
|
|
2647
|
+
"button",
|
|
2648
|
+
{
|
|
2649
|
+
type: "button",
|
|
2650
|
+
"data-keep-action": "previous-page",
|
|
2651
|
+
onClick: () => view.goToPage(view.currentPage - 1),
|
|
2652
|
+
disabled: view.currentPage <= 1,
|
|
2653
|
+
children: view.labels.previous
|
|
2654
|
+
}
|
|
2655
|
+
),
|
|
2656
|
+
view.visiblePages.map((nextPage) => /* @__PURE__ */ jsx12(
|
|
2226
2657
|
"button",
|
|
2227
2658
|
{
|
|
2228
2659
|
type: "button",
|
|
2229
|
-
"
|
|
2230
|
-
"aria-
|
|
2231
|
-
|
|
2660
|
+
"data-keep-action": "select-page",
|
|
2661
|
+
"aria-current": nextPage === view.currentPage ? "page" : void 0,
|
|
2662
|
+
"aria-label": `${view.labels.page} ${nextPage}`,
|
|
2663
|
+
onClick: () => view.goToPage(nextPage),
|
|
2232
2664
|
children: nextPage
|
|
2233
2665
|
},
|
|
2234
2666
|
nextPage
|
|
2235
2667
|
)),
|
|
2236
|
-
/* @__PURE__ */ jsx12(
|
|
2668
|
+
/* @__PURE__ */ jsx12(
|
|
2669
|
+
"button",
|
|
2670
|
+
{
|
|
2671
|
+
type: "button",
|
|
2672
|
+
"data-keep-action": "next-page",
|
|
2673
|
+
onClick: () => view.goToPage(view.currentPage + 1),
|
|
2674
|
+
disabled: view.currentPage >= view.pageCount,
|
|
2675
|
+
children: view.labels.next
|
|
2676
|
+
}
|
|
2677
|
+
)
|
|
2237
2678
|
] });
|
|
2238
2679
|
}
|
|
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
2680
|
|
|
2328
2681
|
// src/KeepCollection.tsx
|
|
2329
2682
|
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
@@ -2348,43 +2701,7 @@ function KeepCollectionContent({
|
|
|
2348
2701
|
className,
|
|
2349
2702
|
...rootProps
|
|
2350
2703
|
}) {
|
|
2351
|
-
const
|
|
2352
|
-
search: true,
|
|
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);
|
|
2704
|
+
const view = useKeepCollection({ query, pageSize, urlSync, urlAdapter, features });
|
|
2388
2705
|
return /* @__PURE__ */ jsxs8(
|
|
2389
2706
|
"section",
|
|
2390
2707
|
{
|
|
@@ -2392,47 +2709,19 @@ function KeepCollectionContent({
|
|
|
2392
2709
|
className,
|
|
2393
2710
|
"data-keepkit": "collection",
|
|
2394
2711
|
"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,
|
|
2712
|
+
"aria-busy": view.list.isLoading || view.list.isMutating || rootProps["aria-busy"],
|
|
2713
|
+
"data-state": getCollectionState(view.list),
|
|
2714
|
+
"data-loading": view.list.isLoading || view.list.isMutating ? "true" : void 0,
|
|
2398
2715
|
children: [
|
|
2399
2716
|
/* @__PURE__ */ jsxs8("div", { children: [
|
|
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
|
|
2717
|
+
view.enabled.search ? /* @__PURE__ */ jsx13(KeepSearchInput, { value: view.searchValue, onValueChange: view.setSearchValue }) : null,
|
|
2718
|
+
view.enabled.sort ? /* @__PURE__ */ jsx13(KeepSortSelect, { value: view.sortValue, onValueChange: view.setSortValue }) : null,
|
|
2719
|
+
view.enabled.tagFilter ? /* @__PURE__ */ jsx13(KeepTagFilter, { query, value: view.tag, onValueChange: view.setTag }) : null
|
|
2431
2720
|
] }),
|
|
2432
2721
|
/* @__PURE__ */ jsx13(
|
|
2433
2722
|
KeepList,
|
|
2434
2723
|
{
|
|
2435
|
-
query: resolvedQuery,
|
|
2724
|
+
query: view.resolvedQuery,
|
|
2436
2725
|
renderItem,
|
|
2437
2726
|
itemCardProps,
|
|
2438
2727
|
layout,
|
|
@@ -2441,16 +2730,16 @@ function KeepCollectionContent({
|
|
|
2441
2730
|
error
|
|
2442
2731
|
}
|
|
2443
2732
|
),
|
|
2444
|
-
enabled.pagination ? /* @__PURE__ */ jsx13(
|
|
2733
|
+
view.enabled.pagination ? /* @__PURE__ */ jsx13(
|
|
2445
2734
|
KeepPagination,
|
|
2446
2735
|
{
|
|
2447
|
-
totalCount: list.totalCount,
|
|
2448
|
-
pageSize: resolvedPageSize,
|
|
2449
|
-
page: list.page,
|
|
2450
|
-
onPageChange:
|
|
2736
|
+
totalCount: view.list.totalCount,
|
|
2737
|
+
pageSize: view.resolvedPageSize,
|
|
2738
|
+
page: view.list.page,
|
|
2739
|
+
onPageChange: view.setPage
|
|
2451
2740
|
}
|
|
2452
2741
|
) : null,
|
|
2453
|
-
enabled.bulkActions ? /* @__PURE__ */ jsx13(KeepBulkActions, { query: resolvedQuery }) : null
|
|
2742
|
+
view.enabled.bulkActions ? /* @__PURE__ */ jsx13(KeepBulkActions, { query: view.resolvedQuery }) : null
|
|
2454
2743
|
]
|
|
2455
2744
|
}
|
|
2456
2745
|
);
|
|
@@ -2459,45 +2748,24 @@ function getCollectionState(list) {
|
|
|
2459
2748
|
if (list.error && list.items.length === 0) return "error";
|
|
2460
2749
|
if (list.isLoading && !list.isHydrated) return "loading";
|
|
2461
2750
|
if (list.isHydrated && list.items.length === 0) return "empty";
|
|
2462
|
-
return "ready";
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
// src/KeepLayout.tsx
|
|
2466
|
-
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
2467
|
-
function KeepLayout({ layout = "list", children, ...props }) {
|
|
2468
|
-
return /* @__PURE__ */ jsx14("div", { ...props, "data-keepkit": "layout", "data-layout": layout, children });
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
// 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");
|
|
2751
|
+
return "ready";
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/KeepLayout.tsx
|
|
2755
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
2756
|
+
function KeepLayout({ layout = "list", children, ...props }) {
|
|
2757
|
+
return /* @__PURE__ */ jsx14("div", { ...props, "data-keepkit": "layout", "data-layout": layout, children });
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// src/KeepNoteEditor.tsx
|
|
2761
|
+
import { isValidElement as isValidElement5 } from "react";
|
|
2762
|
+
|
|
2763
|
+
// src/hooks/useKeepNoteEditor.ts
|
|
2764
|
+
import { useKeepItem as useKeepItem3 } from "@keepkit/core/react";
|
|
2765
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
|
|
2766
|
+
function useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError }) {
|
|
2498
2767
|
const itemState = useKeepItem3(item);
|
|
2499
2768
|
const { error, isMutating, item: savedItem, updateNote } = itemState;
|
|
2500
|
-
const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
|
|
2501
2769
|
const [note, setNote] = useState7(item.note ?? "");
|
|
2502
2770
|
const baselineNote = savedItem?.note ?? item.note ?? "";
|
|
2503
2771
|
const isDirty = note !== baselineNote;
|
|
@@ -2509,9 +2777,9 @@ function KeepNoteEditor({
|
|
|
2509
2777
|
await updateNote(nextNote);
|
|
2510
2778
|
lastSavedNoteRef.current = note;
|
|
2511
2779
|
onSaved?.(nextNote);
|
|
2512
|
-
} catch (
|
|
2513
|
-
onSaveError?.(
|
|
2514
|
-
throw
|
|
2780
|
+
} catch (cause) {
|
|
2781
|
+
onSaveError?.(cause);
|
|
2782
|
+
throw cause;
|
|
2515
2783
|
}
|
|
2516
2784
|
}, [note, onSaveError, onSaved, updateNote]);
|
|
2517
2785
|
useEffect3(() => {
|
|
@@ -2528,31 +2796,62 @@ function KeepNoteEditor({
|
|
|
2528
2796
|
error,
|
|
2529
2797
|
save
|
|
2530
2798
|
};
|
|
2531
|
-
const
|
|
2799
|
+
const submit = (event) => {
|
|
2800
|
+
event.preventDefault();
|
|
2801
|
+
void save().catch(() => void 0);
|
|
2802
|
+
};
|
|
2803
|
+
return {
|
|
2804
|
+
state,
|
|
2805
|
+
submit,
|
|
2806
|
+
handleKeyDown: (event) => {
|
|
2807
|
+
if (event.key !== "Enter" || !event.ctrlKey && !event.metaKey) return;
|
|
2808
|
+
event.preventDefault();
|
|
2809
|
+
void save().catch(() => void 0);
|
|
2810
|
+
},
|
|
2811
|
+
labels: {
|
|
2812
|
+
note: useUiLabel("note"),
|
|
2813
|
+
save: useUiLabel("saveNote"),
|
|
2814
|
+
error: useUiLabel("error")
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// src/KeepNoteEditor.tsx
|
|
2820
|
+
import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2821
|
+
function KeepNoteEditor({
|
|
2822
|
+
item,
|
|
2823
|
+
label,
|
|
2824
|
+
saveLabel,
|
|
2825
|
+
placeholder,
|
|
2826
|
+
debounceMs = 300,
|
|
2827
|
+
onSaved,
|
|
2828
|
+
onSaveError,
|
|
2829
|
+
render,
|
|
2830
|
+
children,
|
|
2831
|
+
asChild = false,
|
|
2832
|
+
className,
|
|
2833
|
+
...formProps
|
|
2834
|
+
}) {
|
|
2835
|
+
const view = useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError });
|
|
2836
|
+
const { error, isDirty, isSaving, note, setNote } = view.state;
|
|
2837
|
+
const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
|
|
2838
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
2532
2839
|
/* @__PURE__ */ jsxs9("label", { children: [
|
|
2533
|
-
label ??
|
|
2840
|
+
label ?? view.labels.note,
|
|
2534
2841
|
/* @__PURE__ */ jsx15(
|
|
2535
2842
|
"textarea",
|
|
2536
2843
|
{
|
|
2844
|
+
"data-keep-action": "edit-note",
|
|
2537
2845
|
value: note,
|
|
2538
2846
|
onChange: (event) => setNote(event.currentTarget.value),
|
|
2539
2847
|
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
|
-
}
|
|
2848
|
+
disabled: isSaving,
|
|
2849
|
+
onKeyDown: view.handleKeyDown
|
|
2547
2850
|
}
|
|
2548
2851
|
)
|
|
2549
2852
|
] }),
|
|
2550
|
-
/* @__PURE__ */ jsx15("button", { type: "submit", disabled:
|
|
2853
|
+
/* @__PURE__ */ jsx15("button", { type: "submit", "data-keep-action": "save-note", disabled: isSaving, "aria-busy": isSaving, children: saveLabel ?? view.labels.save })
|
|
2551
2854
|
] });
|
|
2552
|
-
const handleSubmit = (event) => {
|
|
2553
|
-
event.preventDefault();
|
|
2554
|
-
void save().catch(() => void 0);
|
|
2555
|
-
};
|
|
2556
2855
|
if (!asChild) {
|
|
2557
2856
|
return /* @__PURE__ */ jsxs9(
|
|
2558
2857
|
"form",
|
|
@@ -2560,14 +2859,14 @@ function KeepNoteEditor({
|
|
|
2560
2859
|
...formProps,
|
|
2561
2860
|
className,
|
|
2562
2861
|
"data-keepkit": "note-editor",
|
|
2563
|
-
onSubmit:
|
|
2564
|
-
"aria-busy":
|
|
2862
|
+
onSubmit: view.submit,
|
|
2863
|
+
"aria-busy": isSaving || formProps["aria-busy"],
|
|
2565
2864
|
"data-state": error ? "error" : isDirty ? "dirty" : "clean",
|
|
2566
|
-
"data-loading":
|
|
2567
|
-
"data-disabled":
|
|
2865
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
2866
|
+
"data-disabled": isSaving ? "true" : void 0,
|
|
2568
2867
|
children: [
|
|
2569
2868
|
body,
|
|
2570
|
-
error ? /* @__PURE__ */ jsx15("p", { role: "alert", children: getErrorMessage3(error,
|
|
2869
|
+
error ? /* @__PURE__ */ jsx15("p", { role: "alert", children: getErrorMessage3(error, view.labels.error) }) : null
|
|
2571
2870
|
]
|
|
2572
2871
|
}
|
|
2573
2872
|
);
|
|
@@ -2579,11 +2878,11 @@ function KeepNoteEditor({
|
|
|
2579
2878
|
...formProps,
|
|
2580
2879
|
className,
|
|
2581
2880
|
"data-keepkit": "note-editor",
|
|
2582
|
-
onSubmit:
|
|
2583
|
-
"aria-busy":
|
|
2881
|
+
onSubmit: view.submit,
|
|
2882
|
+
"aria-busy": isSaving || formProps["aria-busy"],
|
|
2584
2883
|
"data-state": error ? "error" : isDirty ? "dirty" : "clean",
|
|
2585
|
-
"data-loading":
|
|
2586
|
-
"data-disabled":
|
|
2884
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
2885
|
+
"data-disabled": isSaving ? "true" : void 0
|
|
2587
2886
|
},
|
|
2588
2887
|
body,
|
|
2589
2888
|
"KeepNoteEditor"
|
|
@@ -2593,9 +2892,62 @@ function getErrorMessage3(error, fallback) {
|
|
|
2593
2892
|
return error instanceof Error ? error.message : fallback;
|
|
2594
2893
|
}
|
|
2595
2894
|
|
|
2596
|
-
// src/
|
|
2895
|
+
// src/hooks/useKeepSyncRecoveryDialog.ts
|
|
2597
2896
|
import { useKeepContext as useKeepContext3 } from "@keepkit/core/react";
|
|
2598
2897
|
import { useEffect as useEffect4, useState as useState8 } from "react";
|
|
2898
|
+
function useKeepSyncRecoveryDialog(options) {
|
|
2899
|
+
const { open, onOpenChange, conflicts, onManualMerge } = options;
|
|
2900
|
+
const context = useKeepContext3();
|
|
2901
|
+
const conflictList = conflicts ?? context.syncState.conflicts ?? [];
|
|
2902
|
+
const hasRecovery = conflictList.length > 0 || context.syncState.status === "error" || Boolean(context.error);
|
|
2903
|
+
const [dismissed, setDismissed] = useState8(false);
|
|
2904
|
+
const [busyId, setBusyId] = useState8();
|
|
2905
|
+
const [error, setError] = useState8();
|
|
2906
|
+
useEffect4(() => {
|
|
2907
|
+
if (hasRecovery) setDismissed(false);
|
|
2908
|
+
}, [hasRecovery]);
|
|
2909
|
+
return {
|
|
2910
|
+
conflictList,
|
|
2911
|
+
isOpen: open ?? (hasRecovery && !dismissed),
|
|
2912
|
+
busyId,
|
|
2913
|
+
error,
|
|
2914
|
+
showBackupRecovery: context.syncState.status === "error" || Boolean(context.error),
|
|
2915
|
+
close: () => {
|
|
2916
|
+
setDismissed(true);
|
|
2917
|
+
onOpenChange?.(false);
|
|
2918
|
+
},
|
|
2919
|
+
resolve: async (conflict, resolution) => {
|
|
2920
|
+
setError(void 0);
|
|
2921
|
+
setBusyId(conflict.id);
|
|
2922
|
+
try {
|
|
2923
|
+
const merged = resolution === "manual" ? await onManualMerge?.(conflict) : void 0;
|
|
2924
|
+
if (resolution === "manual" && !merged) throw new Error("A manual merge result is required.");
|
|
2925
|
+
await context.resolveSyncConflict(conflict.id, resolution, merged);
|
|
2926
|
+
} catch (cause) {
|
|
2927
|
+
setError(cause);
|
|
2928
|
+
} finally {
|
|
2929
|
+
setBusyId(void 0);
|
|
2930
|
+
}
|
|
2931
|
+
},
|
|
2932
|
+
labels: {
|
|
2933
|
+
close: useUiLabel("close"),
|
|
2934
|
+
title: useUiLabel("resolveSync"),
|
|
2935
|
+
conflict: useUiLabel("syncConflict"),
|
|
2936
|
+
keepLocal: useUiLabel("keepLocal"),
|
|
2937
|
+
useServer: useUiLabel("useServer"),
|
|
2938
|
+
manualMerge: useUiLabel("manualMerge"),
|
|
2939
|
+
localVersion: useUiLabel("localVersion"),
|
|
2940
|
+
remoteVersion: useUiLabel("remoteVersion"),
|
|
2941
|
+
updatedAt: useUiLabel("updatedAt"),
|
|
2942
|
+
note: useUiLabel("note"),
|
|
2943
|
+
backupRecovery: useUiLabel("backupRecovery"),
|
|
2944
|
+
backupRecoveryDescription: useUiLabel("backupRecoveryDescription"),
|
|
2945
|
+
error: useUiLabel("error")
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
// src/KeepSyncRecoveryDialog.tsx
|
|
2599
2951
|
import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2600
2952
|
function KeepSyncRecoveryDialog({
|
|
2601
2953
|
open,
|
|
@@ -2609,48 +2961,8 @@ function KeepSyncRecoveryDialog({
|
|
|
2609
2961
|
className,
|
|
2610
2962
|
...props
|
|
2611
2963
|
}) {
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2614
|
-
const defaultDialogTitle = useUiLabel("resolveSync");
|
|
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
|
-
}
|
|
2964
|
+
const view = useKeepSyncRecoveryDialog({ open, onOpenChange, conflicts, onManualMerge });
|
|
2965
|
+
if (!view.isOpen) return null;
|
|
2654
2966
|
return /* @__PURE__ */ jsxs10(
|
|
2655
2967
|
"section",
|
|
2656
2968
|
{
|
|
@@ -2659,29 +2971,29 @@ function KeepSyncRecoveryDialog({
|
|
|
2659
2971
|
role: "dialog",
|
|
2660
2972
|
"aria-modal": "true",
|
|
2661
2973
|
"aria-labelledby": "keepkit-sync-recovery-title",
|
|
2662
|
-
"aria-describedby": error ? "keepkit-sync-recovery-error" : void 0,
|
|
2663
|
-
"aria-busy": busyId !== void 0,
|
|
2974
|
+
"aria-describedby": view.error ? "keepkit-sync-recovery-error" : void 0,
|
|
2975
|
+
"aria-busy": view.busyId !== void 0,
|
|
2664
2976
|
"data-keepkit": "sync-recovery",
|
|
2665
|
-
"data-state": conflictList.length > 0 ? "conflict" : "error",
|
|
2666
|
-
"data-loading": busyId !== void 0 ? "true" : void 0,
|
|
2977
|
+
"data-state": view.conflictList.length > 0 ? "conflict" : "error",
|
|
2978
|
+
"data-loading": view.busyId !== void 0 ? "true" : void 0,
|
|
2667
2979
|
children: [
|
|
2668
2980
|
/* @__PURE__ */ jsxs10("header", { children: [
|
|
2669
|
-
/* @__PURE__ */ jsx16("h2", { id: "keepkit-sync-recovery-title", children:
|
|
2670
|
-
/* @__PURE__ */ jsx16("button", { type: "button", onClick: close, "aria-label":
|
|
2981
|
+
/* @__PURE__ */ jsx16("h2", { id: "keepkit-sync-recovery-title", children: title ?? view.labels.title }),
|
|
2982
|
+
/* @__PURE__ */ jsx16("button", { type: "button", "data-keep-action": "close-dialog", onClick: view.close, "aria-label": view.labels.close, children: view.labels.close })
|
|
2671
2983
|
] }),
|
|
2672
2984
|
children,
|
|
2673
|
-
conflictList.length > 0 ? /* @__PURE__ */ jsxs10("div", { children: [
|
|
2674
|
-
/* @__PURE__ */ jsx16("p", { children:
|
|
2675
|
-
conflictList.map((conflict) => /* @__PURE__ */ jsxs10("article", { "data-conflict-id": conflict.id, children: [
|
|
2985
|
+
view.conflictList.length > 0 ? /* @__PURE__ */ jsxs10("div", { children: [
|
|
2986
|
+
/* @__PURE__ */ jsx16("p", { children: view.labels.conflict }),
|
|
2987
|
+
view.conflictList.map((conflict) => /* @__PURE__ */ jsxs10("article", { "data-conflict-id": conflict.id, children: [
|
|
2676
2988
|
/* @__PURE__ */ jsx16("h3", { children: getMetaTitle(conflict.operation.item?.meta) ?? conflict.id }),
|
|
2677
2989
|
/* @__PURE__ */ jsxs10("div", { "data-conflict-preview": true, children: [
|
|
2678
2990
|
/* @__PURE__ */ jsx16(
|
|
2679
2991
|
ConflictPreview,
|
|
2680
2992
|
{
|
|
2681
2993
|
item: conflict.operation.item,
|
|
2682
|
-
heading:
|
|
2683
|
-
updatedAtLabel,
|
|
2684
|
-
noteLabel,
|
|
2994
|
+
heading: view.labels.localVersion,
|
|
2995
|
+
updatedAtLabel: view.labels.updatedAt,
|
|
2996
|
+
noteLabel: view.labels.note,
|
|
2685
2997
|
side: "local"
|
|
2686
2998
|
}
|
|
2687
2999
|
),
|
|
@@ -2689,34 +3001,53 @@ function KeepSyncRecoveryDialog({
|
|
|
2689
3001
|
ConflictPreview,
|
|
2690
3002
|
{
|
|
2691
3003
|
item: conflict.remote,
|
|
2692
|
-
heading:
|
|
2693
|
-
updatedAtLabel,
|
|
2694
|
-
noteLabel,
|
|
3004
|
+
heading: view.labels.remoteVersion,
|
|
3005
|
+
updatedAtLabel: view.labels.updatedAt,
|
|
3006
|
+
noteLabel: view.labels.note,
|
|
2695
3007
|
side: "remote"
|
|
2696
3008
|
}
|
|
2697
3009
|
)
|
|
2698
3010
|
] }),
|
|
2699
3011
|
/* @__PURE__ */ jsxs10("div", { children: [
|
|
2700
|
-
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "local"), disabled: busyId !== void 0, children: keepLocalLabel }),
|
|
2701
|
-
/* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "remote"), disabled: busyId !== void 0, children: useServerLabel }),
|
|
2702
3012
|
/* @__PURE__ */ jsx16(
|
|
2703
3013
|
"button",
|
|
2704
3014
|
{
|
|
2705
3015
|
type: "button",
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3016
|
+
"data-keep-action": "keep-local",
|
|
3017
|
+
onClick: () => void view.resolve(conflict, "local"),
|
|
3018
|
+
disabled: view.busyId !== void 0,
|
|
3019
|
+
children: view.labels.keepLocal
|
|
3020
|
+
}
|
|
3021
|
+
),
|
|
3022
|
+
/* @__PURE__ */ jsx16(
|
|
3023
|
+
"button",
|
|
3024
|
+
{
|
|
3025
|
+
type: "button",
|
|
3026
|
+
"data-keep-action": "use-server",
|
|
3027
|
+
onClick: () => void view.resolve(conflict, "remote"),
|
|
3028
|
+
disabled: view.busyId !== void 0,
|
|
3029
|
+
children: view.labels.useServer
|
|
3030
|
+
}
|
|
3031
|
+
),
|
|
3032
|
+
/* @__PURE__ */ jsx16(
|
|
3033
|
+
"button",
|
|
3034
|
+
{
|
|
3035
|
+
type: "button",
|
|
3036
|
+
"data-keep-action": "manual-merge",
|
|
3037
|
+
onClick: () => void view.resolve(conflict, "manual"),
|
|
3038
|
+
disabled: view.busyId !== void 0 || !onManualMerge,
|
|
3039
|
+
children: view.labels.manualMerge
|
|
2709
3040
|
}
|
|
2710
3041
|
)
|
|
2711
3042
|
] })
|
|
2712
3043
|
] }, conflict.id))
|
|
2713
3044
|
] }) : null,
|
|
2714
|
-
|
|
2715
|
-
/* @__PURE__ */ jsx16("h3", { children:
|
|
2716
|
-
/* @__PURE__ */ jsx16("p", { children: backupRecoveryDescription }),
|
|
3045
|
+
view.showBackupRecovery ? /* @__PURE__ */ jsxs10("section", { "data-recovery": "backup", children: [
|
|
3046
|
+
/* @__PURE__ */ jsx16("h3", { children: view.labels.backupRecovery }),
|
|
3047
|
+
/* @__PURE__ */ jsx16("p", { children: view.labels.backupRecoveryDescription }),
|
|
2717
3048
|
backup ?? (showBackupControls ? /* @__PURE__ */ jsx16(KeepBackup, {}) : null)
|
|
2718
3049
|
] }) : null,
|
|
2719
|
-
error ? /* @__PURE__ */ jsx16("p", { id: "keepkit-sync-recovery-error", role: "alert", "aria-live": "assertive", children: error instanceof Error ? error.message :
|
|
3050
|
+
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
3051
|
]
|
|
2721
3052
|
}
|
|
2722
3053
|
);
|
|
@@ -2746,16 +3077,9 @@ function formatConflictDate(timestamp) {
|
|
|
2746
3077
|
return new Date(timestamp).toISOString().slice(0, 10);
|
|
2747
3078
|
}
|
|
2748
3079
|
|
|
2749
|
-
// src/
|
|
3080
|
+
// src/hooks/useKeepSyncStatusBanner.ts
|
|
2750
3081
|
import { useKeepContext as useKeepContext4 } from "@keepkit/core/react";
|
|
2751
|
-
|
|
2752
|
-
function KeepSyncStatusBanner({
|
|
2753
|
-
onRetry,
|
|
2754
|
-
onResolveConflicts,
|
|
2755
|
-
children,
|
|
2756
|
-
className,
|
|
2757
|
-
...props
|
|
2758
|
-
}) {
|
|
3082
|
+
function useKeepSyncStatusBanner({ onRetry, children }) {
|
|
2759
3083
|
const context = useKeepContext4();
|
|
2760
3084
|
const retryLabel = useUiLabel("retrySync");
|
|
2761
3085
|
const resolveLabel = useUiLabel("resolveSync");
|
|
@@ -2764,53 +3088,80 @@ function KeepSyncStatusBanner({
|
|
|
2764
3088
|
const syncedLabel = useUiLabel("syncSynced");
|
|
2765
3089
|
const status = context.syncState.status;
|
|
2766
3090
|
const hasConflicts = (context.syncState.conflicts?.length ?? 0) > 0 || context.syncState.conflictIds.length > 0;
|
|
2767
|
-
if (status === "idle" && !hasConflicts) return null;
|
|
2768
3091
|
const message = children ?? (status === "error" ? getErrorMessage4(context.syncState.error) : status === "conflict" || hasConflicts ? conflictLabel : status === "pending" || status === "syncing" ? pendingLabel : syncedLabel);
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3092
|
+
return {
|
|
3093
|
+
status,
|
|
3094
|
+
hasConflicts,
|
|
3095
|
+
message,
|
|
3096
|
+
isMutating: context.isMutating,
|
|
3097
|
+
role: status === "error" || status === "conflict" || hasConflicts ? "alert" : "status",
|
|
3098
|
+
showRetry: status === "error" || status === "pending" || status === "syncing",
|
|
3099
|
+
retryLabel,
|
|
3100
|
+
resolveLabel,
|
|
3101
|
+
retry: async () => {
|
|
3102
|
+
if (onRetry) {
|
|
3103
|
+
await onRetry();
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
await context.flushSync();
|
|
2774
3107
|
}
|
|
2775
|
-
await context.flushSync();
|
|
2776
3108
|
};
|
|
3109
|
+
}
|
|
3110
|
+
function getErrorMessage4(error) {
|
|
3111
|
+
return error instanceof Error ? error.message : "Sync failed.";
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
// src/KeepSyncStatusBanner.tsx
|
|
3115
|
+
import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3116
|
+
function KeepSyncStatusBanner({
|
|
3117
|
+
onRetry,
|
|
3118
|
+
onResolveConflicts,
|
|
3119
|
+
children,
|
|
3120
|
+
className,
|
|
3121
|
+
...props
|
|
3122
|
+
}) {
|
|
3123
|
+
const view = useKeepSyncStatusBanner({ onRetry, children });
|
|
3124
|
+
if (view.status === "idle" && !view.hasConflicts) return null;
|
|
2777
3125
|
return /* @__PURE__ */ jsxs11(
|
|
2778
3126
|
"aside",
|
|
2779
3127
|
{
|
|
2780
3128
|
...props,
|
|
2781
3129
|
className,
|
|
2782
|
-
role: props.role ?? role,
|
|
3130
|
+
role: props.role ?? view.role,
|
|
2783
3131
|
"aria-live": props["aria-live"] ?? "polite",
|
|
2784
3132
|
"data-keepkit": "sync-status",
|
|
2785
|
-
"data-state": status,
|
|
3133
|
+
"data-state": view.status,
|
|
2786
3134
|
children: [
|
|
2787
|
-
/* @__PURE__ */ jsx17("p", { children: message }),
|
|
2788
|
-
|
|
2789
|
-
|
|
3135
|
+
/* @__PURE__ */ jsx17("p", { children: view.message }),
|
|
3136
|
+
view.showRetry ? /* @__PURE__ */ jsx17(
|
|
3137
|
+
"button",
|
|
3138
|
+
{
|
|
3139
|
+
type: "button",
|
|
3140
|
+
"data-keep-action": "retry-sync",
|
|
3141
|
+
onClick: () => void view.retry(),
|
|
3142
|
+
disabled: view.isMutating,
|
|
3143
|
+
children: view.retryLabel
|
|
3144
|
+
}
|
|
3145
|
+
) : null,
|
|
3146
|
+
view.hasConflicts ? /* @__PURE__ */ jsx17(
|
|
3147
|
+
"button",
|
|
3148
|
+
{
|
|
3149
|
+
type: "button",
|
|
3150
|
+
"data-keep-action": "resolve-conflicts",
|
|
3151
|
+
onClick: onResolveConflicts,
|
|
3152
|
+
disabled: !onResolveConflicts,
|
|
3153
|
+
children: view.resolveLabel
|
|
3154
|
+
}
|
|
3155
|
+
) : null
|
|
2790
3156
|
]
|
|
2791
3157
|
}
|
|
2792
3158
|
);
|
|
2793
3159
|
}
|
|
2794
|
-
function getErrorMessage4(error) {
|
|
2795
|
-
return error instanceof Error ? error.message : "Sync failed.";
|
|
2796
|
-
}
|
|
2797
3160
|
|
|
2798
|
-
// src/
|
|
3161
|
+
// src/hooks/useKeepTagEditor.ts
|
|
2799
3162
|
import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
|
|
2800
3163
|
import { useCallback as useCallback3, useEffect as useEffect5, useState as useState9 } from "react";
|
|
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");
|
|
3164
|
+
function useKeepTagEditor({ item, onSaved, onSaveError }) {
|
|
2814
3165
|
const itemState = useKeepItem4(item);
|
|
2815
3166
|
const [tags, setTags] = useState9(item.tags ?? []);
|
|
2816
3167
|
const [input, setInput] = useState9("");
|
|
@@ -2821,60 +3172,91 @@ function KeepTagEditor({
|
|
|
2821
3172
|
await itemState.updateTags(nextTags);
|
|
2822
3173
|
setTags(nextTags);
|
|
2823
3174
|
onSaved?.(nextTags);
|
|
2824
|
-
} catch (
|
|
2825
|
-
onSaveError?.(
|
|
2826
|
-
throw
|
|
3175
|
+
} catch (cause) {
|
|
3176
|
+
onSaveError?.(cause);
|
|
3177
|
+
throw cause;
|
|
2827
3178
|
}
|
|
2828
3179
|
}, [itemState, onSaveError, onSaved, tags]);
|
|
2829
3180
|
const addTag = (tag) => {
|
|
2830
3181
|
setTags(normalizeUiTags([...tags, tag]));
|
|
2831
3182
|
setInput("");
|
|
2832
3183
|
};
|
|
2833
|
-
const
|
|
3184
|
+
const state = { tags, setTags, save, isSaving: itemState.isMutating };
|
|
3185
|
+
return {
|
|
3186
|
+
state,
|
|
3187
|
+
input,
|
|
3188
|
+
setInput,
|
|
3189
|
+
error: itemState.error,
|
|
3190
|
+
handleInputKeyDown: (event) => {
|
|
3191
|
+
if (event.key === "Enter") {
|
|
3192
|
+
if (event.nativeEvent.isComposing) return;
|
|
3193
|
+
event.preventDefault();
|
|
3194
|
+
if (input.trim()) addTag(input);
|
|
3195
|
+
} else if (event.key === "Backspace" && input.length === 0 && tags.length > 0) {
|
|
3196
|
+
event.preventDefault();
|
|
3197
|
+
setTags(tags.slice(0, -1));
|
|
3198
|
+
}
|
|
3199
|
+
},
|
|
3200
|
+
removeTag: (tag) => setTags(tags.filter((current) => current !== tag)),
|
|
3201
|
+
submit: (event) => {
|
|
3202
|
+
event.preventDefault();
|
|
3203
|
+
void save().catch(() => void 0);
|
|
3204
|
+
},
|
|
3205
|
+
labels: {
|
|
3206
|
+
tags: useUiLabel("tagsToApply"),
|
|
3207
|
+
remove: useUiLabel("remove"),
|
|
3208
|
+
apply: useUiLabel("applyTags"),
|
|
3209
|
+
error: useUiLabel("error")
|
|
3210
|
+
}
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
// src/KeepTagEditor.tsx
|
|
3215
|
+
import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3216
|
+
function KeepTagEditor({
|
|
3217
|
+
item,
|
|
3218
|
+
availableTags = [],
|
|
3219
|
+
onSaved,
|
|
3220
|
+
onSaveError,
|
|
3221
|
+
render,
|
|
3222
|
+
...props
|
|
3223
|
+
}) {
|
|
3224
|
+
const view = useKeepTagEditor({ item, onSaved, onSaveError });
|
|
3225
|
+
const { isSaving, tags } = view.state;
|
|
3226
|
+
const body = render ? render(view.state) : /* @__PURE__ */ jsxs12(Fragment6, { children: [
|
|
2834
3227
|
/* @__PURE__ */ jsxs12("label", { children: [
|
|
2835
|
-
|
|
3228
|
+
view.labels.tags,
|
|
2836
3229
|
/* @__PURE__ */ jsx18(
|
|
2837
3230
|
"input",
|
|
2838
3231
|
{
|
|
2839
|
-
|
|
3232
|
+
"data-keep-action": "edit-tags",
|
|
3233
|
+
value: view.input,
|
|
2840
3234
|
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
|
-
}
|
|
3235
|
+
onChange: (event) => view.setInput(event.currentTarget.value),
|
|
3236
|
+
onKeyDown: view.handleInputKeyDown
|
|
2852
3237
|
}
|
|
2853
3238
|
)
|
|
2854
3239
|
] }),
|
|
2855
3240
|
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":
|
|
3241
|
+
/* @__PURE__ */ jsx18("ul", { "aria-label": view.labels.tags, children: tags.map((tag) => /* @__PURE__ */ jsxs12("li", { children: [
|
|
2857
3242
|
tag,
|
|
2858
|
-
/* @__PURE__ */ jsx18("button", { type: "button", onClick: () =>
|
|
3243
|
+
/* @__PURE__ */ jsx18("button", { type: "button", "data-keep-action": "remove-tag", onClick: () => view.removeTag(tag), children: view.labels.remove })
|
|
2859
3244
|
] }, tag)) }),
|
|
2860
|
-
/* @__PURE__ */ jsx18("button", { type: "submit", disabled:
|
|
3245
|
+
/* @__PURE__ */ jsx18("button", { type: "submit", "data-keep-action": "apply-tags", disabled: isSaving, "aria-busy": isSaving, children: view.labels.apply })
|
|
2861
3246
|
] });
|
|
2862
3247
|
return /* @__PURE__ */ jsxs12(
|
|
2863
3248
|
"form",
|
|
2864
3249
|
{
|
|
2865
3250
|
...props,
|
|
2866
|
-
onSubmit:
|
|
2867
|
-
|
|
2868
|
-
void save().catch(() => void 0);
|
|
2869
|
-
},
|
|
2870
|
-
"aria-busy": itemState.isMutating || props["aria-busy"],
|
|
3251
|
+
onSubmit: view.submit,
|
|
3252
|
+
"aria-busy": isSaving || props["aria-busy"],
|
|
2871
3253
|
"data-keepkit": "tag-editor",
|
|
2872
|
-
"data-state":
|
|
2873
|
-
"data-loading":
|
|
2874
|
-
"data-disabled":
|
|
3254
|
+
"data-state": view.error ? "error" : isSaving ? "saving" : "idle",
|
|
3255
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
3256
|
+
"data-disabled": isSaving ? "true" : void 0,
|
|
2875
3257
|
children: [
|
|
2876
3258
|
body,
|
|
2877
|
-
|
|
3259
|
+
view.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: getErrorMessage5(view.error, view.labels.error) }) : null
|
|
2878
3260
|
]
|
|
2879
3261
|
}
|
|
2880
3262
|
);
|
|
@@ -2883,23 +3265,84 @@ function getErrorMessage5(error, fallback) {
|
|
|
2883
3265
|
return error instanceof Error ? error.message : fallback;
|
|
2884
3266
|
}
|
|
2885
3267
|
|
|
2886
|
-
// src/
|
|
3268
|
+
// src/hooks/useKeepUndo.ts
|
|
2887
3269
|
import { useKeepContext as useKeepContext5 } from "@keepkit/core/react";
|
|
3270
|
+
function useKeepUndo() {
|
|
3271
|
+
const context = useKeepContext5();
|
|
3272
|
+
return {
|
|
3273
|
+
canUndo: context.undo.canUndo,
|
|
3274
|
+
undo: () => context.undoLastRemoval(),
|
|
3275
|
+
message: useUiLabel("undoAvailable"),
|
|
3276
|
+
label: useUiLabel("undo")
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
// src/KeepUndo.tsx
|
|
2888
3281
|
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2889
3282
|
function KeepUndo({ children, label, ...props }) {
|
|
2890
|
-
const
|
|
2891
|
-
|
|
2892
|
-
const undoLabel = useUiLabel("undo");
|
|
2893
|
-
if (!context.undo.canUndo) return null;
|
|
3283
|
+
const view = useKeepUndo();
|
|
3284
|
+
if (!view.canUndo) return null;
|
|
2894
3285
|
return /* @__PURE__ */ jsxs13("div", { ...props, role: "status", "aria-live": "polite", "data-keepkit": "undo", "data-state": "available", children: [
|
|
2895
|
-
children ?? message,
|
|
2896
|
-
/* @__PURE__ */ jsx19("button", { type: "button", onClick: () => void
|
|
3286
|
+
children ?? view.message,
|
|
3287
|
+
/* @__PURE__ */ jsx19("button", { type: "button", "data-keep-action": "undo", onClick: () => void view.undo(), children: label ?? view.label })
|
|
2897
3288
|
] });
|
|
2898
3289
|
}
|
|
2899
3290
|
|
|
2900
3291
|
// src/status.tsx
|
|
3292
|
+
import { isValidElement as isValidElement6 } from "react";
|
|
3293
|
+
|
|
3294
|
+
// src/hooks/useStatusViews.ts
|
|
2901
3295
|
import { useKeepContext as useKeepContext6 } from "@keepkit/core/react";
|
|
2902
|
-
import {
|
|
3296
|
+
import { useEffect as useEffect6, useRef as useRef4, useState as useState10 } from "react";
|
|
3297
|
+
function useKeepEmptyState() {
|
|
3298
|
+
return useUiLabel("noItems").replace(/\.$/, "");
|
|
3299
|
+
}
|
|
3300
|
+
function useKeepStatus(status) {
|
|
3301
|
+
const context = useKeepContext6();
|
|
3302
|
+
const resolvedStatus = status ?? getDerivedStatus(context);
|
|
3303
|
+
const state = {
|
|
3304
|
+
status: resolvedStatus,
|
|
3305
|
+
error: context.error,
|
|
3306
|
+
pendingCount: context.syncState.pendingCount,
|
|
3307
|
+
items: context.items
|
|
3308
|
+
};
|
|
3309
|
+
return { state, defaultLabel: useUiLabel(getStatusLabelKey3(resolvedStatus)) };
|
|
3310
|
+
}
|
|
3311
|
+
function useKeepAnnouncements(messages) {
|
|
3312
|
+
const context = useKeepContext6();
|
|
3313
|
+
const savedMessage = useUiLabel("savedMessage", messages?.save);
|
|
3314
|
+
const removedMessage = useUiLabel("removedMessage", messages?.remove);
|
|
3315
|
+
const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
|
|
3316
|
+
const [message, setMessage] = useState10("");
|
|
3317
|
+
const lastChangeRef = useRef4(void 0);
|
|
3318
|
+
useEffect6(() => {
|
|
3319
|
+
const change = context.lastChange;
|
|
3320
|
+
if (!change || change === lastChangeRef.current) return;
|
|
3321
|
+
lastChangeRef.current = change;
|
|
3322
|
+
if (change.action === "save") setMessage(savedMessage);
|
|
3323
|
+
else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
|
|
3324
|
+
else if (change.action === "updateNote") setMessage(noteSavedMessage);
|
|
3325
|
+
}, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
|
|
3326
|
+
return message;
|
|
3327
|
+
}
|
|
3328
|
+
function getDerivedStatus(context) {
|
|
3329
|
+
if (context.error) return "error";
|
|
3330
|
+
if (context.syncState.status === "pending" || context.syncState.status === "syncing") return "syncing";
|
|
3331
|
+
if (context.isMutating) return "saving";
|
|
3332
|
+
if (context.isLoading && !context.isHydrated) return "loading";
|
|
3333
|
+
if (context.isHydrated && context.items.length === 0) return "empty";
|
|
3334
|
+
return "idle";
|
|
3335
|
+
}
|
|
3336
|
+
function getStatusLabelKey3(status) {
|
|
3337
|
+
if (status === "empty") return "noItems";
|
|
3338
|
+
if (status === "loading") return "loadingItems";
|
|
3339
|
+
if (status === "error") return "error";
|
|
3340
|
+
if (status === "saving") return "saving";
|
|
3341
|
+
if (status === "syncing") return "syncing";
|
|
3342
|
+
return "saved";
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// src/status.tsx
|
|
2903
3346
|
import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2904
3347
|
function KeepEmptyState({
|
|
2905
3348
|
title,
|
|
@@ -2910,7 +3353,7 @@ function KeepEmptyState({
|
|
|
2910
3353
|
className,
|
|
2911
3354
|
...rootProps
|
|
2912
3355
|
}) {
|
|
2913
|
-
const defaultTitle =
|
|
3356
|
+
const defaultTitle = useKeepEmptyState();
|
|
2914
3357
|
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
2915
3358
|
const body = contentChildren ?? /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
2916
3359
|
/* @__PURE__ */ jsx20("h2", { children: title ?? defaultTitle }),
|
|
@@ -2934,18 +3377,10 @@ function KeepStatus({
|
|
|
2934
3377
|
className,
|
|
2935
3378
|
...rootProps
|
|
2936
3379
|
}) {
|
|
2937
|
-
const
|
|
3380
|
+
const view = useKeepStatus(status);
|
|
2938
3381
|
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");
|
|
3382
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? labels?.[view.state.status] ?? view.defaultLabel;
|
|
3383
|
+
const role = rootProps.role ?? (view.state.status === "error" ? "alert" : "status");
|
|
2949
3384
|
return renderRoot(
|
|
2950
3385
|
asChild,
|
|
2951
3386
|
isValidElement6(children) ? children : void 0,
|
|
@@ -2955,28 +3390,15 @@ function KeepStatus({
|
|
|
2955
3390
|
"data-keepkit": "status",
|
|
2956
3391
|
role,
|
|
2957
3392
|
"aria-live": rootProps["aria-live"] ?? "polite",
|
|
2958
|
-
"data-state":
|
|
2959
|
-
"data-loading":
|
|
3393
|
+
"data-state": view.state.status,
|
|
3394
|
+
"data-loading": view.state.status === "loading" || view.state.status === "saving" || view.state.status === "syncing" ? "true" : void 0
|
|
2960
3395
|
},
|
|
2961
3396
|
body,
|
|
2962
3397
|
"KeepStatus"
|
|
2963
3398
|
);
|
|
2964
3399
|
}
|
|
2965
3400
|
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]);
|
|
3401
|
+
const message = useKeepAnnouncements(messages);
|
|
2980
3402
|
return /* @__PURE__ */ jsx20(
|
|
2981
3403
|
"div",
|
|
2982
3404
|
{
|
|
@@ -2991,26 +3413,22 @@ function KeepAnnouncements({ messages, ...props }) {
|
|
|
2991
3413
|
);
|
|
2992
3414
|
}
|
|
2993
3415
|
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
3416
|
|
|
3011
3417
|
// src/theme.tsx
|
|
3012
3418
|
import { cloneElement as cloneElement2, isValidElement as isValidElement7 } from "react";
|
|
3013
3419
|
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
3420
|
+
var keepThemeNames = [
|
|
3421
|
+
"default",
|
|
3422
|
+
"ocean",
|
|
3423
|
+
"forest",
|
|
3424
|
+
"sunset",
|
|
3425
|
+
"lavender",
|
|
3426
|
+
"compact",
|
|
3427
|
+
"minimal",
|
|
3428
|
+
"rounded",
|
|
3429
|
+
"high-contrast",
|
|
3430
|
+
"dark"
|
|
3431
|
+
];
|
|
3014
3432
|
function KeepThemeProvider({
|
|
3015
3433
|
children,
|
|
3016
3434
|
theme = "default",
|
|
@@ -3223,6 +3641,7 @@ export {
|
|
|
3223
3641
|
createStorageAdapter,
|
|
3224
3642
|
getKeepLocaleLabels,
|
|
3225
3643
|
isAllSelected,
|
|
3644
|
+
keepThemeNames,
|
|
3226
3645
|
toggleSelectAll,
|
|
3227
3646
|
useKeepContext7 as useKeepContext,
|
|
3228
3647
|
useKeepItem5 as useKeepItem,
|