我的网站目前是基于 Hugo 框架,并结合 virgo 主题来构建的。随着内容的扩展,我可能需要在同一篇文章中展示多种编程语言的代码片段。为了提升阅读体验,我希望通过选项卡切换的方式展示不同语言的代码,这样读者可以轻松查看自己感兴趣的代码实现,而无需反复滚动页面。
为了实现这一目标,我需要实现以下功能:
- 多语言代码展示:在文章中能够包含多个不同语言的代码片段;每个代码片段可以通过选项卡进行切换,避免长篇幅的代码段落占用页面空间。
- 语法高亮:每个代码片段需要支持语法高亮,以便读者更轻松地阅读和理解代码。
- 用户体验:选项卡应能够通过点击进行切换,且当前选中的语言应当高亮显示。
- 易于编辑:在文章中插入代码片段时,编辑方式应当简单直观,并且支持 Markdown 格式。
Hugo 允许我们自定义 Shortcode,这使得在 Markdown 文档中嵌入自定义的功能变得非常方便。因此,我计划自定义一个 Shortcode 来实现上述需求,使其能够解析和渲染多语言代码片段,并通过选项卡切换展示不同的代码内容。
以下是名为 code-tabs.html 的 Shortcode:
<div class="code-tabs-container">
<!-- 首先渲染所有内部内容,以确保所有 code-tab 已经执行 -->
{{ .Inner }}
<!-- 获取存储在 Scratch 中的 tabs 数据,使用 code-tabs 的唯一 id -->
{{ $id := .Get "id" }} {{ $tabs := $.Page.Scratch.Get (printf "tabs-%s" $id)
| default (slice) }}
<ul class="code-tabs-list">
{{ if gt (len $tabs) 0 }} {{ range $tabs }}
<li class="code-tab">
<button class="code-tab-button" data-target="tab-content-{{ .id }}">
{{ .id | $.Page.RenderString }}
</button>
</li>
{{ end }} {{ else }}
<p>No tabs data available</p>
{{ end }}
</ul>
<div class="code-tabs-contents">
{{ range $tabs }}
<div id="tab-content-{{ .id }}" class="code-tab-content">
{{ highlight .content .lang "" | $.Page.RenderString }}
</div>
{{ end }}
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const tabContainers = document.querySelectorAll(".code-tabs-container");
tabContainers.forEach((container) => {
const tabs = container.querySelectorAll(".code-tab-button");
const contents = container.querySelectorAll(".code-tab-content");
tabs.forEach((tab, index) => {
tab.addEventListener("click", function () {
tabs.forEach((t) => t.classList.remove("active"));
contents.forEach((c) => c.classList.remove("active"));
tab.classList.add("active");
contents[index].classList.add("active");
});
});
if (tabs.length > 0) {
tabs[0].classList.add("active");
contents[0].classList.add("active");
}
});
});
</script>
code-tab.html:
{{ $id := .Get "parentID" }}
{{ $tabs := $.Page.Scratch.Get (printf "tabs-%s" $id) | default (slice) }}
{{ $tab := dict "id" (.Get "id") "lang" (.Get "lang") "content" (.Inner | safeHTML) }}
{{ $.Page.Scratch.Set (printf "tabs-%s" $id) ($tabs | append $tab) }}
// assets/scss/partials/codeTab.scss
.code-tabs-container {
border: 1px solid #ddd;
border-radius: 5px;
margin: 20px 0;
display: flex;
flex-direction: column;
.code-tabs-list {
list-style: none;
display: flex;
padding: 0;
margin: 0;
// border-bottom: 1px solid #ddd;
.code-tab {
// margin-right: 10px;
.code-tab-button {
background: none;
border: none;
padding: 10px 20px;
cursor: pointer;
font-size: 14px;
border-bottom: 2px solid transparent;
transition: all 0.3s ease;
&.active {
border-bottom: 2px solid #007acc;
font-weight: bold;
}
&:hover {
color: #007acc;
}
}
}
}
// .code-tabs-contents {
// // padding: 0; /* 移除上下 padding */
// // margin: 0; /* 确保没有额外的 margin */
// // flex: 1; /* 让内容部分自动填充高度 */
// // display: flex; /* Flex 布局 */
// // flex-direction: column; /* 垂直排列内容 */
// }
.code-tab-content {
display: none;
// overflow-x: auto;
// flex: 1; /* 让内容部分自动填充父容器的高度 */
// max-width: 100%;
// white-space: pre-wrap;
// word-wrap: break-word;
// padding: 0; /* 去掉内部 padding */
&.active {
display: block;
}
}
}
基于以上设计的 Shortcode,我可以在 Markdown 文档中使用如下的方式
实现如下的卡片式显示:
from abstractcollection import AbstractCollection
class AbstractStack(AbstractCollection):
"""An abstract stack implementation."""
def __init__(self, sourceCollection):
AbstractCollection.__init__(self, sourceCollection)
def add(self, item):
self.push(item)
#include <iostream>
using namespace std;
int main() {
cout << "Hello, world!" << endl;
return 0;
}
console.log("Hello, world!");
该shortcode也可以嵌入到protected shortcode中 (该块内容在输入密码:hakuna-mat
后,将显示和上面一致的内容):