今天有网友邮件我咨询我现在的主题 Eureka 的一些自定义配置,他想参考一下。由于我的博客仓库是私有的,所以就写一篇文章简单整理一下。
Eureka 是前段时间群友推荐给我的,纯白的朴素风格同时提供了暗色模式瞬间我就喜欢上了。将其 clone
到 Hugo 博客目录 themes/hugo-eureka
下,config.toml
中配置 theme = "hugo-eureka"
即可使用上该款主题。为了方便主题的更新,我将我所有自定义的模板都放在了 layouts
目录下。Hugo 会将主题目录和 layouts
目录下的文件进行合并,并优先使用 layouts
目录中的同名文件。这样之后我只需要单纯的更新 thtmes/hugo-eureka
目录即可。
首页
相较于 Eureka 主题的默认首页,我个人还是比较喜欢传统博客的两栏布局,左侧显示模块列表,右侧显示文章列表,所以我需要自定义首页模板。拷贝以下内容创建 layouts/index.html
文件即可实现同款。
{{ define "main" }}
<div class="pl-scrollbar">
<div class="w-full max-w-screen-xl lg:px-4 xl:px-8 mx-auto">
<div class="max-w-screen-xl mx-auto" style="padding-top: 3rem">
<div class="bg-local bg-cover">
<img class="day" src="/banner-day.png" />
<img class="dark" src="/banner.png" />
</div>
</div>
<!-- <article class="mx-6 my-7">
<h1 class="font-bold text-3xl text-primary-text"></h1>
</article> -->
<div class="grid grid-cols-2 lg:grid-cols-8 gap-4 lg:pt-12">
<div class="col-span-2 sidebar">
<div class="widget bg-secondary-bg rounded p-6">
<h2 class="widget-title">最新文章</h2>
<ul class="widget-list">
{{- $recent := default 5 .Site.Params.numberOfRecentPosts }}
{{- $posts := where (where .Site.RegularPages "Permalink" "!=" .Permalink) "Type" "in" .Site.Params.mainSections }}
{{- range first $recent $posts }}
<li>
<a href="{{ .RelPermalink }}" class="nav-link">{{ .Title }}</a>
</li>
{{- end }}
</ul>
</div>
<div class="widget bg-secondary-bg rounded p-6">
{{ $walineURL := .Site.Params.comment.waline.serverURL }}
<h2 class="widget-title ">最近回复</h2>
<ul class="widget-list recentcomments">
{{ $resp := getJSON $walineURL "/comment?type=recent&count=10" }}
{{ range first 10 $resp }}
<li class="recentcomments">
<a href="{{.Site.BaseURL}}{{ .url }}">{{ .nick }}</a>:{{ .comment | safeHTML | truncate 22 }}
</li>
{{ end }}
</ul>
</div>
<div class="widget bg-secondary-bg rounded p-6">
<h2 class="widget-title">友情链接</h2>
<ul class="widget-list">
{{ range .Site.Menus.friends }}
<li>
<a href="{{ .URL }}">{{ .Name }}</a>
</li>
{{ end }}
</ul>
</div>
<div class="widget bg-secondary-bg rounded p-6">
<h2 class="widget-title">管理</h2>
<ul class="widget-list">
<li>
<a href="/admin">🛠 后台管理</a>
</li>
<li>
<a href="{{ .Site.Params.comment.waline.serverURL }}/ui">💬 评论管理</a>
</li>
</ul>
</div>
</div>
<div class="col-span-2 lg:col-span-6 bg-secondary-bg rounded px-6 py-8">
<div class="bg-secondary-bg rounded overflow-hidden px-4 divide-y">
{{ range .Paginator.Pages }}
<div class="px-2 py-6">
{{ partial "components/summary-plain.html" . }}
</div>
{{ end }}
</div>
{{ template "_internal/pagination.html" . }}
</div>
</div>
</div>
</div>
{{ end }}
其中顶部还增加了一组暗色模式切换的横幅图片,添加以下 CSS 内容至 layouts/partials/custom-head.html
文件中,不存在的话需要新建。
.widget + .widget {
margin-top: 1rem;
}
.widget-title {
font-weight: bold;
margin-bottom: 1rem;
}
.widget-list li {
font-size: 0.9rem;
}
.bg-cover img {
opacity: 1;
transition: all .5s ease-in-out;
}
.bg-cover img.dark {
opacity: 0;
height: 0;
}
.dark .bg-cover img.day {
opacity: 0;
height: 0;
}
.dark .bg-cover img.dark {
opacity: 1;
height: auto;
}
左侧的模块中,评论是使用了本人自研的 Waline 评论系统并进行了一定的改造,具体可参见我之前的文章《静态博客如何高性能插入评论》。当然也可以直接使用 Waline 自带的最近评论挂件。
友情链接则是在 config.toml
中按照如下格式进行配置的。
[[menu.friends]]
name = "童欧巴博客"
url = "https://hungryturbo.com/"
weight = 20
[[menu.friends]]
identifier = "QingXu"
name = "QingXu"
url = "https://blog.qingxu.live"
weight = 19
[[menu.friends]]
identifier = "蜘蛛抱蛋"
name = "蜘蛛抱蛋"
url = "https://blog.zzbd.org/"
weight = 18
后台管理则是使用了 forestry 提供的服务,它支持提供在线后台进行文章、页面和其它配置的管理。评论管理则是链接到了 Waline 服务的后台面板中。
Metadata
Eureka 主题的文章 metadata 显示分为列表页和详情页两个,分别对应 post_metadata.html
和 post_metadata_full.html
两个文件。我们在 layouts/partials/
目录下新建这两个文件用来覆盖主题默认的文件。
2021-03-13 更新: 更新后的 Eureka 统一使用了
components/post-metadata.html
显示文章的 metadata,代码和之前的layouts/partials/post_metadata.html
是一致的。
{{/* layouts/partials/components/post_metadata.html */}}
<div class="flex flex-wrap flex-row items-center my-2 text-tertiary-text">
<div class="mr-6 my-2">
<i class="fas fa-calendar mr-1"></i>
<span>{{ .Date.Format (.Site.Params.dateFormat | default "2006-01-02") }}</span>
</div>
{{- $slug := printf "/%s.html" .Slug}}
{{- $commentsData := (partialCached "utils/get-comments.html" .)}}
{{- $comments := slice }}
{{- range where $commentsData "url" "==" $slug}}
{{$comments = $comments | append .}}
{{- end}}
{{- $count := len $comments}}
<div class="mr-6 my-2">
<a href="{{ .Permalink }}#waline-comments" title="{{ .Title }}">
<i class="fas fa-comment mr-1"></i>
<span>{{- if gt $count 0}}{{$count}} 条评论{{else}}暂无评论{{end -}}</span>
</a>
</div>
<div class="mr-6 my-2">
<i class="fas fa-clock mr-1"></i>
<span>{{ i18n "readingTime" . }}</span>
</div>
{{ with .GetTerms "categories" }}
<div class="mr-6 my-2">
<i class="fas fa-folder mr-1"></i>
{{ range $index, $value := . }}
{{ if gt $index 0 }}
<span>, </span>
{{ end -}}
<a href="{{ .Permalink }}" class="hover:text-eureka">{{ .LinkTitle }}</a>
{{ end }}
</div>
{{ end }}
{{ with .GetTerms "series" }}
<div class="mr-6 my-2">
<i class="fas fa-th-list mr-1"></i>
{{ range $index, $value := . }}
{{ if gt $index 0 }}
<span>, </span>
{{ end -}}
<a href="{{ .Permalink }}" class="hover:text-eureka">{{ .LinkTitle }}</a>
{{ end }}
</div>
{{ end }}
</div>
post_metadata.html
主要是增加了评论条数的显示,而 post_metadata_full.html
中还增加了 Markdown 原文链接的显示。关于如何生成 Markdown 原文链接,可以参考我之前的文章《Hugo 之旅》。
{{/* layouts/partials/post_metadata_full.html */}}
<div class="flex flex-wrap flex-row items-center my-2 text-tertiary-text">
<div class="mr-6 my-2">
<i class="fas fa-calendar mr-1"></i>
<span>{{ .Date.Format (.Site.Params.dateFormat | default "2006-01-02") }}</span>
</div>
{{$resp := getJSON "https://imerd.comment.lithub.cc/comment?type=count&url=/" .Slug ".html" }}
<div class="mr-6 my-2">
<a href="{{ .Permalink }}#waline-comments" title="{{ .Title }}">
<i class="fas fa-comment mr-1"></i>
<span>{{- if gt $resp 0}}{{$resp}} 条评论{{else}}暂无评论{{end -}}</span>
</a>
</div>
{{ if eq .Type "posts" -}}
{{ with .OutputFormats.Get "MarkDown" -}}
<div class="mr-6 my-2">
<a href="{{ .Permalink }}">
<i class="fas fa-book mr-1"></i>
<span>阅读Markdown格式</span>
</a>
</div>
{{- end }}
{{ end }}
<div class="mr-6 my-2">
<a href="{{ .Permalink }}">
<i class="fas fa-pen mr-1"></i>
<span>{{ .WordCount }} 字</span>
</a>
</div>
<div class="mr-6 my-2">
<i class="fas fa-clock mr-1"></i>
<span>{{ i18n "readingTime" . }}</span>
</div>
{{ with .GetTerms "categories" }}
<div class="mr-6 my-2">
<i class="fas fa-folder mr-1"></i>
{{ range $index, $value := . }}
{{ if gt $index 0 }}
<span>, </span>
{{ end -}}
<a href="{{ .Permalink }}" class="hover:text-eureka">{{ .LinkTitle }}</a>
{{ end }}
</div>
{{ end }}
{{ with .GetTerms "series" }}
<div class="mr-6 my-2">
<i class="fas fa-th-list mr-1"></i>
{{ range $index, $value := . }}
{{ if gt $index 0 }}
<span>, </span>
{{ end -}}
<a href="{{ .Permalink }}" class="hover:text-eureka">{{ .LinkTitle }}</a>
{{ end }}
</div>
{{ end }}
</div>
搜索框
搜索也是博客比较重要的功能,为了方便我在顶部增加了搜索框。创建 layouts/partials/header.html
文件用来覆盖默认的头部模板。
{{/* layouts/partials/header.html */}}
<script>
let storageColorScheme = localStorage.getItem("lightDarkMode")
{{- if eq .Site.Params.colorScheme "light" }}
if ((storageColorScheme == 'Auto' && window.matchMedia("(prefers-color-scheme: dark)").matches) || storageColorScheme == "Dark") {
document.getElementsByTagName('html')[0].classList.add('dark')
}
{{- else if eq .Site.Params.colorScheme "dark" }}
if ((storageColorScheme == 'Auto' && window.matchMedia("(prefers-color-scheme: light)").matches) || storageColorScheme == "Light") {
document.getElementsByTagName('html')[0].classList.remove('dark')
}
{{- else }}
if (((storageColorScheme == 'Auto' || storageColorScheme == null) && window.matchMedia("(prefers-color-scheme: dark)").matches) || storageColorScheme == "Dark") {
document.getElementsByTagName('html')[0].classList.add('dark')
}
{{- end }}
</script>
<nav class="flex items-center justify-between flex-wrap px-4 py-4 md:py-0">
<a href="{{ "/" | relLangURL }}" class="mr-6 text-primary-text text-xl font-bold">{{ .Site.Title }}</a>
<button id="navbar-btn" class="md:hidden flex items-center px-3 py-2" aria-label="Open Navbar">
<i class="fas fa-bars"></i>
</button>
<div id="target"
class="hidden block md:flex md:flex-grow md:justify-between md:items-center w-full md:w-auto text-primary-text z-20">
<div class="md:flex md:h-16 text-sm md:flex-grow pb-4 md:pb-0 border-b md:border-b-0">
{{- $relPermalink := .RelPermalink }}
{{- range .Site.Menus.main }}
{{- $url := .URL | relLangURL }}
<a href="{{ $url }}" class="block mt-4 md:inline-block md:mt-0 md:h-(16-4px) md:leading-(16-4px) box-border md:border-t-2 md:border-b-2 {{ if hasPrefix $relPermalink $url }} selected-menu-item {{ else }} border-transparent {{ end }} mr-4">{{ .Name }}</a>
{{- end }}
</div>
<div class="flex">
<div class="search-container relative pt-4 md:pt-0">
<div class="search">
<form role="search" class="search-form" action="/search.html" method="get">
<label>
<input name="q" type="text" placeholder="搜索 ..." class="search-field">
</label>
<button>
<i class="fas fa-search"></i>
</button>
</form>
</div>
</div>
<div class="relative pl-4 pt-4 md:pt-0">
<div class="cursor-pointer hover:text-eureka" id="lightDarkMode">
{{- if eq .Site.Params.colorScheme "dark" }}
<i class="fas fa-moon"></i>
{{- else if eq .Site.Params.colorScheme "light" }}
<i class="fas fa-sun"></i>
{{- else }}
<i class="fas fa-adjust"></i>
{{- end }}
</div>
<div class="fixed hidden inset-0 opacity-0 h-full w-full cursor-default z-30" id="is-open">
</div>
<div class="absolute flex flex-col left-0 md:left-auto right-auto md:right-0 hidden bg-secondary-bg w-48 rounded py-2 border border-tertiary-bg cursor-pointer z-40"
id='lightDarkOptions'>
<span class="px-4 py-1 hover:text-eureka" name="Light">{{i18n "light"}}</span>
<span class="px-4 py-1 hover:text-eureka" name="Dark">{{i18n "dark"}}</span>
<span class="px-4 py-1 hover:text-eureka" name="Auto">{{i18n "auto"}}</span>
</div>
</div>
{{- if .IsTranslated }}
<div class="relative pt-4 pl-4 md:pt-0">
<div class="cursor-pointer hover:text-eureka" id="languageMode">
<i class="fas fa-globe"></i>
<span class="pl-1">{{ .Language.LanguageName }}</span>
</div>
<div class="fixed hidden inset-0 opacity-0 h-full w-full cursor-default z-30" id="is-open-lang">
</div>
<div class="absolute flex flex-col left-0 md:left-auto right-auto md:right-0 hidden bg-secondary-bg w-48 rounded py-2 border border-tertiary-bg cursor-pointer z-40"
id='languageOptions'>
<a class="px-4 py-1 hover:text-eureka" href="{{ .Permalink }}">{{ .Language.LanguageName }}</a>
{{- range .Translations }}
<a class="px-4 py-1 hover:text-eureka" href="{{ .Permalink }}">{{ .Language.LanguageName }}</a>
{{- end }}
</div>
</div>
{{- end }}
</div>
</div>
<div class="fixed hidden inset-0 opacity-0 h-full w-full cursor-default z-0" id="is-open-mobile">
</div>
</nav>
<script>
let element = document.getElementById('lightDarkMode')
{{- if eq .Site.Params.colorScheme "light" }}
if (storageColorScheme == 'Auto') {
element.firstElementChild.classList.remove('fa-sun')
element.firstElementChild.setAttribute("data-icon", 'adjust')
element.firstElementChild.classList.add('fa-adjust')
document.addEventListener('DOMContentLoaded', () => {
switchMode('Auto')
})
} else if (storageColorScheme == "Dark") {
element.firstElementChild.classList.remove('fa-sun')
element.firstElementChild.setAttribute("data-icon", 'moon')
element.firstElementChild.classList.add('fa-moon')
}
{{- else if eq .Site.Params.colorScheme "dark" }}
if (storageColorScheme == 'Auto') {
element.firstElementChild.classList.remove('fa-moon')
element.firstElementChild.setAttribute("data-icon", 'adjust')
element.firstElementChild.classList.add('fa-adjust')
document.addEventListener('DOMContentLoaded', () => {
switchMode('Auto')
})
} else if (storageColorScheme == "Light") {
element.firstElementChild.classList.remove('fa-moon')
element.firstElementChild.setAttribute("data-icon", 'sun')
element.firstElementChild.classList.add('fa-sun')
}
{{- else }}
if (storageColorScheme == null || storageColorScheme == 'Auto') {
document.addEventListener('DOMContentLoaded', () => {
switchMode('Auto')
})
} else if (storageColorScheme == "Light") {
element.firstElementChild.classList.remove('fa-adjust')
element.firstElementChild.setAttribute("data-icon", 'sun')
element.firstElementChild.classList.add('fa-sun')
} else if (storageColorScheme == "Dark") {
element.firstElementChild.classList.remove('fa-adjust')
element.firstElementChild.setAttribute("data-icon", 'moon')
element.firstElementChild.classList.add('fa-moon')
}
{{- end }}
document.addEventListener('DOMContentLoaded', () => {
getcolorscheme();
switchBurger();
{{- if .IsTranslated }}
switchLanguage()
{{- end }}
});
</script>
大部分的内容都是 Eureka 主题提供的,除了增加了 #search-container
搜索框部分。为了让搜索框更美观一点,我在 layouts/partials/custom-head.html
中自定义了一些样式。
.search-container {
margin-top: -0.3rem;
}
.search-container .search {
border: 1px solid #e2e8f0;
border-radius: 4px;
}
.search-container input {
padding-left: 1rem;
line-height: 2rem;
outline: none;
background: transparent;
}
.search-container button {
font-size: 0.8rem;
margin-right: 0.5rem;
color: #e2e8f0;
}
最终搜索框跳转至单独的搜索页面。关于如何给 Hugo 博客添加搜索功能,可查看我之前的文章 《Hugo 之旅》。我这边提供一下我的搜索结果页模板。
{{/* layouts/_default/search.html */}}
{{ define "main" }}
<div class="w-full max-w-screen-xl lg:px-4 xl:px-8 mx-auto">
<article class="mx-6 my-8">
<h1 id="search-count" class="font-bold text-3xl text-primary-text"></h1>
</article>
<div id="search-result" class="bg-secondary-bg rounded overflow-hidden px-4 divide-y">
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.0/fuse.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const qs = new URLSearchParams(location.search);
const searchResult = document.querySelector('#search-result');
const searchCount = document.querySelector('#search-count');
const fuseOptions = {
shouldSort: true,
includeMatches: true,
threshold: 0.0,
tokenize: true,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: "title",
weight: 0.8
},
{
name: "summary",
weight: 0.5
},
{
name: "tags",
weight: 0.3
},
{
name: "date",
weight: 0.3
},
]
};
let fuse = null
async function getFuse() {
if (fuse == null) {
const resp = await fetch('/index.json', {
method: 'get'
})
const indexData = await resp.json()
fuse = new Fuse(indexData, fuseOptions);
}
return fuse
}
function render(items) {
console.log(items);
return items.map(item => {
item = item.item
return `
<div class="px-2 py-6">
<div class="flex flex-col-reverse lg:flex-row justify-between">
<div class="w-full lg:w-2/3">
<div class="my-2">
<div class="mb-4">
<a href="${item.permalink}" class="font-bold text-xl hover:text-eureka">${item.title}</a>
</div>
<div class="content">
${item.summary}
<p class="more">
<a href="${item.permalink}" title="${item.title}">阅读全文<span class="meta-nav">→</span></a>
</p>
</div>
</div>
<div class="flex flex-wrap flex-row items-center my-2 text-tertiary-text">
<div class="mr-6 my-2">
<i class="fas fa-calendar mr-1"></i>
<span>${item.date}</span>
</div>
<div class="mr-6 my-2">
<a href="${item.permalink}#waline-comments" title="${item.title}">
<i class="fas fa-comment mr-1"></i>
<span>${item.comments > 0 ? item.comments + ' 条评论' : '暂无评论'}</span>
</a>
</div>
<div class="mr-6 my-2">
<i class="fas fa-clock mr-1"></i>
<span>${item.time} 分钟阅读时长</span>
</div>
</div>
</div>
<div class="w-full lg:w-1/3 mb-4 lg:mb-0 lg:ml-8">
${item.featuredImage ? `<img src="${item.featuredImage}" class="w-full" alt="Featured Image">` : ''}
</div>
</div>
</div>`;
}).join('');
}
function updateDOM(html, keyword, number) {
document.title = document.title.replace(/包含关键词.*?文章/, `包含关键词 ${keyword} 的文章`)
searchResult.innerHTML = html
searchCount.innerHTML = `共查询到 ${number} 篇文章`
}
async function search(searchString) {
console.log(searchString);
let result = [];
if(searchString) {
const fuse = await getFuse()
result = fuse.search(searchString)
}
const html = render(result)
updateDOM(html, searchString, result.length)
}
document.querySelectorAll('input[name="q"]').forEach(el => el.value = qs.get('q'));
search(qs.get('q') || '')
window.blogSearch = function(keyword) {
if(!keyword) {
return;
}
history.pushState('', '', location.pathname + '?q=' + encodeURIComponent(keyword));
document.querySelectorAll('input[name="q"]').forEach(el => el.value = keyword);
search(keyword);
}
})
</script>
{{ end }}
归档
之前使用 Typecho 的时候有一个归档插件会按照年月列表展示文章,所以我在 Hugo 中按照之前的格式实现了一下。按照如下内容新建 layouts/_default/archive.html
文件,并新建文章 content/日志.md
,文章内容为空即可,在文章的 meta 数据中指定 layout: archive
来映射到该模板。
{{/* layouts/_default/archive.html */}}
{{ define "main" }}
{{ $hasToc := and (in .TableOfContents "<li>" ) (.Params.toc) }}
{{ $hasSidebar := or ($hasToc) (.Params.series) }}
<div class="grid grid-cols-2 lg:grid-cols-8 gap-4 lg:pt-12">
<div
class="col-span-2 {{ if not $hasSidebar }} {{- print "lg:col-start-2" -}} {{ end }} lg:col-span-6 bg-secondary-bg rounded px-6 py-8">
<h1 class="font-bold text-3xl text-primary-text">{{ .Title }}</h1>
<div class="flex flex-wrap flex-row items-center my-2 text-tertiary-text">
<div class="mr-6 my-2">
<i class="fas fa-calendar mr-1"></i>
<span>{{ .Date.Format (.Site.Params.dateFormat | default "2006-01-02") }}</span>
</div>
</div>
{{ $featured := partial "utils/get-featured" . }}
{{ with $featured }}
<div class="my-4">
{{ . }}
</div>
{{ end }}
<div class="content">
<script type='text/javascript' src="https://lib.baomitu.com/jquery/1.11.1/jquery.min.js"></script>
<style type="text/css">.car-collapse .car-yearmonth { cursor: s-resize; } </style>
<script type="text/javascript">
/* <![CDATA[ */
jQuery(document).ready(function() {
jQuery('.car-collapse').find('.car-monthlisting').hide();
jQuery('.car-collapse').find('.car-monthlisting:first').show();
jQuery('.car-collapse').find('.car-yearmonth').click(function() {
jQuery(this).next('ul').slideToggle('fast');
});
jQuery('.car-collapse').find('.car-toggler').click(function() {
if ( '展开全部' == jQuery(this).text() ) {
jQuery(this).parent('.car-container').find('.car-monthlisting').show();
jQuery(this).text('折叠全部');
}
else {
jQuery(this).parent('.car-container').find('.car-monthlisting').hide();
jQuery(this).text('展开全部');
}
return false;
});
});
/* ]]> */
</script>
<div class="car-container car-collapse">
<a href="#" class="car-toggler">展开全部</a>
<ul class="car-list">
{{ range (.Site.RegularPages.GroupByDate "01月 2006") }}
<li>
<span class="car-yearmonth">{{ .Key }} <span title="Post Count">({{ len .Pages }})</span></span>
<ul class="car-monthlisting">
{{ range .Pages }}
<li>
{{ .Date.Format "02"}}: <a href="{{ .Permalink }}">{{ .Title }} </a> <!--<span title="Comment Count">(0)</span>-->
</li>
{{ end }}
</ul>
</li>
{{ end }}
</ul>
</div>
</div>
</div>
</div>
{{ end }}
统计
屈屈的博客中还有一个统计页面,我觉得挺有意思的,于是也在我的博客中复刻了一下。按照如下内容新建 layouts/_default/stats.html
文件,并新建文章 content/统计.md
,文章内容为空即可,在文章的 meta 数据中指定 layout: stats
来映射到该模板。
{{/* layouts/_default/stats.html */}}
{{ define "main" }}
{{- $.Scratch.Add "stats" slice -}}
{{- range .Site.RegularPages -}}
{{- $.Scratch.Add "stats" (dict "title" .Title "slug" .Slug "year" (.Date.Format "2006") "month" (.Date.Format "2006-01") "hour" (.Date.Format "15") "week" (.Date.Format "Monday") "count" .WordCount) -}}
{{- end -}}
{{ $hasToc := and (in .TableOfContents "<li>" ) (.Params.toc) }}
{{ $hasSidebar := or ($hasToc) (.Params.series) }}
<style>
.chart {
margin-top: 15px;
width: 100%;
height: 350px;
}
</style>
<div class="grid grid-cols-2 lg:grid-cols-8 gap-4 lg:pt-12">
<div
class="col-span-2 {{ if not $hasSidebar }} {{- print "lg:col-start-2" -}} {{ end }} lg:col-span-6 bg-secondary-bg rounded px-6 py-8">
<h1 class="font-bold text-3xl text-primary-text">{{ .Title }}</h1>
<div class="flex flex-wrap flex-row items-center my-2 text-tertiary-text">
<div class="mr-6 my-2">
<i class="fas fa-calendar mr-1"></i>
<span>{{ .Date.Format (.Site.Params.dateFormat | default "2006-01-02") }}</span>
</div>
</div>
{{ $featured := partial "utils/get-featured" . }}
{{ with $featured }}
<div class="my-4">
{{ . }}
</div>
{{ end }}
<div class="content">
{{ .Content }}
</div>
</div>
{{ if $hasSidebar}}
<div class="col-span-2">
{{ if .GetTerms "series" }}
{{ partial "components/post-series.html" . }}
{{ end }}
{{ if $hasToc }}
{{ partial "components/post-toc.html" . }}
{{ end }}
</div>
{{ end }}
</div>
<script src="https://lib.baomitu.com/echarts/5.0.0/echarts.min.js"></script>
<script>
const data = {{- $.Scratch.Get "stats" -}};
function showChart(id, title, type, d) {
var chart = echarts.init(document.getElementById(id));
var xData = [];
var yData = [];
d.forEach(function(item) {
xData.push(item[0]);
yData.push(item[1]);
});
var option = {
title : { text : title },
tooltip : { trigger : 'axis' },
xAxis : [ { type : 'category', data : xData } ],
yAxis : [ { type : 'value' } ],
grid : { x : 35, y : 45, x2 : 35, y2 : 35 },
series : [ {
type : 'bar',
name : type,
data : yData,
markLine : {
data : [ {
type : 'average',
name : '平均值'
}],
itemStyle : {
normal : {
color : '#4087bd'
}
}
},
itemStyle : {
normal : {
color : '#87cefa'
}
}
}]
};
chart.setOption(option);
}
window.addEventListener('load', function() {
basicInfo();
yearStats();
monthStats();
hourStats();
weekStats();
});
function basicInfo() {
const articles = {{ len (where .Site.RegularPages "Section" "posts") }};
const pages = data.length - articles;
const comments = data.reduce((count, article) => count + article.comments, 0);
const words = data.reduce((count, article) => count + article.count, 0);
document.querySelector('#basic-info').innerHTML = `
<span>文章:<strong><a href="/">${articles}</a></strong> 篇</span>;<span>页面:<strong><a href="/">${pages}</a></strong> 篇</span>;<span>总字数:<strong>${words}</strong></span>;
`;
};
function yearStats() {
const yearGroup = {};
data.forEach(article => {
const year = parseInt(article.year);
if(!yearGroup.hasOwnProperty(year)) {
yearGroup[year] = 0;
}
yearGroup[year] += 1;
});
const d = [];
for(let i = 2009; i <= (new Date().getFullYear()); i++) {
d.push([i, yearGroup[i] || 0]);
}
showChart('year-stat', '文章数 - 按年统计', '文章数', d);
}
function monthStats() {
const monthGroup = {};
data.forEach(article => {
if(!monthGroup.hasOwnProperty(article.month)) {
monthGroup[article.month] = 0;
}
monthGroup[article.month] += 1;
});
const d = [];
for(let year = 2009; year <= (new Date().getFullYear()); year++) {
for(let month = 1; month < 13; month++) {
const text = `${year}-${month < 10 ? '0' + month : month}`;
d.push([text, monthGroup[text] || 0]);
}
}
showChart('month-stat', '文章数 - 按月统计', '文章数', d);
}
function hourStats() {
const hourGroup = {};
data.forEach(article => {
const hour = parseInt(article.hour);
if(!hourGroup.hasOwnProperty(hour)) {
hourGroup[hour] = 0;
}
hourGroup[hour] += 1;
});
const d = [
['00:00-01:00'],
['01:00-02:00'],
['02:00-03:00'],
['03:00-04:00'],
['04:00-05:00'],
['05:00-06:00'],
['06:00-07:00'],
['07:00-08:00'],
['08:00-09:00'],
['09:00-10:00'],
['10:00-11:00'],
['11:00-12:00'],
['12:00-13:00'],
['13:00-14:00'],
['14:00-15:00'],
['15:00-16:00'],
['16:00-17:00'],
['17:00-18:00'],
['18:00-19:00'],
['19:00-20:00'],
['20:00-21:00'],
['21:00-22:00'],
['22:00-23:00'],
['23:00-24:00']
].map((item, key) => {
item[1] = hourGroup[key] || 0;
return item;
});
showChart('hour-stat', '文章数 - 按时段统计', '文章数', d);
}
function weekStats() {
const weekGroup = {};
data.forEach(article => {
if(!weekGroup.hasOwnProperty(article.week)) {
weekGroup[article.week] = 0;
}
weekGroup[article.week] += 1;
});
const d = [
['星期一', weekGroup.Monday],
['星期二', weekGroup.Tuesday],
['星期三', weekGroup.Wednesday],
['星期四', weekGroup.Thursday],
['星期五', weekGroup.Friday],
['星期六', weekGroup.Saturday],
['星期日', weekGroup.Sunday]
];
showChart('weekday-stat', '文章数 - 按星期几统计', '文章数', d);
}
</script>
{{ end }}
其它
除了以上这些,我的博客中改动最大的当属评论这块,但这块定制型比较高,一般玩家就不推荐了,感兴趣的还是去看我之前的《静态博客如何高性能插入评论》一文。除此之外,我还修改了 footer.html
修改了底部显示文案,增加了网页统计脚本;基于自研的 wxhermit 增加了微信分享自定义相关功能;文章页目录底部增加了个人公众号的展示。由于这些内容都比较简单且定制化内容程度比较高,就不一一展示了,感兴趣的朋友可以自行查看源码查阅。
你好,归档的代码中使用的 components/get-featured.html 文件在代码中没有,我翻了下 2021 年 3 月之前的代码包中也没有发现这个文件。看了下代码感觉和 utils/get-featured.html 应该不是一个文件。请问这个文件在哪里找?
看了下,就是 utils/get-featured.html,eureka 做过一次文件迁移,以前这些文件是在 components 下面的。
想请教下老哥原始的行内代码显示样式(像
code
这样) 如何修改成为像你一样的阴影形式,期待回复~老哥,想请教下你的定制之后文章行内的代码显示样式是调整了那部分的css,之前的行内代码显示样式是
code
这样,想改成想你一样的灰色阴影样式,望回复~@DevonChan: 就是原始样式会显示为’
code
’这样子,并没有阴影的效果@DevonChan: 这块应该没有修改,就是用的主题默认的诶。你说的阴影是啥呀,是不是说的 blockquote 引用的样式啊?
对统计页面特别有兴趣,请问能详细说明一下吗?按照文章中的配置设置过后页面是空白的,像是渲染报错。
另外按照文章说明成功加入了字数统计,非常感谢!
@Mantyke , 已经按照线下沟通重新更新了文章内容针对新版本适配了,感谢反馈。最后能帮到你很开心~
左侧边栏没有分块,不知道哪里的问题
@who , 可以把你的地址提供一下,我去看一下,应该就是少了些样式而已。
老哥,关于首页中的
{{ partial "horizontal_summary.html" . }}
没有提供模板啊,导致了渲染会报错 未找到此模板。如果删除此句的话,会导致首页文章列表没有标题显示。
@袅残烟 , Eureka 主题升级了把文件结构做了重新整理,horizontal_summary.html 这个文件已经没有了,取而代之的是 components/summary-plain.html,你换成这个吧。文章我已经按照最新版的主题更新了一下,你可以重新参考下。
@怡红公子 , 感谢老哥。还有就是你这个评论是不是有点问题,我提交评论的时候会有个英文提示,好像是啥 复制的内容 的意思,我还以为没提交上去呢
@袅残烟 , 评论应该是没问题的,弹出提示可能是你多按了几次提交按钮然后提示你重复内容了吧,这块可以优化一下。
@怡红公子 , 那可能是的,昨天我是用的手机开的热点上网的,估计网速不好。今天用台式机宽带的话就没问题了。
@袅残烟 , 嗯嗯,非常感谢反馈,平常评论比较少这块也没有优化。另外有兴趣的话加个友链呀
感谢分享,正好想换主题