main
html 59 lines 2.98 KB
Raw
1 {{- /*
2 Article visual orchestrator — guarantees every weekly article gets either a
3 meaningful generated visual or an intentional fallback. Lead approach is
4 locally-generated SVG; a safe local-image path is wired as a documented hook
5 for the image policy (#329).
6
7 Selection order:
8 1. visual = "none" -> intentional fallback card
9 2. compliant LOCAL cover image -> processed, locally-hosted <img> (#329 hook)
10 3. default -> generated SVG cover card
11
12 SAFE COVER POLICY (#329): an image is only rendered when `cover.image` resolves
13 to a Hugo resource (page bundle or global asset) — i.e. it is downloaded and
14 locally hosted. External/hotlinked URLs are intentionally ignored here; do NOT
15 rely on fair use or reuse og:image. Attribution + registry handling land in #329.
16 */ -}}
17 {{- $page := . -}}
18 {{- $visual := $page.Params.visual -}}
19 {{- if eq (printf "%v" $visual) "none" -}}
20 {{ partial "visuals/fallback-card.html" (dict "page" $page) }}
21 {{- else -}}
22 {{- $img := "" -}}
23 {{- with $page.Params.cover -}}
24 {{- with .image -}}
25 {{- /* Only accept locally-hosted resources, matched EXACTLY (not as a
26 substring glob) so selection is deterministic and policy-safe.
27 External/hotlinked URLs never resolve to a resource and are ignored. */ -}}
28 {{- /* Escape glob metacharacters (*?[]\) so a cover.image value containing
29 them is matched literally by GetMatch rather than as a glob pattern. */ -}}
30 {{- $pattern := replaceRE `([*?\[\]\\])` `\${1}` . -}}
31 {{- $bundle := $page.Resources.GetMatch $pattern -}}
32 {{- $global := resources.Get . -}}
33 {{- $candidate := or $bundle $global -}}
34 {{- if and $candidate (eq $candidate.ResourceType "image") -}}
35 {{- $img = $candidate -}}
36 {{- end -}}
37 {{- end -}}
38 {{- end -}}
39 {{- if $img -}}
40 {{- $alt := ($page.Params.cover.alt | default $page.Title | plainify) -}}
41 {{- $attribution := $page.Params.cover.attribution -}}
42 <figure class="article-cover article-cover--image">
43 {{- $card := $img.Fill "800x400 webp q82" -}}
44 {{- $card2x := $img.Fill "1600x800 webp q82" -}}
45 <img class="article-cover__img"
46 src="{{ $card.RelPermalink }}"
47 srcset="{{ $card.RelPermalink }} 800w, {{ $card2x.RelPermalink }} 1600w"
48 sizes="(min-width: 768px) 800px, 100vw"
49 width="{{ $card.Width }}" height="{{ $card.Height }}"
50 loading="eager" decoding="async" alt="{{ $alt }}">
51 {{- /* Render attribution as a safe inline subset (#329): allow simple
52 emphasis/links from Markdown, but strip any <img> the markdown
53 image syntax would emit so hotlinked imagery cannot appear here. */ -}}
54 {{- with $attribution }}<figcaption class="article-cover__attribution">{{ . | markdownify | replaceRE `(?i)<img[^>]*>` "" | safeHTML }}</figcaption>{{ end }}
55 </figure>
56 {{- else -}}
57 {{ partial "visuals/cover-card.html" (dict "page" $page) }}
58 {{- end -}}
59 {{- end -}}