PageSourceSearch

https://livenewschat.eu/wp-content/themes/lnc-editorial/assets/js/main.js?ver=1790531174

js livenewschat.eu collected 2026-10-02 17:47:13 UTC 11,483 bytes, 254 lines download raw bytes

1/**
2 * LNC Editorial — front-end behaviors.
3 *  - Mobile nav toggle
4 *  - Dark mode toggle (persists to localStorage)
5 *  - Share buttons
6 *  - (Market ticker moved to assets/js/ticker.js 2026-09-06; conditionally
7 *    enqueued by inc/enqueue.php when lnc_ticker_is_active() returns true.
8 *    Nothing here depends on it.)
9 */
10(function () {
11	'use strict';
12
13	const $ = (s, r = document) => r.querySelector(s);
14	const $$ = (s, r = document) => Array.from(r.querySelectorAll(s));
15
16	/* ---------- 1. Mobile nav -------------------------------------- */
17	const navToggle = $('[data-nav-toggle]');
18	const primaryNav = $('#primary-nav');
19	if (navToggle && primaryNav) {
20		const closeNav = () => {
21			if (!primaryNav.classList.contains('is-open')) return;
22			primaryNav.classList.remove('is-open');
23			navToggle.setAttribute('aria-expanded', 'false');
24		};
25		navToggle.addEventListener('click', () => {
26			const open = primaryNav.classList.toggle('is-open');
27			navToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
28		});
29		document.addEventListener('keydown', e => {
30			if (e.key === 'Escape') closeNav();
31		});
32		primaryNav.addEventListener('click', e => {
33			if (e.target.closest('a')) closeNav();
34		});
35	}
36
37	/* ---------- 1b. Dropdown parents on touch screens ---------------
38	   Above 820px the More panel opens on :hover / :focus-within. On a touch screen
39	   that wide (a tablet in landscape) one tap fires hover AND click together, so
40	   the browser followed More's own link before the panel could be used: Sports,
41	   Crypto, Elections and the rest were unreachable. On touch, the first tap now
42	   opens the panel and a second tap follows the link. Mouse and keyboard are
43	   unchanged. At 820px and below the sub-menu is always shown inline in the
44	   hamburger menu, so this does nothing there. */
45	const dropdownParents = $$('.primary-nav .menu-item-has-children');
46	if (dropdownParents.length) {
47		const wideNav = window.matchMedia('(min-width: 821px)');
48		const touchFirst = window.matchMedia('(hover: none), (pointer: coarse)');
49		const closeDropdowns = keep => dropdownParents.forEach(li => {
50			if (li === keep || !li.classList.contains('is-open')) return;
51			li.classList.remove('is-open');
52			const a = li.querySelector(':scope > a');
53			if (a) a.removeAttribute('aria-expanded');
54		});
55		dropdownParents.forEach(li => {
56			const link = li.querySelector(':scope > a');
57			if (!link) return;
58			link.addEventListener('click', e => {
59				if (!wideNav.matches || !touchFirst.matches) return;
60				if (li.classList.contains('is-open')) return;   // second tap: follow the link
61				e.preventDefault();
62				closeDropdowns(li);
63				li.classList.add('is-open');
64				// Only set while tap-opened, so it never claims "collapsed" to a
65				// mouse or keyboard user who is looking at the hover/focus panel.
66				link.setAttribute('aria-expanded', 'true');
67			});
68		});
69		document.addEventListener('click', e => {
70			if (!e.target.closest('.primary-nav .menu-item-has-children')) closeDropdowns(null);
71		});
72		document.addEventListener('keydown', e => {
73			if (e.key === 'Escape') closeDropdowns(null);
74		});
75	}
76
77	/* ---------- 2. Theme ----------------------------------------------
78	   DARK ONLY since 2026-09-12, fixed server-side as <html data-theme="dark">
79	   in header.php. This section used to read the saved light/dark choice from
80	   localStorage and apply it - but main.js runs after first paint, so a phone
81	   in light mode drew the page light and then snapped to dark. That was the
82	   flash. The toggle is gone; clear the stored key so it cannot linger. */
83	try { localStorage.removeItem('lnc-theme'); } catch {}
84
85	/* ---------- 3. Orphaned table-of-contents blocks ---------------
86	   (Section 3 used to be the old template's `.share-btn` row. That row was removed
87	   from single.php on 2026-09-07 - the real share bar comes from inc/share-bar.php -
88	   so its listener went with it.)
89
90	   16 posts and pages still carry the rendered HTML of a Spectra / UAGB
91	   "Table of Contents" block. The plugin itself is GONE - only litespeed-cache and
92	   lnc-video-ads are active - so the markup ships with no stylesheet and n
92o script:
93
94	     - the chevron is an <svg viewBox="0 0 384 512"> with NO width or height, so with
95	       no CSS to size it, it stretched to fill the column. Measured on
96	       /jack-smith-testimony-trump-caused-jan-6th/: 924 x 1232 CSS pixels.
97	     - the panel had no stylesheet, so `.uagb-toc__list-wrap` got no `display` rule of
98	       its own and the whole list rendered open and unstyled. (An earlier note here
99	       claimed the list carries `uagb-toc__list-hidden`. Measured 2026-09-09 across the
100	       served HTML of all 16 rows: that class is on exactly one of them.)
101
102	   Rather than strip the block out of 16 posts' content, the theme adopts it: main.css
103	   sizes the icon and the panel, and this makes the toggle work. The markup is inert
104	   HTML now, ours to style. If the blocks are ever removed from the content, all of
105	   this becomes dead code and can go with them. */
106	$$('.wp-block-uagb-table-of-contents').forEach(toc => {
107		const title = toc.querySelector('.uagb-toc__title');
108		const wrap  = toc.querySelector('.uagb-toc__list-wrap');
109		if (!title || !wrap) return;
110
111		/* Start collapsed - on every page, unconditionally.
112
113		   This line used to read `!toc.querySelector('.uagb-toc__list-hidden')`. Measured
114		   2026-09-09 on the served HTML of all 16 published rows carrying this block:
115		   `uagb-toc__list-hidden` appears on one row, and the plugin's real collapsed
116		   marker `uagb-toc__collapse` appears on fifteen - on the ROOT element, not a
117		   descendant, so querySelector() could never match it and classList.contains()
118		   is what reads it. So the old probe matched on exactly one row - post 2201,
119		   where the class really does sit on the descendant `.uagb-toc__list-wrap` -
120		   and that page alone already loaded collapsed. On the other fifteen startOpen
121		   was true and the TOC opened, pushing the ad pod and the player down the page
122		   (CNN desktop measured 2026-09-09: TOC 901px tall, pod y=1852, player y=2145;
123		   production's pod sits at y=795).
124
125		   Honouring the markers rather than hard-coding false would still leave one page
126		   open: post 1891, /10-best-ai-story-video-generator-tools-for-story-driven-content/,
127		   carries neither marker. Spectra is uninstalled, so a marker is a fossil of an
128		   editor nobody runs, not an editorial choice - and the requirement is that every
129		   TOC starts closed. So the theme decides, and the answer is closed. main.css
130		   already agrees: `.uagb-toc__list-wrap` is display:none until `.is-open` is added,
131		   so the pre-JS paint is collapsed too and there is no open-then-collapse flash.
132
133		   `wrap.classList.remove(...)` below still matters for the one row that carries
134		   the class: without it, opening that page's TOC would re-expose a class the
135		   plugin's stylesheet used to hide. */
136		const startOpen = false;
137		wrap.classList.remove('uagb-toc__list-hidden');
138		toc.classList.toggle('is-open', startOpen);
139
140		// It was a plain <div>. Make it a real control so keyboard and AT can reach it.
141		title.setAttribute('role', 'button');
142		title.setAttribute('tabindex', '0');
143		title.setAttribute('aria-expanded', String(startOpen));
144		const panelId = wrap.id || ('lnc-toc-panel-' + Math.random().toString(36).slice(2, 8));
145		wrap.id = panelId;
146		title.setAttribute('aria-controls', panelId);
147
148		const toggle = () => {
149			const open = !toc.classList.contains('is-open');
150			toc.classList.toggle('is-open', open);
151			title.setAttribute('aria-expanded', String(open));
152		};
153		title.addEventListener('click', toggle);
154		title.addEventListener('keydown', e => {
155			if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); }
156		});
157	});
158
159	/* ---------- 5. Decorate external article links ----------------- */
160	// Anything in article content that leaves this origin gets target="_blank".
161	$$('.article__content a[href^="http"]').forEach(a => {
162		try {
163			const u = new URL(a.href);
164			if (u.origin !== location.origin) {
165				a.target = '_blank';
166				a.rel = 'noopener noreferrer';
167			}
168		} catch {}
169	});
170
171	/* ---------- 6. Live-nav scroll arrows --------------------------- */
172	const liveScroll = $('[data-live-scroll]');
173	if (liveScroll) {
174		const arrows = $$('[data-scroll]');
175		const step = 320;
176		const updateArrows = () => {
177			const maxScroll = liveScroll.scrollWidth - liveScroll.clientWidth;
178			arrows.forEach(a => {
179				const dir = parseInt(a.dataset.scroll, 10);
180				if (dir < 0) a.disabled = liveScroll.scrollLeft <= 2;
181				if (dir > 0) a.disabled = liveScroll.scrollLeft >= maxScroll - 2;
182			});
183		};
184		arrows.forEach(a => {
185			a.addEventListener('click', () => {
186				const dir = parseInt(a.dataset.scroll, 10);
187				liveScroll.scrollBy({ left: dir * step, behavior: 'smooth' });
188			});
189		});
190		liveScroll.addEventListener('scroll', updateArrows, { passive: true });
191		window.addEventListener('resize', updateArrows);
192		updateArrows();
193	}
194
195	/* ---------- 7. Streams drawer (Watch Live) --------------------- */
196	const drawer = $('#streams-drawer');
197	if (drawer) {
198		const openers = $$('[data-drawer-open="streams"]');
199		const closers = $$('[data-drawer-close]', drawer);
200
201		/* FINDING 38. The drawer declared aria-modal="true" but nothing actually
202		   contained Tab, so a keyboard user tabbed straight out of an element the
203		   markup told assistive tech was modal - and the opener never reported
204		   aria-expanded, so there was no way to know the drawer had opened at all.
205		   Escape, focus-on-open and focus-restore-on-close were already correct and
206		   are left alone. */
207		const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]),' +
208			'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
209		const focusables = () =>
210			$$(FOCUSABLE, drawer).filter(el => el.offsetWidth || el.offsetHeight || el.getClientRects().length);
211
212		const setExpanded = state => openers.forEach(o => o.setAttribute('aria-expanded', state ? 'true' : 'false'));
213
214		let lastFocus = null;
215		const open = () => {
216			lastFocus = document.activeElement;
217			drawer.removeAttribute('hidden');
218			document.body.style.overflow = 'hidden';
219			setExpanded(true);
220			// Focus the close button for a11y
221			const close = drawer.querySelector('.streams-drawer__close');
222			if (close) close.focus();
223		};
224		const close = () => {
225			drawer.setAttribute('hidden', '');
226			document.body.style.overflow = '';
227			setExpanded(false);
228			if (lastFocus && lastFocus.focus) lastFocus.focus();
229		};
230
231		openers.forEach(o => o.addEventListener('click', open));
232		closers.forEach(c => c.addEventListener('click', close));
233		document.addEventListener('keydown', e => {
234			if (drawer.hasAttribute('hidden')) return;
235			if (e.key === 'Escape') { close(); return; }
236			if (e.key !== 'Tab') return;
237
238			/* The trap. Wrap at both ends, and pull focus back in if it has somehow
239			   escaped (e.g. the drawer was opened while focus sat outside it). */
240			const items = focusables();
241			if (!items.length) return;
242			const first = items[0];
243			const last = items[items.length - 1];
244			if (!drawer.contains(document.activeElement)) {
245				e.preventDefault(); first.focus(); return;
246			}
247			if (e.shiftKey && document.activeElement === first) {
248				e.preventDefault(); last.focus();
249			} else if (!e.shiftKey && document.activeElement === last) {
250				e.preventDefault(); first.focus();
251			}
252		});
253	}
254})();

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.