PageSourceSearch

https://www.pcmag.com/js/alpine/table-of-contents-href.js

js pcmag.com collected 2026-09-24 06:04:13 UTC 2,975 bytes, 61 lines download raw bytes

1document.addEventListener('alpine:init', () => {
2    Alpine.data('tableOfContents', links => ({
3        links: links?.map(link => ({ text: _unescape(link.text), anchor: link.anchor })) || [],
4        open: false,
5        selection: { text: 'Table of Contents', anchor: location.hash?.replace('#', '') ?? null },
6        leftRailTOCHeightStyle: '',
7        select(link, isLeftRailTOC = false) {
8            this.selection = link;
9            if (!isLeftRailTOC) this.open = false;
10            const anchor = this.selection.anchor;
11
12            if (anchor === '' || anchor === null) {
13                return;
14            }
15
16            location.hash = anchor;
17            let scrollHeight = window.scrollY || window.pageYOffset;
18            const offset = (anchor === 'compare-our-picks' ? 125 : 25) + (isLeftRailTOC ? 0 : 75);
19
20            window.scrollTo({ top: document.getElementById(anchor).getBoundingClientRect().top + scrollHeight - 200, behavior: 'auto' })
21            // Timeout so ads/embeds load before anchoring to content
22            setTimeout(() => {
23                scrollHeight = window.scrollY || window.pageYOffset;
24                window.scrollTo({ top: document.getElementById(anchor).getBoundingClientRect().top + scrollHeight - offset, behavior: 'smooth' });
25            }, 500);
26        },
27        isValidLink: anchor => !!document.getElementById(anchor),
28        calculateLeftRailTOCHeight() {
29            if (!this.$refs.leftRailTOC) return 'height: 0px';
30
31            const cellCount = this.open ? 10 : 6
32            const childNodes = [...this.$refs.leftRailTOC.children].filter(child =>
32 child.offsetHeight).slice(0, this.links.length < cellCount ? this.links.length : cellCount)
33            const height = Math.round(childNodes.reduce((sum, child) => sum + child.offsetHeight, 0))
34            return `height: ${height}px`;
35        },
36        init() {
37            if (this.links?.length > 3) {
38                const callback = entries => entries.forEach(entry => entry.isIntersecting ? this.selection = this.links.find(link => link.anchor === entry.target.id) : null);
39                const observer = new IntersectionObserver(callback, {
40                    rootMargin: '-35px 0px -' + (window.innerHeight - 175) + 'px 0px',
41                    threshold: 0
42                })
43
44                this.links.forEach(link => {
45                    const linkNode = document.getElementById(link.anchor);
46                    linkNode !== null && observer.observe(linkNode);
47                })
48
49                this.$nextTick(() => {
50                    if (location.hash) {
51                        history.scrollRestoration = 'manual'
52                        this.select(this.selection)
53                    }
54                    this.leftRailTOCHeightStyle = this.calculateLeftRailTOCHeight()
55                })
56
57                this.$watch('open', () => this.leftRailTOCHeightStyle = this.calculateLeftRailTOCHeight())
58            }
59        }
60    }))
61})

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.