Skip to content

sspaeti/second-brain-public

Repository files navigation

My Public Second Brain

See on ssp.sh/brain.

This is a fork of the Quartz repo (v3 with Hugo). I added some additional features such as:

  • Tagging with #publish automatically copies the note from my private second brain in Obsidian to this public second brain
  • Converts the first header (# my title) into frontmatter and removes it (as Quartz expects)
  • Smart description extraction from first paragraph with automatic cleaning:
    • Removes wikilinks, markdown formatting, list markers
    • Intelligent sentence truncation for complete thoughts
    • Displays on OG images as text overlay (Kanagawa color scheme)
    • Used in meta tags for SEO and social media previews
  • BASE file support for publishing Obsidian database views:
    • Publishes Database Folder plugin .base files as standalone Hugo pages
    • Generates HTML tables from database entries with wikilinks
    • Supports folder filters and exclusion patterns
    • Recursive subdirectory scanning
    • Examples: Coffee Beans, Books
  • YouTube links in Obsidian image syntax (![title](https://youtube.com/watch?v=XXX)) render as embedded video players instead of broken images
  • Callout blocks are normalized so compact and spaced forms render identically
  • Mermaid → OG image: set ogimage: mermaid (or mermaid2, mermaid3, …) in a note's frontmatter to render the Nth ```mermaid block as the social-media preview image (rendered via mmdc + ImageMagick to a 1200×630 WebP using a dark theme that matches the site's OG template)

The content/notes themselves are not published in this repo, only on ssp.sh/brain.

Explore with RAG → explore.ssp.sh

Semantic search, hidden connections, and graph traversal powered by obsidian-note-taking-assistant.

Utils

obsidian-quartz: Content processing

Rust CLI tool that processes Obsidian vault notes and outputs Hugo-compatible markdown. Handles frontmatter, tags, images, OG image generation, callout normalization, BASE database views, and more.

Key features:

  • Markdown publishing: Processes notes tagged with #publish
  • BASE database views: Publishes Obsidian Database Folder plugin .base files as HTML tables
  • Filter expressions: Supports folder filters and exclusion patterns (!file.path.contains)
  • Smart descriptions: Auto-extracts clean descriptions from first paragraph
  • OG image generation: Creates social media preview images with SVG→WebP conversion
  • Mermaid OG images: Renders a note's Mermaid diagram as its OG image via ogimage: mermaid / mermaid<N> (uses mmdc + ImageMagick, dark theme matches the site's OG template)

See utils/obsidian-quartz/README.md for details.

hugo-obsidian: Backlink and graph creation

The tool used is hugo-obsidian, a small Go program written by Jacky. Here's the source. It is not maintained anymore (as there is now a v4 without it) and it had bugs and didn't show all my backlinks. That's why I forked it and fixed the backlinks. You can find it here: sspaeti/hugo-obsidian.

It scans the content/ folder for wikilinks and emits two artifacts Hugo consumes to render the interactive graph and per-note backlink lists:

  • assets/indices/linkIndex.json — every [[wikilink]] as a source → target edge, lowercased and de-duplicated (powers the graph and "Links to this note" sections)
  • assets/indices/contentIndex.json — slug → title/content map used for search and link previews

Key fork additions over upstream: case-insensitive link matching, block-reference (^hash) handling, slash-in-title normalization, and performance tuning for large vaults.

Note

sspaeti/hugo-obsidian has been integrated directly in this repository at utils/hugo-obsidian. See utils/hugo-obsidian/README.md for installation, CLI flags, and the full changelog.

Hugo render hooks

Custom render hooks in layouts/_default/_markup/:

  • render-image.html - Detects YouTube URLs and renders responsive iframe embeds (with timestamp support); all other images pass through normally

Configs

Redirects of renamed files

Find these in .htaccess

ChangeLog

2026-06-03: Hover popover previews + scoped CORS for cross-site embedding

A fetch-based hover popover replaces the legacy popover.js. Hovering any internal link opens a scrollable preview of the destination note (title, meta line including reading time, full content) rendered from the actual brain HTML — no precomputed JSON index, so notes always show current content. The same JS/CSS is consumed by the blog and the DEDP book via build-time copy, giving readers on those sites the same brain-link hover experience.

  • Popover implementation (assets/js/popover-v2.js, assets/css/popover-v2.css):
    • Selector parametrized via window.initPopoverV2({selector: "..."}). Brain uses the default a.internal-link[href]; blog/book pass a[href*="ssp.sh/brain"].
    • Branches on Content-Type: HTML extracts elements with class popover-hint (title, meta, content body — marked in single.html / textprocessing.html so site chrome, TOC, tags, footer, graph, backlinks are excluded); image responses render <img>; PDF responses embed <iframe>.
    • Anchor scroll: links to #heading scroll the inner div to the prefixed #popover-internal-<heading>.
    • Per-pathname cache; outer 1rem padding + :hover keeps the popover open while the cursor crosses from the link to the popover for scrolling.
    • Behind feature flag enableLinkPreviewV2 in data/config.yaml. The v1 popover code path remains available for rollback.
    • Light + dark mode rules cover brain ([saved-theme="dark"]), blog (uBlogger body[theme="dark"]), and all 9 book themes (html.ayu, html.burgundy, html.coal, html.kanagawa, html.light, html.navy, html.pinkrose, html.rust, html.tokyonight) — book uses var(--bg) / var(--fg) / var(--links) / var(--inline-code-color) so the popover adapts to whichever theme the reader picks.
  • CORS (static/.htaccess): SetEnvIf Origin "^https://(www\.)?(ssp\.sh|dedp\.online)$" + Header always set Access-Control-Allow-Origin + Header always merge Vary "Origin". The always keyword is required so headers apply to 3xx canonical-host redirects (otherwise the browser blocks the redirect before the final 200). After deploy, the Bunny brain pull-zone cache must be purged once to evict pre-CORS entries.
  • Image wikilinks (layouts/partials/textprocessing.html, utils/obsidian-quartz/src/file_utils.rs): [[image.ext]] (without !) now renders as a working <a href> (was a broken <a> with no href). The rust util's regex relaxed from \[\[...\]\] requiring ! to !?\[\[...\]\], so the asset gets copied to public output regardless of which form is used. Combined with the popover's image branch, hovering an [[img.webp]] link shows the image directly in the popover.
  • Reading time: layouts/_default/single.html now renders {{ .ReadingTime }} min read in the meta line for both the page and the popover preview.
  • Files:
    • assets/js/popover-v2.js, assets/css/popover-v2.css — canonical popover (copied verbatim to blog and book by their sync-popover Makefile targets, alongside floating-ui.core.umd.min.js and floating-ui.dom.umd.min.js)
    • layouts/partials/head.html — flag-gated v1/v2 toggle, loads CSS + JS, calls init
    • layouts/partials/textprocessing.html, layouts/_default/single.htmlpopover-hint markers on title/meta/content; non-! image wikilink handling; reading time in meta
    • static/.htaccess — scoped CORS for (www.)?(ssp.sh|dedp.online) with Header always
    • utils/obsidian-quartz/src/file_utils.rs — image-copy regex catches non-! wikilinks
    • data/config.yamlenableLinkPreviewV2: true

2026-06-01: Interactive graph shows blog posts and book chapters

The local graph on every brain note now surfaces connections to two sister sites — the blog and the DEDP book — alongside brain↔brain wikilinks. This makes the second brain a true hub: every note shows what posts cite it and which book chapters reference it.

  • Node types (legend shown on every local graph that has connections):
    • Current (rose) — the page you're on
    • Note (orange) — brain note linked via wikilink
    • Blog (#60a5fa blue) — blog post at ssp.sh/blog/<slug>
    • Book (#a78bfa purple) — DEDP book chapter at dedp.online/<path>.html
    • ··· Outgoing — dotted line marks brain→external edges (e.g., the brain note has [text](https://ssp.sh/blog/X)). Incoming edges from blog/book stay solid.
  • Click behavior: blog and book nodes open in a new tab, brain nodes use the existing SPA navigation. Search (Ctrl+K) excludes blog and book entries — they live in the graph only.
  • Edge direction styling: outgoing brain→external is dotted, incoming external→brain is solid, brain↔brain is solid.
  • Data flow:
    • Blog repo's helper-scripts/enrich-link-index.py already populates static/indices/linkIndex.json with brain↔blog edges (both directions).
    • DEDP book's utils/dedp-link-index Rust CLI generates linkIndex.js (window.DEDP_LINK_INDEX = {...}) with chapter→brain edges (type: "brain", target brain:<slug>).
    • Two new obsidian-quartz subcommands import these into the brain's indices: enrich-with-blog and enrich-with-book.
  • Frontmatter URL resolution for blog: blog posts use url: /blog/X frontmatter overrides — the script parses each index.en.md / index.md and extracts the actual URL instead of guessing from the folder name.
  • Idempotency: re-running make prepare adds 0 new edges if nothing has changed; nodes with the same slug as a brain note are never overwritten (Map.entry().or_insert_with semantics).
  • Files:
    • utils/obsidian-quartz/src/enrich_with_blog.rs, enrich_with_book.rs — the importers
    • assets/js/graph.js — click routing, dasharray on outgoing edges, conditional legend
    • assets/js/full-text-search.js — skip /blog/ and /book/ keys
    • data/graphConfig.yaml — node colors via paths:
    • Makefile — chains make -C ../sspaeti-hugo-blog prepare and make -C ../../book/dedp link-index before each enrich step (leading - so a missing sister repo is non-fatal)

2026-05-24: Mermaid diagrams as OG images

  • Mermaid OG rendering (utils/obsidian-quartz/src/svg_generator.rs, file_utils.rs):
    • Set ogimage: mermaid in note frontmatter to use the first ```mermaid block as the social-media preview image. Use mermaid2, mermaid3, … to target later blocks.
    • Pipeline: extract block → render via mmdc (dark theme, #1F1F28 background matching the OG template, 3× puppeteer scale for crisp anti-aliasing) → composite onto a 1200×630 canvas with 50px padding via ImageMagick (Lanczos filter, WebP quality 92).
    • Output written to content/_img/feature/mermaid/<slug>.webp and the frontmatter ogimage value is rewritten to the resolved path so the existing head.html resolver works unchanged.
    • On failure (block missing, mmdc errors) the ogimage key is dropped so the title-based generator takes over as a fallback.
    • Existing-file cache: re-renders only when the target WebP doesn't already exist (delete it to force a refresh).
  • Hugo template (layouts/partials/head.html):
    • Added /mermaid/ to the summary_large_image Twitter-card detection (alongside the existing /gen/ rule).
  • External dependencies: @mermaid-js/mermaid-cli (mmdc, Node + Chromium); ImageMagick (magick).

2026-04-17: Obsidian BASE file support for database views

  • BASE File Publishing (utils/obsidian-quartz/src/base_*.rs):
    • Added support for Obsidian Database Folder plugin .base files
    • Parses BASE YAML files with filters, views, properties, and formulas
    • Implements temporary staging workflow to avoid private vault scanning:
      • Copies source files to /content/BASES/<base-name>/ during processing
      • Queries from staging folder to build tables
      • Cleans up staging folder after generation
    • Filter Support:
      • Folder filters: file.path.contains("path")
      • Extension filters: file.ext.contains("md")
      • Exclusion patterns: !file.path.contains("path") to skip folders
      • Recursive subdirectory scanning
    • Table Generation:
      • Renders HTML tables with proper styling (base-table-container, base-table)
      • Generates wikilinks ([[Name]]) that resolve to published content
      • Supports multiple columns with custom properties
      • Includes description/intro content before tables
    • Frontmatter:
      • Sets enableToc: false, enableBacklinks: false, enableGraph: false
      • Auto-generates title from BASE filename
    • Examples: Coffee Beans (46 entries), Books (184 entries, excluding 1256 Study books)
  • Code Structure:
    • base_parser.rs: Parse BASE YAML into Rust structs (serde)
    • base_query.rs: Query notes with filter expressions, extract folder/extension/exclusion patterns
    • base_renderer.rs: Render notes as HTML tables with formatted values (ratings, prices)
    • file_utils.rs: Orchestrate BASE processing workflow with staging and cleanup

2026-04-17: Smart description extraction and OG image enhancement

  • Description Extraction (utils/obsidian-quartz/src/file_utils.rs):
    • Automatically extracts clean descriptions from first paragraph after frontmatter
    • Removes wikilinks ([[Link]]Link), markdown links ([Text](URL)Text)
    • Strips formatting, list markers, blockquotes
    • Smart sentence truncation (prefers complete sentences, max 180 chars)
    • Stores as quoted description: "..." in frontmatter for proper YAML syntax
    • Manual override via desc: frontmatter field
  • OG Image Enhancement (utils/obsidian-quartz/src/svg_generator.rs):
    • Added description text overlay (24px, Kanagawa oldWhite #C8C093)
    • Reduced title font sizes (44-60px) to make room for description
    • Optimized text wrapping (title: 25 chars/line, description: 60 chars/line)
    • Removed fixed accent line for cleaner layout
  • Hugo Template Updates (layouts/partials/head.html):
    • Created $cleanDescription variable with priority: manual → auto-extracted → .Summary
    • Applied wikilink/markdown cleaning to all meta tags
    • Updated og:description, twitter:description, and JSON-LD schema

2025-10-16: Fix cross-section navigation (brain ↔ blog)

  • Modified assets/js/router.js to intercept clicks between /brain/ and other sections, forcing full page loads instead of SPA navigation
  • Prevents "null" page errors when navigating from brain to main blog
  • Compatible with assets/js/external-links.js which handles truly external links

Inconsistencies (later TODO's)

Slug rules — 5 spots, only 1 canonical

Brain note no meetings (async).md → URL slug /no-meetings-async. Five files do slug work. Two can disagree. One is source of truth.

Canonical: utils/hugo-obsidian/util.go::UnicodeSanitize. Strips (), &, @, , ', etc. Collapses -/whitespace runs to one -.

# File Lang Role
1 utils/hugo-obsidian/util.go::UnicodeSanitize Go Canonical. Brain + blog hugo-obsidian use it.
2 utils/obsidian-quartz/src/enrich_with_blog.rs::unicode_sanitize Rust Mirror of #1. Tight loop, no shell-out.
3 utils/obsidian-quartz/src/file_utils.rs:826 Rust Filename lowercase only. #1 slugifies after.
4 sspaeti-hugo-blog/helper-scripts/enrich-link-index.py:41 Python Divergent. Only .lower().replace(" ", "-"). Keeps parens. Source of paren-drop bugs.
5 utils/obsidian-quartz/src/file_utils.rs:925 Rust BASE-page filename. Same as #3.

Real disagreement: #1 vs #4. #2 compensates by re-canonicalising before matching brain IDs. #3 + #5 not slug generators — feed into #1.

Future cleanup: add sluggify subcommand to hugo-obsidian (Go). Blog Python shell out. Collapses #4 into #1. Kills need for #2.

Pending symmetric bug: utils/obsidian-quartz/src/enrich_with_book.rs:154. Same brain_ids.contains() pattern as fixed blog enricher. Same silent-drop on paren'd notes until same fix applied.

In-code pointers exist between #1 and #2 (MIRRORED IN / KEEP IN SYNC WITH).

About

My Public Second Brain Framework and Style (Quartz V3-Hugo)

Resources

License

Code of conduct

Stars

Watchers

Forks

Sponsor this project

 

Contributors

Generated from jackyzha0/quartz