From 37200da79e2d544f2c4c4b149e295bcdfa5569e4 Mon Sep 17 00:00:00 2001 From: organic-CPU Date: Sat, 11 Jul 2026 23:01:25 +0200 Subject: [PATCH] oops fixed --- js/blog.js | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 6 +++ 2 files changed, 132 insertions(+) diff --git a/js/blog.js b/js/blog.js index 5817b6a..cdd50a5 100644 --- a/js/blog.js +++ b/js/blog.js @@ -1,3 +1,129 @@ +const BLOG_DIR = 'blog/'; +const ATTACHMENT_DIR = BLOG_DIR + 'files/'; +const MANIFEST = BLOG_DIR + 'manifest.json'; +const TXT_EXT = /\.(txt|text)$/i; +const ATTACHMENT_RE = /\[\[(?:file|attachment):\s*([^\]]+)\]\]/gi; +const BLOG_COLLAPSE_THRESHOLD = 300; // characters before collapsing +const BLOG_PREVIEW_LINES = 3; // number of lines shown in preview + +function escapeHtml(value) { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function parseIndexHtmlList(text) { + const hrefs = []; + const re = /href="([^"]+)"/g; + let m; + while ((m = re.exec(text)) !== null) { + const name = m[1]; + if (TXT_EXT.test(name)) hrefs.push(name); + } + return hrefs; +} + +async function loadBlogFiles() { + try { + const response = await fetch(MANIFEST, { cache: 'no-cache' }); + if (response.ok) { + return await response.json(); + } + } catch (e) { + // manifest missing or unreadable + } + + try { + const response = await fetch(BLOG_DIR, { cache: 'no-cache' }); + if (response.ok) { + const text = await response.text(); + return parseIndexHtmlList(text); + } + } catch (e) { + console.warn('Could not read blog directory listing', e); + } + + return []; +} + +function parseDateLine(rawDate) { + if (!rawDate) return null; + let value = rawDate.trim(); + if (value.toLowerCase().startsWith('date:')) { + value = value.slice(5).trim(); + } + if (!value) return null; + const normalized = value.replace(' ', 'T'); + const date = new Date(normalized); + return Number.isNaN(date.getTime()) ? null : date; +} + +function extractAttachments(body) { + const attachments = new Set(); + body.replace(ATTACHMENT_RE, (_, filename) => { + const safeName = filename.trim(); + if (safeName) attachments.add(safeName); + return ''; + }); + return Array.from(attachments); +} + +function parseBlogText(text, filename) { + const lines = text.replace(/\r/g, '').split('\n'); + let index = 0; + while (index < lines.length && !lines[index].trim()) index += 1; + + const rawDate = index < lines.length ? lines[index].trim() : ''; + if (rawDate.toLowerCase().startsWith('date:')) { + index += 1; + } else { + index += 1; + } + + while (index < lines.length && !lines[index].trim()) index += 1; + const title = index < lines.length ? lines[index].trim() : 'Untitled'; + index += 1; + + while (index < lines.length && !lines[index].trim()) index += 1; + const body = lines.slice(index).join('\n').trim(); + + return { + filename, + title: title || 'Untitled', + date: parseDateLine(rawDate) || null, + dateLabel: rawDate || '', + body: body || '', + attachments: extractAttachments(body), + }; +} + +function formatDate(date) { + if (!date) return 'Unknown date'; + return new Intl.DateTimeFormat('en-US', { + year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' + }).format(date); +} + +function createParagraphs(body) { + return body + .split(/\n{2,}/) + .filter(Boolean) + .map((paragraph) => { + const p = document.createElement('p'); + const text = escapeHtml(paragraph).replace(ATTACHMENT_RE, (match, filename) => { + const safeName = filename.trim(); + const url = ATTACHMENT_DIR + encodeURI(safeName); + return `${escapeHtml(safeName)}`; + }); + const lines = text.split('\n'); + p.innerHTML = lines.join('
'); + return p; + }); +} + function renderBlogEntries(entries) { const container = document.querySelector('.blog-feed'); container.innerHTML = ''; diff --git a/styles.css b/styles.css index 2e897f7..4535cf0 100644 --- a/styles.css +++ b/styles.css @@ -56,3 +56,9 @@ a:hover { color:#991b1b; text-decoration:underline; } .modal-prev { left:20px; top:50%; transform:translateY(-50%); } .modal-next { right:20px; top:50%; transform:translateY(-50%); } +.blog-collapse summary { cursor: pointer; list-style: none; } +.blog-collapse summary::-webkit-details-marker { display: none; } +.blog-preview { white-space: pre-line; margin-bottom: 0.5rem;} +.read-more-text {display: inline-block; font-weight: bold; margin-top: 0.5rem; } +.read-more-text:hover { text-decoration: underline; } +.blog-collapse[open] .read-more-text { margin-bottom: 1rem; } \ No newline at end of file