Add article thumbnails (first image, max 256x256, bounded by teaser height) to post cards
This commit is contained in:
@@ -26,6 +26,8 @@ no external dependencies (except `highlight.js`, vendored in `lib/`).
|
|||||||
|
|
||||||
- Gruvbox light/dark theme for the share, with a theme toggle in the header.
|
- Gruvbox light/dark theme for the share, with a theme toggle in the header.
|
||||||
- Blog index, category pages, sub-categories, tags and tag overview.
|
- Blog index, category pages, sub-categories, tags and tag overview.
|
||||||
|
- **Article thumbnails**: each post card shows the first image of the article on the left
|
||||||
|
(max 256×256, bounded by the teaser height), summary on the right, lazy-loaded.
|
||||||
- **Pagination**: 10 posts per page on the index and category pages, with prev/next
|
- **Pagination**: 10 posts per page on the index and category pages, with prev/next
|
||||||
navigation between the pages; article prev/next links stay within the article's category.
|
navigation between the pages; article prev/next links stay within the article's category.
|
||||||
- Article metadata: date, category, summary, tags, prev/next navigation, reading time.
|
- Article metadata: date, category, summary, tags, prev/next navigation, reading time.
|
||||||
|
|||||||
@@ -101,6 +101,15 @@ function run() {
|
|||||||
return SHARE_BASE + "/" + encodeURIComponent("tag-" + slugifyTag(t));
|
return SHARE_BASE + "/" + encodeURIComponent("tag-" + slugifyTag(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function firstImageSrc(a) {
|
||||||
|
try {
|
||||||
|
const m = String(a.getContent() || "").match(/<img[^>]+src="([^"]+)"/);
|
||||||
|
return m ? m[1] : "";
|
||||||
|
} catch (e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderArticles(list) {
|
function renderArticles(list) {
|
||||||
const lines = [];
|
const lines = [];
|
||||||
for (const a of list) {
|
for (const a of list) {
|
||||||
@@ -109,6 +118,7 @@ function run() {
|
|||||||
const summary = a.getLabelValue("summary") || "";
|
const summary = a.getLabelValue("summary") || "";
|
||||||
const link = `${SHARE_BASE}/${encodeURIComponent(a.getLabelValue("shareAlias") || a.noteId)}`;
|
const link = `${SHARE_BASE}/${encodeURIComponent(a.getLabelValue("shareAlias") || a.noteId)}`;
|
||||||
const tags = tagList(a);
|
const tags = tagList(a);
|
||||||
|
const thumb = firstImageSrc(a);
|
||||||
|
|
||||||
lines.push('<article class="blog-post">');
|
lines.push('<article class="blog-post">');
|
||||||
lines.push(' <header class="blog-post-header">');
|
lines.push(' <header class="blog-post-header">');
|
||||||
@@ -118,10 +128,23 @@ function run() {
|
|||||||
if (cat) lines.push(` <span class="blog-post-category">${escapeHtml(cat)}</span>`);
|
if (cat) lines.push(` <span class="blog-post-category">${escapeHtml(cat)}</span>`);
|
||||||
lines.push(' </div>');
|
lines.push(' </div>');
|
||||||
lines.push(' </header>');
|
lines.push(' </header>');
|
||||||
|
if (summary || thumb) {
|
||||||
|
lines.push(' <div class="blog-post-inner">');
|
||||||
|
if (thumb) {
|
||||||
|
lines.push(` <a class="blog-thumb-link" href="${escapeHtml(link)}" tabindex="-1" aria-hidden="true">`);
|
||||||
|
lines.push(` <img class="blog-thumb" src="${escapeHtml(thumb)}" alt="" loading="lazy">`);
|
||||||
|
lines.push(' </a>');
|
||||||
|
}
|
||||||
|
lines.push(' <div class="blog-post-body">');
|
||||||
if (summary) lines.push(` <p class="blog-post-summary">${escapeHtml(summary)}</p>`);
|
if (summary) lines.push(` <p class="blog-post-summary">${escapeHtml(summary)}</p>`);
|
||||||
if (tags.length) {
|
if (tags.length) {
|
||||||
lines.push(' <div class="blog-post-tags">' + tags.map(t => `<a class="tag" href="${tagHref(t)}">#${escapeHtml(t)}</a>`).join(" ") + '</div>');
|
lines.push(' <div class="blog-post-tags">' + tags.map(t => `<a class="tag" href="${tagHref(t)}">#${escapeHtml(t)}</a>`).join(" ") + '</div>');
|
||||||
}
|
}
|
||||||
|
lines.push(' </div>');
|
||||||
|
lines.push(' </div>');
|
||||||
|
} else if (tags.length) {
|
||||||
|
lines.push(' <div class="blog-post-tags">' + tags.map(t => `<a class="tag" href="${tagHref(t)}">#${escapeHtml(t)}</a>`).join(" ") + '</div>');
|
||||||
|
}
|
||||||
lines.push('</article>');
|
lines.push('</article>');
|
||||||
}
|
}
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
|
|||||||
@@ -574,6 +574,34 @@ html.theme-light body.is-garden #content h1#title {
|
|||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Blog post thumbnails (Bild links, Teaser rechts) ---------- */
|
||||||
|
.blog-post-inner {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-thumb-link {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: min(256px, 35%);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-thumb {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 256px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--background-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.blog-post-title {
|
.blog-post-title {
|
||||||
margin: 0 0 0.25em;
|
margin: 0 0 0.25em;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user