Added TOC highlighting
This commit is contained in:
		
							parent
							
								
									f8efb09284
								
							
						
					
					
						commit
						2e597f5b8f
					
				
					 4 changed files with 200 additions and 4 deletions
				
			
		| 
						 | 
					@ -17,9 +17,12 @@
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.toc:has(#TableOfContents) {
 | 
					.toc:has(#TableOfContents) {
 | 
				
			||||||
  background-color: rgba(25, 25, 35, 0.45);
 | 
					  background-color: rgba(25, 25, 35, 0.3);
 | 
				
			||||||
  border-radius: 10px;
 | 
					  border-radius: 10px;
 | 
				
			||||||
  max-height: 70vh;
 | 
					  max-height: 70vh;
 | 
				
			||||||
 | 
					  overflow-y: auto;
 | 
				
			||||||
 | 
					  padding: 1rem;
 | 
				
			||||||
 | 
					  scroll-behavior: smooth;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.background-container {
 | 
					.background-container {
 | 
				
			||||||
| 
						 | 
					@ -108,3 +111,44 @@ header button[id^="search-button"]:hover {
 | 
				
			||||||
    backdrop-filter: blur(8px) hue-rotate(-10deg);
 | 
					    backdrop-filter: blur(8px) hue-rotate(-10deg);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TOC link styling */
 | 
				
			||||||
 | 
					#TableOfContents a {
 | 
				
			||||||
 | 
					  color: rgba(255, 255, 255, 0.8);
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  padding: 0.25rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#TableOfContents a:hover {
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  transform: translateX(4px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Active TOC item */
 | 
				
			||||||
 | 
					#TableOfContents a.active {
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  transform: translateX(4px);
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#TableOfContents a.active::before {
 | 
				
			||||||
 | 
					  content: '';
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  left: -1rem;
 | 
				
			||||||
 | 
					  top: 50%;
 | 
				
			||||||
 | 
					  transform: translateY(-50%);
 | 
				
			||||||
 | 
					  width: 4px;
 | 
				
			||||||
 | 
					  height: 1rem;
 | 
				
			||||||
 | 
					  background: linear-gradient(to bottom, #4a9eff, #2d7bda);
 | 
				
			||||||
 | 
					  border-radius: 2px;
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Remove the default smooth scrolling behavior */
 | 
				
			||||||
 | 
					html {
 | 
				
			||||||
 | 
					  scroll-behavior: smooth;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										151
									
								
								assets/js/toc-highlight.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								assets/js/toc-highlight.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,151 @@
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					    const tocLinks = document.querySelectorAll('#TableOfContents a');
 | 
				
			||||||
 | 
					    const tocContainer = document.querySelector('.toc');
 | 
				
			||||||
 | 
					    const sections = new Map(); // Store section elements with their corresponding TOC links
 | 
				
			||||||
 | 
					    let lastActiveSection = null;
 | 
				
			||||||
 | 
					    let scrollTimeout = null;
 | 
				
			||||||
 | 
					    let isScrolling = false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Build a map of section IDs to their TOC links
 | 
				
			||||||
 | 
					    tocLinks.forEach(link => {
 | 
				
			||||||
 | 
					        const sectionId = decodeURIComponent(link.getAttribute('href').substring(1));
 | 
				
			||||||
 | 
					        const section = document.getElementById(sectionId);
 | 
				
			||||||
 | 
					        if (section) {
 | 
				
			||||||
 | 
					            sections.set(section, link);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Function to scroll TOC container to keep active item in view
 | 
				
			||||||
 | 
					    const scrollTocToActive = (activeLink) => {
 | 
				
			||||||
 | 
					        if (!tocContainer || !activeLink) return;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        const containerRect = tocContainer.getBoundingClientRect();
 | 
				
			||||||
 | 
					        const linkRect = activeLink.getBoundingClientRect();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Check if the active link is outside the visible area
 | 
				
			||||||
 | 
					        if (linkRect.top < containerRect.top || linkRect.bottom > containerRect.bottom) {
 | 
				
			||||||
 | 
					            // Calculate the scroll position to center the active link
 | 
				
			||||||
 | 
					            const scrollTop = activeLink.offsetTop - (containerRect.height / 2) + (linkRect.height / 2);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Use CSS smooth scrolling if available, otherwise use JS
 | 
				
			||||||
 | 
					            if ('scrollBehavior' in document.documentElement.style) {
 | 
				
			||||||
 | 
					                tocContainer.scrollTo({
 | 
				
			||||||
 | 
					                    top: scrollTop,
 | 
				
			||||||
 | 
					                    behavior: 'smooth'
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Fallback for browsers that don't support smooth scrolling
 | 
				
			||||||
 | 
					                tocContainer.scrollTop = scrollTop;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Function to find the section closest to the center of the viewport
 | 
				
			||||||
 | 
					    const findClosestSectionToCenter = () => {
 | 
				
			||||||
 | 
					        let closestSection = null;
 | 
				
			||||||
 | 
					        let closestDistance = Infinity;
 | 
				
			||||||
 | 
					        const viewportHeight = window.innerHeight;
 | 
				
			||||||
 | 
					        const viewportCenter = viewportHeight / 2;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        sections.forEach((link, section) => {
 | 
				
			||||||
 | 
					            const rect = section.getBoundingClientRect();
 | 
				
			||||||
 | 
					            // Calculate the center of the section
 | 
				
			||||||
 | 
					            const sectionCenter = rect.top + (rect.height / 2);
 | 
				
			||||||
 | 
					            // Calculate distance from the center of the viewport
 | 
				
			||||||
 | 
					            const distance = Math.abs(sectionCenter - viewportCenter);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // If this section is closer to the center than our current closest
 | 
				
			||||||
 | 
					            if (distance < closestDistance) {
 | 
				
			||||||
 | 
					                closestDistance = distance;
 | 
				
			||||||
 | 
					                closestSection = section;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return closestSection;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Function to update the active section
 | 
				
			||||||
 | 
					    const updateActiveSection = (activeSection) => {
 | 
				
			||||||
 | 
					        if (!activeSection) return;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Update active state
 | 
				
			||||||
 | 
					        tocLinks.forEach(link => link.classList.remove('active'));
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        const activeLink = sections.get(activeSection);
 | 
				
			||||||
 | 
					        if (activeLink) {
 | 
				
			||||||
 | 
					            activeLink.classList.add('active');
 | 
				
			||||||
 | 
					            lastActiveSection = activeSection;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Use a small delay before scrolling to prevent rapid changes
 | 
				
			||||||
 | 
					            if (scrollTimeout) {
 | 
				
			||||||
 | 
					                clearTimeout(scrollTimeout);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            scrollTimeout = setTimeout(() => {
 | 
				
			||||||
 | 
					                scrollTocToActive(activeLink);
 | 
				
			||||||
 | 
					            }, 100);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Function to handle scroll events
 | 
				
			||||||
 | 
					    const handleScroll = () => {
 | 
				
			||||||
 | 
					        // Clear any pending scroll timeout
 | 
				
			||||||
 | 
					        if (scrollTimeout) {
 | 
				
			||||||
 | 
					            clearTimeout(scrollTimeout);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Find the section closest to the center of the viewport
 | 
				
			||||||
 | 
					        const closestSection = findClosestSectionToCenter();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Update the active section
 | 
				
			||||||
 | 
					        updateActiveSection(closestSection);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add scroll event listener with debouncing
 | 
				
			||||||
 | 
					    let scrollDebounceTimer = null;
 | 
				
			||||||
 | 
					    window.addEventListener('scroll', () => {
 | 
				
			||||||
 | 
					        isScrolling = true;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Debounce the scroll event
 | 
				
			||||||
 | 
					        if (scrollDebounceTimer) {
 | 
				
			||||||
 | 
					            clearTimeout(scrollDebounceTimer);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        scrollDebounceTimer = setTimeout(() => {
 | 
				
			||||||
 | 
					            isScrolling = false;
 | 
				
			||||||
 | 
					            handleScroll();
 | 
				
			||||||
 | 
					        }, 100);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Initial check to ensure a section is highlighted
 | 
				
			||||||
 | 
					    setTimeout(() => {
 | 
				
			||||||
 | 
					        handleScroll();
 | 
				
			||||||
 | 
					    }, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Smooth scroll to section when clicking TOC links
 | 
				
			||||||
 | 
					    tocLinks.forEach(link => {
 | 
				
			||||||
 | 
					        link.addEventListener('click', (e) => {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					            const targetId = decodeURIComponent(link.getAttribute('href').substring(1));
 | 
				
			||||||
 | 
					            const targetSection = document.getElementById(targetId);
 | 
				
			||||||
 | 
					            if (targetSection) {
 | 
				
			||||||
 | 
					                // Use our custom smooth scrolling function if available
 | 
				
			||||||
 | 
					                if (window.smoothScrollTo) {
 | 
				
			||||||
 | 
					                    window.smoothScrollTo(targetSection);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Fallback to standard smooth scrolling
 | 
				
			||||||
 | 
					                    targetSection.scrollIntoView({ behavior: 'smooth' });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Also update the active section immediately
 | 
				
			||||||
 | 
					                const activeLink = sections.get(targetSection);
 | 
				
			||||||
 | 
					                if (activeLink) {
 | 
				
			||||||
 | 
					                    tocLinks.forEach(l => l.classList.remove('active'));
 | 
				
			||||||
 | 
					                    activeLink.classList.add('active');
 | 
				
			||||||
 | 
					                    lastActiveSection = targetSection;
 | 
				
			||||||
 | 
					                    scrollTocToActive(activeLink);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}); 
 | 
				
			||||||
							
								
								
									
										4
									
								
								layouts/partials/extend-footer.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								layouts/partials/extend-footer.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					{{ if .TableOfContents }}
 | 
				
			||||||
 | 
					  {{ $tocHighlight := resources.Get "js/toc-highlight.js" | js.Build | fingerprint }}
 | 
				
			||||||
 | 
					  <script src="{{ $tocHighlight.RelPermalink }}" integrity="{{ $tocHighlight.Data.Integrity }}" crossorigin="anonymous"></script>
 | 
				
			||||||
 | 
					{{ end }} 
 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
{{ if .Params.showTableOfContents | default (.Site.Params.article.showTableOfContents | default false) }}
 | 
					 | 
				
			||||||
  <script src="/js/scrollspy.js"></script>
 | 
					 | 
				
			||||||
{{ end }} 
 | 
					 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue