docs: add package layering, plugin type guide, i18n guide, bases views, and import reference

This commit is contained in:
saberzero1
2026-04-03 19:06:48 +02:00
parent 1f555cf6c4
commit 287d41b2ac
3 changed files with 195 additions and 0 deletions

View File

@@ -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 }) => (
<div class="bases-timeline">
{entries.map(entry => <div>{entry.properties.title}</div>)}
</div>
),
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<string, typeof enUS> = {
"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 <h2>{t.title}</h2>
}
```
### 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<string, typeof enUS> = {
"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