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
JimmyandGitHub 25bf63778c feat: use thumbnail in section and links image (#1291)
* feat: use thumbnail for section image

* feat: use thumbnail in links image

* fix typo
2026-03-05 13:57:37 +01:00
JimmyandGitHub a95d0e7c85 refactor: use helper/image in links.html (#1290)
* refactor: use `helper/image` in `links.html`

* fix: check for $image existence before accessing
2026-03-05 13:51:34 +01:00
JimmyandGitHub 12f05571cf refactor: make article-list/compact better for term pages (#1289)
* refactor: rename article-time class to article-footer for consistency

* feat: show the number of subpages in `article-compact` when the page is a term
2026-03-05 13:43:55 +01:00
230a28fa8d feat(widgets): link widget titles to their respective pages (#1277)
- archives.html: Link the "Archives" title to the archives page,
  consistent with how individual year links already work
- taxonomy.html: Change showLink default from false to true so
  tag-cloud and categories widget titles link to their taxonomy
  listing pages by default

Widget titles are the primary affordance users interact with, so
linking them to the full listing page improves navigation. The
showLink param can still be set to false to disable this.

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
2026-03-05 13:18:32 +01:00
JimmyandGitHub a9a14ce815 feat: hide search results container if no keyword is given (#1288)
feat: hide search results container if no keyword is given and add typing
2026-03-05 12:52:06 +01:00
JimmyandGitHub 0ad02c5c0f refactor: add standardized spacing variables and more style cleanup (#1287)
* refactor: add standardized spacing variables and more style cleanup

* refactor: rename `spacing-xxl` to `spacing-2xl` and introduce `spacing-3xl` for consistent spacing variables.

* refactor: Migrate related content styling to use the `subsection-list` component with updated layout adjustments.

* style: adjust padding values for article list tiles.

* refactor: Introduce shadow variables and dynamic spacing to prevent shadow cutoff in list layouts.

* refactor: relocate dark scheme Mermaid variables within the root scope.
2026-03-05 12:29:48 +01:00
JimmyandGitHub 88468ad84e refactor: cleanup stylesheet (#1286)
* refactor: cleanup stylesheet

* Update assets/scss/partials/article.scss
2026-03-05 10:40:13 +01:00
JimmyandGitHub 3dae60ac7a fix: use IsImageResourceWithMeta to check whether image has width / height attributes (#1285) 2026-03-05 09:23:47 +01:00
22edce91ab feat: add responsive image support (#1283)
* feat: create responsive-image helper

* feat: new responsive-image helper

* feat: Implement responsive image rendering in page content using a new partial and configure image processing widths.

* feat: add `sizes` attribute for enhanced image responsiveness

* feat: add responsive image to page header too

* Check for `.Enabled` before processing image

* style: remove wrong article-list--tile width and height

* feat: add `helper/thumbnail-image` for responsive thumbnails

* feat: enable content image processing and update responsive image `sizes` attributes for improved display.

* fix: get src from the attributes so external image can work correctly

* fix: add missing data-title-escaped

* Update layouts/_partials/article/components/header.html

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat: remove default `true` for thumbnail resize parameter.

* Update layouts/_markup/render-image.html

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update layouts/_partials/helper/thumbnail-image.html

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* add original width and height to thumbnail pictures, for case that resize is not enabled

* Update layouts/_partials/helper/thumbnail-image.html

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 23:32:33 +01:00
JimmyandGitHub 78800c7379 fix: keep external image url, and use reflect.IsImageResourceProcessable to check for svg and other non-processable image formats (#1282)
* fix: keep external image url, and use reflect.IsImageResourceProcessable to check for svg

* build: update Hugo version to 0.157.0 in the build script.

* docs: remove `Local` return value and simplify `Resource` description in image helper

* Check for reflect.IsImageResourceProcessable before getting color from image

* chore: update minimum Hugo version to 0.157.0
2026-03-04 13:05:17 +01:00
a0bd0073bb fix: reading time not displaying in article/components/details partial (#1278)
Fix reading time not displaying due to .Site context in dict partial

The details.html partial receives a dict context via:
  partial "article/components/details" (dict "Page" $Page "IsList" $IsList)

When the dot context is a dict, .Site resolves to nil, so
.Site.Params.article.readingTime always returns empty/false,
causing the reading time to never display.

Lines 40 and 67 of the same file already correctly use
$Page.Site.Params — this fixes line 31 to be consistent.

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
2026-03-04 11:58:09 +01:00
Jimmy Cai 3ca74956a6 ci: update Hugo version to 0.156.0 2026-02-19 17:54:42 +01:00
45 changed files with 534 additions and 416 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
main() {
DART_SASS_VERSION=1.97.1
GO_VERSION=1.25.5
HUGO_VERSION=0.154.5
HUGO_VERSION=0.157.0
NODE_VERSION=24.12.0
export TZ=Europe/Oslo
+1 -1
View File
@@ -19,7 +19,7 @@ a {
.section-title {
text-transform: uppercase;
margin-top: 0;
margin-bottom: 10px;
margin-bottom: var(--spacing-sm);
display: block;
font-size: 1.6rem;
font-weight: bold;
+4 -8
View File
@@ -60,14 +60,10 @@
display: flex;
flex-direction: row;
&.column {
flex-direction: column;
}
&.on-phone--column {
flex-direction: column;
@include respond(md) {
flex-direction: unset;
flex-direction: row;
}
}
@@ -93,11 +89,11 @@ main.main {
.main-container {
min-height: 100vh;
align-items: flex-start;
padding: 0 15px;
padding: 0 var(--container-padding);
gap: var(--section-separation);
padding-top: var(--main-top-padding);
@include respond(md) {
padding: 0 20px;
padding-top: 0;
}
}
+5
View File
@@ -0,0 +1,5 @@
@mixin card-style {
background-color: var(--card-background);
box-shadow: var(--shadow-l1);
border-radius: var(--card-border-radius);
}
+36 -70
View File
@@ -7,9 +7,7 @@
article {
display: flex;
flex-direction: column;
background-color: var(--card-background);
box-shadow: var(--shadow-l1);
border-radius: var(--card-border-radius);
@include card-style;
overflow: hidden;
transition: box-shadow 0.3s ease;
@@ -21,16 +19,8 @@
.article-image {
img {
width: 100%;
height: 150px;
height: var(--article-image-height);
object-fit: cover;
@include respond(md) {
height: 200px;
}
@include respond(xl) {
height: 250px;
}
}
}
}
@@ -41,7 +31,7 @@
flex-direction: column;
justify-content: center;
padding: var(--card-padding);
gap: 15px;
gap: var(--spacing-md);
}
.article-title {
@@ -59,7 +49,7 @@
color: var(--card-text-color-main);
&:hover {
color: var(--card-text-color-main);
opacity: 0.8;
}
}
}
@@ -81,12 +71,11 @@
gap: 8px;
}
.article-time,
.article-translations,
.article-header-tags {
.article-meta {
display: flex;
color: var(--card-text-color-tertiary);
gap: 15px;
flex-direction: column;
gap: var(--spacing-lg);
font-size: 1.4rem;
svg {
vertical-align: middle;
@@ -96,34 +85,26 @@
flex-shrink: 0;
}
&,
time,
a {
a,
span {
font-size: 1.4rem;
color: var(--card-text-color-tertiary);
}
& > div {
& > .inline-meta {
display: inline-flex;
align-items: center;
gap: 15px;
}
}
.article-time {
flex-wrap: wrap;
}
.article-translations,
.article-header-tags {
& > div {
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-md);
}
}
.article-category,
.article-tags {
display: flex;
gap: 10px;
gap: var(--spacing-sm);
flex-wrap: wrap;
a {
@@ -144,21 +125,15 @@
/* Compact style article list */
.article-list--compact {
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
background-color: var(--card-background);
--image-size: 50px;
@include respond(md) {
--image-size: 60px;
}
@include card-style;
--image-size: var(--article-compact-image-size);
article {
& > a {
display: flex;
align-items: center;
padding: var(--small-card-padding);
gap: 15px;
gap: var(--spacing-md);
}
&:not(:last-of-type) {
@@ -169,7 +144,7 @@
flex-grow: 1;
padding: 0;
min-height: var(--image-size);
gap: 10px;
gap: var(--spacing-sm);
}
.article-title {
@@ -188,11 +163,7 @@
object-fit: cover;
}
}
.article-time {
font-size: 1.4rem;
}
.article-preview {
font-size: 1.4rem;
color: var(--card-text-color-tertiary);
@@ -204,14 +175,12 @@
/* Tile style article list */
.article-list--tile {
article {
border-radius: var(--card-border-radius);
overflow: hidden;
position: relative;
height: 350px;
width: 250px;
box-shadow: var(--shadow-l1);
width: var(--article-tile-width);
height: var(--article-tile-height);
@include card-style;
overflow: hidden;
transition: box-shadow 0.3s ease;
background-color: var(--card-background);
&:hover {
box-shadow: var(--shadow-l2);
@@ -219,11 +188,11 @@
&.has-image {
.article-details {
background-color: rgba(#000, 0.25);
background-color: var(--article-tile-overlay-bg);
}
.article-title {
color: #fff;
color: var(--article-tile-text-color);
}
}
@@ -328,19 +297,19 @@
button {
padding: 0.5rem 1rem;
background: rgba(255, 255, 255, 0.95);
border: 2px solid #333;
background: var(--mermaid-toolbar-btn-bg);
border: 2px solid var(--mermaid-toolbar-btn-text);
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
color: #333;
color: var(--mermaid-toolbar-btn-text);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: all 0.2s;
&:hover {
background: #333;
color: #fff;
background: var(--mermaid-toolbar-btn-text);
color: var(--card-background);
transform: scale(1.05);
}
}
@@ -354,7 +323,7 @@
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.95);
background: var(--mermaid-modal-bg);
z-index: 9999;
&.active {
@@ -379,14 +348,15 @@
.mermaid-modal-controls button,
.mermaid-modal-close {
padding: 0.5rem 1rem;
background: #fff;
background: var(--mermaid-modal-btn-bg);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
color: var(--card-text-color-main);
&:hover {
background: #ddd;
background: var(--mermaid-modal-btn-hover);
}
}
@@ -404,11 +374,7 @@
bottom: 1rem;
border-radius: 8px;
overflow: hidden;
background: #fff;
[data-scheme="dark"] & {
background: #1e1e1e;
}
background: var(--mermaid-modal-content-bg);
}
.mermaid-panzoom-container {
+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;
}
/**/
}
+12 -14
View File
@@ -16,17 +16,17 @@
margin: 0 auto;
background: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l3);
box-shadow: var(--shadow-l2);
padding: var(--card-padding);
display: flex;
flex-direction: column;
gap: 15px;
gap: var(--spacing-md);
@include respond(md) {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 20px;
gap: var(--spacing-lg);
}
}
@@ -38,7 +38,7 @@
display: block;
color: var(--card-text-color-main);
font-size: 1.6rem;
margin-bottom: 5px;
margin-bottom: var(--spacing-xs);
}
p {
@@ -51,7 +51,7 @@
.cookie-banner__actions {
display: flex;
gap: 10px;
gap: var(--spacing-sm);
flex-shrink: 0;
}
@@ -70,7 +70,6 @@
}
@include respond(md) {
width: auto;
margin-left: auto;
align-self: center;
}
@@ -117,19 +116,19 @@
h3 {
color: var(--card-text-color-main);
font-size: 1.6rem;
margin: 0 0 15px 0;
margin: 0 0 var(--spacing-md) 0;
}
&__actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
gap: var(--spacing-sm);
margin-top: var(--spacing-lg);
}
}
.cookie-category {
padding: 15px 0;
padding: var(--spacing-md) 0;
border-bottom: 1px solid var(--card-separator-color);
&:last-of-type {
@@ -139,7 +138,7 @@
label {
display: flex;
align-items: center;
gap: 10px;
gap: var(--spacing-sm);
cursor: pointer;
input[type="checkbox"] {
@@ -186,14 +185,13 @@
// Comments placeholder when consent not given
.consent-placeholder {
background: var(--card-background);
border-radius: var(--card-border-radius);
@include card-style;
padding: var(--card-padding);
text-align: center;
color: var(--card-text-color-secondary);
font-size: 1.4rem;
p {
margin: 0 0 15px 0;
margin: 0 0 var(--spacing-md) 0;
}
}
+3 -3
View File
@@ -1,5 +1,5 @@
footer.site-footer {
padding: 20px 0 var(--section-separation) 0;
padding: var(--spacing-lg) 0 var(--section-separation) 0;
font-size: 1.4rem;
line-height: 1.75;
@@ -9,13 +9,13 @@ footer.site-footer {
height: 3px;
width: 50px;
background: var(--body-text-color);
margin-bottom: 20px;
margin-bottom: var(--spacing-lg);
}
.copyright {
color: var(--accent-color);
font-weight: bold;
margin-bottom: 5px;
margin-bottom: var(--spacing-xs);
}
.powerby {
+15 -79
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);
}
}
}
@@ -111,14 +77,14 @@
}
li {
margin: 15px 0 15px 20px;
padding: 5px;
margin: var(--spacing-md) 0 var(--spacing-md) var(--spacing-lg);
padding: var(--spacing-xs);
&>ol,
&>ul {
margin-top: 10px;
padding-left: 10px;
margin-bottom: -5px;
margin-top: var(--spacing-sm);
padding-left: var(--spacing-sm);
margin-bottom: calc(var(--spacing-xs) * -1);
&>li:last-child {
margin-bottom: 0;
@@ -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 {
@@ -182,7 +144,7 @@
.article-toc {
padding: 0 var(--card-padding);
margin-top: 20px;
margin-top: var(--spacing-lg);
@include respond(lg) {
display: none;
@@ -203,7 +165,7 @@
summary {
list-style: none;
padding: 15px 20px;
padding: var(--spacing-md) var(--spacing-lg);
cursor: pointer;
&::-webkit-details-marker {
@@ -213,7 +175,7 @@
.article-toc-title {
display: flex;
align-items: center;
gap: 10px;
gap: var(--spacing-sm);
text-transform: uppercase;
font-size: 1.4rem;
font-weight: bold;
@@ -230,35 +192,6 @@
}
}
.related-content {
overflow-x: auto;
padding-bottom: 15px;
&>.flex {
float: left;
}
article {
margin-right: 15px;
flex-shrink: 0;
overflow: hidden;
width: 250px;
height: 150px;
.article-title {
font-size: 1.8rem;
margin: 0;
}
&.has-image {
.article-details {
padding: 20px;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.75) 100%);
}
}
}
}
.article-content {
font-family: var(--article-font-family);
font-size: var(--article-font-size);
@@ -315,7 +248,7 @@
position: relative;
margin: 1.5em 0;
border-inline-start: var(--blockquote-border-size) solid var(--card-separator-color);
padding: 15px calc(var(--card-padding) - var(--blockquote-border-size));
padding: var(--spacing-md) calc(var(--card-padding) - var(--blockquote-border-size));
background-color: var(--blockquote-background-color);
.cite {
@@ -385,7 +318,7 @@
flex-direction: row;
justify-content: center;
margin: 1.5em 0;
gap: 10px;
gap: var(--spacing-sm);
figure {
margin: 0;
@@ -420,6 +353,10 @@
padding: var(--card-padding);
position: relative;
pre {
background-color: transparent;
}
&:hover {
.copyCodeButton {
opacity: 1;
@@ -432,7 +369,6 @@
}
pre {
margin: initial;
padding: 0;
margin: 0;
width: auto;
+15 -11
View File
@@ -1,11 +1,9 @@
.section-card {
border-radius: var(--card-border-radius);
background-color: var(--card-background);
@include card-style;
padding: var(--small-card-padding);
box-shadow: var(--shadow-l1);
display: flex;
align-items: center;
gap: 20px;
gap: var(--spacing-lg);
--separation: 15px;
@@ -47,18 +45,24 @@
}
.subsection-list {
overflow-x: auto;
margin-left: -24px;
--shadow-overlap-x: calc(var(--shadow-l2-blur) + 4px);
--shadow-overlap-y: calc(var(--shadow-l2-blur) + var(--shadow-l2-y) + 4px);
overflow-x: auto;
/// These values are computed from the shadow of the article-list--tile (which uses shadow-l1 and shadow-l2)
/// so that the shadow is not cut off
margin-left: calc(var(--shadow-overlap-x) * -1);
margin-right: calc(var(--shadow-overlap-x) * -1);
margin-bottom: calc(var(--shadow-overlap-y) * -1);
.article-list--tile {
display: flex;
padding: 8px 0 34px 24px;
padding: var(--spacing-xs) var(--shadow-overlap-x) var(--shadow-overlap-y) var(--shadow-overlap-x);
width: max-content;
gap: var(--spacing-lg);
article {
width: 250px;
height: 150px;
margin-right: 20px;
flex-shrink: 0;
.article-title {
@@ -67,7 +71,7 @@
}
.article-details {
padding: 20px;
padding: var(--spacing-lg);
}
}
}
+12 -7
View File
@@ -7,12 +7,12 @@
label {
font-size: 1.3rem;
top: 10px;
top: var(--spacing-sm);
}
input {
font-size: 1.5rem;
padding: 30px 20px 15px 20px;
padding: var(--spacing-2xl) var(--spacing-lg) var(--spacing-md) var(--spacing-lg);
}
}
@@ -23,21 +23,20 @@
label {
position: absolute;
top: 15px;
inset-inline-start: 20px;
top: var(--spacing-md);
inset-inline-start: var(--spacing-lg);
font-size: 1.4rem;
color: var(--card-text-color-tertiary);
}
input {
padding: 40px 20px 20px;
border-radius: var(--card-border-radius);
background-color: var(--card-background);
box-shadow: var(--shadow-l1);
@include card-style;
color: var(--card-text-color-main);
width: 100%;
border: 0;
-webkit-appearance: none;
appearance: none;
transition: box-shadow 0.3s ease;
@@ -79,4 +78,10 @@
}
}
}
.search-result {
&.hidden {
display: none;
}
}
+5 -15
View File
@@ -7,7 +7,7 @@
*/
.hamburger {
padding-top: 10px;
padding-top: var(--spacing-sm);
display: inline-block;
cursor: pointer;
transition-property: opacity, filter;
@@ -26,16 +26,6 @@
opacity: 0.7;
}
.hamburger.is-active:hover {
opacity: 0.7;
}
.hamburger.is-active .hamburger-inner,
.hamburger.is-active .hamburger-inner::before,
.hamburger.is-active .hamburger-inner::after {
background-color: #000;
}
.hamburger-box {
width: 30px;
height: 24px;
@@ -149,7 +139,7 @@
display: none;
margin: 0 calc(var(--container-padding) * -1);
padding: 30px 30px;
padding: var(--spacing-2xl);
@include respond(xl) {
padding: 15px 0;
@@ -158,10 +148,10 @@
&,
.menu-bottom-section ol {
flex-direction: column;
gap: 30px;
gap: var(--spacing-2xl);
@include respond(xl) {
gap: 25px;
gap: var(--spacing-xl);
}
}
@@ -230,7 +220,7 @@
margin: 0;
display: flex;
flex-direction: row;
gap: 10px;
gap: var(--spacing-sm);
svg {
width: 24px;
+1 -3
View File
@@ -1,8 +1,6 @@
.pagination {
display: flex;
background-color: var(--card-background);
box-shadow: var(--shadow-l1);
border-radius: var(--card-border-radius);
@include card-style;
overflow: hidden;
.page-link {
+4 -4
View File
@@ -35,7 +35,7 @@
position: relative;
--sidebar-avatar-size: 100px;
--sidebar-element-separation: 20px;
--sidebar-element-separation: var(--spacing-lg);
--emoji-size: 40px;
--emoji-font-size: 20px;
@@ -48,7 +48,7 @@
@include respond(2xl) {
--sidebar-avatar-size: 120px;
--sidebar-element-separation: 25px;
--sidebar-element-separation: var(--spacing-xl);
--emoji-size: 40px;
}
@@ -67,7 +67,7 @@
}
.site-meta {
gap: 5px;
gap: var(--spacing-xs);
}
.site-name {
@@ -144,7 +144,7 @@
.site-meta {
display: flex;
flex-direction: column;
gap: 10px;
gap: var(--spacing-sm);
justify-content: center;
}
+5 -8
View File
@@ -4,8 +4,8 @@
.widget-icon {
svg {
width: 32px;
height: 32px;
width: var(--widget-icon-size);
height: var(--widget-icon-size);
stroke-width: 1.6;
color: var(--body-text-color);
}
@@ -17,11 +17,10 @@
.tagCloud-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
gap: var(--spacing-sm);
a {
background: var(--card-background);
box-shadow: var(--shadow-l1);
@include card-style;
border-radius: var(--tag-border-radius);
padding: 8px 20px;
color: var(--card-text-color-main);
@@ -38,9 +37,7 @@
/* Archives widget */
.widget.archives {
.widget-archive--list {
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
background-color: var(--card-background);
@include card-style;
}
.archives-year {
+1
View File
@@ -8,6 +8,7 @@
@import "breakpoints.scss";
@import "variables.scss";
@import "mixins.scss";
@import "grid.scss";
@import "external/normalize.scss";
+69 -14
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);
}
}
@@ -120,9 +115,6 @@
--link-background-opacity: 0.5;
--link-background-opacity-hover: 0.7;
--pre-background-color: #272822;
--pre-text-color: #f8f8f2;
--code-background-color: rgba(0, 0, 0, 0.12);
--code-text-color: #808080;
@@ -147,11 +139,13 @@
* Thanks to https://www.figma.com/community/plugin/744987207861965946/Shadow-picker
*/
:root {
--shadow-l1: 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-l2: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-l3: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-l4: 0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04),
0px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-l1-y: 4px;
--shadow-l1-blur: 8px;
--shadow-l2-y: 10px;
--shadow-l2-blur: 20px;
--shadow-l1: 0px var(--shadow-l1-y) var(--shadow-l1-blur) rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-l2: 0px var(--shadow-l2-y) var(--shadow-l2-blur) rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
}
[data-scheme="light"] {
@@ -169,5 +163,66 @@
:root {
--menu-icon-separation: 40px;
--container-padding: 15px;
@include respond(md) {
--container-padding: 20px;
}
--widget-separation: var(--section-separation);
}
/* Spacing tokens */
--spacing-xs: 5px;
--spacing-sm: 10px;
--spacing-md: 15px;
--spacing-lg: 20px;
--spacing-xl: 25px;
--spacing-2xl: 30px;
--spacing-3xl: 40px;
/* Article image height variables */
--article-image-height: 150px;
@include respond(md) {
--article-image-height: 200px;
}
@include respond(xl) {
--article-image-height: 250px;
}
/* Mermaid component variables */
--mermaid-toolbar-btn-bg: rgba(255, 255, 255, 0.95);
--mermaid-toolbar-btn-text: #333;
--mermaid-modal-bg: rgba(0, 0, 0, 0.95);
--mermaid-modal-content-bg: #fff;
--mermaid-modal-btn-bg: #fff;
--mermaid-modal-btn-hover: #ddd;
&[data-scheme="dark"] {
--mermaid-toolbar-btn-bg: rgba(0, 0, 0, 0.85);
--mermaid-toolbar-btn-text: #eee;
--mermaid-modal-content-bg: #1e1e1e;
--mermaid-modal-btn-bg: #333;
--mermaid-modal-btn-hover: #444;
}
/* Article compact image size */
--article-compact-image-size: 50px;
@include respond(md) {
--article-compact-image-size: 60px;
}
/* Widget icon size */
--widget-icon-size: 32px;
/* Article list tile variables */
--article-tile-width: 250px;
--article-tile-height: 150px;
--article-tile-overlay-bg: rgba(0, 0, 0, 0.25);
--article-tile-text-color: #fff;
&[data-scheme="dark"] {
--article-tile-overlay-bg: rgba(0, 0, 0, 0.45);
}
}
+38 -23
View File
@@ -26,15 +26,15 @@ const tagsToReplace = {
'…': '&hellip;'
};
function replaceTag(tag) {
return tagsToReplace[tag] || tag;
function replaceTag(tag: string) {
return (tagsToReplace as Record<string, string>)[tag] || tag;
}
function replaceHTMLEnt(str) {
function replaceHTMLEnt(str: string) {
return str.replace(/[&<>"]/g, replaceTag);
}
function escapeRegExp(string) {
function escapeRegExp(string: string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
}
@@ -45,13 +45,21 @@ class Search {
private list: HTMLDivElement;
private resultTitle: HTMLHeadElement;
private resultTitleTemplate: string;
private container: HTMLDivElement;
constructor({ form, input, list, resultTitle, resultTitleTemplate }) {
constructor({ form, input, list, resultTitle, resultTitleTemplate }: {
form: HTMLFormElement,
input: HTMLInputElement,
list: HTMLDivElement,
resultTitle: HTMLHeadingElement,
resultTitleTemplate: string
}) {
this.form = form;
this.input = input;
this.list = list;
this.resultTitle = resultTitle;
this.resultTitleTemplate = resultTitleTemplate;
this.container = list.parentElement as HTMLDivElement;
/// Check if there's already value in the search input
if (this.input.value.trim() !== '') {
@@ -203,16 +211,18 @@ class Search {
const endTime = performance.now();
this.resultTitle.innerText = this.generateResultTitle(results.length, ((endTime - startTime) / 1000).toPrecision(1));
this.container?.classList.remove('hidden');
}
private generateResultTitle(resultLen, time) {
return this.resultTitleTemplate.replace("#PAGES_COUNT", resultLen).replace("#TIME_SECONDS", time);
private generateResultTitle(resultLen: number, time: string) {
return this.resultTitleTemplate.replace("#PAGES_COUNT", resultLen.toString()).replace("#TIME_SECONDS", time);
}
public async getData() {
if (!this.data) {
/// Not fetched yet
const jsonURL = this.form.dataset.json;
const jsonURL = this.form.dataset.json as string;
this.data = await fetch(jsonURL).then(res => res.json());
const parser = new DOMParser();
@@ -227,7 +237,7 @@ class Search {
private bindSearchForm() {
let lastSearch = '';
const eventHandler = (e) => {
const eventHandler = (e: Event) => {
e.preventDefault();
const keywords = this.input.value.trim();
@@ -251,6 +261,7 @@ class Search {
private clear() {
this.list.innerHTML = '';
this.resultTitle.innerText = '';
this.container.classList.add('hidden');
}
private bindQueryStringChange() {
@@ -261,7 +272,7 @@ class Search {
private handleQueryString() {
const pageURL = new URL(window.location.toString());
const keywords = pageURL.searchParams.get('keyword');
const keywords = pageURL.searchParams.get('keyword') || '';
this.input.value = keywords;
if (keywords) {
@@ -291,19 +302,21 @@ class Search {
}
public static render(item: pageData) {
return <article>
<a href={item.permalink}>
<div class="article-details">
<h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
<section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section>
</div>
{item.image &&
<div class="article-image">
<img src={item.image} loading="lazy" />
return (
<article>
<a href={item.permalink}>
<div class="article-details">
<h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
<section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section>
</div>
}
</a>
</article>;
{item.image &&
<div class="article-image">
<img src={item.image} loading="lazy" />
</div>
}
</a>
</article>
);
}
}
@@ -316,10 +329,12 @@ declare global {
window.addEventListener('load', () => {
setTimeout(function () {
const searchForm = document.querySelector('.search-form') as HTMLFormElement,
searchInput = searchForm.querySelector('input') as HTMLInputElement,
searchInput = searchForm?.querySelector('input') as HTMLInputElement,
searchResultList = document.querySelector('.search-result--list') as HTMLDivElement,
searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;
if (!searchForm || !searchInput || !searchResultList || !searchResultTitle) return;
new Search({
form: searchForm,
input: searchInput,
+1 -1
View File
@@ -1,3 +1,3 @@
[hugoVersion]
extended = true
min = "0.154.0"
min = "0.157.0"
+14 -8
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
@@ -75,11 +74,18 @@ SortBy = "default"
toggle = true
default = "auto"
[imageProcessing.cover]
enabled = true
[imageProcessing]
autoOrient = false
[imageProcessing.content]
enabled = true
[imageProcessing.external]
timeout = "5s"
[imageProcessing.content]
widths = [800, 1600, 2400]
enabled = true
[imageProcessing.thumbnail]
enabled = true
# GDPR Cookie Consent Configuration
# When enabled, analytics and functional cookies require user consent
+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 = [
+25 -19
View File
@@ -1,24 +1,30 @@
{{- $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 -}}
{{/* SVG and external images won't work with gallery layout, because their width and height attributes are unknown */}}
{{/* There are some types of images that don't have width and height attributes, such as SVG */}}
{{- $galleryImage := and $image.Height $image.Width -}}
<img src="{{ $image.Permalink }}"
{{ with $image.Width }}width="{{ . }}"{{ end }}
{{ with $image.Height }}height="{{ . }}"{{ end }}
loading="lazy"
{{ with $alt }}
alt="{{ . }}"
{{ end }}
{{ with $title }}
title="{{ . }}"
data-title-escaped="{{ . | htmlEscape }}"
{{ end }}
{{ if $galleryImage }}
class="gallery-image"
data-flex-grow="{{ div (mul $image.Width 100) $image.Height }}"
data-flex-basis="{{ div (mul $image.Width 240) $image.Height }}px"
{{ end }}
>
{{- $attributes := dict -}}
{{- if $galleryImage -}}
{{- $attributes = (dict
"data-flex-grow" (div (mul $image.Width 100) $image.Height)
"data-flex-basis" (printf "%dpx" (div (mul $image.Width 240) $image.Height))
) -}}
{{- end -}}
{{ partial "helper/responsive-image" (dict
"Resource" $image.Resource
"Widths" (cond .Page.Site.Params.ImageProcessing.Content.Enabled .Page.Site.Params.ImageProcessing.Content.Widths nil)
"Attributes" (merge $attributes (dict
"src" $image.Permalink
"width" $image.Width
"height" $image.Height
"alt" $alt
"title" $title
"data-title-escaped" ($title | htmlEscape)
"class" (cond $galleryImage "gallery-image" "")
"loading" "lazy"
"sizes" "(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"
))
) }}
+35 -8
View File
@@ -4,18 +4,45 @@
<h2 class="article-title">
{{- .Title -}}
</h2>
<footer class="article-time">
<time datetime='{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}'>
{{- .Date | time.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
</time>
</footer>
{{- $description := .Params.description -}}
{{- $IsTerm := in .Kind "term" -}}
{{- if $IsTerm -}}
{{- $description = $description | default (T "list.page" (len .Pages)) -}}
{{- end -}}
{{- if or $description (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>
{{- 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">
<img src="{{ $image.Permalink }}" width="{{ $image.Width }}" height="{{ $image.Height }}" alt="{{ .Title }}"
loading="lazy">
{{ partial "helper/thumbnail-image" (dict
"Resource" $image.Resource
"Width" 60
"Height" 60
"Resize" .Site.Params.ImageProcessing.Thumbnail.Enabled
"Attributes" (dict
"src" $image.Permalink
"alt" .Title
"loading" "lazy"
"width" $image.Width
"height" $image.Height
)
) }}
</div>
{{ end }}
</a>
+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>
+17 -6
View File
@@ -1,15 +1,26 @@
{{ $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">
<img src="{{ $image.Permalink }}" width="{{ $image.Width }}" height="{{ $image.Height }}" loading="lazy"
alt="Featured image of post {{ .context.Title }}">
{{ partial "helper/thumbnail-image" (dict
"Resource" $image.Resource
"Width" 250
"Height" 150
"Resize" .Site.Params.ImageProcessing.Thumbnail.Enabled
"Attributes" (dict
"src" $image.Permalink
"alt" (printf "Featured image of post %s" .Title)
"loading" "lazy"
"width" $image.Width
"height" $image.Height
)
) }}
</div>
{{ end }}
<div class="article-details"
{{- if and $image $image.Resource -}}
{{- if and $image (reflect.IsImageResourceProcessable $image.Resource) -}}
{{- $colors := $image.Resource.Colors -}}
{{- if $colors -}}
{{- $vibrant := index $colors 0 -}}
@@ -19,7 +30,7 @@
{{- end -}}
>
<h2 class="article-title">
{{- .context.Title -}}
{{- .Title -}}
</h2>
</div>
</a>
@@ -28,50 +28,49 @@
{{ end }}
</div>
{{ $showReadingTime := $Page.Params.readingTime | default (.Site.Params.article.readingTime) }}
{{ $showReadingTime := $Page.Params.readingTime | default ($Page.Site.Params.article.readingTime) }}
{{ $showDate := not $Page.Date.IsZero }}
{{ $showFooter := or $showDate $showReadingTime }}
{{ if $showFooter }}
<footer class="article-time">
{{ 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,12 +1,22 @@
{{- $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 }}">
<img src="{{ $image.Permalink }}" width="{{ $image.Width }}" height="{{ $image.Height }}" loading="lazy"
alt="Featured image of post {{ $Page.Title }}" />
{{ partial "helper/responsive-image" (dict
"Resource" $image.Resource
"Widths" (cond $Page.Site.Params.ImageProcessing.Content.Enabled $Page.Site.Params.ImageProcessing.Content.Widths nil)
"Attributes" (dict
"src" $image.Permalink
"width" $image.Width
"height" $image.Height
"alt" (printf "Featured image of post %s" $Page.Title)
"loading" "lazy"
"sizes" "(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"
)
) }}
</a>
</div>
{{ end }}
@@ -1,12 +1,12 @@
<div class="article-list--compact links">
{{ range $i, $link := .Params.links }}
{{ range $link := .Params.links }}
<article>
<a href="{{ $link.website }}" target="_blank" rel="noopener">
<div class="article-details">
<h2 class="article-title">
{{- $link.title -}}
</h2>
<footer class="article-time">
<footer class="article-meta">
{{ with $link.description }}
{{ . }}
{{ else }}
@@ -14,14 +14,23 @@
{{ end }}
</footer>
</div>
{{ with $link.image }}
{{ $permalink := . }}
{{ with ($.Resources.GetMatch (printf "%s" (. | safeURL))) }}
{{ $permalink = .RelPermalink }}
{{ end }}
{{- $image := partial "helper/image" (dict "Image" $link.image "Resources" $.Resources "Context" .) -}}
{{- with $image -}}
<div class="article-image">
<img src="{{ $permalink }}" loading="lazy"{{ with $link.alt }} alt="{{ . }}"{{ end }}>
{{ partial "helper/thumbnail-image" (dict
"Resource" .Resource
"Width" 60
"Height" 60
"Resize" $.Site.Params.ImageProcessing.Thumbnail.Enabled
"Attributes" (dict
"src" $image.Permalink
"alt" $link.title
"loading" "lazy"
"width" .Width
"height" .Height
)
) }}
</div>
{{ end }}
</a>
@@ -1,11 +1,11 @@
{{ $related := (.Site.RegularPages.Related .) | first 5 }}
{{ with $related }}
<aside class="related-content--wrapper">
<aside>
<h2 class="section-title">{{ T "article.relatedContent" }}</h2>
<div class="related-content">
<div class="flex article-list--tile">
<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 }}' />
+37 -20
View File
@@ -1,52 +1,69 @@
/// Params:
/// Resources: Where to search for images
/// Image: Image URL
/// Context: page context (used to access the configuration)
/// Returns:
/// Local: true if the image is local, false if it is remote
/// Resource: Hugo image resource object, not nil if the image is local
/// Resource: Hugo image resource object
/// Permalink: Image URL
/// Height: Image height
/// Width: Image width
{{ $result := "" }}
{{ $result := dict }}
{{ if .Image }}
{{ $url := urls.Parse .Image }}
{{ $resource := "" }}
{{ $permalink := .Image }}
{{ $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)) }}
{{ else }}
{{ with try (resources.GetRemote $url) }}
{{/* Remote image */}}
{{ 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 }}
{{ if $resource }}
{{- $isSVG := eq $resource.MediaType.SubType "svg" -}}
{{- if $isSVG -}}
{{ 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
"Local" true
"Resource" $resource
"Permalink" $resource.RelPermalink
"Height" nil
"Width" nil
}}
{{- else -}}
{{- $result = dict
"Local" true
"Resource" $resource
"Permalink" $resource.RelPermalink
"Permalink" $permalink
"Height" $resource.Height
"Width" $resource.Width
}}
{{- end -}}
{{ else }}
{{ $result = dict
"Resource" $resource
"Permalink" $permalink
"Height" nil
"Width" nil
}}
{{ end }}
{{ end }}
{{ end }}
@@ -0,0 +1,33 @@
{{- /*
Params:
Resource: Hugo image resource object (Optional, for processing)
Widths: Slice of widths to generate for srcset
Attributes: Map of HTML attributes for the <img> tag
*/ -}}
{{- $resource := .Resource -}}
{{- $widths := .Widths -}}
{{- $attributes := .Attributes | default dict -}}
{{/* Generate srcset if Resource and Widths are provided */}}
{{- if and $widths $resource (reflect.IsImageResourceProcessable $resource) -}}
{{- $srcset := slice -}}
{{- range $widths -}}
{{- if lt . $resource.Width -}}
{{- $resized := $resource.Resize (printf "%dx" .) -}}
{{- $srcset = $srcset | append (printf "%s %dw" $resized.RelPermalink .) -}}
{{- end -}}
{{- end -}}
{{- if gt (len $srcset) 0 -}}
{{- $permalink := default $resource.RelPermalink (index $attributes "src") -}}
{{- $srcset = $srcset | append (printf "%s %dw" $permalink $resource.Width) -}}
{{- $attributes = merge $attributes (dict "srcset" (delimit $srcset ", ")) -}}
{{- end -}}
{{- end -}}
<img {{- range $k, $v := $attributes -}}
{{- if $v -}}
{{- printf " %s=%q" (lower $k) (printf "%v" $v) | safeHTMLAttr -}}
{{- end -}}
{{- end -}}>
@@ -0,0 +1,41 @@
{{- /*
Params:
Resource: Hugo image resource object (Optional, for processing)
Width: Image width (Required)
Height: Image height (Required)
Resize: Whether to perform resize (Optional, default: false)
Attributes: Map of HTML attributes for the <img> tag
*/ -}}
{{- $resource := .Resource -}}
{{- $width := .Width -}}
{{- $height := .Height -}}
{{- $resize := .Resize -}}
{{- $attributes := .Attributes | default dict -}}
{{- if and $resize $resource (reflect.IsImageResourceProcessable $resource) $width $height -}}
{{/* Create thumbnail with 1x/2x descriptors */}}
{{- $srcset := slice -}}
{{- range (slice 1 2) -}}
{{- $w := mul $width . -}}
{{- $h := mul $height . -}}
{{- if and (le $w $resource.Width) (le $h $resource.Height) -}}
{{- $resized := $resource.Fill (printf "%dx%d" $w $h) -}}
{{- $srcset = $srcset | append (printf "%s %dx" $resized.RelPermalink .) -}}
{{- if eq . 1 -}}
{{- $attributes = merge $attributes (dict "src" $resized.RelPermalink) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if gt (len $srcset) 0 -}}
{{- $attributes = merge $attributes (dict "width" $width "height" $height "srcset" (delimit $srcset ", ")) -}}
{{- end -}}
{{- end -}}
<img {{- range $k, $v := $attributes -}}
{{- if $v -}}
{{- printf " %s=%q" (lower $k) (printf "%v" $v) | safeHTMLAttr -}}
{{- end -}}
{{- end -}}>
+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 }}"
+5 -1
View File
@@ -7,7 +7,11 @@
<div class="widget-icon">
{{ partial "helper/icon" "infinity" }}
</div>
<h2 class="widget-title section-title">{{ T "widget.archives.title" }}</h2>
<h2 class="widget-title section-title">
<a href="{{ $archivesPage.RelPermalink }}">
{{ T "widget.archives.title" }}
</a>
</h2>
{{ $pages := partial "helper/pages.html" $context.Site.Home }}
{{ $archives := $pages.GroupByDate "2006" }}
+2 -2
View File
@@ -13,14 +13,14 @@
{{- $widgetTitle := .Params.title -}}
{{- $limit := default 10 .Params.limit -}}
{{- $icon := default .Params.taxonomy .Params.icon -}}
{{- $showLink := default false .Params.showLink -}}
{{- $showLink := default true .Params.showLink -}}
<section class="widget tagCloud">
<div class="widget-icon">
{{ 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>
+15 -6
View File
@@ -17,13 +17,22 @@
{{ 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">
<img src="{{ $image.Permalink }}"
width="{{ $image.Width }}"
height="{{ $image.Height }}"
loading="lazy">
{{ partial "helper/thumbnail-image" (dict
"Resource" $image.Resource
"Width" 60
"Height" 60
"Resize" .Site.Params.ImageProcessing.Thumbnail.Enabled
"Attributes" (dict
"src" $image.Permalink
"alt" .Title
"loading" "lazy"
"width" $image.Width
"height" $image.Height
)
) }}
</div>
{{ end }}
</div>
@@ -42,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
@@ -16,7 +16,7 @@
</button>
</form>
<div class="search-result">
<div class="search-result hidden">
<h3 class="search-result--title section-title"></h3>
<div class="search-result--list article-list--compact"></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 -}}
+1 -1
View File
@@ -20,7 +20,7 @@ features = [
"search",
]
min_version = "0.154.0"
min_version = "0.157.0"
[author]
name = "Jimmy Cai"