目录

让hugo支持wikilink

在我决定使用 obsidian 来记录笔记后, 就发现有 wikilink 这个东西, 它不是 CommonMark 的一部分, 所以会导致很多的工具链的不兼容. 例如 vscode 默认无法读取.

而我选择用 hugo 进行站点的构建发布. 所以这篇博客就如题目所说的, 我要打通 hugo 和 obsidian ,所以就有了这一篇文章.我搭建了一个 demo 站点, 你可以看看 效果.

  1. hugo 默认使用 goldmark 进行 markdown 的渲染.
  2. goldmark 可以通过插件支持 wikilink.
  3. abhinav 的 goldmark-wikilink 这个插件与我的主题, 笔记路径, url 配置不兼容, 需要进行调整.
  4. 改 hugo 代码, 加入配置, 编译构建.
  5. 编写 github actions 发布工具.

首先 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
}

同样 fork 一份 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),
			})
		}
	}

下载 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
# 克隆我的示例代码
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 看看效果吧.