使用 Github Actions 自动部署博客
利用 Github Actions 来自动构建和部署博客到 Github Pages。这样既可以简化自己的操作,又能保证自己的博客源码的私密性。
...diff --git a/404.html b/404.html new file mode 100644 index 00000000..b5fee1ce --- /dev/null +++ b/404.html @@ -0,0 +1,4 @@ +
利用 Github Actions 来自动构建和部署博客到 Github Pages。这样既可以简化自己的操作,又能保证自己的博客源码的私密性。
...此篇博客介绍我的博客的修改内容和方法。我的主题为 hugo-PaperMod 。我修改前都将博客内的资源文件夹(assets, i18n, lay)复制到了项目根目录中,这样以后更新主题也不会导致修改消失。
...使用 Hugo 搭建博客。
...公司有一个同事做的项目,其中有一个 Python 写的程序会反复降低 CPU 的电压直至死机重启,程序会在降压前保存本次的数据。听起来很合理,先保存数据再降低电压,如果死机了导致重启,那上次的数据也保存到本地了。但在 windows 电脑上实际运行时,每次程序导致 windows 死机重启后,保存的数据文件都为空。他没搞定这个就离职了,于是我就接手来查这个 bug 了。
...具体位置为:个人设置界面 -> Developer Settings -> Personal access tokens -> Tokens(classic)
在此界面新生成一个 token,需要勾选 repo
和 workflow
选项。Expiration
可以设置为 No expiration
。创建完成后会显示你的 token ,它只会显示这一次,你需要将它记下来,后边会用到。
想利用 Github Actions ,需要在博客的根目录下创建 .github/workflows/
文件夹。在该文件夹下创建 yml 文件会被 Github Actions 执行。
我的 workflow 文件如下:
+name: GitHub Pages
+
+on:
+ push:
+ branches:
+ - main # Set a branch to deploy
+
+ release:
+ types:
+ - published
+jobs:
+ deploy:
+ runs-on: ubuntu-20.04
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: main
+
+ - name: Setup Hugo
+ uses: peaceiris/actions-hugo@v2
+ with:
+ hugo-version: '0.119.0'
+ # 是否启用 hugo extend
+ # extended: true
+
+ - name: Build
+ run: hugo --minify
+
+ - name: Deploy
+ run: |
+ cd ./public
+ git init
+ git config --global user.name '${{ secrets.GITHUBUSERNAME }}'
+ git config --global user.email '${{ secrets.GITHUBEMAIL }}'
+ git add .
+ git commit -m "${{ github.event.head_commit.message }}"
+ git push --force --quiet "https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.GITHUBTOKEN }}@github.com/${{ secrets.GITHUBUSERNAME }}/${{ secrets.GITHUBUSERNAME }}.github.io.git" master:main
+ #git push --force --quiet "https://${{ secrets.TOKENUSER }}:${{ secrets.CODINGTOKEN }}@e.coding.net/${{ secrets.CODINGUSERNAME }}/${{ secrets.CODINGBLOGREPO }}.git" master:master #coding部署写法,需要的自行取消注释
+ #git push --force --quiet "https://${{ secrets.GITEEUSERNAME }}:${{ secrets.GITEETOKEN }}@gitee.com/${{ secrets.GITEEUSERNAME }}/${{ secrets.GITEEUSERNAME }}.git" master:master #gitee部署写法,需要的自行取消注释
+
其中,GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
三个为自定义变量,后边会讲到。
新建或使用一个老的仓库,可见性设置为 private 。在代码仓库的 Settings
页面找到 Secrets and variables
,点击其中的 Actions
,添加 workflow 中用到的三个变量:GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
。
+
+完成上述步骤后即可用 git 提交代码到你的源码仓库,即可在仓库的 Actions 界面看到执行的 workflows。
+来自与 Github 。项目地址为: https://github.com/dsrkafuu/sakana-widget 。具体更改为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- 石蒜组件 -->
+<style>
+ html .pull-right{
+ position: absolute;
+ right: 0;
+ top: 70px;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+ }
+ html #sakana-widget{
+ position: fixed;
+ right: 50px;
+ bottom: 0;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+}
+</style>
+<link
+rel="stylesheet"
+href="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.css"
+/>
+<div id="sakana-widget", style="z-index:999"></div>
+<script>
+function initSakanaWidget() {
+ new SakanaWidget().mount('#sakana-widget');
+}
+</script>
+<script
+async
+onload="initSakanaWidget()"
+src="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.js"
+></script>
+
重新编译后即可拥有可爱石蒜小组件。 +
+有两种 High 一下的代码实现:
+javascript:(function(){function c(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",f);e.setAttribute("class",l);document.body.appendChild(e)}function h(){var e=document.getElementsByClassName(l);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function p(){var e=document.createElement("div");e.setAttribute("class",a);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function d(e){return{height:e.offsetHeight,width:e.offsetWidth}}function v(i){var s=d(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function m(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function g(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function y(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function E(e){var t=m(e);return t>=w&&t<=b+w}function S(){var e=document.createElement("audio");e.setAttribute("class",l);e.src=i;e.loop=false;e.addEventListener("canplay",function(){setTimeout(function(){x(k)},500);setTimeout(function(){N();p();for(var e=0;e<O.length;e++){T(O[e])}},15500)},true);e.addEventListener("ended",function(){N();h()},true);e.innerHTML=" <p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p> <p>";document.body.appendChild(e);e.play()}function x(e){e.className+=" "+s+" "+o}function T(e){e.className+=" "+s+" "+u[Math.floor(Math.random()*u.length)]}function N(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.mp3";var s="mw-harlem_shake_me";var o="im_first";var u=["im_drunk","im_baked","im_trippin","im_blown"];var a="mw-strobe_light";var f="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var l="mw_added_css";var b=g();var w=y();var C=document.getElementsByTagName("*");var k=null;for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){if(E(A)){k=A;break}}}if(A===null){console.warn("Could not find a node of the right size. Please try a different page.");return}c();S();var O=[];for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){O.push(A)}}})()
+
javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()
+
以上两种差不多,我个人用的是第二种。具体用法为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- High 一下 -->
+<div class="pull-right", style="z-index:999">
+<a title="把这个链接拖到你的Chrome收藏夹工具栏中" href='javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()'>High一下!</a>
+</div>
+
其中的 href=
的内容替换为上述两种的其中一种即可。
根据 sulvblog 的方法来改。
+先修改 layouts/partials/toc.html
的代码,替换为:
{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
+{{- $has_headers := ge (len $headers) 1 -}}
+{{- if $has_headers -}}
+<aside id="toc-container" class="toc-container wide">
+ <div class="toc">
+ <details {{if (.Param "TocOpen") }} open{{ end }}>
+ <summary accesskey="c" title="(Alt + C)">
+ <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span>
+ </summary>
+
+ <div class="inner">
+ {{- $largest := 6 -}}
+ {{- range $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+ {{- if lt $headerLevel $largest -}}
+ {{- $largest = $headerLevel -}}
+ {{- end -}}
+ {{- end -}}
+
+ {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
+
+ {{- $.Scratch.Set "bareul" slice -}}
+ <ul>
+ {{- range seq (sub $firstHeaderLevel $largest) -}}
+ <ul>
+ {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
+ {{- end -}}
+ {{- range $i, $header := $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+
+ {{/* get id="xyz" */}}
+ {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
+
+ {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
+ {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
+ {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
+
+ {{- if ne $i 0 -}}
+ {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
+ {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
+ {{- if gt $headerLevel $prevHeaderLevel -}}
+ {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
+ <ul>
+ {{/* the first should not be recorded */}}
+ {{- if ne $prevHeaderLevel . -}}
+ {{- $.Scratch.Add "bareul" . -}}
+ {{- end -}}
+ {{- end -}}
+ {{- else -}}
+ </li>
+ {{- if lt $headerLevel $prevHeaderLevel -}}
+ {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
+ {{- if in ($.Scratch.Get "bareul") . -}}
+ </ul>
+ {{/* manually do pop item */}}
+ {{- $tmp := $.Scratch.Get "bareul" -}}
+ {{- $.Scratch.Delete "bareul" -}}
+ {{- $.Scratch.Set "bareul" slice}}
+ {{- range seq (sub (len $tmp) 1) -}}
+ {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
+ {{- end -}}
+ {{- else -}}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- else }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- end -}}
+ {{- end -}}
+ <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} -->
+ {{- $firstHeaderLevel := $largest }}
+ {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
+ </li>
+ {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
+ {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
+ </ul>
+ {{- else }}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end }}
+ </ul>
+ </div>
+ </details>
+ </div>
+</aside>
+<script>
+ let activeElement;
+ let elements;
+ window.addEventListener('DOMContentLoaded', function (event) {
+ checkTocPosition();
+
+ elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]');
+ // Make the first header active
+ activeElement = elements[0];
+ const id = encodeURI(activeElement.getAttribute('id')).toLowerCase();
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ }, false);
+
+ window.addEventListener('resize', function(event) {
+ checkTocPosition();
+ }, false);
+
+ window.addEventListener('scroll', () => {
+ // Check if there is an object in the top half of the screen or keep the last item active
+ activeElement = Array.from(elements).find((element) => {
+ if ((getOffsetTop(element) - window.pageYOffset) > 0 &&
+ (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) {
+ return element;
+ }
+ }) || activeElement
+
+ elements.forEach(element => {
+ const id = encodeURI(element.getAttribute('id')).toLowerCase();
+ if (element === activeElement){
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ } else {
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active');
+ }
+ })
+ }, false);
+
+ const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10);
+ const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10);
+ const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10);
+
+ function checkTocPosition() {
+ const width = document.body.scrollWidth;
+
+ if (width - main - (toc * 2) - (gap * 4) > 0) {
+ document.getElementById("toc-container").classList.add("wide");
+ } else {
+ document.getElementById("toc-container").classList.remove("wide");
+ }
+ }
+
+ function getOffsetTop(element) {
+ if (!element.getClientRects().length) {
+ return 0;
+ }
+ let rect = element.getBoundingClientRect();
+ let win = element.ownerDocument.defaultView;
+ return rect.top + win.pageYOffset;
+ }
+</script>
+{{- end }}
+
layouts/_default/single.html
文件中默认有 toc.html 的调用,如果未更改的话不用管,更改的话请调用:
{{- if (.Param "ShowToc") }}
+{{- partial "toc.html" . }}
+{{- end }}
+
修改 css/extended/blank.css
文件,加入下方代码:
:root {
+ --nav-width: 1380px;
+ --article-width: 650px;
+ --toc-width: 300px;
+}
+
+.toc {
+ margin: 0 2px 40px 2px;
+ border: 1px solid var(--border);
+ background: var(--entry);
+ border-radius: var(--radius);
+ padding: 0.4em;
+}
+
+.toc-container.wide {
+ position: absolute;
+ height: 100%;
+ border-right: 1px solid var(--border);
+ left: calc((var(--toc-width) + var(--gap)) * -1);
+ top: calc(var(--gap) * 2);
+ width: var(--toc-width);
+}
+
+.wide .toc {
+ position: sticky;
+ top: var(--gap);
+ border: unset;
+ background: unset;
+ border-radius: unset;
+ width: 100%;
+ margin: 0 2px 40px 2px;
+}
+
+.toc details summary {
+ cursor: zoom-in;
+ margin-inline-start: 20px;
+ padding: 12px 0;
+}
+
+.toc details[open] summary {
+ font-weight: 500;
+}
+
+.toc-container.wide .toc .inner {
+ margin: 0;
+}
+
+.active {
+ font-size: 110%;
+ font-weight: 600;
+}
+
+.toc ul {
+ list-style-type: circle;
+}
+
+.toc .inner {
+ margin: 0 0 0 20px;
+ padding: 0px 15px 15px 20px;
+ font-size: 16px;
+
+ /*目录显示高度*/
+ max-height: 83vh;
+ overflow-y: auto;
+}
+
+.toc .inner::-webkit-scrollbar-thumb { /*滚动条*/
+ background: var(--border);
+ border: 7px solid var(--theme);
+ border-radius: var(--radius);
+}
+
+.toc li ul {
+ margin-inline-start: calc(var(--gap) * 0.5);
+ list-style-type: none;
+}
+
+.toc li {
+ list-style: none;
+ font-size: 0.95rem;
+ padding-bottom: 5px;
+}
+
+.toc li a:hover {
+ color: var(--secondary);
+}
+
重新编译后即可看到目录到了左边。
+修改 ·assets\css\core\theme-vars.css
文件的 :root
节点中的 --main-width
我设置的是 1024px
放弃主题默认的 Highlight.js,改为 Hugo 默认的 chroma 方式渲染。根据 官方说明 来改。我用的 style 为 github-dark
在 assets\css\common\post-single.css
文件中找到 .post-content code
增加 max-height: 40em;
。目前设置好最大高度后会与 lineNos: true
设置冲突,会显示两个滑动条 :( ,所以就先不开它。
默认显示的图片在摘要上方而且很大,一页只能显示几个,感觉体验不是很好,所以将它改为在摘要右方显示。我很喜欢 CoolShell 的文章页的显示方法,准备改成差不多样子的。
+文件为 layouts\_default\list.html
。默认的图片显示代码为:
{{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+{{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+
位于 <header class="entry-header">
的上方。将其移动到 <div class="entry-content">
中并替换默认的 Summary 处理方式,如下所示:
<div class="entry-content">
+ {{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+ {{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+ {{ .Summary | replaceRE "\n" "<br>" | safeHTML }}{{ if .Truncated }}...{{ end }}
+</div>
+
文件为 layouts\partials\cover.html
。将 所有 <img
块都加上 align="right"
。完整如下所示:
{{- with .cxt}} {{/* Apply proper context from dict */}}
+{{- if (and .Params.cover.image (not $.isHidden)) }}
+{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
+<figure class="entry-cover">
+ {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
+ {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
+ {{- $pageBundleCover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $globalResourcesCover := (resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $cover := (or $pageBundleCover $globalResourcesCover)}}
+ {{- if $cover -}}{{/* i.e it is present in page bundle */}}
+ {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
+ {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
+ {{- if hugo.IsExtended -}}
+ {{- $processableFormats = $processableFormats | append "webp" -}}
+ {{- end -}}
+ {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
+ {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
+ <img loading="lazy" align="right" srcset="{{- range $size := $sizes -}}
+ {{- if (ge $cover.Width $size) -}}
+ {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
+ {{ end }}
+ {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}"
+ sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}"
+ width="{{ $cover.Width }}" height="{{ $cover.Height }}">
+ {{- else }}{{/* Unprocessable image or responsive images disabled */}}
+ <img loading="lazy" align="right" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- else }}{{/* For absolute urls and external links, no img processing here */}}
+ {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ <img loading="lazy" align="right" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- if $addLink }}</a>{{ end -}}
+ {{/* Display Caption */}}
+ {{- if not $.IsHome }}
+ {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
+ {{- end }}
+</figure>
+{{- end }}{{/* End image */}}
+{{- end -}}{{/* End context */ -}}
+
在文件 assets\css\extended\blank.css
中增加以下代码:
.entry-cover {
+ float:right;
+ width: 20%;
+ margin-left: 20px;
+}
+
+.entry-content {
+ margin: 20px 0;
+ color: var(--secondary);
+ font-size: 14px;
+ line-height: 1.6;
+ overflow: hidden;
+ display: block;
+}
+
这样做完的效果就是文章列表页的摘要可以自由显示文字和图片,默认的 cover 在文字右边,文字会环绕图片显示。
+根据 Sulv’s Blog 的方法来改。
+将 layouts/_default/terms.html
中的 <ul class="terms-tags">
代码块替换为:
<ul class="terms-tags">
+ {{- $type := .Type }}
+ {{- range $key, $value := .Data.Terms.Alphabetical }}
+ {{- $name := .Name }}
+ {{- $count := .Count }}
+ {{- with $.Site.GetPage (printf "/%s/%s" $type $name) }}
+ <li>
+ {{ $largestFontSize := 1.5 }}
+ {{ $smallestFontSize := 1 }}
+ {{ $fontSpread := sub $largestFontSize $smallestFontSize }}
+ {{ $max := add (len (index $.Site.Taxonomies.tags.ByCount 0).Pages) 1 }}
+ {{ $min := len (index $.Site.Taxonomies.tags.ByCount.Reverse 0).Pages }}
+ {{ $spread := sub $max $min }}
+ {{ $fontStep := div $fontSpread $spread }}
+ {{ $weigth := div (sub (math.Log $count) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }}
+ {{ $currentFontSize := (add $smallestFontSize (mul (sub $largestFontSize $smallestFontSize) $weigth)) }}
+ <a href="{{ .Permalink }}" style="font-size: {{ $currentFontSize }}rem; font-weight: {{ mul $currentFontSize 200 }};">
+ {{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup>
+ </a>
+ </li>
+ {{- end }}
+ {{- end }}
+</ul>
+
在 assets/css/extended/blank.css
中增加如下代码:
/*标签*/
+.terms-tags {
+ text-align: center;
+}
+
+.terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags a {
+ border-radius: 30px;
+ background: none;
+ transition: transform 0.5s;
+}
+
+.dark .terms-tags a {
+ background: none;
+}
+
+.dark .terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags li {
+ margin: 5px;
+}
+
根据 Hugo 官网 的文档来安装 Hugo。
+我使用的主题为:hugo-PaperMod ,根据主题文档安装主题(推荐使用 submodule 的方式安装)。将默认的配置文件 hugo.toml 的后缀改为 yaml,方便使用。
+复制主题网站给的 Sample config.yml 的内容到 hugo.yaml 中,额外增加:
+languageCode: zh
+defaultContentLanguage: zh
+
将网站语言设置为中文。
+复制 Sample Page.md
的内容到 archetypes/default.md 中(title 和 date 不覆盖,只将 toml 格式的配置改为 yaml 格式即可)。可以省去自己一个一个寻找增加的时间,当然,具体内容还是要根据自己的要求来改。
使用命令 hugo new content posts/my-first-post.md
来新建一篇文章。
使用命令 hugo server -D
预览生成好的网页。
大概的代码示例如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
从代码结构来看确实没什么问题,是先保存数据再降低电压,即使后边的操作导致死机也是在写入操作完成后,应该不会影响保存的数据才对。但事实是确实有影响,在 windows 上测试了好几次保存的数据都为空。with open()
语句是 Python 中常用的文件操作语句,不应该会导致写入异常,于是怀疑是降压操作导致的。
在 Python 中,当使用 with open()
语句来写入文件时,它会负责管理文件的打开和关闭,通常情况下, with
语句块结束后,Python 会自动关闭文件,并确保所有数据写入硬盘。但,这个操作不是立即发生的。
当写入文件时,操作系统通常会缓存这些操作,以便一次性的将多个写入操作合并,从而提高效率。这意味着即使 Python 代码执行了写入操作(也就是 write()),也不能保证这些数据已经永久的保存到了硬盘上。如果在 with 语句块结束后立即死机,这些数据可能会丢失。
+现在知道了导致数据保存失败的原因是出在 windows 的系统缓存机制上,那只要找到方法可以强制系统将缓存的数据写入硬盘就好了。修改后的代码如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+
+ # 确保数据从 Python 的内部缓冲区写入操作系统的缓冲区
+ f.flush()
+
+ # 确保数据从操作系统的缓冲区写入磁盘
+ os.fsync(f.fileno())
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
新增了两行代码。
+f.flush()
的作用为刷新 Python 的内部缓冲区,确保所有数据写入操作系统的缓冲区。但这个并不能保证操作系统会立刻将数据写入硬盘。os.fsync(f.fileno())
的作用为强制操作系统将其缓冲区的数据写入硬盘。这样就保证了如果之后的代码导致系统死机,这部分数据也会完整的保存在硬盘上。将修改后的程序在 windows 上测试,数据每次都会完整的保存在硬盘上。
+在 Microsoft 的一篇官方文档中有提到 disk write caching ,也就是写入缓存,并给出了关闭的方法。
+关于 disk write caching ,官方的描述为:
+++Additionally, turning disk write caching on may increase operating system performance; however, it may also result in the loss of information if a power failure, equipment failure, or software failure occurs.
+
确实与我遇到的情况一样。
+还有一篇更详细一点的介绍:https://learn.microsoft.com/en-US/windows/client-management/client-tools/change-default-removal-policy-external-storage-media
+总的来说 disk write caching 在一般情况下可以提高性能。但在需要确保极端情况下写入数据完整性时,可以考虑关闭或者手动强制写入。
]]>利用 Github Actions 来自动构建和部署博客到 Github Pages。这样既可以简化自己的操作,又能保证自己的博客源码的私密性。
...此篇博客介绍我的博客的修改内容和方法。我的主题为 hugo-PaperMod 。我修改前都将博客内的资源文件夹(assets, i18n, lay)复制到了项目根目录中,这样以后更新主题也不会导致修改消失。
...使用 Hugo 搭建博客。
...公司有一个同事做的项目,其中有一个 Python 写的程序会反复降低 CPU 的电压直至死机重启,程序会在降压前保存本次的数据。听起来很合理,先保存数据再降低电压,如果死机了导致重启,那上次的数据也保存到本地了。但在 windows 电脑上实际运行时,每次程序导致 windows 死机重启后,保存的数据文件都为空。他没搞定这个就离职了,于是我就接手来查这个 bug 了。
...具体位置为:个人设置界面 -> Developer Settings -> Personal access tokens -> Tokens(classic)
在此界面新生成一个 token,需要勾选 repo
和 workflow
选项。Expiration
可以设置为 No expiration
。创建完成后会显示你的 token ,它只会显示这一次,你需要将它记下来,后边会用到。
想利用 Github Actions ,需要在博客的根目录下创建 .github/workflows/
文件夹。在该文件夹下创建 yml 文件会被 Github Actions 执行。
我的 workflow 文件如下:
+name: GitHub Pages
+
+on:
+ push:
+ branches:
+ - main # Set a branch to deploy
+
+ release:
+ types:
+ - published
+jobs:
+ deploy:
+ runs-on: ubuntu-20.04
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: main
+
+ - name: Setup Hugo
+ uses: peaceiris/actions-hugo@v2
+ with:
+ hugo-version: '0.119.0'
+ # 是否启用 hugo extend
+ # extended: true
+
+ - name: Build
+ run: hugo --minify
+
+ - name: Deploy
+ run: |
+ cd ./public
+ git init
+ git config --global user.name '${{ secrets.GITHUBUSERNAME }}'
+ git config --global user.email '${{ secrets.GITHUBEMAIL }}'
+ git add .
+ git commit -m "${{ github.event.head_commit.message }}"
+ git push --force --quiet "https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.GITHUBTOKEN }}@github.com/${{ secrets.GITHUBUSERNAME }}/${{ secrets.GITHUBUSERNAME }}.github.io.git" master:main
+ #git push --force --quiet "https://${{ secrets.TOKENUSER }}:${{ secrets.CODINGTOKEN }}@e.coding.net/${{ secrets.CODINGUSERNAME }}/${{ secrets.CODINGBLOGREPO }}.git" master:master #coding部署写法,需要的自行取消注释
+ #git push --force --quiet "https://${{ secrets.GITEEUSERNAME }}:${{ secrets.GITEETOKEN }}@gitee.com/${{ secrets.GITEEUSERNAME }}/${{ secrets.GITEEUSERNAME }}.git" master:master #gitee部署写法,需要的自行取消注释
+
其中,GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
三个为自定义变量,后边会讲到。
新建或使用一个老的仓库,可见性设置为 private 。在代码仓库的 Settings
页面找到 Secrets and variables
,点击其中的 Actions
,添加 workflow 中用到的三个变量:GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
。
+
+完成上述步骤后即可用 git 提交代码到你的源码仓库,即可在仓库的 Actions 界面看到执行的 workflows。
+来自与 Github 。项目地址为: https://github.com/dsrkafuu/sakana-widget 。具体更改为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- 石蒜组件 -->
+<style>
+ html .pull-right{
+ position: absolute;
+ right: 0;
+ top: 70px;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+ }
+ html #sakana-widget{
+ position: fixed;
+ right: 50px;
+ bottom: 0;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+}
+</style>
+<link
+rel="stylesheet"
+href="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.css"
+/>
+<div id="sakana-widget", style="z-index:999"></div>
+<script>
+function initSakanaWidget() {
+ new SakanaWidget().mount('#sakana-widget');
+}
+</script>
+<script
+async
+onload="initSakanaWidget()"
+src="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.js"
+></script>
+
重新编译后即可拥有可爱石蒜小组件。 +
+有两种 High 一下的代码实现:
+javascript:(function(){function c(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",f);e.setAttribute("class",l);document.body.appendChild(e)}function h(){var e=document.getElementsByClassName(l);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function p(){var e=document.createElement("div");e.setAttribute("class",a);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function d(e){return{height:e.offsetHeight,width:e.offsetWidth}}function v(i){var s=d(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function m(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function g(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function y(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function E(e){var t=m(e);return t>=w&&t<=b+w}function S(){var e=document.createElement("audio");e.setAttribute("class",l);e.src=i;e.loop=false;e.addEventListener("canplay",function(){setTimeout(function(){x(k)},500);setTimeout(function(){N();p();for(var e=0;e<O.length;e++){T(O[e])}},15500)},true);e.addEventListener("ended",function(){N();h()},true);e.innerHTML=" <p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p> <p>";document.body.appendChild(e);e.play()}function x(e){e.className+=" "+s+" "+o}function T(e){e.className+=" "+s+" "+u[Math.floor(Math.random()*u.length)]}function N(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.mp3";var s="mw-harlem_shake_me";var o="im_first";var u=["im_drunk","im_baked","im_trippin","im_blown"];var a="mw-strobe_light";var f="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var l="mw_added_css";var b=g();var w=y();var C=document.getElementsByTagName("*");var k=null;for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){if(E(A)){k=A;break}}}if(A===null){console.warn("Could not find a node of the right size. Please try a different page.");return}c();S();var O=[];for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){O.push(A)}}})()
+
javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()
+
以上两种差不多,我个人用的是第二种。具体用法为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- High 一下 -->
+<div class="pull-right", style="z-index:999">
+<a title="把这个链接拖到你的Chrome收藏夹工具栏中" href='javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()'>High一下!</a>
+</div>
+
其中的 href=
的内容替换为上述两种的其中一种即可。
根据 sulvblog 的方法来改。
+先修改 layouts/partials/toc.html
的代码,替换为:
{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
+{{- $has_headers := ge (len $headers) 1 -}}
+{{- if $has_headers -}}
+<aside id="toc-container" class="toc-container wide">
+ <div class="toc">
+ <details {{if (.Param "TocOpen") }} open{{ end }}>
+ <summary accesskey="c" title="(Alt + C)">
+ <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span>
+ </summary>
+
+ <div class="inner">
+ {{- $largest := 6 -}}
+ {{- range $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+ {{- if lt $headerLevel $largest -}}
+ {{- $largest = $headerLevel -}}
+ {{- end -}}
+ {{- end -}}
+
+ {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
+
+ {{- $.Scratch.Set "bareul" slice -}}
+ <ul>
+ {{- range seq (sub $firstHeaderLevel $largest) -}}
+ <ul>
+ {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
+ {{- end -}}
+ {{- range $i, $header := $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+
+ {{/* get id="xyz" */}}
+ {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
+
+ {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
+ {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
+ {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
+
+ {{- if ne $i 0 -}}
+ {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
+ {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
+ {{- if gt $headerLevel $prevHeaderLevel -}}
+ {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
+ <ul>
+ {{/* the first should not be recorded */}}
+ {{- if ne $prevHeaderLevel . -}}
+ {{- $.Scratch.Add "bareul" . -}}
+ {{- end -}}
+ {{- end -}}
+ {{- else -}}
+ </li>
+ {{- if lt $headerLevel $prevHeaderLevel -}}
+ {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
+ {{- if in ($.Scratch.Get "bareul") . -}}
+ </ul>
+ {{/* manually do pop item */}}
+ {{- $tmp := $.Scratch.Get "bareul" -}}
+ {{- $.Scratch.Delete "bareul" -}}
+ {{- $.Scratch.Set "bareul" slice}}
+ {{- range seq (sub (len $tmp) 1) -}}
+ {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
+ {{- end -}}
+ {{- else -}}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- else }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- end -}}
+ {{- end -}}
+ <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} -->
+ {{- $firstHeaderLevel := $largest }}
+ {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
+ </li>
+ {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
+ {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
+ </ul>
+ {{- else }}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end }}
+ </ul>
+ </div>
+ </details>
+ </div>
+</aside>
+<script>
+ let activeElement;
+ let elements;
+ window.addEventListener('DOMContentLoaded', function (event) {
+ checkTocPosition();
+
+ elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]');
+ // Make the first header active
+ activeElement = elements[0];
+ const id = encodeURI(activeElement.getAttribute('id')).toLowerCase();
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ }, false);
+
+ window.addEventListener('resize', function(event) {
+ checkTocPosition();
+ }, false);
+
+ window.addEventListener('scroll', () => {
+ // Check if there is an object in the top half of the screen or keep the last item active
+ activeElement = Array.from(elements).find((element) => {
+ if ((getOffsetTop(element) - window.pageYOffset) > 0 &&
+ (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) {
+ return element;
+ }
+ }) || activeElement
+
+ elements.forEach(element => {
+ const id = encodeURI(element.getAttribute('id')).toLowerCase();
+ if (element === activeElement){
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ } else {
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active');
+ }
+ })
+ }, false);
+
+ const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10);
+ const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10);
+ const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10);
+
+ function checkTocPosition() {
+ const width = document.body.scrollWidth;
+
+ if (width - main - (toc * 2) - (gap * 4) > 0) {
+ document.getElementById("toc-container").classList.add("wide");
+ } else {
+ document.getElementById("toc-container").classList.remove("wide");
+ }
+ }
+
+ function getOffsetTop(element) {
+ if (!element.getClientRects().length) {
+ return 0;
+ }
+ let rect = element.getBoundingClientRect();
+ let win = element.ownerDocument.defaultView;
+ return rect.top + win.pageYOffset;
+ }
+</script>
+{{- end }}
+
layouts/_default/single.html
文件中默认有 toc.html 的调用,如果未更改的话不用管,更改的话请调用:
{{- if (.Param "ShowToc") }}
+{{- partial "toc.html" . }}
+{{- end }}
+
修改 css/extended/blank.css
文件,加入下方代码:
:root {
+ --nav-width: 1380px;
+ --article-width: 650px;
+ --toc-width: 300px;
+}
+
+.toc {
+ margin: 0 2px 40px 2px;
+ border: 1px solid var(--border);
+ background: var(--entry);
+ border-radius: var(--radius);
+ padding: 0.4em;
+}
+
+.toc-container.wide {
+ position: absolute;
+ height: 100%;
+ border-right: 1px solid var(--border);
+ left: calc((var(--toc-width) + var(--gap)) * -1);
+ top: calc(var(--gap) * 2);
+ width: var(--toc-width);
+}
+
+.wide .toc {
+ position: sticky;
+ top: var(--gap);
+ border: unset;
+ background: unset;
+ border-radius: unset;
+ width: 100%;
+ margin: 0 2px 40px 2px;
+}
+
+.toc details summary {
+ cursor: zoom-in;
+ margin-inline-start: 20px;
+ padding: 12px 0;
+}
+
+.toc details[open] summary {
+ font-weight: 500;
+}
+
+.toc-container.wide .toc .inner {
+ margin: 0;
+}
+
+.active {
+ font-size: 110%;
+ font-weight: 600;
+}
+
+.toc ul {
+ list-style-type: circle;
+}
+
+.toc .inner {
+ margin: 0 0 0 20px;
+ padding: 0px 15px 15px 20px;
+ font-size: 16px;
+
+ /*目录显示高度*/
+ max-height: 83vh;
+ overflow-y: auto;
+}
+
+.toc .inner::-webkit-scrollbar-thumb { /*滚动条*/
+ background: var(--border);
+ border: 7px solid var(--theme);
+ border-radius: var(--radius);
+}
+
+.toc li ul {
+ margin-inline-start: calc(var(--gap) * 0.5);
+ list-style-type: none;
+}
+
+.toc li {
+ list-style: none;
+ font-size: 0.95rem;
+ padding-bottom: 5px;
+}
+
+.toc li a:hover {
+ color: var(--secondary);
+}
+
重新编译后即可看到目录到了左边。
+修改 ·assets\css\core\theme-vars.css
文件的 :root
节点中的 --main-width
我设置的是 1024px
放弃主题默认的 Highlight.js,改为 Hugo 默认的 chroma 方式渲染。根据 官方说明 来改。我用的 style 为 github-dark
在 assets\css\common\post-single.css
文件中找到 .post-content code
增加 max-height: 40em;
。目前设置好最大高度后会与 lineNos: true
设置冲突,会显示两个滑动条 :( ,所以就先不开它。
默认显示的图片在摘要上方而且很大,一页只能显示几个,感觉体验不是很好,所以将它改为在摘要右方显示。我很喜欢 CoolShell 的文章页的显示方法,准备改成差不多样子的。
+文件为 layouts\_default\list.html
。默认的图片显示代码为:
{{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+{{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+
位于 <header class="entry-header">
的上方。将其移动到 <div class="entry-content">
中并替换默认的 Summary 处理方式,如下所示:
<div class="entry-content">
+ {{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+ {{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+ {{ .Summary | replaceRE "\n" "<br>" | safeHTML }}{{ if .Truncated }}...{{ end }}
+</div>
+
文件为 layouts\partials\cover.html
。将 所有 <img
块都加上 align="right"
。完整如下所示:
{{- with .cxt}} {{/* Apply proper context from dict */}}
+{{- if (and .Params.cover.image (not $.isHidden)) }}
+{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
+<figure class="entry-cover">
+ {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
+ {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
+ {{- $pageBundleCover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $globalResourcesCover := (resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $cover := (or $pageBundleCover $globalResourcesCover)}}
+ {{- if $cover -}}{{/* i.e it is present in page bundle */}}
+ {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
+ {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
+ {{- if hugo.IsExtended -}}
+ {{- $processableFormats = $processableFormats | append "webp" -}}
+ {{- end -}}
+ {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
+ {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
+ <img loading="lazy" align="right" srcset="{{- range $size := $sizes -}}
+ {{- if (ge $cover.Width $size) -}}
+ {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
+ {{ end }}
+ {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}"
+ sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}"
+ width="{{ $cover.Width }}" height="{{ $cover.Height }}">
+ {{- else }}{{/* Unprocessable image or responsive images disabled */}}
+ <img loading="lazy" align="right" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- else }}{{/* For absolute urls and external links, no img processing here */}}
+ {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ <img loading="lazy" align="right" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- if $addLink }}</a>{{ end -}}
+ {{/* Display Caption */}}
+ {{- if not $.IsHome }}
+ {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
+ {{- end }}
+</figure>
+{{- end }}{{/* End image */}}
+{{- end -}}{{/* End context */ -}}
+
在文件 assets\css\extended\blank.css
中增加以下代码:
.entry-cover {
+ float:right;
+ width: 20%;
+ margin-left: 20px;
+}
+
+.entry-content {
+ margin: 20px 0;
+ color: var(--secondary);
+ font-size: 14px;
+ line-height: 1.6;
+ overflow: hidden;
+ display: block;
+}
+
这样做完的效果就是文章列表页的摘要可以自由显示文字和图片,默认的 cover 在文字右边,文字会环绕图片显示。
+根据 Sulv’s Blog 的方法来改。
+将 layouts/_default/terms.html
中的 <ul class="terms-tags">
代码块替换为:
<ul class="terms-tags">
+ {{- $type := .Type }}
+ {{- range $key, $value := .Data.Terms.Alphabetical }}
+ {{- $name := .Name }}
+ {{- $count := .Count }}
+ {{- with $.Site.GetPage (printf "/%s/%s" $type $name) }}
+ <li>
+ {{ $largestFontSize := 1.5 }}
+ {{ $smallestFontSize := 1 }}
+ {{ $fontSpread := sub $largestFontSize $smallestFontSize }}
+ {{ $max := add (len (index $.Site.Taxonomies.tags.ByCount 0).Pages) 1 }}
+ {{ $min := len (index $.Site.Taxonomies.tags.ByCount.Reverse 0).Pages }}
+ {{ $spread := sub $max $min }}
+ {{ $fontStep := div $fontSpread $spread }}
+ {{ $weigth := div (sub (math.Log $count) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }}
+ {{ $currentFontSize := (add $smallestFontSize (mul (sub $largestFontSize $smallestFontSize) $weigth)) }}
+ <a href="{{ .Permalink }}" style="font-size: {{ $currentFontSize }}rem; font-weight: {{ mul $currentFontSize 200 }};">
+ {{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup>
+ </a>
+ </li>
+ {{- end }}
+ {{- end }}
+</ul>
+
在 assets/css/extended/blank.css
中增加如下代码:
/*标签*/
+.terms-tags {
+ text-align: center;
+}
+
+.terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags a {
+ border-radius: 30px;
+ background: none;
+ transition: transform 0.5s;
+}
+
+.dark .terms-tags a {
+ background: none;
+}
+
+.dark .terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags li {
+ margin: 5px;
+}
+
根据 Hugo 官网 的文档来安装 Hugo。
+我使用的主题为:hugo-PaperMod ,根据主题文档安装主题(推荐使用 submodule 的方式安装)。将默认的配置文件 hugo.toml 的后缀改为 yaml,方便使用。
+复制主题网站给的 Sample config.yml 的内容到 hugo.yaml 中,额外增加:
+languageCode: zh
+defaultContentLanguage: zh
+
将网站语言设置为中文。
+复制 Sample Page.md
的内容到 archetypes/default.md 中(title 和 date 不覆盖,只将 toml 格式的配置改为 yaml 格式即可)。可以省去自己一个一个寻找增加的时间,当然,具体内容还是要根据自己的要求来改。
使用命令 hugo new content posts/my-first-post.md
来新建一篇文章。
使用命令 hugo server -D
预览生成好的网页。
大概的代码示例如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
从代码结构来看确实没什么问题,是先保存数据再降低电压,即使后边的操作导致死机也是在写入操作完成后,应该不会影响保存的数据才对。但事实是确实有影响,在 windows 上测试了好几次保存的数据都为空。with open()
语句是 Python 中常用的文件操作语句,不应该会导致写入异常,于是怀疑是降压操作导致的。
在 Python 中,当使用 with open()
语句来写入文件时,它会负责管理文件的打开和关闭,通常情况下, with
语句块结束后,Python 会自动关闭文件,并确保所有数据写入硬盘。但,这个操作不是立即发生的。
当写入文件时,操作系统通常会缓存这些操作,以便一次性的将多个写入操作合并,从而提高效率。这意味着即使 Python 代码执行了写入操作(也就是 write()),也不能保证这些数据已经永久的保存到了硬盘上。如果在 with 语句块结束后立即死机,这些数据可能会丢失。
+现在知道了导致数据保存失败的原因是出在 windows 的系统缓存机制上,那只要找到方法可以强制系统将缓存的数据写入硬盘就好了。修改后的代码如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+
+ # 确保数据从 Python 的内部缓冲区写入操作系统的缓冲区
+ f.flush()
+
+ # 确保数据从操作系统的缓冲区写入磁盘
+ os.fsync(f.fileno())
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
新增了两行代码。
+f.flush()
的作用为刷新 Python 的内部缓冲区,确保所有数据写入操作系统的缓冲区。但这个并不能保证操作系统会立刻将数据写入硬盘。os.fsync(f.fileno())
的作用为强制操作系统将其缓冲区的数据写入硬盘。这样就保证了如果之后的代码导致系统死机,这部分数据也会完整的保存在硬盘上。将修改后的程序在 windows 上测试,数据每次都会完整的保存在硬盘上。
+在 Microsoft 的一篇官方文档中有提到 disk write caching ,也就是写入缓存,并给出了关闭的方法。
+关于 disk write caching ,官方的描述为:
+++Additionally, turning disk write caching on may increase operating system performance; however, it may also result in the loss of information if a power failure, equipment failure, or software failure occurs.
+
确实与我遇到的情况一样。
+还有一篇更详细一点的介绍:https://learn.microsoft.com/en-US/windows/client-management/client-tools/change-default-removal-policy-external-storage-media
+总的来说 disk write caching 在一般情况下可以提高性能。但在需要确保极端情况下写入数据完整性时,可以考虑关闭或者手动强制写入。
]]>公司有一个同事做的项目,其中有一个 Python 写的程序会反复降低 CPU 的电压直至死机重启,程序会在降压前保存本次的数据。听起来很合理,先保存数据再降低电压,如果死机了导致重启,那上次的数据也保存到本地了。但在 windows 电脑上实际运行时,每次程序导致 windows 死机重启后,保存的数据文件都为空。他没搞定这个就离职了,于是我就接手来查这个 bug 了。
大概的代码示例如下:
# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
从代码结构来看确实没什么问题,是先保存数据再降低电压,即使后边的操作导致死机也是在写入操作完成后,应该不会影响保存的数据才对。但事实是确实有影响,在 windows 上测试了好几次保存的数据都为空。with open()
语句是 Python 中常用的文件操作语句,不应该会导致写入异常,于是怀疑是降压操作导致的。
在 Python 中,当使用 with open()
语句来写入文件时,它会负责管理文件的打开和关闭,通常情况下, with
语句块结束后,Python 会自动关闭文件,并确保所有数据写入硬盘。但,这个操作不是立即发生的。
当写入文件时,操作系统通常会缓存这些操作,以便一次性的将多个写入操作合并,从而提高效率。这意味着即使 Python 代码执行了写入操作(也就是 write()),也不能保证这些数据已经永久的保存到了硬盘上。如果在 with 语句块结束后立即死机,这些数据可能会丢失。
现在知道了导致数据保存失败的原因是出在 windows 的系统缓存机制上,那只要找到方法可以强制系统将缓存的数据写入硬盘就好了。修改后的代码如下:
# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+
+ # 确保数据从 Python 的内部缓冲区写入操作系统的缓冲区
+ f.flush()
+
+ # 确保数据从操作系统的缓冲区写入磁盘
+ os.fsync(f.fileno())
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
新增了两行代码。
f.flush()
的作用为刷新 Python 的内部缓冲区,确保所有数据写入操作系统的缓冲区。但这个并不能保证操作系统会立刻将数据写入硬盘。os.fsync(f.fileno())
的作用为强制操作系统将其缓冲区的数据写入硬盘。这样就保证了如果之后的代码导致系统死机,这部分数据也会完整的保存在硬盘上。将修改后的程序在 windows 上测试,数据每次都会完整的保存在硬盘上。
在 Microsoft 的一篇官方文档中有提到 disk write caching ,也就是写入缓存,并给出了关闭的方法。
关于 disk write caching ,官方的描述为:
Additionally, turning disk write caching on may increase operating system performance; however, it may also result in the loss of information if a power failure, equipment failure, or software failure occurs.
确实与我遇到的情况一样。
还有一篇更详细一点的介绍:https://learn.microsoft.com/en-US/windows/client-management/client-tools/change-default-removal-policy-external-storage-media
总的来说 disk write caching 在一般情况下可以提高性能。但在需要确保极端情况下写入数据完整性时,可以考虑关闭或者手动强制写入。
利用 Github Actions 来自动构建和部署博客到 Github Pages。这样既可以简化自己的操作,又能保证自己的博客源码的私密性。
具体位置为:个人设置界面 -> Developer Settings -> Personal access tokens -> Tokens(classic)
在此界面新生成一个 token,需要勾选 repo
和 workflow
选项。Expiration
可以设置为 No expiration
。创建完成后会显示你的 token ,它只会显示这一次,你需要将它记下来,后边会用到。
想利用 Github Actions ,需要在博客的根目录下创建 .github/workflows/
文件夹。在该文件夹下创建 yml 文件会被 Github Actions 执行。
我的 workflow 文件如下:
name: GitHub Pages
+
+on:
+ push:
+ branches:
+ - main # Set a branch to deploy
+
+ release:
+ types:
+ - published
+jobs:
+ deploy:
+ runs-on: ubuntu-20.04
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: main
+
+ - name: Setup Hugo
+ uses: peaceiris/actions-hugo@v2
+ with:
+ hugo-version: '0.119.0'
+ # 是否启用 hugo extend
+ # extended: true
+
+ - name: Build
+ run: hugo --minify
+
+ - name: Deploy
+ run: |
+ cd ./public
+ git init
+ git config --global user.name '${{ secrets.GITHUBUSERNAME }}'
+ git config --global user.email '${{ secrets.GITHUBEMAIL }}'
+ git add .
+ git commit -m "${{ github.event.head_commit.message }}"
+ git push --force --quiet "https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.GITHUBTOKEN }}@github.com/${{ secrets.GITHUBUSERNAME }}/${{ secrets.GITHUBUSERNAME }}.github.io.git" master:main
+ #git push --force --quiet "https://${{ secrets.TOKENUSER }}:${{ secrets.CODINGTOKEN }}@e.coding.net/${{ secrets.CODINGUSERNAME }}/${{ secrets.CODINGBLOGREPO }}.git" master:master #coding部署写法,需要的自行取消注释
+ #git push --force --quiet "https://${{ secrets.GITEEUSERNAME }}:${{ secrets.GITEETOKEN }}@gitee.com/${{ secrets.GITEEUSERNAME }}/${{ secrets.GITEEUSERNAME }}.git" master:master #gitee部署写法,需要的自行取消注释
+
其中,GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
三个为自定义变量,后边会讲到。
新建或使用一个老的仓库,可见性设置为 private 。在代码仓库的 Settings
页面找到 Secrets and variables
,点击其中的 Actions
,添加 workflow 中用到的三个变量:GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
。
完成上述步骤后即可用 git 提交代码到你的源码仓库,即可在仓库的 Actions 界面看到执行的 workflows。
利用 Github Actions 来自动构建和部署博客到 Github Pages。这样既可以简化自己的操作,又能保证自己的博客源码的私密性。
具体位置为:个人设置界面 -> Developer Settings -> Personal access tokens -> Tokens(classic)
在此界面新生成一个 token,需要勾选 repo
和 workflow
选项。Expiration
可以设置为 No expiration
。创建完成后会显示你的 token ,它只会显示这一次,你需要将它记下来,后边会用到。
想利用 Github Actions ,需要在博客的根目录下创建 .github/workflows/
文件夹。在该文件夹下创建 yml 文件会被 Github Actions 执行。
我的 workflow 文件如下:
name: GitHub Pages
+
+on:
+ push:
+ branches:
+ - main # Set a branch to deploy
+
+ release:
+ types:
+ - published
+jobs:
+ deploy:
+ runs-on: ubuntu-20.04
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: main
+
+ - name: Setup Hugo
+ uses: peaceiris/actions-hugo@v2
+ with:
+ hugo-version: 'latest'
+ # 是否启用 hugo extend
+ # extended: true
+
+ - name: Build
+ run: hugo --minify
+
+ - name: Deploy
+ run: |
+ cd ./public
+ git init
+ git config --global user.name '${{ secrets.GITHUBUSERNAME }}'
+ git config --global user.email '${{ secrets.GITHUBEMAIL }}'
+ git add .
+ git commit -m "${{ github.event.head_commit.message }}"
+ git push --force --quiet "https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.GITHUBTOKEN }}@github.com/${{ secrets.GITHUBUSERNAME }}/${{ secrets.GITHUBUSERNAME }}.github.io.git" master:main
+ #git push --force --quiet "https://${{ secrets.TOKENUSER }}:${{ secrets.CODINGTOKEN }}@e.coding.net/${{ secrets.CODINGUSERNAME }}/${{ secrets.CODINGBLOGREPO }}.git" master:master #coding部署写法,需要的自行取消注释
+ #git push --force --quiet "https://${{ secrets.GITEEUSERNAME }}:${{ secrets.GITEETOKEN }}@gitee.com/${{ secrets.GITEEUSERNAME }}/${{ secrets.GITEEUSERNAME }}.git" master:master #gitee部署写法,需要的自行取消注释
+
其中,GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
三个为自定义变量,后边会讲到。
新建或使用一个老的仓库,可见性设置为 private 。在代码仓库的 Settings
页面找到 Secrets and variables
,点击其中的 Actions
,添加 workflow 中用到的三个变量:GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
。
完成上述步骤后即可用 git 提交代码到你的源码仓库,即可在仓库的 Actions 界面看到执行的 workflows。
使用 Hugo 搭建博客。
根据 Hugo 官网 的文档来安装 Hugo。
我使用的主题为:hugo-PaperMod ,根据主题文档安装主题(推荐使用 submodule 的方式安装)。将默认的配置文件 hugo.toml 的后缀改为 yaml,方便使用。
复制主题网站给的 Sample config.yml 的内容到 hugo.yaml 中,额外增加:
languageCode: zh
+defaultContentLanguage: zh
+
将网站语言设置为中文。
复制 Sample Page.md
的内容到 archetypes/default.md 中(title 和 date 不覆盖,只将 toml 格式的配置改为 yaml 格式即可)。可以省去自己一个一个寻找增加的时间,当然,具体内容还是要根据自己的要求来改。
使用命令 hugo new content posts/my-first-post.md
来新建一篇文章。
使用命令 hugo server -D
预览生成好的网页。
此篇博客介绍我的博客的修改内容和方法。我的主题为 hugo-PaperMod 。我修改前都将博客内的资源文件夹(assets, i18n, lay)复制到了项目根目录中,这样以后更新主题也不会导致修改消失。
来自与 Github 。项目地址为: https://github.com/dsrkafuu/sakana-widget 。具体更改为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- 石蒜组件 -->
+<style>
+ html .pull-right{
+ position: absolute;
+ right: 0;
+ top: 70px;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+ }
+ html #sakana-widget{
+ position: fixed;
+ right: 50px;
+ bottom: 0;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+}
+</style>
+<link
+rel="stylesheet"
+href="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.css"
+/>
+<div id="sakana-widget", style="z-index:999"></div>
+<script>
+function initSakanaWidget() {
+ new SakanaWidget().mount('#sakana-widget');
+}
+</script>
+<script
+async
+onload="initSakanaWidget()"
+src="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.js"
+></script>
+
重新编译后即可拥有可爱石蒜小组件。
有两种 High 一下的代码实现:
javascript:(function(){function c(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",f);e.setAttribute("class",l);document.body.appendChild(e)}function h(){var e=document.getElementsByClassName(l);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function p(){var e=document.createElement("div");e.setAttribute("class",a);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function d(e){return{height:e.offsetHeight,width:e.offsetWidth}}function v(i){var s=d(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function m(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function g(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function y(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function E(e){var t=m(e);return t>=w&&t<=b+w}function S(){var e=document.createElement("audio");e.setAttribute("class",l);e.src=i;e.loop=false;e.addEventListener("canplay",function(){setTimeout(function(){x(k)},500);setTimeout(function(){N();p();for(var e=0;e<O.length;e++){T(O[e])}},15500)},true);e.addEventListener("ended",function(){N();h()},true);e.innerHTML=" <p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p> <p>";document.body.appendChild(e);e.play()}function x(e){e.className+=" "+s+" "+o}function T(e){e.className+=" "+s+" "+u[Math.floor(Math.random()*u.length)]}function N(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.mp3";var s="mw-harlem_shake_me";var o="im_first";var u=["im_drunk","im_baked","im_trippin","im_blown"];var a="mw-strobe_light";var f="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var l="mw_added_css";var b=g();var w=y();var C=document.getElementsByTagName("*");var k=null;for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){if(E(A)){k=A;break}}}if(A===null){console.warn("Could not find a node of the right size. Please try a different page.");return}c();S();var O=[];for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){O.push(A)}}})()
+
javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()
+
以上两种差不多,我个人用的是第二种。具体用法为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- High 一下 -->
+<div class="pull-right", style="z-index:999">
+<a title="把这个链接拖到你的Chrome收藏夹工具栏中" href='javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()'>High一下!</a>
+</div>
+
其中的 href=
的内容替换为上述两种的其中一种即可。
根据 sulvblog 的方法来改。
先修改 layouts/partials/toc.html
的代码,替换为:
{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
+{{- $has_headers := ge (len $headers) 1 -}}
+{{- if $has_headers -}}
+<aside id="toc-container" class="toc-container wide">
+ <div class="toc">
+ <details {{if (.Param "TocOpen") }} open{{ end }}>
+ <summary accesskey="c" title="(Alt + C)">
+ <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span>
+ </summary>
+
+ <div class="inner">
+ {{- $largest := 6 -}}
+ {{- range $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+ {{- if lt $headerLevel $largest -}}
+ {{- $largest = $headerLevel -}}
+ {{- end -}}
+ {{- end -}}
+
+ {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
+
+ {{- $.Scratch.Set "bareul" slice -}}
+ <ul>
+ {{- range seq (sub $firstHeaderLevel $largest) -}}
+ <ul>
+ {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
+ {{- end -}}
+ {{- range $i, $header := $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+
+ {{/* get id="xyz" */}}
+ {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
+
+ {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
+ {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
+ {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
+
+ {{- if ne $i 0 -}}
+ {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
+ {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
+ {{- if gt $headerLevel $prevHeaderLevel -}}
+ {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
+ <ul>
+ {{/* the first should not be recorded */}}
+ {{- if ne $prevHeaderLevel . -}}
+ {{- $.Scratch.Add "bareul" . -}}
+ {{- end -}}
+ {{- end -}}
+ {{- else -}}
+ </li>
+ {{- if lt $headerLevel $prevHeaderLevel -}}
+ {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
+ {{- if in ($.Scratch.Get "bareul") . -}}
+ </ul>
+ {{/* manually do pop item */}}
+ {{- $tmp := $.Scratch.Get "bareul" -}}
+ {{- $.Scratch.Delete "bareul" -}}
+ {{- $.Scratch.Set "bareul" slice}}
+ {{- range seq (sub (len $tmp) 1) -}}
+ {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
+ {{- end -}}
+ {{- else -}}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- else }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- end -}}
+ {{- end -}}
+ <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} -->
+ {{- $firstHeaderLevel := $largest }}
+ {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
+ </li>
+ {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
+ {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
+ </ul>
+ {{- else }}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end }}
+ </ul>
+ </div>
+ </details>
+ </div>
+</aside>
+<script>
+ let activeElement;
+ let elements;
+ window.addEventListener('DOMContentLoaded', function (event) {
+ checkTocPosition();
+
+ elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]');
+ // Make the first header active
+ activeElement = elements[0];
+ const id = encodeURI(activeElement.getAttribute('id')).toLowerCase();
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ }, false);
+
+ window.addEventListener('resize', function(event) {
+ checkTocPosition();
+ }, false);
+
+ window.addEventListener('scroll', () => {
+ // Check if there is an object in the top half of the screen or keep the last item active
+ activeElement = Array.from(elements).find((element) => {
+ if ((getOffsetTop(element) - window.pageYOffset) > 0 &&
+ (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) {
+ return element;
+ }
+ }) || activeElement
+
+ elements.forEach(element => {
+ const id = encodeURI(element.getAttribute('id')).toLowerCase();
+ if (element === activeElement){
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ } else {
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active');
+ }
+ })
+ }, false);
+
+ const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10);
+ const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10);
+ const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10);
+
+ function checkTocPosition() {
+ const width = document.body.scrollWidth;
+
+ if (width - main - (toc * 2) - (gap * 4) > 0) {
+ document.getElementById("toc-container").classList.add("wide");
+ } else {
+ document.getElementById("toc-container").classList.remove("wide");
+ }
+ }
+
+ function getOffsetTop(element) {
+ if (!element.getClientRects().length) {
+ return 0;
+ }
+ let rect = element.getBoundingClientRect();
+ let win = element.ownerDocument.defaultView;
+ return rect.top + win.pageYOffset;
+ }
+</script>
+{{- end }}
+
layouts/_default/single.html
文件中默认有 toc.html 的调用,如果未更改的话不用管,更改的话请调用:
{{- if (.Param "ShowToc") }}
+{{- partial "toc.html" . }}
+{{- end }}
+
修改 css/extended/blank.css
文件,加入下方代码:
:root {
+ --nav-width: 1380px;
+ --article-width: 650px;
+ --toc-width: 300px;
+}
+
+.toc {
+ margin: 0 2px 40px 2px;
+ border: 1px solid var(--border);
+ background: var(--entry);
+ border-radius: var(--radius);
+ padding: 0.4em;
+}
+
+.toc-container.wide {
+ position: absolute;
+ height: 100%;
+ border-right: 1px solid var(--border);
+ left: calc((var(--toc-width) + var(--gap)) * -1);
+ top: calc(var(--gap) * 2);
+ width: var(--toc-width);
+}
+
+.wide .toc {
+ position: sticky;
+ top: var(--gap);
+ border: unset;
+ background: unset;
+ border-radius: unset;
+ width: 100%;
+ margin: 0 2px 40px 2px;
+}
+
+.toc details summary {
+ cursor: zoom-in;
+ margin-inline-start: 20px;
+ padding: 12px 0;
+}
+
+.toc details[open] summary {
+ font-weight: 500;
+}
+
+.toc-container.wide .toc .inner {
+ margin: 0;
+}
+
+.active {
+ font-size: 110%;
+ font-weight: 600;
+}
+
+.toc ul {
+ list-style-type: circle;
+}
+
+.toc .inner {
+ margin: 0 0 0 20px;
+ padding: 0px 15px 15px 20px;
+ font-size: 16px;
+
+ /*目录显示高度*/
+ max-height: 83vh;
+ overflow-y: auto;
+}
+
+.toc .inner::-webkit-scrollbar-thumb { /*滚动条*/
+ background: var(--border);
+ border: 7px solid var(--theme);
+ border-radius: var(--radius);
+}
+
+.toc li ul {
+ margin-inline-start: calc(var(--gap) * 0.5);
+ list-style-type: none;
+}
+
+.toc li {
+ list-style: none;
+ font-size: 0.95rem;
+ padding-bottom: 5px;
+}
+
+.toc li a:hover {
+ color: var(--secondary);
+}
+
重新编译后即可看到目录到了左边。
修改 ·assets\css\core\theme-vars.css
文件的 :root
节点中的 --main-width
我设置的是 1024px
放弃主题默认的 Highlight.js,改为 Hugo 默认的 chroma 方式渲染。根据 官方说明 来改。我用的 style 为 github-dark
在 assets\css\common\post-single.css
文件中找到 .post-content code
增加 max-height: 40em;
。目前设置好最大高度后会与 lineNos: true
设置冲突,会显示两个滑动条 :( ,所以就先不开它。
默认显示的图片在摘要上方而且很大,一页只能显示几个,感觉体验不是很好,所以将它改为在摘要右方显示。我很喜欢 CoolShell 的文章页的显示方法,准备改成差不多样子的。
文件为 layouts\_default\list.html
。默认的图片显示代码为:
{{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+{{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+
位于 <header class="entry-header">
的上方。将其移动到 <div class="entry-content">
中并替换默认的 Summary 处理方式,如下所示:
<div class="entry-content">
+ {{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+ {{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+ {{ .Summary | replaceRE "\n" "<br>" | safeHTML }}{{ if .Truncated }}...{{ end }}
+</div>
+
文件为 layouts\partials\cover.html
。将 所有 <img
块都加上 align="right"
。完整如下所示:
{{- with .cxt}} {{/* Apply proper context from dict */}}
+{{- if (and .Params.cover.image (not $.isHidden)) }}
+{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
+<figure class="entry-cover">
+ {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
+ {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
+ {{- $pageBundleCover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $globalResourcesCover := (resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $cover := (or $pageBundleCover $globalResourcesCover)}}
+ {{- if $cover -}}{{/* i.e it is present in page bundle */}}
+ {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
+ {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
+ {{- if hugo.IsExtended -}}
+ {{- $processableFormats = $processableFormats | append "webp" -}}
+ {{- end -}}
+ {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
+ {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
+ <img loading="lazy" align="right" srcset="{{- range $size := $sizes -}}
+ {{- if (ge $cover.Width $size) -}}
+ {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
+ {{ end }}
+ {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}"
+ sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}"
+ width="{{ $cover.Width }}" height="{{ $cover.Height }}">
+ {{- else }}{{/* Unprocessable image or responsive images disabled */}}
+ <img loading="lazy" align="right" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- else }}{{/* For absolute urls and external links, no img processing here */}}
+ {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ <img loading="lazy" align="right" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- if $addLink }}</a>{{ end -}}
+ {{/* Display Caption */}}
+ {{- if not $.IsHome }}
+ {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
+ {{- end }}
+</figure>
+{{- end }}{{/* End image */}}
+{{- end -}}{{/* End context */ -}}
+
在文件 assets\css\extended\blank.css
中增加以下代码:
.entry-cover {
+ float:right;
+ width: 20%;
+ margin-left: 20px;
+}
+
+.entry-content {
+ margin: 20px 0;
+ color: var(--secondary);
+ font-size: 14px;
+ line-height: 1.6;
+ overflow: hidden;
+ display: block;
+}
+
这样做完的效果就是文章列表页的摘要可以自由显示文字和图片,默认的 cover 在文字右边,文字会环绕图片显示。
根据 Sulv’s Blog 的方法来改。
将 layouts/_default/terms.html
中的 <ul class="terms-tags">
代码块替换为:
<ul class="terms-tags">
+ {{- $type := .Type }}
+ {{- range $key, $value := .Data.Terms.Alphabetical }}
+ {{- $name := .Name }}
+ {{- $count := .Count }}
+ {{- with $.Site.GetPage (printf "/%s/%s" $type $name) }}
+ <li>
+ {{ $largestFontSize := 1.5 }}
+ {{ $smallestFontSize := 1 }}
+ {{ $fontSpread := sub $largestFontSize $smallestFontSize }}
+ {{ $max := add (len (index $.Site.Taxonomies.tags.ByCount 0).Pages) 1 }}
+ {{ $min := len (index $.Site.Taxonomies.tags.ByCount.Reverse 0).Pages }}
+ {{ $spread := sub $max $min }}
+ {{ $fontStep := div $fontSpread $spread }}
+ {{ $weigth := div (sub (math.Log $count) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }}
+ {{ $currentFontSize := (add $smallestFontSize (mul (sub $largestFontSize $smallestFontSize) $weigth)) }}
+ <a href="{{ .Permalink }}" style="font-size: {{ $currentFontSize }}rem; font-weight: {{ mul $currentFontSize 200 }};">
+ {{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup>
+ </a>
+ </li>
+ {{- end }}
+ {{- end }}
+</ul>
+
在 assets/css/extended/blank.css
中增加如下代码:
/*标签*/
+.terms-tags {
+ text-align: center;
+}
+
+.terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags a {
+ border-radius: 30px;
+ background: none;
+ transition: transform 0.5s;
+}
+
+.dark .terms-tags a {
+ background: none;
+}
+
+.dark .terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags li {
+ margin: 5px;
+}
+
公司有一个同事做的项目,其中有一个 Python 写的程序会反复降低 CPU 的电压直至死机重启,程序会在降压前保存本次的数据。听起来很合理,先保存数据再降低电压,如果死机了导致重启,那上次的数据也保存到本地了。但在 windows 电脑上实际运行时,每次程序导致 windows 死机重启后,保存的数据文件都为空。他没搞定这个就离职了,于是我就接手来查这个 bug 了。
...大概的代码示例如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
从代码结构来看确实没什么问题,是先保存数据再降低电压,即使后边的操作导致死机也是在写入操作完成后,应该不会影响保存的数据才对。但事实是确实有影响,在 windows 上测试了好几次保存的数据都为空。with open()
语句是 Python 中常用的文件操作语句,不应该会导致写入异常,于是怀疑是降压操作导致的。
在 Python 中,当使用 with open()
语句来写入文件时,它会负责管理文件的打开和关闭,通常情况下, with
语句块结束后,Python 会自动关闭文件,并确保所有数据写入硬盘。但,这个操作不是立即发生的。
当写入文件时,操作系统通常会缓存这些操作,以便一次性的将多个写入操作合并,从而提高效率。这意味着即使 Python 代码执行了写入操作(也就是 write()),也不能保证这些数据已经永久的保存到了硬盘上。如果在 with 语句块结束后立即死机,这些数据可能会丢失。
+现在知道了导致数据保存失败的原因是出在 windows 的系统缓存机制上,那只要找到方法可以强制系统将缓存的数据写入硬盘就好了。修改后的代码如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+
+ # 确保数据从 Python 的内部缓冲区写入操作系统的缓冲区
+ f.flush()
+
+ # 确保数据从操作系统的缓冲区写入磁盘
+ os.fsync(f.fileno())
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
新增了两行代码。
+f.flush()
的作用为刷新 Python 的内部缓冲区,确保所有数据写入操作系统的缓冲区。但这个并不能保证操作系统会立刻将数据写入硬盘。os.fsync(f.fileno())
的作用为强制操作系统将其缓冲区的数据写入硬盘。这样就保证了如果之后的代码导致系统死机,这部分数据也会完整的保存在硬盘上。将修改后的程序在 windows 上测试,数据每次都会完整的保存在硬盘上。
+在 Microsoft 的一篇官方文档中有提到 disk write caching ,也就是写入缓存,并给出了关闭的方法。
+关于 disk write caching ,官方的描述为:
+++Additionally, turning disk write caching on may increase operating system performance; however, it may also result in the loss of information if a power failure, equipment failure, or software failure occurs.
+
确实与我遇到的情况一样。
+还有一篇更详细一点的介绍:https://learn.microsoft.com/en-US/windows/client-management/client-tools/change-default-removal-policy-external-storage-media
+总的来说 disk write caching 在一般情况下可以提高性能。但在需要确保极端情况下写入数据完整性时,可以考虑关闭或者手动强制写入。
]]>利用 Github Actions 来自动构建和部署博客到 Github Pages。这样既可以简化自己的操作,又能保证自己的博客源码的私密性。
...此篇博客介绍我的博客的修改内容和方法。我的主题为 hugo-PaperMod 。我修改前都将博客内的资源文件夹(assets, i18n, lay)复制到了项目根目录中,这样以后更新主题也不会导致修改消失。
...使用 Hugo 搭建博客。
...具体位置为:个人设置界面 -> Developer Settings -> Personal access tokens -> Tokens(classic)
在此界面新生成一个 token,需要勾选 repo
和 workflow
选项。Expiration
可以设置为 No expiration
。创建完成后会显示你的 token ,它只会显示这一次,你需要将它记下来,后边会用到。
想利用 Github Actions ,需要在博客的根目录下创建 .github/workflows/
文件夹。在该文件夹下创建 yml 文件会被 Github Actions 执行。
我的 workflow 文件如下:
+name: GitHub Pages
+
+on:
+ push:
+ branches:
+ - main # Set a branch to deploy
+
+ release:
+ types:
+ - published
+jobs:
+ deploy:
+ runs-on: ubuntu-20.04
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: main
+
+ - name: Setup Hugo
+ uses: peaceiris/actions-hugo@v2
+ with:
+ hugo-version: '0.119.0'
+ # 是否启用 hugo extend
+ # extended: true
+
+ - name: Build
+ run: hugo --minify
+
+ - name: Deploy
+ run: |
+ cd ./public
+ git init
+ git config --global user.name '${{ secrets.GITHUBUSERNAME }}'
+ git config --global user.email '${{ secrets.GITHUBEMAIL }}'
+ git add .
+ git commit -m "${{ github.event.head_commit.message }}"
+ git push --force --quiet "https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.GITHUBTOKEN }}@github.com/${{ secrets.GITHUBUSERNAME }}/${{ secrets.GITHUBUSERNAME }}.github.io.git" master:main
+ #git push --force --quiet "https://${{ secrets.TOKENUSER }}:${{ secrets.CODINGTOKEN }}@e.coding.net/${{ secrets.CODINGUSERNAME }}/${{ secrets.CODINGBLOGREPO }}.git" master:master #coding部署写法,需要的自行取消注释
+ #git push --force --quiet "https://${{ secrets.GITEEUSERNAME }}:${{ secrets.GITEETOKEN }}@gitee.com/${{ secrets.GITEEUSERNAME }}/${{ secrets.GITEEUSERNAME }}.git" master:master #gitee部署写法,需要的自行取消注释
+
其中,GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
三个为自定义变量,后边会讲到。
新建或使用一个老的仓库,可见性设置为 private 。在代码仓库的 Settings
页面找到 Secrets and variables
,点击其中的 Actions
,添加 workflow 中用到的三个变量:GITHUBUSERNAME
,GITHUBEMAIL
, GITHUBTOKEN
。
+
+完成上述步骤后即可用 git 提交代码到你的源码仓库,即可在仓库的 Actions 界面看到执行的 workflows。
+来自与 Github 。项目地址为: https://github.com/dsrkafuu/sakana-widget 。具体更改为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- 石蒜组件 -->
+<style>
+ html .pull-right{
+ position: absolute;
+ right: 0;
+ top: 70px;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+ }
+ html #sakana-widget{
+ position: fixed;
+ right: 50px;
+ bottom: 0;
+
+ transform-origin: 100% 100%; /* 从右下开始变换 */
+}
+</style>
+<link
+rel="stylesheet"
+href="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.css"
+/>
+<div id="sakana-widget", style="z-index:999"></div>
+<script>
+function initSakanaWidget() {
+ new SakanaWidget().mount('#sakana-widget');
+}
+</script>
+<script
+async
+onload="initSakanaWidget()"
+src="https://cdn.jsdelivr.net/npm/sakana-widget@2.4.1/lib/sakana.min.js"
+></script>
+
重新编译后即可拥有可爱石蒜小组件。 +
+有两种 High 一下的代码实现:
+javascript:(function(){function c(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",f);e.setAttribute("class",l);document.body.appendChild(e)}function h(){var e=document.getElementsByClassName(l);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function p(){var e=document.createElement("div");e.setAttribute("class",a);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function d(e){return{height:e.offsetHeight,width:e.offsetWidth}}function v(i){var s=d(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function m(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function g(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function y(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function E(e){var t=m(e);return t>=w&&t<=b+w}function S(){var e=document.createElement("audio");e.setAttribute("class",l);e.src=i;e.loop=false;e.addEventListener("canplay",function(){setTimeout(function(){x(k)},500);setTimeout(function(){N();p();for(var e=0;e<O.length;e++){T(O[e])}},15500)},true);e.addEventListener("ended",function(){N();h()},true);e.innerHTML=" <p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p> <p>";document.body.appendChild(e);e.play()}function x(e){e.className+=" "+s+" "+o}function T(e){e.className+=" "+s+" "+u[Math.floor(Math.random()*u.length)]}function N(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.mp3";var s="mw-harlem_shake_me";var o="im_first";var u=["im_drunk","im_baked","im_trippin","im_blown"];var a="mw-strobe_light";var f="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var l="mw_added_css";var b=g();var w=y();var C=document.getElementsByTagName("*");var k=null;for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){if(E(A)){k=A;break}}}if(A===null){console.warn("Could not find a node of the right size. Please try a different page.");return}c();S();var O=[];for(var L=0;L<C.length;L++){var A=C[L];if(v(A)){O.push(A)}}})()
+
javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()
+
以上两种差不多,我个人用的是第二种。具体用法为在 /layouts/partials/extend_head.html
中加入如下代码:
<!-- High 一下 -->
+<div class="pull-right", style="z-index:999">
+<a title="把这个链接拖到你的Chrome收藏夹工具栏中" href='javascript:(function(){function h(){var e=document.createElement("link");e.setAttribute("type","text/css");e.setAttribute("rel","stylesheet");e.setAttribute("href",l);e.setAttribute("class",c);document.body.appendChild(e)}function p(){var e=document.getElementsByClassName(c);for(var t=0;t<e.length;t++){document.body.removeChild(e[t])}}function d(){var e=document.createElement("div");e.setAttribute("class",f);document.body.appendChild(e);setTimeout(function(){document.body.removeChild(e)},100)}function v(e){return{height:e.offsetHeight,width:e.offsetWidth}}function m(i){var s=v(i);return s.height>e&&s.height<n&&s.width>t&&s.width<r}function g(e){var t=e;var n=0;while(!!t){n+=t.offsetTop;t=t.offsetParent}return n}function y(){var e=document.documentElement;if(!!window.innerWidth){return window.innerHeight}else if(e&&!isNaN(e.clientHeight)){return e.clientHeight}return 0}function b(){if(window.pageYOffset){return window.pageYOffset}return Math.max(document.documentElement.scrollTop,document.body.scrollTop)}function S(e){var t=g(e);return t>=E&&t<=w+E}function x(){var e=document.createElement("audio");e.setAttribute("class",c);e.src=i;e.loop=false;var t=false,n=false,r=false;e.addEventListener("timeupdate",function(){var i=e.currentTime,s=D,o=s.length,u;if(i>=.5&&!t){t=true;T(_)}if(i>=15.5&&!n){n=true;k();d();for(u=0;u<o;u++){N(s[u])}}if(e.currentTime>=28.4&&!r){r=true;C()}},true);e.addEventListener("ended",function(){k();p()},true);e.innerHTML="<p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p>";document.body.appendChild(e);e.play()}function T(e){e.className+=" "+s+" "+u}function N(e){e.className+=" "+s+" "+a[Math.floor(Math.random()*a.length)]}function C(){var e=document.getElementsByClassName(s);for(var t=0;t<e.length;){e[t].className=e[t].className.replace(s,o)}s=o}function k(){var e=document.getElementsByClassName(s);var t=new RegExp("\\b"+s+"\\b");for(var n=0;n<e.length;){e[n].className=e[n].className.replace(t,"")}}var e=30;var t=30;var n=350;var r=350;var i="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake.ogg";var s="mw-harlem_shake_me";var o="mw-harlem_shake_slow";var u="im_first";var a=["im_drunk","im_baked","im_trippin","im_blown"];var f="mw-strobe_light";var l="//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css";var c="mw_added_css";var w=y();var E=b();var L=document.getElementsByTagName("*"),A=L.length,O,M;var _=null;for(O=0;O<A;O++){M=L[O];if(m(M)){if(S(M)){_=M;break}}}if(M===null){console.warn("Could not find a node of the right size. Please try a different page.");return}h();x();var D=[];for(O=0;O<A;O++){M=L[O];if(m(M)){D.push(M)}}})()'>High一下!</a>
+</div>
+
其中的 href=
的内容替换为上述两种的其中一种即可。
根据 sulvblog 的方法来改。
+先修改 layouts/partials/toc.html
的代码,替换为:
{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
+{{- $has_headers := ge (len $headers) 1 -}}
+{{- if $has_headers -}}
+<aside id="toc-container" class="toc-container wide">
+ <div class="toc">
+ <details {{if (.Param "TocOpen") }} open{{ end }}>
+ <summary accesskey="c" title="(Alt + C)">
+ <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span>
+ </summary>
+
+ <div class="inner">
+ {{- $largest := 6 -}}
+ {{- range $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+ {{- if lt $headerLevel $largest -}}
+ {{- $largest = $headerLevel -}}
+ {{- end -}}
+ {{- end -}}
+
+ {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
+
+ {{- $.Scratch.Set "bareul" slice -}}
+ <ul>
+ {{- range seq (sub $firstHeaderLevel $largest) -}}
+ <ul>
+ {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
+ {{- end -}}
+ {{- range $i, $header := $headers -}}
+ {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
+ {{- $headerLevel := len (seq $headerLevel) -}}
+
+ {{/* get id="xyz" */}}
+ {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
+
+ {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
+ {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
+ {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
+
+ {{- if ne $i 0 -}}
+ {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
+ {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
+ {{- if gt $headerLevel $prevHeaderLevel -}}
+ {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
+ <ul>
+ {{/* the first should not be recorded */}}
+ {{- if ne $prevHeaderLevel . -}}
+ {{- $.Scratch.Add "bareul" . -}}
+ {{- end -}}
+ {{- end -}}
+ {{- else -}}
+ </li>
+ {{- if lt $headerLevel $prevHeaderLevel -}}
+ {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
+ {{- if in ($.Scratch.Get "bareul") . -}}
+ </ul>
+ {{/* manually do pop item */}}
+ {{- $tmp := $.Scratch.Get "bareul" -}}
+ {{- $.Scratch.Delete "bareul" -}}
+ {{- $.Scratch.Set "bareul" slice}}
+ {{- range seq (sub (len $tmp) 1) -}}
+ {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
+ {{- end -}}
+ {{- else -}}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- else }}
+ <li>
+ <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
+ {{- end -}}
+ {{- end -}}
+ <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} -->
+ {{- $firstHeaderLevel := $largest }}
+ {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
+ </li>
+ {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
+ {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
+ </ul>
+ {{- else }}
+ </ul>
+ </li>
+ {{- end -}}
+ {{- end }}
+ </ul>
+ </div>
+ </details>
+ </div>
+</aside>
+<script>
+ let activeElement;
+ let elements;
+ window.addEventListener('DOMContentLoaded', function (event) {
+ checkTocPosition();
+
+ elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]');
+ // Make the first header active
+ activeElement = elements[0];
+ const id = encodeURI(activeElement.getAttribute('id')).toLowerCase();
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ }, false);
+
+ window.addEventListener('resize', function(event) {
+ checkTocPosition();
+ }, false);
+
+ window.addEventListener('scroll', () => {
+ // Check if there is an object in the top half of the screen or keep the last item active
+ activeElement = Array.from(elements).find((element) => {
+ if ((getOffsetTop(element) - window.pageYOffset) > 0 &&
+ (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) {
+ return element;
+ }
+ }) || activeElement
+
+ elements.forEach(element => {
+ const id = encodeURI(element.getAttribute('id')).toLowerCase();
+ if (element === activeElement){
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
+ } else {
+ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active');
+ }
+ })
+ }, false);
+
+ const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10);
+ const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10);
+ const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10);
+
+ function checkTocPosition() {
+ const width = document.body.scrollWidth;
+
+ if (width - main - (toc * 2) - (gap * 4) > 0) {
+ document.getElementById("toc-container").classList.add("wide");
+ } else {
+ document.getElementById("toc-container").classList.remove("wide");
+ }
+ }
+
+ function getOffsetTop(element) {
+ if (!element.getClientRects().length) {
+ return 0;
+ }
+ let rect = element.getBoundingClientRect();
+ let win = element.ownerDocument.defaultView;
+ return rect.top + win.pageYOffset;
+ }
+</script>
+{{- end }}
+
layouts/_default/single.html
文件中默认有 toc.html 的调用,如果未更改的话不用管,更改的话请调用:
{{- if (.Param "ShowToc") }}
+{{- partial "toc.html" . }}
+{{- end }}
+
修改 css/extended/blank.css
文件,加入下方代码:
:root {
+ --nav-width: 1380px;
+ --article-width: 650px;
+ --toc-width: 300px;
+}
+
+.toc {
+ margin: 0 2px 40px 2px;
+ border: 1px solid var(--border);
+ background: var(--entry);
+ border-radius: var(--radius);
+ padding: 0.4em;
+}
+
+.toc-container.wide {
+ position: absolute;
+ height: 100%;
+ border-right: 1px solid var(--border);
+ left: calc((var(--toc-width) + var(--gap)) * -1);
+ top: calc(var(--gap) * 2);
+ width: var(--toc-width);
+}
+
+.wide .toc {
+ position: sticky;
+ top: var(--gap);
+ border: unset;
+ background: unset;
+ border-radius: unset;
+ width: 100%;
+ margin: 0 2px 40px 2px;
+}
+
+.toc details summary {
+ cursor: zoom-in;
+ margin-inline-start: 20px;
+ padding: 12px 0;
+}
+
+.toc details[open] summary {
+ font-weight: 500;
+}
+
+.toc-container.wide .toc .inner {
+ margin: 0;
+}
+
+.active {
+ font-size: 110%;
+ font-weight: 600;
+}
+
+.toc ul {
+ list-style-type: circle;
+}
+
+.toc .inner {
+ margin: 0 0 0 20px;
+ padding: 0px 15px 15px 20px;
+ font-size: 16px;
+
+ /*目录显示高度*/
+ max-height: 83vh;
+ overflow-y: auto;
+}
+
+.toc .inner::-webkit-scrollbar-thumb { /*滚动条*/
+ background: var(--border);
+ border: 7px solid var(--theme);
+ border-radius: var(--radius);
+}
+
+.toc li ul {
+ margin-inline-start: calc(var(--gap) * 0.5);
+ list-style-type: none;
+}
+
+.toc li {
+ list-style: none;
+ font-size: 0.95rem;
+ padding-bottom: 5px;
+}
+
+.toc li a:hover {
+ color: var(--secondary);
+}
+
重新编译后即可看到目录到了左边。
+修改 ·assets\css\core\theme-vars.css
文件的 :root
节点中的 --main-width
我设置的是 1024px
放弃主题默认的 Highlight.js,改为 Hugo 默认的 chroma 方式渲染。根据 官方说明 来改。我用的 style 为 github-dark
在 assets\css\common\post-single.css
文件中找到 .post-content code
增加 max-height: 40em;
。目前设置好最大高度后会与 lineNos: true
设置冲突,会显示两个滑动条 :( ,所以就先不开它。
默认显示的图片在摘要上方而且很大,一页只能显示几个,感觉体验不是很好,所以将它改为在摘要右方显示。我很喜欢 CoolShell 的文章页的显示方法,准备改成差不多样子的。
+文件为 layouts\_default\list.html
。默认的图片显示代码为:
{{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+{{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+
位于 <header class="entry-header">
的上方。将其移动到 <div class="entry-content">
中并替换默认的 Summary 处理方式,如下所示:
<div class="entry-content">
+ {{- $isHidden := (site.Params.cover.hidden | default site.Params.cover.hiddenInList) }}
+ {{- partial "cover.html" (dict "cxt" . "IsHome" true "isHidden" $isHidden) }}
+ {{ .Summary | replaceRE "\n" "<br>" | safeHTML }}{{ if .Truncated }}...{{ end }}
+</div>
+
文件为 layouts\partials\cover.html
。将 所有 <img
块都加上 align="right"
。完整如下所示:
{{- with .cxt}} {{/* Apply proper context from dict */}}
+{{- if (and .Params.cover.image (not $.isHidden)) }}
+{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
+<figure class="entry-cover">
+ {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
+ {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
+ {{- $pageBundleCover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $globalResourcesCover := (resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
+ {{- $cover := (or $pageBundleCover $globalResourcesCover)}}
+ {{- if $cover -}}{{/* i.e it is present in page bundle */}}
+ {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
+ {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
+ {{- if hugo.IsExtended -}}
+ {{- $processableFormats = $processableFormats | append "webp" -}}
+ {{- end -}}
+ {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
+ {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
+ <img loading="lazy" align="right" srcset="{{- range $size := $sizes -}}
+ {{- if (ge $cover.Width $size) -}}
+ {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
+ {{ end }}
+ {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}"
+ sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}"
+ width="{{ $cover.Width }}" height="{{ $cover.Height }}">
+ {{- else }}{{/* Unprocessable image or responsive images disabled */}}
+ <img loading="lazy" align="right" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- else }}{{/* For absolute urls and external links, no img processing here */}}
+ {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
+ rel="noopener noreferrer">{{ end -}}
+ <img loading="lazy" align="right" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
+ {{- end }}
+ {{- if $addLink }}</a>{{ end -}}
+ {{/* Display Caption */}}
+ {{- if not $.IsHome }}
+ {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
+ {{- end }}
+</figure>
+{{- end }}{{/* End image */}}
+{{- end -}}{{/* End context */ -}}
+
在文件 assets\css\extended\blank.css
中增加以下代码:
.entry-cover {
+ float:right;
+ width: 20%;
+ margin-left: 20px;
+}
+
+.entry-content {
+ margin: 20px 0;
+ color: var(--secondary);
+ font-size: 14px;
+ line-height: 1.6;
+ overflow: hidden;
+ display: block;
+}
+
这样做完的效果就是文章列表页的摘要可以自由显示文字和图片,默认的 cover 在文字右边,文字会环绕图片显示。
+根据 Sulv’s Blog 的方法来改。
+将 layouts/_default/terms.html
中的 <ul class="terms-tags">
代码块替换为:
<ul class="terms-tags">
+ {{- $type := .Type }}
+ {{- range $key, $value := .Data.Terms.Alphabetical }}
+ {{- $name := .Name }}
+ {{- $count := .Count }}
+ {{- with $.Site.GetPage (printf "/%s/%s" $type $name) }}
+ <li>
+ {{ $largestFontSize := 1.5 }}
+ {{ $smallestFontSize := 1 }}
+ {{ $fontSpread := sub $largestFontSize $smallestFontSize }}
+ {{ $max := add (len (index $.Site.Taxonomies.tags.ByCount 0).Pages) 1 }}
+ {{ $min := len (index $.Site.Taxonomies.tags.ByCount.Reverse 0).Pages }}
+ {{ $spread := sub $max $min }}
+ {{ $fontStep := div $fontSpread $spread }}
+ {{ $weigth := div (sub (math.Log $count) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }}
+ {{ $currentFontSize := (add $smallestFontSize (mul (sub $largestFontSize $smallestFontSize) $weigth)) }}
+ <a href="{{ .Permalink }}" style="font-size: {{ $currentFontSize }}rem; font-weight: {{ mul $currentFontSize 200 }};">
+ {{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup>
+ </a>
+ </li>
+ {{- end }}
+ {{- end }}
+</ul>
+
在 assets/css/extended/blank.css
中增加如下代码:
/*标签*/
+.terms-tags {
+ text-align: center;
+}
+
+.terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags a {
+ border-radius: 30px;
+ background: none;
+ transition: transform 0.5s;
+}
+
+.dark .terms-tags a {
+ background: none;
+}
+
+.dark .terms-tags a:hover {
+ background: none;
+ -moz-transform: scale(1.2);
+ -ms-transform: scale(1.2);
+ -o-transform: scale(1.2);
+ transform: scale(1.3);
+}
+
+.terms-tags li {
+ margin: 5px;
+}
+
根据 Hugo 官网 的文档来安装 Hugo。
+我使用的主题为:hugo-PaperMod ,根据主题文档安装主题(推荐使用 submodule 的方式安装)。将默认的配置文件 hugo.toml 的后缀改为 yaml,方便使用。
+复制主题网站给的 Sample config.yml 的内容到 hugo.yaml 中,额外增加:
+languageCode: zh
+defaultContentLanguage: zh
+
将网站语言设置为中文。
+复制 Sample Page.md
的内容到 archetypes/default.md 中(title 和 date 不覆盖,只将 toml 格式的配置改为 yaml 格式即可)。可以省去自己一个一个寻找增加的时间,当然,具体内容还是要根据自己的要求来改。
使用命令 hugo new content posts/my-first-post.md
来新建一篇文章。
使用命令 hugo server -D
预览生成好的网页。
公司有一个同事做的项目,其中有一个 Python 写的程序会反复降低 CPU 的电压直至死机重启,程序会在降压前保存本次的数据。听起来很合理,先保存数据再降低电压,如果死机了导致重启,那上次的数据也保存到本地了。但在 windows 电脑上实际运行时,每次程序导致 windows 死机重启后,保存的数据文件都为空。他没搞定这个就离职了,于是我就接手来查这个 bug 了。
...大概的代码示例如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
从代码结构来看确实没什么问题,是先保存数据再降低电压,即使后边的操作导致死机也是在写入操作完成后,应该不会影响保存的数据才对。但事实是确实有影响,在 windows 上测试了好几次保存的数据都为空。with open()
语句是 Python 中常用的文件操作语句,不应该会导致写入异常,于是怀疑是降压操作导致的。
在 Python 中,当使用 with open()
语句来写入文件时,它会负责管理文件的打开和关闭,通常情况下, with
语句块结束后,Python 会自动关闭文件,并确保所有数据写入硬盘。但,这个操作不是立即发生的。
当写入文件时,操作系统通常会缓存这些操作,以便一次性的将多个写入操作合并,从而提高效率。这意味着即使 Python 代码执行了写入操作(也就是 write()),也不能保证这些数据已经永久的保存到了硬盘上。如果在 with 语句块结束后立即死机,这些数据可能会丢失。
+现在知道了导致数据保存失败的原因是出在 windows 的系统缓存机制上,那只要找到方法可以强制系统将缓存的数据写入硬盘就好了。修改后的代码如下:
+# 写入文件
+with open(file_path, "w") as f:
+ f.write(some_data)
+
+ # 确保数据从 Python 的内部缓冲区写入操作系统的缓冲区
+ f.flush()
+
+ # 确保数据从操作系统的缓冲区写入磁盘
+ os.fsync(f.fileno())
+logging.warning("Write to checkpoint file")
+
+# 降低 CPU 电压,过低会导致死机
+set_voltage_offset(v_off)
+
新增了两行代码。
+f.flush()
的作用为刷新 Python 的内部缓冲区,确保所有数据写入操作系统的缓冲区。但这个并不能保证操作系统会立刻将数据写入硬盘。os.fsync(f.fileno())
的作用为强制操作系统将其缓冲区的数据写入硬盘。这样就保证了如果之后的代码导致系统死机,这部分数据也会完整的保存在硬盘上。将修改后的程序在 windows 上测试,数据每次都会完整的保存在硬盘上。
+在 Microsoft 的一篇官方文档中有提到 disk write caching ,也就是写入缓存,并给出了关闭的方法。
+关于 disk write caching ,官方的描述为:
+++Additionally, turning disk write caching on may increase operating system performance; however, it may also result in the loss of information if a power failure, equipment failure, or software failure occurs.
+
确实与我遇到的情况一样。
+还有一篇更详细一点的介绍:https://learn.microsoft.com/en-US/windows/client-management/client-tools/change-default-removal-policy-external-storage-media
+总的来说 disk write caching 在一般情况下可以提高性能。但在需要确保极端情况下写入数据完整性时,可以考虑关闭或者手动强制写入。
]]>