diff --git a/js/blog.js b/js/blog.js index 930ae75..fed1b6d 100644 --- a/js/blog.js +++ b/js/blog.js @@ -105,6 +105,56 @@ function formatDate(date) { }).format(date); } +// ---------- new preview logic ---------- +function getPreviewText(body) { + const maxLen = 300; + if (body.length <= maxLen) { + return { preview: body, isTruncated: false }; + } + + // Try to use first 3 non‑empty lines + const lines = body.split('\n').filter(line => line.trim() !== ''); + if (lines.length >= 3) { + const firstThree = lines.slice(0, 3); + const combined = firstThree.join('\n'); + if (combined.length < maxLen) { + return { preview: combined, isTruncated: true }; + } + } + + // Try to use first 3 sentences + const sentenceEnd = /[.!?]+\s*/g; + const sentences = []; + let match; + let lastIndex = 0; + while ((match = sentenceEnd.exec(body)) !== null && sentences.length < 3) { + const end = match.index + match[0].length; + const sentence = body.slice(lastIndex, end).trim(); + if (sentence) sentences.push(sentence); + lastIndex = end; + } + if (sentences.length >= 3) { + const combined = sentences.join(' '); + if (combined.length < maxLen) { + return { preview: combined, isTruncated: true }; + } + } + + // Fallback: take first maxLen chars, cut at last sentence boundary if possible + let cut = maxLen; + const firstPart = body.slice(0, maxLen); + const lastPunct = firstPart.search(/[.!?](?=\s|$)/g); + if (lastPunct !== -1 && lastPunct + 1 < maxLen) { + cut = lastPunct + 1; + } else { + // cut at last space + const lastSpace = firstPart.lastIndexOf(' '); + if (lastSpace > 0) cut = lastSpace; + } + return { preview: body.slice(0, cut) + '…', isTruncated: true }; +} +// --------------------------------------- + function createParagraphs(body) { return body .split(/\n{2,}/) @@ -122,6 +172,13 @@ function createParagraphs(body) { }); } +function renderPlainText(text) { + // Used for preview – shows text with line breaks, no attachment links + const escaped = escapeHtml(text); + const lines = escaped.split('\n'); + return lines.join('
'); +} + function renderBlogEntries(entries) { const container = document.querySelector('.blog-feed'); container.innerHTML = ''; @@ -143,17 +200,57 @@ function renderBlogEntries(entries) { meta.className = 'blog-meta'; meta.textContent = entry.date ? formatDate(entry.date) : entry.dateLabel || 'Date not provided'; - const body = document.createElement('div'); - body.className = 'blog-body'; + // ----- content area with preview & full ----- + const contentWrapper = document.createElement('div'); + contentWrapper.className = 'blog-content'; + + const { preview, isTruncated } = getPreviewText(entry.body); + + // Preview (only shown when truncated) + const previewDiv = document.createElement('div'); + previewDiv.className = 'blog-preview'; + if (isTruncated) { + previewDiv.innerHTML = renderPlainText(preview); + contentWrapper.appendChild(previewDiv); + } + + // Full body (always created, hidden initially if truncated) + const fullDiv = document.createElement('div'); + fullDiv.className = 'blog-full'; + if (isTruncated) { + fullDiv.style.display = 'none'; + } const paragraphs = createParagraphs(entry.body); if (!paragraphs.length) { const empty = document.createElement('p'); empty.textContent = 'No content yet.'; - body.appendChild(empty); + fullDiv.appendChild(empty); } else { - paragraphs.forEach((p) => body.appendChild(p)); + paragraphs.forEach((p) => fullDiv.appendChild(p)); + } + contentWrapper.appendChild(fullDiv); + + // Toggle link (only if truncated) + if (isTruncated) { + const toggle = document.createElement('a'); + toggle.href = '#'; + toggle.className = 'blog-toggle'; + toggle.textContent = 'Read more'; + toggle.addEventListener('click', (e) => { + e.preventDefault(); + const isExpanded = fullDiv.style.display !== 'none'; + fullDiv.style.display = isExpanded ? 'none' : 'block'; + previewDiv.style.display = isExpanded ? 'block' : 'none'; + toggle.textContent = isExpanded ? 'Read more' : 'Collapse'; + }); + contentWrapper.appendChild(toggle); } + article.appendChild(title); + article.appendChild(meta); + article.appendChild(contentWrapper); + + // Attachments (always visible) if (entry.attachments && entry.attachments.length) { const attachTitle = document.createElement('div'); attachTitle.className = 'blog-meta'; @@ -174,9 +271,6 @@ function renderBlogEntries(entries) { article.appendChild(list); } - article.appendChild(title); - article.appendChild(meta); - article.appendChild(body); container.appendChild(article); }); } @@ -215,4 +309,4 @@ async function initBlog() { renderBlogEntries(sortEntries(entries)); } -document.addEventListener('DOMContentLoaded', initBlog); +document.addEventListener('DOMContentLoaded', initBlog); \ No newline at end of file