个性化原则 由于博客主题是通过 Git 子模块的方式引入的,为了避免后面主题的更新不会影响到个性化内容,所以不建议改动「themes」下的内容
我们在博客的根目录下,通过相同文件路径以及文件名 的方式覆盖主题文件即可生效
自定义字体 文章主体,使用 「霞鹜文楷」
修改「config.toml」
1 2 3 4 5 6 fontFamilyTitle = "'LXGW WenKai Screen', sans-serif" fontFamilyHeadings = "'LXGW WenKai Screen', sans-serif" fontFamilyBody = "'LXGW WenKai Screen', sans-serif"
字体链接使用 @一蓑烟雨 分享的 staticfile CDN
1 2 fontsLink = "https://cdn.staticfile.org/lxgw-wenkai-screen-webfont/1.6.0/lxgwwenkaiscreen.css"
文章代码部分,使用 JetBrains Mono
代码的字体直接下载放到项目「static/fonts」下
然后项目下新建「assets/scss/custom」文件夹
新增文件「_custom.scc」
新增文件「_font.scss」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @font-face { font-family : 'JetBrains Mono' ; font-display : swap; src : url ('#{$baseRelURL}/fonts/JetBrainsMono-Regular.woff2' ) format ('woff2' ); font-weight : 400 ; font-style : normal; } @font-face { font-family : 'JetBrains Mono' ; font-display : swap; src : url ('#{$baseRelURL}/fonts/JetBrainsMono-Bold.woff2' ) format ('woff2' ); font-weight : 700 ; font-style : normal; } @font-face { font-family : 'JetBrains Mono' ; font-display : swap; src : url ('#{$baseRelURL}/fonts/JetBrainsMono-Italic.woff2' ) format ('woff2' ); font-weight : 400 ; font-style : italic; } :root { --text-wdth : 90 ; --text-opsz : 40 ; --text-YTLC : 460 ; } body { font-variation-settings : 'wdth' var (--text-wdth), 'opsz' var (--text-opsz), 'YTLC' var (--text-YTLC); } .post-title { font-variation-settings : 'wght' 550 , 'opsz' 60 , 'YOPQ' 90 ; } .list-item-time { font-feature-settings : 'tnum' ; } @media (max-width : 500px ) { body { font-size : $fontSize * 0.875 ; } .drop-cap { font-size : $fontSize * 0.875 * 3 ; margin-right : $lineHeight * $fontSize * 0.875 - $fontSize * 0.875 ; margin-top : $fontSize * 0.875 / $lineHeight ; line -height : $lineHeight * $fontSize * 0.875 ; } }
修改配置文件「config.toml」
1 2 fontFamilyCode = "'JetBrains Mono', 'LXGW WenKai Screen', monospace"
引入 Twikoo 评论系统 Twikoo 一个简洁、安全、免费的静态网站评论系统
通过 Zeabur 部署 Twikoo 使用 GitHub 账号登录 Zeabur [^1]
创建一个新的 Project,名字随意
点击「Add new service」,然后选择「Deploy Other Services」
部署一个 Mongo 服务
到 GitHub 中将 Twikoo-zeabur fork 到自己的账号下
然后再到 Zeabur 中点击「Add new service」,然后选择「Deploy Your Source Code」将「Twikoo-zeabur」部署到 Zeabur
部署完成后,绑定域名
直接访问绑定的域名
这样就说明部署成功了
MemE 中添加 Twikoo 到博客根目录下
新建「layouts/partials/components/comments.html」文件
在原地址文件的基础上增加
1 2 3 {{ if .Site.Params.enableTwikoo }} <div id ="tcomment" > </div > {{ end }}
完整如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 {{ if and (.Params.comments | default .Site.Params.enableComments) (eq hugo.Environment "production") }} {{ if or (in .Site.Params.mainSections .Section) .Params.comments }} {{ if not .Site.Params.autoLoadComments }} <div class ="load-comments" > <div id ="load-comments" > {{ i18n "loadComments" }}</div > </div > {{ end }} {{ if .Site.Params.enableTwikoo }} <div id ="tcomment" > </div > {{ end }} {{ if .Site.Params.enableDisqus }} <div id ="disqus_thread" > </div > {{ end }} {{ if .Site.Params.enableValine }} <div id ="vcomments" > </div > {{ end }} {{ if .Site.Params.enableUtterances }} <div id ="utterances" > </div > {{ end }} {{ if .Site.Params.enableGitalk }} <div id ="gitalk-container" > </div > {{ end }} {{ end }} {{ end }}
新建「layouts/partials/pages/third-party/script.html」文件
在原地址文件的基础上增加
1 2 3 {{ if .Site.Params.enableTwikoo }} {{ partial "third-party/twikoo.html" . }} {{ end }}
完整如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 {{ if .Params.katex | default .Site.Params.enableKaTeX }} {{ partial "third-party/katex.html" . }} {{ end }} {{ if .Params.mathjax | default .Site.Params.enableMathJax }} {{ partial "third-party/mathjax.html" . }} {{ end }} {{ if .Params.mermaid | default .Site.Params.enableMermaid }} {{ partial "third-party/mermaid.html" . }} {{ end }} {{ if and (.Params.comments | default .Site.Params.enableComments) (eq hugo.Environment "production") }} {{ if or (in .Site.Params.mainSections .Section) .Params.comments }} {{ if .Site.Params.enableTwikoo }} {{ partial "third-party/twikoo.html" . }} {{ end }} {{ if .Site.Params.enableDisqus }} {{ partial "third-party/disqus.html" . }} {{ end }} {{ if .Site.Params.enableValine }} {{ partial "third-party/valine.html" . }} {{ end }} {{ if .Site.Params.enableUtterances }} {{ partial "third-party/utterances.html" . }} {{ end }} {{ if .Site.Params.enableGitalk }} {{ partial "third-party/gitalk.html" . }} {{ end }} {{ end }} {{ end }} {{ if .Site.Params.enableMediumZoom }} {{ partial "third-party/medium-zoom.html" . }} {{ end }} {{ if .Site.Params.enableInstantPage }} {{ partial "third-party/instant-page.html" . }} {{ end }} {{ partial "third-party/busuanzi.html" . }} {{ partial "custom/script.html" . }}
新建「layouts/partials/pages/third-party/twikoo.html」文件
这是个新增文件,引入 Twikoo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <script > function loadComments ( ) { if (typeof Twikoo === 'undefined' ) { const getScript = (options ) => { const script = document .createElement ('script' ); script.defer = true ; script.crossOrigin = 'anonymous' ; Object .keys (options).forEach ((key ) => { script[key] = options[key]; }); document .body .appendChild (script); }; getScript ({ src : 'https://cdn.jsdelivr.net/npm/[email protected] /dist/twikoo.all.min.js' , onload : () => { newTwikoo (); } }); } else { newTwikoo (); } } function newTwikoo ( ) { twikoo.init ({ el : '#tcomment' , envId : '{{ .Site.Params.twikooEnvId }}' }); } </script >
最后,到「comfig.toml」中添加对应配置
1 2 3 enableTwikoo = true twikooEnvId = ""
注意 :开发环境下启动 Hugo 时,不会展示评论,需要在生产环境下启动:hugo --environment production server
引入 Memos 同样的,通过 Zeabur 部署 Memos [^2]
在 Zeabur 中绑定完域名后,第一次进入需要注册一个账号,这个账号便是管理员
主页滚动近期 Memos MemE 的主页有「文章摘要」和「诗意人生」两种风格,这两种分别对应「layouts/partials/pages/home-posts.html」和「layouts/partials/pages/home-poetry.html」两个文件
我这里使用的是「文章摘要」的风格
复制「layouts/partials/pages/home-posts.html」到博客根目录
在「main」标签下的第一行增加
1 2 3 <div id ="memos" class ="main-inner" > {{ partial "pages/memos.html" . }} </div >
同级目录下,新建文件「memos.html」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 <script > (() => { window .Lately = new function ( ) { this .lang = { second : "秒" , minute : "分钟" , hour : "小时" , day : "天" , month : "个月" , year : "年" , ago : "前" , error : "NaN" }; const format = (date ) => { date = new Date (date); const floor = (num, _lang ) => Math .floor (num) + _lang, obj = new function ( ) { this .second = (Date .now () - date.getTime ()) / 1000 ; this .minute = this .second / 60 ; this .hour = this .minute / 60 ; this .day = this .hour / 24 ; this .month = this .day / 30 ; this .year = this .month / 12 }, key = Object .keys (obj).reverse ().find (_ => obj[_] >= 1 ); return (key ? floor (obj[key], this .lang [key]) : this .lang .error ) + this .lang .ago ; }, _val = (date ) => { if (date == 'undefined' ) { return false ; } if ((date + '' ).indexOf ('T' ) > 0 ) { return new Date (date).getTime (); } if ((date + '' ).length < 13 ) { return false ; } return date; }; return { init : ({ target = "time" , lang } = {} ) => { if (lang) this .lang = lang; for (let el of document .querySelectorAll (target)) { const date = _val (el.dateTime ) || _val (el.title ) || _val (el.innerHTML ) || 0 ; if (!date) return ; el.title = new Date (+date).toLocaleString ("zh-CN" ); const i = el.innerHTML .indexOf (';' ); if (i > 0 ) { el.innerHTML = el.innerHTML .substring (0 , i+1 ) + format (+date); } else { el.innerHTML = format (+date); } } }, format } } })(); let jsonUrl = "" + Date .parse (new Date ()); fetch (jsonUrl).then ((res ) => res.json ()).then ((resdata ) => { var result = "" , resultAll = "" , data = resdata.data ; for (var i = 0 ; i < data.length ; i++) { var talkTime = data[i].createdTs * 1000 ; var talkContent = data[i].content ; var newtalkContent = talkContent.replace (/```([\s\S]*?)```[\s]*/g , " <code>$1</code> " ) .replace (/`([\s\S ]*?)`[\s]*/g , " <code>$1</code> " ) .replace (/\!\[[\s\S]*?\]\([\s\S]*?\)/g , "🌅" ) .replace (/\[[\s\S]*?\]\([\s\S]*?\)/g , "🔗" ) .replace (/\bhttps?:\/\/(?!\S+(?:jpe?g|png|bmp|gif|webp|jfif|gif))\S+/g , "🔗" ); result += `<li class="item"><span class="memosTime" title=${talkTime} ></span>: <a href="https://venom-memos.zeabur.app" target="_blank">${newtalkContent} </a></li>` ; } var talkDom = document .querySelector ("#memos" ); var talkBefore = `<img title="Memos" style="width: 1.7em; float: left; position: relative; top: 1px;" src="icons/tg.gif"></img><div class="talk-wrap"><ul class="talk-list">` ; var talkAfter = `</ul></div>` ; resultAll = talkBefore + result + talkAfter; talkDom.innerHTML = resultAll; window .Lately && Lately .init ({ target : 'time, .memosTime' }); }); setInterval (function ( ) { var talkWrap = document .querySelector (".talk-list" ); var talkItem = talkWrap.querySelectorAll (".item" ); for (i = 0 ; i < talkItem.length ; i++) { setTimeout (function ( ) { talkWrap.appendChild (talkItem[0 ]); }, 2000 ); } }, 2000 ); function timeConver (t ) { t = t * 1000 ; var now = new Date (t); var year = now.getFullYear (); var month = now.getMonth () + 1 ; var date = now.getDate (); var hour = now.getHours (); var minute = now.getMinutes (); var second = now.getSeconds (); if (month < 10 ) { month = "0" + month; } if (date < 10 ) { date = "0" + date; } if (hour < 10 ) { hour = "0" + hour; } if (minute < 10 ) { minute = "0" + minute; } if (second < 10 ) { second = "0" + second; } return ( year + "-" + month + "-" + date + " " + hour + ":" + minute ); } </script >
jsonUrl
替换为自己的 Memos API 地址
「_custom.scss」中增加
同级目录下增加「_memos.scss」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #memos { display : block; margin : 0 auto; width : 36em ; } .index-talk { display : flex; flex : 1 auto; width : 100% ; text -align: left; position : relative; } .talk-list { padding-left : 0px ; white-space : nowrap; text -overflow : ellipsis; overflow : hidden; } .talk-list li { list-style : none; margin-bottom : 10px ; width : 100% ; white-space : nowrap; text -overflow : ellipsis; overflow : hidden; display : inline; vertical-align : middle; } .talk-list li :not (:first-child) { display : none !important ; } .talk-list li a :hover { text -decoration: none; color : #1890ff ; }
Thanks
[^1]: Zeabur 属于大学生创业产品,团队成员主要来自浙江大学,目前仍处于推广阶段,服务免费使用,后续则将采用免费额度 + 按量计费的模式
[^2]: Memos 一个开源且免费的自托管知识库