156 lines
5.4 KiB
JavaScript
156 lines
5.4 KiB
JavaScript
|
|
document.addEventListener('DOMContentLoaded', () => {
|
||
|
|
// Pre-fetch markdown content for all copy buttons to avoid Safari NotAllowedError
|
||
|
|
// Safari requires clipboard writes to happen synchronously within user gesture
|
||
|
|
const copyButtons = document.querySelectorAll('.hextra-page-context-menu-copy');
|
||
|
|
const contentCache = new Map();
|
||
|
|
|
||
|
|
// Pre-fetch content for each button on page load
|
||
|
|
copyButtons.forEach(button => {
|
||
|
|
const url = button.dataset.url;
|
||
|
|
if (url) {
|
||
|
|
fetch(url)
|
||
|
|
.then(response => {
|
||
|
|
if (response.ok) return response.text();
|
||
|
|
throw new Error('Failed to fetch');
|
||
|
|
})
|
||
|
|
.then(markdown => contentCache.set(url, markdown))
|
||
|
|
.catch(error => console.error('Failed to pre-fetch markdown:', error));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Initialize copy buttons with synchronous clipboard access
|
||
|
|
copyButtons.forEach(button => {
|
||
|
|
button.addEventListener('click', () => {
|
||
|
|
const url = button.dataset.url;
|
||
|
|
const markdown = contentCache.get(url);
|
||
|
|
|
||
|
|
if (markdown) {
|
||
|
|
// Synchronous clipboard write initiation - works in Safari
|
||
|
|
navigator.clipboard.writeText(markdown)
|
||
|
|
.then(() => {
|
||
|
|
button.classList.add('copied');
|
||
|
|
setTimeout(() => button.classList.remove('copied'), 1000);
|
||
|
|
})
|
||
|
|
.catch(error => console.error('Failed to copy markdown:', error));
|
||
|
|
} else {
|
||
|
|
// Fallback: fetch and copy (may fail in Safari if content not pre-fetched)
|
||
|
|
fetch(url)
|
||
|
|
.then(response => {
|
||
|
|
if (!response.ok) throw new Error('Failed to fetch');
|
||
|
|
return response.text();
|
||
|
|
})
|
||
|
|
.then(text => {
|
||
|
|
contentCache.set(url, text);
|
||
|
|
return navigator.clipboard.writeText(text);
|
||
|
|
})
|
||
|
|
.then(() => {
|
||
|
|
button.classList.add('copied');
|
||
|
|
setTimeout(() => button.classList.remove('copied'), 1000);
|
||
|
|
})
|
||
|
|
.catch(error => console.error('Failed to copy markdown:', error));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Initialize dropdown toggles
|
||
|
|
const dropdownToggles = document.querySelectorAll('.hextra-page-context-menu-toggle');
|
||
|
|
dropdownToggles.forEach(toggle => {
|
||
|
|
const container = toggle.closest('.hextra-page-context-menu');
|
||
|
|
const menu = container.querySelector('.hextra-page-context-menu-dropdown');
|
||
|
|
const chevron = toggle.querySelector('[data-chevron]');
|
||
|
|
|
||
|
|
toggle.addEventListener('click', (e) => {
|
||
|
|
e.stopPropagation();
|
||
|
|
const isOpen = toggle.dataset.state === 'open';
|
||
|
|
|
||
|
|
// Close all other dropdowns first
|
||
|
|
dropdownToggles.forEach(t => {
|
||
|
|
if (t !== toggle) {
|
||
|
|
t.dataset.state = 'closed';
|
||
|
|
const otherContainer = t.closest('.hextra-page-context-menu');
|
||
|
|
const otherMenu = otherContainer.querySelector('.hextra-page-context-menu-dropdown');
|
||
|
|
const otherChevron = t.querySelector('[data-chevron]');
|
||
|
|
otherMenu.classList.add('hx:hidden');
|
||
|
|
if (otherChevron) {
|
||
|
|
otherChevron.style.transform = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Toggle current
|
||
|
|
toggle.dataset.state = isOpen ? 'closed' : 'open';
|
||
|
|
menu.classList.toggle('hx:hidden', isOpen);
|
||
|
|
|
||
|
|
// Rotate chevron icon
|
||
|
|
if (chevron) {
|
||
|
|
chevron.style.transform = isOpen ? '' : 'rotate(180deg)';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Close dropdown when clicking outside
|
||
|
|
document.addEventListener('click', (e) => {
|
||
|
|
// Check if click is outside any dropdown container
|
||
|
|
const isOutside = !e.target.closest('.hextra-page-context-menu');
|
||
|
|
if (isOutside) {
|
||
|
|
dropdownToggles.forEach(toggle => {
|
||
|
|
toggle.dataset.state = 'closed';
|
||
|
|
const container = toggle.closest('.hextra-page-context-menu');
|
||
|
|
const menu = container.querySelector('.hextra-page-context-menu-dropdown');
|
||
|
|
const chevron = toggle.querySelector('[data-chevron]');
|
||
|
|
menu.classList.add('hx:hidden');
|
||
|
|
if (chevron) {
|
||
|
|
chevron.style.transform = '';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Helper to close dropdown
|
||
|
|
const closeDropdown = (container) => {
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
const toggle = container.querySelector('.hextra-page-context-menu-toggle');
|
||
|
|
const menu = container.querySelector('.hextra-page-context-menu-dropdown');
|
||
|
|
|
||
|
|
if (!toggle || !menu) return;
|
||
|
|
|
||
|
|
const chevron = toggle.querySelector('[data-chevron]');
|
||
|
|
toggle.dataset.state = 'closed';
|
||
|
|
menu.classList.add('hx:hidden');
|
||
|
|
if (chevron) {
|
||
|
|
chevron.style.transform = '';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Handle dropdown menu copy action
|
||
|
|
document.querySelectorAll('.hextra-page-context-menu-dropdown button[data-action="copy"]').forEach(btn => {
|
||
|
|
btn.addEventListener('click', async (e) => {
|
||
|
|
e.stopPropagation();
|
||
|
|
const container = btn.closest('.hextra-page-context-menu');
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
const copyBtn = container.querySelector('.hextra-page-context-menu-copy');
|
||
|
|
if (!copyBtn) return;
|
||
|
|
|
||
|
|
closeDropdown(container);
|
||
|
|
copyBtn.click();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Handle dropdown menu view action
|
||
|
|
document.querySelectorAll('.hextra-page-context-menu-dropdown button[data-action="view"]').forEach(btn => {
|
||
|
|
btn.addEventListener('click', (e) => {
|
||
|
|
e.stopPropagation();
|
||
|
|
const container = btn.closest('.hextra-page-context-menu');
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
const url = btn.dataset.url;
|
||
|
|
if (!url) return;
|
||
|
|
|
||
|
|
closeDropdown(container);
|
||
|
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|