From c88d2fc98c6b84d847f06b5f1b644d6dadb2299a Mon Sep 17 00:00:00 2001
From: Patrick Kollitsch <83281+davidsneighbour@users.noreply.github.com>
Date: Sat, 06 Jun 2026 06:24:27 +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