Skip to content

BonhamLab/ObsidianXranklin.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ObsidianXranklin.jl

A Julia package that bridges Obsidian vaults with Xranklin.jl static sites. It converts Obsidian-flavored Markdown — wikilinks, callouts, frontmatter, .base table views — into pages that Xranklin can render and deploy.

Example: BonhamLab/LabWebsite uses this package to publish a subset of its private Obsidian vault as a public lab wiki at lab.bonham.ch.

Features

  • Converts [[wikilinks]] to standard Markdown links, with graph edge tracking
  • Converts > [!TYPE] Title callout blocks to styled HTML
  • Converts YAML frontmatter to Xranklin's TOML format
  • Renders embedded .base file table views (Obsidian Bases) as HTML
  • Copies media assets from the vault to _assets/vault/
  • Generates _assets/graph_data.json for an interactive D3.js knowledge graph
  • Live-reloading watcher for local development via watch_vault

Installation

ObsidianXranklin is not in the General registry. Add it to your Xranklin site's project by URL:

using Pkg
Pkg.add(url="https://github.com/BonhamLab/ObsidianXranklin.jl")

Commit the resulting Manifest.toml so CI can reproduce the exact environment with Pkg.instantiate().

Note: If your site's Julia environment uses SSH remotes (e.g. git@github.com:), Julia's built-in LibGit2 cannot use the system SSH agent. Set ENV["JULIA_PKG_USE_CLI_GIT"] = true (e.g. in your shell profile or startup.jl) to make Pkg use the system git binary instead, which respects your SSH agent and ~/.ssh/config.

For CI, the simplest fix is to ensure the repo-url in Manifest.toml uses an HTTPS URL for any public packages so no authentication is needed at all.

Usage

Vault setup

Your Obsidian vault is typically a git submodule inside the Xranklin site:

my-site/
├── vault/          ← git submodule (your Obsidian vault)
├── _assets/
├── _layout/
├── config.jl
├── make.jl         ← runs sync_vault before build
├── utils.jl        ← imports ObsidianXranklin for live dev
└── Project.toml

To mark a note for publishing, add publish: true to its YAML frontmatter:

---
title: My Protocol
publish: true
tags:
  - protocol
  - wetlab
---

Alternatively, pass publish_folders to auto-publish all notes in specific vault folders without requiring per-note frontmatter.

make.jl — syncing before build

Create a make.jl at your site root that runs before Xranklin builds the site:

using Pkg
Pkg.activate(@__DIR__)

using ObsidianXranklin

ObsidianXranklin.sync_vault("vault", ".";
    output_dir  = "notes",       # published notes appear at /notes/<slug>/
    index_note  = "my-wiki-home", # this note's slug becomes /notes/index.md
)

Run it before serving or building:

julia --project make.jl
julia --project -e 'using Xranklin; serve()'

sync_vault options

Keyword Default Description
output_dir "notes" Site subdirectory for published notes
index_note nothing Slug of the note to copy to <output_dir>/index.md (the section homepage)
publish_folders [] Vault-relative folder prefixes whose notes are auto-published without publish: true
skip_folders nothing Folder prefixes to exclude. Falls back to vault_skip_folders in config.jl, then ["Templates/", "Attachments/"]

utils.jl — live development

Add watch_vault to your site's utils.jl so that vault changes trigger re-syncs while Xranklin's live server is running:

using ObsidianXranklin

# Re-sync whenever a vault file changes (polls every 2 seconds)
watch_vault("vault", ".";
    output_dir = "notes",
    index_note = "my-wiki-home",
)

# Expose the interactive graph view as {{obsidian_graph}} in templates
# (hfun_obsidian_graph is exported by ObsidianXranklin)

The watcher runs as a background Task and is safe to re-evaluate — if utils.jl is re-loaded by Xranklin's live server, the old watcher stops cooperatively before the new one starts.

config.jl — site-wide settings

# Folders to exclude from vault discovery (in addition to the built-in defaults)
vault_skip_folders = ["Templates/", "Attachments/", "Private/"]

# Slug of the note used as the wiki section homepage
obsidian_home = "my-wiki-home"

Knowledge graph

Add a page at e.g. notes/graph.md:

+++
title = "Note Graph"
+++

{{obsidian_graph}}

This renders an interactive D3.js force-directed graph of your published notes and their links, reading from _assets/graph_data.json generated by sync_vault.

.base table views

Obsidian Bases files (.base) embedded in notes with ![[file.base]] are rendered as HTML tables. The table respects filters, order, and sort from the .base YAML config. Supported column properties:

  • file.name — note title, linked to its published URL
  • file.folder — vault-relative folder path
  • file.mtime — last modified time, formatted in local timezone
  • file.tags / tags — rendered as styled pill badges
  • Any frontmatter key (e.g. type, status)

GitHub Actions deployment

Vault as a private submodule

If your vault is a private repository, the default GITHUB_TOKEN cannot clone it. Use a deploy key:

  1. Generate a key pair:
    ssh-keygen -t ed25519 -C "github-actions" -f /tmp/deploy_key -N ""
  2. Add the public key (/tmp/deploy_key.pub) as a read-only deploy key on the vault repo (Settings → Deploy keys).
  3. Add the private key (/tmp/deploy_key) as a repository secret (e.g. VAULT_DEPLOY_KEY) on the site repo.
  4. Pass it to actions/checkout — this makes checkout use SSH for submodule cloning instead of the HTTPS token path, which only covers the current repo:
    - uses: actions/checkout@v4
      with:
        ssh-key: ${{ secrets.VAULT_DEPLOY_KEY }}
        submodules: true
        fetch-depth: 0

Example workflow with kescobo/xranklin-build-action

name: Build and Deploy
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
    types: [opened, synchronize]

env:
  GH_USERNAME: "your-github-username"
  PREVIEWS_PREFIX: "previews/PR"

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    permissions: write-all
    steps:
      - uses: actions/checkout@v4
        with:
          ssh-key: ${{ secrets.VAULT_DEPLOY_KEY }}  # omit if vault is public
          submodules: true
          fetch-depth: 0

      - name: Set preview path for PRs
        run: |
          if ${{ github.event_name == 'pull_request' }}
          then
            echo 'PRID=${{ env.PREVIEWS_PREFIX }}${{ github.event.number }}' >> $GITHUB_ENV
          else
            echo 'PRID=' >> $GITHUB_ENV
          fi
        shell: bash

      - name: Build and Deploy
        uses: kescobo/xranklin-build-action@main
        with:
          SITE_FOLDER: "./"
          PREVIEW: ${{ env.PRID }}

      - name: Post preview URL on PRs
        uses: thollander/actions-comment-pull-request@v2
        with:
          message: |
            Preview available at https://your-domain.com/${{ env.PRID }}
        if: github.event_name == 'pull_request'

Note on vault syncing in CI: kescobo/xranklin-build-action runs the Xranklin build, which loads your site's utils.jl. If your utils.jl calls watch_vault, the vault sync runs automatically as part of the build — no separate step needed. If you use make.jl instead, add an explicit step before the build action to run it.

Publish rules

A note is published if (in priority order):

  1. publish: false in frontmatter → never published
  2. publish: true in frontmatter → always published
  3. Note path matches a prefix in publish_folders → published
  4. Otherwise → not published

Unpublished notes still appear in .base table views (so you can list all notes including private ones in a database view), but they are not written to the site and their wikilinks resolve to #wikilink-missing.

Frontmatter handling

YAML frontmatter is converted to Xranklin's TOML format. Obsidian-internal keys (cssclasses, aliases, position, file, publish) are stripped. Dates are parsed and emitted as Date(year, month, day) with a using Dates import prepended automatically.

Known issues

  • Julia 1.12 world-age warning: Tag page generation in Xranklin triggers a world-age warning (Detected access to binding in a world prior to its definition world) due to a Xranklin bug. As a workaround, avoid calling custom hfun_* functions from _layout/tag.html — use static HTML for the nav in that layout instead.
  • Symlinks in vault: If your vault contains symlinks to files outside the vault directory (e.g. from a Zotero integration), those assets are silently skipped during copying rather than causing an error.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors