Fork of Chiri for Astro for my blog
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(plugins): improve copy code button DOM structure and event handling

the3ash 39f48229 7dbd60d4

+73 -27
+29 -5
src/components/ui/CopyCode.astro
··· 26 26 const copyButtons = document.querySelectorAll('.copy-button') 27 27 28 28 copyButtons.forEach((button) => { 29 - const preElement = button.closest('.copy-code-block') 29 + // Avoid initializing multiple times (important for SPA navigation) 30 + if (button.dataset.copyInit) return 31 + button.dataset.copyInit = '1' 32 + 33 + // Find the closest pre element with the .copy-code-block class 34 + let preElement = button.closest('.copy-code-block') 35 + if (!preElement) { 36 + const parent = button.parentElement 37 + if (parent) { 38 + preElement = parent.querySelector('.copy-code-block') 39 + } 40 + } 30 41 if (!preElement) return 31 42 32 43 const codeElement = preElement.querySelector('code') ··· 39 50 button.style.opacity = '0' 40 51 button.style.pointerEvents = 'none' 41 52 42 - preElement.addEventListener('mouseenter', () => { 53 + // Determine the container to attach hover events 54 + let container = preElement 55 + const parent = button.parentElement 56 + if (parent && parent.classList.contains('copy-code-wrapper')) { 57 + container = parent 58 + } 59 + 60 + container.addEventListener('mouseenter', () => { 43 61 button.style.opacity = '1' 44 62 button.style.pointerEvents = 'auto' 45 63 }) 46 64 47 - preElement.addEventListener('mouseleave', () => { 65 + container.addEventListener('mouseleave', () => { 48 66 if (!button.hasAttribute('data-copying')) { 49 67 button.style.opacity = '0' 50 68 button.style.pointerEvents = 'none' ··· 61 79 button.innerHTML = copiedIcon 62 80 63 81 setTimeout(() => { 64 - if (!preElement.matches(':hover')) { 82 + if (!container.matches(':hover')) { 65 83 button.style.opacity = '0' 66 84 button.style.pointerEvents = 'none' 67 85 } ··· 101 119 button.innerHTML = copiedIcon 102 120 103 121 setTimeout(() => { 104 - if (!preElement.matches(':hover')) { 122 + if (!container.matches(':hover')) { 105 123 button.style.opacity = '0' 106 124 button.style.pointerEvents = 'none' 107 125 } ··· 121 139 </script> 122 140 123 141 <style is:inline> 142 + /* Ensure the positioned ancestor is correct */ 143 + .copy-code-wrapper { 144 + position: relative !important; 145 + } 146 + 147 + /* Keep for backward compatibility */ 124 148 .copy-code-block { 125 149 position: relative !important; 126 150 }
+44 -22
src/plugins/rehype-copy-code.mjs
··· 5 5 */ 6 6 export default function rehypeCopyCode() { 7 7 return (tree) => { 8 - visit(tree, 'element', (node) => { 9 - if (node.tagName === 'pre') { 10 - if (!node.children || node.children.length === 0) { 11 - return 12 - } 8 + visit(tree, 'element', (node, index, parent) => { 9 + // Only process pre elements 10 + if (node.tagName !== 'pre') { 11 + return 12 + } 13 + 14 + // Validate pre element has children 15 + if (!node.children?.length) { 16 + return 17 + } 13 18 14 - const codeElement = node.children.find((child) => child.tagName === 'code') 15 - if (!codeElement) { 16 - return 17 - } 19 + // Ensure code element exists 20 + const hasCodeElement = node.children.some((child) => child.tagName === 'code') 21 + if (!hasCodeElement) { 22 + return 23 + } 18 24 19 - node.properties = node.properties || {} 20 - node.properties.className = node.properties.className || [] 25 + // Mark the pre element with class for styling 26 + node.properties = node.properties || {} 27 + node.properties.className = node.properties.className || [] 28 + if (!node.properties.className.includes('copy-code-block')) { 21 29 node.properties.className.push('copy-code-block') 30 + } 22 31 23 - const copyButton = { 24 - type: 'element', 25 - tagName: 'button', 26 - properties: { 27 - className: ['copy-button'], 28 - type: 'button', 29 - 'aria-label': 'Copy code' 30 - }, 31 - children: [] 32 - } 32 + // Create copy button 33 + const copyButton = { 34 + type: 'element', 35 + tagName: 'button', 36 + properties: { 37 + className: ['copy-button'], 38 + type: 'button', 39 + 'aria-label': 'Copy code to clipboard' 40 + }, 41 + children: [] 42 + } 43 + 44 + // Wrap pre and button in a container for better layout control 45 + const wrapper = { 46 + type: 'element', 47 + tagName: 'div', 48 + properties: { 49 + className: ['copy-code-wrapper'] 50 + }, 51 + children: [copyButton, node] 52 + } 33 53 34 - node.children.unshift(copyButton) 54 + // Replace the pre element with the wrapper 55 + if (parent && typeof index === 'number') { 56 + parent.children[index] = wrapper 35 57 } 36 58 }) 37 59 }