Compare commits

...
Author SHA1 Message Date
JimmyandGitHub 8e3687431f fix: ensure taxonomy widget title is only linked if a taxonomy page exits (#1314)
fix: ensure taxonomy widget title is only linked if a taxonomy page exists
2026-03-21 12:01:29 +01:00
JimmyandGitHub cfcdb20d17 refactor: remove custom scrollbar styles and related variables (#1304) 2026-03-08 13:23:23 +01:00
0c941ebb04 fix: skip inaccessible images to avoid build failure (#1301)
* skip inaccessible images to avoid build failure

* undo changes to $result and $local

* fix: initialize $resource as a dictionary instead of an empty string

* feat: add external image processing timeout configuration

---------

Co-authored-by: Jimmy Cai <jimmy@cai.im>
2026-03-08 13:18:21 +01:00
JimmyandGitHub f13c478718 fix: check if resource exists before accessing .RelPermalink (#1299)
* fix: check if resource exists before accessing `.RelPermalink`

closes https://github.com/CaiJimmy/hugo-theme-stack/issues/1297

* Set default value of $result in `helper/image` to dict
2026-03-06 00:13:13 +01:00
JimmyandGitHub bde2914dec chore: Remove featuredImageField from configuration (#1298)
chore: Remove `featuredImageField` from configuration.
2026-03-05 23:56:27 +01:00
JimmyandGitHub f5adf4a273 fix: do not display the date of term pages (#1296) 2026-03-05 18:25:04 +01:00
JimmyandGitHub 07fa7615eb refactor: simplify article-list/tile partial context passing (#1295)
refactor: simplify `article-list/tile` partial context passing and internal usage.
2026-03-05 18:15:05 +01:00
JimmyandGitHub 628bb9a501 feat: use images.AutoOrient in helper/image (#1294)
* feat: use images.AutoOrient in `helper/image`

closes https://github.com/CaiJimmy/hugo-theme-stack/issues/1292

* fix: check if IsImageResourceProcessable before using filter

* remove local field

* feat: add configurable image auto-orientation via a new `autoOrient` parameter in site configuration
2026-03-05 17:58:40 +01:00
JimmyandGitHub 11aa9e349a refactor: rename article-footer to article-meta (#1293)
* refactor: rename `article-footer` to `article-meta`, and remove unnecesary structures

* fix: Update default article license URL to point to Creative Commons.
2026-03-05 17:10:35 +01:00
25 changed files with 110 additions and 163 deletions
+10 -23
View File
@@ -71,12 +71,11 @@
gap: 8px;
}
.article-footer,
.article-translations,
.article-header-tags {
.article-meta {
display: flex;
color: var(--card-text-color-tertiary);
gap: var(--spacing-md);
flex-direction: column;
gap: var(--spacing-lg);
font-size: 1.4rem;
svg {
vertical-align: middle;
@@ -86,30 +85,22 @@
flex-shrink: 0;
}
&,
time,
a {
a,
span {
font-size: 1.4rem;
color: var(--card-text-color-tertiary);
}
& > div {
& > .inline-meta {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-md);
}
}
.article-time {
flex-wrap: wrap;
}
.article-translations,
.article-header-tags {
& > div {
flex-wrap: wrap;
}
}
.article-category,
.article-tags {
display: flex;
@@ -172,11 +163,7 @@
object-fit: cover;
}
}
.article-footer {
font-size: 1.4rem;
}
.article-preview {
font-size: 1.4rem;
color: var(--card-text-color-tertiary);
+2 -22
View File
@@ -1,6 +1,7 @@
html {
font-size: 62.5%;
overflow-y: scroll;
scrollbar-gutter: stable;
}
* {
@@ -17,25 +18,4 @@ body {
-ms-text-autospace: ideograph-alpha;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* scrollbar styles for Firefox */
* {
scrollbar-width: auto;
scrollbar-color: var(--scrollbar-thumb) transparent;
}
/**/
/* scrollbar styles for Chromium */
::-webkit-scrollbar {
height: auto;
}
::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-thumb);
}
::-webkit-scrollbar-track {
background-color: transparent;
}
/**/
}
+1 -39
View File
@@ -45,41 +45,7 @@
.article-footer {
margin: var(--card-padding);
margin-top: 0;
section:not(:first-child) {
margin-top: var(--card-padding);
}
section {
color: var(--card-text-color-tertiary);
text-transform: uppercase;
display: flex;
align-items: center;
font-size: 1.4rem;
gap: 15px;
svg {
width: 20px;
height: 20px;
stroke-width: 1.33;
}
}
.article-tags {
text-transform: unset;
}
.article-copyright,
.article-lastmod {
a {
color: var(--body-text-color);
}
a.link {
box-shadow: unset;
}
}
gap: var(--spacing-2xl);
}
}
}
@@ -167,10 +133,6 @@
display: flex;
flex-direction: column;
color: var(--card-text-color-main);
::-webkit-scrollbar-thumb {
background-color: var(--card-separator-color);
}
}
.widget--toc {
-5
View File
@@ -19,17 +19,12 @@
--section-separation: 40px;
--scrollbar-thumb: hsl(0, 0%, 85%);
--scrollbar-track: var(--body-background);
&[data-scheme="dark"] {
--body-background: #303030;
--accent-color: #ecf0f1;
--accent-color-darker: #bdc3c7;
--accent-color-text: #000;
--body-text-color: rgba(255, 255, 255, 0.7);
--scrollbar-thumb: hsl(0, 0%, 40%);
--scrollbar-track: var(--body-background);
}
}
+8 -4
View File
@@ -1,10 +1,9 @@
mainSections = ["post"]
featuredImageField = "image"
rssFullContent = true
mainSections = ["post"]
rssFullContent = true
# Accepted values: "default", "lastmod"
# default = see https://gohugo.io/quick-reference/glossary/#default-sort-order
# lastmod = sort by last modified date, in descending order
SortBy = "default"
SortBy = "default"
[footer]
since = 2026
@@ -76,6 +75,11 @@ SortBy = "default"
default = "auto"
[imageProcessing]
autoOrient = false
[imageProcessing.external]
timeout = "5s"
[imageProcessing.content]
widths = [800, 1600, 2400]
enabled = true
+1 -1
View File
@@ -13,7 +13,7 @@ favicon = "img/avatar.png"
[article]
[article.license]
enabled = true
default = "Licensed under CC BY-NC-SA 4.0"
default = "Licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)"
[widgets]
homepage = [
+1 -1
View File
@@ -1,4 +1,4 @@
{{- $image := partial "helper/image" (dict "Resources" .Page.Resources "Image" .Destination) -}}
{{- $image := partial "helper/image" (dict "Resources" .Page.Resources "Image" .Destination "Context" .Page) -}}
{{- $alt := .PlainText | safeHTML -}}
{{- $title := .Title | markdownify -}}
+10 -6
View File
@@ -11,19 +11,23 @@
{{- $description = $description | default (T "list.page" (len .Pages)) -}}
{{- end -}}
{{- if or $description (not .Date.IsZero) -}}
<footer class="article-footer">
{{- if not .Date.IsZero -}}
{{/*
If it's a term, the date is extracted from the first page of the collection, which is not very useful.
In that case, we prefer to display the description (if set), or the number of pages within the collection.
*/}}
<footer class="article-meta">
{{- if or $IsTerm .Date.IsZero -}}
<span class="article-description">{{- $description -}}</span>
{{- else if not .Date.IsZero -}}
<time datetime="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}">
{{- .Date | time.Format .Site.Params.dateFormat.published -}}
</time>
{{- else if $description -}}
<span class="article-description">{{- $description -}}</span>
{{ end }}
{{- end }}
</footer>
{{- end -}}
</div>
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) -}}
{{ if $image }}
<div class="article-image">
{{ partial "helper/thumbnail-image" (dict
+1 -1
View File
@@ -1,4 +1,4 @@
{{ $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) }}
{{ $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) }}
<article class="{{ if $image }}has-image{{ end }}">
{{ partial "article/components/header" (dict "Page" . "IsList" true) }}
</article>
+5 -5
View File
@@ -1,16 +1,16 @@
{{ $image := partial "helper/image" (dict "Image" .context.Params.image "Resources" .context.Resources) }}
{{ $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) }}
<article class="{{ if $image }}has-image{{ end }}">
<a href="{{ .context.RelPermalink }}">
<a href="{{ .RelPermalink }}">
{{ if $image }}
<div class="article-image">
{{ partial "helper/thumbnail-image" (dict
"Resource" $image.Resource
"Width" 250
"Height" 150
"Resize" .context.Site.Params.ImageProcessing.Thumbnail.Enabled
"Resize" .Site.Params.ImageProcessing.Thumbnail.Enabled
"Attributes" (dict
"src" $image.Permalink
"alt" (printf "Featured image of post %s" .context.Title)
"alt" (printf "Featured image of post %s" .Title)
"loading" "lazy"
"width" $image.Width
"height" $image.Height
@@ -30,7 +30,7 @@
{{- end -}}
>
<h2 class="article-title">
{{- .context.Title -}}
{{- .Title -}}
</h2>
</div>
</a>
@@ -30,48 +30,47 @@
{{ $showReadingTime := $Page.Params.readingTime | default ($Page.Site.Params.article.readingTime) }}
{{ $showDate := not $Page.Date.IsZero }}
{{ $showFooter := or $showDate $showReadingTime }}
{{ if $showFooter }}
<footer class="article-footer">
{{ if $showDate }}
<div>
{{ partial "helper/icon" "date" }}
<time class="article-time--published" datetime='{{ $Page.Date.Format "2006-01-02T15:04:05Z07:00" }}'>
{{- $Page.Date | time.Format $Page.Site.Params.dateFormat.published -}}
</time>
{{ $showTime := or $showDate $showReadingTime }}
{{ $showTranslations := $Page.IsTranslated }}
{{ $showTags := and $Page.Site.Params.Article.List.ShowTags ($Page.GetTerms "tags") $IsList }}
{{ if or $showTime $showTranslations $showTags }}
<footer class="article-meta">
{{ if $showTime }}
<div class="inline-meta">
{{ if $showDate }}
{{ partial "helper/icon" "date" }}
<time class="article-time--published" datetime='{{ $Page.Date.Format "2006-01-02T15:04:05Z07:00" }}'>
{{- $Page.Date | time.Format $Page.Site.Params.dateFormat.published -}}
</time>
{{ end }}
{{ if $showReadingTime }}
{{ partial "helper/icon" "clock" }}
<time class="article-time--reading">
{{ T "article.readingTime" $Page.ReadingTime }}
</time>
{{ end }}
</div>
{{ end }}
{{ if $showReadingTime }}
<div>
{{ partial "helper/icon" "clock" }}
<time class="article-time--reading">
{{ T "article.readingTime" $Page.ReadingTime }}
</time>
</div>
{{ end }}
</footer>
{{ end }}
{{ if $Page.IsTranslated }}
<footer class="article-translations">
{{ partial "helper/icon" "language" }}
<div>
{{ if $showTranslations }}
<div class="inline-meta">
{{ partial "helper/icon" "language" }}
{{ range $Page.Translations }}
<a href="{{ .RelPermalink }}" class="link">{{ .Language.LanguageName }}</a>
{{ end }}
</div>
</footer>
{{ end }}
{{ end }}
{{ if and $Page.Site.Params.Article.List.ShowTags ($Page.GetTerms "tags") $IsList }}
<footer class="article-header-tags">
{{ partial "helper/icon" "tag" }}
<div>
{{ if $showTags }}
<div class="article-tags inline-meta">
{{ partial "helper/icon" "tag" }}
{{ range ($Page.GetTerms "tags") }}
<a href="{{ .RelPermalink }}" class="link">{{ .LinkTitle }}</a>
{{ end }}
</div>
</footer>
{{ end }}
</footer>
{{ end }}
</div>
@@ -1,15 +1,15 @@
<footer class="article-footer">
<footer class="article-meta article-footer">
{{ partial "article/components/tags" . }}
{{ if and (.Site.Params.article.license.enabled) (not (eq .Params.license false)) }}
<section class="article-copyright">
<section class="article-copyright inline-meta">
{{ partial "helper/icon" "copyright" }}
<span>{{ default .Site.Params.article.license.default .Params.license | markdownify }}</span>
</section>
{{ end }}
{{- if ne .Lastmod .Date -}}
<section class="article-lastmod">
<section class="article-lastmod inline-meta">
{{ partial "helper/icon" "clock" }}
<span>
{{ T "article.lastUpdatedOn" }} {{ .Lastmod | time.Format .Site.Params.dateFormat.lastUpdated }}
@@ -1,7 +1,7 @@
{{- $IsList := .IsList -}}
{{- $Page := .Page -}}
<header class="article-header">
{{- $image := partial "helper/image" (dict "Image" $Page.Params.image "Resources" $Page.Resources) -}}
{{- $image := partial "helper/image" (dict "Image" $Page.Params.image "Resources" $Page.Resources "Context" $Page) -}}
{{ if $image }}
<div class="article-image">
<a href="{{ $Page.RelPermalink }}">
@@ -6,7 +6,7 @@
<h2 class="article-title">
{{- $link.title -}}
</h2>
<footer class="article-footer">
<footer class="article-meta">
{{ with $link.description }}
{{ . }}
{{ else }}
@@ -15,7 +15,7 @@
</footer>
</div>
{{- $image := partial "helper/image" (dict "Image" $link.image "Resources" $.Resources) -}}
{{- $image := partial "helper/image" (dict "Image" $link.image "Resources" $.Resources "Context" .) -}}
{{- with $image -}}
<div class="article-image">
{{ partial "helper/thumbnail-image" (dict
@@ -5,7 +5,7 @@
<div class="subsection-list">
<div class="article-list--tile">
{{ range . }}
{{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "articleList") }}
{{ partial "article-list/tile" . }}
{{ end }}
</div>
</div>
+1 -1
View File
@@ -23,7 +23,7 @@
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
{{- end -}}
{{- $favicon := partial "helper/image" (dict "Image" .Site.Params.favicon "Resources" resources) -}}
{{- $favicon := partial "helper/image" (dict "Image" .Site.Params.favicon "Resources" resources "Context" .) -}}
{{ with $favicon }}
<link rel="shortcut icon" href="{{ .Permalink }}" />
{{ end }}
@@ -37,7 +37,7 @@
{{- end -}}
{{- end -}}
{{ $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) }}
{{ $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) }}
{{- if $image -}}
<meta property='og:image' content='{{ absURL $image.permalink }}' />
{{- end -}}
@@ -9,7 +9,7 @@
<meta name="twitter:title" {{ printf "content=%q" $title | safeHTMLAttr }}>
<meta name="twitter:description" {{ printf "content=%q" $description | safeHTMLAttr }}>
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) -}}
{{- if $image -}}
<meta name="twitter:card" content="{{ .Site.Params.opengraph.twitter.card }}">
<meta name="twitter:image" content='{{ absURL $image.permalink }}' />
+24 -8
View File
@@ -1,6 +1,7 @@
/// Params:
/// Resources: Where to search for images
/// Image: Image URL
/// Context: page context (used to access the configuration)
/// Returns:
/// Resource: Hugo image resource object
@@ -8,32 +9,47 @@
/// Height: Image height
/// Width: Image width
{{ $result := "" }}
{{ $result := dict }}
{{ if .Image }}
{{ $url := urls.Parse .Image }}
{{ $permalink := .Image }}
{{ $resource := "" }}
{{ $resource := dict
"Resource" nil
"Permalink" $permalink
"Height" nil
"Width" nil
}}
{{ $local := true }}
{{ if not (in (slice "https" "http") $url.Scheme) }}
{{/* Local image */}}
{{ $resource = .Resources.Get (printf "%s" (.Image | safeURL)) }}
{{ $permalink = $resource.RelPermalink }}
{{ else }}
{{/* Remote image */}}
{{ with try (resources.GetRemote $url) }}
{{ with try (resources.GetRemote $url (dict "timeout" .Context.Site.Params.imageProcessing.external.timeout)) }}
{{ with .Err }}
{{ errorf "%s" . }}
{{ warnf "Failed to fetch remote resource %q: %s" $url . }}
{{ else with .Value }}
{{ $resource = . }}
{{ $local = false }}
{{ else }}
{{ errorf "Unable to get remote resource %q" $url }}
{{ warnf "Unable to get remote resource %q" $url }}
{{ end }}
{{ end }}
{{ end }}
{{ with $resource }}
{{ if reflect.IsImageResourceWithMeta . }}
{{ if $resource }}
{{ if and .Context.Site.Params.imageProcessing.autoOrient (reflect.IsImageResourceProcessable $resource) }}
{{ $filter := images.AutoOrient }}
{{ $resource = $resource | images.Filter $filter }}
{{ end }}
{{ if $local }}
{{ $permalink = $resource.RelPermalink }}
{{ end }}
{{ if reflect.IsImageResourceWithMeta $resource }}
{{ $result = dict
"Resource" $resource
"Permalink" $permalink
+1 -1
View File
@@ -7,7 +7,7 @@
<header>
{{ with .Site.Params.sidebar.avatar }}
{{- $avatar := partial "helper/image" (dict "Image" . "Resources" resources) -}}
{{- $avatar := partial "helper/image" (dict "Image" . "Resources" resources "Context" $) -}}
<figure class="site-avatar">
<a href="{{ $.Site.Home.RelPermalink }}">
<img src="{{ $avatar.Permalink }}"
+1 -1
View File
@@ -20,7 +20,7 @@
{{ partial "helper/icon" $icon }}
</div>
<h2 class="widget-title section-title">
{{ if $showLink }}
{{ if and $showLink $taxonomyPage }}
<a href="{{ $taxonomyPage.RelPermalink }}">
{{ $widgetTitle }}
</a>
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="subsection-list">
<div class="article-list--tile">
{{ range $terms }}
{{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "taxonomy") }}
{{ partial "article-list/tile" . }}
{{ end }}
</div>
</div>
+2 -2
View File
@@ -17,7 +17,7 @@
{{ end }}
</div>
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) -}}
{{ if $image }}
<div class="section-image">
{{ partial "helper/thumbnail-image" (dict
@@ -51,7 +51,7 @@
<div class="subsection-list">
<div class="article-list--tile">
{{ range . }}
{{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "section") }}
{{ partial "article-list/tile" . }}
{{ end }}
</div>
</div>
+1 -1
View File
@@ -4,7 +4,7 @@
{{- range $pages -}}
{{- $data := dict "title" .Title "date" .Date "permalink" .RelPermalink "content" (.Plain) -}}
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) -}}
{{- if $image -}}
{{- $data = merge $data (dict "image" $image.Permalink) -}}
{{- end -}}
+1 -1
View File
@@ -50,7 +50,7 @@
{{- if .Site.Params.RSSFullContent -}}
{{- $content = .Content -}}
{{- end -}}
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources "Context" .) -}}
{{- if $image -}}
{{- $imgTag := printf "<img src=\"%s\" alt=\"Featured image of post %s\" />" ($image.permalink | absURL) .Title -}}
{{- $content = printf "%s%s" $imgTag $content -}}