bloggg
This commit is contained in:
+26
-23
@@ -1,18 +1,16 @@
|
||||
// blog.js – Loads and renders blog posts with simple "Read more" toggle for long entries
|
||||
// blog.js – Loads blog posts and uses a gradient fade 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);
|
||||
}
|
||||
@@ -20,7 +18,6 @@ function parseIndexHtmlList(html) {
|
||||
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' });
|
||||
@@ -39,7 +36,6 @@ async function loadPostList() {
|
||||
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}`);
|
||||
@@ -50,9 +46,7 @@ async function fetchPost(filename) {
|
||||
|
||||
function renderPost(filename, content) {
|
||||
const lines = content.split('\n');
|
||||
if (lines.length < 3) {
|
||||
return null; // malformed
|
||||
}
|
||||
if (lines.length < 3) return null;
|
||||
const dateTime = lines[0].trim();
|
||||
const title = lines[1].trim();
|
||||
const bodyLines = lines.slice(2);
|
||||
@@ -75,31 +69,41 @@ function renderPost(filename, content) {
|
||||
|
||||
const isLong = body.length > 300;
|
||||
|
||||
// Body container (always created, but hidden for long posts)
|
||||
// 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 (!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);
|
||||
// If long, add collapsed class and a toggle button
|
||||
if (isLong) {
|
||||
wrapper.classList.add('collapsed');
|
||||
|
||||
// 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';
|
||||
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;
|
||||
@@ -120,8 +124,7 @@ async function initBlog() {
|
||||
return;
|
||||
}
|
||||
|
||||
files.sort(); // alphabetical -> chronological
|
||||
|
||||
files.sort();
|
||||
feed.innerHTML = '';
|
||||
|
||||
for (const file of files) {
|
||||
|
||||
+28
-1
@@ -31,13 +31,39 @@ a:hover { color:#991b1b; text-decoration:underline; }
|
||||
.blog-title { margin:0 0 8px 0; font-size:1.5rem; }
|
||||
.blog-meta { margin:0 0 16px 0; color:#7f1d1d; font-size:0.95rem; }
|
||||
|
||||
/* ----- Blog body with fade-out ----- */
|
||||
.blog-body-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 0.5rem;
|
||||
/* No max-height by default (full) */
|
||||
}
|
||||
|
||||
.blog-body {
|
||||
white-space: pre-line;
|
||||
margin: 0 0 1em 0;
|
||||
margin: 0;
|
||||
}
|
||||
.blog-body p { margin:0 0 1em 0; }
|
||||
.blog-body p:last-child { margin-bottom:0; }
|
||||
|
||||
/* When collapsed, limit height and show gradient fade */
|
||||
.blog-body-wrapper.collapsed {
|
||||
max-height: 300px; /* ~6 lines, adjust as needed */
|
||||
overflow: hidden;
|
||||
/* The gradient overlay is done via ::after */
|
||||
}
|
||||
|
||||
.blog-body-wrapper.collapsed::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 80px; /* fade height */
|
||||
background: linear-gradient(to bottom, transparent, #fff);
|
||||
pointer-events: none; /* allow clicking through to the button */
|
||||
/* If your blog background is not white, change #fff to match */
|
||||
}
|
||||
|
||||
/* Toggle button */
|
||||
.blog-toggle {
|
||||
background: none;
|
||||
@@ -53,6 +79,7 @@ a:hover { color:#991b1b; text-decoration:underline; }
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Other existing styles (photography, modal, etc.) */
|
||||
.blog-attachment-list { margin:16px 0 0 0; padding:0; list-style:none; }
|
||||
.blog-attachment-list li { margin:0.5em 0; }
|
||||
.blog-attachment-list a { color:#dc2626; text-decoration:none; }
|
||||
|
||||
Reference in New Issue
Block a user