(() => { const WESTGATE_LATEST_CONFIG = [ { key: 'announcement', cid: 1, emptyTitle: 'No announcements yet', }, { key: 'patch', cid: 10, emptyTitle: 'No patch notes yet', }, ]; const getRelativePath = () => { return window.config && window.config.relative_path ? window.config.relative_path : ''; }; const unwrap = (payload) => { return payload && payload.response ? payload.response : payload; }; const textFromHtml = (html) => { const el = document.createElement('div'); el.innerHTML = html || ''; const firstParagraph = Array.from(el.querySelectorAll('p')) .map((p) => (p.textContent || '').replace(/\s+/g, ' ').trim()) .find(Boolean); return (firstParagraph || el.textContent || '').replace(/\s+/g, ' ').trim(); }; const truncate = (text, limit = 240) => { if (!text || text.length <= limit) { return text || ''; } return `${text.slice(0, limit).replace(/\s+\S*$/, '')}...`; }; const formatDate = (value) => { const timestamp = Number(value); if (!timestamp) { return ''; } const date = new Date(timestamp); if (Number.isNaN(date.getTime())) { return ''; } return new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', }).format(date); }; const getJSON = async (url) => { const response = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json', }, }); if (!response.ok) { throw new Error(`Request failed: ${response.status}`); } return unwrap(await response.json()); }; const getTopicExcerpt = async (topic) => { const relativePath = getRelativePath(); const topicPath = topic.slug || topic.tid; try { const topicData = await getJSON(`${relativePath}/api/topic/${topicPath}?page=1&sort=oldest_to_newest`); const firstPost = topicData.posts && topicData.posts[0]; const firstPostText = textFromHtml(firstPost && firstPost.content); if (firstPostText) { return truncate(firstPostText); } } catch (error) { console.warn('[Westgate latest widget original post excerpt]', error); } if (topic.mainPost && topic.mainPost.content) { const mainPostText = textFromHtml(topic.mainPost.content); if (mainPostText) { return truncate(mainPostText); } } if (topic.teaser && topic.teaser.content) { return truncate(textFromHtml(topic.teaser.content)); } return ''; }; const getLatestTopic = async (cid) => { const relativePath = getRelativePath(); const category = await getJSON(`${relativePath}/api/v3/categories/${cid}/topics?sort=recently_created&after=0`); const topic = category.topics && category.topics[0]; if (!topic) { return null; } let excerpt = ''; try { excerpt = await getTopicExcerpt(topic); } catch (error) { console.warn('[Westgate latest widget excerpt]', error); } return { title: topic.titleRaw || topic.title || 'Untitled', url: `${relativePath}/topic/${topic.slug || topic.tid}`, author: topic.user && (topic.user.displayname || topic.user.username), date: formatDate(topic.timestamp || topic.lastposttime), excerpt, }; }; const renderCard = async (root, source) => { const card = root.querySelector(`[data-card="${source.key}"]`); if (!card) { return; } const title = card.querySelector('.wg-latest-title'); const meta = card.querySelector('.wg-latest-meta'); const excerpt = card.querySelector('.wg-latest-excerpt'); card.classList.add('is-loading'); card.classList.remove('is-empty'); title.textContent = 'Loading...'; title.removeAttribute('href'); meta.textContent = ''; excerpt.textContent = ''; try { const topic = await getLatestTopic(source.cid); if (!topic) { card.classList.remove('is-loading'); card.classList.add('is-empty'); title.textContent = source.emptyTitle; return; } title.textContent = topic.title; title.href = topic.url; meta.textContent = [topic.author, topic.date].filter(Boolean).join(' ยท '); excerpt.textContent = topic.excerpt || ''; card.classList.remove('is-loading', 'is-empty'); } catch (error) { console.warn('[Westgate latest widget]', error); card.classList.remove('is-loading'); card.classList.add('is-empty'); title.textContent = source.emptyTitle; meta.textContent = 'Unable to load right now'; } }; const renderLatestStrip = () => { const root = document.querySelector('[data-westgate-latest]'); if (!root || root.dataset.loaded === '1') { return; } root.dataset.loaded = '1'; WESTGATE_LATEST_CONFIG.forEach((source) => renderCard(root, source)); }; document.addEventListener('DOMContentLoaded', renderLatestStrip); window.addEventListener('load', renderLatestStrip); if (window.jQuery) { window.jQuery(window).on('action:ajaxify.end', renderLatestStrip); } renderLatestStrip(); })();