From 13fb67a0f843097012fa4fe5091a5a897aa96235 Mon Sep 17 00:00:00 2001 From: Daniel Kraus Date: Wed, 2 Sep 2026 21:24:38 +0200 Subject: [PATCH] Add resource-tiles shortcode Displays a page's file resources as a responsive grid of cards with a thumbnail (explicit param, name-matched image, or a generic file icon), title, size and download link. Meant for a downloads page offering a handful of PDFs. Co-Authored-By: Claude Sonnet 5 --- README.md | 55 +++++++++++++ assets/scss/hugo_bootstrap_theme.scss | 33 ++++++++ layouts/_shortcodes/resource-tiles.html | 103 ++++++++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 layouts/_shortcodes/resource-tiles.html diff --git a/README.md b/README.md index 54bc90d..7d82962 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,61 @@ first line of a page's content to get a hero flush against the navbar. Optional params: `title`, `height` (a CSS length such as `60vh`, which crops the image with `object-fit: cover`) and `class`. +### `resource-tiles` + +Displays a page's file resources as a responsive grid of cards +("tiles"), each with a thumbnail, a title, the file size and a download +link. Meant for a downloads page that offers a handful of PDFs, +spreadsheets, archives, … + +``` +{{< resource-tiles >}} +``` + +Params: + +| Param | Default | Meaning | +|---------|---------|--------------------------------------------------------| +| `match` | `*.pdf` | glob selecting which page resources to show | +| `cols` | `3` | number of columns on large screens | +| `sort` | — | resource field to order by (`title`, `name`, `weight`) | +| `class` | — | extra CSS classes for the wrapping row | + +Thumbnails are resolved per resource, in order of preference: + +1. a `thumbnail` param on the resource, pointing at an image in the same + page bundle; +2. an image resource whose name matches the file, e.g. `report.png` or + `report.pdf.png` next to `report.pdf`; +3. a generic file icon showing the upper-cased extension. + +Hugo cannot rasterise PDFs itself, so first-page previews have to be +produced as a build step. With poppler-utils installed, generating a +`.png` next to every PDF under `content/` is a one-liner: + +```sh +find content -name '*.pdf' -exec sh -c \ + 'pdftocairo -png -singlefile -scale-to-x 600 -scale-to-y -1 "$1" "${1%.pdf}"' _ {} \; +``` + +Run it before `hugo`; the tiles then use these as thumbnails +automatically. (`pdftoppm` takes the same flags; `mutool draw` is an +alternative.) + +Per-resource title, description and thumbnail come from the page's front +matter: + +```yaml +resources: + - src: report.pdf + title: Annual report + params: + description: Everything that happened last year. + thumbnail: report-cover.png +``` + +The whole tile is a click target (Bootstrap `.stretched-link`). + ## Navigation diff --git a/assets/scss/hugo_bootstrap_theme.scss b/assets/scss/hugo_bootstrap_theme.scss index 7aa1c40..4a212b5 100644 --- a/assets/scss/hugo_bootstrap_theme.scss +++ b/assets/scss/hugo_bootstrap_theme.scss @@ -87,3 +87,36 @@ h1, h2, h3, h4, h5 { margin-top: -3rem; } +// Resource tiles (see the `resource-tiles` shortcode): a grid of cards, +// one per downloadable file, each with a thumbnail or a generic file +// icon on top. +.resource-tile { + &__thumb, + &__icon { + aspect-ratio: 3 / 2; + width: 100%; + object-fit: cover; + background-color: var(--bs-light); + } + + &__icon { + position: relative; + display: flex; + align-items: center; + justify-content: center; + color: var(--bs-primary); + } + + &__ext { + position: absolute; + bottom: 0.9rem; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.08em; + } + + .card-title { + margin-bottom: 0.25rem; + } +} + diff --git a/layouts/_shortcodes/resource-tiles.html b/layouts/_shortcodes/resource-tiles.html new file mode 100644 index 0000000..09b1de9 --- /dev/null +++ b/layouts/_shortcodes/resource-tiles.html @@ -0,0 +1,103 @@ +{{- /* + resource-tiles — display a page's file resources as a responsive grid + of cards ("tiles"), each with a thumbnail, a title, the file size and a + download link. Handy for a downloads page that offers a handful of + PDFs, spreadsheets, archives, … + + Params: + match glob selecting which page resources to show + (default "*.pdf") + cols number of columns on large screens (default 3) + sort resource field to order by: "title", "name" or "weight" + (default: the order they appear in front matter) + class extra CSS classes for the wrapping row + + Thumbnails are resolved per resource, in order of preference: + 1. a `thumbnail` param on the resource, pointing at an image in the + same page bundle + 2. an image resource whose name matches the file, e.g. `report.png` + or `report.pdf.png` next to `report.pdf` + 3. a generic file icon showing the upper-cased extension + + Per-resource metadata is read from the page's front matter: + + resources: + - src: report.pdf + title: Annual report + params: + description: Everything that happened last year. + thumbnail: report-cover.png +*/ -}} + +{{- $page := .Page -}} +{{- $match := or (.Get "match") "*.pdf" -}} +{{- $cols := or (.Get "cols") 3 -}} +{{- $class := .Get "class" -}} +{{- $imgExts := slice "png" "jpg" "jpeg" "webp" "gif" "avif" -}} + +{{- $resources := $page.Resources.Match $match -}} +{{- with .Get "sort" -}} + {{- $resources = sort $resources . -}} +{{- end -}} + +{{- with $resources -}} +
+ {{- range . }} + {{- $res := . -}} + {{- $ext := strings.TrimPrefix "." (path.Ext .Name) -}} + + {{- /* Title: front-matter `title`, else the humanized file name. + Hugo falls back .Title to .Name, so treat that as unset. */ -}} + {{- $title := .Title -}} + {{- if or (not $title) (eq $title .Name) -}} + {{- $title = humanize (path.BaseName .Name) -}} + {{- end -}} + + {{- /* Resolve a thumbnail image (see the doc comment above). */ -}} + {{- $thumb := false -}} + {{- with .Params.thumbnail -}} + {{- $thumb = $page.Resources.GetMatch . -}} + {{- end -}} + {{- if not $thumb -}} + {{- $base := path.BaseName $res.Name -}} + {{- range $imgExts -}} + {{- if not $thumb -}} + {{- $thumb = or ($page.Resources.GetMatch (printf "%s.%s" $base .)) ($page.Resources.GetMatch (printf "%s.%s" $res.Name .)) -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- if and $thumb (eq $thumb.ResourceType "image") -}} + {{- $thumb = $thumb.Fill "600x400 Center" -}} + {{- end -}} + +
+
+ {{- if $thumb }} + {{ $title }} + {{- else }} + + {{- end }} +
+
{{ $title }}
+ {{- with $res.Params.description }} +

{{ $page.RenderString . }}

+ {{- end }} +
+ +
+
+ {{- end }} +
+{{- end -}}