Hugo Blowfish 主題的客製化
Published:Updated:
超連結
游標移動到連結時變色+底線。範例
在 assets/css/custom.css 新增
.article-content a {
text-decoration: underline;
text-decoration-skip-ink: none;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
text-decoration-color: rgba(var(--color-primary-900), 0.6);
color: rgba(var(--color-primary-900), 1);
}
.article-content a:hover {
text-decoration: underline;
text-decoration-color: rgba(var(--color-primary-600), 1);
color: rgba(var(--color-primary-600), 1);
}
.dark .article-content a {
text-decoration-color: rgba(var(--color-primary-600), 0.6);
color: rgba(var(--color-primary-600), 1);
}
.dark .article-content a:hover {
text-decoration: underline;
text-decoration-color: rgba(var(--color-primary-400), 1);
color: rgba(var(--color-primary-400), 1);
}
文章存檔頁面
列出所有文章,無論是哪一種分類,範例。
在 layouts/_default/archive.html 新增以下:
archive.html
{{ define "main" }}
{{ if .Params.showHero | default (.Site.Params.article.showHero | default false) }}
{{ $style := .Params.heroStyle | default .Site.Params.article.heroStyle | default "basic" }}
{{ $heroPath := print "hero/" $style ".html" }}
{{ if templates.Exists (print "partials/" $heroPath) }}
{{ partial $heroPath . }}
{{ else }}
{{ partial "hero/basic.html" . }}
{{ end }}
{{ end }}
{{ $currentLang := $.Site.Language.Lang }}
{{ $months := index $.Site.Data.months $currentLang }}
<script>
document.addEventListener("DOMContentLoaded", function () {
const postItems = document.querySelectorAll(".post-item")
postItems.forEach((item) => {
item.addEventListener("mouseenter", function () {
this.classList.add("post-item-hover")
})
item.addEventListener("mouseleave", function () {
this.classList.remove("post-item-hover")
})
})
const monthTitles = document.querySelectorAll(".month-title")
monthTitles.forEach((title) => {
const [color1, color2, color3] = generateContrastingColors(false)
const [color4, color5, color6] = generateContrastingColors(true)
const angle1 = generateRandomAngle()
const angle2 = generateRandomAngle()
title.style.setProperty("--gradient-1", `linear-gradient(${angle1}deg, ${color1}, ${color2})`)
title.style.setProperty("--gradient-2", `linear-gradient(${angle2}deg, ${color4}, ${color5})`)
})
})
function generateContrastingColors(isDarkMode) {
const baseColor = Math.random() * 360
const c1 = baseColor
const c2 = (baseColor + 180 + Math.random() * 60 - 15) % 360
const c3 = (baseColor + 90 + Math.random() * 60 - 15) % 360
const c4 = (baseColor + 270 + Math.random() * 60 - 15) % 360
const adjustColor = (color, isDarkMode) => {
const lightness = isDarkMode ? 40 : 75
const saturation = 100
return `hsl(${color}, ${saturation}%, ${lightness}%)`
}
const color1 = adjustColor(c1, isDarkMode)
const color2 = adjustColor(c2, isDarkMode)
const color3 = adjustColor(c3, isDarkMode)
return [color1, color2, color3]
}
function generateRandomAngle() {
const step = 15
const minAngle = 30
const maxAngle = 150
const totalSteps = (maxAngle - minAngle) / step + 1
const randomStep = Math.floor(Math.random() * totalSteps)
return minAngle + randomStep * step
}
</script>
<style>
.timeline {
max-width: 850px;
margin: 0 auto;
padding: 20px;
}
.year-container {
margin-bottom: 40px;
border: 1px solid #e0e0e0;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 60px rgba(0, 0, 0, 0.4);
background-color: rgba(var(--color-neutral-300), 0.56);
}
.year {
background-color: rgba(var(--color-neutral-400), 0.91);
padding: 10px 20px;
font-size: 24px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.post-count {
background-color: #007bff;
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 14px;
}
.content {
padding: 20px;
}
.month-container {
margin-bottom: 30px;
}
.month-title {
font-size: 1.2rem;
font-weight: 700;
color: #333;
background: var(--gradient-1);
padding: 0.6rem 1rem;
border-radius: 40px;
border: transparent;
padding-bottom: 5px;
margin-bottom: 15px;
text-transform: uppercase;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.post-list {
list-style-type: none;
padding: 2px;
}
.post-item {
margin-bottom: 10px;
padding: 10px;
border-radius: 5px;
transition: background-color 0.3s;
transition:
transform 0.3s,
z-index 0.3s,
box-shadow 0.3s,
background-color 0.3s;
background: #ffffff;
}
.post-item-hover {
transform: scale(1.01);
}
.post-item-inner {
display: grid;
grid-template-areas:
"date details"
"date category";
grid-template-columns: auto 1fr;
gap: 1px;
background-color: transparent;
padding-bottom: 0.4em;
}
.post-date {
font-size: 14px;
color: rgb(var(--color-neutral-500));
margin-right: 11px;
margin-left: 4px;
white-space: nowrap;
min-width: 4em;
grid-area: date;
}
.post-details {
grid-area: details;
}
.post-title {
font-size: 20px;
color: rgb(var(--color-neutral-800)) !important;
text-decoration: none;
font-weight: bold;
}
.tag-and-category {
grid-area: category;
margin-bottom: -20px;
}
.dark .year {
background-color: rgba(var(--color-neutral-900), 0.95) !important;
color: rgb(var(--color-neutral-100));
}
.dark .year-container {
border: 1px solid rgb(var(--color-neutral-900));
background-color: rgba(var(--color-neutral-800), 0.3);
}
.dark .month-title {
color: rgb(var(--color-neutral-100));
background: var(--gradient-2);
}
.dark .post-item {
background-color: rgba(var(--color-neutral-800), 1);
}
.dark .post-item-hover {
background-color: rgba(var(--color-neutral-800), 0.8) !important;
}
.dark .post-date {
color: rgb(var(--color-neutral-100));
}
.dark .post-title {
color: rgb(var(--color-neutral-100)) !important;
}
@media (max-width: 768px) {
.timeline {
max-width: 100%;
margin: 0 auto;
padding: 0px;
}
.post-list {
padding: 2px;
}
.post-item {
width: 100%;
margin-bottom: 12px;
background-color: rgba(var(--color-neutral-100), 0.95);
border-radius: 8px;
overflow: hidden;
}
.post-item-inner {
display: grid;
grid-template-areas:
"date"
"details"
"category";
grid-template-columns: 1fr;
grid-template-rows: auto auto auto;
gap: 0;
padding: 5.5px;
margin-bottom: 1px;
}
.post-date {
margin-left: 0px;
grid-area: date;
margin-top: -1.2em;
margin-bottom: -0.8em;
font-size: 0.6em;
color: rgb(var(--color-neutral-500));
padding: 0px;
}
.post-details {
grid-area: details;
margin-bottom: 0.1rem;
padding: 0px;
}
.tag-and-category {
padding: 0px;
grid-area: category;
margin-top: 0px;
}
}
</style>
<article class="timeline">
<header id="single_header" class="mt-5 max-w-prose">
<h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">{{ .Title | emojify }}</h1>
</header>
<div class="article-content max-w-prose mb-20"><br>{{ .Content }}</div>
<section class="all-posts mt-8 text-neutral">
{{ $filteredPages := where .Site.RegularPages "Params.noArchive" "ne" true }}
{{ range $filteredPages.GroupByDate "2006" }}
<div class="year-container">
<div class="year">
{{ .Key }}
<span class="post-count">{{ len .Pages }}</span>
</div>
<div class="content">
{{ range .Pages.GroupByDate "January" }}
<div class="month-container">
<span class="month-title">{{ index $months .Key }}</span>
<ul class="post-list">
{{ range .Pages }}
<li class="post-item">
<div class="post-item-inner">
<div class="post-date">{{ .Date.Format "1月2日" }}</div>
<div class="post-details">
{{ if .Params.externalUrl }}
<a
href="{{ .Params.externalUrl }}"
class="post-title font-bold text-xl text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral"
target="_blank">
{{ .Title }}
<span
class="text-xs align-top cursor-default text-neutral-400 dark:text-neutral-500">
<span class="rtl:hidden">↗</span>
<span class="ltr:hidden">↖</span>
</span>
</a>
{{ else }}
<a
href="{{ .Permalink }}"
class="post-title font-bold text-xl text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral"
>{{ .Title }}</a
>
{{ end }}
</div>
{{ partial "custom/tag-and-category.html" . }}
</div>
</li>
{{ end }}
</ul>
</div>
{{ end }}
</div>
</div>
{{ end }}
</section>
</article>
{{ end }}
在 content/archives 新增 _index.md:
---
title: "所有文章"
layout: "archive"
description: "所有文章列表"
---
最後,在menus.zh-tw.toml新增:
[[footer]]
name = "所有文章"
pageRef = "archives"
weight = 40
閱讀進度
加上文章閱讀進度比例。
在 baseof.html 的 body 標籤結束前,也就是 </body> 前一行貼上
<div id="progress" style="position:fixed;top:0;left:0;height:3px;background:#3b82f6;width:0%;z-index:9999;"></div>
<script>
window.addEventListener('scroll',()=>{
const h=document.documentElement.scrollHeight-window.innerHeight;
document.getElementById('progress').style.width=(window.scrollY/h*100)+'%';
});
</script>
簡碼 - expand
warning
請愛用原生 HTML 語法而不是 shortcode!否則你將失去 Markdown 跨平台的優勢!
請直接在 Markdown 裡面這樣寫:
<details>
<summary>展開的標題</summary>
內容
</details>
或者你可以安裝 KKKZOZ/hugo-admonitions,他支援摺疊功能,這是 GFM blockquote (Github flavored markdown),因此非常多平台都支援此語法。
GFM 摺疊範例
GFM example.
自動加上編輯時間
在文章末放上編輯日期提醒。
在 layouts/_default/single.html 找到 {{ .Content }} ,在他下面貼上:
<br><br><br>
<div class="max-w-fit">
{{ if ne (.Lastmod.Format "2006-01-02") (.Date.Format "2006-01-02") }}
<div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl">
{{- $lastmodContent := partial "meta/date-updated.html" .Lastmod -}}
{{ (printf "文章更新:%s" $lastmodContent) | markdownify }}
</div>
{{ end }}
</div>
修改 metadata
更新
Google 以不再用 keywords 為 SEO 關鍵字所以沒必要改這個。
Google SEO 會參考 meta name,而 Blowfish 的 tags 優先於 keywords 關鍵字。想要有 SEO 同時不想要 tags 打一堆次要標籤的修改如下:
找到 layouts/partials/head.html 中的
{{ with .Params.Tags | default .Site.Params.keywords -}}
<meta name="keywords" content="{{ range . }}{{ . }}, {{ end -}}" />
{{- end }}
整段換成
{{- $mytags := .Params.Tags | default slice -}}
{{- $mykeywords := .Params.Keywords | default .Site.Params.keywords -}}
{{- $allKeywords := $mytags | append $mykeywords | uniq -}}
{{ if $allKeywords }}
<meta name="keywords" content="{{ delimit $allKeywords ", " }}" />
{{ end }}