Files
Website-2026/js/blog.js
T
2026-07-12 00:06:22 +02:00

151 lines
4.1 KiB
JavaScript
Raw 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 and renders blog posts with simple "Read more" toggle for long entries
const BLOG_DIR = 'blog/';
const MANIFEST = BLOG_DIR + 'manifest.json';
// ---------- helpers ----------
// crude HTML parser for directory listings (when manifest is missing)
function parseIndexHtmlList(html) {
const hrefs = [];
const re = /href="([^"]+)"/g;
let m;
while ((m = re.exec(html)) !== null) {
const name = m[1];
// accept typical text file extensions (and no extension)
if (name.match(/\.(txt|md|text)$/i) || !name.includes('.')) {
if (!name.endsWith('/')) hrefs.push(name);
}
}
return hrefs;
}
// fetch list of blog post filenames (manifest first, fallback to directory listing)
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 [];
}
// fetch a single post file
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; // malformed
}
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;
// Body container (always created, but hidden for long posts)
const bodyDiv = document.createElement('div');
bodyDiv.className = 'blog-body';
bodyDiv.style.whiteSpace = 'pre-line';
bodyDiv.textContent = body;
if (!isLong) {
// Short post show body directly
postDiv.appendChild(bodyDiv);
} else {
// Long post hide body initially, add toggle button
bodyDiv.style.display = 'none';
postDiv.appendChild(bodyDiv);
// Toggle button
const toggleBtn = document.createElement('button');
toggleBtn.className = 'blog-toggle';
toggleBtn.textContent = 'Read more';
postDiv.appendChild(toggleBtn);
toggleBtn.addEventListener('click', function() {
const isHidden = bodyDiv.style.display === 'none';
bodyDiv.style.display = isHidden ? 'block' : 'none';
this.textContent = isHidden ? 'Show less' : 'Read more';
});
}
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(); // alphabetical -> chronological
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);