让hugo支持wikilink
目录
前情提要
在我决定使用 obsidian 来记录笔记后, 就发现有 wikilink
这个东西, 它不是 CommonMark 的一部分, 所以会导致很多的工具链的不兼容. 例如 vscode 默认无法读取.
而我选择用 hugo 进行站点的构建发布. 所以这篇博客就如题目所说的, 我要打通 hugo 和 obsidian ,所以就有了这一篇文章.我搭建了一个 demo 站点, 你可以看看 效果.
我做了什么
原理
- hugo 默认使用 goldmark 进行 markdown 的渲染.
- goldmark 可以通过插件支持 wikilink.
- abhinav 的 goldmark-wikilink 这个插件与我的主题, 笔记路径, url 配置不兼容, 需要进行调整.
- 改 hugo 代码, 加入配置, 编译构建.
- 编写 github actions 发布工具.
准备 goldmark-wikilink
首先 fork 一份 goldmark-wikilink 代码到 我的仓库.
改动 go.mod
module github.com/kentxxq/goldmark-wikilink
改动 resolver.go, 添加解析代码. 下面是伪代码, 讲一下我做了什么:
// 默认解析,看起来不错
// [[Foo]] // => "Foo.html"
// [[Foo bar]] // => "Foo bar.html"
// [[foo/Bar]] // => "foo/Bar.html"
// [[foo.pdf]] // => "foo.pdf"
// [[foo.png]] // => "foo.png"
var DefaultResolver Resolver = defaultResolver{}
// hugo默认路径是/Foo/,所以我加了一个PrettyResolver解决这个问题
// 关于url的路径切换可以参考文档 https://gohugo.io/content-management/urls/#appearance
// [[Foo]] // => "Foo/"
var PrettyResolver Resolver = prettyResolver{}
// 当我的obsidian笔记wikilink使用`相对于当前文件的路径`时
// /root/Foo.md url: /root/Foo/
// /root/a.md include [[Foo]] . url: /root/a/ wikilink: /root/a/Foo/ not found!
// 所以我加上了RelResolver
// [[Foo]] // => "../Foo/" worked!
var RelResolver Resolver = relResolver{}
// 但其实obsidian中使用这样的格式并不好看.我改成了`相对于项目根路径`后
// when i use pretty url with [[absolute path]]
// /Foo.md url: /posts/Foo/
// /a.md include [[root/Foo]] . url: /posts/a/ wikilink: /posts/a/posts/Foo/ not found!
// so...
// [[Foo]] // => "/root/Foo/" worked!
var RootResolver = func(b string) Resolver {
return &rootResolver{
base: b,
}
}
var pretty_html = []byte("/")
// 相对路径就是在最前面加上../,变成请求上一级目录
var rel_head = []byte("../")
type relResolver struct{}
func (relResolver) ResolveWikilink(n *Node) ([]byte, error) {
dest := make([]byte, len(rel_head)+len(n.Target)+len(pretty_html)+len(_hash)+len(n.Fragment))
var i int
if len(n.Target) > 0 {
i += copy(dest, rel_head)
i += copy(dest[i:], n.Target)
if filepath.Ext(string(n.Target)) == "" {
i += copy(dest[i:], pretty_html)
}
}
if len(n.Fragment) > 0 {
i += copy(dest[i:], _hash)
i += copy(dest[i:], n.Fragment)
}
return dest[:i], nil
}
// 绝对路径就是传入前缀,然后直接加上wikilink的内容即可
type rootResolver struct {
base string
}
func (r rootResolver) ResolveWikilink(n *Node) ([]byte, error) {
dest := make([]byte, len(r.base)+len(n.Target)+len(pretty_html)+len(_hash)+len(n.Fragment))
var i int
if len(n.Target) > 0 {
i += copy(dest, []byte(r.base))
i += copy(dest[i:], n.Target)
if filepath.Ext(string(n.Target)) == "" {
i += copy(dest[i:], pretty_html)
}
}
if len(n.Fragment) > 0 {
i += copy(dest[i:], _hash)
i += copy(dest[i:], n.Fragment)
}
return dest[:i], nil
}
改动 hugo
安装依赖 go get github.com/kentxxq/goldmark-wikilink
.
改动 markup/goldmark/goldmark_config/config.go,加入配置参数
type Extensions struct {
Typographer Typographer
Footnote bool
DefinitionList bool
// GitHub flavored markdown
Table bool
Strikethrough bool
Linkify bool
LinkifyProtocol string
TaskList bool
// 下面是我们新加的参数
// 采用那种方法解析链接?
WikilinkReslover string
// ROOT模式下,传入路径前缀
WikilinkRootPath string
// 是否启用wikilink
EnableWikilink bool
}
改动 markup/goldmark/convert.go,让我们的配置和 wikilink 解析器生效.
import (
"bytes"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/goldmark/images"
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/tableofcontents"
wikilink "github.com/kentxxq/goldmark-wikilink" //引入解析
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
)
extensions = append(extensions, images.New(cfg.Parser.WrapStandAloneImageWithinParagraph))
// 加入
if mcfg.Goldmark.Extensions.EnableWikilink {
switch mcfg.Goldmark.Extensions.WikilinkReslover {
case "DefaultResolver":
extensions = append(extensions, &wikilink.Extender{
Resolver: wikilink.DefaultResolver,
})
case "PrettyResolver":
extensions = append(extensions, &wikilink.Extender{
Resolver: wikilink.PrettyResolver,
})
case "RelResolver":
extensions = append(extensions, &wikilink.Extender{
Resolver: wikilink.RelResolver,
})
case "RootResolver":
extensions = append(extensions, &wikilink.Extender{
Resolver: wikilink.RootResolver(mcfg.Goldmark.Extensions.WikilinkRootPath),
})
}
}
配置 dart-sass
下载 Releases · sass/dart-sass 的对应系统版本, 我用的 dart-sass-1.63.6-windows-x64.zip
.解压后配置到环境变量里.
打开终端验证效果
sass --version
1.63.6
开始构建代码
$GOOS=windows
$GOARCH=amd64
$CGO_ENABLED=1
$CC="gcc"
$CXX="g++"
# 构建命令
# -v 详细信息
# -x 打印出执行的命令,以及相关的详细信息
# extended是加入sass,release则是hugo自定义
# `-s` 表示禁用符号表,`-w` 表示禁用 DWARF 调试信息,`-extldflags '-static'` 表示使用静态链接方式进行链接。
go build -v -x -tags extended,release -ldflags "-s -w -extldflags '-static'"
# 文件夹多出一个hugo.exe
hugo 预览
# 克隆我的示例代码
git clone https://github.com/kentxxq/doit-demo.git
# 注意hugo.toml文件加入了如下配置
EnableWikilink = true
WikilinkRootPath = "/posts/"
WikilinkReslover = "RootResolver"
# 启动3333端口
cd doit-demo
hugo server --disableFastRender -p 3333
访问 http://localhost:3333 看看效果吧.
相关资料
- 除非 CommonMark 添加对 wikilink 的支持, hugo 可能永远都不会有 wikilink 了. Support wiki-style internal page links · Issue #3606 · gohugoio/hugo · GitHub
- 除了让 hugo 在渲染阶段支持 wikilink, 还可以再主题内进行 url 的处理,例如 obsidian发布hugo-quartz.但是这样的主题很少…