Featured image of post Obsidian+Hugo+Github+PicGo+jsDelivrCDN

Obsidian+Hugo+Github+PicGo+jsDelivrCDN

1.本地Hugo框架搭建

参考教程 【Hugo】Hugo + Github 免费部署自己的博客 (letere-gzj.github.io)

1.1.下载hugo

1.1.1.官网下载地址

gohugoio/hugo: The world’s fastest framework for building websites. (github.com) |810x27

1.2.下载git

1.2.1官网下载

Git (git-scm.com)

1.3.搭建博客

(1)创建博客

hugo.exe所在文件夹的地址栏敲打cmd,然后Enter唤起命令行

敲打命令hugo new site xxxx(项目名)创建hugo项目 进入项目文件夹中并把hugo.exe文件复制到项目文件夹下 敲打命令hugo server -D启动服务,访问http://localhost:1313,Ctrl+C停止服务 (hugo默认是没有主题的,需要进行主题配置)

1.4.配置主题

1.4.1基本配置

前往【Hugo Themes】,查找自己喜欢的主题,进行下载前往【Hugo Themes】,查找自己喜欢的主题,进行下载(我这边选用stack) 主题解压,放到/themes文件夹中 |810x214 (这时的解压后的文件不是这个名字,这是我根据配置文件修改的)

exampleSite样例数据中的 Content 和 hugo.yaml 复制到主文件夹中,并删掉hugo.tomlcontent/post/rich-content |515x437 |517x337 修改 hugo.yaml 中的 theme,将他修改为跟主题文件夹同名 |476x572 这就是前面我说的,主题解压后的文件夹名应与hugo.yaml中的theme属性值相同

自定义配置

参考文章 HugoStack装修 (pillar.fun)

注意 custom.scss搞完后,要在config.yaml中添加引用 |459x260 |459x461|423x314

热力图

参考 HugoStack装修 (pillar.fun)

添加文件

在  /themes/hugo-theme-stack/layouts/shortcodes和  /themes/hugo-theme-stack/layouts/partials里添加一个heatmap.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
159
160
161
162
163
164
165
<div id="heatmap" style="
  max-width: 1900px;
  height: 180px;
  padding: 2px;
  text-align: center;
  "
></div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.0/dist/echarts.min.js"></script>
<script type="text/javascript">
  var chartDom = document.getElementById('heatmap');
  var myChart = echarts.init(chartDom);
  window.onresize = function() {
      myChart.resize();
  };
  var option;

  // echart heatmap data seems to only support two elements tuple
  // it doesn't render when each item has 3 value
  // it also only pass first 2 elements when reading event param
  // so here we build a map to store additional metadata like link and title
  // map format {date: [{wordcount, link, title}]}
  // for more information see https://blog.douchi.space/hugo-blog-heatmap
  var dataMap = new Map();
  {{ range ((where .Site.RegularPages "Type" "post")) }}
    var key = {{ .Date.Format "2006-01-02" }};
    var value = dataMap.get(key);
    var wordCount = {{ .WordCount }};
    var link = {{ .RelPermalink}};
    var title = {{ .Title }};
  
    // multiple posts in same day
    if (value == null) {
      dataMap.set(key, [{wordCount, link, title}]);
    } else {
      value.push({wordCount, link, title});
    }
  {{- end -}}

  var data = [];
  // sum up the word count
  for (const [key, value] of dataMap.entries()) {
    var sum = 0;
    for (const v of value) {
      sum += v.wordCount;
    }
    data.push([key, (sum / 1000).toFixed(1)]);
  }
  
  var startDate = new Date();
  var year_Mill = startDate.setFullYear((startDate.getFullYear() - 1));
  var startDate = +new Date(year_Mill);
  var endDate = +new Date();

  var dayTime = 3600 * 24 * 1000;
  startDate = echarts.format.formatTime('yyyy-MM-dd', startDate);
  endDate = echarts.format.formatTime('yyyy-MM-dd', endDate);

  // change date range according to months we want to render
  function heatmap_width(months){         
    var startDate = new Date();
    var mill = startDate.setMonth((startDate.getMonth() - months));
    var endDate = +new Date();
    startDate = +new Date(mill);

    endDate = echarts.format.formatTime('yyyy-MM-dd', endDate);
    startDate = echarts.format.formatTime('yyyy-MM-dd', startDate);

    var showmonth = [];
    showmonth.push([
        startDate,
        endDate
    ]);
    return showmonth
  };

  function getRangeArr() {
    const windowWidth = window.innerWidth;
    if (windowWidth >= 600) {
      return heatmap_width(12);
    } else if (windowWidth >= 400) {
      return heatmap_width(9);
    } else {
      return heatmap_width(6);
    }
  }

  option = {
    title: {
        top: 0,
        left: 'center',
        text: '博客热力图'
    },
    tooltip: {
      hideDelay: 1000,
      enterable: true,
      formatter: function (p) {
        const date = p.data[0];
        const posts = dataMap.get(date);
        var content = `${date}`;
        for (const [i, post] of posts.entries()) {
            content += "<br>";
            var link = post.link;
            var title = post.title;
            var wordCount = (post.wordCount / 1000).toFixed(1);
            content += `<a href="${link}" target="_blank">${title} | ${wordCount} k</a>`
        }
        return content;
      }
    },
    visualMap: {
        min: 0,
        max: 10,
        type: 'piecewise',
        orient: 'horizontal',
        left: 'center',
        top: 30,
    
        inRange: {   
          //  [floor color, ceiling color]
          color: ['#7aa8744c', '#7AA874' ] 
        },
        splitNumber: 4,
        text: ['千字', ''],
        showLabel: true,
        itemGap: 20,
    },
    calendar: {
        top: 80,
        left: 20,
        right: 4,
        cellSize: ['auto', 13],
        range: getRangeArr(),
        itemStyle: {
            color: '#F1F1F1',
            borderWidth: 1.5,
            borderColor: '#fff',
        },
        yearLabel: { show: false },
        // the splitline between months. set to transparent for now.
        splitLine: {
          lineStyle: {
            color: 'rgba(0, 0, 0, 0.0)',
            // shadowColor: 'rgba(0, 0, 0, 0.5)',
            // shadowBlur: 5,
            // width: 0.5,
            // type: 'dashed',
          }
        }
    },
    series: {
        type: 'heatmap',
        coordinateSystem: 'calendar',
        data: data,
    }
  };
  myChart.setOption(option);
  myChart.on('click', function(params) {
    if (params.componentType === 'series') {
      // open the first post on the day
      const post = dataMap.get(params.data[0])[0];
      const link = window.location.origin + post.link;
      window.open(link, '_blank').focus();
    }
});
</script> 

如果在html模板中使用,就在要用的位置写 {{ partial "heatmap" . }} 如果在Markdown里用,在要用的位置写{{/* heatmap */}}

比如\themes\hugo-theme-stack\layouts\_default中,对各个页面的html修改 例如在主页html里面添加 |569x458 在归档html里面添加(已删除) |568x399

明暗适配设置

1.5.GitHub手动部署(建议开梯子)

前往【Github官网】,创建仓库 {github用户名}.github.io

前往Setting -> Pages -> Branch选择main分支,然后保存,会自动开启 https://{github用户名}.github.io 的地址,这地址也是以后访问博客的地址 |575x366 回到hugo文件中,执行命令hugo -D,会生成 public 静态资源文件夹 |579x461 在 public 执行以下命令上传到github仓库上,第一次上传可能需要输入账号密码

1
2
3
4
5
6
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin {你的github仓库地址}
git push -u origin main

上传成功后,- (5)上传成功后访问https://{github用户名}.github.io ,成功搭建属于自己的Hugo博客

[!attention] 若对网站的配置进行了修改,则还需要重新生成public(把原来文件夹中的删掉) 然后再重新上传到远程仓库中,很麻烦,所以就有了action辅助自动执行public操作

1.6.GitHub-Action自动部署到GitHub(建议开梯子)

1.6.1.基础方式(Obsidian-enveloppe + 本地仓库)

网站配置采用本地仓库修改后上传

Github创建一个新的仓库,用于存放Hugo的主文件 前往Setttings -> Developer Settings -> Personal access tokens,创建一个token(classic) |649x159 token选择永不过期,并勾选 repo 和 workflow 选项 |653x396 |650x446 在hugo主文件创建一个.github/workflows/xxxx.yaml文件,将以下内容复制进去,想具体了解更多,可查看【Github Action文档

 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
name: deploy

# 代码提交到main分支时触发github action
on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
        - name: Checkout
          uses: actions/checkout@v4
          with:
              fetch-depth: 0

        - name: Setup Hugo
          uses: peaceiris/actions-hugo@v3
          with:
              hugo-version: "latest"
              extended: true

        - name: Build Web
          run: hugo -D

        - name: Deploy Web
          uses: peaceiris/actions-gh-pages@v4
          with:
              PERSONAL_TOKEN: ${{ secrets.你的token变量名 }}
              EXTERNAL_REPOSITORY: 你的github名/你的public文件夹的仓库名
              PUBLISH_BRANCH: main
              PUBLISH_DIR: ./public
              commit_message: auto deploy

在hugo主文件创建.gitignore文件,来避免提交不必要的文件

1
2
3
4
5
6
7
# 自动生成的文件
public
resources
.hugo_build.lock

# hugo命令
hugo.exe

将hugo的主文件上传到仓库,上传成功后会触发Github Action,来自动部署你的静态页面

1
2
3
4
5
6
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin {你的github仓库地址}
git push -u origin main

往后更新配置时使用以下命令进行上传与同步 先拉取远程仓库main分支(防止由于远程仓库部分文件修改导致历史不同步发生冲突)

1
2
git checkout main
git pull origin main

然后上传本地更新

1
2
3
git add .
git commit -m "update"
git push
Obsidian-enveloppe来快捷上传笔记到网站

参考教程 使用 Obsidian 免费建个人博客 | PrintLove

Enveloppe配置

参考教程 Enveloppe 插件适配 Hugo 的配置 | PrintLove |558x418 |557x405

  • 通过 dir 属性获取上传的目录,最终的上传路径为 Root folder / Property key,即 content/post/{dir}
  • 如果 dir 属性没有设置则默认上传到 Default folder 目录下,即 content/post/posts

|593x581

图片

在 Obsidian 中引用本地图片链接时是不需要路径的,而在 Hugo 中一般会把图片统一放在 /static/images 目录下。

因此在 Hugo 文章的引用方式路径为 /images/图片名称

因为我是用的图床那这个配置也没那么重要,如果想与项目存储到一块则设置这一项

现在要做的就是给 Obsidian 的本地图片路径增加 /images 前缀,使用到了 Text replacer 正则。

格式转化

支持 ![]() 的图片格式,箭头 ↑ 表示会等 ![[]] 转化成 ![]() 之后执行:

1
2
3
匹配内容:/\]\(([^/\)]+?)\.(png|jpg|jpeg|webp|gif|avif)/
替换:](/images/$1.$2
箭头:↑

支持 ![[]] 的图片格式,且增加了 caption 和 size 的支持,分两种情况:

1
2
3
4
5
6
7
8
9
情况一,支持 ![[filename.png|size]] 
匹配内容:/\!\[\[([^/\]]+?)\.(png|jpg|jpeg|webp|gif|avif)\|(\d+)(x(\d+))?\]\]/
替换:
箭头:↓ 情况二,支持 ![[filename.png|caption|size]] 匹配内容:/\!\[\[([^/\]]+?)\.(png|jpg|jpeg|webp|gif|avif)\|([^\|]*?)(\|(\d+)(x(\d+))?)?\]\]/ 替换:
$3

$3

箭头:↓

上面的两种图片格式都支持,区别就是 ![[]] 格式会支持图片的 caption 和 size。

内部文章引用

|641x304

文章锚点

如果文章链接中出现了锚点跳转,则记着下拉框选择 Lower:6eb9e895a11107c864eeb7a31509946a.webp

锚点跳转就是跳转到文章的某个标题处,在 Obsidian 的格式为 [[文章#标题]]

该配置会将锚点 (#标题) 处理:

  1. 转小写
  2. 空格转化减号 (-)

只有转化才能正常访问锚点。

属性一级转二级

在 Obsidian 中如果属性是二级的话,展示效果就非常不友好直观,所以我会在 Obsidian 里这样写需要转二级的属性:

1
2
3
---
cover.image: 
---

这个属性我用来设置文章的封面,具体要看看自己的主题是不是这样,如果不是那需要自己调整下。

Text replacer 正则:

1
2
3
匹配内容:/cover\.image/
替换:cover:\n    image
箭头:↑

替换中二级的缩紧正常应该使用 \t,但我测试发现无效就改用 4 个空格。

删除留坑标记

当我发布的文章中如果许诺了一件事情,我就会配合 Obsidian 的 dataview 插件写一个标记。

然后使用 dataview 插件写的仪表盘里展示出留了哪些坑?

坑列表示意图

坑列表示意图

标记格式(todo1 记着改成 todo,我不想识别到这个标记,只是演示而已):

1
[todo1:: 留坑的内容]

dataview 语法:

1
LIST todo WHERE todo != null and share

现在使用 Text replacer 正则删除文章里的标记:

1
2
3
匹配内容:/\[todo::[^\]]+?\]/
替换这块不填,就会删除匹配到的内容
箭头:↑
标签

在 Obsidian 里是支持 / 方式渲染多级标签的,在 Hugo 中是不支持的,所以需要开启将 / 转位 _3c3e40b83ac5be7799d55696eadc7218.webp

踩的坑

Markdown hard line break 最好关闭,我这边开启造成了空行变多,使代码难看、表格无法渲染。

附件上传(图片等)

PicGo图床+JsDelivrCDN加速

静态图片资源不在项目中存储,而是存在外部图床

搭建Github图床

创建一个新的仓库,然后配置PicGo

CDN加速

jsDelivr - A free, fast, and reliable CDN for JS and open source 自定义前缀:https://cdn.jsdelivr.net/gh/GitHub用户名/Github图床仓库名

[!attention] Title 自定义域名要设置前缀,从而使得CDN加速访问Github图床资源,有利于图片加载速度提高

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计