From 287d41b2ac16257291eb3556c5cf79701a3403d6 Mon Sep 17 00:00:00 2001 From: saberzero1 Date: Fri, 3 Apr 2026 19:06:48 +0200 Subject: [PATCH] docs: add package layering, plugin type guide, i18n guide, bases views, and import reference --- docs/advanced/architecture.md | 23 ++++ docs/advanced/creating components.md | 17 +++ docs/advanced/making plugins.md | 155 +++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) diff --git a/docs/advanced/architecture.md b/docs/advanced/architecture.md index 5cb860b..cfd56cc 100644 --- a/docs/advanced/architecture.md +++ b/docs/advanced/architecture.md @@ -50,6 +50,26 @@ This question is best answered by tracing what happens when a user (you!) runs ` 2. If it's not, we wire up the `"nav"` event to just be fired a single time after page load to allow for consistency across how state is setup across both SPA and non-SPA contexts. 3. A separate `"render"` event can be dispatched when the DOM is updated in-place without a full navigation (e.g. after content decryption). Components that attach listeners to content elements should listen for both `"nav"` and `"render"`. +## Community Package Layering + +Quartz v5 separates shared code into three community packages, each with a distinct responsibility: + +- **`@quartz-community/types`** — Type definitions, interfaces, and the canonical `vfile` DataMap augmentation. This is the "contract" between Quartz and plugins. It has no runtime dependencies. +- **`@quartz-community/utils`** — Shared utility functions (path manipulation, DOM helpers, sorting, date formatting, JSX conversion, etc.). Depends on `@quartz-community/types`. +- **`@quartz-community/runtime`** — Browser-only utilities for client-side scripts (event handling, navigation, storage, script loading). Depends on both `types` and `utils`. + +``` +types (no deps) + ↑ +utils (depends on types) + ↑ +runtime (depends on types + utils) + ↑ +plugins (depend on any combination) +``` + +Plugins should import types from `@quartz-community/types`, utility functions from `@quartz-community/utils`, and browser utilities from `@quartz-community/runtime`. This layering ensures plugins don't depend on Quartz core. + ## Plugin System Page types define how a category of pages is rendered. They are configured in the `pageTypes` array in `quartz.config.yaml`. @@ -64,6 +84,9 @@ There are now four plugin categories: - **Filters**: Filter content (remove drafts, explicit publish) - **Emitters**: Reduce over content (generate RSS, sitemaps, alias redirects, OG images) - **Page Types**: Define how pages are rendered. Each page type handles a specific kind of page (content notes, folder listings, tag listings, 404). The `PageTypeDispatcher` emitter routes pages to the appropriate page type plugin based on the content. +- **Bases Views**: Custom view renderers for the `bases-page` plugin's database-like view system. Plugins can register new view types (e.g., timeline, kanban) via the `ViewRegistry`. See [[making plugins#Bases Views]] for details. + +Note that plugin types are **not mutually exclusive** — a single plugin can be a transformer AND provide components (e.g., `obsidian-flavored-markdown`), or be a page type AND provide custom frames (e.g., `canvas-page`). ### Plugin Resolution diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md index 6a4d57b..bdffa40 100644 --- a/docs/advanced/creating components.md +++ b/docs/advanced/creating components.md @@ -109,6 +109,23 @@ Component.css = styles > [!warning] > Quartz does not use CSS modules so any styles you declare here apply _globally_. If you only want it to apply to your component, make sure you use specific class names and selectors. +### Internationalization + +Component plugins should use the i18n pattern for any user-facing strings. See [[making plugins#Internationalization (i18n)]] for the full setup guide. + +Quick reference: + +```tsx +import { i18n } from "../i18n" + +const MyComponent: QuartzComponent = ({ cfg }) => { + const t = i18n(cfg.locale ?? "en-US").components.myComponent + return

{t.title}

+} +``` + +Always provide at least an `en-US` locale as the fallback. Additional locales are optional but encouraged for international reach. + ### Scripts and Interactivity For interactivity, you can declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties on the component. These should be strings containing the JavaScript to be executed in the browser. diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index cf9aae4..c92f0ee 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -63,6 +63,25 @@ The plugin's `package.json` should declare dependencies on `@quartz-community/ty ## Plugin Types +## Choosing a Plugin Type + +Quartz supports six plugin capabilities. A single plugin can combine multiple types. + +| I want to... | Plugin Type | +| ------------------------------------------------ | ----------- | +| Transform Markdown/HTML content | Transformer | +| Decide which pages to publish | Filter | +| Generate output files (RSS, sitemaps, manifests) | Emitter | +| Define how a category of pages renders | Page Type | +| Add a UI component to the layout | Component | +| Add a custom view to the Bases database system | Bases View | + +These are **not mutually exclusive**. For example: + +- `obsidian-flavored-markdown` is both a **transformer** (processes OFM syntax) and provides **components** (mermaid rendering) +- `canvas-page` is a **page type** that also provides a custom **frame** +- A plugin could be a **transformer** that adds metadata AND a **component** that displays it + ### Transformers Transformers **map** over content, taking a Markdown file and outputting modified content or adding metadata to the file itself. @@ -466,6 +485,40 @@ When a user installs your plugin, Quartz automatically loads the frame from the > [!tip] > See the [`canvas-page`](https://github.com/quartz-community/canvas-page) plugin for a complete real-world example of a plugin-provided frame. +### Bases Views + +The `bases-page` plugin provides a database-like view system similar to Obsidian Bases. Other plugins can register custom view types via the `ViewRegistry`: + +```ts +import { viewRegistry } from "@quartz-community/bases-page"; +import type { ViewTypeRegistration } from "@quartz-community/bases-page"; + +viewRegistry.register({ + id: "timeline", + name: "Timeline", + icon: "git-branch", + render: ({ entries, view, slug, allSlugs }) => ( +
+ {entries.map(entry =>
{entry.properties.title}
)} +
+ ), + css: `.bases-timeline { display: flex; flex-direction: column; }`, + afterDOMLoaded: `document.addEventListener("nav", () => { /* setup */ })`, +}); +``` + +Each view registration includes: + +- `id`: Unique identifier (e.g., `"timeline"`, `"kanban"`) +- `name`: Display name shown in the view selector +- `icon`: Optional Lucide icon name +- `render`: Function that receives `ViewRendererProps` and returns Preact JSX +- `css`: Optional CSS string (deduplicated by view ID) +- `afterDOMLoaded`: Optional client-side script (same lifecycle as component scripts) +- `options`: Optional configuration passed to every render invocation + +The `ViewRegistry` is a global singleton (via `Symbol.for`) ensuring all copies of the module share the same registry. + ## Building and Testing ```shell @@ -475,6 +528,108 @@ npm run build npx tsup ``` +## What to Import from Where + +| You need... | Import from | +| ---------------------------------------------------------------------- | ----------------------------------------------------------------- | +| Type definitions (`QuartzTransformerPlugin`, `QuartzComponent`, etc.) | `@quartz-community/types` | +| Path utilities (`simplifySlug`, `resolveRelative`, `pathToRoot`) | `@quartz-community/utils/path` | +| DOM utilities (`removeAllChildren`, `registerEscapeHandler`) | `@quartz-community/utils/dom` | +| JSX conversion (`htmlToJsx`) | `@quartz-community/utils/jsx` | +| Language utilities (`classNames`, `capitalize`) | `@quartz-community/utils/lang` | +| Date/sort utilities (`formatDate`, `getDate`, `byDateAndAlphabetical`) | `@quartz-community/utils/date` and `@quartz-community/utils/sort` | +| HTML escaping (`escapeHTML`, `unescapeHTML`) | `@quartz-community/utils/escape` | +| Emoji utilities (`getIconCode`) | `@quartz-community/utils/emoji` | +| Browser runtime (`onNav`, `onRender`, `fetchContentIndex`) | `@quartz-community/runtime` | + +Do **not** import from `@jackyzha0/quartz` or from `vfile` directly. Use the community packages instead. + +## Internationalization (i18n) + +Plugins should provide their own translations for user-facing strings. Do **not** hardcode strings in components. + +### Setting Up i18n + +Create the following structure: + +``` +src/i18n/ +├── index.ts +└── locales/ + └── en-US.ts +``` + +**`src/i18n/locales/en-US.ts`** (required base locale): + +```ts +export default { + components: { + myPlugin: { + title: "My Plugin", + description: "A description", + itemCount: ({ count }: { count: number }) => (count === 1 ? "1 item" : `${count} items`), + }, + }, +} +``` + +**`src/i18n/index.ts`**: + +```ts +import enUS from "./locales/en-US" + +const locales: Record = { + "en-US": enUS, +} + +export function i18n(locale: string) { + return locales[locale] || enUS +} +``` + +### Using i18n in Components + +```tsx +import { i18n } from "../i18n" + +const MyComponent: QuartzComponent = ({ cfg }) => { + const locale = cfg.locale ?? "en-US" + const t = i18n(locale).components.myPlugin + return

{t.title}

+} +``` + +### Adding Translations + +To add a new locale, copy `en-US.ts`, translate the strings, and register it: + +```ts +// src/i18n/locales/fr-FR.ts +export default { + components: { + myPlugin: { + title: "Mon Plugin", + description: "Une description", + itemCount: ({ count }: { count: number }) => + count === 1 ? "1 élément" : `${count} éléments`, + }, + }, +} +``` + +```ts +// src/i18n/index.ts +import enUS from "./locales/en-US" +import frFR from "./locales/fr-FR" + +const locales: Record = { + "en-US": enUS, + "fr-FR": frFR, +} +``` + +Use [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag) locale codes (e.g., `en-US`, `de-DE`, `ja-JP`, `zh-CN`). For dynamic content, use function-based translations as shown with `itemCount` above. + ## Installing Your Plugin ```shell