@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/dist/index.d.cts
CHANGED
|
@@ -114,7 +114,7 @@ type VaultSort = "name" | "name-desc" | "website" | "updated" | "created" | "vie
|
|
|
114
114
|
type VaultSelection = "all" | "mine" | "shared" | (string & {});
|
|
115
115
|
interface VaultLocation {
|
|
116
116
|
vault: VaultSelection;
|
|
117
|
-
/** `all`, `favorites`, `authenticator`, `trash`, `category:<id>`, `type:<definitionKey>` or `tag:<tag>`. */
|
|
117
|
+
/** `all`, `favorites`, `authenticator`, `trash`, `category:<id>`, `family:<family>`, `type:<definitionKey>` or `tag:<tag>`. */
|
|
118
118
|
view: string;
|
|
119
119
|
q: string;
|
|
120
120
|
sort: VaultSort;
|
|
@@ -134,7 +134,14 @@ declare const DEFAULT_LOCATION: VaultLocation;
|
|
|
134
134
|
declare function locationFromParams(params: URLSearchParams, itemId?: string | null): VaultLocation;
|
|
135
135
|
/** Writes everything that is not a default (and not the item id, which the host places). */
|
|
136
136
|
declare function locationToParams(loc: VaultLocation): URLSearchParams;
|
|
137
|
-
|
|
137
|
+
/**
|
|
138
|
+
* The one search every list shares. It reads what an item SAYS about itself, never a secret value:
|
|
139
|
+
* name, description, type (label and key), the type's family ("AI & model providers"), tags,
|
|
140
|
+
* websites, every field's label, key and env key, and the plaintext custom fields a person wrote
|
|
141
|
+
* (label and value — the server keeps those unencrypted by design). `familyLabel` comes from the
|
|
142
|
+
* catalog the caller holds.
|
|
143
|
+
*/
|
|
144
|
+
declare function matchesQuery(item: VaultItem, query: string, familyLabel?: string): boolean;
|
|
138
145
|
/**
|
|
139
146
|
* The authenticator's own storage on an item (the seed, digits, period, issuer…): shown only as the
|
|
140
147
|
* one "One-time password" row, never as loose fields — and always kept when the item is saved.
|
|
@@ -423,6 +430,7 @@ interface AuthenticatorEntry {
|
|
|
423
430
|
* `mfa_method` so agents know to hand control back at the verification step.
|
|
424
431
|
*/
|
|
425
432
|
type MfaMethod = "sms" | "push_or_other";
|
|
433
|
+
|
|
426
434
|
type ItemRef = Pick<VaultItem, "id" | "owner">;
|
|
427
435
|
/**
|
|
428
436
|
* One-time passwords: every code, enrol by key / otpauth address / QR image, enable, disable,
|
package/dist/index.d.ts
CHANGED
|
@@ -114,7 +114,7 @@ type VaultSort = "name" | "name-desc" | "website" | "updated" | "created" | "vie
|
|
|
114
114
|
type VaultSelection = "all" | "mine" | "shared" | (string & {});
|
|
115
115
|
interface VaultLocation {
|
|
116
116
|
vault: VaultSelection;
|
|
117
|
-
/** `all`, `favorites`, `authenticator`, `trash`, `category:<id>`, `type:<definitionKey>` or `tag:<tag>`. */
|
|
117
|
+
/** `all`, `favorites`, `authenticator`, `trash`, `category:<id>`, `family:<family>`, `type:<definitionKey>` or `tag:<tag>`. */
|
|
118
118
|
view: string;
|
|
119
119
|
q: string;
|
|
120
120
|
sort: VaultSort;
|
|
@@ -134,7 +134,14 @@ declare const DEFAULT_LOCATION: VaultLocation;
|
|
|
134
134
|
declare function locationFromParams(params: URLSearchParams, itemId?: string | null): VaultLocation;
|
|
135
135
|
/** Writes everything that is not a default (and not the item id, which the host places). */
|
|
136
136
|
declare function locationToParams(loc: VaultLocation): URLSearchParams;
|
|
137
|
-
|
|
137
|
+
/**
|
|
138
|
+
* The one search every list shares. It reads what an item SAYS about itself, never a secret value:
|
|
139
|
+
* name, description, type (label and key), the type's family ("AI & model providers"), tags,
|
|
140
|
+
* websites, every field's label, key and env key, and the plaintext custom fields a person wrote
|
|
141
|
+
* (label and value — the server keeps those unencrypted by design). `familyLabel` comes from the
|
|
142
|
+
* catalog the caller holds.
|
|
143
|
+
*/
|
|
144
|
+
declare function matchesQuery(item: VaultItem, query: string, familyLabel?: string): boolean;
|
|
138
145
|
/**
|
|
139
146
|
* The authenticator's own storage on an item (the seed, digits, period, issuer…): shown only as the
|
|
140
147
|
* one "One-time password" row, never as loose fields — and always kept when the item is saved.
|
|
@@ -423,6 +430,7 @@ interface AuthenticatorEntry {
|
|
|
423
430
|
* `mfa_method` so agents know to hand control back at the verification step.
|
|
424
431
|
*/
|
|
425
432
|
type MfaMethod = "sms" | "push_or_other";
|
|
433
|
+
|
|
426
434
|
type ItemRef = Pick<VaultItem, "id" | "owner">;
|
|
427
435
|
/**
|
|
428
436
|
* One-time passwords: every code, enrol by key / otpauth address / QR image, enable, disable,
|
package/dist/index.js
CHANGED
|
@@ -419,6 +419,7 @@ function needsRecentSignIn(e) {
|
|
|
419
419
|
// src/auth/IdentityGate.tsx
|
|
420
420
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
421
421
|
var CANCELLED = "Not confirmed.";
|
|
422
|
+
var NOT_CONFIRMED_NOTICE = "Not confirmed, so nothing was done";
|
|
422
423
|
var Ctx2 = createContext2((run) => run());
|
|
423
424
|
function useConfirmed() {
|
|
424
425
|
return useContext2(Ctx2);
|
|
@@ -427,6 +428,7 @@ var PROVIDER_LABEL = { google: "Google", github: "GitHub", azure: "Microsoft", a
|
|
|
427
428
|
var providerLabel = (p) => PROVIDER_LABEL[p] ?? p.charAt(0).toUpperCase() + p.slice(1);
|
|
428
429
|
function IdentityGate({ identity, children }) {
|
|
429
430
|
const [asking, setAsking] = useState2(false);
|
|
431
|
+
const notify = useNotify();
|
|
430
432
|
const waiters = useRef2([]);
|
|
431
433
|
const [returnProblem, setReturnProblem] = useState2(null);
|
|
432
434
|
useEffect2(() => {
|
|
@@ -457,6 +459,7 @@ function IdentityGate({ identity, children }) {
|
|
|
457
459
|
waiters.current = [];
|
|
458
460
|
setAsking(false);
|
|
459
461
|
for (const x of w) ok ? x.resolve() : x.reject(new Error(CANCELLED));
|
|
462
|
+
if (!ok && w.length > 0) notify(NOT_CONFIRMED_NOTICE, "info");
|
|
460
463
|
};
|
|
461
464
|
return /* @__PURE__ */ jsxs3(Ctx2.Provider, { value: confirmed, children: [
|
|
462
465
|
children,
|
|
@@ -569,22 +572,35 @@ function useTransientSecret(given) {
|
|
|
569
572
|
const fromHost = useContext3(RevealMs);
|
|
570
573
|
const ms = given ?? fromHost;
|
|
571
574
|
const [value, setValue] = useState3(null);
|
|
575
|
+
const [until, setUntil] = useState3(null);
|
|
572
576
|
const timer2 = useRef3(null);
|
|
573
577
|
const clear = useCallback2(() => {
|
|
574
578
|
if (timer2.current) clearTimeout(timer2.current);
|
|
575
579
|
timer2.current = null;
|
|
576
580
|
setValue(null);
|
|
581
|
+
setUntil(null);
|
|
577
582
|
}, []);
|
|
578
583
|
const show = useCallback2(
|
|
579
584
|
(next) => {
|
|
580
585
|
if (timer2.current) clearTimeout(timer2.current);
|
|
581
586
|
setValue(next);
|
|
587
|
+
setUntil(Date.now() + ms);
|
|
582
588
|
timer2.current = setTimeout(clear, ms);
|
|
583
589
|
},
|
|
584
590
|
[clear, ms]
|
|
585
591
|
);
|
|
586
592
|
useEffect3(() => clear, [clear]);
|
|
587
|
-
return { value, show, clear };
|
|
593
|
+
return { value, show, clear, until };
|
|
594
|
+
}
|
|
595
|
+
function useSecondsLeft(until) {
|
|
596
|
+
const [now, setNow] = useState3(() => Date.now());
|
|
597
|
+
useEffect3(() => {
|
|
598
|
+
if (until === null) return;
|
|
599
|
+
setNow(Date.now());
|
|
600
|
+
const t = setInterval(() => setNow(Date.now()), 1e3);
|
|
601
|
+
return () => clearInterval(t);
|
|
602
|
+
}, [until]);
|
|
603
|
+
return until === null ? null : Math.max(0, Math.ceil((until - now) / 1e3));
|
|
588
604
|
}
|
|
589
605
|
function useElementWidth(ref) {
|
|
590
606
|
const [width, setWidth] = useState3(0);
|
|
@@ -677,6 +693,15 @@ function SecretText({ value, multiline }) {
|
|
|
677
693
|
return /* @__PURE__ */ jsx5("span", { className: "break-all font-mono text-sm tracking-wide", children: [...value].map((ch, i) => /* @__PURE__ */ jsx5("span", { className: /[0-9]/.test(ch) ? "text-primary" : /[^A-Za-z0-9]/.test(ch) ? "text-warning" : void 0, children: ch }, i)) });
|
|
678
694
|
}
|
|
679
695
|
var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
|
|
696
|
+
function HidesIn({ until }) {
|
|
697
|
+
const left = useSecondsLeft(until);
|
|
698
|
+
if (left === null) return null;
|
|
699
|
+
return /* @__PURE__ */ jsxs5("span", { className: "shrink-0 px-1 text-xs tabular-nums text-muted-foreground", "data-hides-in": "", title: "Shown values hide on their own", children: [
|
|
700
|
+
"Hides in ",
|
|
701
|
+
left,
|
|
702
|
+
"s"
|
|
703
|
+
] });
|
|
704
|
+
}
|
|
680
705
|
function SecretRow({ port, item, field, extra, onTurnOn }) {
|
|
681
706
|
const confirmed = useConfirmed();
|
|
682
707
|
const copyValue2 = useCopy();
|
|
@@ -732,6 +757,7 @@ function SecretRow({ port, item, field, extra, onTurnOn }) {
|
|
|
732
757
|
actions: /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
733
758
|
extra,
|
|
734
759
|
off && onTurnOn ? /* @__PURE__ */ jsx5(TurnOnButton, { label: field.label, onTurnOn }) : null,
|
|
760
|
+
!visible && secret.value !== null ? /* @__PURE__ */ jsx5(HidesIn, { until: secret.until }) : null,
|
|
735
761
|
canSee && !visible ? /* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: secret.value !== null ? /* @__PURE__ */ jsx5(EyeOffGlyph, {}) : /* @__PURE__ */ jsx5(EyeGlyph, {}), "aria-label": secret.value !== null ? `Hide ${field.label}` : `Show ${field.label}`, disabled: busy, onClick: () => void toggle2() }) : null,
|
|
736
762
|
canSee ? /* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: /* @__PURE__ */ jsx5(CopyIcon, {}), "aria-label": `Copy ${field.label}`, disabled: busy, onClick: () => void copy() }) : null
|
|
737
763
|
] }),
|
|
@@ -906,15 +932,28 @@ function inView(item, view) {
|
|
|
906
932
|
return true;
|
|
907
933
|
}
|
|
908
934
|
var AUTHENTICATOR_WORDS = ["authenticator", "2fa", "two-factor", "two factor", "totp", "one-time password", "verification code", "2-step"];
|
|
909
|
-
function matchesQuery(item, query) {
|
|
935
|
+
function matchesQuery(item, query, familyLabel2) {
|
|
910
936
|
const q = query.trim().toLowerCase();
|
|
911
937
|
if (!q) return true;
|
|
912
938
|
if (item.authenticator && AUTHENTICATOR_WORDS.some((w) => w === q || q.length >= 3 && w.startsWith(q))) return true;
|
|
913
|
-
return [
|
|
939
|
+
return [
|
|
940
|
+
item.name,
|
|
941
|
+
item.description,
|
|
942
|
+
item.definitionLabel,
|
|
943
|
+
item.definitionKey,
|
|
944
|
+
familyLabel2 ?? "",
|
|
945
|
+
...item.tags,
|
|
946
|
+
...item.loginUrls,
|
|
947
|
+
...item.fields.filter((f) => !isAuthenticatorStorage(f.key)).flatMap((f) => [f.label, f.key, f.envKey ?? ""]),
|
|
948
|
+
...humanCustomFields(item).map((c) => `${c.label} ${c.value}`)
|
|
949
|
+
].some((h) => h !== "" && h.toLowerCase().includes(q));
|
|
914
950
|
}
|
|
915
951
|
var HIDDEN_ITEM_TAG = "matrx:hidden";
|
|
916
952
|
var isHiddenItem = (item) => (item.tags ?? []).includes(HIDDEN_ITEM_TAG);
|
|
917
953
|
var isAuthenticatorStorage = (key) => key === "totp_seed" || key.startsWith("totp_") || key === "authenticator_last_time_step";
|
|
954
|
+
var MFA_METHOD_KEY = "mfa_method";
|
|
955
|
+
var isInternalCustomKey = (key) => isAuthenticatorStorage(key) || key === MFA_METHOD_KEY;
|
|
956
|
+
var humanCustomFields = (item) => item.custom.filter((c) => !isInternalCustomKey(c.key));
|
|
918
957
|
function loginSiteOf(item) {
|
|
919
958
|
const raw = item.loginUrls[0];
|
|
920
959
|
if (!raw) return "";
|
|
@@ -1532,6 +1571,7 @@ function HistoryRow({ item, port, page, entry, label, onRestored, onConflict, on
|
|
|
1532
1571
|
{
|
|
1533
1572
|
label: when,
|
|
1534
1573
|
actions: entry.available ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
1574
|
+
secret.value !== null ? /* @__PURE__ */ jsx7(HidesIn, { until: secret.until }) : null,
|
|
1535
1575
|
/* @__PURE__ */ jsx7(Button4, { variant: "quiet", icon: secret.value !== null ? /* @__PURE__ */ jsx7(EyeOffGlyph, {}) : /* @__PURE__ */ jsx7(EyeGlyph, {}), "aria-label": secret.value !== null ? "Hide" : "Show", disabled: busy, onClick: () => void toggle2() }),
|
|
1536
1576
|
/* @__PURE__ */ jsx7(Button4, { variant: "quiet", icon: /* @__PURE__ */ jsx7(CopyIcon2, {}), "aria-label": "Copy", disabled: busy, onClick: () => void copy() }),
|
|
1537
1577
|
canRestore(page, entry) ? /* @__PURE__ */ jsx7(Button4, { variant: "quiet", icon: /* @__PURE__ */ jsx7(RestoreGlyph, {}), "aria-label": "Restore", disabled: busy, onClick: () => void restore() }) : null
|
|
@@ -2067,7 +2107,6 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSepara
|
|
|
2067
2107
|
import { confirm as confirm2 } from "@ai-matrx/kit/confirm-opener";
|
|
2068
2108
|
|
|
2069
2109
|
// src/authenticator/port.ts
|
|
2070
|
-
var MFA_METHOD_KEY = "mfa_method";
|
|
2071
2110
|
var OTP_SEED_KEY = "totp_seed";
|
|
2072
2111
|
function mfaMethodOf(item) {
|
|
2073
2112
|
const v = item.custom.find((c) => c.key === MFA_METHOD_KEY)?.value;
|
|
@@ -5194,6 +5233,7 @@ function RecoveryCodesRow({ item, field, port, onChanged }) {
|
|
|
5194
5233
|
label: "Recovery codes",
|
|
5195
5234
|
top: codes !== null,
|
|
5196
5235
|
actions: /* @__PURE__ */ jsxs20(Fragment14, { children: [
|
|
5236
|
+
codes ? /* @__PURE__ */ jsx21(HidesIn, { until: secret.until }) : null,
|
|
5197
5237
|
canSee ? /* @__PURE__ */ jsx21(Button17, { variant: "quiet", icon: codes ? /* @__PURE__ */ jsx21(EyeOffGlyph, {}) : /* @__PURE__ */ jsx21(EyeGlyph, {}), "aria-label": codes ? "Hide codes" : "Show codes", disabled: busy, onClick: () => void toggle2() }) : null,
|
|
5198
5238
|
canEdit && !sealed ? /* @__PURE__ */ jsx21(Button17, { variant: "quiet", icon: /* @__PURE__ */ jsx21(PencilGlyph, {}), "aria-label": "Replace all codes", disabled: busy, onClick: () => setDraft("") }) : null
|
|
5199
5239
|
] }),
|
|
@@ -6129,8 +6169,8 @@ function ItemPane(props) {
|
|
|
6129
6169
|
const patchFieldSettings = (key, p) => setDraft((d) => ({ ...d, fieldSettings: { ...d.fieldSettings, [key]: { ...d.fieldSettings?.[key], ...p } } }));
|
|
6130
6170
|
const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
|
|
6131
6171
|
const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
|
|
6132
|
-
const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) &&
|
|
6133
|
-
const viewCustom = (item?.custom ?? []).filter((c) => !declaredKeys.has(c.key) &&
|
|
6172
|
+
const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) && !isInternalCustomKey(c.key));
|
|
6173
|
+
const viewCustom = (item?.custom ?? []).filter((c) => !declaredKeys.has(c.key) && !isInternalCustomKey(c.key));
|
|
6134
6174
|
const isLogin = (item?.definitionKey ?? definition?.key) === "website_login" || (item?.definitionKey ?? definition?.key) === "username_password";
|
|
6135
6175
|
const typeLabel = definition?.label ?? item?.definitionLabel ?? "";
|
|
6136
6176
|
const recoveryDeclared = declaredKeys.has(RECOVERY_CODES_KEY);
|
|
@@ -6489,7 +6529,7 @@ function subtitleOf(item, withOwner) {
|
|
|
6489
6529
|
const what = loginSiteOf(item) || item.definitionLabel;
|
|
6490
6530
|
return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
|
|
6491
6531
|
}
|
|
6492
|
-
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 }) {
|
|
6532
|
+
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 }) {
|
|
6493
6533
|
const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
|
|
6494
6534
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6495
6535
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
@@ -6517,6 +6557,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6517
6557
|
header,
|
|
6518
6558
|
/* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
|
|
6519
6559
|
/* @__PURE__ */ jsx28(SearchField, { value: query, onChange: (e) => onQuery(e.target.value), onKeyDown, placeholder: `Search ${title}`, className: "min-w-0 flex-1", "aria-label": "Search the vault" }),
|
|
6560
|
+
actions,
|
|
6520
6561
|
/* @__PURE__ */ jsx28(Button23, { variant: "primary", icon: /* @__PURE__ */ jsx28(PlusGlyph, {}), "aria-label": "New item", onClick: onNew })
|
|
6521
6562
|
] }),
|
|
6522
6563
|
/* @__PURE__ */ jsxs26("div", { className: "flex items-center justify-between gap-2 px-1", children: [
|
|
@@ -6579,7 +6620,7 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6579
6620
|
const copyCode = () => code && void run("One-time code", async () => (await port.authenticator.code(item)).code, true);
|
|
6580
6621
|
const copyName = () => void copy(item.name, "Name", { secret: false });
|
|
6581
6622
|
const others = otherCopyFields(item, user, secret);
|
|
6582
|
-
const custom = canUse ? item.
|
|
6623
|
+
const custom = canUse ? humanCustomFields(item).filter((c) => c.value) : [];
|
|
6583
6624
|
const hostEntries = extra?.(item) ?? [];
|
|
6584
6625
|
const copyEntries = [
|
|
6585
6626
|
...user ? [{ id: "copy:username", kind: "copy", label: `Copy ${user.label.toLowerCase()}`, onSelect: copyUser }] : [],
|
|
@@ -8179,20 +8220,33 @@ function VaultSelect({ value, choices, onChange }) {
|
|
|
8179
8220
|
}
|
|
8180
8221
|
var CATEGORY_GLYPHS = { logins: GlobeIcon5, passkeys: ShieldGlyph, api: KeyGlyph, oauth: PlugGlyph, infra: DatabaseGlyph, env: TerminalGlyph, files: FileGlyph, other: NoteGlyph };
|
|
8181
8222
|
var familyOf = (key, definitions) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
8223
|
+
function itemFamily(item, definitions) {
|
|
8224
|
+
return familyOf(item.definitionKey, definitions);
|
|
8225
|
+
}
|
|
8226
|
+
var familyLabel = (family) => FAMILY_LABELS[family] ?? family.replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
|
|
8182
8227
|
function itemCategory(item, definitions) {
|
|
8183
8228
|
return categoryOf(item.definitionKey, familyOf(item.definitionKey, definitions));
|
|
8184
8229
|
}
|
|
8185
8230
|
function buildViews(items, definitions, favoriteIds2, recentIds2) {
|
|
8186
8231
|
const byCat = /* @__PURE__ */ new Map();
|
|
8232
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
8187
8233
|
const byTag = /* @__PURE__ */ new Map();
|
|
8188
8234
|
for (const i of items) {
|
|
8189
8235
|
const c = itemCategory(i, definitions);
|
|
8190
8236
|
byCat.set(c, (byCat.get(c) ?? 0) + 1);
|
|
8237
|
+
const fam = itemFamily(i, definitions);
|
|
8238
|
+
const fams = byFamily.get(c) ?? /* @__PURE__ */ new Map();
|
|
8239
|
+
fams.set(fam, (fams.get(fam) ?? 0) + 1);
|
|
8240
|
+
byFamily.set(c, fams);
|
|
8191
8241
|
for (const t of i.tags) byTag.set(t, (byTag.get(t) ?? 0) + 1);
|
|
8192
8242
|
}
|
|
8193
8243
|
const ids = new Set(items.map((i) => i.id));
|
|
8194
8244
|
return {
|
|
8195
|
-
categories: CATEGORIES.filter((c) => byCat.has(c.id)).map((c) =>
|
|
8245
|
+
categories: CATEGORIES.filter((c) => byCat.has(c.id)).map((c) => {
|
|
8246
|
+
const fams = [...(byFamily.get(c.id) ?? /* @__PURE__ */ new Map()).entries()];
|
|
8247
|
+
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;
|
|
8248
|
+
return { view: `category:${c.id}`, label: c.label, count: byCat.get(c.id) ?? 0, glyph: CATEGORY_GLYPHS[c.id], ...families ? { families } : {} };
|
|
8249
|
+
}),
|
|
8196
8250
|
tags: [...byTag.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([t, count2]) => ({ view: `tag:${t}`, label: t, count: count2, glyph: TagGlyph })),
|
|
8197
8251
|
favorites: favoriteIds2 ? [...favoriteIds2].filter((id) => ids.has(id)).length : void 0,
|
|
8198
8252
|
authenticator: items.filter((i) => i.authenticator).length,
|
|
@@ -8205,21 +8259,27 @@ function viewLabel(view, views) {
|
|
|
8205
8259
|
if (view === "authenticator") return AUTHENTICATOR_LABEL;
|
|
8206
8260
|
if (view === "trash") return "Trash";
|
|
8207
8261
|
if (view === "page") return "This page";
|
|
8262
|
+
if (view.startsWith("family:")) return familyLabel(view.slice(7));
|
|
8208
8263
|
return [...views.categories, ...views.tags].find((v) => v.view === view)?.label ?? "All items";
|
|
8209
8264
|
}
|
|
8210
|
-
function
|
|
8265
|
+
function openCategory(view, views) {
|
|
8266
|
+
return views.categories.find((c) => c.view === view || c.families?.some((f) => f.view === view));
|
|
8267
|
+
}
|
|
8268
|
+
function viewOptions(views, allCount, deletedCount, hasFavorites, hasRecents, hasPage = false, current = "all") {
|
|
8269
|
+
const open = openCategory(current, views);
|
|
8211
8270
|
return [
|
|
8212
8271
|
...hasPage ? [{ value: "page", label: "This page" }] : [],
|
|
8213
8272
|
{ value: "all", label: "Every category", meta: String(allCount) },
|
|
8214
8273
|
...hasFavorites ? [{ value: "favorites", label: "Favorites", meta: views.favorites === void 0 ? "" : String(views.favorites) }] : [],
|
|
8215
8274
|
...hasRecents ? [{ value: "recent", label: "Recently opened", meta: views.recent === void 0 ? "" : String(views.recent) }] : [],
|
|
8216
8275
|
{ value: "authenticator", label: AUTHENTICATOR_LABEL, meta: String(views.authenticator) },
|
|
8217
|
-
|
|
8276
|
+
// The open category's families follow it, indented by their sign; other categories stay one line.
|
|
8277
|
+
...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) })) : []]),
|
|
8218
8278
|
...views.tags.map((v) => ({ value: v.view, label: `#${v.label}`, meta: String(v.count) })),
|
|
8219
8279
|
{ value: "trash", label: "Trash", meta: String(deletedCount) }
|
|
8220
8280
|
];
|
|
8221
8281
|
}
|
|
8222
|
-
function Entry({ on, glyph: Icon, label, count: count2, onClick }) {
|
|
8282
|
+
function Entry({ on, glyph: Icon, label, count: count2, onClick, nested = false }) {
|
|
8223
8283
|
const desktop = useLook2() === "desktop";
|
|
8224
8284
|
return /* @__PURE__ */ jsxs37(
|
|
8225
8285
|
"button",
|
|
@@ -8227,9 +8287,10 @@ function Entry({ on, glyph: Icon, label, count: count2, onClick }) {
|
|
|
8227
8287
|
type: "button",
|
|
8228
8288
|
"aria-current": on ? "page" : void 0,
|
|
8229
8289
|
onClick,
|
|
8230
|
-
|
|
8290
|
+
"data-nested": nested ? "" : void 0,
|
|
8291
|
+
className: cn15("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"),
|
|
8231
8292
|
children: [
|
|
8232
|
-
/* @__PURE__ */ jsx39(Icon, { className: "size-4 shrink-0" }),
|
|
8293
|
+
nested ? null : /* @__PURE__ */ jsx39(Icon, { className: "size-4 shrink-0" }),
|
|
8233
8294
|
/* @__PURE__ */ jsx39("span", { className: "min-w-0 flex-1 truncate", children: label }),
|
|
8234
8295
|
count2 !== void 0 ? /* @__PURE__ */ jsx39("span", { className: "text-xs tabular-nums text-muted-foreground", children: count2 }) : null
|
|
8235
8296
|
]
|
|
@@ -8257,7 +8318,10 @@ function VaultSidebar({ vault, choices, onVault, view, allCount, deletedCount, v
|
|
|
8257
8318
|
hasRecents ? /* @__PURE__ */ jsx39(Entry, { on: view === "recent", glyph: ClockGlyph, label: "Recently opened", count: views.recent, onClick: () => onView("recent") }) : null,
|
|
8258
8319
|
/* @__PURE__ */ jsx39(Entry, { on: view === "authenticator", glyph: LockGlyph, label: AUTHENTICATOR_LABEL, count: views.authenticator, onClick: () => onView("authenticator") })
|
|
8259
8320
|
] }),
|
|
8260
|
-
views.categories.length > 0 ? /* @__PURE__ */ jsx39(Group, { title: "Categories", children: views.categories.map((e) => /* @__PURE__ */
|
|
8321
|
+
views.categories.length > 0 ? /* @__PURE__ */ jsx39(Group, { title: "Categories", children: views.categories.map((e) => /* @__PURE__ */ jsxs37("div", { className: "flex flex-col gap-0.5", children: [
|
|
8322
|
+
/* @__PURE__ */ jsx39(Entry, { on: view === e.view, glyph: e.glyph, label: e.label, count: e.count, onClick: () => onView(e.view) }),
|
|
8323
|
+
e === openCategory(view, views) ? e.families?.map((f) => /* @__PURE__ */ jsx39(Entry, { nested: true, on: view === f.view, glyph: f.glyph, label: f.label, count: f.count, onClick: () => onView(f.view) }, f.view)) : null
|
|
8324
|
+
] }, e.view)) }) : null,
|
|
8261
8325
|
views.tags.length > 0 ? /* @__PURE__ */ jsxs37("div", { className: "flex flex-col gap-0.5", children: [
|
|
8262
8326
|
/* @__PURE__ */ jsxs37("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: [
|
|
8263
8327
|
tagsOpen ? /* @__PURE__ */ jsx39(ChevronDownGlyph, { className: "size-3.5" }) : /* @__PURE__ */ jsx39(ChevronRightGlyph, { className: "size-3.5" }),
|
|
@@ -8522,10 +8586,11 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8522
8586
|
const views = useMemo6(() => buildViews(inThisVault, definitions, favorites, recents), [inThisVault, definitions, favorites, recents]);
|
|
8523
8587
|
const visible = useMemo6(() => {
|
|
8524
8588
|
const filtered = inThisVault.filter((i) => {
|
|
8525
|
-
if (!matchesQuery(i, loc.q)) return false;
|
|
8589
|
+
if (!matchesQuery(i, loc.q, familyLabel(itemFamily(i, definitions)))) return false;
|
|
8526
8590
|
if (loc.view === "favorites") return favorites?.has(i.id) ?? false;
|
|
8527
8591
|
if (loc.view === "recent") return recents?.includes(i.id) ?? false;
|
|
8528
8592
|
if (loc.view.startsWith("category:")) return itemCategory(i, definitions) === loc.view.slice(9);
|
|
8593
|
+
if (loc.view.startsWith("family:")) return itemFamily(i, definitions) === loc.view.slice(7);
|
|
8529
8594
|
return inView(i, loc.view);
|
|
8530
8595
|
});
|
|
8531
8596
|
if (loc.view === "recent" && recents) return [...filtered].sort((a, b) => recents.indexOf(a.id) - recents.indexOf(b.id));
|
|
@@ -8577,65 +8642,66 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8577
8642
|
}
|
|
8578
8643
|
const hasFavorites = port.lists.favorites !== void 0;
|
|
8579
8644
|
const title = loc.vault === "all" && loc.view === "all" ? "All items" : viewLabel(loc.view, views);
|
|
8580
|
-
const compactHeader = mode === "full" ? void 0 : (
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
/* @__PURE__ */
|
|
8598
|
-
|
|
8599
|
-
/* @__PURE__ */ jsxs38(
|
|
8600
|
-
/* @__PURE__ */
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
/* @__PURE__ */
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
/* @__PURE__ */
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
/* @__PURE__ */
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
] }) : null
|
|
8632
|
-
] })
|
|
8645
|
+
const compactHeader = mode === "full" ? void 0 : /* @__PURE__ */ jsxs38("div", { className: "grid grid-cols-[repeat(auto-fit,minmax(11rem,1fr))] gap-2", "data-vault-compact-header": "", children: [
|
|
8646
|
+
/* @__PURE__ */ jsx40(VaultSelect, { value: loc.vault, choices, onChange: (vault) => go({ vault, itemId: null, panel: null }) }),
|
|
8647
|
+
/* @__PURE__ */ jsx40(Select12, { 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" })
|
|
8648
|
+
] });
|
|
8649
|
+
const compactActions = mode === "full" ? void 0 : /* @__PURE__ */ jsxs38("div", { className: "flex shrink-0 items-center gap-2", "data-vault-compact-actions": "", children: [
|
|
8650
|
+
/* @__PURE__ */ jsx40(
|
|
8651
|
+
Button34,
|
|
8652
|
+
{
|
|
8653
|
+
variant: loc.view === "authenticator" ? "outline" : "quiet",
|
|
8654
|
+
icon: /* @__PURE__ */ jsx40(LockGlyph, {}),
|
|
8655
|
+
"aria-label": AUTHENTICATOR_LABEL,
|
|
8656
|
+
"aria-pressed": loc.view === "authenticator",
|
|
8657
|
+
title: AUTHENTICATOR_LABEL,
|
|
8658
|
+
onClick: () => go({ view: loc.view === "authenticator" ? "all" : "authenticator", itemId: null, mode: "view", panel: null })
|
|
8659
|
+
}
|
|
8660
|
+
),
|
|
8661
|
+
/* @__PURE__ */ jsxs38(DropdownMenu6, { children: [
|
|
8662
|
+
/* @__PURE__ */ jsx40(DropdownMenuTrigger6, { asChild: true, children: /* @__PURE__ */ jsx40(Button34, { variant: "quiet", icon: /* @__PURE__ */ jsx40(MoreGlyph, {}), "aria-label": "Vault actions" }) }),
|
|
8663
|
+
/* @__PURE__ */ jsxs38(DropdownMenuContent6, { align: "end", children: [
|
|
8664
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "import" }), children: [
|
|
8665
|
+
/* @__PURE__ */ jsx40(ImportGlyph, { className: "size-4" }),
|
|
8666
|
+
"Import"
|
|
8667
|
+
] }),
|
|
8668
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "export" }), children: [
|
|
8669
|
+
/* @__PURE__ */ jsx40(ExportGlyph, { className: "size-4" }),
|
|
8670
|
+
"Export"
|
|
8671
|
+
] }),
|
|
8672
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "backup" }), children: [
|
|
8673
|
+
/* @__PURE__ */ jsx40(SafeGlyph, { className: "size-4" }),
|
|
8674
|
+
"Backup"
|
|
8675
|
+
] }),
|
|
8676
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "devices" }), children: [
|
|
8677
|
+
/* @__PURE__ */ jsx40(ShieldGlyph, { className: "size-4" }),
|
|
8678
|
+
"Browsers that fill"
|
|
8679
|
+
] }),
|
|
8680
|
+
host.nativeImport ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "import-passkeys" }), children: [
|
|
8681
|
+
/* @__PURE__ */ jsx40(ImportGlyph, { className: "size-4" }),
|
|
8682
|
+
"Import passkeys"
|
|
8683
|
+
] }) : null,
|
|
8684
|
+
host.nativeProvider && host.nativeProvider.status !== "unavailable" ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: host.nativeProvider.openSetup, children: [
|
|
8685
|
+
/* @__PURE__ */ jsx40(ShieldGlyph, { className: "size-4" }),
|
|
8686
|
+
"System AutoFill"
|
|
8687
|
+
] }) : null,
|
|
8688
|
+
host.lock?.lock ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => void host.lock?.lock?.(), children: [
|
|
8689
|
+
/* @__PURE__ */ jsx40(LockGlyph, { className: "size-4" }),
|
|
8690
|
+
"Lock"
|
|
8691
|
+
] }) : null,
|
|
8692
|
+
host.openWebVault ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: host.openWebVault, children: [
|
|
8693
|
+
/* @__PURE__ */ jsx40(ExternalGlyph, { className: "size-4" }),
|
|
8694
|
+
"Open on the web"
|
|
8695
|
+
] }) : null
|
|
8633
8696
|
] })
|
|
8634
8697
|
] })
|
|
8635
|
-
);
|
|
8698
|
+
] });
|
|
8636
8699
|
const viewHeader = /* @__PURE__ */ jsxs38("div", { className: "flex flex-col gap-2", children: [
|
|
8637
8700
|
compactHeader,
|
|
8638
|
-
/* @__PURE__ */
|
|
8701
|
+
/* @__PURE__ */ jsxs38("div", { className: "flex items-center gap-2", children: [
|
|
8702
|
+
/* @__PURE__ */ jsx40(SearchField4, { 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" }),
|
|
8703
|
+
compactActions
|
|
8704
|
+
] })
|
|
8639
8705
|
] });
|
|
8640
8706
|
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 };
|
|
8641
8707
|
const pageView = loc.view === "page" && host.page && !loc.q ? /* @__PURE__ */ jsx40(
|
|
@@ -8653,7 +8719,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8653
8719
|
onNewForPage: (url) => go({ mode: "new", kind: null, url, itemId: null, panel: null }),
|
|
8654
8720
|
onBrowse: () => go({ view: "all", itemId: null }),
|
|
8655
8721
|
onChanged: load,
|
|
8656
|
-
...compactHeader ? { header:
|
|
8722
|
+
...compactHeader ? { header: /* @__PURE__ */ jsxs38("div", { className: "flex flex-col gap-2", children: [
|
|
8723
|
+
compactHeader,
|
|
8724
|
+
/* @__PURE__ */ jsx40("div", { className: "flex justify-end", children: compactActions })
|
|
8725
|
+
] }) } : {}
|
|
8657
8726
|
}
|
|
8658
8727
|
) : null;
|
|
8659
8728
|
const specialView = pageView ?? (loc.view === "authenticator" ? /* @__PURE__ */ jsx40(AuthenticatorView, { ...viewProps }) : loc.view === "trash" ? /* @__PURE__ */ jsx40(TrashView, { ...viewProps }) : null);
|
|
@@ -8680,7 +8749,8 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8680
8749
|
rowContextMenu: host.rowContextMenu ?? "package",
|
|
8681
8750
|
...host.rowMenuItems ? { rowMenuItems: host.rowMenuItems } : {},
|
|
8682
8751
|
...matchingIds ? { matchingIds } : {},
|
|
8683
|
-
...compactHeader ? { header: compactHeader } : {}
|
|
8752
|
+
...compactHeader ? { header: compactHeader } : {},
|
|
8753
|
+
...compactActions ? { actions: compactActions } : {}
|
|
8684
8754
|
}
|
|
8685
8755
|
);
|
|
8686
8756
|
const showingDetail = loc.mode === "new" || loc.itemId !== null;
|