mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-20 01:04:15 +00:00
119 lines
3.4 KiB
JavaScript
119 lines
3.4 KiB
JavaScript
// Copy to clipboard
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
// Show success feedback
|
|
const btn = event.target;
|
|
const originalText = btn.textContent;
|
|
btn.textContent = '✓ Copied!';
|
|
setTimeout(() => {
|
|
btn.textContent = originalText;
|
|
}, 2000);
|
|
}).catch(err => {
|
|
console.error('Failed to copy:', err);
|
|
});
|
|
}
|
|
|
|
// Time ago helper (for client-side rendering)
|
|
function timeAgo(date) {
|
|
const seconds = Math.floor((new Date() - new Date(date)) / 1000);
|
|
|
|
const intervals = {
|
|
year: 31536000,
|
|
month: 2592000,
|
|
week: 604800,
|
|
day: 86400,
|
|
hour: 3600,
|
|
minute: 60,
|
|
second: 1
|
|
};
|
|
|
|
for (const [name, secondsInInterval] of Object.entries(intervals)) {
|
|
const interval = Math.floor(seconds / secondsInInterval);
|
|
if (interval >= 1) {
|
|
return interval === 1 ? `1 ${name} ago` : `${interval} ${name}s ago`;
|
|
}
|
|
}
|
|
|
|
return 'just now';
|
|
}
|
|
|
|
// Update timestamps on page load and HTMX swaps
|
|
function updateTimestamps() {
|
|
document.querySelectorAll('time[datetime]').forEach(el => {
|
|
const date = el.getAttribute('datetime');
|
|
if (date && !el.dataset.noUpdate) {
|
|
const ago = timeAgo(date);
|
|
if (el.textContent !== ago) {
|
|
el.textContent = ago;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initial timestamp update
|
|
document.addEventListener('DOMContentLoaded', updateTimestamps);
|
|
|
|
// Update timestamps after HTMX swaps
|
|
document.addEventListener('htmx:afterSwap', updateTimestamps);
|
|
|
|
// Update timestamps periodically
|
|
setInterval(updateTimestamps, 60000); // Every minute
|
|
|
|
// Toggle repository details (for images page)
|
|
function toggleRepo(name) {
|
|
const details = document.getElementById('repo-' + name);
|
|
const btn = document.getElementById('btn-' + name);
|
|
|
|
if (details.style.display === 'none') {
|
|
details.style.display = 'block';
|
|
btn.textContent = '▲';
|
|
} else {
|
|
details.style.display = 'none';
|
|
btn.textContent = '▼';
|
|
}
|
|
}
|
|
|
|
// User dropdown menu
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const menuBtn = document.getElementById('user-menu-btn');
|
|
const dropdownMenu = document.getElementById('user-dropdown-menu');
|
|
|
|
if (menuBtn && dropdownMenu) {
|
|
// Toggle dropdown on button click
|
|
menuBtn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const isExpanded = menuBtn.getAttribute('aria-expanded') === 'true';
|
|
|
|
if (isExpanded) {
|
|
closeDropdown();
|
|
} else {
|
|
openDropdown();
|
|
}
|
|
});
|
|
|
|
// Close dropdown when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
if (!menuBtn.contains(e.target) && !dropdownMenu.contains(e.target)) {
|
|
closeDropdown();
|
|
}
|
|
});
|
|
|
|
// Close dropdown on Escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
closeDropdown();
|
|
}
|
|
});
|
|
|
|
function openDropdown() {
|
|
menuBtn.setAttribute('aria-expanded', 'true');
|
|
dropdownMenu.removeAttribute('hidden');
|
|
}
|
|
|
|
function closeDropdown() {
|
|
menuBtn.setAttribute('aria-expanded', 'false');
|
|
dropdownMenu.setAttribute('hidden', '');
|
|
}
|
|
}
|
|
});
|