问题

我有一个obsidian仓库,私有的,因为里面有一些笔记涉及公司和个人隐私。但其余部分都是可公开的,有没有一个方案同时满足,

  • 免费
  • 可以控制哪些发布,哪些不发布
  • 原始仓库是私有的

方案

工具名称优点缺点
obsidian-digital-garden1. 一键部署到Vercel/Netlify
2. 通过Frontmatter控制单笔记发布
3. 无需维护GitHub CI
1. 国内访问慢(Vercel已无法访问)
2. 导出网站存在异常,未深究
3. 部署GitHub Pages困难且定制性低
obsidian-webpage-export1. 支持仓库整体导出为HTML
2. 可部署到GitHub Pages
1. 开发中功能不完善
2. 部分笔记导出报错且提示不清晰
3. 不支持单笔记发布控制
quartz1. 支持Glob匹配发布控制
2. 案例丰富参考性强
3. 文档齐全
4. UI高度可定制
1. 不支持Dataview语法
2. 不兼容Obsidian的Base文件

最后选用了quartz. 看看这个网站有多美!

Quartz需要一些内部文件来完成文件转网页的工作,通常是一些js、ts、json文件。这样以来便有两种选择,

  1. 将quartz集成到obsidian仓库(私有)中,将转换好的文件publish到另一个仓库(公开)
  2. 新建一个公开仓库放quartz,然后将obsidian仓库作为submodule引入,直接在本仓库执行构建,发布

这里我选用方案2,因为方案1会污染obsidian仓库,而它本应聚焦于内容创作!而这个新建的仓库就是guyueshui/quartz,参考其中的action文件

# Copied from jackyzha0/quartz's ci.yaml
name: Build vault
 
on:
  pull_request:
    branches:
      - master
  push:
    branches:
      - master
    paths:
      - '.gitmodules'
      - 'learn2live/**'
  # receive event of submodule update
  repository_dispatch:
    types: [submodule_updated]
  workflow_dispatch:
 
jobs:
  build-and-test:
    strategy:
      matrix:
        os: [ubuntu-latest]
    runs-on: ${{ matrix.os }}
    permissions:
      contents: write
      pages: write
      id-token: write
 
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
          token: ${{ secrets.VAULT_PAT }}
          submodules: 'recursive'
 
      - name: Update submodule to latest
        run: |
          git config --global user.email "actions@github.com"
          git config --global user.name "GitHub Actions"
          git submodule foreach --recursive 'git checkout main || git checkout master'
          git submodule update --remote --merge
          git add .
          git commit -m "chore: sync submodules to HEAD" || exit 0
          git push
 
      - name: Setup Node
        uses: actions/setup-node@v5
        with:
          node-version: 22
 
      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
 
      - run: npm ci
 
      - name: Test
        run: npm test
 
      - name: Ensure Quartz builds, check bundle info
        run: npx quartz build --bundleInfo -d learn2live
 
      - name: Setup GitHub Pages
        uses: actions/configure-pages@v4
 
      - name: Upload GitHub Pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./public
          retention-days: 1
 
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

而obsidian仓库(私有)仅作为submodule提供内容,同时也需要配置一个action,在仓库更新后,触发quartz仓库的构建。Actions文件如下,

name: Trigger deployment of obsidian vault
# see: https://github.com/guyueshui/quartz
 
on:
  pull_request:
    branches:
      - master
  push:
    branches:
      - master
  # 允许手动触发
  workflow_dispatch:
 
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Post "submodule_updated" event
        # 向quartz仓库推送内容更新的事件,对应quartz仓库ci中的repository_dispatch
        run: |
          curl -X POST \
            -H "Authorization: token ${{ secrets.VAULT_PAT }}" \
            -H "Accept: application/vnd.github.everest-preview+json" \
            https://api.github.com/repos/guyueshui/quartz/dispatches \
            -d '{"event_type":"submodule_updated"}'
      - run: |
          echo "Onwer: ${{ github.repository_owner }}"
          echo "Repo:  ${{ github.event.repository.name }}"
  

另外配置还有几点要注意:

  • 可以创建一个PAT(Personal Access Token)同时具备这两个仓库的读写权限,实际上obsidna仓库只需要读
  • PAT创建入口:https://github.com/settings/personal-access-tokens
  • PAT权限注意勾选workflow,因为要跨仓库触发quartz仓库的workflow
  • quartz仓库设置中的Actions > Workflow permissions更改为”Read and write permissions”
  • secrets.VAULT_PAT对应仓库中配置的Settings > Secrets and variables > Actions > Repository secrets

此时,就可以做到更新obsidian仓库,触发quartz仓库的构建操作,进而更新发布出去的网站。

Quartz定制

私有笔记不发布

参考:https://quartz.jzhao.xyz/features/private-pages

例如我的忽略配置如下,

ignorePatterns: [
  "private", "templates", ".obsidian",
  "__obex", ".+", "work",
  "life/亲子",
  "**/202305302345.md",
  "**/2022-07-22.md",
  "**/2022-09-06.md",
  // "assets",
],

asset必须要发布,不然笔记中的图片就找不到了。

Accent color

直接修改quartz.config.ts中的config.theme.colors字段。改颜色是个审美的活,简单点就好。

Explorer过滤assets文件夹

参考:https://quartz.jzhao.xyz/features/explorer

diff --git a/quartz.layout.ts b/quartz.layout.ts
index bd16975..042210d 100644
--- a/quartz.layout.ts
+++ b/quartz.layout.ts
@@ -1,5 +1,27 @@
 import { PageLayout, SharedLayout } from "./quartz/cfg"
 import * as Component from "./quartz/components"
+import { Options as ExplOptions } from "./quartz/components/Explorer"
+
+// add explorer customizations, cf. https://quartz.jzhao.xyz/features/explorer
+export const explMapFn : ExplOptions["mapFn"] =
+(node) => {
+  if (node.isFolder) {
+    node.displayName = "📁 " + node.displayName
+  } else {
+    node.displayName = "📄 " + node.displayName
+  }
+}
+
+export const explFilterFn : ExplOptions["filterFn"] =
+(node) => {
+  // set containing names of everything you want to filter out
+    const omit = new Set(["assets", "tags", "advanced"])
+ 
+    // can also use node.slug or by anything on node.data
+    // note that node.data is only present for files that exist on disk
+    // (e.g. implicit folder nodes that have no associated index.md)
+    return !omit.has(node.displayName.toLowerCase())
+}
 
 // components shared across all pages
 export const sharedPageComponents: SharedLayout = {
@@ -38,7 +60,7 @@ export const defaultContentPageLayout: PageLayout = {
         { Component: Component.ReaderMode() },
       ],
     }),
-    Component.Explorer(),
+    Component.Explorer({'mapFn': explMapFn, 'filterFn': explFilterFn}),
   ],
   right: [
     Component.Graph(),
@@ -62,7 +84,7 @@ export const defaultListPageLayout: PageLayout = {
         { Component: Component.Darkmode() },
       ],
     }),
-    Component.Explorer(),
+    Component.Explorer({'mapFn': explMapFn, 'filterFn': explFilterFn}),
   ],
   right: [],
 }

Explorer显示文件名而非title

diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx
index 7bf2382..5652b1a 100644
--- a/quartz/components/PageList.tsx
+++ b/quartz/components/PageList.tsx
@@ -67,7 +67,15 @@ export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort
   return (
     <ul class="section-ul">
       {list.map((page) => {
-        const title = page.frontmatter?.title
+        // use filename instead of page title, yychi@2025/10/04
+        const slugParts = page.slug!.split("/")
+        let title = null
+        for (let i = slugParts.length - 1; i >= 0; --i) {
+          if (slugParts[i] != "index") {
+            title = slugParts[i]
+            break
+          }
+        }
         const tags = page.frontmatter?.tags ?? []
 
         return (
diff --git a/quartz/util/fileTrie.ts b/quartz/util/fileTrie.ts
index 9e6706f..c36eb98 100644
--- a/quartz/util/fileTrie.ts
+++ b/quartz/util/fileTrie.ts
@@ -28,9 +28,9 @@ export class FileTrieNode<T extends FileTrieData = ContentDetails> {
   }
 
   get displayName(): string {
-    const nonIndexTitle = this.data?.title === "index" ? undefined : this.data?.title
+    // use filename instead of page title, yychi@2025/10/04
     return (
-      this.displayNameOverride ?? nonIndexTitle ?? this.fileSegmentHint ?? this.slugSegment ?? ""
+      this.displayNameOverride ?? this.fileSegmentHint ?? this.slugSegment ?? ""
     )
   }

小结

可见,quartz的定制还是非常方便的。