/* The OC Guy — post feed. One manifest (data/posts.json) written by scripts/sync_posts.py, three views: Tips & Tricks, Community news and the Blog. Search is client-side over headline + teaser + category, the same technique as blog/index.json on odoo4projects.com. No JS, no feed: the sections say so instead of lying. */ (function () { 'use strict'; var SOURCE = 'data/posts.json'; var BADGE = { tips: 'Tips & Tricks', news: 'Community news', blog: 'Blog' }; function el(tag, cls, text) { var n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; } function fmtDate(iso) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var d = new Date(iso + 'T00:00:00Z'); if (isNaN(d.getTime())) return iso || ''; return d.getUTCDate() + ' ' + months[d.getUTCMonth()] + ' ' + d.getUTCFullYear(); } function matches(p, term) { if (!term) return true; var hay = (p.headline + ' ' + p.teaser + ' ' + p.category + ' ' + (p.tags || []).join(' ')).toLowerCase(); return hay.indexOf(term) !== -1; } function emptyState(node, kind) { node.textContent = ''; var box = el('p', 'feed-empty'); box.textContent = kind === 'news' ? 'No community news published yet. Posts from the daily feed land here.' : 'Nothing published in this section yet.'; node.appendChild(box); } function card(p) { var a = el('a', 'card'); a.href = p.link; if (p.image) { var img = el('img', 'card-img'); img.src = p.image; img.alt = ''; img.loading = 'lazy'; img.width = 1000; img.height = 666; a.appendChild(img); } a.appendChild(el('span', 'card-cat', BADGE[p.category] || p.category)); a.appendChild(el('h3', null, p.headline)); a.appendChild(el('p', 'card-desc', p.teaser)); a.appendChild(el('span', 'card-meta', fmtDate(p.date))); return a; } function newsRow(p) { var li = el('li'); var when = el('span', 'news-date'); var t = el('time'); t.dateTime = p.date; t.textContent = fmtDate(p.date); when.appendChild(t); var body = el('div', 'news-body'); var h3 = el('h3'); var a = el('a', null, p.headline); a.href = p.link; h3.appendChild(a); body.appendChild(h3); body.appendChild(el('p', null, p.teaser)); li.appendChild(when); li.appendChild(body); li.appendChild(el('span', 'news-source', p.category === 'tips' ? 'Tips & Tricks' : 'Community')); return li; } function render(view, posts, term) { var list = posts.filter(function (p) { return (view.all || p.category === view.category) && matches(p, term); }); var node = view.node; if (view.countBox) { view.countBox.textContent = list.length + (list.length === 1 ? ' post' : ' posts'); } if (!list.length) { emptyState(node, view.kind); return; } node.textContent = ''; if (view.kind === 'news') { list.slice(0, view.limit || 5).forEach(function (p) { node.appendChild(newsRow(p)); }); } else { list.slice(0, view.limit || 50).forEach(function (p) { node.appendChild(card(p)); }); } if (view.count) view.count.textContent = String(list.length); } function pickCategory(p) { var c = (p.category || 'blog').toLowerCase(); return BADGE[c] ? c : 'blog'; } fetch(SOURCE + '?_=' + Date.now()) .then(function (r) { if (!r.ok) throw new Error('http ' + r.status); return r.json(); }) .then(function (data) { var posts = (data && data.posts ? data.posts : []).map(function (p) { return { id: p.id, date: p.date, headline: p.headline, teaser: p.teaser || '', image: p.image || null, link: p.link || '#', category: pickCategory(p), tags: p.tags || [] }; }); var views = [ { node: document.getElementById('tipsGrid'), category: 'tips', kind: 'cards', search: document.getElementById('tipsSearch'), count: document.getElementById('tipsCount'), limit: 3 }, { node: document.getElementById('newsList'), category: 'news', kind: 'news', search: document.getElementById('newsSearch'), count: document.getElementById('newsCount'), limit: 6 }, { node: document.getElementById('blogGrid'), category: 'blog', kind: 'cards', all: true, search: document.getElementById('blogSearch'), count: document.getElementById('blogCount'), limit: 60 } ].filter(function (v) { return v.node; }); views.forEach(function (v) { v.term = ''; if (v.count) v.countBox = v.count.parentElement; render(v, posts, ''); if (v.search) { v.search.addEventListener('input', function () { v.term = this.value.trim().toLowerCase(); render(v, posts, v.term); }); } }); /* posts tagged both tips and blog are listed twice on purpose: the sections are channels, not storage. */ var total = document.getElementById('feedTotal'); if (total) total.textContent = String(posts.length); }) .catch(function () { ['tipsGrid', 'newsList', 'blogGrid'].forEach(function (id) { var n = document.getElementById(id); if (n) emptyState(n, id === 'newsList' ? 'news' : 'cards'); }); }); })();