PageSourceSearch

https://smartinnovationnorway.com/wp-content/themes/wp-theme-sin-n…?ver=2.117.0&sb-version=1790065487

js smartinnovationnorway.com collected 2026-09-26 00:45:49 UTC 4,642 bytes, 153 lines download raw bytes

1/**
2 * Scroll-spy for the Yoast table of contents.
3 *
4 * A static list answers "what is on this page". This answers "where am I",
5 * which is the difference between a module that feels part of the page and one
6 * that feels dropped onto it. Yoast ships no such behaviour, so the theme adds
7 * it around the block's own markup — nothing here depends on Yoast internals
8 * beyond the anchors it already writes.
9 *
10 * Degrades to a plain list of links: without JS, or without IntersectionObserver,
11 * every link still jumps to its heading. Only the highlight is lost.
12 */
13( function () {
14	'use strict';
15
16	var toc = document.querySelector( '.yoast-table-of-contents' );
17
18	if ( ! toc || ! ( 'IntersectionObserver' in window ) ) {
19		return;
20	}
21
22	/*
23	 * Only same-document anchors, and only ones whose target actually exists.
24	 * Yoast writes the list from the headings at save time, so a heading that
25	 * was renamed or deleted afterwards leaves a link pointing at nothing —
26	 * observing null would throw and take the whole highlight with it.
27	 */
28	var links = [];
29
30	Array.prototype.forEach.call( toc.querySelectorAll( 'a[href*="#"]' ), function ( link ) {
31		var id = link.hash ? link.hash.slice( 1 ) : '';
32
33		if ( ! id ) {
34			return;
35		}
36
37		var target;
38
39		/* getElementById over a selector: Yoast's ids start with a digit often
40		   enough (a heading like "2026 in review") that querySelector would
41		   throw on the invalid selector. */
42		try {
43			target = document.getElementById( decodeURIComponent( id ) );
44		} catch ( e ) {
45			target = document.getElementById( id );
46		}
47
48		if ( target ) {
49			links.push( { link: link, target: target } );
50		}
51	} );
52
53	if ( links.length < 2 ) {
54		/* One heading is not a table of contents, and highlighting the only
55		   entry says nothing. */
56		return;
57	}
58
59	var current = null;
60
61	function setCurrent( entry ) {
62		if ( entry === current ) {
63			return;
64		}
65
66		if ( current ) {
67			current.link.classList.remove( 'is-current' );
68			current.link.removeAttribute( 'aria-current' );
69		}
70
71		current = entry;
72
73		if ( current ) {
74			current.link.classList.add( 'is-current' );
75			/* `location` rather than `true`: this marks the reader's position
76			   within the page, not the current page in a set of pages. */
77			current.link.setAttribute( 'aria-current', 'location' );
78		}
79	}
80
81	/*
82	 * Which heading counts as "current" is decided by position, not by what the
83	 * observer happens to report. The observer is only a cheap way to be told
84	 * that something moved — asking the DOM afterwards is what makes the answer
85	 * correct when several headings share the viewport, or when none does
86	 * because a long section fills it.
87	 */
88	function recompute() {
89		/* The sticky header covers the top of the viewport, so the line that
90		   decides "have I reached this heading" sits below it. --sin-header-h is
91		   published by header-scroll.js; the fallback matches its resting
92		   height, and the same value is what scroll-margin-top uses. */
93		var headerH = parseFloat(
94			getComputedStyle( document.documentElement ).getPropertyValue( '--sin-header-h' )
95		) || 84;
96		var line = headerH + 24;
97		var found = null;
98
99		for ( var i = 0; i < links.length; i++ ) {
100			if ( links[ i ].target.getBoundingClientRect().top <= line ) {
101				found = links[ i ];
102			} else {
103				break;
104			}
105		}
106
107		/*
108		 * Above the first heading, nothing is highlighted rather than the first
109		 * link being highlighted pre-emptively — the reader has not reached it,
110		 * and saying they have is worse than saying nothing.
111		 *
112		 * At the very bottom the last section can be too short to ever cross the
113		 * line, so the page can end with the second-to-last link lit. Reaching
114		 * the bottom means you are in the last section, whatever the arithmetic
115		 * says.
116		 */
117		if ( window.innerHeight + window.scrollY >= document.body.offsetHeight - 2 ) {
118			found = links[ links.length - 1 ];
119		}
120
121		setCurrent( found );
122	}
123
124	var ticking = false;
125
126	function onScroll() {
127		if ( ticking ) {
128			return;
129		}
130
131		ticking = true;
132		window.requestAnimationFrame( function () {
133			recompute();
134			ticking = false;
135		} );
136	}
137
138	/* The observer fires on the crossings; the scroll listener covers the long
139	   drifts between them. Both funnel into the same rAF-throttled recompute, so
140	   the cost is one layout read per frame at most. */
141	var observer = new IntersectionObserver( onScroll, {
142		threshold: [ 0, 1 ],
143	} );
144
145	links.forEach( function ( entry ) {
146		observer.observe( entry.target );
147	} );
148
149	window.addEventListener( 'scroll', onScroll, { passive: true } );
150	window.addEventListener( 'resize', onScroll, { passive: true } );
151
152	recompute();
153}() );

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.