126 lines
4.4 KiB
JavaScript
126 lines
4.4 KiB
JavaScript
/* The OC Guy — post feed.
|
|
One manifest (data/posts.json) written by scripts/sync_posts.py, three views that are also
|
|
the three categories: Tips & Tricks, News, Modules. A post appears in exactly one of them,
|
|
because the category is the area. 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: 'News', modules: 'Modules' };
|
|
|
|
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 === 'modules'
|
|
? 'No module write-ups yet. Posts in the Modules category land here.'
|
|
: kind === 'news'
|
|
? 'No 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 render(view, posts, term) {
|
|
var list = posts.filter(function (p) {
|
|
return 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 = '';
|
|
list.slice(0, view.limit || 50).forEach(function (p) { node.appendChild(card(p)); });
|
|
}
|
|
|
|
function pickCategory(p) {
|
|
var c = (p.category || 'news').toLowerCase();
|
|
return BADGE[c] ? c : 'news';
|
|
}
|
|
|
|
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: 'tips',
|
|
search: document.getElementById('tipsSearch'),
|
|
count: document.getElementById('tipsCount'), limit: 3 },
|
|
{ node: document.getElementById('newsGrid'), category: 'news', kind: 'news',
|
|
search: document.getElementById('newsSearch'),
|
|
count: document.getElementById('newsCount'), limit: 60 },
|
|
{ node: document.getElementById('modulesGrid'), category: 'modules', kind: 'modules',
|
|
search: null, count: null, limit: 6 }
|
|
].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);
|
|
});
|
|
}
|
|
});
|
|
|
|
var total = document.getElementById('feedTotal');
|
|
if (total) total.textContent = String(posts.length);
|
|
})
|
|
.catch(function () {
|
|
['tipsGrid', 'newsGrid', 'modulesGrid'].forEach(function (id) {
|
|
var n = document.getElementById(id);
|
|
if (n) emptyState(n, id.replace('Grid', '').toLowerCase());
|
|
});
|
|
});
|
|
})();
|