很长一段时间,我的博客是用的hexo,也用博客记录过 搭建过程 ,但有几个不太舒服的地方

  • 一是依赖过重,每次在一台新电脑上安装node+npm+hexo环境都会出现一些新问题
  • 二是构建速度比较慢

这次尝试用hugo+PaperMod主题+github action搭建一个新的博客,保持简洁的风格,同时能够利用CI自动构建,此后写博客所做的事情只是新建和编辑markdown文件,push到git仓库就可以了。

此外增强了PaperMod的搜索功能,目前还算满意。

放两张新旧博客界面的截图,作为纪念。

PaperMod配置和魔改

自定义html/js/css

可以在网站根目录下相应目录建立文件,构建时会被merge,并且优先级高于papermod的对应文件,就可以在不更改原有主题的前提下起到覆盖和增加主题文件的效果了。

比如我这里自定义了 blank.css/fastsearch.js/list.html/toc.html 这几个文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ ls -lhR assets/css/extended/ assets/js/ layouts/ themes/hugo-PaperMod-7.0/assets/css/extended/ themes/hugo-PaperMod-7.0/assets/js/ themes/hugo-PaperMod-7.0/layouts/partials/toc.html
-rwxrwxrwx 1 bob bob 4.3K Feb 12 14:53 themes/hugo-PaperMod-7.0/layouts/partials/toc.html

assets/css/extended/:
total 4.0K
-rwxrwxrwx 1 bob bob 1.9K May 10 09:15 blank.css

assets/js/:
total 12K
-rwxrwxrwx 1 bob bob 9.4K May  9 23:49 fastsearch.js

layouts/:
total 4.0K
-rwxrwxrwx 1 bob bob  815 May  9 16:08 list.html
drwxrwxrwx 1 bob bob 4.0K May 10 09:12 partials

layouts/partials:
total 8.0K
-rwxrwxrwx 1 bob bob 7.3K May 10 09:26 toc.html

themes/hugo-PaperMod-7.0/assets/css/extended/:
total 0
-rwxrwxrwx 1 bob bob 239 Feb 12 14:53 blank.css

themes/hugo-PaperMod-7.0/assets/js/:
total 124K
-rwxrwxrwx 1 bob bob 5.5K Feb 12 14:53 fastsearch.js
-rwxrwxrwx 1 bob bob  15K Feb 12 14:53 fuse.basic.min.js
-rwxrwxrwx 1 bob bob 100K Feb 12 14:53 highlight.min.js
-rwxrwxrwx 1 bob bob  192 Feb 12 14:53 license.js

修改目录位置到侧边栏

toc.html

添加文件 \blog\layouts\partials\toc.html

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
{{- $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 }}
<!-- 
————————————————
版权声明:本文为「Sulv's Blog」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://www.sulvblog.cn/posts/blog/hugo_toc_side/ -->

blank.css

添加 \blog\assets\css\extended\blank.css

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
: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);
}

/* ————————————————
版权声明:本文为「Sulv's Blog」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://www.sulvblog.cn/posts/blog/hugo_toc_side/ */

增强全文搜索

默认情况下的搜索只会显示搜索出来的文章名字,增强以后可以可以获得全文搜索功能并高亮匹配字段。中文基本OK,英文的搜索效果需要改进,不知是否有可能搜索某个单词而不是字符。

  1. \blog\config.toml 加入:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 本地搜索
# https://github.com/adityatelange/hugo-PaperMod/wiki/Features#search-page
[outputs]
home = [ "HTML", "RSS", "JSON" ]
# fusejs.io通用配置部分
[params.fuseOpts]
isCaseSensitive = false
tokenize = true
matchAllTokens =true
shouldSort = true
findAllMatches = true
includeScore = true
includeMatches = true
minMatchCharLength = 3
threshold=0.6
keys =  ['title', 'permalink',  'content']
distance=0
useExtendedSearch=true
# fusejs.io中文搜索配置部分,会根据搜索语言覆盖通用部分的配置
zh_minMatchCharLength = 2
zh_threshold=0.4
# fusejs.io英文搜索配置部分,会根据搜索语言覆盖通用部分的配置
en_minMatchCharLength = 4
en_threshold=0.0
  1. 新建以下文件 \blog\assets\js\fastsearch.js
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
import * as params from "@params";

let fuse; // holds our search engine
let resList = document.getElementById("searchResults");
let sInput = document.getElementById("searchInput");
let first,
  last,
  current_elem = null;
let resultsAvailable = false;

// load our search index
window.onload = function () {
  let xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        let data = JSON.parse(xhr.responseText);
        if (data) {
          // fuse.js options; check fuse.js website for details
          let options = {
            distance: 100,
            threshold: 0.4,
            ignoreLocation: true,
            keys: ["title", "permalink", "summary", "content"],
          };
          fuse = new Fuse(data, options); // build the index from the json file
        }
      } else {
        console.log(xhr.responseText);
      }
    }
  };
  xhr.open("GET", "../index.json");
  xhr.send();
};

function activeToggle(ae) {
  document.querySelectorAll(".focus").forEach(function (element) {
    // rm focus class
    element.classList.remove("focus");
  });
  if (ae) {
    ae.focus();
    document.activeElement = current_elem = ae;
    ae.parentElement.classList.add("focus");
  } else {
    document.activeElement.parentElement.classList.add("focus");
  }
}

function reset() {
  resultsAvailable = false;
  resList.innerHTML = sInput.value = ""; // clear inputbox and searchResults
  sInput.focus(); // shift focus to input box
}

// execute search as each character is typed
sInput.onkeyup = function (e) {
  // 把实时搜索改成回车后搜索,避免卡顿
  if (e.key !== "Enter") {
    return;
  }
  // run a search query (for "term") every time a letter is typed
  // in the search box
  if (fuse) {
    // var before_search = new Date().getTime();
    fuse.options = generateFuseOptionAccordingToLanguageOfSearchvalue(
      fuse.options,
      this.value.trim()
    );
    const results = fuse.search(this.value.trim()); // the actual query being run using fuse.js
    // console.log(fuse.options.minMatchCharLength);
    // console.log("search cost ",(new Date().getTime() - before_search)/1000),'s';
    if (results.length !== 0) {
      // build our html if result exists
      let resultSet = ""; // our results bucket

      // 方法一:原来的做法,只能显示标题
      // for (let item in results) {
      //     resultSet += `<li class="post-entry"><header class="entry-header">${results[item].item.title}&nbsp;»</header>` +
      //     `<a href="${results[item].item.permalink}" aria-label="${results[item].item.title}"></a></li>`
      // }

      // 方法二:自定义的可行的方法,高亮匹配内容,但是方向键不生效
      // resList.innerHTML = resultSet;
      // renderSearchResults(results);

      // 方法三:效果相当不错的,修复了方向键不好使的问题
      resultSet = renderSearchResults2(results);

      resList.innerHTML = resultSet;
      resultsAvailable = true;
      first = resList.firstChild;
      last = resList.lastChild;
    } else {
      resultsAvailable = false;
      resList.innerHTML = "sorry we found nothing (;′⌒`)";
    }
  }
};

sInput.addEventListener("search", function (e) {
  // clicked on x
  if (!this.value) reset();
});

// kb bindings
document.onkeydown = function (e) {
  let key = e.key;
  let ae = document.activeElement;

  let inbox = document.getElementById("searchbox").contains(ae);

  if (ae === sInput) {
    let elements = document.getElementsByClassName("focus");
    while (elements.length > 0) {
      elements[0].classList.remove("focus");
    }
  } else if (current_elem) ae = current_elem;

  if (key === "Escape") {
    reset();
  } else if (!resultsAvailable || !inbox) {
    return;
  } else if (key === "ArrowDown") {
    e.preventDefault();
    if (ae == sInput) {
      // if the currently focused element is the search input, focus the <a> of first <li>
      activeToggle(resList.firstChild.lastChild);
    } else if (ae.parentElement != last) {
      // if the currently focused element's parent is last, do nothing
      // otherwise select the next search result
      activeToggle(ae.parentElement.nextSibling.lastChild);
    }
  } else if (key === "ArrowUp") {
    e.preventDefault();
    if (ae.parentElement == first) {
      // if the currently focused element is first item, go to input box
      activeToggle(sInput);
    } else if (ae != sInput) {
      // if the currently focused element is input box, do nothing
      // otherwise select the previous search result
      activeToggle(ae.parentElement.previousSibling.lastChild);
    }
  } else if (key === "ArrowRight") {
    ae.click(); // click on active link
  }
};

// 方法二使用的函数,暂时不要删除
function renderSearchResults(results) {
  if (results.length > 0) {
    // Clear the previous search results
    resList.innerHTML = "";

    // Loop through each search result item
    results.forEach(function (item) {
      var hilighted_content = "";
      // Get the matched part of the title
      var title = item.item.title;
      var titleMatches = item.matches.filter(function (match) {
        return match.key === "title";
      });
      if (titleMatches.length > 0) {
        hilighted_content =
          "title: " +
          highlightMatch(titleMatches[0].value, titleMatches[0].indices);

        // Create a new list item element to display the search result
        var li = document.createElement("li");
        var a = document.createElement("a");
        a.href = item.item.permalink;
        a.innerText = item.item.title;
        a.style = "font-weight: bold";
        li.appendChild(a);
        li.appendChild(document.createElement("br"));
        // Create a new paragraph element to display the matched text
        var p = document.createElement("p");
        p.innerHTML = hilighted_content;
        li.appendChild(p);
        // Add the list item to the search results list
        resList.appendChild(li);
        return;
      }

      // // Get the matched part of the description
      // var description = item.description || '';
      // var descriptionMatches = item.matches.filter(function(match) {
      //   return match.key === 'description';
      // });
      // if (descriptionMatches.length > 0) {
      //   description = highlightMatch(description, descriptionMatches[0].indices);
      // }

      // Get the matched part of the content
      var content = item.content || "";
      var contentMatches = item.matches.filter(function (match) {
        return match.key === "content";
      });
      if (contentMatches.length > 0) {
        hilighted_content = highlightMatch(
          contentMatches[0].value,
          contentMatches[0].indices
        );
        // Create a new list item element to display the search result
        var li = document.createElement("li");
        var a = document.createElement("a");
        // console.log("item:",item)
        a.href = item.item.permalink;
        a.innerText = item.item.title;
        a.style = "font-weight: bold";
        li.appendChild(a);
        li.appendChild(document.createElement("br"));
        // Create a new paragraph element to display the matched text
        var p = document.createElement("p");
        p.innerHTML = hilighted_content;
        li.appendChild(p);
        // Add the list item to the search results list
        resList.appendChild(li);
        return;
      }
    });
  }
}

function renderSearchResults2(results) {
  var tmp_resList = document.createElement("ul");
  var matched_essay_num = 0;
  var matched_text_num = 0;

  if (results.length > 0) {
    // Clear the previous search results
    tmp_resList.innerHTML = "";
    // Loop through each search result item
    results.forEach(function (item) {
      var hilighted_content = "";

      var titleMatches = item.matches.filter(function (match) {
        return match.key === "title";
      });
      if (titleMatches.length > 0) {
        hilighted_content = highlightMatch(
          titleMatches[0].value,
          titleMatches[0].indices
        );
        matched_text_num += titleMatches[0].indices.length;
        li = `<li class="post-entry"><header class="entry-header" style="font-weight: bold">${item.item.title}&nbsp;»</header>`;

        var p = document.createElement("p");
        p.innerHTML = hilighted_content;
        li += p.innerHTML;

        li += `<a href="${item.item.permalink}" aria-label="${item.item.title}" ></a>`;
        li += "</li>";
        tmp_resList.innerHTML += li;
      }

      // Get the matched part of the content
      var contentMatches = item.matches.filter(function (match) {
        return match.key === "content";
      });
      if (contentMatches.length > 0) {
        hilighted_content = highlightMatch(
          contentMatches[0].value,
          contentMatches[0].indices
        );
        matched_text_num += contentMatches[0].indices.length;
        li = `<li class="post-entry"><header class="entry-header" style="font-weight: bold">${item.item.title}&nbsp;»</header>`;

        var p = document.createElement("p");
        p.innerHTML = hilighted_content;
        li += p.innerHTML;

        li += `<a href="${item.item.permalink}" aria-label="${item.item.title}" ></a>`;
        li += "</li>";
        tmp_resList.innerHTML += li;
      }
      matched_essay_num += titleMatches.length + contentMatches.length;
    });
  }

  total_matched_notice = `<li class="post-entry"><header class="entry-header" style="font-weight: bold">共找到 ${matched_essay_num} 篇文章,包含 ${matched_text_num} 处匹配</header><a href="" aria-label="" ></a></li>`;
  tmp_resList.innerHTML = total_matched_notice + tmp_resList.innerHTML;
  return tmp_resList.innerHTML;
}

function highlightMatch(text, indices) {
  if (text == undefined) {
    return "";
  }
  var hilighted_content = "";
  var around_letters_num = 25;
  for (let index = 0; index < indices.length; index++) {
    const element = indices[index];
    var start = element[0];
    var end = element[1];
    hilighted_content += "<p>";
    hilighted_content +=
      text.substring(Math.max(start - around_letters_num, 0), start) +
      '<span style="background-color: yellow;font-weight: bold;">' +
      text.substring(start, end + 1) +
      "</span>" +
      text.substring(end + 1, Math.min(end + around_letters_num, text.length));
    hilighted_content += "</p></br>";
  }
  return hilighted_content;
}

function idLanguage(query) {
  const chineseStringRegex = /^[\u4e00-\u9fa5]+$/; // Matches Chinese string
  const chineseCharRegex = /[\u4e00-\u9fa5]/; // Matches any Chinese character
  if (chineseStringRegex.test(query)) {
    return "allzh"; // 全中文
  } else if (!chineseCharRegex.test(query)) {
    return "nozh"; // 无中文
  }
  return "others"; //中英混合或者其他
}

function generateFuseOptionAccordingToLanguageOfSearchvalue(
  originalOptions,
  searchValue
) {
  // 参考 https://fusejs.io/api/options.html
  if (params.fuseOpts) {
    // 通用配置
    adaptedOptions = {
      isCaseSensitive: params.fuseOpts.iscasesensitive ?? false,
      includeScore: params.fuseOpts.includescore ?? false,
      includeMatches: params.fuseOpts.includematches ?? false,
      minMatchCharLength: params.fuseOpts.minmatchcharlength ?? 1,
      shouldSort: params.fuseOpts.shouldsort ?? true,
      findAllMatches: params.fuseOpts.findallmatches ?? false,
      keys: params.fuseOpts.keys ?? [
        "title",
        "permalink",
        "summary",
        "content",
      ],
      location: params.fuseOpts.location ?? 0,
      threshold: params.fuseOpts.threshold ?? 0.4,
      distance: params.fuseOpts.distance ?? 100,
      ignoreLocation: params.fuseOpts.ignorelocation ?? false,
      useExtendedSearch: params.fuseOpts.useextendedsearch ?? false,
    };
    let searchValueLanguage = idLanguage(searchValue);
    if (searchValueLanguage == "allzh") {
      // 如果全都是中文,使用这个配置
      adaptedOptions.minMatchCharLength =
        params.fuseOpts.zh_minmatchcharlength ??
        adaptedOptions.minMatchCharLength;
    } else if (searchValueLanguage == "nozh") {
      // 如果没有中文,使用这个配置
      adaptedOptions.minMatchCharLength =
        params.fuseOpts.en_minmatchcharlength ??
        adaptedOptions.minMatchCharLength;
    } else if (searchValueLanguage == "others") {
      // do nothing
      // 暂时保持通用配置,比较均衡
      // to be considered
    }
    console.log(adaptedOptions);
    // console.log(params.fuseOpts)
    return adaptedOptions;
  }
  return originalOptions;
}

fusejs.io运行时调整搜索选项,如果是中文则减小minMatchCharLength

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function generateFuseOptionAccordingToLanguageOfSearchvalue(
  originalOptions,
  searchValue
) {
  // 参考 https://fusejs.io/api/options.html
  if (params.fuseOpts) {
    // 通用配置
    adaptedOptions = {
      ignoreLocation: params.fuseOpts.ignorelocation ?? true,
      // distance: params.fuseOpts.distance ?? 100,
      // threshold: params.fuseOpts.threshold ?? 0.4,
      // location: params.fuseOpts.location ?? 0,

      isCaseSensitive: params.fuseOpts.iscasesensitive ?? false,
      tokenize: params.fuseOpts.tokenize ?? true,
      matchAllTokens: params.fuseOpts.matchalltokens ?? true,
      shouldSort: params.fuseOpts.shouldsort ?? true,
      findAllMatches: params.fuseOpts.findallmatches ?? true,
      includeScore: params.fuseOpts.includescore ?? true,
      includeMatches: params.fuseOpts.includematches ?? true,
      minMatchCharLength: params.fuseOpts.minmatchcharlength ?? 3,
      keys: params.fuseOpts.keys ?? [
        "title",
        "permalink",
        "summary",
        "content",
      ],
      useExtendedSearch: params.fuseOpts.useextendedsearch ?? false,
    };
    let searchValueLanguage = idLanguage(searchValue);
    if (searchValueLanguage == "allzh") {
      // 如果全都是中文,使用这个配置
      adaptedOptions.minMatchCharLength =
        params.fuseOpts.zh_minmatchcharlength ??
        adaptedOptions.minMatchCharLength;
    } else if (searchValueLanguage == "nozh") {
      // 如果没有中文,使用这个配置
      adaptedOptions.minMatchCharLength =
        params.fuseOpts.en_minmatchcharlength ??
        adaptedOptions.minMatchCharLength;
    } else if (searchValueLanguage == "others") {
      // do nothing
      // 暂时保持通用配置,比较均衡
      // to be considered
    }
    console.log(adaptedOptions);
    // console.log(params.fuseOpts)
    return adaptedOptions;
  }
  return originalOptions;
}

设置 ignoreLocation = true

阅读搜索原理 https://fusejs.io/concepts/scoring-theory.html#scoring-theory ,了解 ignoreLocation选项会影响是否考虑distance/threshold/location,我们不关心匹配字段出现在离开头多远的地方,我们想要全文搜索,所以只要直接设置这一项为true就可以了。下面三项也没有了设置的意义。

1
2
3
# distance=0
# threshold=0.6
# location=0

useExtendedSearch不起作用

根据 Explanation of Different Builds ,fusejs.io 有不同版本。Papermod7.0自带的是 fuse.basic.min.js 属于basic版本,只具备fuzzy search功能。

UMDCommonJSES Module (for bundlers)
Fullfuse.jsfuse.common.jsfuse.esm.js
Basicfuse.basic.jsfuse.basic.common.jsfuse.basic.esm.js
Full (Production)fuse.min.js-fuse.esm.min.js
Basic (Production)fuse.basic.min.js-fuse.basic.esm.min.js

所以我们选择 Full的UDM版本,https://cdn.jsdelivr.net/npm/fuse.js@6.6.2/dist/fuse.min.js 。下载下来以后重命名为 fuse.basic.min.js 并放在 `\blog\assets\js下,就可以覆盖PaperMod原有的js文件了。这样useExtendedSearch就能工作了。

有了ExtendedSearch以后,前面针对不同语言设置不同 minMatchCharLength 的操作也大可不必了。记录一份扩展搜索的语法和一些注意事项。

  • ="scheme language" 表示从头到尾完全匹配,一般都匹配不上,除非是配置了链接的搜索,然后搜类似 ="http://localhost:1313/post/230509-brandnew-blog-based-on-hugo-and-githubci/"
  • 用得最多的还是 'python'"python -c"
  • 如果有空格,字符串需要用双引号包裹,单引号不行。

ExtendedSearch_doc

以下摘自:https://fusejs.io/examples.html#extended-search

White space acts as an AND operator, while a single pipe (|) character acts as an OR operator. To escape white space, use double quote ex. ="scheme language" for exact match.

TokenMatch typeDescription
jscriptfuzzy-matchItems that fuzzy match jscript
=schemeexact-matchItems that are scheme
'pythoninclude-matchItems that include python
!rubyinverse-exact-matchItems that do not include ruby
^javaprefix-exact-matchItems that start with java
!^earlanginverse-prefix-exact-matchItems that do not start with earlang
.js$suffix-exact-matchItems that end with .js
!.go$inverse-suffix-exact-matchItems that do not end with .go

改造搜索框默认行为

默认使用include-match语法,在输入的字符串周围加上符号,把xxxxx 变成 '"xxxxx" 。如果要使用原生的扩展搜索语法,则需要输入 r:xxxxx ,搜索框会自动去掉开头的 r:

修改页面宽度

复制文件 \blog\themes\hugo-PaperMod-7.0\assets\css\core\theme-vars.css\blog\assets\css\core\theme-vars.css,并修改 --main-width: 的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
:root {
    --gap: 24px;
    --content-gap: 20px;
    --nav-width: 1024px; 
    --main-width: 60%; /* 修改此处调整博客页面总宽度,初始值是720px*/
    --header-height: 60px;
    --footer-height: 60px;
    --radius: 8px;
    --theme: rgb(255, 255, 255);
    --entry: rgb(255, 255, 255);
    --primary: rgb(30, 30, 30);
    --secondary: rgb(108, 108, 108);
    --tertiary: rgb(214, 214, 214);
    --content: rgb(31, 31, 31);
    --hljs-bg: rgb(28, 29, 33);
    --code-bg: rgb(245, 245, 245);
    --border: rgb(238, 238, 238);
}

.dark {
    --theme: rgb(29, 30, 32);
    --entry: rgb(46, 46, 51);
    --primary: rgb(218, 218, 219);
    --secondary: rgb(155, 156, 157);
    --tertiary: rgb(65, 66, 68);
    --content: rgb(196, 196, 197);
    --hljs-bg: rgb(46, 46, 51);
    --code-bg: rgb(55, 56, 62);
    --border: rgb(51, 51, 51);
}

.list {
    background: var(--code-bg);
}

.dark.list {
    background: var(--theme);
}

代码显示效果

高亮行号等配置

\blog\config.toml 加入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# https://gohugo.io/getting-started/configuration-markup/#highlight
# https://gohugo.io/content-management/syntax-highlighting/#generate-syntax-highlighter-css
# https://gohugo.io/content-management/syntax-highlighting/#highlight-shortcode
[markup]
  [markup.highlight]
    anchorLineNos = true # 给每行代码提供一个锚点
    codeFences = true # 是否提供代码块边框 ,不要关闭,太丑了
    guessSyntax = true # 是否猜测语法。开启后没有标明语言的代码块也可以得到code fence
    hl_Lines = ''  # 高亮哪些行,可以具体控制
    hl_inline = false # 不要开启,太丑了
    lineAnchors = ''
    lineNoStart = 1
    lineNos = true
    lineNumbersInTable = true
    noClasses = true # 如果是false,需要自己生成syntax.css
    noHl = false
    style = 'monokai'
    tabWidth = 4

code fence微调

可以控制显示的起始行号和特别高亮的行数,例如这样的原始代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 ```toml {hl_lines=[3,"9-11"],linenostart=199}
 
 [markup]
  [markup.highlight]
    anchorLineNos = false
    codeFences = true
    guessSyntax = false
    hl_Lines = ''
    hl_inline = false
    lineAnchors = ''
    lineNoStart = 1
    lineNos = true
    lineNumbersInTable = true
    noClasses = true
    noHl = false
    style = 'monokai'
    tabWidth = 4
​```

显示效果是这样的

199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
 [markup]
 [markup.highlight]
   anchorLineNos = false
   codeFences = true
   guessSyntax = false
   hl_Lines = ''
   hl_inline = false
   lineAnchors = ''
   lineNoStart = 1
   lineNos = true
   lineNumbersInTable = true
   noClasses = true
   noHl = false
   style = 'monokai'
   tabWidth = 4

效果不算特别好,但也能用。

配置值参考 https://gohugo.io/content-management/syntax-highlighting/#highlight-shortcode

代码块折叠

方案一

使用内置的rawhtml 这个shortcode

原始代码(删除REMOVEME)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{{REMOVEME< rawhtml >}}

<details>
  <summary>展开查看</summary>
  <pre><code> 
     System.out.println("Hello");
  </code></pre>
</details>

{{REMOVEME< /rawhtml >}}

最终效果


展开查看
 
     System.out.println("Hello");
  

🔨方案二

参考 https://jiridj.be/posts/collapsible-code-block/ 没成功

🔨代码块显示语言类型

🔨高亮主题选择

可选的有这些:https://xyproto.github.io/splash/docs/all.html

https://gohugo.io/content-management/syntax-highlighting/

🔨全局字体修改

添加编辑链接,直达github dev,在线修改

\blog\config.toml 加入:

1
2
3
4
[params.editPost]
URL = "https://github.dev/findneo/priblog/content"
Text = "edit"
appendFilePath = true

自定义shortcode,插入并预览PDF/PPT

参考此文:Hugo博客自定义shortcodes | Sulv’s Blog

添加 blog\layouts\shortcodes\ppt.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE HTML>
<html lang="en">
<head>
    <style type="text/css">
        #googleslides_shortcodes {
            padding-bottom: 66%;
            position: relative;
            display: block;
            width: 100%;
            border-bottom: 5px solid;
        }
        #googleslides_shortcodes iframe {
            position: absolute;
            top: 0;
            left: 0
        }
    </style>
    <title></title>
</head>
<body>
<div id="googleslides_shortcodes">
    <iframe id="googleSlideIframe"
            width="100%"
            height="100%"
            src="{{ .Get "src" }}"
            frameborder="0"
            allowfullscreen="" >
    </iframe>
</div>
</body>
</html>
<!-- 
————————————————
版权声明:本文为「Sulv's Blog」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://www.sulvblog.cn/posts/blog/shortcodes/#1-%e5%bc%95%e5%85%a5-ppt-%e5%8a%9f%e8%83%bd -->

引用(去掉removethis)

1
{{removethis< ppt src="0day2.pdf" >}} 

Papermode主题支持哪些配置项

将官方的配置文件 https://adityatelange.github.io/hugo-PaperMod/posts/papermod/papermod-installation/#sample-configyml 这个yaml文件通过 https://www.convertsimple.com/convert-yaml-to-toml/ 转化为 toml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# papermod_available_config_backup.toml
baseURL = "https://examplesite.com/"
title = "ExampleSite"
paginate = 5
theme = "PaperMod"
enableRobotsTXT = true
buildDrafts = false
buildFuture = false
buildExpired = false
googleAnalytics = "UA-123-45"
pygmentsUseClasses = true

[minify]
disableXML = true
minifyOutput = true

[params]
env = "production"
title = "ExampleSite"
description = "ExampleSite description"
keywords = [ "Blog", "Portfolio", "PaperMod" ]
author = "Me"
images = [ "<link or path of image for opengraph, twitter-cards>" ]
DateFormat = "January 2, 2006"
defaultTheme = "auto"
disableThemeToggle = false
ShowReadingTime = true
ShowShareButtons = true
ShowPostNavLinks = true
ShowBreadCrumbs = true
ShowCodeCopyButtons = false
ShowWordCount = true
ShowRssButtonInSectionTermList = true
UseHugoToc = true
disableSpecial1stPost = false
disableScrollToTop = false
comments = false
hidemeta = false
hideSummary = false
showtoc = false
tocopen = false

  [params.assets]
  favicon = "<link / abs url>"
  favicon16x16 = "<link / abs url>"
  favicon32x32 = "<link / abs url>"
  apple_touch_icon = "<link / abs url>"
  safari_pinned_tab = "<link / abs url>"

  [params.label]
  text = "Home"
  icon = "/apple-touch-icon.png"
  iconHeight = 35

  [params.profileMode]
  enabled = false
  title = "ExampleSite"
  subtitle = "This is subtitle"
  imageUrl = "<img location>"
  imageWidth = 120
  imageHeight = 120
  imageTitle = "my image"

    [[params.profileMode.buttons]]
    name = "Posts"
    url = "posts"

    [[params.profileMode.buttons]]
    name = "Tags"
    url = "tags"

  [params.homeInfoParams]
  Title = "Hi there 👋"
  Content = "Welcome to my blog"

  [[params.socialIcons]]
  name = "twitter"
  url = "https://twitter.com/"

  [[params.socialIcons]]
  name = "stackoverflow"
  url = "https://stackoverflow.com"

  [[params.socialIcons]]
  name = "github"
  url = "https://github.com/"

[params.analytics.google]
SiteVerificationTag = "XYZabc"

[params.analytics.bing]
SiteVerificationTag = "XYZabc"

[params.analytics.yandex]
SiteVerificationTag = "XYZabc"

  [params.cover]
  hidden = true
  hiddenInList = true
  hiddenInSingle = true

  [params.editPost]
  URL = "https://github.com/<path_to_repo>/content"
  Text = "Suggest Changes"
  appendFilePath = true

  [params.fuseOpts]
  isCaseSensitive = false
  shouldSort = true
  location = 0
  distance = 1_000
  threshold = 0.4
  minMatchCharLength = 0
  keys = [ "title", "permalink", "summary", "content" ]

[[menu.main]]
identifier = "categories"
name = "categories"
url = "/categories/"
weight = 10

[[menu.main]]
identifier = "tags"
name = "tags"
url = "/tags/"
weight = 20

[[menu.main]]
identifier = "example"
name = "example.org"
url = "https://example.org"
weight = 30

[markup.highlight]
noClasses = false

自定义首页及同级子目录的layout

在blog/content下新建一个mind目录,其中放一个 _index.md 文件,内容如下

1
2
3
4
---
title: 随笔
layout: mindlist
---

blog\layouts\_default 下放list.html 和mindlist.html分别作为首页和mind目录的列目录的layout

list.html如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{{- define "main" }}

{{- $pages :=  (.Site.GetPage "post").Pages}}
{{- range $pages.GroupByPublishDate "2006" }}

    <div class="archive-posts">
      {{- range .Pages }}
      {{- if eq .Kind "page" }}
      <div class="archive-entry">
            {{- .Date | time.Format ("2006-01-02")  }} &nbsp; &nbsp; &nbsp; &nbsp;  {{- .Title | markdownify  }}
            {{- if .Draft }}<sup><span class="entry-isdraft">&nbsp;&nbsp;[draft]</span></sup>{{- end }}
        <a class="entry-link" aria-label="post link to {{ .Title | plainify }}" href="{{ .Permalink }}"></a>
      </div>
      {{- end }}
      {{- end }}
    </div>

{{- end }}
{{- end }}{{/* end main */}}

mindlist如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{{- define "main" }}

{{- $pages :=  (.Site.GetPage "mind").Pages}}
{{- range $pages.GroupByPublishDate "2006" }}

    <div class="archive-posts">
      {{- range .Pages }}
      {{- if eq .Kind "page" }}
      <div class="archive-entry">
            {{- .Date | time.Format ("2006-01-02")  }} &nbsp; &nbsp; &nbsp; &nbsp;  {{- .Title | markdownify  }}
            {{- if .Draft }}<sup><span class="entry-isdraft">&nbsp;&nbsp;[draft]</span></sup>{{- end }}
        <a class="entry-link" aria-label="post link to {{ .Title | plainify }}" href="{{ .Permalink }}"></a>
      </div>
      {{- end }}
      {{- end }}
    </div>

{{- end }}
{{- end }}{{/* end main */}}

在站点config.toml 中添加页面右上角的导航。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[[menu.main]]
identifier = "Tech"
name = "Tech"
url = "/"
weight = 10

[[menu.main]]
identifier = "Mind"
name = "Mind"
url = "/mind/"
weight = 20


[[menu.main]]
identifier = "About"
name = "About"
url = "/about/"
weight = 30

[[menu.main]]
identifier = "Search"
name = "Search"
url = "/search/"
weight = 40

[[menu.main]]
identifier = "Feed"
name = "Feed"
url = "/sitemap.xml"
weight = 50

[[menu.main]]
identifier = "GitHub"
name = "GitHub"
url = "https://github.com/findneo"
weight = 60

一些全局快捷键

有点儿小惊喜,该主题全局支持一些快捷键

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ rg accesskey themes/hugo-PaperMod-7.0
themes/hugo-PaperMod-7.0/layouts/partials/header.html
47:            <a href="{{ "" | absLangURL }}" accesskey="h" title="{{ $label_text }} (Alt + H)">
77:                <button id="theme-toggle" accesskey="t" title="(Alt + T)">
130:                {{- cond $is_search (" accesskey=/" | safeHTMLAttr) ("" | safeHTMLAttr ) }}>

themes/hugo-PaperMod-7.0/layouts/partials/footer.html
17:<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">

themes/hugo-PaperMod-7.0/layouts/partials/toc.html
6:        <summary accesskey="c" title="(Alt + C)">

具体功能如下

1
2
3
4
5
Alt + H 回到首页,back Home 
Alt + T 切换主题,change Theme
Alt + / 全站搜索
Alt + G 回到页顶,go to Top
Alt + C 展开和收起目录,Collapse toc

批量修改文件名为小写

如果文件夹名字中含有大写字符,图片可能无法显示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# rename_upperletter.py
# python3
import os
            

def upper_to_lower(name):
    res=''
    for i in name:
        if i.isupper():
            res+=i.lower()
        else:
            res+=i
    return res


def main():
    for root, dirs, files in os.walk(os.path.abspath("content")):
        for file in files:
            if not file.endswith(".md"):
                continue
            for x in file:
                if x.isupper():
                    before_name=os.path.join(root,file)
                    after_name=upper_to_lower(before_name)
                    print("%s => %s"%(before_name,after_name))
                    os.renames(before_name,after_name)                    
                    break
        for dir in dirs:
            for d in dir:
                if d.isupper():
                    before_name=os.path.join(root,dir)
                    after_name=upper_to_lower(before_name)
                    print("%s => %s"%(before_name,after_name))
                    os.renames(before_name,after_name)                    
                    # exit(1)
                    break

if __name__ == '__main__':
    main()

更多魔改

参考这位朋友的博客,实现了许多看起来很不错的功能:https://www.sulvblog.cn/posts/blog/

GitHub Action发布

参考 https://yangt.me/post/hugo-blog-with-github-actions/#github-actions

触发条件设置为 on push

1
2
3
4
on:
  push:
    braches:
      - master

error:Waiting for a runner to pick up this job

如果 action 迟迟不被执行,提示 waiting-for-a-runner-to-pick-up-this-job 那么可能是因为选择的平台不再被支持。比如应该将 runs-on: ubuntu-18.04 换成 runs-on: ubuntu-latest 。 参考此文:https://stackoverflow.com/questions/70959954/error-waiting-for-a-runner-to-pick-up-this-job-using-github-actions 。

定时任务

此文(https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule)说GitHub action的定时任务是基于UTC时间的。所以只要根据时区相应计算就可以。

比如北京时间 22:24 执行 定时任务就设置为如下

1
2
3
on:
  schedule:
    - cron: "24 14 * * *"

同时通过定时任务触发和手动触发更新博客

参考 https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow?tool=cli 添加一个触发条件,然后就能看到下面这个 Run workflow,不然看不到。

1
2
3
4
on:
  workflow_dispatch:
  schedule:
    - cron: "24 14 * * *"

使用restAPI手动更新博客:workflow_dispatch.sh

根据 https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow#running-a-workflow-using-the-rest-api ,我们可以通过浏览器触发、github cli触发和restAPI触发。

参考 https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event 填上参数,写成 sh 脚本就可以通过命令行手动更新博客了。在每日定时更新之外又多了一种更新方式。

1
2
3
4
5
6
7
curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches \
  -d '{"ref":"topic-branch","inputs":{"name":"Mona the Octocat","home":"San Francisco, CA"}}'

其中 WORKFLOW_ID 是workflow文件中定义的ID,也可以直接用workflow的yaml文件的名字,我这里是 hugo.yml

参考 https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token 获得 <YOUR-TOKEN> ,并且替换到文件中。TOKEN要选择fine-grained personal access token (需要对目标repo有 actions:write 的权限),有效期最长一年。

1
2
3
4
5
6
7
curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <需要替换这个YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/findneo/priblog/actions/workflows/hugo.yml/dispatches \
  -d '{"ref":"master"}'

workflow_dispatch.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash
./ok.sh "this is a commmit before manual update github page immediately"

echo 'triggerring workflow dispatch...'

curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer [TOKEN]"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/findneo/priblog/actions/workflows/hugo.yml/dispatches \
  -d '{"ref":"master"}'

🔨只有当上次运行之后仓库有更新才执行push,否则什么也不做

为了避免网站页面的更新时间总是变化。

更新仓库后github page没有刷新内容

可能的原因非常的多,我这里最后发现的原因非常地细微。最开始使用的是如下的yaml文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
name: deploy hugo pages

on:
  schedule:
    - cron: "24 14 * * *"
  push:
    braches:
      - master

env:
  GIT_USER: ci
  GIT_EMAIL: ci@github.com
  GIT_PRIVATEKEY: ${{ secrets.HUGO }}
  DATA_REPO: findneo/findneo.github.io
  DATA_REPO_BRANCH: master
  DATA_LOCAL_PATH: public

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master

      - name: build local blog
        run: |
          echo "### installing hugo and build blog static pages... ###"
          wget https://github.com/gohugoio/hugo/releases/download/v0.111.1/hugo_extended_0.111.1_linux-amd64.tar.gz
          tar xzf hugo_extended_0.111.1_linux-amd64.tar.gz
          ./hugo --minify
          rm -f hugo_extended_0.111.1_linux-amd64.tar.gz LICENSE README.md hugo          

      - name: Push changes
        run: |
          echo "### setting up git repo and pushing change... ###"
          pwd
          ls -lhA
          sudo timedatectl set-timezone "Asia/Shanghai"
          mkdir -p ~/.ssh/
          echo "${{ env.GIT_PRIVATEKEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan github.com >> ~/.ssh/known_hosts
          git config --global user.email ${{ env.GIT_EMAIL }}
          git config --global user.name ${{ env.GIT_USER }}
          cd ${{ env.DATA_LOCAL_PATH }}
          git init .
          git add .
          git commit -m "update blog static pages"
          git remote add origin git@github.com:${{ env.DATA_REPO }}.git
          git push -f origin master
          rm -rf ~/.ssh/          

注意到第47行是 git add . 。后来发现这个命令并不是追踪所有变化,而是只跟踪已有文件的变化,即修改和删除的操作。若要追踪所有变化,需要使用 git add -A

所以这里的原因实际上是没有push上去更新的内容。

一键更新ok.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

git status

read -p "=========================
Press [Enter] to continue
Press [CTRL+C] to stop
=========================

"

git add -A

if [ "$1" = "" ]
then
git commit -m "simple update, no msg."
else
git commit -m "$1"
fi

git push

需要注意,git add -Agit add -a 是不同的。前者会包含新增的文件,后者只包含修改和删除的文件。git add -A 等价于 git add . 加上 git add -u

如果在Windows里面编辑的sh脚本在WSL中运行时报错

1
2
3
./ok.sh: line 2: $'\r': command not found
' is not a git command. See 'git --help'.
...

可以使用以下命令,转换后即可使用

1
dos2unix.exe ok.sh

使用Linux本地定时任务每3分钟任务同步保存一次博客

脚本 autocommit_if_things_change.sh

1
2
3
4
#!/bin/bash
git add -A
git commit -m "this is a auto commit every 3 minutes if things in current repo changes"
git push

定时任务(此方法不行,因为WSL中的cron不好使)

1
2
$ tail /var/spool/cron/crontabs/bob -n1
*/3 * * * * cd /mnt/d/hakbase/priblog && sh autocommit_if_things_change.sh

使用Windows本地定时任务每3分钟任务同步保存一次博客

可以使用这个方法,执行以后后台一直运行,每分钟自动commit一次 autocommit_if_things_change.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash
# https://github.com/microsoft/WSL/issues/9072
# wsl -d ubuntu bash -c "nohup sh /mnt/d/hakbase/priblog/autocommit_if_things_change.sh >/dev/null &"
# wsl -d ubuntu bash -c "nohup sh /mnt/d/hakbase/priblog/autocommit_if_things_change.sh  </dev/null >/dev/null 2>&1 &"
while true
do
    sleep 60s
    cd /mnt/d/hakbase/priblog/ 
    git add -A
    git commit -m "this is a auto commit every serveral minutes if things in current repo changes"
    git push
done

通过Windows的“任务计划程序”让该文件在每次用户登录时执行:autocommit_if_things_change.bat

1
wsl -d ubuntu bash -c "nohup sh /mnt/d/hakbase/priblog/autocommit_if_things_change.sh  </dev/null >/dev/null 2>&1 &"

新建、编辑、预览和发布草稿

1
2
3
4
5
新建 hugo new draft/my-draft.md
预览 hugo server --buildDrafts
	访问 http://localhost:1313/draft/
发布 hugo publish draft/my-draft.md
列举草稿 hugo list drafts

待办事项

  • 优化代码块显示效果
  • 博客内容密码保护
  • 匿名化和清理typora的图片
  • 评论区:https://github.com/giscus/giscus
  • 优化目录样式,长目录折叠
  • 优化移动端展示效果
  • 全站加密,全量发布,定期自动更新密钥。输入密码以后才能访问搜索页面。