PageSourceSearch

https://ravichaganti.com/js/toc-scrollspy.js

js ravichaganti.com collected 2026-10-02 04:58:46 UTC 3,071 bytes, 79 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2    const toc = document.getElementById('sidebar-toc');
3    if (!toc) return;
4
5    const links = toc.querySelectorAll('a');
6    const content = document.querySelector('.prose');
7    if (!content) return;
8
9    const headers = Array.from(content.querySelectorAll('h1, h2, h3, h4, h5, h6'));
10    if (headers.length === 0) return;
11
12    // Smooth scrolling for TOC links
13    links.forEach(link => {
14        link.addEventListener('click', (e) => {
15            e.preventDefault();
16            const targetId = link.getAttribute('href').substring(1);
17            const targetHeader = document.getElementById(targetId);
18            
19            if (targetHeader) {
20                // Adjust for sticky header if you have one, or just a bit of padding
21                const headerOffset = 100; 
22                const elementPosition = targetHeader.getBoundingClientRect().top;
23                const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
24
25                window.scrollTo({
26                    top: offsetPosition,
27                    behavior: "smooth"
28                });
29
30                // Update URL hash without jumping
31                history.pushState(null, null, '#' + targetId);
32            }
33        });
34    });
35
36    // ScrollSpy implementation using IntersectionObserver
37    const observerOptions = {
38        root: null,
39        rootMargin: '-10% 0px -80% 0px', // Trigger when header is near the top
40        threshold: 0
41    };
42
43    const observerCallback = (entries) => {
44        entries.forEach(entry => {
45            if (entry.isIntersecting) {
46                const activeId = entry.target.id;
47                
48                // Remove active class from all links
49                links.forEach(link => {
50                    link.classList.remove('active');
51                    // Also expand/collapse parents if implementing nested collapsing
52                });
53
54                // Add active class to corresponding link
55                const activeLink = toc.querySelector(`a[href="#${activeId}"]`);
56                if (activeLink) {
57                    activeLink.classList.add('active');
58                    
59                    // Optional: Scroll TOC to keep active item in view
60                    const tocRect = toc.getBoundingClientRect();
61                    const linkRect = activeLink.getBoundingClientRect();
62                    
63                    if (linkRect.top < tocRect.top || linkRect.bottom > tocRect.bottom) {
64                        activeLink.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
65                    }
66                }
67            }
68        });
69    };
70
71    const observer = new IntersectionObserver(observerCallback, observerOptions);
72    headers.forEach(header => observer.observe(header));
73    
74    // Fallback/highlight on load if hash exists
75    if (window.location.hash) {
76        const activeLink = toc.querySelector(`a[href="${window.location.hash}"]`);
77        if (activeLink) activeLink.classList.add('active');
78    }
79});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.