1(function(){ 2 const articles = (window.POSTIQ_HELP_ARTICLES || []).slice(); 3 const bySlug = new Map(articles.map(a => [a.slug, a])); 4 5 const homeEl = document.getElementById('helpHome'); 6 const articleEl = document.getElementById('articleView'); 7 const gridEl = document.getElementById('articlesGrid'); 8 const recommendedEl = document.getElementById('recommendedWrap'); 9 10 const supportModal = document.getElementById('supportModal'); 11 const openSupportBtn = document.getElementById('openSupportBtn'); 12 const closeSupportBtn = document.getElementById('closeSupportBtn'); 13 14 function escapeHtml(value){ 15 return String(value).replace(/[&<>'"]/g, (c) => ({ '&':'&','<':'<','>':'>',"'":''','"':'"' }[c])); 16 } 17 18 function articleCard(article, opts){ 19 const featuredClass = opts.featured ? ' featured' : ''; 20 return `<a class="article-card${featuredClass}" href="?article=${encodeURIComponent(article.slug)}" data-open-article="${article.slug}" aria-label="Read guide: ${escapeHtml(article.title)}"><h3 class="article-title">${article.title}</h3><p class="article-summary">${article.summary}</p><span class="read-link">Read guide â</span></a>`; 21 } 22 23 function renderRecommended(){ 24 const featured = articles.filter(a => a.featured === true).slice(0, 3); 25 recommendedEl.innerHTML = featured.length 26 ? `<div class="section-head">Start here</div><div class="featured-grid">${featured.map(item => articleCard(item, { featured: true })).join('')}</div>` 27 : ''; 28 return new Set(featured.map(f => f.slug)); 29 } 30 31 function renderHome(){ 32 const hiddenSlugs = renderRecommended(); 33 const baseList = articles.filter(a => !hiddenSlugs.has(a.slug)); 34 gridEl.innerHTML = `<section><h2 class="section-head">All guides</h2><div class="articles-grid">${baseList.map(a => articleCard(a, { featured: false })).join('')}</div></section>`; 35 } 36 37 function renderArticle(slug){ 38 const article = bySlug.get(slug); 39 if (!article){ articleEl.hidden = true; homeEl.hidden = false; renderHome(); return; } 40 const related = (article.relatedArticles || []).map(rs => bySlug.get(rs)).filter(Boolean); 41 articleEl.innerHTML = `<h2>${article.title}</h2><p class="meta">${article.category}</p><div class="article-body">${article.body}</div><div class="related-list">${related.length ? `<h3>Related guides</h3>${related.map(r => `<a href="?article=${encodeURIComponent(r.slug)}" data-open-article="${r.slug}">${r.title}</a>`).join('')}` : ''}</div><div class="article-nav"><a class="btn-link" href="/help/" data-open-home="true">â Back to all guides</a><a class="btn-link" href="/app.html">Back to PostIQ</a></div>`; 42 homeEl.hidden = true; 43 articleEl.hidden = false; 44 window.scrollTo({ top: 0, behavior: 'instant' }); 45 } 46 47 function applyRoute(){ 48 const slug = new URLSearchParams(window.location.search).get('article'); 49 if (slug) renderArticle(slug); else { articleEl.hidden = true; homeEl.hidden = false; renderHome(); } 50 } 51 52 function openSupportModal() { 53 if (!supportModal) return; 54 supportModal.classList.remove('hidden'); 55 supportModal.classList.add('is-open'); 56 supportModal.setAttribute('aria-hidden', 'false'); 57 document.body.style.overflow = 'hidden'; 58 closeSupportBtn?.focus(); 59 } 60 61 function closeSupportModal() { 62 if (!supportModal) return; 63 supportModal.classList.add('hidden'); 64 supportModal.classList.remove('is-open'); 65 supportModal.setAttribute('aria-hidden', 'true'); 66 document.body.style.overflow = ''; 67 } 68 69 openSupportBtn?.addEventListener('click', openSupportModal); 70 closeSupportBtn?.addEventListener('click', closeSupportModal); 71 72 supportModal?.addEventListener('click', function(event) { 73 if (event.target === supportModal) closeSupportModal(); 74 }); 75 76 document.addEventListener('keydown', function(event) { 77 if (event.key === 'Escape' && supportModal && supportModal.classList.contains('is-open')) closeSupportModal(); 78 }); 79 80 document.addEventListener('click', (e) => { 81 const articleLink = e.target.closest('[data-open-article]'); 82 if (articleLink){ e.preventDefault(); history.pushState({}, '', `?article=${encodeURIComponent(articleLink.dataset.openArticle)}`); renderArticle(articleLink.dataset.openArticle); return; } 83 const homeLink = e.target.closest('[data-open-home]'); 84 if (homeLink){ e.preventDefault(); history.pushState({}, '', '/help/');
84 applyRoute(); } 85 }); 86 87 window.addEventListener('popstate', applyRoute); 88 applyRoute(); 89})();
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.