Skip to main content

讓 Obsidian 與 Docusaurus 一起協作

Docusaurus

Docusaurus 是 Facebook 的開源專案,主要作為靜態網站產生器 (我認為很像精簡版的 Next.js),可以用來快速打造文件網站,甚至也有許多人拿這個工具來寫部落格

(我印象中 Redux 的 document 也是用其建造)

由於使用 React 作為底層,如果熟悉 React 的話可以進行高度的客製化,這一點真的很棒,以前使用過 Hexo 或 Hugo 的時候根本改不動 QQ

緣由

先前我在自建的 Docusaurus 部落格上發表了幾篇筆記,後來使用 Obsidian 作為主要知識管理工具,發現更能專注於寫作與進入心流

我一直在思考怎麼融合 Obsidian 與 Docusaurus,並把寫作的 workflow 連結起來,於是終於慢慢思索出不會太折騰的方式 (對一般人來說也是有點折騰就是了...)

使用 Obsidian 會更傾向卡片盒筆記法,把知識點原子化,並使用筆記之間的連結組織起來,而不是寫一篇超長的文章,在這情況下處理文章之間的連結並能兼容兩者,就會變得麻煩且重要

LLM Prompt Engineering MOC 就是個例子

先前有考慮過 obsidian-export,但是輸出後的 YAML 格式會被替換,會和 Docusauraus 不相容,後來還是決定自己手動設定 + Obsidian plugin 批次更改

Obsidian 重要 Plugin 參考

必須要裝的

  • Linter : 對 YAML frontmatter 進行批次處理,也可以作為 md 的 prettier,非常強大
  • `obsidian-paste-image-rename : 除了自動複製貼上的圖片進行快速更名,也能把同一篇文章引用的圖片進行批次更名
  • Obsidian Link Converter : 謹慎使用,把 Wikilink 轉換成一般的 MDLinks,使用前請先小規模測試或備份
  • Obsidian Git : 不多說,備份與同步神器

有了這些會舒服多

  • Paste URL into Selection : 選起來的文字貼上連結,會變成 md Link 格式
  • AutoTitleLink : 自動 fetch 貼上連結 的標題
  • Tag Wrangler : 批次修改 tags 名稱很方便

Obsidian 的核心精神

  • 使用內置的 explorer 搬遷筆記、圖片,以及對其進行更名,都會自動更新引用這些檔案的筆記
    • 例如對 A 進行更名和搬遷,所有引用 A 的筆記,都會自動更新筆記內的引用路徑和名稱 (圖片也是同理)
    • 但是要注意,不要使用一般的檔案總管,這樣會無法自動更新
  • 在維持純文本和單純資料夾的格式之下,實現強大功能
    • 備份和同步,只要處理整個資料夾就好,不受制於筆記與筆記軟體的耦合性 (例如 Notion)
    • 只要最原始的檔案都是純文本,就可以保留許多用程式語言來批次處理的客製空間
  • 超級好用的筆記連結自動完成
    • Obsidian 的模糊搜索相當厲害,UX 滿點

在 Obsidian 筆記組織的原則

課題分離,不用整個大改

Obsidian 有不少把整個 vault 輸出成 SSG 的服務,例如 jackyzha0/quartz、甚至是官方的 obsidian publish,缺點是中間的過程都是黑魔法,很難符合自己的需求,而且可能會有許多 bug 要折騰很久

後來思考到,因為我的 vault 裡有許多個人的日記還有專案資料夾,這些是不需要 publish 出去的,所以我不需要這種 end to end 的服務,只要將要公開發表的筆記都放在一個資料夾統一處理即可

如果你的 Obsidian 已經很龐雜,先獨立一個資料夾專門給 SSG 來使用,把要發佈的筆記慢慢搬遷進來,後記我會簡稱為 pubish 資料夾

Pubish 資料夾的寫作原則

在 vault 新增一個資料夾,作為 Docusaurus 的文章基地之後,我認為有幾個規定需要考慮

在 publish 資料夾使用最廣為通行的 md 語法

  • 只寫最通用的 md 格式,不使用 Obsidian 獨有的 format 和功能,例如 dataview、admonition
    • 但是 code block 和 math jax 算是很普及的 md 功能了,可以使用
  • 只用一般 relative path + markdown link,不使用 wiki link 格式
    • 這是為了在 Docusaurus 裡也能維持筆記之間的連結
    • relative path + markdown link 可能在 source 模式會顯得很長,但是使用 live preview 模式幾乎無感
    • 輸入 [[ 仍可享用 obsidian 厲害的模糊搜尋,按下 enter 也會變成 markdown link 格式
  • 只用最單純的筆記連結,而不使用 block reference

最後,我會在 Obsidian 設定 attachment 資料夾,集中所有圖片,而不是散落在各個子資料夾之間,這樣會更方便處理

筆記檔名與 id

由於主要日常工作還是在自己的筆記系統,想維持檔名是可讀性高的格式 (例如有中文和空白) 所以需要額外功夫來處理筆記 route 和顯示的標題

在筆記檔名有中文和空白的情況下,會造成網址路徑很長 (因為轉換 URL encode),故使用 id 來固定 route 連結穩定度,title 則是設定 Docusaurus 的顯示名稱

因為我不喜歡在 Obsidian 有 h1,所以才需要設 title,如果筆記內有 h1,就可以不用設 title (我記得 Docusaurus 會自動取用 h1)

可參考這裡

範例

讓 Obsidian 與 Docusaurus 一起協作.md

id: obs-docusaurus-cowork
title: 讓 Obsidian 與 Docusaurus 一起協作

id 需要自己想,可以先不加,但如果要發在社群或公開發表,建議要加,以增加連結的穩定度

title 可以用 Linter 批次處理,會自動用檔名或 h1 來添加

首先開啟 Obsidian 設定,找到 Files & Links,並套用以下設定

這樣往後新增 link 都會啟用 relative path + markdown link 格式

過往的筆記如果想要批次轉換,可使用 Obsidian Link Converter不過記得在設定選 relative path,wiki-link to MD Link 我實測下來問題不大,但是反向轉換就會比較多 bug

建議啟用指令模版,在資料夾的 scope 之下逐步轉換

老話一句,批次轉換前請 git 備份

使用 Linter 的小撇步

我主要會使用 linter 這個 plugin 來處理 YAML,裡面有很多針對 md 進行格式化的選項,也可以依喜好使用

  • 我習慣把 Lint on Save 關掉,並把 linter 設入快捷鍵,自己手動觸發
  • 如果想對數個檔案一次 linter,可以放在同一個資料夾,然後右鍵選取 Linter File
  • 配合 git 備份,盡量小規模測試,不要一次大範圍改

這個功能會將中文和英文數字之間自動加上空格,超級好用

找出聯集,先服務 Docusaurus

Docusaurus 的限制會比 Obsidian 還要多,所以會 先以 Docusaurus 能動起來為主,以下是我觀察到 Docusaurus 的一些小坑

docs 和 blog 適用的 front matter 如下

📦 plugin-content-docs | Docusaurus

📦 plugin-content-blog | Docusaurus

如果你使用的 key 在 Docusaurus front matter 列表中卻是空值的話,Docusaurus 會報錯,解決辦法就是整個拿掉

---
id:
---

這樣會報錯

使用 single line array,避免一些縮排的坑,而且這種格式跟 md 的 list 語法太類似,很容易跟一些 plugin 相衝

---
tags:
- note-status/🌱
- note-framework/🔧how-to
---

bad
---
tags: [note-status/🌱, note-framework/🔧how-to]
---

good

如果你的現有筆記是 multi line,可以靠 linter 來幫你批次更改格式

這樣設定,就可以把 tags 格式自動進行批次轉換

重要 ! YAML key 請使用 tags,Docusaurus 只認得 tags

Obsidian 可以認得 tags 和 tag,所以可能過往的筆記會混合使用,可以用搜尋取代簡單解決

圖片處理

  • 先假設 vault 的 attachment 資料夾叫做 img-src,將 img-src 複製到 Docusaurus 資料夾的 static
  • 鎖定 Docusaurus docsblog 資料夾中的 md,將筆記中圖片的相對路徑移除,處理成根目錄,例如 /img-src/ 變成成 /img-src
    • 建議用 js 正則表達式來做,並在 npm script 裡同時啟用

圖片檔名不要含空白跟中文,這很重要

圖片檔名包含中文和空白,在 Obsidian 中的路徑會被處理成 URL Encode

上述把 img-src 放在 static 是屬於 absolute path 的方式,而 Docusaurus 在 relative path 圖片路徑有進行 bug 修復,可以支援 URL encode,但是 absolute path 碰到 URL encoded 的路徑還是會有問題 (可能是忘記吧...)

fix(mdx-loader): allow image paths to be URL encoded by Josh-Cena · Pull Request #6792 · facebook/docusaurus · GitHub

實際的 file diff

不想手動改圖檔名稱的話,可以使用 obsidian-paste-image-rename

主要流程

  1. 在 Obsidian 把特定資料夾作為發佈區,裡面的筆記要符合 Docusauraus 的規範,以及只使用一般 md 用法
  2. 筆記之間的連結使用 relative path + markdown link
  3. Obsidian 中圖片統一放在 attachment 資料夾,檔名不要有中文和空白
  4. 把 attachment 資料夾複製到 Docusaurus 的 static 資料夾底下
  5. 把 Docusaurus docs & blog 資料夾底下的 md,其圖片路徑取代為絕對路徑 (/img-src/ 轉換為 /img-src/)

我的 prebuild script

初步開發階段,請酌量參考

require('dotenv').config();
const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');

const src = process.env.SOURCE;
const srcDocDir = process.env.SOURCE_DOC_DIR;
const srcBlogDir = process.env.SOURCE_BLOG_DIR;
const srcImgDir = process.env.SOURCE_IMG_DIR;

const dest = process.env.DESTINATION;
const destDocDir = process.env.DESTINATION_DOC_DIR;
const destBlogDir = process.env.DESTINATION_BLOG_DIR;
const destImgDir = process.env.DESTINATION_IMG_DIR;

console.log(path.join(src, srcDocDir))

fs.removeSync(path.join(dest, destDocDir));
fs.removeSync(path.join(dest, destBlogDir));

fs.copySync(path.join(src, srcDocDir), path.join(dest, destDocDir));
fs.copySync(path.join(src, srcBlogDir), path.join(dest, destBlogDir));
fs.copySync(path.join(src, srcImgDir), path.join(dest, destImgDir));

// "../../../img_src" 改為 "/img_src/"
glob(path.join(dest, '{docs,blog}/**/*.md'), (err, files) => {
if (err) throw err;

files.forEach(file => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) throw err;

// TODO: img-src 改為從 .env 讀取 SOURCE_IMG_DIR
const result = data.replace(/(\.\.\/)+img-src/g, '/img-src/');
fs.writeFile(file, result, 'utf8', (err) => {
if (err) throw err;
});
});
});
});

目前限制

Docusaurus docs 與 blog 之間沒辦法使用 relative path 互相連結,官方說後續會修正