* feat(context-menu): implement page context menu for copy Markdown - Added a context menu to Markdown pages allowing users to copy content as Markdown or view it in a new tab. - Introduced new layout files for Markdown rendering and updated existing layouts to include the context menu. - Enhanced configuration options to enable or disable the context menu globally or per page. - Updated internationalization files to support new context menu actions. - Improved documentation to guide users on the new features and their usage. * feat(context-menu): enhance dropdown positioning and responsiveness - Updated the context menu dropdown to use responsive positioning classes for better alignment on different screen sizes. - Added new CSS classes to ensure the dropdown appears correctly on smaller devices, improving user experience. * refactor(context-menu): adjust button styles for improved layout and usability - Modified button classes in the page context menu to enhance spacing and alignment. - Reduced padding and gap sizes for a more compact design, improving overall user experience. * refactor(styles): update CSS classes for improved layout and consistency - Removed outdated ring classes and adjusted padding and border styles in various components for a cleaner design. - Enhanced hover effects and gap sizes for better user experience across language switcher, navbar, and theme toggle elements. - Standardized rounded styles to maintain visual consistency throughout the interface. * feat(context-menu): add custom links to page context menu - Introduced new configuration options for custom links in the page context menu, allowing users to open documentation in ChatGPT and Claude. - Updated the context menu layout to include a separator and display the new links with appropriate styling. - Enhanced the CSS classes for better visual integration with existing context menu elements. * feat(icons): add new AI icons and update context menu links - Introduced new icons for AI tools including ChatGPT, Claude, Gemini, and others in the icons.yaml file. - Enhanced the page context menu in multiple languages to include links for opening documentation in ChatGPT and Claude, improving user accessibility to AI resources. - Updated internationalization files to reflect new context menu options and additional copy functionalities. * refactor(layouts): simplify page structure in glossary and section layouts - Removed the conditional rendering of page links in the glossary and section layouts to streamline the markup. - Adjusted the blog and docs list layouts to ensure consistent closing of HTML tags and improved formatting. * feat(context-menu): add outbound icons to external links Add arrow-up-right icons to "View as Markdown" and custom links in the page context menu to indicate they open in new tabs. * Revert "feat(context-menu): add outbound icons to external links" This reverts commit 670175e200f091ed89b15bd16f44a585355db57d. * chore: rebuild css * chore: update stats json * chore: rename project and update context menu structure - Changed project name from "wizardly-wing" to "hextra" in package-lock.json. - Refactored context menu structure in hugo.yaml to improve organization and consistency across multiple languages. - Updated context menu links to ensure proper functionality and accessibility. * feat(context-menu): enhance clipboard functionality and pre-fetching - Implemented pre-fetching of markdown content for copy buttons to improve performance and avoid clipboard access issues in Safari. - Updated click event handlers to utilize cached content for clipboard operations, with a fallback to fetch content if not pre-fetched. - Added checks to ensure elements exist before performing actions, enhancing robustness of the context menu interactions. * fix(context-menu): improve hover effects and border styles - Enhanced the context menu's border styles with transition effects for better visual feedback on hover. - Updated CSS classes to ensure consistent styling across different themes, improving user experience. * feat(context-menu): add page context menu functionality across multiple languages - Introduced a page context menu that allows users to copy content as Markdown or view the raw Markdown source, enhancing usability for documentation sites. - Added configuration options to enable the context menu globally and control it on a per-page basis. - Implemented support for custom links in the context menu, allowing integration with external services. - Updated documentation in Persian, Japanese, Chinese, and English to reflect these new features. * fix(icons): update SVG definitions for Gemini and add fill attribute - Updated the SVG definition for the Gemini icon to include the 'fill' attribute for better rendering. - Ensured consistency in the SVG structure for the Gemini icon while maintaining the existing definitions for ChatGPT and Claude. * chore: rebuild css * chore: remove Claude links from context menu in multiple languages - Removed the "Open in Claude" option from the context menu for Persian, Japanese, and Simplified Chinese languages to streamline the user experience. * fix(context-menu): update cursor styles for buttons in context menu - Changed cursor style from default to pointer for buttons in the page context menu to enhance user interaction feedback.
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');
|
|
});
|
|
});
|
|
});
|