这几天捣鼓博客的时候发现 Quartz 不原生支持 Excalidraw,一个解决方法是用 Obsidian 的 Quartz Syncer 插件,不过我尝试了一下之后发现这个插件不支持先同步到本地 git 仓库再推送到远程,而是直接用 Github token 来做同步,笔记多了之后速度很慢。
思来想去还是直接写两个脚本最简单,于是就有了这篇博客,供参考。
同步到本地仓库
因为后面还有可能会调一调 quartz.config.ts 或者 quartz.layout.ts 之类的,所以我还是在本地保留了一个仓库。因此每次同步时要先同步到本地仓库再推送。
首先写一个 sync.sh
#!/bin/bash
OBSIDIAN_PATH="/mnt/e/Obsidian Vault/Obsidian Vault"
QUARTZ_CONTENT_PATH="./content"
echo "Strat to sync..." >&2
mkdir -p "$QUARTZ_CONTENT_PATH"
rsync -av --delete \
--exclude='.git' \
--exclude='.obsidian' \
--exclude='.trash' \
--exclude='Scripts/' \
--exclude='*.canvas' \
"$OBSIDIAN_PATH/" "$QUARTZ_CONTENT_PATH"
echo "Sync finished!
" >&2
echo "Differences:" >&2
git diff --name-only >&2为了能直接在 Obsidian 界面内同步,我安装了 Shell Command 插件,这个插件可以让你在 Obsidian 内运行自定义的命令。(这里我将 stdout 的输出忽略了,因此提示信息我都重定向到了 stderr)
推送到远程仓库
再写一个 deploy.sh
#!/bin/bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
echo "Processing Excalidraw replacement..." >&2
python3 replace_excalidraw.py >&2
echo "Bulding and pushing to Github..." >&2
npx quartz sync
echo "Successfully deployed!" >&2直接调用 npx quartz sync 就行了,会自动推送到远程仓库。这里记得设置一下 NVM 的环境变量,不然不能直接在 Shell Command 中运行。
替换 Excalidraw 绘图文件为 SVG
这里的 replace_excalidraw.py 是为了将笔记中的 Excalidraw 绘图文件的引用转为显示 SVG 的 HTML,Quartz 可以直接支持笔记中对图片的引用,但是因为我想支持博客中深色/浅色主题下显示不同的图片,所以这里直接转成了 HTML:
import os
import re
# 配置 Quartz 的内容目录
CONTENT_DIR = "/home/evan/WORK/quartz/content"
def process_excalidraw_images(content_dir):
pattern = re.compile(r'!\[\[(.*?)(\.excalidraw)(.*?)\]\]')
for root, dirs, files in os.walk(content_dir):
for file in files:
if file.endswith(".md"):
file_path = os.path.join(root, file)
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 检查是否存在 excalidraw 引用
if '.excalidraw' in content:
# 替换逻辑
def replace_func(match):
base_name = match.group(1) # e.g., "attachments/drawing"
ext = match.group(2) # ".excalidraw"
alias = match.group(3) # e.g., "|500" or ""
# 构建导出图片的文件名 (需与你的 Excalidraw 插件设置一致)
# 注意:Quartz 处理图片链接通常需要 URL 编码或相对路径,
# 这里假设图片和笔记在同一层级或 Quartz 能自动解析 filename
# 构造 HTML 块
# class="image-switch" 用于 CSS 控制
# 注意处理 alias (如调整宽度)
style = ""
if "|" in alias:
width = alias.split("|")[-1]
if width.isdigit():
style = f'style="width:{width}px"'
# 生成 HTML
# 注意:Quartz 4 通常能解析 Markdown 中的 HTML <img> 标签
html_block = (
f'<div class="image-switch" {style}>'
f'<img class="light-img" src="{base_name}.excalidraw.light.svg" alt="{base_name}" />'
f'<img class="dark-img" src="{base_name}.excalidraw.dark.svg" alt="{base_name}" />'
f'</div>'
)
return html_block
new_content = pattern.sub(replace_func, content)
# 写回文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"Processed Excalidraw in: {file}")
if __name__ == "__main__":
process_excalidraw_images(CONTENT_DIR)(看到这么详细的注释就知道是 AI 写的了)
接着记得在 Obsidian 的 Excalidraw 插件中的嵌入到 Markdow 文档中的绘图 - 导出 - 导出设置中开启自动导出 SVG 副本和同时导出深色和浅色主题图片。
最后在 Obsidian 中设置好调用两个脚本的命令和快捷键就可以使用了,平时在 Obsidian 中正常用 Excalidraw 绘图,推送到仓库时会自动将对 .excalidraw.md 后缀文件的引用替换为显示对应 SVG 的 HTML,并且支持随博客显示的深色/浅色主题更换绘图的深色/浅色主题。