Hugo个人博客搭建

Hugo 能做什么 通过 Hugo 你可以快速搭建你的静态网站,比如博客系统、文档介绍、公司主页、产品介绍等等。相对于其他静态网站生成器来说,Hugo 具备如下特点: 极快的页面编译生成速度。( ~1 ms 每页面) 完全跨平台支持,可以运行在 Mac OS X, Linux, Windows, 以及更多! 安装方便 Installation 本地调试 Usage 时通过 LiveReload 自动即时刷新页面。 完全的皮肤支持。 可以部署在任何的支持 HTTP 的服务器上。 安装部署简要说明: 操作系统是Ubuntu 22.04.4 LTS Hugo生成静态页面 Nginx发布服务 Go环境安装 前往go.dev下载最新的go程序 wget https://go.dev/dl/go1.22.1.linux-amd64.tar.gz 解压安装 sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.22.1.linux-amd64.tar.gz 加入环境变量 sudo vim /etc/profile 在文件最后增加 export PATH=$PATH:/usr/local/go/bin 退出登录或者source /etc/profile重新加载环境配置,检查go是否正常运行,能正常输出版本信息就正常 go version Hugo安装 由于Ubuntu源里的版本不是最新版,这里直接采用go的编译安装模式,在主目录下运行 go install github.com/gohugoio/hugo@latest 加入环境变量 vim ~/.bashrc 在文件最后增加 export PATH=$PATH:~/go/bin 退出登录或者source ~/.bashrc重新加载环境配置,检查hugo是否正常运行,能正常输出版本信息就正常 hugo version 现在使用的hugo版本信息 ...

十一月 19, 2024 · 4 分钟 · 814 字 · Byter ·  Hugo ·  Blog ·  Ubuntu

Hugo集成大善人Cloudflare+D1网页访客统计服务

本文主要是采用开源项目analytics_with_cloudflare 。 analytics_with_cloudflare服务部署 下载项目文件 git clone https://github.com/yestool/analytics_with_cloudflare 安装依赖 cd analytics_with_cloudflare npm install -g wrangler npm install hono 登录 跳转cloudflare网页授权 npx wrangler login 创建D1数据库:[web_analytics] 数据库名称为web_analytics,与package.json内保持一致 npx wrangler d1 create web_analytics 成功后显示: ✅ Successfully created DB web_analytics [[d1_databases]] binding = "DB" # available in your Worker on env.DB database_name = "web_analytics" database_id = "<unique-ID-for-your-database>" 配置worker和D1数据库绑定 将上个步骤返回的unique-ID-for-your-database写进wrangler.toml中 name = "analytics_with_cloudflare" main = "src/index.ts" compatibility_date = "2024-06-14" [[d1_databases]] binding = "DB" # available in your Worker on env.DB database_name = "web_analytics" database_id = "<unique-ID-for-your-database>" 初始化D1数据库的表结构 npm run initSql 修改src/index.ts 内容 const body = await c.req.json() const hostname = body.hostname const url_path = body.url const referrer = body.referrer const pv = body.pv const uv = body.uv // 添加以下两行 const spv = body.spv const suv = body.suv // 中间代码忽略 // 修改下面代码内容 const resData:{pv?: number, uv?: number, spv?: number, suv?: number} = {} // 中间代码忽略 if (uv){ const total = await c.env.DB.prepare('SELECT COUNT(*) AS total from (select DISTINCT visitor_ip from t_web_visitor where website_id = ? and url_path = ?) t').bind(websiteId, url_path).first('total'); resData['uv'] = Number(total) } // 添加以下两段代码 if (spv){ const total = await c.env.DB.prepare('SELECT COUNT(*) AS total from t_web_visitor where website_id = ?').bind(websiteId).first('total'); resData['spv'] = Number(total) } if (suv){ const total = await c.env.DB.prepare('SELECT COUNT(*) AS total from (select DISTINCT visitor_ip from t_web_visitor where website_id = ?) t').bind(websiteId).first('total'); resData['suv'] = Number(total) } // 修改完成 完整代码内容,可以下载index.ts 原来的代码只支撑返回单个页面的访问数据,不支持返回整个网站的访问数据,增加spv和suv两个返回值,分别是全站访问人次和全站访问人数。 ...

Hugo集成ECharts支持鼠标点击链接

先看看效果展示,实现鼠标点击提示框链接进入跑步详情页面,整体效果 跑步总结。 为了实现提示框可以点击,来回折腾浪费了很长时间,记录下来以备不时之需。 在ECharts的示例编辑里的tooltip配置项内容: "tooltip": { "triggerOn": "click", "enterable": true, "formatter":function (params) { if (params.value[1] <= 3) { return ''; } else { return ( '<a href="/posts/run/' + params.value[2] + '/" target="_blank\">' + params.value[0].slice(-5) + '</a> <br>' + Number(params.value[1]).toFixed(2) + 'km ' ); } } } Hugo编译时报如下错误: Error: error building site: "xx/content/posts/run/run_stats.md:135:1": "xx/layouts/_default/_markup/render-codeblock-echarts.html:6:37": execute of template failed: template: _default/_markup/render-codeblock-echarts.html:6:37: executing "_default/_markup/render-codeblock-echarts.html" at <transform.Unmarshal>: error calling Unmarshal: "_stream.json:5:1": unmarshal failed: invalid character 'u' in literal false (expecting 'a') Hugo编译时会使用transform.Unmarshal校验json内容,formmater的参数内容不符合规范内容。 ...

九月 15, 2024 · 2 分钟 · 263 字 · Byter ·  ECharts ·  Hugo

Obsidian通过Dropbox同步实现文章自动发布

graph LR 网易云笔记-->Evernote; Evernote-->为知笔记; 为知笔记-->Evernote; Evernote-->Joplin; Joplin-->Obsidian; 现在用的Obsidian是Markdown支持最好的,可以说写文档是一种享受了,但是要用官方的同步还是付费,于是就找有没有同步的方案,没有采用第三方插件Remotely Save或者是Remotely Sync,而是采用Dropbox直接同步文件形式,个人使用,没有太多的协同编写的场景。 graph TD B("fab:fa-dropbox Dropbox") B<-->C["fa:fa-laptop Obsidian maestral"] B<-->D["fa:fa-mobile-screen-button Obsidian Dropsync"] B<-->E["fab:fa-linux Hugo maestral"] PC端用maestral先同步dropbox文件,然后Obsidian直接在dropbox的同步目录创建仓库,之后编辑的内容就会实时同步了,可以Show Recent Change 功能查看已经同步的变更文件,有冲突的文件会有提示。 移动端使用Dropsync同步文件,然后Obsidian打开仓库,在PC安装的插件和设置都可以在移动端使用。需要注意的是,因为Dropsync 不是采用文件变化通知机制同步,而是定时同步机制,因此需要在手机端打开Obsidian之前,需要手动同步一下。另外就是最好PC端、移动端不要同时设置或者打开关闭笔记,会有同步文件冲突。有冲突的情况下,可以进入仓库查看笔记或者.obsidian目录下是否存在appearance (conflict 2024-04-15-08-35-19).json 类似的文件。 可以在Dropsync软件的同步历史查看同步日志信息。 服务器端主要用maestral在命令行下运行同步Dropbox文件,然后通过脚本把对应笔记md文件拷贝到 Hugo 目录下,编译发布成个人站点文章。服务器没有设置成一有文件变更就同步更新到Hugo,感觉有些频繁,我是设置了脚本的定时任务,每天晚上编译发布,确实想实时发布的,就到服务器上手动执行一下脚本。 整个流程下来对于使用基本是无感的,同步速度也很快,基本在PC端编辑完之后,你登录上服务器就可以看到同步好的文件。 用到的软件: Obsidian: https://obsidian.md/ Maestral: https://maestral.app/ Dropsync: https://metactrl.com/#our-apps

Hugo代码块固定高度设置

Hugo个人博客搭建过程参考Hugo个人博客搭建 新增code的css mkdir -p assets/css/extended vim assets/css/extended/code.css code.css内容如下: .post-content pre code { max-height: 40em; /* 根据需要调整高度 */ overflow: auto; } 设置目录到左侧是参考3rd’s Blog的博客 感谢

Hugo文章TOC固定在左侧显示

Hugo个人博客搭建过程参考Hugo个人博客搭建 在layouts/partials目录下创建toc.html vim layouts/partials/toc.html 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 }} 修改toc的css ...

四月 14, 2024 · 4 分钟 · 838 字 · Byter ·  Hugo ·  TOC

Hugo日子列表和文章头部显示标签

Hugo个人博客搭建过程参考Hugo个人博客搭建 在layouts/partials目录下创建 post_meta.html cp themes/hugo-PaperMod-master/layouts/partials/post_meta.html layouts/partials/post_meta.html vim layouts/partials/post_meta.html 将一下部分的内容 {{- if not (.Param "hideAuthor") -}} {{- with (partial "author.html" .) }} {{- $scratch.Add "meta" (slice .) }} {{- end }} {{- end }} 修改为 {{- $author := (partial "author.html" .) }} {{- $tags := (partial "tags.html" .) }} {{- if not (.Param "hideAuthor") -}} {{- if $tags }} {{- $scratch.Add "meta" (slice $author $tags) -}} {{- else}} {{- $scratch.Add "meta" (slice $author) -}} {{- end}} {{- else}} {{- if $tags }} {{- $scratch.Add "meta" (slice $tags) -}} {{- end}} {{- end }} 在layouts/partials目录下创建tags.html ...

四月 14, 2024 · 1 分钟 · 144 字 · Byter ·  Hugo ·  Post ·  Meta

Hugo支持渲染mermaid图

Hugo个人博客搭建过程参考Hugo个人博客搭建 sequenceDiagram participant Alice participant Bob Alice->>Bob: Hello Bob! Bob->>Alice: Hello Alice! 在layouts目录下创建_default目录,拷贝主题下对应的内容 mkdir -p layouts/_default cp themes/hugo-PaperMod-master/layouts/_default/baseof.html layouts/_default/ vim layouts/_default/baseof.html 在文件后面增加以下内容 {{ if .Store.Get "hasMermaid" }} <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> {{ end }} 在layouts/_default目录下创建_markup目录 mkdir -p layouts/_default/_markup vim layouts/_default/_markup/render-codeblock-mermaid.html render-codeblock-mermaid.html文件内容如下 <pre class="mermaid"> {{- .Inner | safeHTML }} </pre> {{ .Page.Store.Set "hasMermaid" true }}

四月 14, 2024 · 1 分钟 · 61 字 · Byter ·  Hugo ·  Mermaid

Hugo集成Disqus评论

Hugo个人博客搭建过程参考Hugo个人博客搭建 Hugo 包含 Disqus 的嵌入式模板,Disqus 是一种流行的评论系统,适用于静态和动态网站。要有效使用 Disqus,请通过注册免费服务获得 Disqus “shortname”。 Disqus注册连接 signing up 在layouts创建partials再创建comments.html文件vim layouts/partials/comments.html cd bytejog/ mkdir -p layouts/partials vim layouts/partials/comments.html 文件内容为: <div id="disqus_thread"></div> <script type="text/javascript"> (function() { // Don't ever inject Disqus on localhost--it creates unwanted // discussions from 'localhost:1313' on your Disqus account... if (window.location.hostname == "localhost") return; var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; var disqus_shortname = '{{ .Site.Config.Services.Disqus.Shortname }}'; dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script> <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript> <a href="https://disqus.com/" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a> 修改hugo.yaml文件vim hugo.yaml services: disqus: shortname: #shortname

四月 14, 2024 · 1 分钟 · 102 字 · Byter ·  Hugo ·  Disqus