feat(quick-search): boutons par entree - utilisateur, mot de passe ou note, code 2FA (s'ils existent)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-18 23:15:49 +01:00
parent cfa2a798e5
commit 652815ea98
3 changed files with 53 additions and 0 deletions
+48
View File
@@ -88,6 +88,21 @@ function quickSearchRender() {
main.appendChild(el('div', { class: 'quick-search-sub' }, e.username));
row.appendChild(avatar);
row.appendChild(main);
// Un bouton par valeur presente : pas besoin de retenir clic / Ctrl+clic.
const verb = quickSearchFillMode ? 'Fill ' : 'Copy ';
const acts = el('div', { class: 'quick-search-acts' });
const act = (ic, title, mode) => {
const b = el('button', { class: 'icon-btn icon-btn-sm', type: 'button', title });
b.appendChild(icon(ic));
b.addEventListener('click', ev => { ev.stopPropagation(); quickSearchPickEntry(e, mode); });
b.addEventListener('contextmenu', ev => ev.stopPropagation());
acts.appendChild(b);
};
if (e.username) act('i-user', verb + 'username', 'user');
if (e.kind === 'note') act('i-note', 'Copy note', 'note');
else act('i-key', verb + 'password', 'pwd');
if (e.totp_secret && e.totp_iv) act('i-clock', verb + '2FA code', 'totp');
row.appendChild(acts);
// Left click → password only; Ctrl+click → username only
// (step-2 forms / unlock screens).
row.addEventListener('click', ev =>
@@ -106,9 +121,42 @@ function quickSearchRender() {
// mode: 'full' (user + Tab + password), 'user' (username only) or 'pwd'
// (password only). In fill mode each maps to a SendInput variant; in copy
// mode 'full' has no meaning so it falls back to copying the password.
// 'totp' = code 2FA courant (tape comme un mot de passe, ou copie) ; 'note' =
// corps d'une note, toujours COPIE - le taper enverrait ses retours a la ligne
// comme des Entree dans le formulaire cible.
async function quickSearchPickEntry(entry, mode) {
mode = mode || 'full';
if (mode === 'totp') {
const secret = await decryptTotpSecret(entry.totp_secret, entry.totp_iv);
let code = '';
try { if (secret && secret !== '[ERROR]') code = (await generateTOTP(secret)).code; }
catch (_) { code = ''; }
if (!code) { toast('2FA: cannot read the secret', 'error'); return; }
if (quickSearchFillMode) {
autofillPendingToast = entryDisplayName(entry) + ' · 2FA code sent';
if (Bridge.active) Bridge.executeAutofill('', code, quickSearchHideAfter);
quickSearchFillMode = false;
quickSearchHideAfter = false;
closeQuickSearchModal();
return;
}
if (Bridge.active) Bridge.copySecure(code, 30000);
else { try { await navigator.clipboard.writeText(code); } catch (_) {} }
toast(entryDisplayName(entry) + ' · 2FA code copied');
closeQuickSearchModal(true);
return;
}
if (mode === 'note') {
const body = await decryptPwd(entry.encrypted_password, entry.iv);
if (body === '[ERROR]') { toast('Decryption error', 'error'); return; }
if (Bridge.active) Bridge.copySecure(body, 30000);
else { try { await navigator.clipboard.writeText(body); } catch (_) {} }
toast(entryDisplayName(entry) + ' · note copied');
closeQuickSearchModal(true);
return;
}
// Fill mode (Ctrl+Shift+Q hotkey): SendInput directly into the HWND
// Delphi saved when the hotkey fired. No clipboard touch.
if (quickSearchFillMode) {