feat(quick-search): interrupteur Copy / Fill quand une fenetre cible existe
Ouverte par Ctrl+Shift+Q ou la barrette, la recherche remplissait toujours ; copier restait impossible alors que le remplissage echoue vers une application lancee en administrateur, ou quand le gestionnaire de Chrome s'en mele. Sans cible (tray, palette), l'interrupteur n'apparait pas : remplir n'aurait rien a viser. Le choix est retenu en pref DPAPI, les verbes des boutons et l'aide suivent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+45
-9
@@ -27,6 +27,39 @@ let quickSearchHideAfter = false;
|
||||
// HWND and is waiting for cmd://autofill/execute. On pick we SendInput
|
||||
// the password instead of copying to the clipboard.
|
||||
let quickSearchFillMode = false;
|
||||
// CE QUE L'UTILISATEUR PREFERE quand une cible existe : remplir, ou copier
|
||||
// quand meme - le remplissage echoue vers une application lancee en
|
||||
// administrateur, ou quand le gestionnaire de Chrome s'en mele. Retenu en pref
|
||||
// DPAPI : localStorage se vide a chaque lancement (port ephemere).
|
||||
let quickSearchPreferCopy = false;
|
||||
|
||||
/// Remplit-on vraiment ? Il faut une cible ET ne pas avoir choisi Copy.
|
||||
function qsFills() { return quickSearchFillMode && !quickSearchPreferCopy; }
|
||||
|
||||
function qsApplyMode() {
|
||||
const box = document.getElementById('qsMode');
|
||||
if (box) {
|
||||
box.hidden = !quickSearchFillMode;
|
||||
box.querySelectorAll('[data-qsmode]').forEach(b =>
|
||||
b.classList.toggle('is-on', (b.dataset.qsmode === 'fill') === qsFills()));
|
||||
}
|
||||
const hintEl = document.querySelector('#quickSearchModal .quick-search-hint');
|
||||
if (hintEl) {
|
||||
hintEl.textContent = qsFills()
|
||||
? 'Enter / click = password · Shift+Enter / right-click = user+password · Ctrl+Enter / Ctrl+click = username · Esc = cancel'
|
||||
: 'Enter / click = copy password · Ctrl+Enter / Ctrl+click = copy username · Esc = close';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', ev => {
|
||||
const b = ev.target.closest && ev.target.closest('[data-qsmode]');
|
||||
if (!b) return;
|
||||
quickSearchPreferCopy = b.dataset.qsmode === 'copy';
|
||||
if (Bridge.active) Bridge.setPref('qsCopy', quickSearchPreferCopy ? '1' : '0');
|
||||
qsApplyMode();
|
||||
quickSearchRender();
|
||||
document.getElementById('quickSearchInput').focus();
|
||||
});
|
||||
|
||||
function quickSearchScoreEntry(e, q) {
|
||||
if (!q) return 1; // empty query → all entries pass, ordering preserved
|
||||
@@ -89,7 +122,7 @@ function quickSearchRender() {
|
||||
row.appendChild(avatar);
|
||||
row.appendChild(main);
|
||||
// Un bouton par valeur presente : pas besoin de retenir clic / Ctrl+clic.
|
||||
const verb = quickSearchFillMode ? 'Fill ' : 'Copy ';
|
||||
const verb = qsFills() ? '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 });
|
||||
@@ -133,7 +166,7 @@ async function quickSearchPickEntry(entry, mode) {
|
||||
try { if (secret && secret !== '[ERROR]') code = (await generateTOTP(secret)).code; }
|
||||
catch (_) { code = ''; }
|
||||
if (!code) { toast('2FA: cannot read the secret', 'error'); return; }
|
||||
if (quickSearchFillMode) {
|
||||
if (qsFills()) {
|
||||
autofillPendingToast = entryDisplayName(entry) + ' · 2FA code sent';
|
||||
if (Bridge.active) Bridge.executeAutofill('', code, quickSearchHideAfter);
|
||||
quickSearchFillMode = false;
|
||||
@@ -159,7 +192,7 @@ async function quickSearchPickEntry(entry, mode) {
|
||||
|
||||
// Fill mode (Ctrl+Shift+Q hotkey): SendInput directly into the HWND
|
||||
// Delphi saved when the hotkey fired. No clipboard touch.
|
||||
if (quickSearchFillMode) {
|
||||
if (qsFills()) {
|
||||
if (mode === 'user') {
|
||||
const u = entry.username || '';
|
||||
if (!u) { toast('No username on this entry', 'warning'); return; }
|
||||
@@ -527,12 +560,15 @@ function openQuickSearchModal(hideAfter, forFill) {
|
||||
quickSearchSelected = 0;
|
||||
quickSearchHideAfter = !!hideAfter;
|
||||
quickSearchFillMode = !!forFill;
|
||||
// Subtle hint to the user about what Enter will do.
|
||||
const hintEl = modal.querySelector('.quick-search-hint');
|
||||
if (hintEl) {
|
||||
hintEl.textContent = forFill
|
||||
? 'Enter / click = password · Shift+Enter / right-click = user+password · Ctrl+Enter / Ctrl+click = username · Esc = cancel'
|
||||
: 'Enter / click = copy password · Ctrl+Enter / Ctrl+click = copy username · Esc = close';
|
||||
qsApplyMode();
|
||||
// La preference arrive de Delphi : on affiche tout de suite, et on
|
||||
// rectifie des qu'elle est la (une fraction de seconde).
|
||||
if (forFill && Bridge.active) {
|
||||
Bridge.getPref('qsCopy').then(v => {
|
||||
quickSearchPreferCopy = v === '1';
|
||||
qsApplyMode();
|
||||
quickSearchRender();
|
||||
}).catch(() => {});
|
||||
}
|
||||
quickSearchRender();
|
||||
setTimeout(() => input.focus(), 50);
|
||||
|
||||
Reference in New Issue
Block a user