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:
@@ -2133,6 +2133,16 @@ input[type="range"]::-webkit-slider-thumb {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.qs-mode {
|
||||||
|
display: flex; gap: 2px; padding: 2px; flex-shrink: 0;
|
||||||
|
background: var(--bg-elev-2); border-radius: 999px;
|
||||||
|
}
|
||||||
|
.qs-mode button {
|
||||||
|
border: 0; background: none; cursor: pointer;
|
||||||
|
color: var(--text-dim); font-size: 12px;
|
||||||
|
padding: 4px 10px; border-radius: 999px;
|
||||||
|
}
|
||||||
|
.qs-mode button.is-on { background: var(--accent); color: #fff; }
|
||||||
.quick-search-acts {
|
.quick-search-acts {
|
||||||
display: flex; gap: 2px; flex-shrink: 0;
|
display: flex; gap: 2px; flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1231,6 +1231,12 @@
|
|||||||
<input id="quickSearchInput" type="text"
|
<input id="quickSearchInput" type="text"
|
||||||
placeholder="Search and press Enter to copy password…"
|
placeholder="Search and press Enter to copy password…"
|
||||||
autocomplete="off">
|
autocomplete="off">
|
||||||
|
<!-- Copier ou remplir : visible seulement quand une fenetre cible
|
||||||
|
existe - sans elle, remplir n'aurait rien a viser. -->
|
||||||
|
<div class="qs-mode" id="qsMode" hidden>
|
||||||
|
<button type="button" data-qsmode="copy">Copy</button>
|
||||||
|
<button type="button" data-qsmode="fill">Fill</button>
|
||||||
|
</div>
|
||||||
<kbd>Esc</kbd>
|
<kbd>Esc</kbd>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-search-results" id="quickSearchResults"></div>
|
<div class="quick-search-results" id="quickSearchResults"></div>
|
||||||
|
|||||||
+45
-9
@@ -27,6 +27,39 @@ let quickSearchHideAfter = false;
|
|||||||
// HWND and is waiting for cmd://autofill/execute. On pick we SendInput
|
// HWND and is waiting for cmd://autofill/execute. On pick we SendInput
|
||||||
// the password instead of copying to the clipboard.
|
// the password instead of copying to the clipboard.
|
||||||
let quickSearchFillMode = false;
|
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) {
|
function quickSearchScoreEntry(e, q) {
|
||||||
if (!q) return 1; // empty query → all entries pass, ordering preserved
|
if (!q) return 1; // empty query → all entries pass, ordering preserved
|
||||||
@@ -89,7 +122,7 @@ function quickSearchRender() {
|
|||||||
row.appendChild(avatar);
|
row.appendChild(avatar);
|
||||||
row.appendChild(main);
|
row.appendChild(main);
|
||||||
// Un bouton par valeur presente : pas besoin de retenir clic / Ctrl+clic.
|
// 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 acts = el('div', { class: 'quick-search-acts' });
|
||||||
const act = (ic, title, mode) => {
|
const act = (ic, title, mode) => {
|
||||||
const b = el('button', { class: 'icon-btn icon-btn-sm', type: 'button', title });
|
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; }
|
try { if (secret && secret !== '[ERROR]') code = (await generateTOTP(secret)).code; }
|
||||||
catch (_) { code = ''; }
|
catch (_) { code = ''; }
|
||||||
if (!code) { toast('2FA: cannot read the secret', 'error'); return; }
|
if (!code) { toast('2FA: cannot read the secret', 'error'); return; }
|
||||||
if (quickSearchFillMode) {
|
if (qsFills()) {
|
||||||
autofillPendingToast = entryDisplayName(entry) + ' · 2FA code sent';
|
autofillPendingToast = entryDisplayName(entry) + ' · 2FA code sent';
|
||||||
if (Bridge.active) Bridge.executeAutofill('', code, quickSearchHideAfter);
|
if (Bridge.active) Bridge.executeAutofill('', code, quickSearchHideAfter);
|
||||||
quickSearchFillMode = false;
|
quickSearchFillMode = false;
|
||||||
@@ -159,7 +192,7 @@ async function quickSearchPickEntry(entry, mode) {
|
|||||||
|
|
||||||
// Fill mode (Ctrl+Shift+Q hotkey): SendInput directly into the HWND
|
// Fill mode (Ctrl+Shift+Q hotkey): SendInput directly into the HWND
|
||||||
// Delphi saved when the hotkey fired. No clipboard touch.
|
// Delphi saved when the hotkey fired. No clipboard touch.
|
||||||
if (quickSearchFillMode) {
|
if (qsFills()) {
|
||||||
if (mode === 'user') {
|
if (mode === 'user') {
|
||||||
const u = entry.username || '';
|
const u = entry.username || '';
|
||||||
if (!u) { toast('No username on this entry', 'warning'); return; }
|
if (!u) { toast('No username on this entry', 'warning'); return; }
|
||||||
@@ -527,12 +560,15 @@ function openQuickSearchModal(hideAfter, forFill) {
|
|||||||
quickSearchSelected = 0;
|
quickSearchSelected = 0;
|
||||||
quickSearchHideAfter = !!hideAfter;
|
quickSearchHideAfter = !!hideAfter;
|
||||||
quickSearchFillMode = !!forFill;
|
quickSearchFillMode = !!forFill;
|
||||||
// Subtle hint to the user about what Enter will do.
|
qsApplyMode();
|
||||||
const hintEl = modal.querySelector('.quick-search-hint');
|
// La preference arrive de Delphi : on affiche tout de suite, et on
|
||||||
if (hintEl) {
|
// rectifie des qu'elle est la (une fraction de seconde).
|
||||||
hintEl.textContent = forFill
|
if (forFill && Bridge.active) {
|
||||||
? 'Enter / click = password · Shift+Enter / right-click = user+password · Ctrl+Enter / Ctrl+click = username · Esc = cancel'
|
Bridge.getPref('qsCopy').then(v => {
|
||||||
: 'Enter / click = copy password · Ctrl+Enter / Ctrl+click = copy username · Esc = close';
|
quickSearchPreferCopy = v === '1';
|
||||||
|
qsApplyMode();
|
||||||
|
quickSearchRender();
|
||||||
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
quickSearchRender();
|
quickSearchRender();
|
||||||
setTimeout(() => input.focus(), 50);
|
setTimeout(() => input.focus(), 50);
|
||||||
|
|||||||
Reference in New Issue
Block a user