ui: Replace Copy Key button with clickable key

- Remove separate "Copy Key" button
- Make public key prefix clickable to copy
- Add fallback for HTTP contexts (execCommand)
- Add hover and copied state styling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-01-23 20:16:12 +01:00
parent 51c05f7794
commit bbc8b1db25
2 changed files with 76 additions and 27 deletions

View File

@@ -1650,11 +1650,12 @@ function createExistingContactCard(contact, index) {
infoRow.appendChild(nameDiv);
infoRow.appendChild(typeBadge);
// Public key row
// Public key row (clickable to copy)
const keyDiv = document.createElement('div');
keyDiv.className = 'contact-key';
keyDiv.className = 'contact-key clickable-key';
keyDiv.textContent = contact.public_key_prefix;
keyDiv.title = 'Public Key Prefix';
keyDiv.title = 'Click to copy';
keyDiv.onclick = () => copyToClipboard(contact.public_key_prefix, keyDiv);
// Last advert row (with activity status indicator)
const lastAdvertDiv = document.createElement('div');
@@ -1700,14 +1701,6 @@ function createExistingContactCard(contact, index) {
const actionsDiv = document.createElement('div');
actionsDiv.className = 'd-flex gap-2 mt-2';
// Copy key button
const copyBtn = document.createElement('button');
copyBtn.className = 'btn btn-sm btn-outline-secondary';
copyBtn.innerHTML = '<i class="bi bi-clipboard"></i> Copy Key';
copyBtn.onclick = () => copyContactKey(contact.public_key_prefix, copyBtn);
actionsDiv.appendChild(copyBtn);
// Map button (only if GPS coordinates available)
if (contact.adv_lat && contact.adv_lon && (contact.adv_lat !== 0 || contact.adv_lon !== 0)) {
const mapBtn = document.createElement('button');
@@ -1748,25 +1741,63 @@ function createExistingContactCard(contact, index) {
return card;
}
function copyContactKey(publicKeyPrefix, buttonEl) {
navigator.clipboard.writeText(publicKeyPrefix).then(() => {
// Visual feedback
const originalHTML = buttonEl.innerHTML;
buttonEl.innerHTML = '<i class="bi bi-check"></i> Copied!';
buttonEl.classList.remove('btn-outline-secondary');
buttonEl.classList.add('btn-success');
/**
* Copy text to clipboard with fallback for HTTP contexts.
* @param {string} text - Text to copy
* @param {HTMLElement} element - Element for visual feedback
*/
function copyToClipboard(text, element) {
const originalText = element.textContent;
setTimeout(() => {
buttonEl.innerHTML = originalHTML;
buttonEl.classList.remove('btn-success');
buttonEl.classList.add('btn-outline-secondary');
}, 2000);
// Try modern clipboard API first (requires HTTPS)
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
showCopyFeedback(element, originalText);
}).catch(() => {
// Fallback to legacy method
legacyCopy(text, element, originalText);
});
} else {
// Fallback for HTTP contexts
legacyCopy(text, element, originalText);
}
}
showToast('Key copied to clipboard', 'info');
}).catch(err => {
/**
* Legacy copy method using execCommand (works on HTTP).
*/
function legacyCopy(text, element, originalText) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showCopyFeedback(element, originalText);
} catch (err) {
console.error('Failed to copy:', err);
showToast('Failed to copy to clipboard', 'danger');
});
showToast('Failed to copy', 'danger');
}
document.body.removeChild(textArea);
}
/**
* Show visual feedback after successful copy.
*/
function showCopyFeedback(element, originalText) {
element.textContent = 'Copied!';
element.classList.add('copied');
setTimeout(() => {
element.textContent = originalText;
element.classList.remove('copied');
}, 1500);
showToast('Key copied to clipboard', 'info');
}
function showDeleteModal(contact) {

View File

@@ -73,6 +73,24 @@
margin-bottom: 0.75rem;
}
.contact-key.clickable-key {
cursor: pointer;
transition: color 0.15s, background-color 0.15s;
padding: 0.15rem 0.3rem;
margin-left: -0.3rem;
border-radius: 0.25rem;
}
.contact-key.clickable-key:hover {
color: #0d6efd;
background-color: #e7f1ff;
}
.contact-key.clickable-key.copied {
color: #198754;
background-color: #d1e7dd;
}
.empty-state {
text-align: center;
padding: 1.5rem 1rem;