丝滑的 Obsidian 和 Hugo
自从 Typora 开始收费过后我一直找不到好用的 Markdown 编辑器。大部分支持 Markdown 的编辑器都不支持所见即所得,这可是 Typora 的 Killer Feature,支持的那几个对中文排版也不怎么友好。
后来我开始用 Obsidian,这东西在我最开始尝试的时候还很不成熟,但如今可能是最好的笔记软件之一了。支持所见即所得、没有复杂的概念(双链和 Markdown 扩展语法是可选的)、默认配置趁手(简简单单用文件夹分类)、拥有良好的生态、本地优先… 很少有软件让我挑不出什么毛病。除了它不是开源的,不过我也没有这么强的许可证洁癖,能和 Obsidian 扳扳手腕的 Logseq 用起来太卡,我没什么兴趣。
唯一的不足是,没有什么现成的方法让 Hugo 和 Obsidian 联动起来,导致我不能很方便的发表写的东西。作为个人笔记倒是没什么,不过写东西出来多少希望有点反馈,否则自说自话,意义太过有限。网上大部分联动的方案是用 QuickAdd 写一个宏,快速往 Hugo 的内容目录里新建文档,然后手动往里面粘贴内容,我这么用过一段时间,效果不错,不过依旧没有达到我想要的效果。

后来偶然间发现了 Templater,一款和 QuickAdd 类似的 Obsidian 扩展,不过功能强大很多。这玩意似乎是用来作为编辑宏的,不过鉴于它提供了很多 API,你完全可以用它来实现上面提到的功能,还能更进一步。以往我需要手动粘贴内容到新建的文档里,现在直接在 Obsidian 里写好,调用一下模板,就直接把内容和图片“复制”过去了。
现在,我只需要在 Obsidian 里写/编辑文章:

写完之后调用一下模板:


就能自动把文章内容和图片复制到 Hugo 里:

最后附上我的 Templater 模板:
<%*
// Capture post name and sanitize it
let postName = await tp.system.prompt("Post Path");
let sanitizedName = postName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
// Define Hugo content paths
const basePath = "Blog/posts";
const folderPath = `${basePath}/${sanitizedName}`;
const filePath = `${folderPath}/index.md`;
// Get current note's content and directory
let content = tp.file.content;
const currentDir = tp.file.folder(true);
// Improved image detection regex
const imageRegex = /!\[.*?\]\((.*?)\)/g;
let matches = [...content.matchAll(imageRegex)];
let imagePaths = matches.map(m => m[1]);
// Create folder first, for images
await app.vault.createFolder(folderPath).catch(() => {});
// Process images
for (let imagePath of imagePaths) {
// Skip external URLs
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) continue;
// Resolve absolute path
const absoluteImagePath = `attachments/${imagePath}`;
const imageFile = app.vault.getAbstractFileByPath(absoluteImagePath);
if (imageFile) {
try {
// Copy to target directory
const newPath = `${folderPath}/${imageFile.name}`;
await app.vault.copy(imageFile, newPath);
// Update link - use relative path from content folder
content = content.replace(imagePath, imageFile.name);
} catch (e) {
console.error(`Failed to copy image: ${imageFile.path}`, e);
}
} else {
console.warn(`Image not found: ${absoluteImagePath}`);
}
}
// Create front matter with fixed date format
const frontMatter = `---
title: "${tp.file.title}"
date: "${tp.date.now("YYYY-MM-DDTHH:mm:ss+08:00")}"
draft: true
description:
build:
publishResources: false
---
`;
// Create folder index.md
await app.vault.create(filePath, frontMatter + content);
// Open the new file
app.workspace.openLinkText(filePath, "", true);
%>
要注意,这个东西还有点小 Bug:它没法很好的处理带有空格的图片文件名,也不能识别 Obsidian 默认的双链图片格式。要使用这个模板,请把 Obsidian 插入图片的行为设置为标准 Markdown 格式,并确保 Obsidian 把图片存储在库的 attachments 文件夹下。
