@ai-matrx/vault 0.1.18 → 0.1.19
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 +5 -0
- package/dist/index.cjs +219 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +219 -77
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -116,6 +116,8 @@ interface VaultLocation {
|
|
|
116
116
|
/** New-item flow: the chosen definition, and the URL pasted at the start (kept across steps). */
|
|
117
117
|
kind: string | null;
|
|
118
118
|
url: string;
|
|
119
|
+
/** New-item flow: a name the opener chose ("Instagram login"); beats the one guessed from the address. */
|
|
120
|
+
name?: string;
|
|
119
121
|
/** A panel open over the workspace: `share`, `history`, `activity`, `files`, `move`, `import`, … */
|
|
120
122
|
panel: string | null;
|
|
121
123
|
}
|
|
@@ -1202,6 +1204,23 @@ interface VaultHostCapabilities {
|
|
|
1202
1204
|
clipboard?: VaultClipboard;
|
|
1203
1205
|
/** How long a shown value stays shown, in seconds (an organization knob the host reads); default 20. */
|
|
1204
1206
|
revealSeconds?: number;
|
|
1207
|
+
/**
|
|
1208
|
+
* How often the list re-reads itself while the vault is on screen, in seconds (the desktop's
|
|
1209
|
+
* refresh knob). Without it the organization's `vault.list.refresh_seconds` knob decides; with
|
|
1210
|
+
* neither, the list still re-reads whenever the window or panel comes back into focus.
|
|
1211
|
+
*/
|
|
1212
|
+
refreshSeconds?: number;
|
|
1213
|
+
/**
|
|
1214
|
+
* Who answers a right-click on a list row. `package` (default): the row's own menu (copy any
|
|
1215
|
+
* field, open, copy name, plus `rowMenuItems`). `host`: the host wraps the vault in its own
|
|
1216
|
+
* right-click menu (the website's agents menu) and the row leaves the event alone.
|
|
1217
|
+
*/
|
|
1218
|
+
rowContextMenu?: "package" | "host";
|
|
1219
|
+
/** Extra entries a host adds to every row's menu (the More button and the right-click menu). */
|
|
1220
|
+
rowMenuItems?: (item: VaultItem) => readonly {
|
|
1221
|
+
label: string;
|
|
1222
|
+
onSelect: () => void;
|
|
1223
|
+
}[];
|
|
1205
1224
|
nativeProvider?: {
|
|
1206
1225
|
status: "ready" | "needs_setup" | "unavailable";
|
|
1207
1226
|
openSetup: () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -116,6 +116,8 @@ interface VaultLocation {
|
|
|
116
116
|
/** New-item flow: the chosen definition, and the URL pasted at the start (kept across steps). */
|
|
117
117
|
kind: string | null;
|
|
118
118
|
url: string;
|
|
119
|
+
/** New-item flow: a name the opener chose ("Instagram login"); beats the one guessed from the address. */
|
|
120
|
+
name?: string;
|
|
119
121
|
/** A panel open over the workspace: `share`, `history`, `activity`, `files`, `move`, `import`, … */
|
|
120
122
|
panel: string | null;
|
|
121
123
|
}
|
|
@@ -1202,6 +1204,23 @@ interface VaultHostCapabilities {
|
|
|
1202
1204
|
clipboard?: VaultClipboard;
|
|
1203
1205
|
/** How long a shown value stays shown, in seconds (an organization knob the host reads); default 20. */
|
|
1204
1206
|
revealSeconds?: number;
|
|
1207
|
+
/**
|
|
1208
|
+
* How often the list re-reads itself while the vault is on screen, in seconds (the desktop's
|
|
1209
|
+
* refresh knob). Without it the organization's `vault.list.refresh_seconds` knob decides; with
|
|
1210
|
+
* neither, the list still re-reads whenever the window or panel comes back into focus.
|
|
1211
|
+
*/
|
|
1212
|
+
refreshSeconds?: number;
|
|
1213
|
+
/**
|
|
1214
|
+
* Who answers a right-click on a list row. `package` (default): the row's own menu (copy any
|
|
1215
|
+
* field, open, copy name, plus `rowMenuItems`). `host`: the host wraps the vault in its own
|
|
1216
|
+
* right-click menu (the website's agents menu) and the row leaves the event alone.
|
|
1217
|
+
*/
|
|
1218
|
+
rowContextMenu?: "package" | "host";
|
|
1219
|
+
/** Extra entries a host adds to every row's menu (the More button and the right-click menu). */
|
|
1220
|
+
rowMenuItems?: (item: VaultItem) => readonly {
|
|
1221
|
+
label: string;
|
|
1222
|
+
onSelect: () => void;
|
|
1223
|
+
}[];
|
|
1205
1224
|
nativeProvider?: {
|
|
1206
1225
|
status: "ready" | "needs_setup" | "unavailable";
|
|
1207
1226
|
openSetup: () => void;
|
package/dist/index.js
CHANGED
|
@@ -860,6 +860,7 @@ function locationFromParams(params, itemId = params.get("item")) {
|
|
|
860
860
|
mode: mode === "edit" || mode === "new" ? mode : "view",
|
|
861
861
|
kind: params.get("kind"),
|
|
862
862
|
url: params.get("url") ?? "",
|
|
863
|
+
...params.get("name") ? { name: params.get("name") ?? "" } : {},
|
|
863
864
|
panel: params.get("panel")
|
|
864
865
|
};
|
|
865
866
|
}
|
|
@@ -872,6 +873,7 @@ function locationToParams(loc) {
|
|
|
872
873
|
if (loc.mode !== "view") q.set("mode", loc.mode);
|
|
873
874
|
if (loc.kind) q.set("kind", loc.kind);
|
|
874
875
|
if (loc.url) q.set("url", loc.url);
|
|
876
|
+
if (loc.name) q.set("name", loc.name);
|
|
875
877
|
if (loc.panel) q.set("panel", loc.panel);
|
|
876
878
|
return q;
|
|
877
879
|
}
|
|
@@ -1912,10 +1914,16 @@ function QrScanPanel({ open, onResult, onClose }) {
|
|
|
1912
1914
|
/* @__PURE__ */ jsx10("p", { className: "text-center text-xs text-muted-foreground", children: "Point at the site's QR code" })
|
|
1913
1915
|
] });
|
|
1914
1916
|
}
|
|
1915
|
-
function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status }) {
|
|
1917
|
+
function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status, autoStartCamera = false }) {
|
|
1916
1918
|
const file = useRef8(null);
|
|
1917
1919
|
const canScan = useCanScan();
|
|
1918
1920
|
const [scanning, setScanning] = useState10(false);
|
|
1921
|
+
const autoStarted = useRef8(false);
|
|
1922
|
+
useEffect10(() => {
|
|
1923
|
+
if (!autoStartCamera || !canScan || disabled || autoStarted.current || value) return;
|
|
1924
|
+
autoStarted.current = true;
|
|
1925
|
+
setScanning(true);
|
|
1926
|
+
}, [autoStartCamera, canScan, disabled, value]);
|
|
1919
1927
|
const [reading, setReading] = useState10(false);
|
|
1920
1928
|
const [imageError, setImageError] = useState10(null);
|
|
1921
1929
|
const [dragging, setDragging] = useState10(false);
|
|
@@ -2300,6 +2308,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2300
2308
|
label: "Setup key",
|
|
2301
2309
|
value: input,
|
|
2302
2310
|
autoFocus: true,
|
|
2311
|
+
autoStartCamera: true,
|
|
2303
2312
|
disabled: busy,
|
|
2304
2313
|
onChange: (v) => {
|
|
2305
2314
|
setImage(null);
|
|
@@ -3852,7 +3861,7 @@ var MAX_FAILURES_IN_A_ROW = 5;
|
|
|
3852
3861
|
var failureText = (cause) => cause instanceof Error && cause.message ? cause.message : "The server refused this one.";
|
|
3853
3862
|
async function runImport(input) {
|
|
3854
3863
|
const { io, organizationId, lines, keys, confirmed } = input;
|
|
3855
|
-
const result = { created: [], failed: [], withOtp: 0, stoppedEarly: false, untried: [] };
|
|
3864
|
+
const result = { created: [], failed: [], withOtp: 0, needOtp: [], stoppedEarly: false, untried: [] };
|
|
3856
3865
|
let streak = 0;
|
|
3857
3866
|
for (let i = 0; i < lines.length; i += 1) {
|
|
3858
3867
|
const line = lines[i];
|
|
@@ -3871,7 +3880,10 @@ async function runImport(input) {
|
|
|
3871
3880
|
try {
|
|
3872
3881
|
const made = await confirmed(() => io.createImported(command.body, organizationId, key));
|
|
3873
3882
|
result.created.push(made);
|
|
3874
|
-
if (command.hasOtp)
|
|
3883
|
+
if (command.hasOtp) {
|
|
3884
|
+
result.withOtp += 1;
|
|
3885
|
+
result.needOtp.push(made);
|
|
3886
|
+
}
|
|
3875
3887
|
streak = 0;
|
|
3876
3888
|
} catch (cause) {
|
|
3877
3889
|
const reason = failureText(cause);
|
|
@@ -4471,8 +4483,9 @@ var STATE_BADGE = {
|
|
|
4471
4483
|
unsupported: { text: "Not supported", tone: "neutral" }
|
|
4472
4484
|
};
|
|
4473
4485
|
var ME = "me";
|
|
4474
|
-
function ImportPanel({ port, host, items, organizations, onClose, onChanged, limits }) {
|
|
4486
|
+
function ImportPanel({ port, host, items, definitions, organizations, onClose, onChanged, limits }) {
|
|
4475
4487
|
const confirmed = useConfirmed();
|
|
4488
|
+
const [otpFor, setOtpFor] = useState19(null);
|
|
4476
4489
|
const fileInput = useRef12(null);
|
|
4477
4490
|
const abort = useRef12(null);
|
|
4478
4491
|
const stop = useRef12(false);
|
|
@@ -4580,6 +4593,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
|
|
|
4580
4593
|
failed: result.failed,
|
|
4581
4594
|
skipped,
|
|
4582
4595
|
withOtp: (previous?.withOtp ?? 0) + result.withOtp,
|
|
4596
|
+
needOtp: [...previous?.needOtp ?? [], ...result.needOtp],
|
|
4583
4597
|
untried: result.untried
|
|
4584
4598
|
};
|
|
4585
4599
|
setDone(merged);
|
|
@@ -4605,7 +4619,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
|
|
|
4605
4619
|
setRunning({ done: 0, total: chosen });
|
|
4606
4620
|
try {
|
|
4607
4621
|
const made = await confirmed(() => port.io.importEnv(text, organizationId, injectSandbox));
|
|
4608
|
-
setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, untried: [] });
|
|
4622
|
+
setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, needOtp: [], untried: [] });
|
|
4609
4623
|
if (made.length > 0) onChanged();
|
|
4610
4624
|
} catch (cause) {
|
|
4611
4625
|
setError(cause instanceof Error ? cause.message : "The import could not finish.");
|
|
@@ -4720,9 +4734,32 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
|
|
|
4720
4734
|
plural(done.withOtp, "login"),
|
|
4721
4735
|
" still need the one-time password added."
|
|
4722
4736
|
] }) }) : null,
|
|
4737
|
+
done.needOtp.map((made) => {
|
|
4738
|
+
const now = items.find((i) => i.id === made.id);
|
|
4739
|
+
if (now?.authenticator) return null;
|
|
4740
|
+
return /* @__PURE__ */ jsx19(VaultRow, { label: "Authenticator", children: /* @__PURE__ */ jsxs18("button", { type: "button", className: "truncate text-left text-primary hover:underline", onClick: () => setOtpFor(made.id), children: [
|
|
4741
|
+
"Set up authenticator for ",
|
|
4742
|
+
made.name
|
|
4743
|
+
] }) }, `otp:${made.id}`);
|
|
4744
|
+
}),
|
|
4723
4745
|
done.failed.map((f) => /* @__PURE__ */ jsx19(VaultRow, { label: f.title, children: /* @__PURE__ */ jsx19("span", { className: "block truncate text-destructive", title: f.reason, children: f.reason }) }, f.key))
|
|
4724
4746
|
] }) : null,
|
|
4725
|
-
/* @__PURE__ */ jsx19(ErrorLine, { message: error })
|
|
4747
|
+
/* @__PURE__ */ jsx19(ErrorLine, { message: error }),
|
|
4748
|
+
otpFor ? /* @__PURE__ */ jsx19(
|
|
4749
|
+
AddOtpPanel,
|
|
4750
|
+
{
|
|
4751
|
+
port,
|
|
4752
|
+
items,
|
|
4753
|
+
definitions,
|
|
4754
|
+
itemId: otpFor,
|
|
4755
|
+
onClose: () => setOtpFor(null),
|
|
4756
|
+
onChanged,
|
|
4757
|
+
onDone: () => {
|
|
4758
|
+
setOtpFor(null);
|
|
4759
|
+
onChanged();
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
) : null
|
|
4726
4763
|
] });
|
|
4727
4764
|
}
|
|
4728
4765
|
function toggle(set, key, on) {
|
|
@@ -6344,6 +6381,13 @@ function ItemPane(props) {
|
|
|
6344
6381
|
|
|
6345
6382
|
// src/ItemList.tsx
|
|
6346
6383
|
import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
6384
|
+
var RECOVERY_KEYS = /^recovery_codes?$/;
|
|
6385
|
+
function otherCopyFields(item, user, secret) {
|
|
6386
|
+
if (!item.capabilities.canUse) return [];
|
|
6387
|
+
return item.fields.filter(
|
|
6388
|
+
(f) => f !== user && f !== secret && f.active !== false && f.handling !== "sealed" && (f.handling === "visible" || item.capabilities.canReveal) && !isAuthenticatorStorage(f.key) && !RECOVERY_KEYS.test(f.key)
|
|
6389
|
+
);
|
|
6390
|
+
}
|
|
6347
6391
|
var SORTS2 = [
|
|
6348
6392
|
{ value: "name", label: "Name A\u2013Z" },
|
|
6349
6393
|
{ value: "name-desc", label: "Name Z\u2013A" },
|
|
@@ -6356,7 +6400,7 @@ function subtitleOf(item, withOwner) {
|
|
|
6356
6400
|
const what = loginSiteOf(item) || item.definitionLabel;
|
|
6357
6401
|
return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
|
|
6358
6402
|
}
|
|
6359
|
-
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false }) {
|
|
6403
|
+
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems }) {
|
|
6360
6404
|
const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
|
|
6361
6405
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6362
6406
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
@@ -6377,7 +6421,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6377
6421
|
}
|
|
6378
6422
|
};
|
|
6379
6423
|
const mixedOwners = showOwner ?? new Set(items.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
|
|
6380
|
-
const row = (i) => /* @__PURE__ */ jsx28(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners }, i.id);
|
|
6424
|
+
const row = (i) => /* @__PURE__ */ jsx28(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners, ownContextMenu: rowContextMenu === "package", extra: rowMenuItems }, i.id);
|
|
6381
6425
|
const sortLabel = sorts.find((s) => s.value === sort)?.label ?? "Name A\u2013Z";
|
|
6382
6426
|
return /* @__PURE__ */ jsxs26("section", { "aria-label": title, className: "flex h-full min-h-0 flex-col border-r border-border bg-background", children: [
|
|
6383
6427
|
/* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-2 px-3 pb-2 pt-3", children: [
|
|
@@ -6392,9 +6436,12 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6392
6436
|
" ",
|
|
6393
6437
|
items.length === 1 ? "item" : "items"
|
|
6394
6438
|
] }),
|
|
6395
|
-
/* @__PURE__ */ jsxs26(
|
|
6396
|
-
/* @__PURE__ */ jsx28(
|
|
6397
|
-
/* @__PURE__ */
|
|
6439
|
+
/* @__PURE__ */ jsxs26("span", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
6440
|
+
onRefresh ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(RefreshGlyph, { className: refreshing ? "animate-spin" : "" }), "aria-label": "Refresh", title: "Refresh the list", disabled: refreshing, onClick: onRefresh }) : null,
|
|
6441
|
+
/* @__PURE__ */ jsxs26(DropdownMenu4, { children: [
|
|
6442
|
+
/* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(SortGlyph, {}), "aria-label": `Sort: ${sortLabel}`, children: sortLabel }) }),
|
|
6443
|
+
/* @__PURE__ */ jsx28(DropdownMenuContent4, { align: "end", children: /* @__PURE__ */ jsx28(DropdownMenuRadioGroup, { value: sort, onValueChange: (v) => onSort(sorts.find((s) => s.value === v)?.value ?? "name"), children: sorts.map((s) => /* @__PURE__ */ jsx28(DropdownMenuRadioItem, { value: s.value, children: s.label }, s.value)) }) })
|
|
6444
|
+
] })
|
|
6398
6445
|
] })
|
|
6399
6446
|
] })
|
|
6400
6447
|
] }),
|
|
@@ -6409,7 +6456,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6409
6456
|
] }) })
|
|
6410
6457
|
] });
|
|
6411
6458
|
}
|
|
6412
|
-
function ListRow({ item, family, port, on, onSelect, withOwner }) {
|
|
6459
|
+
function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra }) {
|
|
6413
6460
|
const confirmed = useConfirmed();
|
|
6414
6461
|
const copy = useCopy();
|
|
6415
6462
|
const notify = useNotify();
|
|
@@ -6434,67 +6481,93 @@ function ListRow({ item, family, port, on, onSelect, withOwner }) {
|
|
|
6434
6481
|
const copyPassword = () => secret && void run("Password", () => port.reveal(item, secret.key), true);
|
|
6435
6482
|
const copyCode = () => code && void run("One-time code", async () => (await port.authenticator.code(item)).code, true);
|
|
6436
6483
|
const copyName = () => void copy(item.name, "Name", { secret: false });
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
]
|
|
6453
|
-
}
|
|
6454
|
-
),
|
|
6455
|
-
/* @__PURE__ */ jsxs26("div", { className: "absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-0.5", "data-surface": on ? "primary" : void 0, children: [
|
|
6456
|
-
user || secret ? /* @__PURE__ */ jsxs26("div", { className: cn13("hidden items-center gap-0.5 rounded-md pl-2 [@media(hover:hover)]:group-hover:flex [@media(hover:hover)]:group-focus-within:flex", on ? "bg-primary" : "bg-accent"), "data-row-quick": "", children: [
|
|
6457
|
-
user ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
|
|
6458
|
-
secret ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
|
|
6459
|
-
] }) : null,
|
|
6460
|
-
/* @__PURE__ */ jsxs26(DropdownMenu4, { open: menuOpen, onOpenChange: setMenuOpen, children: [
|
|
6461
|
-
/* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(
|
|
6462
|
-
Button23,
|
|
6484
|
+
const others = otherCopyFields(item, user, secret);
|
|
6485
|
+
const custom = canUse ? item.custom.filter((c) => c.value) : [];
|
|
6486
|
+
const hostEntries = extra?.(item) ?? [];
|
|
6487
|
+
return /* @__PURE__ */ jsxs26(
|
|
6488
|
+
"li",
|
|
6489
|
+
{
|
|
6490
|
+
className: "group relative",
|
|
6491
|
+
"data-item": item.id,
|
|
6492
|
+
onContextMenu: ownContextMenu ? (e) => {
|
|
6493
|
+
e.preventDefault();
|
|
6494
|
+
setMenuOpen(true);
|
|
6495
|
+
} : void 0,
|
|
6496
|
+
children: [
|
|
6497
|
+
/* @__PURE__ */ jsxs26(
|
|
6498
|
+
"button",
|
|
6463
6499
|
{
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
"
|
|
6467
|
-
|
|
6468
|
-
className: cn13("
|
|
6500
|
+
type: "button",
|
|
6501
|
+
"aria-current": on ? "true" : void 0,
|
|
6502
|
+
"data-surface": on ? "primary" : void 0,
|
|
6503
|
+
onClick: () => onSelect(item.id),
|
|
6504
|
+
className: cn13("flex h-12 w-full items-center gap-3 rounded-lg px-2 pr-10 text-left", on ? "bg-primary text-primary-foreground" : "hover:bg-accent"),
|
|
6505
|
+
children: [
|
|
6506
|
+
/* @__PURE__ */ jsx28(ItemIcon, { definitionKey: item.definitionKey, family, site: loginSiteOf(item), inverted: on }),
|
|
6507
|
+
/* @__PURE__ */ jsxs26("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
|
|
6508
|
+
/* @__PURE__ */ jsx28("span", { className: "truncate text-sm font-medium", children: item.name }),
|
|
6509
|
+
/* @__PURE__ */ jsx28("span", { className: "truncate text-xs text-muted-foreground", children: subtitleOf(item, withOwner) })
|
|
6510
|
+
] })
|
|
6511
|
+
]
|
|
6469
6512
|
}
|
|
6470
|
-
)
|
|
6471
|
-
/* @__PURE__ */ jsxs26(
|
|
6472
|
-
user ? /* @__PURE__ */ jsxs26(
|
|
6473
|
-
/* @__PURE__ */ jsx28(
|
|
6474
|
-
"Copy ",
|
|
6475
|
-
user.label.toLowerCase()
|
|
6476
|
-
] }) : null,
|
|
6477
|
-
secret ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyPassword, children: [
|
|
6478
|
-
/* @__PURE__ */ jsx28(KeyGlyph, { className: "size-4" }),
|
|
6479
|
-
"Copy password"
|
|
6480
|
-
] }) : null,
|
|
6481
|
-
code ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyCode, children: [
|
|
6482
|
-
/* @__PURE__ */ jsx28(ClockGlyph, { className: "size-4" }),
|
|
6483
|
-
"Copy one-time code"
|
|
6513
|
+
),
|
|
6514
|
+
/* @__PURE__ */ jsxs26("div", { className: "absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-0.5", "data-surface": on ? "primary" : void 0, children: [
|
|
6515
|
+
user || secret ? /* @__PURE__ */ jsxs26("div", { className: cn13("hidden items-center gap-0.5 rounded-md pl-2 [@media(hover:hover)]:group-hover:flex [@media(hover:hover)]:group-focus-within:flex", on ? "bg-primary" : "bg-accent"), "data-row-quick": "", children: [
|
|
6516
|
+
user ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
|
|
6517
|
+
secret ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
|
|
6484
6518
|
] }) : null,
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6519
|
+
/* @__PURE__ */ jsxs26(DropdownMenu4, { open: menuOpen, onOpenChange: setMenuOpen, children: [
|
|
6520
|
+
/* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(
|
|
6521
|
+
Button23,
|
|
6522
|
+
{
|
|
6523
|
+
variant: "quiet",
|
|
6524
|
+
icon: /* @__PURE__ */ jsx28(MoreGlyph, {}),
|
|
6525
|
+
"aria-label": `Actions for ${item.name}`,
|
|
6526
|
+
disabled: busy,
|
|
6527
|
+
className: cn13("[@media(hover:hover)]:invisible [@media(hover:hover)]:group-hover:visible [@media(hover:hover)]:group-focus-within:visible", menuOpen && "[@media(hover:hover)]:visible")
|
|
6528
|
+
}
|
|
6529
|
+
) }),
|
|
6530
|
+
/* @__PURE__ */ jsxs26(DropdownMenuContent4, { align: "end", children: [
|
|
6531
|
+
user ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyUser, children: [
|
|
6532
|
+
/* @__PURE__ */ jsx28(UserGlyph, { className: "size-4" }),
|
|
6533
|
+
"Copy ",
|
|
6534
|
+
user.label.toLowerCase()
|
|
6535
|
+
] }) : null,
|
|
6536
|
+
secret ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyPassword, children: [
|
|
6537
|
+
/* @__PURE__ */ jsx28(KeyGlyph, { className: "size-4" }),
|
|
6538
|
+
"Copy password"
|
|
6539
|
+
] }) : null,
|
|
6540
|
+
code ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyCode, children: [
|
|
6541
|
+
/* @__PURE__ */ jsx28(ClockGlyph, { className: "size-4" }),
|
|
6542
|
+
"Copy one-time code"
|
|
6543
|
+
] }) : null,
|
|
6544
|
+
others.map((f) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void run(f.label, () => port.reveal(item, f.key), f.handling !== "visible"), children: [
|
|
6545
|
+
/* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
|
|
6546
|
+
"Copy ",
|
|
6547
|
+
f.label.toLowerCase()
|
|
6548
|
+
] }, f.key)),
|
|
6549
|
+
custom.map((c) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void copy(c.value, c.label, { secret: false }), children: [
|
|
6550
|
+
/* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
|
|
6551
|
+
"Copy ",
|
|
6552
|
+
c.label.toLowerCase()
|
|
6553
|
+
] }, `custom:${c.key}`)),
|
|
6554
|
+
user || secret || code || others.length > 0 || custom.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
|
|
6555
|
+
/* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => onSelect(item.id), children: [
|
|
6556
|
+
/* @__PURE__ */ jsx28(ChevronRightGlyph, { className: "size-4" }),
|
|
6557
|
+
"Open"
|
|
6558
|
+
] }),
|
|
6559
|
+
/* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyName, children: [
|
|
6560
|
+
/* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
|
|
6561
|
+
"Copy name"
|
|
6562
|
+
] }),
|
|
6563
|
+
hostEntries.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
|
|
6564
|
+
hostEntries.map((h) => /* @__PURE__ */ jsx28(DropdownMenuItem4, { onSelect: h.onSelect, children: h.label }, h.label))
|
|
6565
|
+
] })
|
|
6493
6566
|
] })
|
|
6494
6567
|
] })
|
|
6495
|
-
]
|
|
6496
|
-
|
|
6497
|
-
|
|
6568
|
+
]
|
|
6569
|
+
}
|
|
6570
|
+
);
|
|
6498
6571
|
}
|
|
6499
6572
|
|
|
6500
6573
|
// src/lists/TrashView.tsx
|
|
@@ -8121,6 +8194,7 @@ import { jsx as jsx40, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
|
8121
8194
|
var FULL_AT = 1120;
|
|
8122
8195
|
var SPLIT_AT = 640;
|
|
8123
8196
|
var NO_HOST = {};
|
|
8197
|
+
var FOCUS_GAP_MS = 2e3;
|
|
8124
8198
|
var ITEM_PANELS = {
|
|
8125
8199
|
share: SharePanel,
|
|
8126
8200
|
move: MovePanel,
|
|
@@ -8249,9 +8323,13 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8249
8323
|
const knobsRef = useRef19(host.knobs);
|
|
8250
8324
|
knobsRef.current = host.knobs;
|
|
8251
8325
|
const [signedOut, setSignedOut] = useState41(false);
|
|
8252
|
-
const
|
|
8253
|
-
|
|
8254
|
-
|
|
8326
|
+
const [refreshError, setRefreshError] = useState41(null);
|
|
8327
|
+
const [refreshing, setRefreshing] = useState41(false);
|
|
8328
|
+
const loadedAt = useRef19(0);
|
|
8329
|
+
const inFlight = useRef19(false);
|
|
8330
|
+
const apply = useCallback5(
|
|
8331
|
+
(s) => {
|
|
8332
|
+
loadedAt.current = Date.now();
|
|
8255
8333
|
setSnap(s);
|
|
8256
8334
|
setItems(s.items);
|
|
8257
8335
|
setDeleted(s.deleted);
|
|
@@ -8259,15 +8337,71 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8259
8337
|
(rows) => setDeleted([...s.deleted, ...rows.filter((r) => !s.deleted.some((d) => d.id === r.id))]),
|
|
8260
8338
|
() => void 0
|
|
8261
8339
|
);
|
|
8262
|
-
}
|
|
8340
|
+
},
|
|
8341
|
+
[port]
|
|
8342
|
+
);
|
|
8343
|
+
const refresh = useCallback5(() => {
|
|
8344
|
+
if (inFlight.current) return;
|
|
8345
|
+
inFlight.current = true;
|
|
8346
|
+
setRefreshing(true);
|
|
8347
|
+
port.load().then((s) => {
|
|
8348
|
+
setRefreshError(null);
|
|
8349
|
+
apply(s);
|
|
8350
|
+
}).catch((e) => setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."))).finally(() => {
|
|
8351
|
+
inFlight.current = false;
|
|
8352
|
+
setRefreshing(false);
|
|
8353
|
+
});
|
|
8354
|
+
port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
|
|
8355
|
+
port.lists.recents?.list().then(setRecents, () => void 0);
|
|
8356
|
+
}, [port, apply]);
|
|
8357
|
+
const load = useCallback5(() => {
|
|
8358
|
+
setError(null);
|
|
8359
|
+
setRefreshError(null);
|
|
8360
|
+
port.load().then(apply).catch((e) => {
|
|
8263
8361
|
setSignedOut(e?.status === 401);
|
|
8264
8362
|
setError(e instanceof Error ? e.message : "The vault could not be loaded.");
|
|
8265
8363
|
});
|
|
8266
8364
|
port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
|
|
8267
8365
|
knobsRef.current?.().catch(() => void 0);
|
|
8268
8366
|
port.lists.recents?.list().then(setRecents, () => void 0);
|
|
8269
|
-
}, [port]);
|
|
8367
|
+
}, [port, apply]);
|
|
8270
8368
|
useEffect23(load, [load]);
|
|
8369
|
+
const quietRef = useRef19({ loc, ready: false });
|
|
8370
|
+
quietRef.current = { loc, ready: snap !== null && !error };
|
|
8371
|
+
const [knobSeconds, setKnobSeconds] = useState41(null);
|
|
8372
|
+
useEffect23(() => {
|
|
8373
|
+
if (host.refreshSeconds !== void 0 || !knobsRef.current) return;
|
|
8374
|
+
let live = true;
|
|
8375
|
+
knobsRef.current().then(
|
|
8376
|
+
(r) => {
|
|
8377
|
+
const v = r["vault.list.refresh_seconds"];
|
|
8378
|
+
if (live && typeof v === "number" && v > 0) setKnobSeconds(v);
|
|
8379
|
+
},
|
|
8380
|
+
() => void 0
|
|
8381
|
+
);
|
|
8382
|
+
return () => {
|
|
8383
|
+
live = false;
|
|
8384
|
+
};
|
|
8385
|
+
}, [host.refreshSeconds]);
|
|
8386
|
+
const everySeconds = host.refreshSeconds ?? knobSeconds;
|
|
8387
|
+
useEffect23(() => {
|
|
8388
|
+
const quietly = (minGapMs) => {
|
|
8389
|
+
const { loc: here, ready } = quietRef.current;
|
|
8390
|
+
if (!ready || document.visibilityState === "hidden" || dirty.current || here.mode !== "view") return;
|
|
8391
|
+
if (Date.now() - loadedAt.current < minGapMs) return;
|
|
8392
|
+
refresh();
|
|
8393
|
+
};
|
|
8394
|
+
const onBack = () => quietly(FOCUS_GAP_MS);
|
|
8395
|
+
const onVisible = () => document.visibilityState === "visible" && onBack();
|
|
8396
|
+
window.addEventListener("focus", onBack);
|
|
8397
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
8398
|
+
const timer2 = everySeconds && everySeconds > 0 ? setInterval(() => quietly(0), everySeconds * 1e3) : null;
|
|
8399
|
+
return () => {
|
|
8400
|
+
window.removeEventListener("focus", onBack);
|
|
8401
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
8402
|
+
if (timer2) clearInterval(timer2);
|
|
8403
|
+
};
|
|
8404
|
+
}, [refresh, everySeconds]);
|
|
8271
8405
|
const inventoryRef = useRef19(onInventory);
|
|
8272
8406
|
inventoryRef.current = onInventory;
|
|
8273
8407
|
useEffect23(() => {
|
|
@@ -8433,6 +8567,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8433
8567
|
onSort: (sort) => go({ sort }, "replace"),
|
|
8434
8568
|
canSortViewed: port.lists.recents !== void 0,
|
|
8435
8569
|
onNew: () => go({ itemId: null, mode: "new", kind: null, panel: null }),
|
|
8570
|
+
onRefresh: refresh,
|
|
8571
|
+
refreshing,
|
|
8572
|
+
rowContextMenu: host.rowContextMenu ?? "package",
|
|
8573
|
+
...host.rowMenuItems ? { rowMenuItems: host.rowMenuItems } : {},
|
|
8436
8574
|
...matchingIds ? { matchingIds } : {},
|
|
8437
8575
|
...compactHeader ? { header: compactHeader } : {}
|
|
8438
8576
|
}
|
|
@@ -8457,14 +8595,14 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8457
8595
|
host,
|
|
8458
8596
|
organizations: orgs,
|
|
8459
8597
|
defaultOwner: ownerDefault,
|
|
8460
|
-
seed: { name: guess?.name
|
|
8598
|
+
seed: { name: loc.name?.trim() || guess?.name || "", url: guess?.url ?? loc.url.trim() },
|
|
8461
8599
|
onBack: () => go({ kind: null }),
|
|
8462
8600
|
onMode: () => void 0,
|
|
8463
8601
|
onSaved: (item, _wasNew, n, enrolled) => {
|
|
8464
8602
|
replaceItem(item);
|
|
8465
8603
|
dirty.current = false;
|
|
8466
8604
|
if (enrolled) setFirstCode(item);
|
|
8467
|
-
move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", panel: null }, "replace");
|
|
8605
|
+
move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", name: "", panel: null }, "replace");
|
|
8468
8606
|
setNotice(n);
|
|
8469
8607
|
notify("Saved");
|
|
8470
8608
|
},
|
|
@@ -8472,7 +8610,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8472
8610
|
onDirty: (d) => {
|
|
8473
8611
|
dirty.current = d;
|
|
8474
8612
|
},
|
|
8475
|
-
onCancelNew: () => go({ mode: "view", kind: null, url: "", itemId: null }),
|
|
8613
|
+
onCancelNew: () => go({ mode: "view", kind: null, url: "", name: "", itemId: null }),
|
|
8476
8614
|
onPanel: () => void 0,
|
|
8477
8615
|
onChanged: () => void 0
|
|
8478
8616
|
},
|
|
@@ -8563,13 +8701,17 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8563
8701
|
const grid = mode === "full" ? "grid-cols-[15rem_21rem_minmax(0,1fr)]" : mode === "split" ? "grid-cols-[19rem_minmax(0,1fr)]" : "grid-cols-1";
|
|
8564
8702
|
const shown = panesShown(mode, specialView !== null, showingDetail);
|
|
8565
8703
|
return /* @__PURE__ */ jsxs38("div", { ref: rootRef, className: "flex h-full min-h-0 w-full flex-col bg-background text-foreground", children: [
|
|
8704
|
+
refreshError ? /* @__PURE__ */ jsxs38("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-4 py-2 text-sm", children: [
|
|
8705
|
+
/* @__PURE__ */ jsx40("span", { className: "min-w-0 flex-1 truncate text-warning", title: refreshError, children: "Couldn\u2019t refresh. Showing what was loaded." }),
|
|
8706
|
+
/* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
|
|
8707
|
+
] }) : null,
|
|
8566
8708
|
snap.unavailable.length > 0 ? /* @__PURE__ */ jsxs38("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-4 py-2 text-sm", children: [
|
|
8567
8709
|
/* @__PURE__ */ jsxs38("span", { className: "min-w-0 flex-1 truncate text-warning", children: [
|
|
8568
8710
|
"Couldn\u2019t load ",
|
|
8569
8711
|
snap.unavailable.join(", "),
|
|
8570
8712
|
". Showing the rest."
|
|
8571
8713
|
] }),
|
|
8572
|
-
/* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick:
|
|
8714
|
+
/* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
|
|
8573
8715
|
] }) : null,
|
|
8574
8716
|
/* @__PURE__ */ jsxs38("div", { className: `grid min-h-0 w-full flex-1 ${grid} overflow-hidden`, children: [
|
|
8575
8717
|
mode === "full" ? /* @__PURE__ */ jsx40(
|