05/19 Enhance codes 3
This commit is contained in:
+52
-42
@@ -480,8 +480,8 @@ const Vault = (() => {
|
|||||||
_sortOrder === "folder"
|
_sortOrder === "folder"
|
||||||
? Object.keys(groups).sort()
|
? Object.keys(groups).sort()
|
||||||
: ["(No folder)", ..._folders.map((f) => f.name)].filter(
|
: ["(No folder)", ..._folders.map((f) => f.name)].filter(
|
||||||
(k) => groups[k],
|
(k) => groups[k],
|
||||||
);
|
);
|
||||||
Object.keys(groups).forEach((k) => {
|
Object.keys(groups).forEach((k) => {
|
||||||
if (!keys.includes(k)) keys.push(k);
|
if (!keys.includes(k)) keys.push(k);
|
||||||
});
|
});
|
||||||
@@ -549,7 +549,7 @@ const Vault = (() => {
|
|||||||
case "card":
|
case "card":
|
||||||
subText = item.plain.card_number
|
subText = item.plain.card_number
|
||||||
? "•••• " +
|
? "•••• " +
|
||||||
String(item.plain.card_number).replace(/\s/g, "").slice(-4)
|
String(item.plain.card_number).replace(/\s/g, "").slice(-4)
|
||||||
: "";
|
: "";
|
||||||
break;
|
break;
|
||||||
case "bank":
|
case "bank":
|
||||||
@@ -634,6 +634,8 @@ const Vault = (() => {
|
|||||||
// Wire the bulk-select checkbox.
|
// Wire the bulk-select checkbox.
|
||||||
const checkbox = li.querySelector(".item-checkbox");
|
const checkbox = li.querySelector(".item-checkbox");
|
||||||
if (checkbox) {
|
if (checkbox) {
|
||||||
|
// Stop the click from bubbling to the li (which would open the modal).
|
||||||
|
checkbox.addEventListener("click", (e) => e.stopPropagation());
|
||||||
checkbox.addEventListener("change", () => {
|
checkbox.addEventListener("change", () => {
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
_selectedIds.add(item.id);
|
_selectedIds.add(item.id);
|
||||||
@@ -714,7 +716,18 @@ const Vault = (() => {
|
|||||||
confirmDeleteItem(item);
|
confirmDeleteItem(item);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
li.addEventListener("click", () => openModal("edit", item));
|
li.addEventListener("click", (e) => {
|
||||||
|
if (_selectMode) {
|
||||||
|
// Toggle the checkbox when the row itself is clicked (not the checkbox).
|
||||||
|
const cb = li.querySelector(".item-checkbox");
|
||||||
|
if (cb && e.target !== cb) {
|
||||||
|
cb.checked = !cb.checked;
|
||||||
|
cb.dispatchEvent(new Event("change"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openModal("edit", item);
|
||||||
|
});
|
||||||
return li;
|
return li;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1112,9 +1125,9 @@ const Vault = (() => {
|
|||||||
0,
|
0,
|
||||||
Math.round(
|
Math.round(
|
||||||
100 -
|
100 -
|
||||||
(weak.length / total) * 40 -
|
(weak.length / total) * 40 -
|
||||||
(reused.length / total) * 30 -
|
(reused.length / total) * 30 -
|
||||||
(old.length / total) * 15,
|
(old.length / total) * 15,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const cls =
|
const cls =
|
||||||
@@ -1264,16 +1277,16 @@ const Vault = (() => {
|
|||||||
</div>
|
</div>
|
||||||
<ul class="sec-item-list">
|
<ul class="sec-item-list">
|
||||||
${breached
|
${breached
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
const count =
|
const count =
|
||||||
hibpResults.find((r) => r.item.id === i.id)?.count || 0;
|
hibpResults.find((r) => r.item.id === i.id)?.count || 0;
|
||||||
return `<li class="sec-item">
|
return `<li class="sec-item">
|
||||||
<span class="sec-item-name">${escHtml(i.name)}</span>
|
<span class="sec-item-name">${escHtml(i.name)}</span>
|
||||||
<span class="sec-item-sub">${escHtml(i.plain?.username || "")} — seen ${count.toLocaleString()} time${count !== 1 ? "s" : ""} in breaches</span>
|
<span class="sec-item-sub">${escHtml(i.plain?.username || "")} — seen ${count.toLocaleString()} time${count !== 1 ? "s" : ""} in breaches</span>
|
||||||
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Change</button>
|
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Change</button>
|
||||||
</li>`;
|
</li>`;
|
||||||
})
|
})
|
||||||
.join("")}
|
.join("")}
|
||||||
</ul>`;
|
</ul>`;
|
||||||
hibpSection.querySelectorAll("[data-sec-edit]").forEach((btn) => {
|
hibpSection.querySelectorAll("[data-sec-edit]").forEach((btn) => {
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => {
|
||||||
@@ -1835,10 +1848,9 @@ const Vault = (() => {
|
|||||||
<span class="share-name">${escHtml(s.item_name)}</span>
|
<span class="share-name">${escHtml(s.item_name)}</span>
|
||||||
<span class="share-meta">From ${escHtml(s.owner_email)}${expiryLabel}</span>
|
<span class="share-meta">From ${escHtml(s.owner_email)}${expiryLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
${
|
${!s.accepted
|
||||||
!s.accepted
|
? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>`
|
||||||
? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>`
|
: `<button class="btn-secondary btn-sm"
|
||||||
: `<button class="btn-secondary btn-sm"
|
|
||||||
data-view-share="${s.id}"
|
data-view-share="${s.id}"
|
||||||
data-owner-key="${escHtml(s.owner_public_key || "")}"
|
data-owner-key="${escHtml(s.owner_public_key || "")}"
|
||||||
data-enc="${escHtml(s.enc_data)}"
|
data-enc="${escHtml(s.enc_data)}"
|
||||||
@@ -1847,7 +1859,7 @@ const Vault = (() => {
|
|||||||
data-iv-name="${escHtml(s.iv_name || "")}"
|
data-iv-name="${escHtml(s.iv_name || "")}"
|
||||||
data-name="${escHtml(s.item_name)}"
|
data-name="${escHtml(s.item_name)}"
|
||||||
data-type="${escHtml(s.item_type || "")}">View</button>`
|
data-type="${escHtml(s.item_type || "")}">View</button>`
|
||||||
}
|
}
|
||||||
</li>`;
|
</li>`;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -2389,10 +2401,9 @@ const Vault = (() => {
|
|||||||
<span class="em-vault-chevron">▸</span>
|
<span class="em-vault-chevron">▸</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="em-vault-item-body hidden" id="em-vault-body-${idx}">
|
<div class="em-vault-item-body hidden" id="em-vault-body-${idx}">
|
||||||
${
|
${hasFields
|
||||||
hasFields
|
? '<div class="detail-body em-detail-body"></div>'
|
||||||
? '<div class="detail-body em-detail-body"></div>'
|
: '<p class="vault-empty">Could not decrypt this item.</p>'
|
||||||
: '<p class="vault-empty">Could not decrypt this item.</p>'
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</li>`;
|
</li>`;
|
||||||
@@ -2721,8 +2732,8 @@ const Vault = (() => {
|
|||||||
const typeLabel = transports.includes("internal")
|
const typeLabel = transports.includes("internal")
|
||||||
? "📱 Device"
|
? "📱 Device"
|
||||||
: transports.some((t) => ["usb", "nfc", "ble", "smart-card"].includes(t))
|
: transports.some((t) => ["usb", "nfc", "ble", "smart-card"].includes(t))
|
||||||
? "🔑 Security key"
|
? "🔑 Security key"
|
||||||
: "🔑 Passkey";
|
: "🔑 Passkey";
|
||||||
return `<div class="passkey-item" data-cred-id="${c.id}">
|
return `<div class="passkey-item" data-cred-id="${c.id}">
|
||||||
<div class="passkey-info">
|
<div class="passkey-info">
|
||||||
<span class="passkey-name">${escHtml(c.name)}</span>
|
<span class="passkey-name">${escHtml(c.name)}</span>
|
||||||
@@ -2985,16 +2996,15 @@ const Vault = (() => {
|
|||||||
<h3 style="margin:0 0 8px;font-size:16px;color:#111827;">
|
<h3 style="margin:0 0 8px;font-size:16px;color:#111827;">
|
||||||
${isFirstTime ? "🔐 MFA enabled — save your backup codes" : "🔐 New backup codes"}
|
${isFirstTime ? "🔐 MFA enabled — save your backup codes" : "🔐 New backup codes"}
|
||||||
</h3>
|
</h3>
|
||||||
${
|
${isFirstTime
|
||||||
isFirstTime
|
? `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
||||||
? `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
|
||||||
These codes let you sign in if you lose access to your authenticator app.
|
These codes let you sign in if you lose access to your authenticator app.
|
||||||
<strong>Save them now — they will not be shown again.</strong>
|
<strong>Save them now — they will not be shown again.</strong>
|
||||||
</p>`
|
</p>`
|
||||||
: `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
: `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
|
||||||
Your previous codes have been invalidated. Save these new codes securely.
|
Your previous codes have been invalidated. Save these new codes securely.
|
||||||
</p>`
|
</p>`
|
||||||
}
|
}
|
||||||
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:12px;display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px;">
|
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:12px;display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px;">
|
||||||
${codesHtml}
|
${codesHtml}
|
||||||
</div>
|
</div>
|
||||||
@@ -3503,14 +3513,14 @@ const Vault = (() => {
|
|||||||
const pool = !_activeFilter
|
const pool = !_activeFilter
|
||||||
? _items
|
? _items
|
||||||
: _activeFilter.type === "itemType"
|
: _activeFilter.type === "itemType"
|
||||||
? _items.filter((i) => i.item_type === _activeFilter.value)
|
? _items.filter((i) => i.item_type === _activeFilter.value)
|
||||||
: _activeFilter.type === "folder"
|
: _activeFilter.type === "folder"
|
||||||
? _items.filter((i) => i.folder_id === _activeFilter.value)
|
? _items.filter((i) => i.folder_id === _activeFilter.value)
|
||||||
: _activeFilter.type === "tag"
|
: _activeFilter.type === "tag"
|
||||||
? _items.filter((i) =>
|
? _items.filter((i) =>
|
||||||
(i.plain?.tags || []).includes(_activeFilter.value),
|
(i.plain?.tags || []).includes(_activeFilter.value),
|
||||||
)
|
)
|
||||||
: _items;
|
: _items;
|
||||||
renderItemList(
|
renderItemList(
|
||||||
pool.filter((item) => {
|
pool.filter((item) => {
|
||||||
const p = item.plain || {};
|
const p = item.plain || {};
|
||||||
@@ -3719,7 +3729,7 @@ const Vault = (() => {
|
|||||||
if (
|
if (
|
||||||
(mode === "add" &&
|
(mode === "add" &&
|
||||||
(document.getElementById("field-type").value || "password") ===
|
(document.getElementById("field-type").value || "password") ===
|
||||||
"password") ||
|
"password") ||
|
||||||
(mode === "edit" && (item?.item_type || "password") === "password")
|
(mode === "edit" && (item?.item_type || "password") === "password")
|
||||||
) {
|
) {
|
||||||
initPasswordFieldEnhancements();
|
initPasswordFieldEnhancements();
|
||||||
@@ -3984,7 +3994,7 @@ const Vault = (() => {
|
|||||||
"X-CSRFToken": csrfToken(),
|
"X-CSRFToken": csrfToken(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||||
}).catch(() => {});
|
}).catch(() => { });
|
||||||
VaultSession.clear();
|
VaultSession.clear();
|
||||||
SharingSession.clear();
|
SharingSession.clear();
|
||||||
sessionStorage.removeItem("access_token");
|
sessionStorage.removeItem("access_token");
|
||||||
@@ -4089,11 +4099,11 @@ const Vault = (() => {
|
|||||||
// Auto-clear clipboard after 30 seconds — industry-standard hygiene.
|
// Auto-clear clipboard after 30 seconds — industry-standard hygiene.
|
||||||
if (_clipboardClearTimer) clearTimeout(_clipboardClearTimer);
|
if (_clipboardClearTimer) clearTimeout(_clipboardClearTimer);
|
||||||
_clipboardClearTimer = setTimeout(() => {
|
_clipboardClearTimer = setTimeout(() => {
|
||||||
navigator.clipboard.writeText("").catch(() => {});
|
navigator.clipboard.writeText("").catch(() => { });
|
||||||
_clipboardClearTimer = null;
|
_clipboardClearTimer = null;
|
||||||
}, 30_000);
|
}, 30_000);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
function escHtml(str) {
|
function escHtml(str) {
|
||||||
@@ -4713,4 +4723,4 @@ const Vault = (() => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", Vault.init);
|
document.addEventListener("DOMContentLoaded", Vault.init);
|
||||||
Reference in New Issue
Block a user