When I GM an RPG, I accumulate a wiki full of notes about characters, locations, worldbuilding, and my future plans for them. I realized that wiki (minus the “future plans”) would be a useful resource for other players at the table. And given that I keep my notes in Markdown in Obsidian, how hard could it be to make them available as a website? Turns out to be not that bad at all
I have a private Github repo where everything takes place. There's a tiny shell script in there that pulls in the latest markdown files:
#!/bin/sh
rm -r src/
cp -r ~/wiki/campaign-notes/armour-astir/ ./src/
and a much beefier Typescript file that processes these Markdown files, which:
- Takes Obsidian's non-standard [[target|Description]] link syntax and make it the standard [Description](target)
- Hides everything below the first horizontal rule (
--- in Markdown), to allow me to have secret and non-secret content close together
- Converts the Markdown to HTML
- Spits out that HTML (with appropriate links to styling etc) to disk
Then I push it up to Github, where a Github Action builds the site automatically and pushes it to a specific branch, which I have a Netlify site configured to watch.
All in all: I write my notes like I normally would, make sure to redact any secret information, and sync them into a git repo. One Rube Goldberg machine later, the players have a quick reference that includes what happened in each session, our collaborative world-building history, notable NPCs, etc.
If I ever write up part 2 I’ll talk about the dynamic star system map I coded up; that one is entirely self-indulgent lol
The typescript file is as follows (this is a total mess):
import fs from "fs";
import child_process from "child_process";
import jsdom from "jsdom";
// Run the obsidian-export tool over my notes
fs.mkdirSync("./intermediate", { recursive: true });
child_process.spawnSync("obsidian-export", ["./src", "./intermediate"]);
const files = fs.readdirSync("./intermediate");
while (files.length > 0) {
const file = files.pop();
if (file == null) {
break;
}
const pathToFile = `./intermediate/${file}`;
const fileStats = fs.lstatSync(pathToFile);
// Recursively traverse the directory structure
if (fileStats.isDirectory()) {
fs.mkdirSync(`./site/${file}`, { recursive: true });
const children = fs.readdirSync(pathToFile);
files.push(...children.map((child) => `${file}/${child}`));
continue;
}
const contents = fs.readFileSync(pathToFile, { encoding: "utf8" });
// Strip secret notes
const [censoredContents] = contents.split("---");
// Remove incorrect file extensions from link targets
const input = censoredContents.replace(/.md\)/g, ")").replace(/.md#/g, "#");
const process = child_process.spawnSync(
"pulldown-cmark",
["--enable-heading-attributes"],
{ input }
);
const [name] = file.split(".");
const [firstLine] = contents.split("\n");
const title = firstLine.startsWith("# ")
? `Codex: ${firstLine.substring(2)}`
: "Armour Astir";
const html = `<html><head><title>${title}</title><meta name="viewport" content="width=device-width, initial-scale=1" /><link rel="stylesheet" href="/style.css"></head><body><a href="/">To Index</a>${process.stdout.toString()}</body></html>`;
// Add appropriate anchor IDs to link to the middle of documents
const { document } = new jsdom.JSDOM(html).window;
document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((node) => {
const innerText = node.textContent ?? "";
if (innerText) {
node.id = innerText.toLowerCase().replace(/ /g, "-");
}
});
fs.writeFileSync(`./site/${name}.html`, document.documentElement.innerHTML);
}
The Github action (no idea why this is running on macOS and not Linux?)
on:
push:
branches:
- main
jobs:
build:
runs-on: macOS-latest
steps:
- uses: actions/checkout@master
- name: Install obsidian-export
uses: baptiste0928/cargo-install@v1
with:
crate: obsidian-export
- name: Install pulldown-cmark
uses: baptiste0928/cargo-install@v1
with:
crate: pulldown-cmark
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- name: Run obsidian-export
run: mkdir ./intermediate && obsidian-export ./src ./intermediate
- name: Build HTML files
run: npm ci && npm run build
- name: Push
run: git checkout -b pub && git add . && git commit -am "Build" && git push -fu origin pub