增加「返回頂部」按鈕
=> 具體請見 Hugo Stack 主題創建返回頂部按鈕
增加移動端 TOC
我的設想是:
- 點擊 TOC 按鈕后,從底部滑出目錄欄
- 點擊 任意標題、目錄欄外或叉號,目錄欄關閉
- 向下滑動頁面時,按鈕隱藏,向上滑動一段后按鈕顯示
HTML 部分
同樣寫入 layouts/partials/footer/custom.html
點擊顯示
<!-- ToC button -->
<div id="toc-button" class="toc-btn">
<button>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
<path
d="M8 6L21 6.00078M8 12L21 12.0008M8 18L21 18.0007M3 6.5H4V5.5H3V6.5ZM3 12.5H4V11.5H3V12.5ZM3 18.5H4V17.5H3V18.5Z" />
</svg>
</button>
</div>
<!-- ToC drawer -->
<div id="toc-drawer" class="toc-drawer">
<div class="drawer-header">
<!-- icon of TOC -->
{{ partial "helper/icon" "hash" }}
<h2> TABLE OF CONTENT</h2>
<button id="close-drawer" class="close-btn">✕</button>
</div>
<div class="widget--toc">
<!-- Use hugo generated ToC -->
{{ .TableOfContents }}
</div>
</div
<!-- ToC Overlay -->
<div id="drawer-overlay" class="overlay"></div>
首先是拷貝 back-to-top 的按鈕部分,然後是上滑的目錄欄和剩餘陰影部分,這裏可以參考 partials/widget/toc.html
CSS 部分
寫入 assets/scss/partials/footer.scss
點擊顯示
/* Buttons CSS */
.button-float {
padding: 8px;
border: none;
border-radius: 8px;
background-color: var(--button-float-bg);
box-shadow: 0 3px 5px var(--button-float-shadow);
cursor: pointer;
width: 36px;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
&:hover {
background-color: var(--button-float-bg-hover);
box-shadow: 0 5px 8px var(--button-float-shadow-hover);
transform: translateY(-2px);
}
svg {
width: 16px;
height: 16px;
stroke: var(--button-float-arrow);
transition: stroke 0.3s ease;
}
&:hover svg {
stroke: var(--button-float-arrow);
}
}
/* Back-to-top button */
.back-to-top {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
display: none;
button {
@extend .button-float;
}
}
/* ToC button */
.toc-btn {
position: fixed;
bottom: 60px;
right: 20px;
z-index: 1001;
display: none;
@media (max-width: 1023px) {
display: block;
}
body.homepage &,
body.template-archives &,
body.template-search & {
display: none !important;
}
button {
@extend .button-float;
}
}
/* ToC drawer */
.toc-drawer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 60%;
background: var(--button-float-bg);
border-radius: 12px 12px 0 0;
transform: translateY(100%);
transition: transform 0.3s ease-in-out;
z-index: 1001;
display: flex;
flex-direction: column;
.widget--toc {
border-radius: 0px;
box-shadow: none;
}
}
.drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px;
border-radius: 12px 12px 0 0;
border-bottom: 2px dashed var(--body-background);
font-size: 14px;
font-weight: bold;
color: var(--button-float-arrow);
background: var(--button-float-bg-hover);
h2 {
margin: 10px 0;
}
}
.close-btn {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: var(--button-float-arrow);
}
/* ToC background overlay */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(5px);
z-index: 1000;
display: none;
}
合并了 back-to-top
和 toc-button
的樣式,減少代碼重複。另外使用現有的 widget--toc
樣式,單獨更改了部分屬性讓顯示不至於突兀。
JS 部分
同樣的路徑,合并了 back-to-top
的監聽和動作:
點擊顯示
<script>
document.addEventListener("DOMContentLoaded", () => {
const backToTop = document.getElementById("back-to-top");
const tocButton = document.getElementById("toc-button");
let lastScrollY = window.scrollY;
let lastShowY = window.scrollY; // Stores last Y position when buttons were shown
window.addEventListener("scroll", () => {
let currentScrollY = window.scrollY;
if (currentScrollY < lastScrollY) { // User is scrolling up
if (lastShowY - currentScrollY > 50) { // Ensure user scrolled up 50px
tocButton.style.display = "";
// Ensure back to top display only after 400px
if (window.scrollY > 400) {
backToTop.style.display = "block";
}
// Adjust TOC button position if both are visible
// if (backToTop.style.display != "none" && tocButton.style.display != "none") {
// backToTop.style.bottom = "60px";
// tocButton.style.bottom = "20px";
// // }
}
} else {
// User is scrolling down, reset reference position
lastShowY = currentScrollY;
backToTop.style.display = "none";
tocButton.style.display = "none";
}
lastScrollY = currentScrollY; // Update last scroll position
});
// Back to Top Click Event
backToTop.addEventListener("click", function () {
window.scrollTo({ top: 0, behavior: "smooth" });
});
});
// Toc Drawer open and close
document.addEventListener("DOMContentLoaded", () => {
const tocButton = document.getElementById("toc-button");
const tocDrawer = document.getElementById("toc-drawer");
const drawerOverlay = document.getElementById("drawer-overlay");
const closeDrawer = document.getElementById("close-drawer");
const tocLinks = document.querySelectorAll("#toc-drawer li a"); // Get all TOC links
function openTOC() {
tocDrawer.style.transform = "translateY(0)";
drawerOverlay.style.display = "block";
}
function closeTOC() {
tocDrawer.style.transform = "translateY(100%)";
drawerOverlay.style.display = "none";
}
tocButton.addEventListener("click", openTOC);
closeDrawer.addEventListener("click", closeTOC);
drawerOverlay.addEventListener("click", closeTOC);
tocLinks.forEach(link => {
link.addEventListener("click", closeTOC);
});
});
</script>
碰到的一些問題
❌ 所有頁面都會顯示 ToC
完成三個部分后發現不止 post 頁面含有 ToC 按鈕,所有頁面都出現了(這點在返回按鈕中沒有任何問題),所以在 CSS 中額外添加:
.toc-btn {
...
body.homepage &,
body.template-archives &,
body.template-search & {
display: none !important;
}
}
archives
和 search
分別都有名稱,主頁沒有,因此需要在 layouts/_default/baseof.html
中添加:
- <body class="{{ block `body-class` . }}{{ end }}">
+ <body class="{{ block `body-class` . }}homepage{{ end }}">
❌ 桌面端 ToC 重複
最後修改的是去除桌面端的 ToC 按鈕(我看有的操作是全部替換原有的目錄欄,但我還挺喜歡這種直觀的顯示)。一開始我想通過一個 JS 判斷文檔中是否已有 ToC widget
以決定 ToC button
的顯示(基本邏輯同下滑不顯示),但想想就很麻煩,於是轉用 CSS,畢竟寬度縮小后 ToC widget
就消失了,可以參考這個。不過找了半天只找到 right-sidebar breakpoint 變化,於是索性手動輸入數字:
.toc-btn {
...
@media (max-width: 1023px) {
display: block;
}
}
❓ 顯示與隱藏的時機
現在的設定是 ToC 默認顯示,因此點擊進入一篇文章后會立刻看見按鈕,只能説有好(能夠預覽結構)有壞(顯得很突兀),反之也是。所以總之現在能跑,就這樣不改動了。
增加評論功能
本來的首選是 Remark42 (誰讓它名字裏有「宇宙的答案」呢🙄),但搞了半天總之不是很方便管理,於是轉向了 Twikoo ,按照官方文檔一步一步設定基本沒有問題(雖然期間碰見 MongoDB URL 設定有誤,版本鎖定等亂七八糟,但忘記怎麽解決地解決了,之後碰上再説吧)。
沒想到 Github 爲我保留了這不知道怎麽解決的解決:
[comments.twikoo]
envId = "my-envID"
region = "my-region"
- path = "/twikoo"
+ path = "window.location.pathname"
lang = "en"
至於版本,直接搜索當前版本號然後就可以找到地方更改了。
評論表情 Stickers
參考文章: 嘰嘰乞乞:Twikoo評論系統的個性化設置
第一眼看到某站的表情作爲默認選項簡直立刻生理不適,火速搜尋並更改為 Blobcat,保證了一些 San 值。

可爱泡泡猫
增加外鏈符號
參考文章: 第三夏尔 | Third Shire:Hugo Stack主题装修笔记
給外鏈添加了一個意味「離開本站」的符號,同時一并把超鏈接設定為 rel="noopener noreferrer nofollow"
增加字數統計
更改設定
第一步需要確定 config.toml
中含有中日韓字符設定開啓,即 hasCJKLanguage = true
我另外在 params.toml
中增加了一行 wordCount = true
方便後續變動(雖然好多月以後看這個似乎和 readingTime
互相搞混但也不知道怎麽一回事……)
添加代碼
在 /layouts/partials/article/components/details.html
中添加代碼:
點擊顯示
{{ $showWordCount := .Params.wordCount | default (.Site.Params.article.wordCount) }}
...
{{ if $showWordCount}}
<div>
{{ partial "helper/icon" "word-count" }}
<time class="article-time--reading">
{{ T "article.wordCount" .WordCount }}
</time>
</div>
{{ end }}
本質上還是拷貝原有的時間顯示,所以也沿用 Style 的設定。
更改代碼塊樣式
參考文章: Naive Koala:Hugo Stack 魔改美化
我做了些改動,主要是:
- 如果沒有填寫具體語言,返回
TEXT
而不是默認的FALLBACK
- 符合樣式增加了一個
toolbar
,實在不喜歡那個飄浮的拷貝按鈕
寫在原有的 main.ts
裏:
點擊顯示
...
/**
* Add copy button to code block
*/
const highlights = document.querySelectorAll('.article-content div.highlight');
const copyText = '✂ Copy';
const copiedText = '✔ Copied';
highlights.forEach(highlight => {
const codeBlock = highlight.querySelector('code[data-lang]');
// console.log("Raw data-lang:", codeBlock?.getAttribute('data-lang'));
if (!codeBlock) return;
// Get language
const rawLang = codeBlock.getAttribute('data-lang');
const lang = (rawLang === "fallback" || !rawLang) ? "TEXT" : rawLang.toUpperCase();
// console.log("Final language:", lang);
const langTag = createButton(lang, 'languageTagButton');
// New copy button
const copyButton = createButton(copyText, 'copyCodeButton');
// Create toolbar
const toolbar = document.createElement('div');
toolbar.classList.add('toolbar');
toolbar.appendChild(langTag);
toolbar.appendChild(copyButton);
highlight.insertBefore(toolbar, highlight.firstChild);
// Copy function
copyButton.addEventListener('click', () => {
navigator.clipboard.writeText(codeBlock.textContent)
.then(() => {
copyButton.textContent = copiedText;
setTimeout(() => {
copyButton.textContent = copyText;
}, 1000);
})
.catch(err => {
alert(err);
console.error('Copy failed:', err);
});
});
});
// helper function
function createButton(text, className) {
const button = document.createElement('button');
button.innerHTML = text;
button.classList.add(className);
return button;
}
new StackColorScheme(document.getElementById('dark-mode-toggle'));
另外把原先的 assets/scss/partials/layout/article.scss
中 highlight
部分改掉了:
點擊顯示
.highlight {
max-width: 102% !important;
padding: 0px;
position: relative;
border-radius: 8px;
margin-left: -8px !important;
margin-right: -2px;
box-shadow: var(--shadow-l1) !important;
// keep Codeblocks LTR
[dir="rtl"] & {
direction: ltr;
}
pre {
margin: initial;
padding: 15px 25px;
margin: 0;
width: auto;
border-radius: 0 0 8px 8px;
}
.toolbar {
position: relative;
width: 100%;
height: 30px;
background-color: var(--code-toolbar-color);
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 16px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
.languageTagButton {
background: none;
border: none;
font-size: 12px;
font-family: var(--code-font-family);
color: var(--code-button-color);
cursor: default;
}
.copyCodeButton {
background: none;
border: none;
color: var(--code-button-color);
font-size: 12px;
cursor: pointer;
}
}
}
總之感覺這個 code 部分的樣式有很多,讓人暈頭轉向,但是能跑就行,嗯 (ˉ﹃ˉ)
多語言設定
其實我不是很能理解 Hugo 的多語言結構。我的理解是它按照 站點/語言/頁面
排序,而我的設想是 站點/頁面/語言
,這樣一來頁面即文章是更高一級的存在。這樣 文章 - 語言 是一對多的關係,主頁是唯一的。現在這個關係是建立在 站點 上,因此不同語言有不同版本的主頁,導致無法統計獨特文章(例如某篇我想用西語寫而不是中文,這樣一來如果不相應創建西語站點那麽就看不到那篇文章)。
然後花了很多時間折騰這個獨特的 URL,但是非常複雜又不符合 Hugo 自己的簡便方法非常不便捷,因此作罷選擇了 它提供的方法 。
我的倔强讓我想讓中文主頁也顯示英語(以維持首頁一致性),這個無法通過 config
完成,一種「能跑就行」的策略是去 i18n
裏把目標語言改成和英文一樣 ㄟ( ▔, ▔ )ㄏ
Timeout 超時設定
在我瘋狂往巴黎游記添加許多照片后(~150MB),Cloudflare Build 報錯:
點擊查看 Error
Error: error building site: render: failed to render pages: [...]: execute of template failed: template:
partials/article-list/default.html:3:7: executing "partials/article-list/default.html" at <partial "article/
components/header" .>: error calling partial: partial "article/components/header" timed out after 30s. This is
most likely due to infinite recursion. If this is just a slow template, you can try to increase the 'timeout'
config setting.
看到這無情的 most likely due to infinite recursion ,心中不免一涼,讓我找哪裏循環引用無疑是大海撈針,於是寄希望於後者。
搜尋 Hugo Stack 的 Issues,找到有人有
相同問題
。於是嘗試在 config.toml
中修改設定 timeout = 180
然而并沒有任何反應;另外覺得是 Cloudflare 的問題,又用 Github pages render 卻也是同樣問題。
那個階段發現有用的 work round 有:
- 把長文章拆分成幾個部分,有時有效
- 使用批量工具壓縮圖片,但會很模糊
- 本地 Build 完成之後上載到另一個 branch,Cloudflare 不選擇 framework 可以部署
真的很麻煩又不穩定,有一回突然發瘋把 timeout = 100000
寫上,結果大家都很順利了(。現在看 Cloudflare Build 大概需要 50 秒,不知道未來該怎麽辦。
我覺得主要問題還是在 Hugo Stack 的 Picture Gallery 特點,似乎在為不同媒介準備不同大小的縮略圖,因此需要耗費很多時間。不知道未來是否會有更多問題,以及如何改善,但現在還能跑那就先這樣…
Shortcode 短代碼
摺叠内容
參考文章: Stackoverflow: Add collapsible section in hugo ; 林中阴影:在 Hugo 中折叠部分内容
摺叠效果
這裡是一些 Markdown 內容。
- 列表項目
- 另一個列表項
注意:
- 應使用 區塊模式(Block Shortcode), 即
{% shortcode %}
。如果使用 内聯模式(Inline Shortcode),即{< shortcode >}
會導致無法讀到 Summary 内容(實際使用時應爲雙括號)。
Neodb 卡片
參考文章: 眠于水月间:引用 NeoDB 条目 ; 椒盐豆豉:Hugo 装修小记之二
可能參考代碼沒有匹配 Hugo 版本(目前基於 v0.141.0
),套用時報錯:
ERROR deprecated: data.GetJSON was deprecated in Hugo v0.123.0 and will be removed in Hugo 0.142.0.
use resources.Get or resources.GetRemote with transform.Unmarshal.
於是進行了一番修改,另外把 dbType
和 dbUuid
分開查找。
點擊顯示
{{ $dbUrl := .Get 0 }}
{{ $dbApiBase := "https://neodb.social/api" }}
{{ $dbType := "" }}
{{ $dbUuid := "" }}
<!-- Extract item type and UUID -->
{{ if (findRE `^.*neodb\.social\/.*` $dbUrl) }}
{{ $dbType = replaceRE `.*neodb.social\/([^\/]+)\/.*` "$1" $dbUrl }}
{{ $dbUuid = replaceRE `.*neodb.social\/[^\/]+\/([^\/]+)` "$1" $dbUrl }}
{{ end }}
<!-- Construct full API URL -->
{{ $fullApiUrl := printf "%s/%s/%s" $dbApiBase $dbType $dbUuid }}
<!-- Fetch data from API -->
{{ $remoteResource := resources.GetRemote $fullApiUrl }}
{{ if $remoteResource }}
{{ $dbFetch := $remoteResource | transform.Unmarshal }}
...
可愛的效果,這樣是不是看書的動力更足了呢 ┌( ´_ゝ` )┐
總之,目前的功能和樣式與我的期望沒什麽大差別了。希望還是專注於内容而不是站點本身吧(。另外 Git 真是個好東西,完全彌補了差記性,很值得一番研究……