mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
docs: add PageFrame system to architecture overview
This commit is contained in:
parent
98dcb1e79d
commit
e052c8326f
@ -26,6 +26,7 @@ This document provides a bird's-eye view of the Quartz v5 rework for maintainer
|
|||||||
- [QuartzComponent](#quartzcomponent)
|
- [QuartzComponent](#quartzcomponent)
|
||||||
- [Component Registry](#component-registry)
|
- [Component Registry](#component-registry)
|
||||||
- [Page Rendering](#page-rendering)
|
- [Page Rendering](#page-rendering)
|
||||||
|
- [Page Frames](#page-frames)
|
||||||
- [Transclusion](#transclusion)
|
- [Transclusion](#transclusion)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [quartz.config.yaml](#quartzconfigyaml)
|
- [quartz.config.yaml](#quartzconfigyaml)
|
||||||
@ -304,6 +305,7 @@ interface QuartzPageTypePluginInstance {
|
|||||||
match: PageMatcher // Determines if this page type handles a given page
|
match: PageMatcher // Determines if this page type handles a given page
|
||||||
generate?: PageGenerator // Produces virtual pages (tag listings, folder pages, etc.)
|
generate?: PageGenerator // Produces virtual pages (tag listings, folder pages, etc.)
|
||||||
layout: string // Layout key (used for per-page-type overrides)
|
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
|
body: QuartzComponentConstructor // The Body component for rendering page content
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -642,9 +644,149 @@ The layout builder looks up components by:
|
|||||||
|
|
||||||
1. Deep-clones the hast tree (to preserve the cached version)
|
1. Deep-clones the hast tree (to preserve the cached version)
|
||||||
2. Resolves transclusions (see below)
|
2. Resolves transclusions (see below)
|
||||||
3. Destructures the layout into Head, header[], beforeBody[], Content, afterBody[], left[], right[], Footer
|
3. Destructures the layout into Head, header[], beforeBody[], Content, afterBody[], left[], right[], Footer, and frame name
|
||||||
4. Renders the full HTML document as a Preact JSX tree
|
4. Resolves the frame via `resolveFrame(frameName)` — falls back to `DefaultFrame` if unset
|
||||||
5. Serializes to HTML string via `preact-render-to-string`
|
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 | `canvas-page` |
|
||||||
|
| **MinimalFrame** | `"minimal"` | No sidebars, no header/beforeBody chrome. Only content + footer | `404` page |
|
||||||
|
|
||||||
|
#### Frame Resolution Priority
|
||||||
|
|
||||||
|
Frames are resolved in the `PageTypeDispatcher.resolveLayout()` function with this priority chain:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. YAML config override: layout.byPageType.<name>.template
|
||||||
|
2. Page type declaration: pageType.frame (in plugin source)
|
||||||
|
3. 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
|
||||||
|
|
||||||
|
New frames are 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` registry
|
||||||
|
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/index.ts` | Frame registry and `resolveFrame()` |
|
||||||
|
|
||||||
### Transclusion
|
### Transclusion
|
||||||
|
|
||||||
@ -913,6 +1055,7 @@ Some plugins span multiple categories:
|
|||||||
| `quartz/plugins/loader/conditions.ts` | Built-in and custom condition predicates |
|
| `quartz/plugins/loader/conditions.ts` | Built-in and custom condition predicates |
|
||||||
| `quartz/components/types.ts` | `QuartzComponent`, `QuartzComponentProps` |
|
| `quartz/components/types.ts` | `QuartzComponent`, `QuartzComponentProps` |
|
||||||
| `quartz/components/registry.ts` | `ComponentRegistry` singleton |
|
| `quartz/components/registry.ts` | `ComponentRegistry` singleton |
|
||||||
| `quartz/components/renderPage.tsx` | Page rendering with transclusion resolution |
|
| `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/Flex.tsx` | Flex layout container for grouped components |
|
||||||
| `quartz/components/MobileOnly.tsx` | Mobile display wrapper |
|
| `quartz/components/MobileOnly.tsx` | Mobile display wrapper |
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user