PageSourceSearch

https://www.ijcit.org/wp-content/themes/ijcit-child/assets/js/main.js

js ijcit.org collected 2026-10-05 19:07:30 UTC 14,758 bytes, 309 lines download raw bytes

1(function () {
2	'use strict';
3
4	/* =========================================================================
5	   Mobile menu toggle
6	   ========================================================================= */
7	var hb = document.querySelector('.ij-topnav__hamburger');
8	var mobile = document.getElementById('ij-mobile');
9	if (hb && mobile) {
10		hb.addEventListener('click', function () {
11			var open = mobile.hasAttribute('hidden');
12			if (open) {
13				mobile.removeAttribute('hidden');
14				hb.setAttribute('aria-expanded', 'true');
15			} else {
16				mobile.setAttribute('hidden', '');
17				hb.setAttribute('aria-expanded', 'false');
18			}
19		});
20	}
21
22	/* =========================================================================
23	   Page-bottom reading progress (Carbon-native variant)
24	   ========================================================================= */
25	var progressFill = document.querySelector('.ij-progress__fill');
26	if (progressFill) {
27		var ticking = false;
28		function update() {
29			var doc = document.documentElement;
30			var max = doc.scrollHeight - window.innerHeight;
31			if (max <= 0) { progressFill.style.width = '0%'; ticking = false; return; }
32			var p = Math.max(0, Math.min(1, window.scrollY / max));
33			progressFill.style.width = (p * 100).toFixed(1) + '%';
34			ticking = false;
35		}
36		window.addEventListener('scroll', function () {
37			if (!ticking) { requestAnimationFrame(update); ticking = true; }
38		}, { passive: true });
39		update();
40	}
41
42	/* =========================================================================
43	   TOC scroll-spy
44	   ========================================================================= */
45	var tocLinks = Array.prototype.slice.call(document.querySelectorAll('.ij-toc__item a'));
46	if (tocLinks.length) {
47		var sections = tocLinks
48			.map(function (a) { return document.getElementById(a.dataset.toc); })
49			.filter(Boolean);
50		function setActive() {
51			var pos = window.scrollY + 120;
52			var idx = -1;
53			for (var i = 0; i < sections.length; i++) {
54				if (sections[i].offsetTop <= pos) idx = i;
55			}
56			tocLinks.forEach(function (a, i) {
57				a.parentElement.classList.toggle('is-active', i === idx);
58			});
59		}
60		window.addEventListener('scroll', setActive, { passive: true });
61		setActive();
62	}
63
64	/* =========================================================================
65	   Theme toggle (Carbon Gray-100 dark mode)
66	   ========================================================================= */
67	var themeBtn = document.querySelector('.ij-theme-toggle');
68	if (themeBtn) {
69		themeBtn.addEventListener('click', function () {
70			var current = document.documentElement.getAttribute('data-theme') || 'light';
71			var next = current === 'dark' ? 'light' : 'dark';
72			document.documentElement.setAttribute('data-theme', next);
73			try { localStorage.setItem('ij-theme', next); } catch (e) {}
74			themeBtn.setAttribute('aria-pressed', next === 'dark' ? 'true' : 'false');
75		});
76		var t = document.documentElement.getAttribute('data-theme');
77		themeBtn.setAttribute('aria-pressed', t === 'dark' ? 'true' : 'false');
78	}
79
80	/* =========================================================================
81	   Glossary auto-wrap (IT acronyms → <abbr> with hover tooltip)
82	   ========================================================================= */
83	var GLOSSARY = {
84		// Healthcare IT
85		'EHR':    'Electronic Health Record — the digital version of a patient\'s paper chart.',
86		'EMR':    'Electronic Medical Record — single-practice digital chart.',
87		'HIPAA':  'Health Insurance Portability and Accountability Act — US healthcare data-privacy law (1996).',
88		'HITECH': 'Health Information Technology for Economic and Clinical Health Act — US 2009.',
89		'PHI':    'Protected Health Information — patient data covered by HIPAA.',
90		'FHIR':   'Fast Healthcare Interoperability Resources — HL7 healthcare-data exchange standard.',
91		'HL7':    'Health Level Seven — international healthcare interoperability standards body.',
92		'NHS':    'National Health Service — UK publicly-funded healthcare system.',
93		'ICB':    'Integrated Care Board — NHS regional commissioning body (England).',
94		'RPM':    'Remote Patient Monitoring.',
95		'IoMT':   'Internet of Medical Things — networked medical devices.',
96		'mHealth':'Mobile health — health services delivered via mobile devices.',
97		'CDSS':   'Clinical Decision Support System.',
98		'PACS':   'Picture Archiving and Communication System — medical imaging.',
99		'HIE':    'Health Information Exchange.',
100		// Finance IT
101		'PSD2':   'Payment Services Directive 2 — EU open-banking regulation (2018).',
102		'PSD3':   'Payment Services Directive 3 — EU successor to PSD2 (in legislative process).',
103		'SEPA':   'Single Euro Payments Area — EU/EEA euro payment standard.',
104		'KYC':    'Know Your Customer — identity-verification compliance.',
105		'AML':    'Anti-Money Laundering — financial crime compliance.',
106		'CDD':    'Customer Due Diligence — KYC sub-process.',
107		'DORA':   'Digital Operational Resilience Act — EU financial-sector IT-resil
107ience regulation (2025).',
108		'SWIFT':  'Society for Worldwide Interbank Financial Telecommunication — interbank messaging network.',
109		'ISO20022':'ISO 20022 — modern financial-messaging standard, replacing legacy SWIFT MT.',
110		'FIX':    'Financial Information eXchange — electronic trading protocol.',
111		'BNPL':   'Buy Now Pay Later — deferred-payment lending product.',
112		'DeFi':   'Decentralised Finance — blockchain-based financial services.',
113		'CeFi':   'Centralised Finance — traditional intermediated financial services.',
114		'CBDC':   'Central Bank Digital Currency.',
115		'FX':     'Foreign Exchange.',
116		// Cross-sector regulatory
117		'GDPR':   'General Data Protection Regulation — EU data-protection law (2018).',
118		'NIS2':   'Network and Information Security Directive 2 — EU cybersecurity (2024).',
119		'CCPA':   'California Consumer Privacy Act.',
120		'ICO':    'Information Commissioner\'s Office — UK data-protection regulator.',
121		'CISO':   'Chief Information Security Officer.',
122		'CIO':    'Chief Information Officer.',
123		'CTO':    'Chief Technology Officer.',
124		'CDO':    'Chief Data Officer.',
125		'CPO':    'Chief Privacy Officer.',
126		'COO':    'Chief Operating Officer.',
127		'SaaS':   'Software as a Service.',
128		'PaaS':   'Platform as a Service.',
129		'IaaS':   'Infrastructure as a Service.',
130		'BaaS':   'Banking as a Service.',
131		// Architecture / infra
132		'API':    'Application Programming Interface.',
133		'SDK':    'Software Development Kit.',
134		'REST':   'Representational State Transfer — web API architectural style.',
135		'gRPC':   'gRPC Remote Procedure Calls — modern RPC framework (Google).',
136		'JWT':    'JSON Web Token — compact signed token for auth.',
137		'MFA':    'Multi-Factor Authentication.',
138		'2FA':    'Two-Factor Authentication.',
139		'SSO':    'Single Sign-On.',
140		'IAM':    'Identity and Access Management.',
141		'RBAC':   'Role-Based Access Control.',
142		'ABAC':   'Attribute-Based Access Control.',
143		'TLS':    'Transport Layer Security — encryption protocol for HTTPS and other traffic.',
144		'mTLS':   'Mutual TLS — both client and server authenticate.',
145		'VPN':    'Virtual Private Network.',
146		'SIEM':   'Security Information and Event Management.',
147		'SOC':    'Security Operations Center.',
148		'EDR':    'Endpoint Detection and Response.',
149		'XDR':    'Extended Detection and Response.',
150		'ZTA':    'Zero Trust Architecture.',
151		'SBOM':   'Software Bill of Materials.',
152		'CVE':    'Common Vulnerabilities and Exposures — public vulnerability catalogue.',
153		'RTO':    'Recovery Time Objective — disaster-recovery target.',
154		'RPO':    'Recovery Point Objective — data-loss tolerance target.',
155		// Data / AI
156		'ML':     'Machine Learning.',
157		'LLM':    'Large Language Model.',
158		'RAG':    'Retrieval-Augmented Generation.',
159		'ETL':    'Extract, Transform, Load — data-pipeline pattern.',
160		'ELT':    'Extract, Load, Transform — modern data-pipeline pattern.',
161		'CDC':    'Change Data Capture — incremental data-sync pattern.',
162		'ML Ops': 'Machine Learning Operations — productionising ML systems.'
163	};
164
165	// Build a sorted-by-length regex so longer acronyms match before shorter substrings
166	var keys = Object.keys(GLOSSARY).sort(function (a, b) { return b.length - a.length; });
167	var glossRegex = new RegExp('\\b(' + keys.map(function (k) { return k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }).join('|') + ')\\b', 'g');
168
169	function wrapGlossary(root) {
170		var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
171			acceptNode: function (node) {
172				// Skip text inside abbr, code, pre, script, style, a, headings, and existing glossary spans
173				if (!node.parentElement) return NodeFilter.FILTER_REJECT;
174				var p = node.parentElement;
175				var skip = ['ABBR','CODE','PRE','SCRIPT','STYLE','A','H1','H2','H3','H4','H5','H6','BUTTON','INPUT','TEXTAREA','SELECT','LABEL'];
176				if (skip.indexOf(p.tagName) !== -1) return NodeFilter.FILTER_REJECT;
177				if (p.closest && (p.closest('.ij-toc') || p.closest('.ij-search') || p.closest('.ij-takeaways__head') || p.closest('.ij-callout__head'))) return NodeFilter.FILTER_REJECT;
178				return glossRegex.test(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
179			}
180		});
181		var nodes = [];
182		var n;
183		while ((n = walker.nextNode())) nodes.push(n);
184		nodes.forEach(function (node) {
185			glossRegex.lastIndex = 0;
186			var html = node.nodeValue.replace(glossRegex, function (match) {
187				var def = GLOSSARY[match];
188				if (!def) return match;
189				return '<abbr class="ij-gloss" data-def="' + def.replace(/"/g, '&quot;') + '" title="' + def.replace(/"/g, '&quot;') + '" tabindex="0" role="term">' + match + '</abbr>';
190			});
191			if (html !== node.nodeValue) {
192				var span = document.createElement('span');
193				span.innerHTML = html;
194				node.parentNode.replaceChild(span, node);
195				// Unwrap the placeholder span (preserve original structure)
196				while (span.firstChild) span.parentNode.insertBefore(span.firstChild, span);
197				span.parentNode.removeChild(span);
198			}
199		});
200	}
201
202	var articleBody = document.querySelector('.ij-article__body');
203	if (articleBody) wrapGlossary(articleBody);
204
205	/* =========================================================================
206	   Search overlay (hybrid keyword + vertical filter)
207	   ========================================================================= */
208	var searchEl  = document.getElementById('ij-search');
209	var searchBtn = document.querySelector('.ij-search-toggle');
210	if (searchEl && searchBtn) {
211		var input   = searchEl.querySelector('.ij-search__input');
212		var closeBt = searchEl.querySelector('.ij-search__close');
213		var resBox  = searchEl.querySelector('.ij-search__results');
214		var filters = Array.prototype.slice.call(searchEl.querySelectorAll('.ij-search__filter'));
215		var indexJSON = document.getElementById('ij-search-index');
216		var index = [];
217		try { index = JSON.parse(indexJSON ? indexJSON.textContent : '[]'); } catch (e) {}
218		var activeFilter = 'all';
219		var activeIdx = -1;
220
221		function openSearch() {
222			searchEl.removeAttribute('hidden');
223			searchBtn.setAttribute('aria-expanded', 'true');
224			document.body.style.overflow = 'hidden';
225			setTimeout(function () { input.focus(); }, 50);
226			render();
227		}
228		function closeSearch() {
229			searchEl.setAttribute('hidden', '');
230			searchBtn.setAttribute('aria-expanded', 'false');
231			document.body.style.overflow = '';
232			input.value = '';
233			activeIdx = -1;
234		}
235		function render() {
236			var q = input.value.trim().toLowerCase();
237			var matches = index.filter(function (p) {
238				if (activeFilter !== 'all' && p.vert !== activeFilter) return false;
239				if (!q) return true;
240				var hay = (p.title + ' ' + (p.sum || '') + ' ' + (p.vert || '')).toLowerCase();
241				return q.split(/\s+/).every(function (term) { return hay.indexOf(term) !== -1; });
242			}).slice(0, 30);
243			activeIdx = -1;
244			if (!matches.length) {
245				resBox.innerHTML = '<p class="ij-search__empty">No briefings match. Try a broader term or switch the vertical filter.</p>';
246				return;
247			}
248			resBox.innerHTML = matches.map(function (p, i) {
249				return '<a class="ij-search__result" href="' + p.url + '" data-i="' + i + '">' +
250					'<span class="ij-search__result-vert">' + (p.vert || '') + '</span>' +
251					'<span class="ij-search__result-title">' + p.title + '</span>' +
252					'<span class="ij-search__result-sum">' + (p.sum || '').slice(0, 140) + '</span>' +
253					'</a>';
254			}).join('');
255		}
256		function highlight() {
257			var items = resBox.querySelectorAll('.ij-search__result');
258			items.forEach(function (it, i) { it.classList.toggle('is-active', i === activeIdx); });
259			if (activeIdx >= 0 && items[activeIdx]) items[activeIdx].scrollIntoView({ block: 'nearest' });
260		}
261
262		searchBtn.addEventListener('click', openSearch);
263		closeBt.addEventListener('click', closeSearch);
264		searchEl.addEventListener('click', function (e) { if (e.target === searchEl) closeSearch(); });
265		input.addEventListener('input', render);
266		filters.forEach(function (f) {
267			f.addEventListener('click', function () {
268				activeFilter = f.dataset.filter;
269				filters.forEach(function (x) {
270					x.classList.toggle('is-active', x === f);
271					x.setAttribute('aria-selected', x === f ? 'true' : 'false');
272				});
273				render();
274			});
275		});
276		document.addEventListener('keydown', function (e) {
277			// Global open on /
278			if ((e.key === '/' || (e.key === 'k' && (e.metaKey || e.ctrlKey))) && searchEl.hasAttribute('hidden')) {
279				e.preventDefault();
280				openSearch();
281				return;
282			}
283			if (searchEl.hasAttribute('hidden')) return;
284			if (e.key === 'Escape') { closeSearch(); return; }
285			var items = resBox.querySelectorAll('.ij-search__result');
286			if (!items.length) return;
287			if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = (activeIdx + 1) % items.length; highlight(); }
288			if (e.key === 'ArrowUp')   { e.preventDefault(); activeIdx = (activeIdx - 1 + items.length) % items.length; highlight(); }
289			if (e.key === 'Enter' && activeIdx >= 0) { e.preventDefault(); window.location = items[activeIdx].href; }
290		});
291	}
292
293	/* =========================================================================
294	   Section-anchor hover-to-copy (H2/H3 with id)
295	   ========================================================================= */
296	if (articleBody) {
297		articleBody.addEventListener('click', function (e) {
298			var h = e.target.closest('h2[id], h3[id]');
299			if (!h) return;
300			// only when clicking the ::before "#"
301			var rect = h.getBoundingClientRect();
302			if (e.clientX < rect.left) {
303				var url = window.location.origin + window.location.pathname + '#' + h.id;
304				if (navigator.clipboard) navigator.clipboard.writeText(url);
305				history.replaceState(null, '', '#' + h.id);
306			}
307		});
308	}
309})();

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.