Add copy ticket button to Job Details and improve cross-browser copy functionality
Changes:
- Added copy ticket button (⧉) next to ticket numbers in Job Details modal
- Implemented robust cross-browser clipboard copy mechanism:
1. Modern navigator.clipboard API (works in HTTPS contexts)
2. Legacy document.execCommand('copy') fallback (works in older browsers)
3. Prompt fallback as last resort
- Applied improved copy function to both Run Checks and Job Details pages
- Copy now works directly in all browsers (Firefox, Edge, Chrome) without popup
This eliminates the manual copy step in Edge that previously required a popup.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c777728c91
commit
35ec337c54
@ -284,6 +284,60 @@
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
// Cross-browser copy to clipboard function
|
||||
function copyToClipboard(text, button) {
|
||||
// Method 1: Modern Clipboard API (works in most browsers with HTTPS)
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text)
|
||||
.then(function () {
|
||||
showCopyFeedback(button);
|
||||
})
|
||||
.catch(function () {
|
||||
// Fallback to method 2 if clipboard API fails
|
||||
fallbackCopy(text, button);
|
||||
});
|
||||
} else {
|
||||
// Method 2: Legacy execCommand method
|
||||
fallbackCopy(text, button);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy(text, button) {
|
||||
var textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
textarea.style.top = '0';
|
||||
textarea.style.left = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
var successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
showCopyFeedback(button);
|
||||
} else {
|
||||
// If execCommand fails, use prompt as last resort
|
||||
window.prompt('Copy ticket number:', text);
|
||||
}
|
||||
} catch (err) {
|
||||
// If all else fails, show prompt
|
||||
window.prompt('Copy ticket number:', text);
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
function showCopyFeedback(button) {
|
||||
if (!button) return;
|
||||
var original = button.textContent;
|
||||
button.textContent = '✓';
|
||||
setTimeout(function () {
|
||||
button.textContent = original;
|
||||
}, 800);
|
||||
}
|
||||
|
||||
(function () {
|
||||
var currentRunId = null;
|
||||
|
||||
@ -319,12 +373,14 @@
|
||||
html += '<div class="mb-2"><strong>Tickets</strong><div class="mt-1">';
|
||||
tickets.forEach(function (t) {
|
||||
var status = t.resolved_at ? 'Resolved' : 'Active';
|
||||
var ticketCode = (t.ticket_code || '').toString();
|
||||
html += '<div class="mb-2 border rounded p-2" data-alert-type="ticket" data-id="' + t.id + '">' +
|
||||
'<div class="d-flex align-items-start justify-content-between gap-2">' +
|
||||
'<div class="flex-grow-1 min-w-0">' +
|
||||
'<div class="text-truncate">' +
|
||||
'<span class="me-1" title="Ticket">🎫</span>' +
|
||||
'<span class="fw-semibold">' + escapeHtml(t.ticket_code || '') + '</span>' +
|
||||
'<span class="fw-semibold">' + escapeHtml(ticketCode) + '</span>' +
|
||||
'<button type="button" class="btn btn-sm btn-outline-secondary ms-2 py-0 px-1" title="Copy ticket number" data-action="copy-ticket" data-code="' + escapeHtml(ticketCode) + '">⧉</button>' +
|
||||
'<span class="ms-2 badge ' + (t.resolved_at ? 'bg-secondary' : 'bg-warning text-dark') + '">' + status + '</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
@ -371,7 +427,16 @@
|
||||
ev.preventDefault();
|
||||
var action = btn.getAttribute('data-action');
|
||||
var id = btn.getAttribute('data-id');
|
||||
if (!action || !id) return;
|
||||
if (!action) return;
|
||||
|
||||
if (action === 'copy-ticket') {
|
||||
var code = btn.getAttribute('data-code') || '';
|
||||
if (!code) return;
|
||||
copyToClipboard(code, btn);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!id) return;
|
||||
if (action === 'resolve-ticket') {
|
||||
if (!confirm('Mark ticket as resolved?')) return;
|
||||
apiJson('/api/tickets/' + encodeURIComponent(id) + '/resolve', {method: 'POST', body: '{}'})
|
||||
|
||||
@ -447,6 +447,60 @@ function escapeHtml(s) {
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
// Cross-browser copy to clipboard function
|
||||
function copyToClipboard(text, button) {
|
||||
// Method 1: Modern Clipboard API (works in most browsers with HTTPS)
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text)
|
||||
.then(function () {
|
||||
showCopyFeedback(button);
|
||||
})
|
||||
.catch(function () {
|
||||
// Fallback to method 2 if clipboard API fails
|
||||
fallbackCopy(text, button);
|
||||
});
|
||||
} else {
|
||||
// Method 2: Legacy execCommand method
|
||||
fallbackCopy(text, button);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy(text, button) {
|
||||
var textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
textarea.style.top = '0';
|
||||
textarea.style.left = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
var successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
showCopyFeedback(button);
|
||||
} else {
|
||||
// If execCommand fails, use prompt as last resort
|
||||
window.prompt('Copy ticket number:', text);
|
||||
}
|
||||
} catch (err) {
|
||||
// If all else fails, show prompt
|
||||
window.prompt('Copy ticket number:', text);
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
function showCopyFeedback(button) {
|
||||
if (!button) return;
|
||||
var original = button.textContent;
|
||||
button.textContent = '✓';
|
||||
setTimeout(function () {
|
||||
button.textContent = original;
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function getSelectedJobIds() {
|
||||
var cbs = table.querySelectorAll('tbody .rc_row_cb');
|
||||
var ids = [];
|
||||
@ -840,20 +894,7 @@ table.addEventListener('change', function (e) {
|
||||
if (action === 'copy-ticket') {
|
||||
var code = btn.getAttribute('data-code') || '';
|
||||
if (!code) return;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(code)
|
||||
.then(function () {
|
||||
var original = btn.textContent;
|
||||
btn.textContent = '✓';
|
||||
setTimeout(function () { btn.textContent = original; }, 800);
|
||||
})
|
||||
.catch(function () {
|
||||
// Fallback: select/copy via prompt
|
||||
window.prompt('Copy ticket number:', code);
|
||||
});
|
||||
} else {
|
||||
window.prompt('Copy ticket number:', code);
|
||||
}
|
||||
copyToClipboard(code, btn);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user