154 lines
4.0 KiB
JavaScript
154 lines
4.0 KiB
JavaScript
// blog.js – Loads blog posts and uses a gradient fade for long entries
|
||
|
||
const BLOG_DIR = 'blog/';
|
||
const MANIFEST = BLOG_DIR + 'manifest.json';
|
||
|
||
// ---------- helpers ----------
|
||
|
||
function parseIndexHtmlList(html) {
|
||
const hrefs = [];
|
||
const re = /href="([^"]+)"/g;
|
||
let m;
|
||
while ((m = re.exec(html)) !== null) {
|
||
const name = m[1];
|
||
if (name.match(/\.(txt|md|text)$/i) || !name.includes('.')) {
|
||
if (!name.endsWith('/')) hrefs.push(name);
|
||
}
|
||
}
|
||
return hrefs;
|
||
}
|
||
|
||
async function loadPostList() {
|
||
try {
|
||
const r = await fetch(MANIFEST, { cache: 'no-cache' });
|
||
if (r.ok) return await r.json();
|
||
} catch (_) { /* ignore */ }
|
||
|
||
try {
|
||
const r = await fetch(BLOG_DIR, { cache: 'no-cache' });
|
||
if (r.ok) {
|
||
const html = await r.text();
|
||
return parseIndexHtmlList(html);
|
||
}
|
||
} catch (e) {
|
||
console.warn('Could not read blog directory listing', e);
|
||
}
|
||
return [];
|
||
}
|
||
|
||
async function fetchPost(filename) {
|
||
const res = await fetch(BLOG_DIR + filename, { cache: 'no-cache' });
|
||
if (!res.ok) throw new Error(`Failed to fetch ${filename}`);
|
||
return await res.text();
|
||
}
|
||
|
||
// ---------- rendering ----------
|
||
|
||
function renderPost(filename, content) {
|
||
const lines = content.split('\n');
|
||
if (lines.length < 3) return null;
|
||
const dateTime = lines[0].trim();
|
||
const title = lines[1].trim();
|
||
const bodyLines = lines.slice(2);
|
||
const body = bodyLines.join('\n').trim();
|
||
|
||
const postDiv = document.createElement('div');
|
||
postDiv.className = 'blog-post';
|
||
|
||
// Title
|
||
const titleEl = document.createElement('h3');
|
||
titleEl.className = 'blog-title';
|
||
titleEl.textContent = title;
|
||
postDiv.appendChild(titleEl);
|
||
|
||
// Date
|
||
const metaEl = document.createElement('p');
|
||
metaEl.className = 'blog-meta';
|
||
metaEl.textContent = dateTime;
|
||
postDiv.appendChild(metaEl);
|
||
|
||
const isLong = body.length > 300;
|
||
|
||
// Container for the body (with optional fade)
|
||
const wrapper = document.createElement('div');
|
||
wrapper.className = 'blog-body-wrapper';
|
||
|
||
// The actual text
|
||
const bodyDiv = document.createElement('div');
|
||
bodyDiv.className = 'blog-body';
|
||
bodyDiv.style.whiteSpace = 'pre-line';
|
||
bodyDiv.textContent = body;
|
||
wrapper.appendChild(bodyDiv);
|
||
|
||
// If long, add collapsed class and a toggle button
|
||
if (isLong) {
|
||
wrapper.classList.add('collapsed');
|
||
|
||
const toggleBtn = document.createElement('button');
|
||
toggleBtn.className = 'blog-toggle';
|
||
toggleBtn.textContent = 'Read more';
|
||
|
||
toggleBtn.addEventListener('click', function() {
|
||
const isCollapsed = wrapper.classList.contains('collapsed');
|
||
if (isCollapsed) {
|
||
wrapper.classList.remove('collapsed');
|
||
this.textContent = 'Show less';
|
||
} else {
|
||
wrapper.classList.add('collapsed');
|
||
this.textContent = 'Read more';
|
||
}
|
||
});
|
||
|
||
postDiv.appendChild(wrapper);
|
||
postDiv.appendChild(toggleBtn);
|
||
} else {
|
||
// Short post – no collapse, no button
|
||
postDiv.appendChild(wrapper);
|
||
}
|
||
|
||
return postDiv;
|
||
}
|
||
|
||
// ---------- main ----------
|
||
|
||
async function initBlog() {
|
||
const feed = document.querySelector('.blog-feed');
|
||
if (!feed) return;
|
||
|
||
feed.textContent = 'Loading blog posts…';
|
||
|
||
try {
|
||
const files = await loadPostList();
|
||
if (!files.length) {
|
||
feed.textContent = 'No blog posts found.';
|
||
return;
|
||
}
|
||
|
||
files.sort();
|
||
feed.innerHTML = '';
|
||
|
||
for (const file of files) {
|
||
try {
|
||
const content = await fetchPost(file);
|
||
const el = renderPost(file, content);
|
||
if (el) feed.appendChild(el);
|
||
} catch (err) {
|
||
console.warn(`Failed to load ${file}:`, err);
|
||
const errDiv = document.createElement('div');
|
||
errDiv.className = 'blog-post';
|
||
errDiv.style.color = '#b91c1c';
|
||
errDiv.textContent = `⚠️ Could not load post: ${file}`;
|
||
feed.appendChild(errDiv);
|
||
}
|
||
}
|
||
|
||
if (feed.children.length === 0) {
|
||
feed.textContent = 'No valid blog posts found.';
|
||
}
|
||
} catch (err) {
|
||
console.error('Blog init error:', err);
|
||
feed.textContent = 'Error loading blog posts.';
|
||
}
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', initBlog); |