@ai-matrx/vault 0.1.20 → 0.1.22
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 +18 -0
- package/dist/index.cjs +250 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -2
- package/dist/index.d.ts +38 -2
- package/dist/index.js +250 -123
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -553,6 +553,8 @@ function ConfirmIdentityDialog({ open, identity, onDone, onCancel }) {
|
|
|
553
553
|
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
554
554
|
import { Badge, Button as Button2, Field as Field2, Textarea } from "@ai-matrx/design-system/controls";
|
|
555
555
|
import { cn as cn3 } from "@ai-matrx/design-system";
|
|
556
|
+
import { withReferral } from "@ai-matrx/kit/outbound";
|
|
557
|
+
import { OutboundLink } from "@ai-matrx/kit/outbound-react";
|
|
556
558
|
import { CopyIcon } from "@ai-matrx/design-system/icons";
|
|
557
559
|
|
|
558
560
|
// src/hooks.ts
|
|
@@ -764,13 +766,13 @@ function PlainRow({ label, value, hint: hint2, openUrl, extra, multiline = false
|
|
|
764
766
|
top: multiline,
|
|
765
767
|
actions: /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
766
768
|
extra,
|
|
767
|
-
isUrl && openUrl ? /* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: /* @__PURE__ */ jsx5(ExternalGlyph, {}), "aria-label": `Open ${value}`, onClick: () => openUrl(value) }) : null,
|
|
769
|
+
isUrl && openUrl ? /* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: /* @__PURE__ */ jsx5(ExternalGlyph, {}), "aria-label": `Open ${value}`, onClick: () => openUrl(withReferral(value)) }) : null,
|
|
768
770
|
/* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: /* @__PURE__ */ jsx5(CopyIcon, {}), "aria-label": `Copy ${typeof label === "string" ? label : "value"}`, onClick: () => void copyValue2(value, typeof label === "string" && label ? label : "Value", { secret: false }) })
|
|
769
771
|
] }),
|
|
770
772
|
children: isUrl && openUrl ? (
|
|
771
773
|
// A real link (right-click, copy link address, open in a new tab); a click goes through the host's opener.
|
|
772
774
|
/* @__PURE__ */ jsx5(
|
|
773
|
-
|
|
775
|
+
OutboundLink,
|
|
774
776
|
{
|
|
775
777
|
href: value,
|
|
776
778
|
target: "_blank",
|
|
@@ -778,7 +780,7 @@ function PlainRow({ label, value, hint: hint2, openUrl, extra, multiline = false
|
|
|
778
780
|
className: "block max-w-full truncate text-primary hover:underline",
|
|
779
781
|
onClick: (e) => {
|
|
780
782
|
e.preventDefault();
|
|
781
|
-
openUrl(value);
|
|
783
|
+
openUrl(withReferral(value));
|
|
782
784
|
},
|
|
783
785
|
children: value.replace(/^https?:\/\//i, "").replace(/\/$/, "")
|
|
784
786
|
}
|
|
@@ -847,6 +849,19 @@ import { formatAbsoluteDate as formatAbsoluteDate2 } from "@ai-matrx/kit/format"
|
|
|
847
849
|
import { formatRelativeTime } from "@ai-matrx/kit/format";
|
|
848
850
|
|
|
849
851
|
// src/model.ts
|
|
852
|
+
function manageUnavailableReason(item) {
|
|
853
|
+
if (item.capabilities.canManage) return null;
|
|
854
|
+
if (item.owner.memberShared) return "Only the member who owns it can share, move or delete it";
|
|
855
|
+
if (item.owner.kind === "shared") return "Only its owner can share, move or delete it";
|
|
856
|
+
if (item.owner.kind === "organization") return "Only this organization's vault admins can change that";
|
|
857
|
+
return "You can use this item but not share, move or delete it";
|
|
858
|
+
}
|
|
859
|
+
function ownerDetail(owner) {
|
|
860
|
+
if (owner.memberShared) return `A member's own credential, shared into ${owner.label}`;
|
|
861
|
+
if (owner.kind === "organization") return `Organization: ${owner.label}`;
|
|
862
|
+
if (owner.kind === "shared") return "Shared with you by its owner";
|
|
863
|
+
return "Your own vault";
|
|
864
|
+
}
|
|
850
865
|
var DEFAULT_LOCATION = { vault: "all", view: "all", q: "", sort: "name", itemId: null, mode: "view", kind: null, url: "", panel: null };
|
|
851
866
|
var SORTS = ["name", "name-desc", "website", "updated", "created", "viewed"];
|
|
852
867
|
function locationFromParams(params, itemId = params.get("item")) {
|
|
@@ -5989,6 +6004,7 @@ function ItemPane(props) {
|
|
|
5989
6004
|
const setValue = (key, v) => setDraft((d) => ({ ...d, values: { ...d.values, [key]: v } }));
|
|
5990
6005
|
const canEdit = item ? item.capabilities.canEdit || item.capabilities.canManage : true;
|
|
5991
6006
|
const canManage = item?.capabilities.canManage ?? false;
|
|
6007
|
+
const manageReason = item ? manageUnavailableReason(item) : null;
|
|
5992
6008
|
async function save() {
|
|
5993
6009
|
if (!definition && !item) return;
|
|
5994
6010
|
setBusy(true);
|
|
@@ -6135,7 +6151,8 @@ function ItemPane(props) {
|
|
|
6135
6151
|
/* @__PURE__ */ jsx27("h2", { className: "truncate text-base font-semibold leading-tight", children: editing ? draft.name || (mode === "new" ? `New ${typeLabel.toLowerCase()}` : item?.name) : item?.name }),
|
|
6136
6152
|
/* @__PURE__ */ jsxs25("span", { className: "flex min-w-0 items-center gap-1 text-xs text-muted-foreground", children: [
|
|
6137
6153
|
typeLabel ? /* @__PURE__ */ jsx27("span", { className: "truncate", children: typeLabel }) : null,
|
|
6138
|
-
shownOwner ? /* @__PURE__ */ jsx27(Badge7, { className: "shrink-0", icon: shownOwner.glyph, "aria-label": `Saved in ${shownOwner.label}`, children: shownOwner.label }) : null,
|
|
6154
|
+
shownOwner ? /* @__PURE__ */ jsx27(Badge7, { className: "shrink-0", icon: shownOwner.glyph, "aria-label": `Saved in ${shownOwner.label}`, title: item && mode !== "new" ? ownerDetail(item.owner) : void 0, children: shownOwner.label }) : null,
|
|
6155
|
+
item && mode !== "new" && item.owner.memberShared ? /* @__PURE__ */ jsx27("span", { className: "shrink-0", "data-member-shared": "", children: "\xB7 A member\u2019s own" }) : null,
|
|
6139
6156
|
item && !editing ? /* @__PURE__ */ jsx27(SharingSummary, { item, port, className: "hidden truncate before:mr-1 before:content-['\xB7'] sm:inline" }, `${item.updatedAt}-${refreshKey ?? ""}`) : null
|
|
6140
6157
|
] })
|
|
6141
6158
|
] }),
|
|
@@ -6173,9 +6190,9 @@ function ItemPane(props) {
|
|
|
6173
6190
|
/* @__PURE__ */ jsx27(ShareGlyph, { className: "size-4" }),
|
|
6174
6191
|
"Share"
|
|
6175
6192
|
] }) : null,
|
|
6176
|
-
canManage ? /* @__PURE__ */ jsxs25(DropdownMenuItem3, { onSelect: () => onPanel("move"), children: [
|
|
6193
|
+
canManage || item.capabilities.canEdit ? /* @__PURE__ */ jsxs25(DropdownMenuItem3, { onSelect: () => onPanel("move"), children: [
|
|
6177
6194
|
/* @__PURE__ */ jsx27(MoveGlyph, { className: "size-4" }),
|
|
6178
|
-
"Move or copy"
|
|
6195
|
+
canManage ? "Move or copy" : "Copy to\u2026"
|
|
6179
6196
|
] }) : null,
|
|
6180
6197
|
item.capabilities.canReveal ? /* @__PURE__ */ jsxs25(DropdownMenuItem3, { onSelect: () => onPanel("send"), children: [
|
|
6181
6198
|
/* @__PURE__ */ jsx27(SendGlyph, { className: "size-4" }),
|
|
@@ -6199,6 +6216,17 @@ function ItemPane(props) {
|
|
|
6199
6216
|
/* @__PURE__ */ jsx27(TrashGlyph, { className: "size-4" }),
|
|
6200
6217
|
"Delete"
|
|
6201
6218
|
] })
|
|
6219
|
+
] }) : manageReason ? /* @__PURE__ */ jsxs25(Fragment18, { children: [
|
|
6220
|
+
/* @__PURE__ */ jsx27(DropdownMenuSeparator3, {}),
|
|
6221
|
+
/* @__PURE__ */ jsxs25(DropdownMenuItem3, { disabled: true, children: [
|
|
6222
|
+
/* @__PURE__ */ jsx27(ShareGlyph, { className: "size-4" }),
|
|
6223
|
+
"Share"
|
|
6224
|
+
] }),
|
|
6225
|
+
/* @__PURE__ */ jsxs25(DropdownMenuItem3, { disabled: true, children: [
|
|
6226
|
+
/* @__PURE__ */ jsx27(TrashGlyph, { className: "size-4" }),
|
|
6227
|
+
"Delete"
|
|
6228
|
+
] }),
|
|
6229
|
+
/* @__PURE__ */ jsx27("div", { className: "max-w-64 px-2 py-1.5 text-xs text-muted-foreground", "data-manage-reason": "", children: manageReason })
|
|
6202
6230
|
] }) : null
|
|
6203
6231
|
] })
|
|
6204
6232
|
] })
|
|
@@ -6429,6 +6457,17 @@ function ItemPane(props) {
|
|
|
6429
6457
|
] }) });
|
|
6430
6458
|
}
|
|
6431
6459
|
|
|
6460
|
+
// src/row-menu.ts
|
|
6461
|
+
var readers = /* @__PURE__ */ new WeakMap();
|
|
6462
|
+
function registerRowMenu(el, read) {
|
|
6463
|
+
if (el) readers.set(el, read);
|
|
6464
|
+
}
|
|
6465
|
+
function rowMenuEntriesAt(target) {
|
|
6466
|
+
const row = target?.closest("[data-item]");
|
|
6467
|
+
const read = row ? readers.get(row) : void 0;
|
|
6468
|
+
return read ? read() : [];
|
|
6469
|
+
}
|
|
6470
|
+
|
|
6432
6471
|
// src/ItemList.tsx
|
|
6433
6472
|
import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
6434
6473
|
var RECOVERY_KEYS = /^recovery_codes?$/;
|
|
@@ -6450,7 +6489,7 @@ function subtitleOf(item, withOwner) {
|
|
|
6450
6489
|
const what = loginSiteOf(item) || item.definitionLabel;
|
|
6451
6490
|
return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
|
|
6452
6491
|
}
|
|
6453
|
-
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems }) {
|
|
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 }) {
|
|
6454
6493
|
const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
|
|
6455
6494
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6456
6495
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
@@ -6471,7 +6510,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6471
6510
|
}
|
|
6472
6511
|
};
|
|
6473
6512
|
const mixedOwners = showOwner ?? new Set(items.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
|
|
6474
|
-
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);
|
|
6513
|
+
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, favorite: onToggleFavorite ? { on: favorites?.has(i.id) ?? false, toggle: () => onToggleFavorite(i.id) } : void 0 }, i.id);
|
|
6475
6514
|
const sortLabel = sorts.find((s) => s.value === sort)?.label ?? "Name A\u2013Z";
|
|
6476
6515
|
return /* @__PURE__ */ jsxs26("section", { "aria-label": title, className: "flex h-full min-h-0 flex-col border-r border-border bg-background", children: [
|
|
6477
6516
|
/* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-2 px-3 pb-2 pt-3", children: [
|
|
@@ -6481,10 +6520,18 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6481
6520
|
/* @__PURE__ */ jsx28(Button23, { variant: "primary", icon: /* @__PURE__ */ jsx28(PlusGlyph, {}), "aria-label": "New item", onClick: onNew })
|
|
6482
6521
|
] }),
|
|
6483
6522
|
/* @__PURE__ */ jsxs26("div", { className: "flex items-center justify-between gap-2 px-1", children: [
|
|
6484
|
-
/* @__PURE__ */ jsxs26("span", { className: "truncate text-xs text-muted-foreground", children: [
|
|
6523
|
+
/* @__PURE__ */ jsxs26("span", { className: "flex min-w-0 items-center gap-1.5 truncate text-xs text-muted-foreground", role: "status", "aria-live": "polite", children: [
|
|
6485
6524
|
items.length,
|
|
6486
6525
|
" ",
|
|
6487
|
-
items.length === 1 ? "item" : "items"
|
|
6526
|
+
items.length === 1 ? "item" : "items",
|
|
6527
|
+
stillLoading && stillLoading.length > 0 ? /* @__PURE__ */ jsxs26("span", { className: "flex min-w-0 items-center gap-1 truncate", title: `Still loading ${stillLoading.join(", ")}`, "data-still-loading": "", children: [
|
|
6528
|
+
/* @__PURE__ */ jsx28(RefreshGlyph, { className: "size-3 shrink-0 animate-spin" }),
|
|
6529
|
+
/* @__PURE__ */ jsxs26("span", { className: "truncate", children: [
|
|
6530
|
+
"loading ",
|
|
6531
|
+
stillLoading.length === 1 ? stillLoading[0] : `${stillLoading.length} more`,
|
|
6532
|
+
"\u2026"
|
|
6533
|
+
] })
|
|
6534
|
+
] }) : null
|
|
6488
6535
|
] }),
|
|
6489
6536
|
/* @__PURE__ */ jsxs26("span", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
6490
6537
|
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,
|
|
@@ -6496,7 +6543,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6496
6543
|
] })
|
|
6497
6544
|
] }),
|
|
6498
6545
|
/* @__PURE__ */ jsx28("div", { className: "min-h-0 flex-1 overflow-y-auto px-2 pb-3", onKeyDown, children: items.length === 0 ? /* @__PURE__ */ jsxs26("div", { className: "flex flex-col items-center gap-3 px-4 py-12 text-center", children: [
|
|
6499
|
-
/* @__PURE__ */ jsx28("span", { className: "text-sm text-muted-foreground", children: query ? `Nothing matches \u201C${query}\u201D.` : "Nothing here yet." }),
|
|
6546
|
+
/* @__PURE__ */ jsx28("span", { className: "text-sm text-muted-foreground", children: query ? `Nothing matches \u201C${query}\u201D.` : stillLoading && stillLoading.length > 0 ? "Still loading\u2026" : "Nothing here yet." }),
|
|
6500
6547
|
query ? /* @__PURE__ */ jsx28(Button23, { variant: "outline", onClick: () => onQuery(""), children: "Clear search" }) : /* @__PURE__ */ jsx28(Button23, { variant: "outline", icon: /* @__PURE__ */ jsx28(PlusGlyph, {}), onClick: onNew, children: "New item" })
|
|
6501
6548
|
] }) : /* @__PURE__ */ jsxs26("ul", { ref: listRef, className: "flex flex-col gap-px", children: [
|
|
6502
6549
|
pinned.length > 0 ? /* @__PURE__ */ jsx28("li", { className: "px-2 pb-1 pt-1 text-xs font-medium text-muted-foreground", children: "This page" }) : null,
|
|
@@ -6506,7 +6553,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6506
6553
|
] }) })
|
|
6507
6554
|
] });
|
|
6508
6555
|
}
|
|
6509
|
-
function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra }) {
|
|
6556
|
+
function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra, favorite }) {
|
|
6510
6557
|
const confirmed = useConfirmed();
|
|
6511
6558
|
const copy = useCopy();
|
|
6512
6559
|
const notify = useNotify();
|
|
@@ -6534,9 +6581,32 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6534
6581
|
const others = otherCopyFields(item, user, secret);
|
|
6535
6582
|
const custom = canUse ? item.custom.filter((c) => c.value) : [];
|
|
6536
6583
|
const hostEntries = extra?.(item) ?? [];
|
|
6584
|
+
const copyEntries = [
|
|
6585
|
+
...user ? [{ id: "copy:username", kind: "copy", label: `Copy ${user.label.toLowerCase()}`, onSelect: copyUser }] : [],
|
|
6586
|
+
...secret ? [{ id: "copy:password", kind: "copy", label: "Copy password", onSelect: copyPassword }] : [],
|
|
6587
|
+
...code ? [{ id: "copy:code", kind: "copy", label: "Copy one-time code", onSelect: copyCode }] : [],
|
|
6588
|
+
...others.map((f) => ({ id: `copy:${f.key}`, kind: "copy", label: `Copy ${f.label.toLowerCase()}`, onSelect: () => void run(f.label, () => port.reveal(item, f.key), f.handling !== "visible") })),
|
|
6589
|
+
...custom.map((c) => ({ id: `copy:custom:${c.key}`, kind: "copy", label: `Copy ${c.label.toLowerCase()}`, onSelect: () => void copy(c.value, c.label, { secret: false }) }))
|
|
6590
|
+
];
|
|
6591
|
+
const favoriteEntry = favorite ? { id: "favorite", kind: "favorite", label: favorite.on ? "Remove from favorites" : "Add to favorites", onSelect: favorite.toggle } : null;
|
|
6592
|
+
const entries = [
|
|
6593
|
+
...copyEntries,
|
|
6594
|
+
...favoriteEntry ? [favoriteEntry] : [],
|
|
6595
|
+
{ id: "open", kind: "open", label: "Open", onSelect: () => onSelect(item.id) },
|
|
6596
|
+
{ id: "copy-name", kind: "copy-name", label: "Copy name", onSelect: copyName },
|
|
6597
|
+
...hostEntries.map((h) => ({ ...h, kind: h.kind ?? "host" }))
|
|
6598
|
+
];
|
|
6599
|
+
const entriesRef = useRef15(entries);
|
|
6600
|
+
entriesRef.current = entries;
|
|
6601
|
+
const glyphOf = (e) => e.id === "copy:username" ? /* @__PURE__ */ jsx28(UserGlyph, { className: "size-4" }) : e.id === "copy:password" ? /* @__PURE__ */ jsx28(KeyGlyph, { className: "size-4" }) : e.id === "copy:code" ? /* @__PURE__ */ jsx28(ClockGlyph, { className: "size-4" }) : e.kind === "favorite" ? /* @__PURE__ */ jsx28(StarGlyph, { className: cn13("size-4", favorite?.on && "fill-current text-warning") }) : e.kind === "open" ? /* @__PURE__ */ jsx28(ChevronRightGlyph, { className: "size-4" }) : e.kind === "host" ? null : /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" });
|
|
6602
|
+
const menuItem = (e) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { disabled: e.disabled ?? false, onSelect: e.onSelect, children: [
|
|
6603
|
+
glyphOf(e),
|
|
6604
|
+
e.label
|
|
6605
|
+
] }, e.id ?? e.label);
|
|
6537
6606
|
return /* @__PURE__ */ jsxs26(
|
|
6538
6607
|
"li",
|
|
6539
6608
|
{
|
|
6609
|
+
ref: (el) => registerRowMenu(el, () => entriesRef.current),
|
|
6540
6610
|
className: "group relative",
|
|
6541
6611
|
"data-item": item.id,
|
|
6542
6612
|
onContextMenu: ownContextMenu ? (e) => {
|
|
@@ -6562,7 +6632,8 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6562
6632
|
}
|
|
6563
6633
|
),
|
|
6564
6634
|
/* @__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: [
|
|
6565
|
-
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: [
|
|
6635
|
+
user || secret || favorite ? /* @__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: [
|
|
6636
|
+
favorite ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(StarGlyph, { className: favorite.on ? "fill-current text-warning" : "" }), "aria-label": favorite.on ? "Remove from favorites" : "Add to favorites", "aria-pressed": favorite.on, onClick: favorite.toggle }) : null,
|
|
6566
6637
|
user ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
|
|
6567
6638
|
secret ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
|
|
6568
6639
|
] }) : null,
|
|
@@ -6578,40 +6649,11 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6578
6649
|
}
|
|
6579
6650
|
) }),
|
|
6580
6651
|
/* @__PURE__ */ jsxs26(DropdownMenuContent4, { align: "end", children: [
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
user.label.toLowerCase()
|
|
6585
|
-
] }) : null,
|
|
6586
|
-
secret ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyPassword, children: [
|
|
6587
|
-
/* @__PURE__ */ jsx28(KeyGlyph, { className: "size-4" }),
|
|
6588
|
-
"Copy password"
|
|
6589
|
-
] }) : null,
|
|
6590
|
-
code ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyCode, children: [
|
|
6591
|
-
/* @__PURE__ */ jsx28(ClockGlyph, { className: "size-4" }),
|
|
6592
|
-
"Copy one-time code"
|
|
6593
|
-
] }) : null,
|
|
6594
|
-
others.map((f) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void run(f.label, () => port.reveal(item, f.key), f.handling !== "visible"), children: [
|
|
6595
|
-
/* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
|
|
6596
|
-
"Copy ",
|
|
6597
|
-
f.label.toLowerCase()
|
|
6598
|
-
] }, f.key)),
|
|
6599
|
-
custom.map((c) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void copy(c.value, c.label, { secret: false }), children: [
|
|
6600
|
-
/* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
|
|
6601
|
-
"Copy ",
|
|
6602
|
-
c.label.toLowerCase()
|
|
6603
|
-
] }, `custom:${c.key}`)),
|
|
6604
|
-
user || secret || code || others.length > 0 || custom.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
|
|
6605
|
-
/* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => onSelect(item.id), children: [
|
|
6606
|
-
/* @__PURE__ */ jsx28(ChevronRightGlyph, { className: "size-4" }),
|
|
6607
|
-
"Open"
|
|
6608
|
-
] }),
|
|
6609
|
-
/* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyName, children: [
|
|
6610
|
-
/* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
|
|
6611
|
-
"Copy name"
|
|
6612
|
-
] }),
|
|
6652
|
+
copyEntries.map(menuItem),
|
|
6653
|
+
copyEntries.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
|
|
6654
|
+
entries.filter((e) => e.kind === "favorite" || e.kind === "open" || e.kind === "copy-name").map(menuItem),
|
|
6613
6655
|
hostEntries.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
|
|
6614
|
-
|
|
6656
|
+
entries.filter((e) => e.kind === "host").map(menuItem)
|
|
6615
6657
|
] })
|
|
6616
6658
|
] })
|
|
6617
6659
|
] })
|
|
@@ -8119,15 +8161,17 @@ import { Select as Select11 } from "@ai-matrx/design-system/controls";
|
|
|
8119
8161
|
import { GlobeIcon as GlobeIcon5 } from "@ai-matrx/design-system/icons";
|
|
8120
8162
|
import { jsx as jsx39, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
8121
8163
|
var AUTHENTICATOR_LABEL = "Authenticator";
|
|
8122
|
-
function vaultChoices(items, organizations,
|
|
8164
|
+
function vaultChoices(items, organizations, _current) {
|
|
8123
8165
|
const n = (f) => items.filter(f).length;
|
|
8124
8166
|
const shared = n((i) => i.owner.kind === "shared");
|
|
8125
8167
|
return [
|
|
8126
8168
|
{ value: "all", label: "All items", count: items.length },
|
|
8127
8169
|
{ value: "mine", label: "Personal", count: n((i) => i.owner.kind === "me") },
|
|
8128
8170
|
...shared > 0 ? [{ value: "shared", label: "Shared with me", count: shared }] : [],
|
|
8129
|
-
//
|
|
8130
|
-
|
|
8171
|
+
// Every organization the person belongs to, empty ones too: an empty vault opens on its own
|
|
8172
|
+
// "Nothing here yet" with New item, so the first credential can go there. (The organization
|
|
8173
|
+
// list carries no role, so who may add is the server's answer to the save, said in place.)
|
|
8174
|
+
...organizations.map((o) => ({ value: o.id, label: o.name, count: n((i) => i.owner.organizationId === o.id) }))
|
|
8131
8175
|
];
|
|
8132
8176
|
}
|
|
8133
8177
|
function VaultSelect({ value, choices, onChange }) {
|
|
@@ -8407,7 +8451,16 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8407
8451
|
const load = useCallback5(() => {
|
|
8408
8452
|
setError(null);
|
|
8409
8453
|
setRefreshError(null);
|
|
8410
|
-
|
|
8454
|
+
let finished = false;
|
|
8455
|
+
port.load((partial) => {
|
|
8456
|
+
if (finished) return;
|
|
8457
|
+
setSnap(partial);
|
|
8458
|
+
setItems(partial.items);
|
|
8459
|
+
setDeleted(partial.deleted);
|
|
8460
|
+
}).then((s) => {
|
|
8461
|
+
finished = true;
|
|
8462
|
+
apply(s);
|
|
8463
|
+
}).catch((e) => {
|
|
8411
8464
|
setSignedOut(e?.status === 401);
|
|
8412
8465
|
setError(e instanceof Error ? e.message : "The vault could not be loaded.");
|
|
8413
8466
|
});
|
|
@@ -8524,59 +8577,62 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8524
8577
|
}
|
|
8525
8578
|
const hasFavorites = port.lists.favorites !== void 0;
|
|
8526
8579
|
const title = loc.vault === "all" && loc.view === "all" ? "All items" : viewLabel(loc.view, views);
|
|
8527
|
-
const compactHeader = mode === "full" ? void 0 :
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
/* @__PURE__ */
|
|
8531
|
-
|
|
8532
|
-
{
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
/* @__PURE__ */ jsxs38(
|
|
8545
|
-
/* @__PURE__ */
|
|
8546
|
-
|
|
8547
|
-
"
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
"
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
"
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
"
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
"
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
"
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8580
|
+
const compactHeader = mode === "full" ? void 0 : (
|
|
8581
|
+
// The two pickers keep a real width at every compact size (the list column is 19rem in split
|
|
8582
|
+
// mode): the Authenticator button is icon-only beside them, its name in its tooltip.
|
|
8583
|
+
/* @__PURE__ */ jsxs38("div", { className: "grid grid-cols-[minmax(6rem,1fr)_minmax(6rem,1fr)_auto_auto] gap-2", "data-vault-compact-header": "", children: [
|
|
8584
|
+
/* @__PURE__ */ jsx40(VaultSelect, { value: loc.vault, choices, onChange: (vault) => go({ vault, itemId: null, panel: null }) }),
|
|
8585
|
+
/* @__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), "aria-label": "Show" }),
|
|
8586
|
+
/* @__PURE__ */ jsx40(
|
|
8587
|
+
Button34,
|
|
8588
|
+
{
|
|
8589
|
+
variant: loc.view === "authenticator" ? "outline" : "quiet",
|
|
8590
|
+
icon: /* @__PURE__ */ jsx40(LockGlyph, {}),
|
|
8591
|
+
"aria-label": AUTHENTICATOR_LABEL,
|
|
8592
|
+
"aria-pressed": loc.view === "authenticator",
|
|
8593
|
+
title: AUTHENTICATOR_LABEL,
|
|
8594
|
+
onClick: () => go({ view: loc.view === "authenticator" ? "all" : "authenticator", itemId: null, mode: "view", panel: null })
|
|
8595
|
+
}
|
|
8596
|
+
),
|
|
8597
|
+
/* @__PURE__ */ jsxs38(DropdownMenu6, { children: [
|
|
8598
|
+
/* @__PURE__ */ jsx40(DropdownMenuTrigger6, { asChild: true, children: /* @__PURE__ */ jsx40(Button34, { variant: "quiet", icon: /* @__PURE__ */ jsx40(MoreGlyph, {}), "aria-label": "Vault actions" }) }),
|
|
8599
|
+
/* @__PURE__ */ jsxs38(DropdownMenuContent6, { align: "end", children: [
|
|
8600
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "import" }), children: [
|
|
8601
|
+
/* @__PURE__ */ jsx40(ImportGlyph, { className: "size-4" }),
|
|
8602
|
+
"Import"
|
|
8603
|
+
] }),
|
|
8604
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "export" }), children: [
|
|
8605
|
+
/* @__PURE__ */ jsx40(ExportGlyph, { className: "size-4" }),
|
|
8606
|
+
"Export"
|
|
8607
|
+
] }),
|
|
8608
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "backup" }), children: [
|
|
8609
|
+
/* @__PURE__ */ jsx40(SafeGlyph, { className: "size-4" }),
|
|
8610
|
+
"Backup"
|
|
8611
|
+
] }),
|
|
8612
|
+
/* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "devices" }), children: [
|
|
8613
|
+
/* @__PURE__ */ jsx40(ShieldGlyph, { className: "size-4" }),
|
|
8614
|
+
"Browsers that fill"
|
|
8615
|
+
] }),
|
|
8616
|
+
host.nativeImport ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => go({ panel: "import-passkeys" }), children: [
|
|
8617
|
+
/* @__PURE__ */ jsx40(ImportGlyph, { className: "size-4" }),
|
|
8618
|
+
"Import passkeys"
|
|
8619
|
+
] }) : null,
|
|
8620
|
+
host.nativeProvider && host.nativeProvider.status !== "unavailable" ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: host.nativeProvider.openSetup, children: [
|
|
8621
|
+
/* @__PURE__ */ jsx40(ShieldGlyph, { className: "size-4" }),
|
|
8622
|
+
"System AutoFill"
|
|
8623
|
+
] }) : null,
|
|
8624
|
+
host.lock?.lock ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: () => void host.lock?.lock?.(), children: [
|
|
8625
|
+
/* @__PURE__ */ jsx40(LockGlyph, { className: "size-4" }),
|
|
8626
|
+
"Lock"
|
|
8627
|
+
] }) : null,
|
|
8628
|
+
host.openWebVault ? /* @__PURE__ */ jsxs38(DropdownMenuItem6, { onSelect: host.openWebVault, children: [
|
|
8629
|
+
/* @__PURE__ */ jsx40(ExternalGlyph, { className: "size-4" }),
|
|
8630
|
+
"Open on the web"
|
|
8631
|
+
] }) : null
|
|
8632
|
+
] })
|
|
8577
8633
|
] })
|
|
8578
8634
|
] })
|
|
8579
|
-
|
|
8635
|
+
);
|
|
8580
8636
|
const viewHeader = /* @__PURE__ */ jsxs38("div", { className: "flex flex-col gap-2", children: [
|
|
8581
8637
|
compactHeader,
|
|
8582
8638
|
/* @__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)}` })
|
|
@@ -8619,6 +8675,8 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8619
8675
|
onNew: () => go({ itemId: null, mode: "new", kind: null, panel: null }),
|
|
8620
8676
|
onRefresh: refresh,
|
|
8621
8677
|
refreshing,
|
|
8678
|
+
...snap.pending && snap.pending.length > 0 ? { stillLoading: snap.pending } : {},
|
|
8679
|
+
...hasFavorites ? { favorites: favorites ?? /* @__PURE__ */ new Set(), onToggleFavorite: toggleFavorite } : {},
|
|
8622
8680
|
rowContextMenu: host.rowContextMenu ?? "package",
|
|
8623
8681
|
...host.rowMenuItems ? { rowMenuItems: host.rowMenuItems } : {},
|
|
8624
8682
|
...matchingIds ? { matchingIds } : {},
|
|
@@ -9508,6 +9566,12 @@ function createApiVaultPort(api, options = {}) {
|
|
|
9508
9566
|
if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
|
|
9509
9567
|
return mine || userId ? { kind: "me", label: "Personal", organizationId: null } : { kind: "shared", label: "Shared with me", organizationId: null };
|
|
9510
9568
|
};
|
|
9569
|
+
const ownerInOrgList = (w, via, meId) => {
|
|
9570
|
+
if (w.organization_id) return orgOf(w.organization_id, null, false);
|
|
9571
|
+
if (w.user_id && meId && w.user_id === meId) return { kind: "me", label: "Personal", organizationId: null };
|
|
9572
|
+
if (via) return { kind: "organization", label: via.name, organizationId: via.id, memberShared: { userId: w.user_id ?? null } };
|
|
9573
|
+
return { kind: "shared", label: "Shared with me", organizationId: null };
|
|
9574
|
+
};
|
|
9511
9575
|
async function refetch(id, owner) {
|
|
9512
9576
|
const w = await account9.getItem(api, { path: { item_id: id } });
|
|
9513
9577
|
return itemOf(w, owner, defs, auth);
|
|
@@ -9535,38 +9599,100 @@ function createApiVaultPort(api, options = {}) {
|
|
|
9535
9599
|
io: createIoPort(ctx),
|
|
9536
9600
|
fields: createFieldsPort(ctx),
|
|
9537
9601
|
lists: createListsPort(ctx, options.userState),
|
|
9538
|
-
async load() {
|
|
9602
|
+
async load(onProgress) {
|
|
9539
9603
|
const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
|
|
9540
9604
|
const authP = retrying(() => apps2.listAuthenticators(api));
|
|
9541
9605
|
const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
|
|
9542
9606
|
const sharedP = retrying(() => account9.sharedWithMe(api));
|
|
9543
9607
|
const quiet = (p) => void p.catch(() => void 0);
|
|
9544
9608
|
[catalogP, authP, userP, sharedP].forEach(quiet);
|
|
9545
|
-
const
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
const [authSettled, sharedSettled, ...orgSettled] = await Promise.allSettled([authP, sharedP, ...orgListsP]);
|
|
9609
|
+
const raw = {
|
|
9610
|
+
orgLists: [],
|
|
9611
|
+
orgsKnown: options.organizations !== void 0,
|
|
9612
|
+
meId: null
|
|
9613
|
+
};
|
|
9551
9614
|
const unavailable = [];
|
|
9552
|
-
|
|
9553
|
-
const
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) continue;
|
|
9564
|
-
const owner = sharedList ? orgOf(w.organization_id, null, false) : orgOf(w.organization_id, w.user_id, i === 0);
|
|
9565
|
-
const item = itemOf(w, sharedList && !w.organization_id ? { kind: "shared", label: "Shared with me", organizationId: null } : owner, defs, auth);
|
|
9615
|
+
let done = false;
|
|
9616
|
+
const build = () => {
|
|
9617
|
+
const catalog2 = raw.catalog ?? [];
|
|
9618
|
+
defs = new Map(parseDefinitions(catalog2.filter((e) => e.is_active).map((e) => ({ key: e.key, payload: e.payload, sort_order: e.sort_order }))).map((d) => [d.key, d]));
|
|
9619
|
+
const mineItems = raw.mine?.items ?? [];
|
|
9620
|
+
const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
|
|
9621
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9622
|
+
const deleted = /* @__PURE__ */ new Map();
|
|
9623
|
+
const take = (w, owner) => {
|
|
9624
|
+
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
|
|
9625
|
+
const item = itemOf(w, owner, defs, auth);
|
|
9566
9626
|
(isDeleted(w) ? deleted : seen).set(w.id, item);
|
|
9627
|
+
};
|
|
9628
|
+
for (const w of mineItems) take(w, orgOf(w.organization_id, null, true));
|
|
9629
|
+
raw.orgLists.forEach((l, i) => {
|
|
9630
|
+
const via = orgs[i];
|
|
9631
|
+
for (const w of l?.items ?? []) take(w, ownerInOrgList(w, via, meId));
|
|
9632
|
+
});
|
|
9633
|
+
for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null });
|
|
9634
|
+
const pending = done ? [] : [...raw.orgsKnown ? orgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
|
|
9635
|
+
return {
|
|
9636
|
+
unavailable: [...unavailable],
|
|
9637
|
+
...pending.length > 0 ? { pending } : {},
|
|
9638
|
+
items: [...seen.values()],
|
|
9639
|
+
deleted: [...deleted.values()],
|
|
9640
|
+
organizations: orgs,
|
|
9641
|
+
definitions: [...defs.values()].sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label))
|
|
9642
|
+
};
|
|
9643
|
+
};
|
|
9644
|
+
const progress = () => {
|
|
9645
|
+
if (!done && onProgress && raw.catalog && raw.mine) onProgress(build());
|
|
9646
|
+
};
|
|
9647
|
+
const orgReportP = options.organizations ? null : retrying(() => platform.listMyOrganizations(api));
|
|
9648
|
+
if (orgReportP) quiet(orgReportP);
|
|
9649
|
+
const orgListsReady = (async () => {
|
|
9650
|
+
const report = orgReportP ? await orgReportP : null;
|
|
9651
|
+
raw.meId = report?.user_id ?? null;
|
|
9652
|
+
orgs = [...new Map((options.organizations ?? (report?.organizations ?? []).filter((o) => !o.archived_at).map((o) => ({ id: o.id, name: o.name }))).map((o) => [o.id, o])).values()];
|
|
9653
|
+
raw.orgLists = orgs.map(() => void 0);
|
|
9654
|
+
raw.orgsKnown = true;
|
|
9655
|
+
progress();
|
|
9656
|
+
return orgs.map((o, i) => {
|
|
9657
|
+
const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
|
|
9658
|
+
const arrived = p.then((l) => {
|
|
9659
|
+
raw.orgLists[i] = l;
|
|
9660
|
+
progress();
|
|
9661
|
+
return l;
|
|
9662
|
+
});
|
|
9663
|
+
quiet(arrived);
|
|
9664
|
+
return arrived;
|
|
9665
|
+
});
|
|
9666
|
+
})();
|
|
9667
|
+
quiet(orgListsReady);
|
|
9668
|
+
const authDone = authP.then((a) => {
|
|
9669
|
+
auth = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
|
|
9670
|
+
progress();
|
|
9671
|
+
}, () => {
|
|
9672
|
+
unavailable.push("One-time codes");
|
|
9673
|
+
});
|
|
9674
|
+
const sharedDone = sharedP.then((sh) => {
|
|
9675
|
+
raw.shared = sh;
|
|
9676
|
+
progress();
|
|
9677
|
+
}, () => {
|
|
9678
|
+
raw.shared = { items: [], count: 0 };
|
|
9679
|
+
unavailable.push("Shared with me");
|
|
9680
|
+
});
|
|
9681
|
+
const [catalog, mine] = await Promise.all([catalogP, userP]);
|
|
9682
|
+
raw.catalog = catalog;
|
|
9683
|
+
raw.mine = mine;
|
|
9684
|
+
progress();
|
|
9685
|
+
const orgPs = await orgListsReady;
|
|
9686
|
+
const orgSettled = await Promise.allSettled(orgPs);
|
|
9687
|
+
orgSettled.forEach((r, i) => {
|
|
9688
|
+
if (r.status === "rejected") {
|
|
9689
|
+
raw.orgLists[i] = { items: [], count: 0 };
|
|
9690
|
+
unavailable.push(orgs[i]?.name ?? "An organization");
|
|
9567
9691
|
}
|
|
9568
9692
|
});
|
|
9569
|
-
|
|
9693
|
+
await Promise.all([authDone, sharedDone]);
|
|
9694
|
+
done = true;
|
|
9695
|
+
return build();
|
|
9570
9696
|
},
|
|
9571
9697
|
async reveal(item, fieldKey2) {
|
|
9572
9698
|
const field = item.fields.find((f) => f.key === fieldKey2);
|
|
@@ -9733,6 +9859,7 @@ export {
|
|
|
9733
9859
|
needsRecentSignIn,
|
|
9734
9860
|
parseDefinitions,
|
|
9735
9861
|
providerOf,
|
|
9862
|
+
rowMenuEntriesAt,
|
|
9736
9863
|
siteDomainOf,
|
|
9737
9864
|
sortItems,
|
|
9738
9865
|
useConfirmed,
|