PageSourceSearch

https://journalspress.com/core/modules/fffec4dd6a/assets/js/wma-toc.js?rnd=52244

js journalspress.com collected 2026-10-02 14:44:41 UTC 8,447 bytes, 216 lines download raw bytes

1/**
2 * Wasabi Article Integrator - Interactive Table of Contents & Sidebar Smooth Scroll
3 */
4(function() {
5    'use strict';
6
7    function getStickyHeaderHeight() {
8        var stickyHeader = document.querySelector('.header.-sticky, header.header, .header, .site-header');
9        if (stickyHeader) {
10            var height = stickyHeader.offsetHeight;
11            if (height > 30) {
12                return height + 20;
13            }
14        }
15        return 100; // Default offset for sticky header
16    }
17
18    function findTargetElement(hash) {
19        if (!hash || hash === '#' || hash.length < 2) {
20            return null;
21        }
22
23        var id = hash.substring(1);
24
25        // 1. Direct getElementById
26        var el = document.getElementById(id);
27        if (el) return el;
28
29        // 2. CSS escape querySelector
30        try {
31            if (window.CSS && window.CSS.escape) {
32                el = document.querySelector('#' + window.CSS.escape(id));
33                if (el) return el;
34            }
35        } catch (e) {}
36
37        // 3. Decoded URL ID
38        try {
39            var decoded = decodeURIComponent(id);
40            el = document.getElementById(decoded);
41            if (el) return el;
42            if (window.CSS && window.CSS.escape) {
43                el = document.querySelector('#' + window.CSS.escape(decoded));
44                if (el) return el;
45            }
46        } catch (e) {}
47
48        // 4. Fallback anchor name
49        var anchors = document.getElementsByName(id);
50        if (anchors && anchors.length > 0) {
51            return anchors[0];
52        }
53
54        return null;
55    }
56
57    function centerLinkInContainer(container, link) {
58        if (!container || !link || container.scrollHeight <= container.clientHeight) {
59            return;
60        }
61
62        var containerRect = container.getBoundingClientRect();
63        var linkRect = link.getBoundingClientRect();
64        var currentScroll = container.scrollTop;
65        var offsetToCenter = (linkRect.top - containerRect.top) - (container.clientHeight / 2) + (linkRect.height / 2);
66        var targetScroll = currentScroll + offsetToCenter;
67        var maxScroll = Math.max(0, container.scrollHeight - container.clientHeight);
68
69        targetScroll = Math.max(0, Math.min(maxScroll, targetScroll));
70
71        if (Math.abs(targetScroll - currentScroll) < 4) {
72            return;
73        }
74
75        if (typeof container.scrollTo === 'function') {
76            container.scrollTo({
77                top: targetScroll,
78                behavior: 'smooth'
79            });
80        } else {
81            container.scrollTop = targetScroll;
82        }
83    }
84
85    function initTocScroll() {
86        var selector = '.wasabi-sidebar-toc-container a[href^="#"], .article-nav-list a[href^="#"]';
87        var links = document.querySelectorAll(selector);
88
89        links.forEach(function(link) {
90            link.addEventListener('click', function(e) {
91                var href = this.getAttribute('href');
92                if (!href || href === '#') return;
93
94                var targetEl = findTargetElement(href);
95                if (targetEl) {
96                    e.preventDefault();
97
98                    var headerOffset = getStickyHeaderHeight();
99                    var elementPosition = targetEl.getBoundingClientRect().top + window.pageYOffset;
100                    var offsetPosition = elementPosition - headerOffset;
101
102                    window.scrollTo({
103                        top: Math.max(0, offsetPosition),
104                        behavior: 'smooth'
105                    });
106
107                    // Set active link class
108                    document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) {
109                        l.classList.remove('active');
110                    });
111                    this.classList.add('active');
112
113                    // Update URL state without page jump
114                    if (history.pushState) {
115                        history.pushState(null, null, href);
116                    }
117                }
118            });
119        });
120
121        // ScrollSpy logic for TOC links
122        var tocLinks = document.querySelectorAll('.wasabi-sidebar-toc-container a[href^="#"]');
123        var fullTextLink = document.querySelector('.article-nav-list a[href="#wasabi-article-section"]');
124        var sidebarContainer = document.querySelector('.article-nav-sidebar');
125        var fullTextSection = document.getElementById('wasabi-article-section');
126        var lastActive = null;
127
128        if (tocLinks.length > 0) {
129            var sectionMap = [];
130            tocLinks.forEach(function(l) {
131                var href = l.getAttribute('href');
132                var el = findTargetElement(href);
133                if (el) {
134                    sectionMap.push({ link: l, element: el });
135                }
136            });
137
138            if (sectionMap.length > 0) {
139                var ticking = false;
140                var onScroll = function() {
141                    var scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
142                    var headerOffset = getStickyHeaderHeight();
143                    var currentActive = null;
144                    var fullTextActive = false;
145
146                    for (var i = 0; i < sectionMap.length; i++) {
147                        var sec = sectionMap[i];
148                        var top = sec.element.getBoundingClientRect().top + scrollPosition - headerOffset - 30;
149                        if (scrollPosition >= top) {
150                            currentActive = sec.link;
151                        }
152                    }
153
154                    if (fullTextLink && fullTextSection) {
155                        var fullTextTop = fullTextSection.getBoundingClientRect().top + scrollPosition - headerOffset - 30;
156                        var fullTextBottom = fullTextTop + fullTextSection.offsetHeight;
157                        fullTextActive = scrollPosition >= fullTextTop && scrollPosition < fullTextBottom;
158                    }
159
160                    if (!fullTextActive) {
161                        currentActive = null;
162                    }
163
164                    if (currentActive) {
165                        if (currentActive !== lastActive) {
166                            document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) {
167                                l.classList.remove('active');
168                            });
169                            currentActive.classList.add('active');
170                            if (fullTextLink) {
171                                fullTextLink.classList.add('active');
172                            }
173                            lastActive = currentActive;
174
175                            centerLinkInContainer(sidebarContainer, currentActive);
176                        }
177                    } else if (fullTextActive && fullTextLink) {
178                        if (lastActive !== fullTextLink) {
179                            document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) {
180                                l.classList.remove('active');
181                            });
182                            fullTextLink.classList.add('active');
183                            lastActive = fullTextLink;
184                            centerLinkInContainer(sidebarContainer, fullTextLink);
185                        }
186                    } else {
187                        if (lastActive) {
188                            document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) {
189                                l.classList.remove('active');
190                            });
191                            lastActive = null;
192                        }
193                    }
194                };
195
196                window.addEventListener('scroll', function() {
197                    if (!ticking) {
198                        window.requestAnimationFrame(function() {
199                            onScroll();
200                            ticking = false;
201                        });
202                        ticking = true;
203                    }
204                }, { passive: true });
205
206                onScroll();
207            }
208        }
209    }
210
211    if (document.readyState === 'loading') {
212        document.addEventListener('DOMContentLoaded', initTocScroll);
213    } else {
214        initTocScroll();
215    }
216})();

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.