mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
1099 lines
53 KiB
Markdown
1099 lines
53 KiB
Markdown
# Quartz v5 Architecture Overview
|
|
|
|
This document provides a bird's-eye view of the Quartz v5 rework for maintainer review. It covers the plugin-based architecture, build pipeline, page type system, layout engine, and the full catalog of community plugins.
|
|
|
|
## Table of Contents
|
|
|
|
- [High-Level Architecture](#high-level-architecture)
|
|
- [Build Pipeline](#build-pipeline)
|
|
- [Plugin System](#plugin-system)
|
|
- [Plugin Categories](#plugin-categories)
|
|
- [Plugin Lifecycle](#plugin-lifecycle)
|
|
- [Plugin Manifest](#plugin-manifest)
|
|
- [Plugin Template Structure](#plugin-template-structure)
|
|
- [PageType System](#pagetype-system)
|
|
- [Matchers](#matchers)
|
|
- [Virtual Pages](#virtual-pages)
|
|
- [PageTypeDispatcher](#pagetypedispatcher)
|
|
- [Layout System](#layout-system)
|
|
- [Layout Positions](#layout-positions)
|
|
- [Priority and Ordering](#priority-and-ordering)
|
|
- [Groups (Flex Containers)](#groups-flex-containers)
|
|
- [Display Modifiers](#display-modifiers)
|
|
- [Conditions](#conditions)
|
|
- [Per-Page-Type Overrides](#per-page-type-overrides)
|
|
- [Component Architecture](#component-architecture)
|
|
- [QuartzComponent](#quartzcomponent)
|
|
- [Component Registry](#component-registry)
|
|
- [Page Rendering](#page-rendering)
|
|
- [Page Frames](#page-frames)
|
|
- [Transclusion](#transclusion)
|
|
- [Configuration](#configuration)
|
|
- [quartz.config.yaml](#quartzconfigyaml)
|
|
- [TypeScript Override](#typescript-override)
|
|
- [Plugin Catalog](#plugin-catalog)
|
|
- [Transformers](#transformers)
|
|
- [Filters](#filters)
|
|
- [Emitters](#emitters)
|
|
- [PageType Plugins](#pagetype-plugins)
|
|
- [Component Plugins](#component-plugins)
|
|
|
|
---
|
|
|
|
## High-Level Architecture
|
|
|
|
Quartz v5 is a fully plugin-based static site generator for digital gardens. Every functional unit — content transformation, filtering, page generation, and UI rendering — is implemented as an external community plugin loaded from Git repositories.
|
|
|
|
```
|
|
quartz.config.yaml ← Single source of truth for all configuration
|
|
│
|
|
▼
|
|
┌─────────────┐
|
|
│ Config Loader│ Reads YAML, installs plugins from Git, validates
|
|
│ (loader/) │ dependencies, categorizes, sorts, instantiates
|
|
└──────┬──────┘
|
|
│
|
|
▼
|
|
┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐
|
|
│ Transformers│────▶│ Filters │────▶│ Emitters │────▶│ Output (HTML)│
|
|
│ (parse) │ │ (filter) │ │ (emit) │ │ CSS, JS, etc│
|
|
└─────────────┘ └──────────┘ └──────────┘ └──────────────┘
|
|
│
|
|
┌──────┴──────┐
|
|
│ PageType │
|
|
│ Dispatcher │
|
|
│ (phase 1) │
|
|
└─────────────┘
|
|
```
|
|
|
|
**Key architectural decisions:**
|
|
|
|
1. **Everything is a plugin.** There is no monolithic core. The framework provides the pipeline, loader, and component infrastructure; all domain logic lives in plugins.
|
|
2. **Git-native plugin distribution.** Plugins are cloned from GitHub repositories into `.quartz/plugins/` via `isomorphic-git`. No npm registry required.
|
|
3. **Declarative configuration.** A single `quartz.config.yaml` declares all plugins, their options, ordering, and layout positions. TypeScript overrides are optional.
|
|
4. **PageType system.** A new abstraction that unifies content rendering, virtual page generation, and layout binding into a single plugin type.
|
|
5. **Two-phase emit.** The PageTypeDispatcher runs first (generating virtual pages), then all other emitters receive content + virtual pages together.
|
|
|
|
---
|
|
|
|
## Build Pipeline
|
|
|
|
The build pipeline lives in `quartz/build.ts` and the `quartz/processors/` directory. It follows a linear flow:
|
|
|
|
### 1. File Discovery
|
|
|
|
```
|
|
glob("**/*.*", directory, ignorePatterns)
|
|
→ allFiles (all file paths)
|
|
→ allSlugs (slugified paths)
|
|
→ Extension-stripped slug aliases for PageType extensions (.canvas, .base)
|
|
```
|
|
|
|
PageType plugins register file extensions (e.g., `.canvas`, `.base`). The build adds extension-stripped slug aliases so wikilinks like `![[file.canvas]]` resolve to the virtual page slug `file`.
|
|
|
|
### 2. Parse (`quartz/processors/parse.ts`)
|
|
|
|
Only `.md` files are parsed. The parsing pipeline:
|
|
|
|
```
|
|
Markdown source
|
|
→ textTransform() Transformer plugins modify raw text
|
|
→ remark-parse Markdown → mdast (Markdown AST)
|
|
→ markdownPlugins() Transformer plugins add remark plugins
|
|
→ remark-rehype mdast → hast (HTML AST)
|
|
→ htmlPlugins() Transformer plugins add rehype plugins
|
|
→ ProcessedContent [hast tree, VFile with data]
|
|
```
|
|
|
|
Each step runs every enabled transformer plugin's corresponding hook in order.
|
|
|
|
### 3. Filter (`quartz/processors/filter.ts`)
|
|
|
|
```
|
|
ProcessedContent[]
|
|
→ filter.shouldPublish(ctx, content) For each filter plugin
|
|
→ FilteredContent[]
|
|
```
|
|
|
|
Filter plugins decide which files to publish. Examples: `remove-draft` (excludes `draft: true` frontmatter), `explicit-publish` (requires `publish: true`).
|
|
|
|
### 4. Emit (`quartz/processors/emit.ts`)
|
|
|
|
Emission is split into two phases to support virtual pages:
|
|
|
|
```
|
|
Phase 1: PageTypeDispatcher
|
|
├── Generate virtual pages (tag pages, folder pages, bases pages, etc.)
|
|
├── Populate ctx.virtualPages
|
|
├── Render Body components → populate htmlAst (for transclusion)
|
|
└── Emit all pages as HTML
|
|
|
|
Phase 2: All other emitters
|
|
├── Receive content + virtualPages merged
|
|
├── ContentIndex (sitemap, RSS, search index)
|
|
├── AliasRedirects, CNAME, Favicon, OGImage
|
|
└── ComponentResources (CSS, JS bundles), Assets, Static files
|
|
```
|
|
|
|
This two-phase approach ensures that emitters like ContentIndex include virtual pages in their output (sitemap, RSS, explorer data).
|
|
|
|
### Incremental Rebuild
|
|
|
|
During `--watch` mode, the same two-phase approach applies. `partialEmit` is used when available to only re-emit changed files. The content map tracks add/change/delete events, and the trie is rebuilt on each partial emit.
|
|
|
|
---
|
|
|
|
## Plugin System
|
|
|
|
### Plugin Categories
|
|
|
|
There are **4 processing categories** and **1 UI category**:
|
|
|
|
| Category | Interface | Key Methods | Purpose |
|
|
| --------------- | ----------------------------------- | ---------------------------------------------------------------------- | --------------------------------------- |
|
|
| **Transformer** | `QuartzTransformerPluginInstance` | `textTransform`, `markdownPlugins`, `htmlPlugins`, `externalResources` | Transform content during parsing |
|
|
| **Filter** | `QuartzFilterPluginInstance` | `shouldPublish` | Decide which files to include |
|
|
| **Emitter** | `QuartzEmitterPluginInstance` | `emit`, `partialEmit`, `getQuartzComponents`, `externalResources` | Produce output files |
|
|
| **PageType** | `QuartzPageTypePluginInstance` | `match`, `generate`, `body`, `layout`, `fileExtensions`, `priority` | Define page rendering and virtual pages |
|
|
| **Component** | _(registered in ComponentRegistry)_ | QuartzComponent function + static CSS/JS | UI components placed via layout config |
|
|
|
|
A plugin can belong to multiple categories. For example, `content-page` is both `pageType` and `component` — it defines how content pages are matched/rendered AND provides the Body component.
|
|
|
|
### Plugin Lifecycle
|
|
|
|
```
|
|
quartz.config.yaml
|
|
│
|
|
▼
|
|
1. Read YAML config (config-loader.ts)
|
|
│
|
|
▼
|
|
2. For each enabled plugin entry:
|
|
│ a. Parse source string (e.g., "github:quartz-community/explorer")
|
|
│ b. Clone/update from GitHub via isomorphic-git (gitLoader.ts)
|
|
│ c. Read manifest from package.json "quartz" field
|
|
│ d. Load components into ComponentRegistry (componentLoader.ts)
|
|
│ e. Load frames into FrameRegistry (frameLoader.ts)
|
|
│
|
|
▼
|
|
3. Validate dependencies
|
|
│ a. Check all dependencies are present and enabled
|
|
│ b. Verify execution order (dependency must run before dependent)
|
|
│ c. Detect circular dependencies
|
|
│
|
|
▼
|
|
4. Categorize plugins
|
|
│ a. Read category from manifest
|
|
│ b. For dual-category (e.g., ["pageType", "component"]), resolve processing category
|
|
│ c. Component-only plugins are loaded into registry but not into processing pipeline
|
|
│ d. If category unknown, detect by instantiating and inspecting exported methods
|
|
│
|
|
▼
|
|
5. Sort by order within each category (entry.order ?? manifest.defaultOrder ?? 50)
|
|
│
|
|
▼
|
|
6. Instantiate plugins
|
|
│ a. Import module from dist/index.js
|
|
│ b. Find factory function (default export, or "plugin" export, or first matching export)
|
|
│ c. Merge options: { ...manifest.defaultOptions, ...entry.options }
|
|
│ d. Call factory(mergedOptions) → plugin instance
|
|
│
|
|
▼
|
|
7. Build layout (loadQuartzLayout)
|
|
│ a. Resolve component references from ComponentRegistry
|
|
│ b. Apply display wrappers (MobileOnly/DesktopOnly)
|
|
│ c. Apply condition wrappers (ConditionalRender)
|
|
│ d. Resolve flex groups
|
|
│ e. Build per-page-type layout overrides
|
|
│
|
|
▼
|
|
8. Add built-in plugins
|
|
│ a. ComponentResources, Assets, Static (emitters)
|
|
│ b. NotFoundPageType (built-in 404 page)
|
|
│ c. PageTypeDispatcher (wired with resolved layout)
|
|
│
|
|
▼
|
|
9. Return QuartzConfig { configuration, plugins }
|
|
```
|
|
|
|
### Plugin Manifest
|
|
|
|
Each plugin declares metadata in its `package.json` under the `"quartz"` field:
|
|
|
|
```json
|
|
{
|
|
"quartz": {
|
|
"name": "explorer",
|
|
"displayName": "Explorer",
|
|
"category": "component",
|
|
"version": "1.0.0",
|
|
"quartzVersion": ">=5.0.0",
|
|
"dependencies": [],
|
|
"defaultOrder": 50,
|
|
"defaultEnabled": true,
|
|
"defaultOptions": {},
|
|
"components": {
|
|
"Explorer": {
|
|
"displayName": "Explorer",
|
|
"defaultPosition": "left",
|
|
"defaultPriority": 50
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
| ---------------- | ---------------------------------------- | --------------------------------------------------------------------------- |
|
|
| `name` | `string` | Plugin identifier (kebab-case) |
|
|
| `displayName` | `string` | Human-readable name |
|
|
| `category` | `string \| string[]` | One or more of: `transformer`, `filter`, `emitter`, `pageType`, `component` |
|
|
| `version` | `string` | Plugin version |
|
|
| `quartzVersion` | `string` | Compatible Quartz version range |
|
|
| `dependencies` | `string[]` | Required plugin sources (e.g., `"github:quartz-community/crawl-links"`) |
|
|
| `defaultOrder` | `number` | Default execution order (0-100, lower = runs first) |
|
|
| `defaultEnabled` | `boolean` | Whether enabled by default on install |
|
|
| `defaultOptions` | `object` | Default options merged with user config |
|
|
| `configSchema` | `object` | JSON Schema for options validation |
|
|
| `components` | `Record<string, ComponentManifest>` | UI components provided by this plugin |
|
|
| `frames` | `Record<string, { exportName: string }>` | Page frames provided by this plugin (see [Page Frames](#page-frames)) |
|
|
|
|
### Plugin Template Structure
|
|
|
|
Every community plugin follows this structure:
|
|
|
|
```
|
|
plugin-name/
|
|
├── src/
|
|
│ ├── index.ts # Main exports (plugin factory + manifest)
|
|
│ ├── [plugin-type].ts # Plugin implementation
|
|
│ ├── types.ts # Type definitions
|
|
│ └── components/ # UI components (if any)
|
|
│ ├── index.ts # Component exports
|
|
│ ├── Component.tsx # Preact component
|
|
│ └── Component.scss # Styles
|
|
├── dist/ # Compiled output (tsup)
|
|
│ ├── index.js / index.d.ts
|
|
│ └── components/
|
|
├── package.json # With "quartz" metadata field
|
|
├── tsconfig.json # TypeScript config
|
|
├── tsup.config.ts # Build config (sass.compile for SCSS)
|
|
├── vitest.config.ts # Test config
|
|
├── .eslintrc.json
|
|
├── .prettierrc
|
|
├── README.md
|
|
├── LICENSE
|
|
├── CHANGELOG.md
|
|
└── .github/ # CI workflows
|
|
```
|
|
|
|
**Build configuration (`tsup.config.ts`):**
|
|
|
|
All plugins use `tsup` for building. Plugins with SCSS styles use a custom `esbuildPlugins` entry that compiles SCSS via `sass.compile()` and injects the CSS as a string export.
|
|
|
|
---
|
|
|
|
## PageType System
|
|
|
|
The PageType system is the central innovation of v5. It replaces the previous monolithic page rendering approach with a pluggable, extensible system where each page "type" (content, folder, tag, canvas, bases) is a separate plugin.
|
|
|
|
### Core Interface
|
|
|
|
```typescript
|
|
interface QuartzPageTypePluginInstance {
|
|
name: string
|
|
priority?: number // Higher = matched first (descending sort)
|
|
fileExtensions?: string[] // e.g., [".canvas"] — registers for slug aliasing
|
|
match: PageMatcher // Determines if this page type handles a given page
|
|
generate?: PageGenerator // Produces virtual pages (tag listings, folder pages, etc.)
|
|
layout: string // Layout key (used for per-page-type overrides)
|
|
frame?: string // PageFrame name (e.g. "full-width", "minimal") — defaults to "default"
|
|
body: QuartzComponentConstructor // The Body component for rendering page content
|
|
}
|
|
```
|
|
|
|
### Matchers
|
|
|
|
The `matchers.ts` module provides composable matcher factories:
|
|
|
|
```typescript
|
|
import { match } from "quartz/plugins/pageTypes/matchers"
|
|
|
|
match.ext(".canvas") // Matches files with .canvas extension
|
|
match.slugPrefix("tags/") // Matches slugs starting with "tags/"
|
|
match.frontmatter(
|
|
"layout", // Matches based on frontmatter values
|
|
(v) => v === "custom",
|
|
)
|
|
match.and(m1, m2) // Logical AND
|
|
match.or(m1, m2) // Logical OR
|
|
match.not(m1) // Logical NOT
|
|
match.all() // Always matches
|
|
match.none() // Never matches
|
|
```
|
|
|
|
### Virtual Pages
|
|
|
|
PageType plugins with a `generate` method produce virtual pages — pages that don't exist as files on disk but are rendered as if they do. Examples:
|
|
|
|
- **Tag pages**: One virtual page per tag (e.g., `/tags/javascript`)
|
|
- **Folder pages**: One virtual page per folder (e.g., `/posts/`)
|
|
- **Bases pages**: View-based aggregate pages from `.base` files
|
|
|
|
Virtual pages are represented as:
|
|
|
|
```typescript
|
|
interface VirtualPage {
|
|
slug: string
|
|
title: string
|
|
data: Partial<QuartzPluginData> & Record<string, unknown>
|
|
}
|
|
```
|
|
|
|
They are converted to `ProcessedContent` (with a synthetic hast tree and VFile) by the PageTypeDispatcher.
|
|
|
|
### PageTypeDispatcher
|
|
|
|
The PageTypeDispatcher (`quartz/plugins/pageTypes/dispatcher.ts`) is a special emitter plugin that orchestrates all PageType plugins. It runs as Phase 1 of emission and has a 3-phase internal flow:
|
|
|
|
```
|
|
Phase 1: Generate Virtual Pages
|
|
For each PageType plugin with a generate() method:
|
|
→ Call generate({ content, cfg, ctx })
|
|
→ Create ProcessedContent entries (defaultProcessedContent)
|
|
→ Push to ctx.virtualPages (except 404)
|
|
→ Collect in virtualEntries[]
|
|
|
|
Phase 2: Populate htmlAst
|
|
For each virtual entry:
|
|
→ Render BodyComponent via preact-render-to-string
|
|
→ Parse HTML string to hast via hast-util-from-html
|
|
→ Set tree.children and vfile.data.htmlAst
|
|
(This enables transclusion of virtual pages, e.g., ![[file.canvas]])
|
|
|
|
Phase 3: Emit Pages
|
|
a. Emit regular pages:
|
|
For each file in content:
|
|
→ Find first matching PageType (sorted by priority, descending)
|
|
→ Resolve layout (defaults + per-page-type overrides)
|
|
→ renderPage() → write HTML
|
|
b. Emit virtual pages:
|
|
For each virtual entry:
|
|
→ renderPage() → write HTML
|
|
```
|
|
|
|
**Priority:** PageType plugins are sorted by `priority` (descending, higher = matched first). The first match wins. This allows specialized types (canvas, bases) to take precedence over the generic content type.
|
|
|
|
---
|
|
|
|
## Layout System
|
|
|
|
The layout system determines which UI components appear on each page and where they are positioned. It is fully declarative via `quartz.config.yaml`.
|
|
|
|
### Layout Positions
|
|
|
|
Each component is placed in one of four positions:
|
|
|
|
| Position | Location | Rendered As |
|
|
| ------------ | ------------------ | ----------------------------------- |
|
|
| `left` | Left sidebar | `<div class="left sidebar">` |
|
|
| `right` | Right sidebar | `<div class="right sidebar">` |
|
|
| `beforeBody` | Above page content | Inside `<div class="popover-hint">` |
|
|
| `afterBody` | Below page content | Inside `<div class="page-footer">` |
|
|
|
|
Additionally, there are structural slots not user-configurable per-component:
|
|
|
|
- `head` — Built-in `<Head>` component (meta tags, CSS links)
|
|
- `header` — Page header area
|
|
- `footer` — Footer component (from `footer` plugin)
|
|
|
|
### Full Page Layout Structure
|
|
|
|
```html
|
|
<html>
|
|
<head />
|
|
<!-- head -->
|
|
<body>
|
|
<div class="page">
|
|
<body>
|
|
<div class="left sidebar">
|
|
<!-- left[] components by priority -->
|
|
</div>
|
|
<div class="center">
|
|
<div class="page-header">
|
|
<header>
|
|
<!-- header[] components -->
|
|
</header>
|
|
<div class="popover-hint">
|
|
<!-- beforeBody[] components by priority -->
|
|
</div>
|
|
</div>
|
|
<content />
|
|
<!-- pageBody (from PageType) -->
|
|
<hr />
|
|
<div class="page-footer">
|
|
<!-- afterBody[] components by priority -->
|
|
</div>
|
|
</div>
|
|
<div class="right sidebar">
|
|
<!-- right[] components by priority -->
|
|
</div>
|
|
<footer />
|
|
<!-- footer -->
|
|
</body>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Priority and Ordering
|
|
|
|
Each component has a numeric `priority`. Lower values render first (closer to the top/start of the position).
|
|
|
|
```yaml
|
|
plugins:
|
|
- source: github:quartz-community/page-title
|
|
layout:
|
|
position: left
|
|
priority: 10 # Renders first in left sidebar
|
|
|
|
- source: github:quartz-community/search
|
|
layout:
|
|
position: left
|
|
priority: 20 # Renders second
|
|
```
|
|
|
|
### Groups (Flex Containers)
|
|
|
|
Components sharing the same `group` name are collected into a `Flex` container. The group itself is treated as a single unit with its own priority.
|
|
|
|
```yaml
|
|
plugins:
|
|
- source: github:quartz-community/search
|
|
layout:
|
|
position: left
|
|
priority: 20
|
|
group: toolbar
|
|
groupOptions:
|
|
grow: true
|
|
|
|
- source: github:quartz-community/darkmode
|
|
layout:
|
|
position: left
|
|
priority: 30
|
|
group: toolbar
|
|
|
|
- source: github:quartz-community/reader-mode
|
|
layout:
|
|
position: left
|
|
priority: 35
|
|
group: toolbar
|
|
|
|
layout:
|
|
groups:
|
|
toolbar:
|
|
priority: 15 # Group renders at priority 15 (before search's 20)
|
|
direction: row
|
|
gap: 0.5rem
|
|
```
|
|
|
|
**Group resolution:** The `resolveGroups()` function builds a unified list of ungrouped components and Flex groups, each with a priority. Groups use their explicit `priority` from `layout.groups` config, falling back to the first member's priority. The final list is sorted by priority.
|
|
|
|
**FlexGroupConfig options:**
|
|
|
|
| Option | Type | Default | Description |
|
|
| ----------- | -------------------------------------------------------- | ----------------------- | --------------------------- |
|
|
| `priority` | `number` | First member's priority | Sort priority for the group |
|
|
| `direction` | `"row" \| "column" \| "row-reverse" \| "column-reverse"` | `"row"` | Flex direction |
|
|
| `wrap` | `"nowrap" \| "wrap" \| "wrap-reverse"` | — | Flex wrap |
|
|
| `gap` | `string` | `"1rem"` | Gap between items |
|
|
|
|
**Per-component groupOptions:**
|
|
|
|
| Option | Type | Description |
|
|
| --------- | ------------------------------------------------------- | ------------------ |
|
|
| `grow` | `boolean` | `flex-grow: 1` |
|
|
| `shrink` | `boolean` | `flex-shrink: 1` |
|
|
| `basis` | `string` | `flex-basis` value |
|
|
| `order` | `number` | CSS `order` |
|
|
| `align` | `"start" \| "end" \| "center" \| "stretch"` | `align-self` |
|
|
| `justify` | `"start" \| "end" \| "center" \| "between" \| "around"` | `justify-self` |
|
|
|
|
### Display Modifiers
|
|
|
|
Components can be conditionally shown based on viewport:
|
|
|
|
```yaml
|
|
layout:
|
|
display: mobile-only # Only visible on mobile
|
|
display: desktop-only # Only visible on desktop
|
|
display: all # Always visible (default)
|
|
```
|
|
|
|
Under the hood, `mobile-only` wraps the component in `MobileOnly()` (renders a `<div>` with `display: contents` that hides on desktop via CSS media query), and `desktop-only` wraps in `DesktopOnly()`.
|
|
|
|
### Conditions
|
|
|
|
Components can be conditionally rendered based on page data:
|
|
|
|
```yaml
|
|
layout:
|
|
condition: not-index # Don't render on the index page
|
|
```
|
|
|
|
**Built-in conditions:**
|
|
|
|
| Condition | Description |
|
|
| --------------- | ---------------------------- |
|
|
| `not-index` | Page slug is not `"index"` |
|
|
| `has-tags` | Page has at least one tag |
|
|
| `has-backlinks` | Page has backlinks |
|
|
| `has-toc` | Page has a table of contents |
|
|
|
|
**Custom conditions** can be registered via:
|
|
|
|
```typescript
|
|
import { registerCondition } from "quartz/plugins/loader/conditions"
|
|
|
|
registerCondition("my-condition", (props) => {
|
|
return props.fileData.frontmatter?.myField === true
|
|
})
|
|
```
|
|
|
|
### Per-Page-Type Overrides
|
|
|
|
The `layout.byPageType` section allows overriding the layout for specific page types:
|
|
|
|
```yaml
|
|
layout:
|
|
byPageType:
|
|
"404":
|
|
positions:
|
|
beforeBody: [] # No beforeBody components on 404
|
|
left: [] # No left sidebar on 404
|
|
right: [] # No right sidebar on 404
|
|
|
|
folder:
|
|
exclude:
|
|
- reader-mode # No reader-mode on folder pages
|
|
positions:
|
|
right: [] # No right sidebar on folder pages
|
|
|
|
tag:
|
|
exclude:
|
|
- reader-mode
|
|
positions:
|
|
right: []
|
|
```
|
|
|
|
**Override resolution:** For each page type, the config loader:
|
|
|
|
1. Starts with the full default layout
|
|
2. Removes excluded plugins
|
|
3. Rebuilds positions from remaining entries
|
|
4. Overwrites positions that have explicit overrides (empty array = clear that position)
|
|
|
|
---
|
|
|
|
## Component Architecture
|
|
|
|
### QuartzComponent
|
|
|
|
A `QuartzComponent` is a Preact function component with optional static resource declarations:
|
|
|
|
```typescript
|
|
type QuartzComponent = ((props: QuartzComponentProps) => any) & {
|
|
displayName?: string
|
|
css?: StringResource // CSS injected into page
|
|
beforeDOMLoaded?: StringResource // JS that runs before DOM ready
|
|
afterDOMLoaded?: StringResource // JS that runs after DOM ready
|
|
}
|
|
|
|
type QuartzComponentConstructor<Options> = (opts: Options) => QuartzComponent
|
|
```
|
|
|
|
**Props received by every component:**
|
|
|
|
```typescript
|
|
type QuartzComponentProps = {
|
|
ctx: BuildCtx // Build context (config, slugs, etc.)
|
|
externalResources: StaticResources // CSS/JS resources
|
|
fileData: QuartzPluginData // Current page's data (frontmatter, slug, etc.)
|
|
cfg: GlobalConfiguration // Site configuration
|
|
children: (QuartzComponent | JSX.Element)[]
|
|
tree: Node // Current page's hast tree
|
|
allFiles: QuartzPluginData[] // All files (including virtual pages)
|
|
}
|
|
```
|
|
|
|
### Component Registry
|
|
|
|
The `ComponentRegistry` (`quartz/components/registry.ts`) is a singleton `Map` that stores all loaded components. Components are registered under multiple keys for flexible lookup:
|
|
|
|
1. **Fully-qualified**: `"pluginName/ExportName"` (e.g., `"explorer/Explorer"`)
|
|
2. **Export name**: `"Explorer"` (PascalCase)
|
|
3. **Plugin name**: `"explorer"` (kebab-case, only if plugin has exactly one component)
|
|
|
|
The layout builder looks up components by:
|
|
|
|
1. Plugin name (kebab-case from source URL)
|
|
2. Fully-qualified key (`source/name`)
|
|
3. PascalCase conversion of plugin name
|
|
|
|
### Page Rendering
|
|
|
|
`renderPage()` in `quartz/components/renderPage.tsx`:
|
|
|
|
1. Deep-clones the hast tree (to preserve the cached version)
|
|
2. Resolves transclusions (see below)
|
|
3. Destructures the layout into Head, header[], beforeBody[], Content, afterBody[], left[], right[], Footer, and frame name
|
|
4. Resolves the frame via `resolveFrame(frameName)` — checks plugin-registered frames (FrameRegistry) first, then built-in frames, then falls back to `DefaultFrame`
|
|
5. Delegates the inner page structure to `frame.render({...all slots...})`
|
|
6. Wraps the frame output in the stable outer shell: `<html>` → `<Head/>` → `<body data-slug>` → `<div id="quartz-root" data-frame={frame.name}>` → `<Body>`
|
|
7. Serializes to HTML string via `preact-render-to-string`
|
|
|
|
The `data-frame` attribute on `#quartz-root` enables CSS targeting per-frame (see [Page Frames](#page-frames)).
|
|
|
|
### Page Frames
|
|
|
|
The PageFrame system allows page types to optionally define completely different inner HTML structures (e.g. with/without sidebars, horizontal layouts, minimal chrome) while the outer shell remains stable for SPA navigation.
|
|
|
|
#### Architecture
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────┐
|
|
│ <html> │ Stable outer shell
|
|
│ <head /> │ (never changes)
|
|
│ <body data-slug="..."> │
|
|
│ <div id="quartz-root" data-frame="..."> │
|
|
│ <Body> │
|
|
│ ┌──────────────────────────────────┐ │
|
|
│ │ PageFrame.render() │ │ ← Frame controls this
|
|
│ │ (sidebars, header, content, etc.)│ │
|
|
│ └──────────────────────────────────┘ │
|
|
│ </Body> │
|
|
│ </div> │
|
|
│ </body> │
|
|
│ </html> │
|
|
└──────────────────────────────────────────────────┘
|
|
```
|
|
|
|
#### Interfaces
|
|
|
|
```typescript
|
|
interface PageFrame {
|
|
name: string // e.g. "default", "full-width", "minimal"
|
|
render: (props: PageFrameProps) => JSX.Element // Renders the inner page structure
|
|
css?: string // Optional frame-specific CSS
|
|
}
|
|
|
|
interface PageFrameProps {
|
|
componentData: QuartzComponentProps // Shared props for all components
|
|
head: QuartzComponent // Head component (for completeness)
|
|
header: QuartzComponent[] // Header slot
|
|
beforeBody: QuartzComponent[] // Before-body slot
|
|
pageBody: QuartzComponent // Content component (from PageType)
|
|
afterBody: QuartzComponent[] // After-body slot
|
|
left: QuartzComponent[] // Left sidebar components
|
|
right: QuartzComponent[] // Right sidebar components
|
|
footer: QuartzComponent // Footer component
|
|
}
|
|
```
|
|
|
|
#### Built-in Frames
|
|
|
|
| Frame | Name | Description | Used By |
|
|
| ------------------ | -------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
|
| **DefaultFrame** | `"default"` | Original three-column layout: left sidebar + center (header, beforeBody, content, afterBody) + right sidebar + footer | All page types by default |
|
|
| **FullWidthFrame** | `"full-width"` | No sidebars. Single center column spanning full width with header, content, afterBody, and footer | — |
|
|
| **MinimalFrame** | `"minimal"` | No sidebars, no header/beforeBody chrome. Only content + footer | `404` page |
|
|
|
|
#### Plugin-Provided Frames
|
|
|
|
Plugins can register their own frames via the Frame Registry. For example, the `canvas-page` plugin provides a `"canvas"` frame:
|
|
|
|
| Frame | Name | Source Plugin | Description |
|
|
| --------------- | ---------- | ------------- | ------------------------------------------------------------ |
|
|
| **CanvasFrame** | `"canvas"` | `canvas-page` | Fullscreen canvas with togglable left sidebar for navigation |
|
|
|
|
#### Frame Resolution Priority
|
|
|
|
Frames are resolved in the `PageTypeDispatcher.resolveLayout()` function with this priority chain:
|
|
|
|
```
|
|
1. YAML config override: layout.byPageType.<name>.template
|
|
2. Plugin-registered frame: FrameRegistry (loaded from plugin ./frames exports)
|
|
3. Built-in frame: builtinFrames map in quartz/components/frames/index.ts
|
|
4. Default: "default"
|
|
```
|
|
|
|
This means site authors can override any page type's frame via configuration without touching plugin code:
|
|
|
|
```yaml
|
|
layout:
|
|
byPageType:
|
|
canvas:
|
|
template: minimal # Override canvas pages to use minimal frame
|
|
```
|
|
|
|
#### Creating Custom Frames
|
|
|
|
There are two ways to add custom frames:
|
|
|
|
**1. Plugin-provided frames** (recommended for reusable, distributable frames):
|
|
|
|
Plugins can ship frames by exporting them from a `./frames` subpath and declaring them in the `package.json` manifest under `"quartz"."frames"`. The config loader calls `loadFramesFromPackage()` during plugin initialization, which imports the frame and registers it in the `FrameRegistry`.
|
|
|
|
```json title="package.json (excerpt)"
|
|
{
|
|
"exports": {
|
|
"./frames": {
|
|
"import": "./dist/frames/index.js"
|
|
}
|
|
},
|
|
"quartz": {
|
|
"frames": {
|
|
"CanvasFrame": { "exportName": "CanvasFrame" }
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**2. Core frames** (for project-specific frames):
|
|
|
|
New frames can be added in `quartz/components/frames/`:
|
|
|
|
1. Create a new `.tsx` file implementing the `PageFrame` interface
|
|
2. Register it in `quartz/components/frames/index.ts` → `builtinFrames` map
|
|
3. Reference it by name in page type plugins or YAML config
|
|
|
|
```typescript
|
|
// quartz/components/frames/PresentationFrame.tsx
|
|
import { PageFrame, PageFrameProps } from "./types"
|
|
|
|
export const PresentationFrame: PageFrame = {
|
|
name: "presentation",
|
|
render({ componentData, pageBody: Content, footer: Footer }: PageFrameProps) {
|
|
return (
|
|
<>
|
|
<div class="center presentation">
|
|
<Content {...componentData} />
|
|
</div>
|
|
<Footer {...componentData} />
|
|
</>
|
|
)
|
|
},
|
|
}
|
|
```
|
|
|
|
#### CSS Targeting
|
|
|
|
The `data-frame` attribute on `#quartz-root` enables frame-specific CSS without class conflicts:
|
|
|
|
```scss
|
|
// Full-width: single column, no sidebar grid areas
|
|
.page[data-frame="full-width"] > #quartz-body {
|
|
grid-template-columns: 1fr;
|
|
grid-template-areas: "center";
|
|
}
|
|
|
|
// Minimal: content-only grid
|
|
.page[data-frame="minimal"] > #quartz-body {
|
|
grid-template-columns: 1fr;
|
|
grid-template-areas: "center";
|
|
}
|
|
```
|
|
|
|
#### SPA Safety
|
|
|
|
Frame changes between pages are SPA-safe because:
|
|
|
|
1. The SPA router (`micromorph`) morphs `document.body` — it does not hardcode selectors like `.center` or `.sidebar`
|
|
2. The `data-frame` attribute updates naturally during the morph
|
|
3. CSS grid overrides apply immediately based on the new `data-frame` value
|
|
4. `collectComponents()` collects components from ALL slots regardless of frame, ensuring all component resources (CSS/JS) are available globally
|
|
|
|
#### Source Files
|
|
|
|
| File | Purpose |
|
|
| --------------------------------------------- | ------------------------------------------------------ |
|
|
| `quartz/components/frames/types.ts` | `PageFrame` and `PageFrameProps` interfaces |
|
|
| `quartz/components/frames/DefaultFrame.tsx` | Default three-column layout |
|
|
| `quartz/components/frames/FullWidthFrame.tsx` | Full-width single-column layout |
|
|
| `quartz/components/frames/MinimalFrame.tsx` | Minimal content-only layout |
|
|
| `quartz/components/frames/registry.ts` | `FrameRegistry` singleton for plugin-registered frames |
|
|
| `quartz/components/frames/index.ts` | `resolveFrame()` and built-in frame map |
|
|
| `quartz/plugins/loader/frameLoader.ts` | Loads frames from plugin `./frames` exports |
|
|
|
|
### Transclusion
|
|
|
|
Transclusion (`![[page]]` syntax) is resolved at render time. The `renderTranscludes()` function walks the hast tree looking for `<blockquote class="transclude">` nodes and replaces them with the target page's content.
|
|
|
|
**Resolution strategy:**
|
|
|
|
1. Look up target slug directly in `allFiles`
|
|
2. If not found, strip the file extension and try again (handles virtual page slugs like `CanvasPage.canvas` → `CanvasPage`)
|
|
3. Support block references (`#^blockId`) and header references (`#heading`)
|
|
4. Detect and prevent circular transclusions
|
|
|
|
**Virtual page transclusion:** The PageTypeDispatcher populates `htmlAst` on virtual pages by rendering their Body component via `preact-render-to-string` and parsing the result with `hast-util-from-html`. This allows wikilinks like `![[navigation.base]]` to inline the virtual page's rendered content.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
### quartz.config.yaml
|
|
|
|
The single configuration file has three top-level sections:
|
|
|
|
```yaml
|
|
# Site-wide settings
|
|
configuration:
|
|
pageTitle: "My Site"
|
|
pageTitleSuffix: ""
|
|
enableSPA: true
|
|
enablePopovers: true
|
|
analytics:
|
|
provider: plausible
|
|
locale: en-US
|
|
baseUrl: example.com
|
|
ignorePatterns:
|
|
- private
|
|
- templates
|
|
- .obsidian
|
|
defaultDateType: modified
|
|
theme:
|
|
fontOrigin: googleFonts
|
|
cdnCaching: true
|
|
typography:
|
|
header: Schibsted Grotesk
|
|
body: Source Sans Pro
|
|
code: IBM Plex Mono
|
|
colors:
|
|
lightMode:
|
|
light: "#faf8f8"
|
|
lightgray: "#e5e5e5"
|
|
gray: "#b8b8b8"
|
|
darkgray: "#4e4e4e"
|
|
dark: "#2b2b2b"
|
|
secondary: "#284b63"
|
|
tertiary: "#84a59d"
|
|
highlight: "rgba(143, 159, 169, 0.15)"
|
|
textHighlight: "#fff23688"
|
|
darkMode:
|
|
light: "#161618"
|
|
lightgray: "#393639"
|
|
gray: "#646464"
|
|
darkgray: "#d4d4d4"
|
|
dark: "#ebebec"
|
|
secondary: "#7b97aa"
|
|
tertiary: "#84a59d"
|
|
highlight: "rgba(143, 159, 169, 0.15)"
|
|
textHighlight: "#b3aa0288"
|
|
|
|
# Plugin declarations (order matters for transformers)
|
|
plugins:
|
|
- source: github:quartz-community/obsidian-flavored-markdown
|
|
enabled: true
|
|
options:
|
|
enableInHtmlEmbed: false
|
|
enableCheckbox: true
|
|
order: 30
|
|
|
|
- source: github:quartz-community/explorer
|
|
enabled: true
|
|
layout:
|
|
position: left
|
|
priority: 50
|
|
|
|
- source: github:quartz-community/search
|
|
enabled: true
|
|
layout:
|
|
position: left
|
|
priority: 20
|
|
group: toolbar
|
|
groupOptions:
|
|
grow: true
|
|
|
|
# Global layout configuration
|
|
layout:
|
|
groups:
|
|
toolbar:
|
|
priority: 15
|
|
direction: row
|
|
gap: 0.5rem
|
|
byPageType:
|
|
"404":
|
|
positions:
|
|
beforeBody: []
|
|
left: []
|
|
right: []
|
|
folder:
|
|
exclude:
|
|
- reader-mode
|
|
positions:
|
|
right: []
|
|
```
|
|
|
|
**Plugin entry fields:**
|
|
|
|
| Field | Type | Required | Description |
|
|
| --------- | ------------------------- | -------- | -------------------------------------------------------- |
|
|
| `source` | `string` | Yes | Git source (e.g., `github:quartz-community/plugin-name`) |
|
|
| `enabled` | `boolean` | Yes | Whether the plugin is active |
|
|
| `options` | `object` | No | Plugin-specific options (merged with `defaultOptions`) |
|
|
| `order` | `number` | No | Execution order override (default: 50) |
|
|
| `layout` | `PluginLayoutDeclaration` | No | Layout placement for component plugins |
|
|
|
|
**Layout declaration fields:**
|
|
|
|
| Field | Type | Required | Description |
|
|
| -------------- | -------------------------------------------------- | -------- | ------------------------------- |
|
|
| `position` | `"left" \| "right" \| "beforeBody" \| "afterBody"` | Yes | Where to place the component |
|
|
| `priority` | `number` | Yes | Sort order (lower = first) |
|
|
| `display` | `"all" \| "mobile-only" \| "desktop-only"` | No | Viewport visibility |
|
|
| `condition` | `string` | No | Conditional rendering predicate |
|
|
| `group` | `string` | No | Flex group name |
|
|
| `groupOptions` | `object` | No | Per-component flex options |
|
|
|
|
### TypeScript Override
|
|
|
|
For advanced use cases, `quartz.ts` can override the YAML-loaded config:
|
|
|
|
```typescript
|
|
import * as ExternalPlugin from "./.quartz/plugins"
|
|
|
|
// Override plugin options programmatically
|
|
ExternalPlugin.Explorer({
|
|
title: "Explorer",
|
|
folderDefaultState: "collapsed",
|
|
folderClickBehavior: "link",
|
|
useSavedState: true,
|
|
})
|
|
```
|
|
|
|
The config loader first reads `quartz.config.yaml`, then applies any overrides from `quartz.ts`.
|
|
|
|
---
|
|
|
|
## Plugin Catalog
|
|
|
|
All 40 community plugins are hosted under the [`quartz-community`](https://github.com/quartz-community) GitHub organization.
|
|
|
|
### Transformers
|
|
|
|
Transform content during the markdown parsing pipeline.
|
|
|
|
| Plugin | Description | Repository |
|
|
| ------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
| **note-properties** | Extracts and renders YAML/TOML frontmatter properties | [quartz-community/note-properties](https://github.com/quartz-community/note-properties) |
|
|
| **created-modified-date** | Extracts creation/modification dates from frontmatter, git, or filesystem | [quartz-community/created-modified-date](https://github.com/quartz-community/created-modified-date) |
|
|
| **syntax-highlighting** | Syntax highlighting for code blocks (Shiki-based, light/dark themes) | [quartz-community/syntax-highlighting](https://github.com/quartz-community/syntax-highlighting) |
|
|
| **obsidian-flavored-markdown** | Obsidian-specific markdown extensions (callouts, wikilinks, embeds, checkboxes) | [quartz-community/obsidian-flavored-markdown](https://github.com/quartz-community/obsidian-flavored-markdown) |
|
|
| **github-flavored-markdown** | GFM extensions (tables, strikethrough, autolinks, task lists) | [quartz-community/github-flavored-markdown](https://github.com/quartz-community/github-flavored-markdown) |
|
|
| **table-of-contents** | Generates table of contents from headings | [quartz-community/table-of-contents](https://github.com/quartz-community/table-of-contents) |
|
|
| **crawl-links** | Resolves and rewrites internal links (wikilinks, relative paths) | [quartz-community/crawl-links](https://github.com/quartz-community/crawl-links) |
|
|
| **description** | Extracts or generates page descriptions for meta tags and previews | [quartz-community/description](https://github.com/quartz-community/description) |
|
|
| **latex** | LaTeX math rendering (KaTeX or MathJax) | [quartz-community/latex](https://github.com/quartz-community/latex) |
|
|
| **citations** | Academic citation support (BibTeX) | [quartz-community/citations](https://github.com/quartz-community/citations) |
|
|
| **hard-line-breaks** | Treats single newlines as `<br>` tags | [quartz-community/hard-line-breaks](https://github.com/quartz-community/hard-line-breaks) |
|
|
| **ox-hugo** | Compatibility layer for ox-hugo exported markdown | [quartz-community/ox-hugo](https://github.com/quartz-community/ox-hugo) |
|
|
| **roam** | Compatibility layer for Roam Research exports | [quartz-community/roam](https://github.com/quartz-community/roam) |
|
|
|
|
### Filters
|
|
|
|
Decide which content files to include in the build output.
|
|
|
|
| Plugin | Description | Repository |
|
|
| -------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
|
| **remove-draft** | Excludes pages with `draft: true` frontmatter | [quartz-community/remove-draft](https://github.com/quartz-community/remove-draft) |
|
|
| **explicit-publish** | Requires `publish: true` frontmatter to include a page | [quartz-community/explicit-publish](https://github.com/quartz-community/explicit-publish) |
|
|
|
|
### Emitters
|
|
|
|
Produce output files (HTML, JSON, XML, images, etc.).
|
|
|
|
| Plugin | Description | Repository |
|
|
| ------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
| **alias-redirects** | Generates redirect pages for frontmatter aliases | [quartz-community/alias-redirects](https://github.com/quartz-community/alias-redirects) |
|
|
| **content-index** | Generates sitemap.xml, RSS feed, and contentIndex.json (search/explorer data) | [quartz-community/content-index](https://github.com/quartz-community/content-index) |
|
|
| **favicon** | Generates favicon files | [quartz-community/favicon](https://github.com/quartz-community/favicon) |
|
|
| **og-image** | Generates Open Graph social preview images | [quartz-community/og-image](https://github.com/quartz-community/og-image) |
|
|
| **cname** | Generates CNAME file for custom domain hosting | [quartz-community/cname](https://github.com/quartz-community/cname) |
|
|
|
|
### PageType Plugins
|
|
|
|
Define how different types of pages are matched, generated, and rendered. All PageType plugins are dual-category (`pageType` + `component`).
|
|
|
|
| Plugin | Match Strategy | Generates Virtual Pages? | Description | Repository |
|
|
| ---------------- | ---------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
| **content-page** | `match.all()` (lowest priority fallback) | No | Default page type for regular markdown content | [quartz-community/content-page](https://github.com/quartz-community/content-page) |
|
|
| **folder-page** | Folder index pages | Yes (one per folder) | Renders folder listing pages with file trees | [quartz-community/folder-page](https://github.com/quartz-community/folder-page) |
|
|
| **tag-page** | Tag index pages | Yes (one per tag + tag index) | Renders tag listing pages | [quartz-community/tag-page](https://github.com/quartz-community/tag-page) |
|
|
| **canvas-page** | `match.ext(".canvas")` | No (reads `.canvas` files) | Interactive JSON Canvas visualization (pan/zoom). Provides `"canvas"` page frame with fullscreen layout and togglable sidebar | [quartz-community/canvas-page](https://github.com/quartz-community/canvas-page) |
|
|
| **bases-page** | `match.ext(".base")` | Yes (view-based aggregate pages) | Extensible view-based pages from `.base` files (table, list, board, gallery, calendar) | [quartz-community/bases-page](https://github.com/quartz-community/bases-page) |
|
|
|
|
### Component Plugins
|
|
|
|
UI components placed via the layout system. These are pure `component` category plugins (no processing hooks).
|
|
|
|
| Plugin | Default Position | Description | Repository |
|
|
| ----------------- | ---------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
| **page-title** | `left` | Site title with link to home page | [quartz-community/page-title](https://github.com/quartz-community/page-title) |
|
|
| **search** | `left` | Full-text search with keyboard navigation | [quartz-community/search](https://github.com/quartz-community/search) |
|
|
| **darkmode** | `left` | Light/dark theme toggle | [quartz-community/darkmode](https://github.com/quartz-community/darkmode) |
|
|
| **reader-mode** | `left` | Reader mode toggle (hides sidebars) | [quartz-community/reader-mode](https://github.com/quartz-community/reader-mode) |
|
|
| **explorer** | `left` | Interactive file tree navigation with collapsible folders | [quartz-community/explorer](https://github.com/quartz-community/explorer) |
|
|
| **spacer** | `left` | Visual spacer element (often used as `mobile-only`) | [quartz-community/spacer](https://github.com/quartz-community/spacer) |
|
|
| **article-title** | `beforeBody` | Page title (h1) | [quartz-community/article-title](https://github.com/quartz-community/article-title) |
|
|
| **breadcrumbs** | `beforeBody` | Breadcrumb navigation trail | [quartz-community/breadcrumbs](https://github.com/quartz-community/breadcrumbs) |
|
|
| **content-meta** | `beforeBody` | Creation/modification dates and reading time | [quartz-community/content-meta](https://github.com/quartz-community/content-meta) |
|
|
| **tag-list** | `beforeBody` | Displays page tags as clickable links | [quartz-community/tag-list](https://github.com/quartz-community/tag-list) |
|
|
| **graph** | `right` | Interactive link graph visualization (D3-based) | [quartz-community/graph](https://github.com/quartz-community/graph) |
|
|
| **backlinks** | `right` | Lists pages that link to the current page | [quartz-community/backlinks](https://github.com/quartz-community/backlinks) |
|
|
| **comments** | `afterBody` | Comment system integration (Giscus, Utterances, etc.) | [quartz-community/comments](https://github.com/quartz-community/comments) |
|
|
| **recent-notes** | — | Lists recently modified notes | [quartz-community/recent-notes](https://github.com/quartz-community/recent-notes) |
|
|
| **footer** | _(structural)_ | Page footer with configurable links | [quartz-community/footer](https://github.com/quartz-community/footer) |
|
|
|
|
### Dual-Category Plugins
|
|
|
|
Some plugins span multiple categories:
|
|
|
|
| Plugin | Categories | Notes |
|
|
| ------------------- | --------------------------- | -------------------------------------------------------------- |
|
|
| **note-properties** | `transformer` + `component` | Transforms frontmatter AND renders a properties view component |
|
|
| **content-page** | `pageType` + `component` | Matches content pages AND provides the Body component |
|
|
| **folder-page** | `pageType` + `component` | Matches folder pages AND provides the FolderContent component |
|
|
| **tag-page** | `pageType` + `component` | Matches tag pages AND provides the TagContent component |
|
|
| **canvas-page** | `pageType` + `component` | Matches `.canvas` files AND provides the CanvasBody component |
|
|
| **bases-page** | `pageType` + `component` | Matches `.base` files AND provides the BasesBody component |
|
|
|
|
---
|
|
|
|
## Key Source Files Reference
|
|
|
|
| File | Purpose |
|
|
| ------------------------------------------ | ------------------------------------------------------------- |
|
|
| `quartz.config.yaml` | All configuration (site settings, plugins, layout) |
|
|
| `quartz.ts` | Entry point — loads config from config-loader |
|
|
| `quartz/build.ts` | Build orchestration (glob, parse, filter, emit, watch) |
|
|
| `quartz/cfg.ts` | `GlobalConfiguration`, `FullPageLayout`, `QuartzConfig` types |
|
|
| `quartz/plugins/types.ts` | All plugin type interfaces |
|
|
| `quartz/plugins/vfile.ts` | `ProcessedContent`, `QuartzPluginData` |
|
|
| `quartz/processors/parse.ts` | Markdown parsing with transformer hooks |
|
|
| `quartz/processors/filter.ts` | Content filtering |
|
|
| `quartz/processors/emit.ts` | Two-phase emit orchestration |
|
|
| `quartz/plugins/pageTypes/dispatcher.ts` | PageTypeDispatcher (3-phase emit) |
|
|
| `quartz/plugins/pageTypes/matchers.ts` | Composable page matchers |
|
|
| `quartz/plugins/loader/config-loader.ts` | YAML config loading, plugin instantiation, layout building |
|
|
| `quartz/plugins/loader/types.ts` | `PluginManifest`, layout types, config types |
|
|
| `quartz/plugins/loader/gitLoader.ts` | Git clone/update for plugins |
|
|
| `quartz/plugins/loader/componentLoader.ts` | Component loading and registry population |
|
|
| `quartz/plugins/loader/conditions.ts` | Built-in and custom condition predicates |
|
|
| `quartz/components/types.ts` | `QuartzComponent`, `QuartzComponentProps` |
|
|
| `quartz/components/registry.ts` | `ComponentRegistry` singleton |
|
|
| `quartz/components/renderPage.tsx` | Page rendering with frame delegation and transclusion |
|
|
| `quartz/components/frames/` | PageFrame system (types, registry, built-in frames) |
|
|
| `quartz/components/Flex.tsx` | Flex layout container for grouped components |
|
|
| `quartz/components/MobileOnly.tsx` | Mobile display wrapper |
|