@ai-matrx/vault 0.1.22 → 0.1.24
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/CHANGELOG.md +12 -0
- package/dist/index.cjs +143 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +143 -73
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.24
|
|
4
|
+
|
|
5
|
+
- **A copy or reveal that needs "Confirm it's you" is never silent.** Closing the prompt says "Not confirmed, so nothing was done" once, from `IdentityGate` (every caller already stays quiet about a cancel via `wasCancelled`), so the row buttons, the row's More menu, a host's right-click menu (`rowMenuEntriesAt`) and the keyboard all end in either the copy or a message. Guard: `src/review-0-1-23.test.tsx` § 1. Consumer action: none (a host that passes `notify` receives the line with kind `info`).
|
|
6
|
+
- **The authenticator's own settings are never copy entries or search hits.** `totp_enabled` / `totp_digits` / `totp_period` / `totp_algorithm` / issuer / label and the `mfa_method` marker are internal (`isInternalCustomKey`, `humanCustomFields`); the row menu no longer offers "Copy authenticator digits…". They are still kept on save. Guard: § 2. Consumer action: none.
|
|
7
|
+
- **Search reads everything an item says about itself, never a secret**: name, description, type label and key, the type's family ("AI & model providers"), tags, websites, every field's label, key and env key, and the person's own plaintext custom fields (`matchesQuery(item, query, familyLabel?)`). Guard: § 3. Consumer action: none (a host calling `matchesQuery` may pass the family label).
|
|
8
|
+
- **The old type families are back, nested under their category.** A category holding more than one family lists them indented beneath it while it is open (sidebar) and after it in the compact Show picker (`family:<family>` view, `ViewEntry.families`, `itemFamily`, `familyLabel`; `viewOptions(..., current)`). Guard: § 4. Consumer action: none.
|
|
9
|
+
- **A shown value says when it hides**: a quiet "Hides in Ns" beside every revealed secret, earlier value and recovery code set (`HidesIn`, `useTransientSecret().until`, `useSecondsLeft`). Guard: § 5. Consumer action: none.
|
|
10
|
+
|
|
11
|
+
## 0.1.23
|
|
12
|
+
|
|
13
|
+
- **The Vault and Show pickers read in full at every compact width (640-1119px).** They no longer share a row with the Authenticator and More buttons (which squeezed each to ~90px: "All ite…", long vault names cut): the pickers take the whole row (each at least 11rem; one above the other in the 19rem split column, side by side once there is room), and the Authenticator and More buttons moved onto the search row beside the + (`ItemList` `actions`; Trash, Authenticator and the extension's page view carry them on their own search row). Nothing removed, every button one press away as before. Guard: `src/parity-review-0-1-22.test.tsx` § 2. Consumer action: none.
|
|
14
|
+
|
|
3
15
|
## 0.1.22
|
|
4
16
|
|
|
5
17
|
- **A member's credential shared into an organization is the organization's, never "Personal".** An item in an organization's list is the viewer's own only when the viewer owns it (`platform.list_my_organizations` `user_id`, else the owner of the person's own list); a member's own credential shared into the organization is attributed to that organization (`VaultOwner.memberShared`), counted there, its badge says "A member's own credential, shared into <org>", and Share / Delete show disabled with the reason (`manageUnavailableReason`, `ownerDetail`); Copy stays offered to an editor. Consumer action: none.
|
package/dist/index.cjs
CHANGED
|
@@ -489,6 +489,7 @@ function needsRecentSignIn(e) {
|
|
|
489
489
|
// src/auth/IdentityGate.tsx
|
|
490
490
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
491
491
|
var CANCELLED = "Not confirmed.";
|
|
492
|
+
var NOT_CONFIRMED_NOTICE = "Not confirmed, so nothing was done";
|
|
492
493
|
var Ctx2 = (0, import_react2.createContext)((run) => run());
|
|
493
494
|
function useConfirmed() {
|
|
494
495
|
return (0, import_react2.useContext)(Ctx2);
|
|
@@ -497,6 +498,7 @@ var PROVIDER_LABEL = { google: "Google", github: "GitHub", azure: "Microsoft", a
|
|
|
497
498
|
var providerLabel = (p) => PROVIDER_LABEL[p] ?? p.charAt(0).toUpperCase() + p.slice(1);
|
|
498
499
|
function IdentityGate({ identity, children }) {
|
|
499
500
|
const [asking, setAsking] = (0, import_react2.useState)(false);
|
|
501
|
+
const notify = useNotify();
|
|
500
502
|
const waiters = (0, import_react2.useRef)([]);
|
|
501
503
|
const [returnProblem, setReturnProblem] = (0, import_react2.useState)(null);
|
|
502
504
|
(0, import_react2.useEffect)(() => {
|
|
@@ -527,6 +529,7 @@ function IdentityGate({ identity, children }) {
|
|
|
527
529
|
waiters.current = [];
|
|
528
530
|
setAsking(false);
|
|
529
531
|
for (const x of w) ok ? x.resolve() : x.reject(new Error(CANCELLED));
|
|
532
|
+
if (!ok && w.length > 0) notify(NOT_CONFIRMED_NOTICE, "info");
|
|
530
533
|
};
|
|
531
534
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Ctx2.Provider, { value: confirmed, children: [
|
|
532
535
|
children,
|
|
@@ -639,22 +642,35 @@ function useTransientSecret(given) {
|
|
|
639
642
|
const fromHost = (0, import_react3.useContext)(RevealMs);
|
|
640
643
|
const ms = given ?? fromHost;
|
|
641
644
|
const [value, setValue] = (0, import_react3.useState)(null);
|
|
645
|
+
const [until, setUntil] = (0, import_react3.useState)(null);
|
|
642
646
|
const timer2 = (0, import_react3.useRef)(null);
|
|
643
647
|
const clear = (0, import_react3.useCallback)(() => {
|
|
644
648
|
if (timer2.current) clearTimeout(timer2.current);
|
|
645
649
|
timer2.current = null;
|
|
646
650
|
setValue(null);
|
|
651
|
+
setUntil(null);
|
|
647
652
|
}, []);
|
|
648
653
|
const show = (0, import_react3.useCallback)(
|
|
649
654
|
(next) => {
|
|
650
655
|
if (timer2.current) clearTimeout(timer2.current);
|
|
651
656
|
setValue(next);
|
|
657
|
+
setUntil(Date.now() + ms);
|
|
652
658
|
timer2.current = setTimeout(clear, ms);
|
|
653
659
|
},
|
|
654
660
|
[clear, ms]
|
|
655
661
|
);
|
|
656
662
|
(0, import_react3.useEffect)(() => clear, [clear]);
|
|
657
|
-
return { value, show, clear };
|
|
663
|
+
return { value, show, clear, until };
|
|
664
|
+
}
|
|
665
|
+
function useSecondsLeft(until) {
|
|
666
|
+
const [now, setNow] = (0, import_react3.useState)(() => Date.now());
|
|
667
|
+
(0, import_react3.useEffect)(() => {
|
|
668
|
+
if (until === null) return;
|
|
669
|
+
setNow(Date.now());
|
|
670
|
+
const t = setInterval(() => setNow(Date.now()), 1e3);
|
|
671
|
+
return () => clearInterval(t);
|
|
672
|
+
}, [until]);
|
|
673
|
+
return until === null ? null : Math.max(0, Math.ceil((until - now) / 1e3));
|
|
658
674
|
}
|
|
659
675
|
function useElementWidth(ref) {
|
|
660
676
|
const [width, setWidth] = (0, import_react3.useState)(0);
|
|
@@ -747,6 +763,15 @@ function SecretText({ value, multiline }) {
|
|
|
747
763
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "break-all font-mono text-sm tracking-wide", children: [...value].map((ch, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: /[0-9]/.test(ch) ? "text-primary" : /[^A-Za-z0-9]/.test(ch) ? "text-warning" : void 0, children: ch }, i)) });
|
|
748
764
|
}
|
|
749
765
|
var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
|
|
766
|
+
function HidesIn({ until }) {
|
|
767
|
+
const left = useSecondsLeft(until);
|
|
768
|
+
if (left === null) return null;
|
|
769
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "shrink-0 px-1 text-xs tabular-nums text-muted-foreground", "data-hides-in": "", title: "Shown values hide on their own", children: [
|
|
770
|
+
"Hides in ",
|
|
771
|
+
left,
|
|
772
|
+
"s"
|
|
773
|
+
] });
|
|
774
|
+
}
|
|
750
775
|
function SecretRow({ port, item, field, extra, onTurnOn }) {
|
|
751
776
|
const confirmed = useConfirmed();
|
|
752
777
|
const copyValue2 = useCopy();
|
|
@@ -802,6 +827,7 @@ function SecretRow({ port, item, field, extra, onTurnOn }) {
|
|
|
802
827
|
actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
803
828
|
extra,
|
|
804
829
|
off && onTurnOn ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TurnOnButton, { label: field.label, onTurnOn }) : null,
|
|
830
|
+
!visible && secret.value !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HidesIn, { until: secret.until }) : null,
|
|
805
831
|
canSee && !visible ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: secret.value !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeGlyph, {}), "aria-label": secret.value !== null ? `Hide ${field.label}` : `Show ${field.label}`, disabled: busy, onClick: () => void toggle2() }) : null,
|
|
806
832
|
canSee ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons2.CopyIcon, {}), "aria-label": `Copy ${field.label}`, disabled: busy, onClick: () => void copy() }) : null
|
|
807
833
|
] }),
|
|
@@ -976,15 +1002,28 @@ function inView(item, view) {
|
|
|
976
1002
|
return true;
|
|
977
1003
|
}
|
|
978
1004
|
var AUTHENTICATOR_WORDS = ["authenticator", "2fa", "two-factor", "two factor", "totp", "one-time password", "verification code", "2-step"];
|
|
979
|
-
function matchesQuery(item, query) {
|
|
1005
|
+
function matchesQuery(item, query, familyLabel2) {
|
|
980
1006
|
const q = query.trim().toLowerCase();
|
|
981
1007
|
if (!q) return true;
|
|
982
1008
|
if (item.authenticator && AUTHENTICATOR_WORDS.some((w) => w === q || q.length >= 3 && w.startsWith(q))) return true;
|
|
983
|
-
return [
|
|
1009
|
+
return [
|
|
1010
|
+
item.name,
|
|
1011
|
+
item.description,
|
|
1012
|
+
item.definitionLabel,
|
|
1013
|
+
item.definitionKey,
|
|
1014
|
+
familyLabel2 ?? "",
|
|
1015
|
+
...item.tags,
|
|
1016
|
+
...item.loginUrls,
|
|
1017
|
+
...item.fields.filter((f) => !isAuthenticatorStorage(f.key)).flatMap((f) => [f.label, f.key, f.envKey ?? ""]),
|
|
1018
|
+
...humanCustomFields(item).map((c) => `${c.label} ${c.value}`)
|
|
1019
|
+
].some((h) => h !== "" && h.toLowerCase().includes(q));
|
|
984
1020
|
}
|
|
985
1021
|
var HIDDEN_ITEM_TAG = "matrx:hidden";
|
|
986
1022
|
var isHiddenItem = (item) => (item.tags ?? []).includes(HIDDEN_ITEM_TAG);
|
|
987
1023
|
var isAuthenticatorStorage = (key) => key === "totp_seed" || key.startsWith("totp_") || key === "authenticator_last_time_step";
|
|
1024
|
+
var MFA_METHOD_KEY = "mfa_method";
|
|
1025
|
+
var isInternalCustomKey = (key) => isAuthenticatorStorage(key) || key === MFA_METHOD_KEY;
|
|
1026
|
+
var humanCustomFields = (item) => item.custom.filter((c) => !isInternalCustomKey(c.key));
|
|
988
1027
|
function loginSiteOf(item) {
|
|
989
1028
|
const raw = item.loginUrls[0];
|
|
990
1029
|
if (!raw) return "";
|
|
@@ -1602,6 +1641,7 @@ function HistoryRow({ item, port, page, entry, label, onRestored, onConflict, on
|
|
|
1602
1641
|
{
|
|
1603
1642
|
label: when,
|
|
1604
1643
|
actions: entry.available ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1644
|
+
secret.value !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HidesIn, { until: secret.until }) : null,
|
|
1605
1645
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_controls4.Button, { variant: "quiet", icon: secret.value !== null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EyeGlyph, {}), "aria-label": secret.value !== null ? "Hide" : "Show", disabled: busy, onClick: () => void toggle2() }),
|
|
1606
1646
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_controls4.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons3.CopyIcon, {}), "aria-label": "Copy", disabled: busy, onClick: () => void copy() }),
|
|
1607
1647
|
canRestore(page, entry) ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_controls4.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RestoreGlyph, {}), "aria-label": "Restore", disabled: busy, onClick: () => void restore() }) : null
|
|
@@ -2137,7 +2177,6 @@ var import_design_system9 = require("@ai-matrx/design-system");
|
|
|
2137
2177
|
var import_confirm_opener2 = require("@ai-matrx/kit/confirm-opener");
|
|
2138
2178
|
|
|
2139
2179
|
// src/authenticator/port.ts
|
|
2140
|
-
var MFA_METHOD_KEY = "mfa_method";
|
|
2141
2180
|
var OTP_SEED_KEY = "totp_seed";
|
|
2142
2181
|
function mfaMethodOf(item) {
|
|
2143
2182
|
const v = item.custom.find((c) => c.key === MFA_METHOD_KEY)?.value;
|
|
@@ -5261,6 +5300,7 @@ function RecoveryCodesRow({ item, field, port, onChanged }) {
|
|
|
5261
5300
|
label: "Recovery codes",
|
|
5262
5301
|
top: codes !== null,
|
|
5263
5302
|
actions: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
5303
|
+
codes ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(HidesIn, { until: secret.until }) : null,
|
|
5264
5304
|
canSee ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_controls17.Button, { variant: "quiet", icon: codes ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeGlyph, {}), "aria-label": codes ? "Hide codes" : "Show codes", disabled: busy, onClick: () => void toggle2() }) : null,
|
|
5265
5305
|
canEdit && !sealed ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_controls17.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PencilGlyph, {}), "aria-label": "Replace all codes", disabled: busy, onClick: () => setDraft("") }) : null
|
|
5266
5306
|
] }),
|
|
@@ -6196,8 +6236,8 @@ function ItemPane(props) {
|
|
|
6196
6236
|
const patchFieldSettings = (key, p) => setDraft((d) => ({ ...d, fieldSettings: { ...d.fieldSettings, [key]: { ...d.fieldSettings?.[key], ...p } } }));
|
|
6197
6237
|
const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
|
|
6198
6238
|
const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
|
|
6199
|
-
const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) &&
|
|
6200
|
-
const viewCustom = (item?.custom ?? []).filter((c) => !declaredKeys.has(c.key) &&
|
|
6239
|
+
const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) && !isInternalCustomKey(c.key));
|
|
6240
|
+
const viewCustom = (item?.custom ?? []).filter((c) => !declaredKeys.has(c.key) && !isInternalCustomKey(c.key));
|
|
6201
6241
|
const isLogin = (item?.definitionKey ?? definition?.key) === "website_login" || (item?.definitionKey ?? definition?.key) === "username_password";
|
|
6202
6242
|
const typeLabel = definition?.label ?? item?.definitionLabel ?? "";
|
|
6203
6243
|
const recoveryDeclared = declaredKeys.has(RECOVERY_CODES_KEY);
|
|
@@ -6556,7 +6596,7 @@ function subtitleOf(item, withOwner) {
|
|
|
6556
6596
|
const what = loginSiteOf(item) || item.definitionLabel;
|
|
6557
6597
|
return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
|
|
6558
6598
|
}
|
|
6559
|
-
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems, stillLoading, favorites, onToggleFavorite }) {
|
|
6599
|
+
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, actions, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems, stillLoading, favorites, onToggleFavorite }) {
|
|
6560
6600
|
const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
|
|
6561
6601
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6562
6602
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
@@ -6584,6 +6624,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6584
6624
|
header,
|
|
6585
6625
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
6586
6626
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.SearchField, { value: query, onChange: (e) => onQuery(e.target.value), onKeyDown, placeholder: `Search ${title}`, className: "min-w-0 flex-1", "aria-label": "Search the vault" }),
|
|
6627
|
+
actions,
|
|
6587
6628
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(PlusGlyph, {}), "aria-label": "New item", onClick: onNew })
|
|
6588
6629
|
] }),
|
|
6589
6630
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center justify-between gap-2 px-1", children: [
|
|
@@ -6646,7 +6687,7 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6646
6687
|
const copyCode = () => code && void run("One-time code", async () => (await port.authenticator.code(item)).code, true);
|
|
6647
6688
|
const copyName = () => void copy(item.name, "Name", { secret: false });
|
|
6648
6689
|
const others = otherCopyFields(item, user, secret);
|
|
6649
|
-
const custom = canUse ? item.
|
|
6690
|
+
const custom = canUse ? humanCustomFields(item).filter((c) => c.value) : [];
|
|
6650
6691
|
const hostEntries = extra?.(item) ?? [];
|
|
6651
6692
|
const copyEntries = [
|
|
6652
6693
|
...user ? [{ id: "copy:username", kind: "copy", label: `Copy ${user.label.toLowerCase()}`, onSelect: copyUser }] : [],
|
|
@@ -8246,20 +8287,33 @@ function VaultSelect({ value, choices, onChange }) {
|
|
|
8246
8287
|
}
|
|
8247
8288
|
var CATEGORY_GLYPHS = { logins: import_icons16.GlobeIcon, passkeys: ShieldGlyph, api: KeyGlyph, oauth: PlugGlyph, infra: DatabaseGlyph, env: TerminalGlyph, files: FileGlyph, other: NoteGlyph };
|
|
8248
8289
|
var familyOf = (key, definitions) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
8290
|
+
function itemFamily(item, definitions) {
|
|
8291
|
+
return familyOf(item.definitionKey, definitions);
|
|
8292
|
+
}
|
|
8293
|
+
var familyLabel = (family) => FAMILY_LABELS[family] ?? family.replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
|
|
8249
8294
|
function itemCategory(item, definitions) {
|
|
8250
8295
|
return categoryOf(item.definitionKey, familyOf(item.definitionKey, definitions));
|
|
8251
8296
|
}
|
|
8252
8297
|
function buildViews(items, definitions, favoriteIds2, recentIds2) {
|
|
8253
8298
|
const byCat = /* @__PURE__ */ new Map();
|
|
8299
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
8254
8300
|
const byTag = /* @__PURE__ */ new Map();
|
|
8255
8301
|
for (const i of items) {
|
|
8256
8302
|
const c = itemCategory(i, definitions);
|
|
8257
8303
|
byCat.set(c, (byCat.get(c) ?? 0) + 1);
|
|
8304
|
+
const fam = itemFamily(i, definitions);
|
|
8305
|
+
const fams = byFamily.get(c) ?? /* @__PURE__ */ new Map();
|
|
8306
|
+
fams.set(fam, (fams.get(fam) ?? 0) + 1);
|
|
8307
|
+
byFamily.set(c, fams);
|
|
8258
8308
|
for (const t of i.tags) byTag.set(t, (byTag.get(t) ?? 0) + 1);
|
|
8259
8309
|
}
|
|
8260
8310
|
const ids = new Set(items.map((i) => i.id));
|
|
8261
8311
|
return {
|
|
8262
|
-
categories: CATEGORIES.filter((c) => byCat.has(c.id)).map((c) =>
|
|
8312
|
+
categories: CATEGORIES.filter((c) => byCat.has(c.id)).map((c) => {
|
|
8313
|
+
const fams = [...(byFamily.get(c.id) ?? /* @__PURE__ */ new Map()).entries()];
|
|
8314
|
+
const families = fams.length > 1 ? fams.map(([f, count2]) => ({ view: `family:${f}`, label: familyLabel(f), count: count2, glyph: CATEGORY_GLYPHS[c.id] })).sort((a, b) => a.label.localeCompare(b.label)) : void 0;
|
|
8315
|
+
return { view: `category:${c.id}`, label: c.label, count: byCat.get(c.id) ?? 0, glyph: CATEGORY_GLYPHS[c.id], ...families ? { families } : {} };
|
|
8316
|
+
}),
|
|
8263
8317
|
tags: [...byTag.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([t, count2]) => ({ view: `tag:${t}`, label: t, count: count2, glyph: TagGlyph })),
|
|
8264
8318
|
favorites: favoriteIds2 ? [...favoriteIds2].filter((id) => ids.has(id)).length : void 0,
|
|
8265
8319
|
authenticator: items.filter((i) => i.authenticator).length,
|
|
@@ -8272,21 +8326,27 @@ function viewLabel(view, views) {
|
|
|
8272
8326
|
if (view === "authenticator") return AUTHENTICATOR_LABEL;
|
|
8273
8327
|
if (view === "trash") return "Trash";
|
|
8274
8328
|
if (view === "page") return "This page";
|
|
8329
|
+
if (view.startsWith("family:")) return familyLabel(view.slice(7));
|
|
8275
8330
|
return [...views.categories, ...views.tags].find((v) => v.view === view)?.label ?? "All items";
|
|
8276
8331
|
}
|
|
8277
|
-
function
|
|
8332
|
+
function openCategory(view, views) {
|
|
8333
|
+
return views.categories.find((c) => c.view === view || c.families?.some((f) => f.view === view));
|
|
8334
|
+
}
|
|
8335
|
+
function viewOptions(views, allCount, deletedCount, hasFavorites, hasRecents, hasPage = false, current = "all") {
|
|
8336
|
+
const open = openCategory(current, views);
|
|
8278
8337
|
return [
|
|
8279
8338
|
...hasPage ? [{ value: "page", label: "This page" }] : [],
|
|
8280
8339
|
{ value: "all", label: "Every category", meta: String(allCount) },
|
|
8281
8340
|
...hasFavorites ? [{ value: "favorites", label: "Favorites", meta: views.favorites === void 0 ? "" : String(views.favorites) }] : [],
|
|
8282
8341
|
...hasRecents ? [{ value: "recent", label: "Recently opened", meta: views.recent === void 0 ? "" : String(views.recent) }] : [],
|
|
8283
8342
|
{ value: "authenticator", label: AUTHENTICATOR_LABEL, meta: String(views.authenticator) },
|
|
8284
|
-
|
|
8343
|
+
// The open category's families follow it, indented by their sign; other categories stay one line.
|
|
8344
|
+
...views.categories.flatMap((v) => [{ value: v.view, label: v.label, meta: String(v.count) }, ...v === open ? (v.families ?? []).map((f) => ({ value: f.view, label: `\xB7 ${f.label}`, meta: String(f.count) })) : []]),
|
|
8285
8345
|
...views.tags.map((v) => ({ value: v.view, label: `#${v.label}`, meta: String(v.count) })),
|
|
8286
8346
|
{ value: "trash", label: "Trash", meta: String(deletedCount) }
|
|
8287
8347
|
];
|
|
8288
8348
|
}
|
|
8289
|
-
function Entry({ on, glyph: Icon, label, count: count2, onClick }) {
|
|
8349
|
+
function Entry({ on, glyph: Icon, label, count: count2, onClick, nested = false }) {
|
|
8290
8350
|
const desktop = (0, import_design_system25.useLook)() === "desktop";
|
|
8291
8351
|
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
8292
8352
|
"button",
|
|
@@ -8294,9 +8354,10 @@ function Entry({ on, glyph: Icon, label, count: count2, onClick }) {
|
|
|
8294
8354
|
type: "button",
|
|
8295
8355
|
"aria-current": on ? "page" : void 0,
|
|
8296
8356
|
onClick,
|
|
8297
|
-
|
|
8357
|
+
"data-nested": nested ? "" : void 0,
|
|
8358
|
+
className: (0, import_design_system25.cn)("flex h-8 w-full items-center gap-2.5 rounded-lg px-2.5 text-left text-sm", nested && "pl-9", on ? desktop ? "bg-sidebar-accent text-primary" : "bg-sidebar-accent font-medium text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent"),
|
|
8298
8359
|
children: [
|
|
8299
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Icon, { className: "size-4 shrink-0" }),
|
|
8360
|
+
nested ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Icon, { className: "size-4 shrink-0" }),
|
|
8300
8361
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate", children: label }),
|
|
8301
8362
|
count2 !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "text-xs tabular-nums text-muted-foreground", children: count2 }) : null
|
|
8302
8363
|
]
|
|
@@ -8324,7 +8385,10 @@ function VaultSidebar({ vault, choices, onVault, view, allCount, deletedCount, v
|
|
|
8324
8385
|
hasRecents ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Entry, { on: view === "recent", glyph: ClockGlyph, label: "Recently opened", count: views.recent, onClick: () => onView("recent") }) : null,
|
|
8325
8386
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Entry, { on: view === "authenticator", glyph: LockGlyph, label: AUTHENTICATOR_LABEL, count: views.authenticator, onClick: () => onView("authenticator") })
|
|
8326
8387
|
] }),
|
|
8327
|
-
views.categories.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Group, { title: "Categories", children: views.categories.map((e) => /* @__PURE__ */ (0, import_jsx_runtime39.
|
|
8388
|
+
views.categories.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Group, { title: "Categories", children: views.categories.map((e) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-0.5", children: [
|
|
8389
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Entry, { on: view === e.view, glyph: e.glyph, label: e.label, count: e.count, onClick: () => onView(e.view) }),
|
|
8390
|
+
e === openCategory(view, views) ? e.families?.map((f) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Entry, { nested: true, on: view === f.view, glyph: f.glyph, label: f.label, count: f.count, onClick: () => onView(f.view) }, f.view)) : null
|
|
8391
|
+
] }, e.view)) }) : null,
|
|
8328
8392
|
views.tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-0.5", children: [
|
|
8329
8393
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("button", { type: "button", "aria-expanded": tagsOpen, onClick: () => setTagsOpen((o) => !o), className: "flex h-7 items-center gap-1 rounded-md px-2.5 text-left text-xs font-medium text-muted-foreground hover:text-foreground", children: [
|
|
8330
8394
|
tagsOpen ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ChevronDownGlyph, { className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ChevronRightGlyph, { className: "size-3.5" }),
|
|
@@ -8589,10 +8653,11 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8589
8653
|
const views = (0, import_react42.useMemo)(() => buildViews(inThisVault, definitions, favorites, recents), [inThisVault, definitions, favorites, recents]);
|
|
8590
8654
|
const visible = (0, import_react42.useMemo)(() => {
|
|
8591
8655
|
const filtered = inThisVault.filter((i) => {
|
|
8592
|
-
if (!matchesQuery(i, loc.q)) return false;
|
|
8656
|
+
if (!matchesQuery(i, loc.q, familyLabel(itemFamily(i, definitions)))) return false;
|
|
8593
8657
|
if (loc.view === "favorites") return favorites?.has(i.id) ?? false;
|
|
8594
8658
|
if (loc.view === "recent") return recents?.includes(i.id) ?? false;
|
|
8595
8659
|
if (loc.view.startsWith("category:")) return itemCategory(i, definitions) === loc.view.slice(9);
|
|
8660
|
+
if (loc.view.startsWith("family:")) return itemFamily(i, definitions) === loc.view.slice(7);
|
|
8596
8661
|
return inView(i, loc.view);
|
|
8597
8662
|
});
|
|
8598
8663
|
if (loc.view === "recent" && recents) return [...filtered].sort((a, b) => recents.indexOf(a.id) - recents.indexOf(b.id));
|
|
@@ -8644,65 +8709,66 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8644
8709
|
}
|
|
8645
8710
|
const hasFavorites = port.lists.favorites !== void 0;
|
|
8646
8711
|
const title = loc.vault === "all" && loc.view === "all" ? "All items" : viewLabel(loc.view, views);
|
|
8647
|
-
const compactHeader = mode === "full" ? void 0 : (
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.
|
|
8665
|
-
|
|
8666
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.
|
|
8667
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
] }) : null
|
|
8699
|
-
] })
|
|
8712
|
+
const compactHeader = mode === "full" ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "grid grid-cols-[repeat(auto-fit,minmax(11rem,1fr))] gap-2", "data-vault-compact-header": "", children: [
|
|
8713
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(VaultSelect, { value: loc.vault, choices, onChange: (vault) => go({ vault, itemId: null, panel: null }) }),
|
|
8714
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_controls35.Select, { value: loc.view, onValueChange: (view) => go({ view, itemId: null, panel: null }), options: viewOptions(views, inThisVault.length, deleted.length, hasFavorites, port.lists.recents !== void 0, host.page !== void 0, loc.view), "aria-label": "Show" })
|
|
8715
|
+
] });
|
|
8716
|
+
const compactActions = mode === "full" ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex shrink-0 items-center gap-2", "data-vault-compact-actions": "", children: [
|
|
8717
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
8718
|
+
import_controls35.Button,
|
|
8719
|
+
{
|
|
8720
|
+
variant: loc.view === "authenticator" ? "outline" : "quiet",
|
|
8721
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(LockGlyph, {}),
|
|
8722
|
+
"aria-label": AUTHENTICATOR_LABEL,
|
|
8723
|
+
"aria-pressed": loc.view === "authenticator",
|
|
8724
|
+
title: AUTHENTICATOR_LABEL,
|
|
8725
|
+
onClick: () => go({ view: loc.view === "authenticator" ? "all" : "authenticator", itemId: null, mode: "view", panel: null })
|
|
8726
|
+
}
|
|
8727
|
+
),
|
|
8728
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenu, { children: [
|
|
8729
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_design_system26.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_controls35.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(MoreGlyph, {}), "aria-label": "Vault actions" }) }),
|
|
8730
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuContent, { align: "end", children: [
|
|
8731
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: () => go({ panel: "import" }), children: [
|
|
8732
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ImportGlyph, { className: "size-4" }),
|
|
8733
|
+
"Import"
|
|
8734
|
+
] }),
|
|
8735
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: () => go({ panel: "export" }), children: [
|
|
8736
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ExportGlyph, { className: "size-4" }),
|
|
8737
|
+
"Export"
|
|
8738
|
+
] }),
|
|
8739
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: () => go({ panel: "backup" }), children: [
|
|
8740
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SafeGlyph, { className: "size-4" }),
|
|
8741
|
+
"Backup"
|
|
8742
|
+
] }),
|
|
8743
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: () => go({ panel: "devices" }), children: [
|
|
8744
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ShieldGlyph, { className: "size-4" }),
|
|
8745
|
+
"Browsers that fill"
|
|
8746
|
+
] }),
|
|
8747
|
+
host.nativeImport ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: () => go({ panel: "import-passkeys" }), children: [
|
|
8748
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ImportGlyph, { className: "size-4" }),
|
|
8749
|
+
"Import passkeys"
|
|
8750
|
+
] }) : null,
|
|
8751
|
+
host.nativeProvider && host.nativeProvider.status !== "unavailable" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: host.nativeProvider.openSetup, children: [
|
|
8752
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ShieldGlyph, { className: "size-4" }),
|
|
8753
|
+
"System AutoFill"
|
|
8754
|
+
] }) : null,
|
|
8755
|
+
host.lock?.lock ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: () => void host.lock?.lock?.(), children: [
|
|
8756
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(LockGlyph, { className: "size-4" }),
|
|
8757
|
+
"Lock"
|
|
8758
|
+
] }) : null,
|
|
8759
|
+
host.openWebVault ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_design_system26.DropdownMenuItem, { onSelect: host.openWebVault, children: [
|
|
8760
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ExternalGlyph, { className: "size-4" }),
|
|
8761
|
+
"Open on the web"
|
|
8762
|
+
] }) : null
|
|
8700
8763
|
] })
|
|
8701
8764
|
] })
|
|
8702
|
-
);
|
|
8765
|
+
] });
|
|
8703
8766
|
const viewHeader = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
8704
8767
|
compactHeader,
|
|
8705
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.
|
|
8768
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
8769
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_controls35.SearchField, { value: loc.q, onChange: (e) => go({ q: e.target.value }, "replace"), placeholder: `Search ${viewLabel(loc.view, views)}`, "aria-label": `Search ${viewLabel(loc.view, views)}`, className: "min-w-0 flex-1" }),
|
|
8770
|
+
compactActions
|
|
8771
|
+
] })
|
|
8706
8772
|
] });
|
|
8707
8773
|
const viewProps = { port, host, items: inThisVault, deleted, definitions, query: loc.q, onOpen: (id) => go({ view: "all", itemId: id, mode: "view", panel: null }), onChanged: load, header: viewHeader };
|
|
8708
8774
|
const pageView = loc.view === "page" && host.page && !loc.q ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
@@ -8720,7 +8786,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8720
8786
|
onNewForPage: (url) => go({ mode: "new", kind: null, url, itemId: null, panel: null }),
|
|
8721
8787
|
onBrowse: () => go({ view: "all", itemId: null }),
|
|
8722
8788
|
onChanged: load,
|
|
8723
|
-
...compactHeader ? { header:
|
|
8789
|
+
...compactHeader ? { header: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
8790
|
+
compactHeader,
|
|
8791
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex justify-end", children: compactActions })
|
|
8792
|
+
] }) } : {}
|
|
8724
8793
|
}
|
|
8725
8794
|
) : null;
|
|
8726
8795
|
const specialView = pageView ?? (loc.view === "authenticator" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(AuthenticatorView, { ...viewProps }) : loc.view === "trash" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(TrashView, { ...viewProps }) : null);
|
|
@@ -8747,7 +8816,8 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8747
8816
|
rowContextMenu: host.rowContextMenu ?? "package",
|
|
8748
8817
|
...host.rowMenuItems ? { rowMenuItems: host.rowMenuItems } : {},
|
|
8749
8818
|
...matchingIds ? { matchingIds } : {},
|
|
8750
|
-
...compactHeader ? { header: compactHeader } : {}
|
|
8819
|
+
...compactHeader ? { header: compactHeader } : {},
|
|
8820
|
+
...compactActions ? { actions: compactActions } : {}
|
|
8751
8821
|
}
|
|
8752
8822
|
);
|
|
8753
8823
|
const showingDetail = loc.mode === "new" || loc.itemId !== null;
|