From 713bb6dd58f4e4bb1db3ca1b8e56e385016b06ea Mon Sep 17 00:00:00 2001
From: Patrick Kollitsch <83281+davidsneighbour@users.noreply.github.com>
Date: Sun, 07 Jun 2026 03:47:19 +0000
Subject: [PATCH] fix: allow images in list summary cards (#971)
---
layouts/summary-with-image.html | 6 +
scripts/test-hugo-quickstart.ts | 174 ++++++++++++++++++++++++++++++++++
layouts/post/list.html | 7 +
layouts/post/summary.html | 9 +
layouts/summary.html | 6 +
layouts/single.html | 14 --
layouts/list.html | 7 +
layouts/_partials/func/ShowDate.html | 31 ++++++
8 files changed, 235 insertions(+), 19 deletions(-)
diff --git a/layouts/_partials/func/ShowDate.html b/layouts/_partials/func/ShowDate.html
new file mode 100644
index 0000000..9b784fa
--- /dev/null
+++ b/layouts/_partials/func/ShowDate.html
@@ -0,0 +1,31 @@
+{{/*
+ ShowDate
+
+ Decides whether a page's date should be rendered.
+
+ Dates are shown by default. They are hidden when the site sets
+ `ananke.pages.show_date = false`, unless a page opts back in with
+ `ananke.show_date = true` in its front matter. A page can also hide its
+ own date with `ananke.show_date = false`. Pages without a date
+ (`.Date.IsZero`) never render one.
+
+ This mirrors the date logic used in `single.html` so that single pages and
+ summary cards stay consistent.
+
+ @param . The page context.
+ @return boolean True when the date should be displayed.
+
+*/}}
+{{ return and
+ (not .Date.IsZero)
+ (or
+ (and
+ (not (eq false site.Params.ananke.pages.show_date))
+ (not (eq false .Params.ananke.show_date))
+ )
+ (and
+ (eq false site.Params.ananke.pages.show_date)
+ (eq true .Params.ananke.show_date)
+ )
+ )
+}}
diff --git a/layouts/list.html b/layouts/list.html
index 7de098b..a015d51 100644
--- a/layouts/list.html
+++ b/layouts/list.html
@@ -1,4 +1,9 @@
{{ define "main" }}
+ {{ $summary_template := "summary" }}
+ {{ if $.Param "ananke.pages.show_list_images" }}
+ {{ $summary_template = "summary-with-image" }}
+ {{ end }}
+
<article class="pa3 pa4-ns nested-copy-line-height">
<section class="cf ph3 ph5-l pv3 pv4-l f4 tc-l center measure-wide lh-copy nested-links {{ $.Param "text_color" | compare.Default "mid-gray" }}">
{{- .Content -}}
@@ -6,7 +11,7 @@
<section class="flex-ns mt5 flex-wrap justify-around">
{{ range .Paginator.Pages }}
<div class="w-100 w-30-l mb4 relative bg-white">
- {{ .Render "summary" }}
+ {{ .Render $summary_template }}
</div>
{{ end }}
</section>{{/* former internal template */}}
diff --git a/layouts/post/list.html b/layouts/post/list.html
index 7be0cdd..c9fb0da 100644
--- a/layouts/post/list.html
+++ b/layouts/post/list.html
@@ -2,6 +2,11 @@
{{/*
This template is the same as the default and is here to demonstrate that if you have a content directory called "post" you can create a layouts directory, just for that section.
*/}}
+ {{ $summary_template := "summary" }}
+ {{ if $.Param "ananke.pages.show_list_images" }}
+ {{ $summary_template = "summary-with-image" }}
+ {{ end }}
+
<article class="pa3 pa4-ns nested-copy-line-height">
<section class="cf ph3 ph5-l pv3 pv4-l f4 tc-l center measure-wide lh-copy nested-links {{ $.Param "text_color" | compare.Default "mid-gray" }}">
{{ .Content }}
@@ -9,7 +14,7 @@
<aside class="flex-ns mt5 flex-wrap justify-around">
{{ range .Paginator.Pages }}
<div class="w-100 w-30-l mb4 relative bg-white">
- {{ .Render "summary" }}
+ {{ .Render $summary_template }}
</div>
{{ end }}
</aside>
diff --git a/layouts/post/summary.html b/layouts/post/summary.html
index 651642b..4236aa8 100644
--- a/layouts/post/summary.html
+++ b/layouts/post/summary.html
@@ -1,8 +1,9 @@
+ {{ $show_date := partials.Include "func/ShowDate.html" . }}
<div class="mb3 pa4 {{ $.Param "text_color" | compare.Default "mid-gray" }} overflow-hidden">
- {{ if .Date }}
- <div class="f6">
- {{ .Date | time.Format (compare.Default "January 2, 2006" .Site.Params.date_format) }}
- </div>
+ {{ if $show_date }}
+ <time class="f6 db" {{ fmt.Printf `datetime="%s"` (.Date.Format "2006-01-02T15:04:05Z07:00") | safe.HTMLAttr }}>
+ {{- .Date | time.Format (compare.Default "January 2, 2006" .Site.Params.date_format) -}}
+ </time>
{{ end }}
<h1 class="f3 near-black">
<a href="{{ .RelPermalink }}" class="link black dim">
diff --git a/layouts/single.html b/layouts/single.html
index e80393b..c7422bf 100644
--- a/layouts/single.html
+++ b/layouts/single.html
@@ -36,19 +36,7 @@
</p>
{{ end }}
{{/* Hugo uses Go's date formatting is set by example. Here are two formats */}}
- {{ if and
- (not .Date.IsZero)
- (or
- (and
- (not (eq false site.Params.ananke.pages.show_date))
- (not (eq false .Params.ananke.show_date))
- )
- (and
- (eq false site.Params.ananke.pages.show_date)
- (eq true .Params.ananke.show_date)
- )
- )
- }}
+ {{ if partials.Include "func/ShowDate.html" . }}
<time class="f6 mv4 dib tracked" {{ fmt.Printf `datetime="%s"` (.Date.Format "2006-01-02T15:04:05Z07:00") | safe.HTMLAttr }}>
{{- .Date | time.Format (compare.Default "January 2, 2006" .Site.Params.date_format) -}}
</time>
diff --git a/layouts/summary-with-image.html b/layouts/summary-with-image.html
index 33bbce3..5084742 100644
--- a/layouts/summary-with-image.html
+++ b/layouts/summary-with-image.html
@@ -1,4 +1,5 @@
{{ $featured_image := partials.Include "func/GetFeaturedImage.html" . }}
+{{ $show_date := partials.Include "func/ShowDate.html" . }}
<article class="bb b--black-10">
<div class="db pv4 ph3 ph0-l dark-gray no-underline">
<div class="flex-column flex-row-ns flex">
@@ -11,6 +12,11 @@
</div>
{{ end }}
<div class="blah w-100{{ if $featured_image }} w-60-ns {{ compare.Conditional (compare.Eq $.Site.Language.Direction "rtl") "pr3-ns" "pl3-ns" }}{{ end }}">
+ {{ if $show_date }}
+ <time class="f6 db" {{ fmt.Printf `datetime="%s"` (.Date.Format "2006-01-02T15:04:05Z07:00") | safe.HTMLAttr }}>
+ {{- .Date | time.Format (compare.Default "January 2, 2006" .Site.Params.date_format) -}}
+ </time>
+ {{ end }}
<h1 class="f3 fw1 athelas mt0 lh-title">
<a href="{{.RelPermalink}}" class="color-inherit dim link">
{{ .Title }}
diff --git a/layouts/summary.html b/layouts/summary.html
index a2d5d30..52b0517 100644
--- a/layouts/summary.html
+++ b/layouts/summary.html
@@ -1,6 +1,12 @@
+{{ $show_date := partials.Include "func/ShowDate.html" . }}
<div class="w-100 mb4 nested-copy-line-height relative bg-white">
<div class="mb3 pa4 gray overflow-hidden bg-white">
{{with .CurrentSection.Title }}<span class="f6 db">{{ . }}</span>{{end}}
+ {{ if $show_date }}
+ <time class="f6 db" {{ fmt.Printf `datetime="%s"` (.Date.Format "2006-01-02T15:04:05Z07:00") | safe.HTMLAttr }}>
+ {{- .Date | time.Format (compare.Default "January 2, 2006" .Site.Params.date_format) -}}
+ </time>
+ {{ end }}
<h1 class="f3 near-black">
<a href="{{ .RelPermalink }}" class="link black dim">
{{ .Title }}
diff --git a/scripts/test-hugo-quickstart.ts b/scripts/test-hugo-quickstart.ts
index d80d64d..1eddccd 100644
--- a/scripts/test-hugo-quickstart.ts
+++ b/scripts/test-hugo-quickstart.ts
@@ -809,6 +809,109 @@
}
/**
+ * Markup only emitted by `summary-with-image.html` when a featured image is
+ * rendered, used to detect that list cards switched to the image template.
+ */
+const LIST_CARD_IMAGE_MARKER = 'class="img"';
+
+/**
+ * Markup emitted by the summary templates when a card date is rendered.
+ */
+const SUMMARY_CARD_DATE_MARKER = "datetime=";
+
+/**
+ * Create a content section whose list page exercises both the image and the
+ * date behaviour of summary cards: one page has a featured image, the other
+ * does not, and both carry an explicit date.
+ *
+ * @param contentDir Absolute path to the project `content` directory.
+ */
+async function writeListCardFixtures(contentDir: string): Promise<void> {
+ const sectionDir = join(contentDir, "cards");
+ await mkdir(sectionDir, { recursive: true });
+
+ const sectionIndex = ["+++", "title = 'Cards'", "+++", "", ""].join("\n");
+
+ const withImage = [
+ "+++",
+ "title = 'Card With Image'",
+ "date = 2024-01-15T00:00:00Z",
+ "featured_image = '/images/card-hero.jpg'",
+ "+++",
+ "",
+ "Card body.",
+ "",
+ ].join("\n");
+
+ const withoutImage = [
+ "+++",
+ "title = 'Card Without Image'",
+ "date = 2024-02-20T00:00:00Z",
+ "+++",
+ "",
+ "Card body.",
+ "",
+ ].join("\n");
+
+ await writeTextFile(join(sectionDir, "_index.md"), sectionIndex);
+ await writeTextFile(join(sectionDir, "with-image.md"), withImage);
+ await writeTextFile(join(sectionDir, "without-image.md"), withoutImage);
+}
+
+/**
+ * Assert that a list page renders summary cards with the expected image and
+ * date behaviour.
+ *
+ * @param listHtml HTML from the rendered list page.
+ * @param label Human-readable description of the configuration under test.
+ * @param expectations Whether image cards and dates are expected in the output.
+ * @throws Error when the rendered cards do not match the expectations.
+ */
+function assertListCardSummaries(
+ listHtml: string,
+ label: string,
+ expectations: { images: boolean; dates: boolean },
+): void {
+ const failures: string[] = [];
+ const hasImage = listHtml.includes(LIST_CARD_IMAGE_MARKER);
+ const hasDate = listHtml.includes(SUMMARY_CARD_DATE_MARKER);
+
+ if (expectations.images && !hasImage) {
+ failures.push(
+ "- expected image summary cards but the list rendered no card image",
+ );
+ }
+
+ if (!expectations.images && hasImage) {
+ failures.push(
+ "- image summary cards were rendered when 'ananke.pages.show_list_images' was not enabled",
+ );
+ }
+
+ if (expectations.dates && !hasDate) {
+ failures.push(
+ "- expected summary card dates but none were rendered by default",
+ );
+ }
+
+ if (!expectations.dates && hasDate) {
+ failures.push(
+ "- summary card dates were rendered when 'ananke.pages.show_date' was false",
+ );
+ }
+
+ if (failures.length > 0) {
+ throw new Error(
+ [
+ `Strict assertion failed: list card summaries (${label}) did not behave as expected.`,
+ "Failed assertions:",
+ ...failures,
+ ].join("\n"),
+ );
+ }
+}
+
+/**
* Determine whether a directory is the work tree of a Git repository.
*
* @param path Absolute directory path.
@@ -1236,6 +1339,77 @@
await assertHeaderSectionClassConfigurable(projectRoot);
console.log("[OK ] Configurable hero header section class (issue #504)");
+ console.log("\n[RUN] List page image cards and summary dates (issue #217)");
+ await writeListCardFixtures(join(projectRoot, "content"));
+ const cardsListPath = join(
+ projectRoot,
+ "public",
+ "cards",
+ "index.html",
+ );
+
+ // Default configuration: no image cards, but dates show by default.
+ await writeTextFile(
+ configPath,
+ [
+ "baseURL = 'https://example.com/'",
+ "title = 'Ananke Test Quickstart'",
+ `theme = '${options.themeName}'`,
+ "",
+ ].join("\n"),
+ );
+ const cardsDefaultBuildStep: StepDefinition = {
+ name: "Build site with default summary cards",
+ command: "hugo",
+ args: [],
+ cwd: projectRoot,
+ expectedFiles: ["public/cards/index.html"],
+ };
+ const cardsDefaultBuildReport = await executeHugoBuildStep(
+ cardsDefaultBuildStep,
+ projectRoot,
+ );
+ reports.push(cardsDefaultBuildReport);
+ assertListCardSummaries(
+ await readTextFile(cardsListPath),
+ "defaults",
+ { images: false, dates: true },
+ );
+
+ // Opt in to image cards and disable summary dates: images appear and the
+ // dates disappear, proving the two settings are independent.
+ await writeTextFile(
+ configPath,
+ [
+ "baseURL = 'https://example.com/'",
+ "title = 'Ananke Test Quickstart'",
+ `theme = '${options.themeName}'`,
+ "[params.ananke.pages]",
+ "show_list_images = true",
+ "show_date = false",
+ "",
+ ].join("\n"),
+ );
+ const cardsImagesBuildStep: StepDefinition = {
+ name: "Build site with image cards and dates disabled",
+ command: "hugo",
+ args: [],
+ cwd: projectRoot,
+ expectedFiles: ["public/cards/index.html"],
+ };
+ const cardsImagesBuildReport = await executeHugoBuildStep(
+ cardsImagesBuildStep,
+ projectRoot,
+ );
+ reports.push(cardsImagesBuildReport);
+ assertListCardSummaries(
+ await readTextFile(cardsListPath),
+ "image cards with dates disabled",
+ { images: true, dates: false },
+ );
+
+ console.log("[OK ] List page image cards and summary dates (issue #217)");
+
console.log("\nResult: PASS");
if (options.keepOnSuccess) {
--
Gitblit v1.10.0