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(toc): improve component behavior

the3ash 950eb17a 9dbc7e6f

+54 -14
+53 -13
src/components/ui/TableOfContents.astro
··· 8 8 <div class="toc-container" id="toc"> 9 9 <nav class="toc-nav"> 10 10 <ul class="toc-list" id="toc-list"> 11 - <!-- Back to top link --> 11 + <!-- Back to top link - always available for navigation --> 12 12 <li class="toc-item toc-level-0"> 13 13 <a href="#" class="toc-link toc-title" title="Back to top" data-text="Back to top"> 14 14 Back to top ··· 48 48 let tocLinks = null 49 49 let headings = null 50 50 let titleLink = null 51 + // Add efficient Map lookup 52 + const headingMap = new Map() 51 53 52 54 function initDOMCache() { 53 55 tocContainer = document.querySelector('.toc-container') 54 56 tocLinks = document.querySelectorAll('.toc-link') 55 57 headings = document.querySelectorAll('h1, h2, h3') 56 58 titleLink = document.querySelector('.toc-link.toc-title') 59 + 60 + // Build heading map for efficient lookup 61 + buildHeadingMap() 62 + } 63 + 64 + // Cache DOM queries to avoid repeated lookups 65 + function refreshDOMCache() { 66 + tocLinks = document.querySelectorAll('.toc-link') 67 + headings = document.querySelectorAll('h1, h2, h3') 68 + titleLink = document.querySelector('.toc-link.toc-title') 69 + buildHeadingMap() 70 + } 71 + 72 + function buildHeadingMap() { 73 + headingMap.clear() 74 + if (!tocLinks) return 75 + 76 + tocLinks.forEach((link) => { 77 + const href = link.getAttribute('href') 78 + if (href && href.startsWith('#')) { 79 + const id = href.substring(1) 80 + headingMap.set(id, link) 81 + } 82 + }) 57 83 } 58 84 59 85 // TOC positioning logic (similar to BackButton) ··· 98 124 99 125 // Hide TOC if there are no headings (only title) 100 126 function checkTOCVisibility() { 101 - const tocItems = document.querySelectorAll('.toc-item:not(.toc-level-0)') 127 + // Use cached tocLinks if available, otherwise query once 128 + const tocItems = tocLinks 129 + ? Array.from(tocLinks).filter((link) => !link.classList.contains('toc-title')) 130 + : document.querySelectorAll('.toc-item:not(.toc-level-0)') 102 131 103 132 if (tocContainer && tocItems.length === 0) { 104 133 tocContainer.style.display = 'none' ··· 148 177 function updateActiveTOCItem() { 149 178 // Check if cache is valid, if not, re-fetch 150 179 if (!tocLinks || !headings || tocLinks.length === 0 || headings.length === 0) { 151 - tocLinks = document.querySelectorAll('.toc-link') 152 - headings = document.querySelectorAll('h1, h2, h3') 153 - titleLink = document.querySelector('.toc-link.toc-title') 180 + refreshDOMCache() 154 181 } 155 182 156 183 if (!tocLinks || !headings) return ··· 170 197 } 171 198 }) 172 199 200 + // Clear all active states first 173 201 tocLinks.forEach((link) => { 174 202 link.classList.remove('active') 175 - if (link.getAttribute('href') === `#${currentActive}`) { 176 - link.classList.add('active') 177 - } 178 203 }) 179 204 180 - // Highlight "Back to top" if no other item is active 181 - if (!currentActive && titleLink) { 182 - titleLink.classList.add('active') 205 + // Only highlight a TOC item if we have a valid active heading 206 + if (currentActive && headingMap.has(currentActive)) { 207 + const activeLink = headingMap.get(currentActive) 208 + activeLink.classList.add('active') 209 + } else { 210 + // If no heading is active (e.g., at the very top of the page), 211 + // highlight "Back to top" to indicate we're at the beginning 212 + if (titleLink) { 213 + titleLink.classList.add('active') 214 + } 183 215 } 184 216 } 185 217 ··· 192 224 193 225 // Listen for various page load events 194 226 document.addEventListener('astro:page-load', () => { 195 - initDOMCache() 227 + refreshDOMCache() 196 228 adjustTOCPosition() 197 229 addTOCEventListeners() 198 230 updateActiveTOCItem() // Update active state after page load ··· 209 241 210 242 // Re-initialize on resize and scroll 211 243 window.addEventListener('resize', adjustTOCPosition) 244 + 245 + // Add debounced scroll listener for better performance 246 + let scrollTimeout = null 212 247 window.addEventListener('scroll', () => { 213 - updateActiveTOCItem() 248 + if (scrollTimeout) { 249 + clearTimeout(scrollTimeout) 250 + } 251 + scrollTimeout = setTimeout(() => { 252 + updateActiveTOCItem() 253 + }, 16) // ~60fps, 16ms debounce 214 254 }) 215 255 })() 216 256 </script>
+1 -1
src/plugins/remark-toc.mjs
··· 56 56 57 57 return text.trim() 58 58 } 59 - 59 + 60 60 // Generate a slug from text 61 61 function generateSlug(text) { 62 62 return (