Files
2026-07-12 10:15:22 +02:00

154 lines
4.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);