I hope this works
This commit is contained in:
+22
-95
@@ -1,4 +1,4 @@
|
||||
// blog.js – Loads and renders blog posts with automatic collapse for long entries
|
||||
// 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';
|
||||
@@ -14,7 +14,6 @@ function parseIndexHtmlList(html) {
|
||||
const name = m[1];
|
||||
// accept typical text file extensions (and no extension)
|
||||
if (name.match(/\.(txt|md|text)$/i) || !name.includes('.')) {
|
||||
// avoid directory links (ending with /)
|
||||
if (!name.endsWith('/')) hrefs.push(name);
|
||||
}
|
||||
}
|
||||
@@ -47,44 +46,12 @@ async function fetchPost(filename) {
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
// generate preview: up to 3 sentences, but not exceeding ~300 chars
|
||||
function getPreview(text, maxLen = 300) {
|
||||
if (text.length <= maxLen) return text;
|
||||
|
||||
// try to split on sentence boundaries (. ! ?) followed by space or newline
|
||||
const sentences = text.match(/[^.!?]+[.!?]+/g) || [];
|
||||
let preview = '';
|
||||
let count = 0;
|
||||
for (const s of sentences) {
|
||||
if (preview.length + s.length <= maxLen) {
|
||||
preview += s;
|
||||
count++;
|
||||
if (count >= 3) break;
|
||||
} else {
|
||||
// if we haven't reached 3 sentences, break and use what we have
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have at least one sentence, use that; otherwise fallback to word‑break
|
||||
if (preview.trim().length > 0) {
|
||||
return preview.trim();
|
||||
}
|
||||
|
||||
// fallback: take first maxLen chars and cut at last space
|
||||
let truncated = text.slice(0, maxLen);
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
if (lastSpace > 0) truncated = truncated.slice(0, lastSpace);
|
||||
return truncated + '…';
|
||||
}
|
||||
|
||||
// ---------- rendering ----------
|
||||
|
||||
function renderPost(filename, content) {
|
||||
const lines = content.split('\n');
|
||||
if (lines.length < 3) {
|
||||
// malformed: skip or treat as title‑only
|
||||
return null;
|
||||
return null; // malformed
|
||||
}
|
||||
const dateTime = lines[0].trim();
|
||||
const title = lines[1].trim();
|
||||
@@ -94,13 +61,13 @@ function renderPost(filename, content) {
|
||||
const postDiv = document.createElement('div');
|
||||
postDiv.className = 'blog-post';
|
||||
|
||||
// title
|
||||
// Title
|
||||
const titleEl = document.createElement('h3');
|
||||
titleEl.className = 'blog-title';
|
||||
titleEl.textContent = title;
|
||||
postDiv.appendChild(titleEl);
|
||||
|
||||
// meta
|
||||
// Date
|
||||
const metaEl = document.createElement('p');
|
||||
metaEl.className = 'blog-meta';
|
||||
metaEl.textContent = dateTime;
|
||||
@@ -108,66 +75,31 @@ function renderPost(filename, content) {
|
||||
|
||||
const isLong = body.length > 300;
|
||||
|
||||
if (!isLong) {
|
||||
// short post – display full body directly
|
||||
// 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 – show preview with toggle
|
||||
const container = document.createElement('div');
|
||||
container.className = 'blog-collapsible';
|
||||
// Long post – hide body initially, add toggle button
|
||||
bodyDiv.style.display = 'none';
|
||||
postDiv.appendChild(bodyDiv);
|
||||
|
||||
// preview
|
||||
const previewDiv = document.createElement('div');
|
||||
previewDiv.className = 'blog-preview';
|
||||
previewDiv.style.whiteSpace = 'pre-line';
|
||||
let previewText = getPreview(body);
|
||||
// ensure ellipsis if preview is truncated
|
||||
if (previewText.length < body.length && !previewText.endsWith('…')) {
|
||||
previewText += '…';
|
||||
}
|
||||
previewDiv.textContent = previewText;
|
||||
container.appendChild(previewDiv);
|
||||
// Toggle button
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.className = 'blog-toggle';
|
||||
toggleBtn.textContent = 'Read more';
|
||||
postDiv.appendChild(toggleBtn);
|
||||
|
||||
// Read more button
|
||||
const readMoreBtn = document.createElement('button');
|
||||
readMoreBtn.className = 'blog-toggle read-more';
|
||||
readMoreBtn.textContent = 'Read more';
|
||||
container.appendChild(readMoreBtn);
|
||||
|
||||
// Full content (hidden initially)
|
||||
const fullDiv = document.createElement('div');
|
||||
fullDiv.className = 'blog-full';
|
||||
fullDiv.style.whiteSpace = 'pre-line';
|
||||
fullDiv.textContent = body;
|
||||
fullDiv.style.display = 'none';
|
||||
container.appendChild(fullDiv);
|
||||
|
||||
// Show less button (hidden initially) – appears at the bottom
|
||||
const showLessBtn = document.createElement('button');
|
||||
showLessBtn.className = 'blog-toggle show-less';
|
||||
showLessBtn.textContent = '✕ Show less';
|
||||
showLessBtn.style.display = 'none';
|
||||
container.appendChild(showLessBtn);
|
||||
|
||||
// Toggle logic
|
||||
function expand() {
|
||||
fullDiv.style.display = 'block';
|
||||
readMoreBtn.style.display = 'none';
|
||||
showLessBtn.style.display = 'inline-block';
|
||||
}
|
||||
function collapse() {
|
||||
fullDiv.style.display = 'none';
|
||||
readMoreBtn.style.display = 'inline-block';
|
||||
showLessBtn.style.display = 'none';
|
||||
}
|
||||
readMoreBtn.addEventListener('click', expand);
|
||||
showLessBtn.addEventListener('click', collapse);
|
||||
|
||||
postDiv.appendChild(container);
|
||||
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;
|
||||
@@ -188,11 +120,8 @@ async function initBlog() {
|
||||
return;
|
||||
}
|
||||
|
||||
// sort files by name (oldest first? we'll sort by date if embedded, but we'll just alphabetical)
|
||||
// Typically filenames like 2025-01-01.txt, so alphabetical = chronological
|
||||
files.sort();
|
||||
files.sort(); // alphabetical -> chronological
|
||||
|
||||
// clear feed
|
||||
feed.innerHTML = '';
|
||||
|
||||
for (const file of files) {
|
||||
@@ -202,7 +131,6 @@ async function initBlog() {
|
||||
if (el) feed.appendChild(el);
|
||||
} catch (err) {
|
||||
console.warn(`Failed to load ${file}:`, err);
|
||||
// optionally show error placeholder
|
||||
const errDiv = document.createElement('div');
|
||||
errDiv.className = 'blog-post';
|
||||
errDiv.style.color = '#b91c1c';
|
||||
@@ -211,7 +139,6 @@ async function initBlog() {
|
||||
}
|
||||
}
|
||||
|
||||
// if nothing was added, show message
|
||||
if (feed.children.length === 0) {
|
||||
feed.textContent = 'No valid blog posts found.';
|
||||
}
|
||||
|
||||
+23
-40
@@ -24,17 +24,40 @@ a:hover { color:#991b1b; text-decoration:underline; }
|
||||
#links li { margin:0; }
|
||||
#links a { display:block; padding:12px 16px; background:#fff; border:1px solid #e5e7eb; border-radius:6px; transition:all 0.3s ease; }
|
||||
#links a:hover { border-color:#dc2626; box-shadow:0 4px 12px rgba(220,38,38,0.15); }
|
||||
|
||||
.blog-feed { display:grid; gap:24px; margin-top:16px; }
|
||||
.blog-post { background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:24px; box-shadow:0 1px 6px rgba(139,18,18,0.08); }
|
||||
.blog-post + .blog-post { border-top:4px solid #dc2626; margin-top:32px; padding-top:32px; }
|
||||
.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 {
|
||||
white-space: pre-line;
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
.blog-body p { margin:0 0 1em 0; }
|
||||
.blog-body p:last-child { margin-bottom:0; }
|
||||
|
||||
/* Toggle button */
|
||||
.blog-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.95rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.blog-toggle:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.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; }
|
||||
.blog-attachment-list a:hover { text-decoration:underline; }
|
||||
|
||||
@media (max-width:520px){ h2 { font-size:1.4rem; } .site-header h1 { font-size:1.6rem; } .container { padding:16px; } }
|
||||
|
||||
.header-links { margin-top:12px; }
|
||||
@@ -55,43 +78,3 @@ a:hover { color:#991b1b; text-decoration:underline; }
|
||||
.modal-close { right:20px; top:20px; }
|
||||
.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; }
|
||||
|
||||
/* Collapsible blog post styles */
|
||||
.blog-collapsible {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.blog-preview {
|
||||
white-space: pre-line;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.blog-full {
|
||||
white-space: pre-line;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.blog-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #dc2626;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.blog-toggle:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.show-less {
|
||||
display: none; /* hidden by default, shown when expanded */
|
||||
}
|
||||
Reference in New Issue
Block a user