From 2c7f6188e364f4611107c29460ceb98015da1804 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:42:26 +0100 Subject: [PATCH] fix(clipboard): copy toasts reflect the configured clear delay The "clears in 30s" suffix was hardcoded in ~14 toasts. Single helper clipClearSuffix() reads clipboardClearSeconds (''=Never); prefixes concatenated to it. Cheatsheet text made generic "(auto-clears)". Co-Authored-By: Claude Opus 4.8 --- js/app.js | 25 ++++++++++++++++--------- js/app.overlays.js | 6 +++--- js/app.unlock.js | 4 ++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/js/app.js b/js/app.js index ff01c02..9539e39 100644 --- a/js/app.js +++ b/js/app.js @@ -1106,6 +1106,13 @@ function toast(msg, type) { function $(sel, root) { return (root || document).querySelector(sel); } function $$(sel, root) { return Array.from((root || document).querySelectorAll(sel)); } +// Toast suffix for a just-copied secret, honouring the auto-clear setting. +// '' when the user disabled auto-clear (Never). +function clipClearSuffix() { + const s = state.clipboardClearSeconds; + return s > 0 ? ' · clears in ' + s + 's' : ''; +} + function el(tag, props, ...kids) { const e = document.createElement(tag); if (props) for (const k in props) { @@ -4205,7 +4212,7 @@ function buildCustomFieldRow(field, idx, rerender) { if (!v) return; if (Bridge.active) Bridge.copySecure(v, 30000); else { try { navigator.clipboard.writeText(v); } catch (_) {} } - toast('Copied · clears in 30s'); + toast('Copied' + clipClearSuffix()); }); const delBtn = el('button', { class: 'icon-btn icon-btn-sm', type: 'button', @@ -4366,10 +4373,10 @@ function soPasswordField(plain) { copy.appendChild(icon('i-copy')); copy.addEventListener('click', () => { if (Bridge.copySecure(input.value, 30000)) { - toast('Copied · clears in 30s'); + toast('Copied' + clipClearSuffix()); } else { navigator.clipboard.writeText(input.value).then(() => { - toast('Copied · clears in 30s'); + toast('Copied' + clipClearSuffix()); setTimeout(() => navigator.clipboard.writeText('').catch(()=>{}), 30000); }); } @@ -4497,10 +4504,10 @@ function soTotpField(plainSecret) { try { const t = await generateTOTP(secret); if (Bridge.copySecure(t.code, 30000)) { - toast('Code copied · clears in 30s'); + toast('Code copied' + clipClearSuffix()); } else { navigator.clipboard.writeText(t.code).then(() => { - toast('Code copied · clears in 30s'); + toast('Code copied' + clipClearSuffix()); setTimeout(() => navigator.clipboard.writeText('').catch(()=>{}), 30000); }); } @@ -5418,9 +5425,9 @@ async function copyPassword(entry) { const p = await decryptPwd(entry.encrypted_password, entry.iv); if (p === '[ERROR]') return toast('Cannot decrypt', 'error'); if (Bridge.copySecure(p, 30000)) { - toast('Password copied · clears in 30s'); + toast('Password copied' + clipClearSuffix()); } else { - navigator.clipboard.writeText(p).then(() => toast('Password copied · clears in 30s')); + navigator.clipboard.writeText(p).then(() => toast('Password copied' + clipClearSuffix())); setTimeout(() => navigator.clipboard.writeText('').catch(()=>{}), 30000); } touchEntry(entry.id); @@ -7334,10 +7341,10 @@ async function init() { $('#genCopy').addEventListener('click', () => { if (!genCurrent) return; if (Bridge.copySecure(genCurrent, 30000)) { - toast('Copied · clears in 30s'); + toast('Copied' + clipClearSuffix()); } else { navigator.clipboard.writeText(genCurrent).then(() => { - toast('Copied · clears in 30s'); + toast('Copied' + clipClearSuffix()); setTimeout(() => navigator.clipboard.writeText('').catch(()=>{}), 30000); }); } diff --git a/js/app.overlays.js b/js/app.overlays.js index 32c313d..73eec8a 100644 --- a/js/app.overlays.js +++ b/js/app.overlays.js @@ -152,7 +152,7 @@ async function quickSearchPickEntry(entry, mode) { if (!u) { toast('No username on this entry', 'warning'); return; } if (Bridge.active) Bridge.copySecure(u, 30000); else { try { await navigator.clipboard.writeText(u); } catch (_) {} } - toast('Username copied · clears in 30s'); + toast('Username copied' + clipClearSuffix()); } else { const pwd = await decryptPwd(entry.encrypted_password, entry.iv); if (pwd === '[ERROR]') { toast('Decryption error', 'error'); return; } @@ -202,7 +202,7 @@ const CHEATSHEET_GROUPS = [ title: 'On each card', items: [ { keys: [{ icon: 'i-globe' }], desc: 'Open the site in your default browser' }, - { keys: [{ icon: 'i-copy' }], desc: 'Copy password to the secure clipboard (auto-clears in 30s)' }, + { keys: [{ icon: 'i-copy' }], desc: 'Copy password to the secure clipboard (auto-clears)' }, { keys: ['Click card'], desc: 'Open the entry details / edit panel' }, ], }, @@ -306,7 +306,7 @@ async function openHistoryModal(entryId) { copy.addEventListener('click', () => { if (Bridge.active) Bridge.copySecure(plain, 30000); else { try { navigator.clipboard.writeText(plain); } catch (_) {} } - toast('Copied · clears in 30s'); + toast('Copied' + clipClearSuffix()); }); preview.appendChild(copy); diff --git a/js/app.unlock.js b/js/app.unlock.js index 124bf92..2c44766 100644 --- a/js/app.unlock.js +++ b/js/app.unlock.js @@ -733,10 +733,10 @@ async function doGenerateRecoveryKey() { // running embedded, navigator.clipboard with manual scrub // otherwise. if (Bridge.copySecure(code, 30000)) { - toast('Recovery code copied · clears in 30s'); + toast('Recovery code copied' + clipClearSuffix()); } else { navigator.clipboard.writeText(code).then(() => { - toast('Recovery code copied · clears in 30s'); + toast('Recovery code copied' + clipClearSuffix()); setTimeout(() => navigator.clipboard.writeText('').catch(()=>{}), 30000); }); }