04/26 Enhanced extension functions
This commit is contained in:
@@ -480,6 +480,7 @@ function renderList() {
|
||||
|
||||
// SVG icons for action buttons
|
||||
const svgCopy = `<svg viewBox="0 0 24 24" fill="none"><rect x="9" y="9" width="11" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" stroke="currentColor" stroke-width="1.7"/></svg>`;
|
||||
const svgUser = `<svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.7"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>`;
|
||||
const svgDots = `<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>`;
|
||||
const svgFill = `<svg viewBox="0 0 24 24" fill="none"><path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M17 3l4 4-9 9H8v-4l9-9z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>`;
|
||||
const svgTotp = `<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><rect x="5" y="2" width="14" height="20" rx="2" stroke="currentColor" stroke-width="1.7"/><circle cx="12" cy="17" r="1" fill="currentColor"/><path d="M9 7h6M9 11h4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>`;
|
||||
@@ -503,10 +504,11 @@ function renderList() {
|
||||
${hasTotp ? `<div class="item-totp-row"><span class="totp-code-inline" id="totp-${item.id}">······</span><span class="totp-timer-inline" id="totp-t-${item.id}"></span></div>` : ''}
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
${item.plain?.username ? `<button class="btn-item-action" data-copy-user="${item.id}" title="Copy username">${svgUser}</button>` : ''}
|
||||
${canCopy ? `<button class="btn-item-action" data-copy-pass="${item.id}" title="Copy password">${svgCopy}</button>` : ''}
|
||||
${hasTotp ? `<button class="btn-item-action totp-btn" data-copy-totp="${item.id}" title="Copy 2FA code">${svgTotp}</button>` : ''}
|
||||
${canFill ? `<button class="btn-item-action" data-autofill="${item.id}" title="Autofill">${svgFill}</button>` : ''}
|
||||
<button class="btn-item-action" data-menu="${item.id}" title="More">${svgDots}</button>
|
||||
<button class="btn-item-action" data-menu="${item.id}" title="More options">${svgDots}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
@@ -578,17 +580,69 @@ function renderList() {
|
||||
})
|
||||
);
|
||||
|
||||
// Three-dot menu: copy username
|
||||
// Copy username (dedicated button)
|
||||
listEl.querySelectorAll('[data-copy-user]').forEach(btn =>
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const item = _items.find(i => i.id === parseInt(btn.dataset.copyUser));
|
||||
if (item?.plain?.username) {
|
||||
navigator.clipboard.writeText(item.plain.username);
|
||||
btn.title = 'Copied!';
|
||||
setTimeout(() => { btn.title = 'Copy username'; }, 1500);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Three-dot menu: proper flyout with contextual actions
|
||||
listEl.querySelectorAll('[data-menu]').forEach(btn =>
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
// Close any already-open flyout first.
|
||||
document.querySelectorAll('.pk-flyout').forEach(el => el.remove());
|
||||
|
||||
const item = _items.find(i => i.id === parseInt(btn.dataset.menu));
|
||||
if (!item?.plain) return;
|
||||
if (item.plain.username) {
|
||||
navigator.clipboard.writeText(item.plain.username);
|
||||
btn.title = 'Username copied!';
|
||||
setTimeout(() => { btn.title = 'More'; }, 1500);
|
||||
}
|
||||
if (!item) return;
|
||||
|
||||
const flyout = document.createElement('div');
|
||||
flyout.className = 'pk-flyout';
|
||||
|
||||
const menuItems = [
|
||||
item.plain?.url ? {
|
||||
label: 'Open URL',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M15 3h6v6M10 14L21 3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
action: () => { chrome.tabs.create({ url: item.plain.url }); flyout.remove(); },
|
||||
} : null,
|
||||
item.plain?.username ? {
|
||||
label: 'Copy username',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.7"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
|
||||
action: () => { navigator.clipboard.writeText(item.plain.username); flyout.remove(); },
|
||||
} : null,
|
||||
item.plain?.password ? {
|
||||
label: 'Copy password',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><rect x="3" y="10" width="18" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 10V7a4 4 0 018 0v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
|
||||
action: () => { navigator.clipboard.writeText(item.plain.password); flyout.remove(); },
|
||||
} : null,
|
||||
].filter(Boolean);
|
||||
|
||||
menuItems.forEach(mi => {
|
||||
const row = document.createElement('button');
|
||||
row.className = 'pk-flyout-item';
|
||||
row.innerHTML = mi.icon + '<span>' + escHtml(mi.label) + '</span>';
|
||||
row.addEventListener('click', e => { e.stopPropagation(); mi.action(); });
|
||||
flyout.appendChild(row);
|
||||
});
|
||||
|
||||
// Position flyout below the button, right-aligned.
|
||||
btn.style.position = 'relative';
|
||||
btn.appendChild(flyout);
|
||||
|
||||
// Close on any outside click.
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', function handler() {
|
||||
flyout.remove();
|
||||
document.removeEventListener('click', handler);
|
||||
});
|
||||
}, 0);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user