From cdfb1bd85b63d98c70c963258cad64c8eaca4298 Mon Sep 17 00:00:00 2001 From: saberzero1 Date: Sat, 14 Feb 2026 01:35:44 +0100 Subject: [PATCH] docs: rewrite documentation for v5 plugin system Update feature docs, hosting, CI/CD, getting started, configuration, layout, architecture, creating components, making plugins, and migration guide to reflect the v5 community plugin architecture. --- docs/advanced/architecture.md | 43 ++++- docs/advanced/creating components.md | 240 ++++++++++--------------- docs/advanced/making plugins.md | 192 ++++++++++++++------ docs/ci-cd.md | 18 +- docs/configuration.md | 50 ++++-- docs/features/backlinks.md | 9 +- docs/features/breadcrumbs.md | 13 +- docs/features/comments.md | 9 +- docs/features/darkmode.md | 7 +- docs/features/full-text search.md | 8 +- docs/features/reader mode.md | 5 +- docs/features/recent notes.md | 18 +- docs/hosting.md | 36 ++-- docs/index.md | 3 +- docs/layout.md | 55 +++++- docs/migrating from Quartz 4.md | 253 ++++++++++++++++++++++++++- 16 files changed, 667 insertions(+), 292 deletions(-) diff --git a/docs/advanced/architecture.md b/docs/advanced/architecture.md index 33da89d90..d7dabc7fd 100644 --- a/docs/advanced/architecture.md +++ b/docs/advanced/architecture.md @@ -11,7 +11,7 @@ This question is best answered by tracing what happens when a user (you!) runs ` 1. After running `npx quartz build`, npm will look at `package.json` to find the `bin` entry for `quartz` which points at `./quartz/bootstrap-cli.mjs`. 2. This file has a [shebang]() line at the top which tells npm to execute it using Node. 3. `bootstrap-cli.mjs` is responsible for a few things: - 1. Parsing the command-line arguments using [yargs](http://yargs.js.org/). + 1. Parsing the command-line arguments using [yargs](http://yargs.js.org/). The `plugin` subcommand is also handled here for managing external plugins. 2. Transpiling and bundling the rest of Quartz (which is in Typescript) to regular JavaScript using [esbuild](https://esbuild.github.io/). The `esbuild` configuration here is slightly special as it also handles `.scss` file imports using [esbuild-sass-plugin v2](https://www.npmjs.com/package/esbuild-sass-plugin). Additionally, we bundle 'inline' client-side scripts (any `.inline.ts` file) that components declare using a custom `esbuild` plugin that runs another instance of `esbuild` which bundles for the browser instead of `node`. Modules of both types are imported as plain text. 3. Running the local preview server if `--serve` is set. This starts two servers: 1. A WebSocket server on port 3001 to handle hot-reload signals. This tracks all inbound connections and sends a 'rebuild' message a server-side change is detected (either content or configuration). @@ -49,4 +49,43 @@ This question is best answered by tracing what happens when a user (you!) runs ` 1. If the [[SPA Routing|enableSPA option]] is enabled in the [[configuration]], this `"nav"` event is also fired on any client-navigation to allow for components to unregister and reregister any event handlers and state. 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. -The architecture and design of the plugin system was intentionally left pretty vague here as this is described in much more depth in the guide on [[making plugins|making your own plugin]]. +## Plugin System + +### External Plugin Architecture + +Quartz v5 introduces a community plugin system. Plugins are standalone Git repositories that are cloned into `.quartz/plugins/` and re-exported through an auto-generated index file at `.quartz/plugins/index.ts`. + +### Plugin Types + +There are now four plugin categories: + +- **Transformers**: Map over content (parse frontmatter, generate descriptions, syntax highlighting) +- **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. + +### Plugin Resolution + +When `npx quartz plugin add github:quartz-community/explorer` is run: + +1. The repository is cloned into `.quartz/plugins/explorer/` +2. The plugin is built using `tsup` (defined in each plugin's `tsup.config.ts`) +3. An auto-generated `.quartz/plugins/index.ts` re-exports all installed plugins +4. The plugin's commit hash is recorded in `quartz.lock.json` + +### Plugin CLI Commands + +- `npx quartz plugin add github:quartz-community/` — Install a community plugin +- `npx quartz plugin update` — Update all plugins to latest commits +- `npx quartz plugin restore` — Restore plugins from locked commits in `quartz.lock.json` (used in CI/CD) +- `npx quartz plugin remove ` — Remove an installed plugin + +### Plugin Structure + +Each community plugin repository contains: + +- `src/index.ts` — Plugin entry point exporting the plugin function +- `tsup.config.ts` — Build configuration using tsup +- `package.json` — Declares dependencies on `@quartz-community/types` and `@quartz-community/utils` + +The architecture and design of the plugin system was intentionally left pretty vague here as this is described in much more depth in the guide on [[making plugins|creating plugins]]. diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md index 84e038012..d74610b7d 100644 --- a/docs/advanced/creating components.md +++ b/docs/advanced/creating components.md @@ -1,5 +1,5 @@ --- -title: Creating your own Quartz components +title: Creating Component Plugins --- > [!warning] @@ -20,17 +20,31 @@ However, HTML doesn't let you create reusable templates. If you wanted to create In effect, components allow you to write a JavaScript function that takes some data and produces HTML as an output. **While Quartz doesn't use React, it uses the same component concept to allow you to easily express layout templates in your Quartz site.** -## An Example Component +## Community Component Plugins -### Constructor +In v5, most components are community plugins — standalone repositories that export a `QuartzComponent`. These plugins are decoupled from the core Quartz repository, allowing for easier maintenance and sharing. -Component files are written in `.tsx` files that live in the `quartz/components` folder. These are re-exported in `quartz/components/index.ts` so you can use them in layouts and other components more easily. +### Getting Started -Each component file should have a default export that satisfies the `QuartzComponentConstructor` function signature. It's a function that takes in a single optional parameter `opts` and returns a Quartz Component. The type of the parameters `opts` is defined by the interface `Options` which you as the component creator also decide. +To create a new component plugin, you can use the official plugin template: -In your component, you can use the values from the configuration option to change the rendering behaviour inside of your component. For example, the component in the code snippet below will not render if the `favouriteNumber` option is below 0. +```shell +git clone https://github.com/quartz-community/plugin-template.git my-component +cd my-component +npm install +``` + +### Plugin Structure + +A component plugin's `src/index.ts` typically exports a function (a constructor) that returns a `QuartzComponent`. This allows users to pass configuration options to your component. + +```tsx title="src/index.ts" +import { + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, +} from "@quartz-community/types" -```tsx {11-17} interface Options { favouriteNumber: number } @@ -39,28 +53,25 @@ const defaultOptions: Options = { favouriteNumber: 42, } -export default ((userOpts?: Options) => { - const opts = { ...userOpts, ...defaultOpts } - function YourComponent(props: QuartzComponentProps) { - if (opts.favouriteNumber < 0) { - return null - } +const MyComponent: QuartzComponentConstructor = (userOpts?: Options) => { + const opts = { ...defaultOptions, ...userOpts } + const Component: QuartzComponent = (props: QuartzComponentProps) => { + if (opts.favouriteNumber < 0) return null return

My favourite number is {opts.favouriteNumber}

} - return YourComponent -}) satisfies QuartzComponentConstructor + return Component +} + +export default MyComponent ``` ### Props -The Quartz component itself (lines 11-17 highlighted above) looks like a React component. It takes in properties (sometimes called [props](https://react.dev/learn/passing-props-to-a-component)) and returns JSX. - All Quartz components accept the same set of props: -```tsx title="quartz/components/types.ts" -// simplified for sake of demonstration +```tsx export type QuartzComponentProps = { fileData: QuartzPluginData cfg: GlobalConfiguration @@ -70,50 +81,29 @@ export type QuartzComponentProps = { } ``` -- `fileData`: Any metadata [[making plugins|plugins]] may have added to the current page. +- `fileData`: Any metadata plugins may have added to the current page. - `fileData.slug`: slug of the current page. - `fileData.frontmatter`: any frontmatter parsed. - `cfg`: The `configuration` field in `quartz.config.ts`. -- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file. This is useful if you'd like to render the content using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) (you can find an example of this in `quartz/components/pages/Content.tsx`). +- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file. - `allFiles`: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure. -- `displayClass`: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting. Helpful if you want to conditionally hide a component on mobile or desktop. +- `displayClass`: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting. ### Styling -Quartz components can also define a `.css` property on the actual function component which will get picked up by Quartz. This is expected to be a CSS string which can either be inlined or imported from a `.scss` file. +In community plugins, styles are bundled with the plugin. You can define styles using the `.css` property on the component: -Note that inlined styles **must** be plain vanilla CSS: - -```tsx {6-10} title="quartz/components/YourComponent.tsx" -export default (() => { - function YourComponent() { - return

Example Component

- } - - YourComponent.css = ` - p.red-text { - color: red; - } - ` - - return YourComponent -}) satisfies QuartzComponentConstructor +```tsx +Component.css = ` + .my-component { color: red; } +` ``` -Imported styles, however, can be from SCSS files: +For SCSS, you can import it and assign it to the `.css` property. The build system will handle the transformation: -```tsx {1-2,9} title="quartz/components/YourComponent.tsx" -// assuming your stylesheet is in quartz/components/styles/YourComponent.scss -import styles from "./styles/YourComponent.scss" - -export default (() => { - function YourComponent() { - return

Example Component

- } - - YourComponent.css = styles - return YourComponent -}) satisfies QuartzComponentConstructor +```tsx +import styles from "./styles.scss" +Component.css = styles ``` > [!warning] @@ -121,128 +111,78 @@ export default (() => { ### Scripts and Interactivity -What about interactivity? Suppose you want to add an-click handler for example. Like the `.css` property on the component, you can also declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties that are strings that contain the script. +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. -```tsx title="quartz/components/YourComponent.tsx" -export default (() => { - function YourComponent() { - return - } +- `.beforeDOMLoaded`: Executed _before_ the page is done loading. Used for prefetching or early initialization. +- `.afterDOMLoaded`: Executed once the page has been completely loaded. - YourComponent.beforeDOMLoaded = ` - console.log("hello from before the page loads!") - ` - - YourComponent.afterDOMLoaded = ` - document.getElementById('btn').onclick = () => { - alert('button clicked!') - } - ` - return YourComponent -}) satisfies QuartzComponentConstructor -``` - -> [!hint] -> For those coming from React, Quartz components are different from React components in that it only uses JSX for templating and layout. Hooks like `useEffect`, `useState`, etc. are not rendered and other properties that accept functions like `onClick` handlers will not work. Instead, do it using a regular JS script that modifies the DOM element directly. - -As the names suggest, the `.beforeDOMLoaded` scripts are executed _before_ the page is done loading so it doesn't have access to any elements on the page. This is mostly used to prefetch any critical data. - -The `.afterDOMLoaded` script executes once the page has been completely loaded. This is a good place to setup anything that should last for the duration of a site visit (e.g. getting something saved from local storage). - -If you need to create an `afterDOMLoaded` script that depends on _page specific_ elements that may change when navigating to a new page, you can listen for the `"nav"` event that gets fired whenever a page loads (which may happen on navigation if [[SPA Routing]] is enabled). +If you need to create an `afterDOMLoaded` script that depends on page-specific elements that may change when navigating, listen for the `"nav"` event: ```ts document.addEventListener("nav", () => { // do page specific logic here - // e.g. attach event listeners const toggleSwitch = document.querySelector("#switch") as HTMLInputElement - toggleSwitch.addEventListener("change", switchTheme) - window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) + if (toggleSwitch) { + toggleSwitch.addEventListener("change", switchTheme) + window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) + } }) ``` -You can also add the equivalent of a `beforeunload` event for [[SPA Routing]] via the `prenav` event. +You can also use the `"prenav"` event, which fires before the page is replaced during SPA navigation. -```ts -document.addEventListener("prenav", () => { - // executed after an SPA navigation is triggered but - // before the page is replaced - // one usage pattern is to store things in sessionStorage - // in the prenav and then conditionally load then in the consequent - // nav -}) -``` - -It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. -This will get called on page navigation. +It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks during SPA navigation. #### Importing Code -Of course, it isn't always practical (nor desired!) to write your code as a string literal in the component. +In community plugins, TypeScript scripts should be transpiled at build time. The plugin template includes an `inlineScriptPlugin` in `tsup.config.ts` that automatically transpiles `.inline.ts` files imported as text: -Quartz supports importing component code through `.inline.ts` files. +```tsx title="src/index.ts" +import script from "./script.inline.ts" -```tsx title="quartz/components/YourComponent.tsx" -// @ts-ignore: typescript doesn't know about our inline bundling system -// so we need to silence the error -import script from "./scripts/graph.inline" - -export default (() => { - function YourComponent() { - return - } - - YourComponent.afterDOMLoaded = script - return YourComponent -}) satisfies QuartzComponentConstructor +const Component: QuartzComponent = (props) => { + return +} +Component.afterDOMLoaded = script ``` -```ts title="quartz/components/scripts/graph.inline.ts" -// any imports here are bundled for the browser -import * as d3 from "d3" +The `inlineScriptPlugin` handles transpiling TypeScript to browser-compatible JavaScript during the build step, allowing you to write type-safe client-side code. -document.getElementById("btn").onclick = () => { - alert("button clicked!") +### Installing Your Component + +Once your component is published (e.g., to GitHub or npm), users can install it using the Quartz CLI: + +```shell +npx quartz plugin add github:your-username/my-component +``` + +Then, they can use it in their `quartz.layout.ts`: + +```ts title="quartz.layout.ts" +import * as Plugin from "./.quartz/plugins" + +export const layout = { + defaults: { ... }, + byPageType: { + content: { + left: [Plugin.MyComponent()], + }, + }, } ``` -Additionally, like what is shown in the example above, you can import packages in `.inline.ts` files. This will be bundled by Quartz and included in the actual script. +## Internal Components -### Using a Component +Quartz also has internal components that provide layout utilities. These live in `quartz/components/` and are primarily used for structural purposes: -After creating your custom component, re-export it in `quartz/components/index.ts`: +- `Component.Head()` — renders the `` tag +- `Component.Spacer()` — adds flexible space +- `Component.Flex()` — flexible layout container +- `Component.MobileOnly()` — shows component only on mobile +- `Component.DesktopOnly()` — shows component only on desktop +- `Component.ConditionalRender()` — conditionally renders based on page data -```ts title="quartz/components/index.ts" {4,10} -import ArticleTitle from "./ArticleTitle" -import Content from "./pages/Content" -import Darkmode from "./Darkmode" -import YourComponent from "./YourComponent" - -export { ArticleTitle, Content, Darkmode, YourComponent } -``` - -Then, you can use it like any other component in `quartz.layout.ts` via `Component.YourComponent()`. See the [[configuration#Layout|layout]] section for more details. - -As Quartz components are just functions that return React components, you can compositionally use them in other Quartz components. - -```tsx title="quartz/components/AnotherComponent.tsx" -import YourComponentConstructor from "./YourComponent" - -export default (() => { - const YourComponent = YourComponentConstructor() - - function AnotherComponent(props: QuartzComponentProps) { - return ( -
-

It's nested!

- -
- ) - } - - return AnotherComponent -}) satisfies QuartzComponentConstructor -``` +See [[layout-components]] for more details on these utilities. > [!hint] -> Look in `quartz/components` for more examples of components in Quartz as reference for your own components! +> Look at existing community plugins like [Explorer](https://github.com/quartz-community/explorer) or [Darkmode](https://github.com/quartz-community/darkmode) for real-world examples. diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index f5cb19901..f920d8e2d 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -18,20 +18,52 @@ type QuartzPluginInstance = | QuartzTransformerPluginInstance | QuartzFilterPluginInstance | QuartzEmitterPluginInstance + | QuartzPageTypePluginInstance ``` The following sections will go into detail for what methods can be implemented for each plugin type. Before we do that, let's clarify a few more ambiguous types: -- `BuildCtx` is defined in `quartz/ctx.ts`. It consists of +- `BuildCtx` is defined in `@quartz-community/types`. It consists of - `argv`: The command line arguments passed to the Quartz [[build]] command - `cfg`: The full Quartz [[configuration]] - `allSlugs`: a list of all the valid content slugs (see [[paths]] for more information on what a slug is) -- `StaticResources` is defined in `quartz/resources.tsx`. It consists of - - `css`: a list of CSS style definitions that should be loaded. A CSS style is described with the `CSSResource` type which is also defined in `quartz/resources.tsx`. It accepts either a source URL or the inline content of the stylesheet. - - `js`: a list of scripts that should be loaded. A script is described with the `JSResource` type which is also defined in `quartz/resources.tsx`. It allows you to define a load time (either before or after the DOM has been loaded), whether it should be a module, and either the source URL or the inline content of the script. +- `StaticResources` is defined in `@quartz-community/types`. It consists of + - `css`: a list of CSS style definitions that should be loaded. A CSS style is described with the `CSSResource` type. It accepts either a source URL or the inline content of the stylesheet. + - `js`: a list of scripts that should be loaded. A script is described with the `JSResource` type. It allows you to define a load time (either before or after the DOM has been loaded), whether it should be a module, and either the source URL or the inline content of the script. - `additionalHead`: a list of JSX elements or functions that return JSX elements to be added to the `` tag of the page. Functions receive the page's data as an argument and can conditionally render elements. -## Transformers +## Getting Started + +In v5, plugins are standalone repositories. The easiest way to create one is using the plugin template: + +```shell +# Use the plugin template to create a new repository on GitHub +# Then clone it locally +git clone https://github.com/your-username/my-plugin.git +cd my-plugin +npm install +``` + +The template provides the build configuration (`tsup.config.ts`), TypeScript setup, and correct package structure. + +## Plugin Structure + +The basic file structure of a plugin is as follows: + +``` +my-plugin/ +├── src/ +│ └── index.ts # Plugin entry point +├── tsup.config.ts # Build configuration +├── package.json # Dependencies and exports +└── tsconfig.json # TypeScript configuration +``` + +The plugin's `package.json` should declare dependencies on `@quartz-community/types` (for type definitions) and optionally `@quartz-community/utils` (for shared utilities). + +## Plugin Types + +### Transformers Transformers **map** over content, taking a Markdown file and outputting modified content or adding metadata to the file itself. @@ -52,15 +84,15 @@ All transformer plugins must define at least a `name` field to register the plug - `htmlPlugins` defines a list of [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md). Similar to how `remark` works, `rehype` is a tool that transforms HTML to HTML in a structured way. - `externalResources` defines any external resources the plugin may need to load on the client-side for it to work properly. -Normally for both `remark` and `rehype`, you can find existing plugins that you can use to . If you'd like to create your own `remark` or `rehype` plugin, checkout the [guide to creating a plugin](https://unifiedjs.com/learn/guide/create-a-plugin/) using `unified` (the underlying AST parser and transformer library). +Normally for both `remark` and `rehype`, you can find existing plugins that you can use. If you'd like to create your own `remark` or `rehype` plugin, checkout the [guide to creating a plugin](https://unifiedjs.com/learn/guide/create-a-plugin/) using `unified` (the underlying AST parser and transformer library). A good example of a transformer plugin that borrows from the `remark` and `rehype` ecosystems is the [[plugins/Latex|Latex]] plugin: -```ts title="quartz/plugins/transformers/latex.ts" +```ts import remarkMath from "remark-math" import rehypeKatex from "rehype-katex" import rehypeMathjax from "rehype-mathjax/svg" -import { QuartzTransformerPlugin } from "../types" +import { QuartzTransformerPlugin } from "@quartz-community/types" interface Options { renderEngine: "katex" | "mathjax" @@ -109,6 +141,8 @@ export const Latex: QuartzTransformerPlugin = (opts?: Options) => { Another common thing that transformer plugins will do is parse a file and add extra data for that file: ```ts +import { QuartzTransformerPlugin } from "@quartz-community/types" + export const AddWordCount: QuartzTransformerPlugin = () => { return { name: "AddWordCount", @@ -140,45 +174,50 @@ declare module "vfile" { Finally, you can also perform transformations over Markdown or HTML ASTs using the `visit` function from the `unist-util-visit` package or the `findAndReplace` function from the `mdast-util-find-and-replace` package. ```ts +import { visit } from "unist-util-visit" +import { findAndReplace } from "mdast-util-find-and-replace" +import { QuartzTransformerPlugin } from "@quartz-community/types" +import { Link } from "mdast" + export const TextTransforms: QuartzTransformerPlugin = () => { return { name: "TextTransforms", markdownPlugins() { - return [() => { - return (tree, file) => { - // replace _text_ with the italics version - findAndReplace(tree, /_(.+)_/, (_value: string, ...capture: string[]) => { - // inner is the text inside of the () of the regex - const [inner] = capture - // return an mdast node - // https://github.com/syntax-tree/mdast - return { - type: "emphasis", - children: [{ type: 'text', value: inner }] - } - }) + return [ + () => { + return (tree, file) => { + // replace _text_ with the italics version + findAndReplace(tree, /_(.+)_/, (_value: string, ...capture: string[]) => { + // inner is the text inside of the () of the regex + const [inner] = capture + // return an mdast node + // https://github.com/syntax-tree/mdast + return { + type: "emphasis", + children: [{ type: "text", value: inner }], + } + }) - // remove all links (replace with just the link content) - // match by 'type' field on an mdast node - // https://github.com/syntax-tree/mdast#link in this example - visit(tree, "link", (link: Link) => { - return { - type: "paragraph" - children: [{ type: 'text', value: link.title }] - } - }) - } - }] - } + // remove all links (replace with just the link content) + // match by 'type' field on an mdast node + // https://github.com/syntax-tree/mdast#link in this example + visit(tree, "link", (link: Link) => { + return { + type: "paragraph", + children: [{ type: "text", value: link.title }], + } + }) + } + }, + ] + }, } } ``` -All transformer plugins can be found under `quartz/plugins/transformers`. If you decide to write your own transformer plugin, don't forget to re-export it under `quartz/plugins/transformers/index.ts` - A parting word: transformer plugins are quite complex so don't worry if you don't get them right away. Take a look at the built in transformers and see how they operate over content to get a better sense for how to accomplish what you are trying to do. -## Filters +### Filters Filters **filter** content, taking the output of all the transformers and determining what files to actually keep and what to discard. @@ -197,8 +236,8 @@ A filter plugin must define a `name` field and a `shouldPublish` function that t For example, here is the built-in plugin for removing drafts: -```ts title="quartz/plugins/filters/draft.ts" -import { QuartzFilterPlugin } from "../types" +```ts +import { QuartzFilterPlugin } from "@quartz-community/types" export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({ name: "RemoveDrafts", @@ -210,7 +249,7 @@ export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({ }) ``` -## Emitters +### Emitters Emitters **reduce** over content, taking in a list of all the transformed and filtered content and creating output files. @@ -242,7 +281,7 @@ An emitter plugin must define a `name` field, an `emit` function, and a `getQuar - `partialEmit` is an optional function that enables incremental builds. It receives information about which files have changed (`changeEvents`) and can selectively rebuild only the necessary files. This is useful for optimizing build times in development mode. If `partialEmit` is undefined, it will default to the `emit` function. - `getQuartzComponents` declares which Quartz components the emitter uses to construct its pages. -Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `quartz/plugins/emitters/helpers.ts` if you are creating files that contain text. `write` has the following signature: +Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `@quartz-community/utils` if you are creating files that contain text. `write` has the following signature: ```ts export type WriteOptions = (data: { @@ -262,26 +301,23 @@ This is a thin wrapper around writing to the appropriate output folder and ensur If you are creating an emitter plugin that needs to render components, there are three more things to be aware of: - Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information. -- You can use the `renderPage` function defined in `quartz/components/renderPage.tsx` to render Quartz components into HTML. -- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `quartz/util/jsx.ts`. An example of this can be found in `quartz/components/pages/Content.tsx`. +- You can use the `renderPage` function defined in `@quartz-community/utils` to render Quartz components into HTML. +- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `@quartz-community/utils`. For example, the following is a simplified version of the content page plugin that renders every single page. -```tsx title="quartz/plugins/emitters/contentPage.tsx" +```tsx +import { QuartzEmitterPlugin, FullPageLayout, QuartzComponentProps } from "@quartz-community/types" +import { renderPage, canonicalizeServer, pageResources, write } from "@quartz-community/utils" + export const ContentPage: QuartzEmitterPlugin = () => { - // construct the layout - const layout: FullPageLayout = { - ...sharedPageComponents, - ...defaultContentPageLayout, - pageBody: Content(), - } - const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = layout return { name: "ContentPage", - getQuartzComponents() { + getQuartzComponents(ctx) { + const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = ctx.cfg.layout return [head, ...header, ...beforeBody, pageBody, ...afterBody, ...left, ...right, footer] }, - async emit(ctx, content, resources, emit): Promise { + async emit(ctx, content, resources): Promise { const cfg = ctx.cfg.configuration const fps: FilePath[] = [] const allFiles = content.map((c) => c[1].data) @@ -297,8 +333,9 @@ export const ContentPage: QuartzEmitterPlugin = () => { allFiles, } - const content = renderPage(cfg, slug, componentData, opts, externalResources) - const fp = await emit({ + const content = renderPage(cfg, slug, componentData, {}, externalResources) + const fp = await write({ + ctx, content, slug: file.data.slug!, ext: ".html", @@ -312,7 +349,48 @@ export const ContentPage: QuartzEmitterPlugin = () => { } ``` -Note that it takes in a `FullPageLayout` as the options. It's made by combining a `SharedLayout` and a `PageLayout` both of which are provided through the `quartz.layout.ts` file. +### Page Types -> [!hint] -> Look in `quartz/plugins` for more examples of plugins in Quartz as reference for your own plugins! +Page types define how a category of pages is rendered. They are configured in the `pageTypes` array in `quartz.config.ts`. + +```ts +export type QuartzPageTypePluginInstance = { + name: string + pageType: string // e.g. "content", "folder", "tag" + getQuartzComponents(ctx: BuildCtx): QuartzComponent[] + emit( + ctx: BuildCtx, + content: ProcessedContent[], + resources: StaticResources, + layout: FullPageLayout, + ): Promise | AsyncGenerator +} +``` + +## Building and Testing + +```shell +# Build the plugin +npm run build +# or +npx tsup +``` + +## Installing Your Plugin + +```shell +# In your Quartz project +npx quartz plugin add github:your-username/my-plugin +``` + +Then add the plugin to the appropriate array in `quartz.config.ts`: + +```ts +import * as ExternalPlugin from "./.quartz/plugins" +// ... +transformers: [ExternalPlugin.MyPlugin()] +``` + +## Component Plugins + +For plugins that provide visual components (like Explorer, Graph, Search), see the [[creating components|creating component plugins]] guide. diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 2f645047e..e797bee0f 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -23,7 +23,7 @@ This installs all dependencies including `@quartz-community/types` which is requ ### 2. Install Git-based plugins ```bash -npx quartz plugin install +npx quartz plugin restore ``` This reads `quartz.lock.json` and clones the exact plugin versions specified. @@ -61,7 +61,7 @@ jobs: run: npm ci - name: Install Quartz plugins - run: npx quartz plugin install + run: npx quartz plugin restore - name: Build Quartz run: npx quartz build @@ -86,7 +86,7 @@ build: - .quartz/plugins/ script: - npm ci - - npx quartz plugin install + - npx quartz plugin restore - npx quartz build artifacts: paths: @@ -98,14 +98,14 @@ build: For Netlify, use a custom build command: ```bash -npm ci && npx quartz plugin install && npx quartz build +npm ci && npx quartz plugin restore && npx quartz build ``` Add this to your `netlify.toml`: ```toml [build] - command = "npm ci && npx quartz plugin install && npx quartz build" + command = "npm ci && npx quartz plugin restore && npx quartz build" publish = "public" [build.environment] @@ -116,7 +116,7 @@ Add this to your `netlify.toml`: For Vercel, configure the build settings: -- **Build Command:** `npm ci && npx quartz plugin install && npx quartz build` +- **Build Command:** `npm ci && npx quartz plugin restore && npx quartz build` - **Output Directory:** `public` - **Node.js Version:** `22.x` @@ -133,7 +133,7 @@ RUN npm ci # Install plugins from lockfile COPY quartz.lock.json . -RUN npx quartz plugin install +RUN npx quartz plugin restore FROM node:22-slim WORKDIR /usr/src/app @@ -219,7 +219,7 @@ The key change from traditional npm-based plugin systems: | Step | Traditional | Git-based | | --------------- | -------------------- | --------------------------- | | Install deps | `npm ci` | `npm ci` | -| Install plugins | (included in npm ci) | `npx quartz plugin install` | +| Install plugins | (included in npm ci) | `npx quartz plugin restore` | | Build | `npx quartz build` | `npx quartz build` | -**Always run `npx quartz plugin install` after `npm ci` and before `npx quartz build`.** +**Always run `npx quartz plugin restore` after `npm ci` and before `npx quartz build`.** diff --git a/docs/configuration.md b/docs/configuration.md index dcc52bcc9..73c89287b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -72,43 +72,61 @@ plugins: { transformers: [...], filters: [...], emitters: [...], + pageTypes: [...], } ``` - [[tags/plugin/transformer|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description) - [[tags/plugin/filter|Filters]] **filter** content (e.g. filtering out drafts) - [[tags/plugin/emitter|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag) +- **Page Types** define how different types of pages are rendered (content pages, folder listings, tag listings) -You can customize the behaviour of Quartz by adding, removing and reordering plugins in the `transformers`, `filters` and `emitters` fields. +### Internal vs External Plugins -> [!note] -> Each node is modified by every transformer _in order_. Some transformers are position sensitive, so you may need to pay particular attention to whether they need to come before or after certain other plugins. - -You should take care to add the plugin to the right entry corresponding to its plugin type. For example, to add the [[ExplicitPublish]] plugin (a [[tags/plugin/filter|Filter]]), you would add the following line: +Quartz distinguishes between internal plugins that are bundled with Quartz and community plugins that are installed separately. ```ts title="quartz.config.ts" -filters: [ - ... - Plugin.ExplicitPublish(), - ... +import * as Plugin from "./quartz/plugins" // internal plugins +import * as ExternalPlugin from "./.quartz/plugins" // community plugins +``` + +Internal plugins (like `Plugin.FrontMatter()`) are bundled with Quartz. Community plugins (like `ExternalPlugin.Explorer()`) are installed separately. + +### Community Plugins + +The `externalPlugins` array in your configuration declares which community plugin repositories to install. Each entry is a GitHub repository reference. + +```ts title="quartz.config.ts" +externalPlugins: [ + "github:quartz-community/explorer", + "github:quartz-community/syntax-highlighting", + // ... other community plugins ], ``` -To remove a plugin, you should remove all occurrences of it in the `quartz.config.ts`. +To install a community plugin, you can use the following command: -To customize plugins further, some plugins may also have their own configuration settings that you can pass in. If you do not pass in a configuration, the plugin will use its default settings. +```shell +npx quartz plugin add github:quartz-community/explorer +``` -For example, the [[plugins/Latex|Latex]] plugin allows you to pass in a field specifying the `renderEngine` to choose between Katex and MathJax. +This adds the plugin to `externalPlugins` and installs it to `.quartz/plugins/`. + +### Usage + +You can customize the behaviour of Quartz by adding, removing and reordering plugins in the `transformers`, `filters`, `emitters`, and `pageTypes` fields. You can mix internal and external plugins as needed. ```ts title="quartz.config.ts" transformers: [ - Plugin.FrontMatter(), // use default options - Plugin.Latex({ renderEngine: "katex" }), // set some custom options + Plugin.FrontMatter(), // internal + ExternalPlugin.CreatedModifiedDate({ + // community + priority: ["frontmatter", "git", "filesystem"], + }), + ExternalPlugin.Latex({ renderEngine: "katex" }), // community with options ] ``` -Some plugins are included by default in the [`quartz.config.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz.config.ts), but there are more available. - You can see a list of all plugins and their configuration options [[tags/plugin|here]]. If you'd like to make your own plugins, see the [[making plugins|making custom plugins]] guide. diff --git a/docs/features/backlinks.md b/docs/features/backlinks.md index 6862720e1..be9b2ab71 100644 --- a/docs/features/backlinks.md +++ b/docs/features/backlinks.md @@ -8,8 +8,7 @@ A backlink for a note is a link from another note to that note. Links in the bac ## Customization -- Removing backlinks: delete all usages of `Component.Backlinks()` from `quartz.layout.ts`. -- Hide when empty: hide `Backlinks` if given page doesn't contain any backlinks (default to `true`). To disable this, use `Component.Backlinks({ hideWhenEmpty: false })`. -- Component: `quartz/components/Backlinks.tsx` -- Style: `quartz/components/styles/backlinks.scss` -- Script: `quartz/components/scripts/search.inline.ts` +- Removing backlinks: delete all usages of `Plugin.Backlinks()` from `quartz.layout.ts`. +- Hide when empty: hide `Backlinks` if given page doesn't contain any backlinks (default to `true`). To disable this, use `Plugin.Backlinks({ hideWhenEmpty: false })`. +- Install: `npx quartz plugin add github:quartz-community/backlinks` +- Source: [`quartz-community/backlinks`](https://github.com/quartz-community/backlinks) diff --git a/docs/features/breadcrumbs.md b/docs/features/breadcrumbs.md index f3545059d..d2c54dad2 100644 --- a/docs/features/breadcrumbs.md +++ b/docs/features/breadcrumbs.md @@ -10,12 +10,12 @@ By default, the element at the very top of your page is the breadcrumb navigatio ## Customization -Most configuration can be done by passing in options to `Component.Breadcrumbs()`. +Most configuration can be done by passing in options to `Plugin.Breadcrumbs()`. For example, here's what the default configuration looks like: ```typescript title="quartz.layout.ts" -Component.Breadcrumbs({ +Plugin.Breadcrumbs({ spacerSymbol: "❯", // symbol between crumbs rootName: "Home", // name of first/root element resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles @@ -25,11 +25,10 @@ Component.Breadcrumbs({ When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field. -You can also adjust where the breadcrumbs will be displayed by adjusting the [[layout]] (moving `Component.Breadcrumbs()` up or down) +You can also adjust where the breadcrumbs will be displayed by adjusting the [[layout]] (moving `Plugin.Breadcrumbs()` up or down) Want to customize it even more? -- Removing breadcrumbs: delete all usages of `Component.Breadcrumbs()` from `quartz.layout.ts`. -- Component: `quartz/components/Breadcrumbs.tsx` -- Style: `quartz/components/styles/breadcrumbs.scss` -- Script: inline at `quartz/components/Breadcrumbs.tsx` +- Removing breadcrumbs: delete all usages of `Plugin.Breadcrumbs()` from `quartz.layout.ts`. +- Install: `npx quartz plugin add github:quartz-community/breadcrumbs` +- Source: [`quartz-community/breadcrumbs`](https://github.com/quartz-community/breadcrumbs) diff --git a/docs/features/comments.md b/docs/features/comments.md index 6e5a25ca1..b7f156922 100644 --- a/docs/features/comments.md +++ b/docs/features/comments.md @@ -30,11 +30,11 @@ After entering both your repository and selecting the discussion category, Giscu ![[giscus-results.png]] -Finally, in `quartz.layout.ts`, edit the `afterBody` field of `sharedPageComponents` to include the following options but with the values you got from above: +Finally, in `quartz.layout.ts`, edit the `afterBody` field of the `defaults` layout to include the following options but with the values you got from above: ```ts title="quartz.layout.ts" afterBody: [ - Component.Comments({ + Plugin.Comments({ provider: 'giscus', options: { // from data-repo @@ -52,6 +52,9 @@ afterBody: [ ], ``` +> [!note] +> Install the comments plugin first: `npx quartz plugin add github:quartz-community/comments` + ### Customization Quartz also exposes a few of the other Giscus options as well and you can provide them the same way `repo`, `repoId`, `category`, and `categoryId` are provided. @@ -108,7 +111,7 @@ For example, if you have a light theme `light-theme.css`, a dark theme `dark-the ```ts afterBody: [ - Component.Comments({ + Plugin.Comments({ provider: 'giscus', options: { // Other options diff --git a/docs/features/darkmode.md b/docs/features/darkmode.md index dff75b44d..e5c030c31 100644 --- a/docs/features/darkmode.md +++ b/docs/features/darkmode.md @@ -8,10 +8,9 @@ Quartz supports darkmode out of the box that respects the user's theme preferenc ## Customization -- Removing darkmode: delete all usages of `Component.Darkmode()` from `quartz.layout.ts`. -- Component: `quartz/components/Darkmode.tsx` -- Style: `quartz/components/styles/darkmode.scss` -- Script: `quartz/components/scripts/darkmode.inline.ts` +- Removing darkmode: delete all usages of `Plugin.Darkmode()` from `quartz.layout.ts`. +- Install: `npx quartz plugin add github:quartz-community/darkmode` +- Source: [`quartz-community/darkmode`](https://github.com/quartz-community/darkmode) You can also listen to the `themechange` event to perform any custom logic when the theme changes. diff --git a/docs/features/full-text search.md b/docs/features/full-text search.md index 85ec03006..a8b2161d5 100644 --- a/docs/features/full-text search.md +++ b/docs/features/full-text search.md @@ -23,8 +23,6 @@ It properly tokenizes Chinese, Korean, and Japenese characters and constructs se ## Customization -- Removing search: delete all usages of `Component.Search()` from `quartz.layout.ts`. -- Component: `quartz/components/Search.tsx` -- Style: `quartz/components/styles/search.scss` -- Script: `quartz/components/scripts/search.inline.ts` - - You can edit `contextWindowWords`, `numSearchResults` or `numTagResults` to suit your needs +- Removing search: delete all usages of `Plugin.Search()` from `quartz.layout.ts`. +- Install: `npx quartz plugin add github:quartz-community/search` +- Source: [`quartz-community/search`](https://github.com/quartz-community/search) diff --git a/docs/features/reader mode.md b/docs/features/reader mode.md index d1c142916..d08c37d04 100644 --- a/docs/features/reader mode.md +++ b/docs/features/reader mode.md @@ -12,9 +12,12 @@ Reader Mode is enabled by default. To disable it, you can remove the component f ```ts // Remove or comment out this line -Component.ReaderMode(), +Plugin.ReaderMode(), ``` +- Install: `npx quartz plugin add github:quartz-community/reader-mode` +- Source: [`quartz-community/reader-mode`](https://github.com/quartz-community/reader-mode) + ## Usage The Reader Mode toggle appears as a button with a book icon. When clicked: diff --git a/docs/features/recent notes.md b/docs/features/recent notes.md index 75406e504..89f711a4b 100644 --- a/docs/features/recent notes.md +++ b/docs/features/recent notes.md @@ -3,15 +3,15 @@ title: Recent Notes tags: component --- -Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Component.RecentNotes` in `quartz.layout.ts`. +Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Plugin.RecentNotes` in `quartz.layout.ts`. ## Customization -- Changing the title from "Recent notes": pass in an additional parameter to `Component.RecentNotes({ title: "Recent writing" })` -- Changing the number of recent notes: pass in an additional parameter to `Component.RecentNotes({ limit: 5 })` -- Display the note's tags (defaults to true): `Component.RecentNotes({ showTags: false })` -- Show a 'see more' link: pass in an additional parameter to `Component.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists. -- Customize filtering: pass in an additional parameter to `Component.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`. -- Customize sorting: pass in an additional parameter to `Component.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`. See `byDateAndAlphabetical` in `quartz/components/PageList.tsx` for an example. -- Component: `quartz/components/RecentNotes.tsx` -- Style: `quartz/components/styles/recentNotes.scss` +- Changing the title from "Recent notes": pass in an additional parameter to `Plugin.RecentNotes({ title: "Recent writing" })` +- Changing the number of recent notes: pass in an additional parameter to `Plugin.RecentNotes({ limit: 5 })` +- Display the note's tags (defaults to true): `Plugin.RecentNotes({ showTags: false })` +- Show a 'see more' link: pass in an additional parameter to `Plugin.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists. +- Customize filtering: pass in an additional parameter to `Plugin.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`. +- Customize sorting: pass in an additional parameter to `Plugin.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`. +- Install: `npx quartz plugin add github:quartz-community/recent-notes` +- Source: [`quartz-community/recent-notes`](https://github.com/quartz-community/recent-notes) diff --git a/docs/hosting.md b/docs/hosting.md index e3d3d8825..644063515 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -18,19 +18,22 @@ However, if you'd like to publish your site to the world, you need a way to host 2. In Account Home, select **Compute (Workers)** > **Workers & Pages** > **Create application** > **Pages** > **Connect to Git**. 3. Select the new GitHub repository that you created and, in the **Set up builds and deployments** section, provide the following information: -| Configuration option | Value | -| ---------------------- | ------------------ | -| Production branch | `v4` | -| Framework preset | `None` | -| Build command | `npx quartz build` | -| Build output directory | `public` | +| Configuration option | Value | +| ---------------------- | ----------------------------------------------- | +| Production branch | `v5` | +| Framework preset | `None` | +| Build command | `npx quartz plugin restore && npx quartz build` | +| Build output directory | `public` | Press "Save and deploy" and Cloudflare should have a deployed version of your site in about a minute. Then, every time you sync your Quartz changes to GitHub, your site should be updated. To add a custom domain, check out [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/custom-domains/). > [!warning] -> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz build`). +> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz plugin restore && npx quartz build`). + +> [!note] +> For more detailed CI/CD configuration including caching and plugin management, see [[ci-cd]]. ## GitHub Pages @@ -42,7 +45,7 @@ name: Deploy Quartz site to GitHub Pages on: push: branches: - - v4 + - v5 permissions: contents: read @@ -142,11 +145,11 @@ Before deploying to Vercel, a `vercel.json` file is required at the root of the 3. Give the project a name (lowercase characters and hyphens only) 4. Check that these configuration options are set: -| Configuration option | Value | -| ----------------------------------------- | ------------------ | -| Framework Preset | `Other` | -| Root Directory | `./` | -| Build and Output Settings > Build Command | `npx quartz build` | +| Configuration option | Value | +| ----------------------------------------- | ----------------------------------------------- | +| Framework Preset | `Other` | +| Root Directory | `./` | +| Build and Output Settings > Build Command | `npx quartz plugin restore && npx quartz build` | 5. Press Deploy. Once it's live, you'll have 2 `*.vercel.app` URLs to view the page. @@ -177,7 +180,7 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c 1. Log in to the [Netlify dashboard](https://app.netlify.com/) and click "Add new site". 2. Select your Git provider and repository containing your Quartz project. -3. Under "Build command", enter `npx quartz build`. +3. Under "Build command", enter `npx quartz plugin restore && npx quartz build`. 4. Under "Publish directory", enter `public`. 5. Press Deploy. Once it's live, you'll have a `*.netlify.app` URL to view the page. 6. To add a custom domain, check "Domain management" in the left sidebar, just like with Vercel. @@ -200,11 +203,12 @@ cache: # Cache modules in between jobs build: stage: build rules: - - if: '$CI_COMMIT_REF_NAME == "v4"' + - if: '$CI_COMMIT_REF_NAME == "v5"' before_script: - hash -r - npm ci --cache .npm --prefer-offline script: + - npx quartz plugin restore - npx quartz build artifacts: paths: @@ -215,7 +219,7 @@ build: pages: stage: deploy rules: - - if: '$CI_COMMIT_REF_NAME == "v4"' + - if: '$CI_COMMIT_REF_NAME == "v5"' script: - echo "Deploying to GitLab Pages..." artifacts: diff --git a/docs/index.md b/docs/index.md index 68ea5d4e6..044f09e17 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,9 +11,10 @@ Quartz requires **at least [Node](https://nodejs.org/) v22** and `npm` v10.9.2 t Then, in your terminal of choice, enter the following commands line by line: ```shell -git clone https://github.com/jackyzha0/quartz.git +git clone -b v5 https://github.com/jackyzha0/quartz.git cd quartz npm i +npx quartz plugin restore npx quartz create ``` diff --git a/docs/layout.md b/docs/layout.md index be6294939..fc32bd4b8 100644 --- a/docs/layout.md +++ b/docs/layout.md @@ -2,7 +2,12 @@ title: Layout --- -Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To enable easy customization, these emitters allow you to fully rearrange the layout of the page. The default page layouts can be found in `quartz.layout.ts`. +Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To enable easy customization, these emitters allow you to fully rearrange the layout of the page. + +In v5, the layout is defined in `quartz.layout.ts` using a `defaults` + `byPageType` structure. + +- `defaults` contains layout components shared across ALL page types (head, header, afterBody, footer). +- `byPageType` contains per-page-type overrides (content, folder, tag, 404) for beforeBody, left, and right sections. Each page is composed of multiple different sections which contain `QuartzComponents`. The following code snippet lists all of the valid sections that you can add components to: @@ -31,11 +36,49 @@ These correspond to following parts of the page: > There are two additional layout fields that are _not_ shown in the above diagram. > > 1. `head` is a single component that renders the `` [tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) in the HTML. This doesn't appear visually on the page and is only is responsible for metadata about the document like the tab title, scripts, and styles. -> 2. `header` is a set of components that are laid out horizontally and appears _before_ the `beforeBody` section. This enables you to replicate the old Quartz 3 header bar where the title, search bar, and dark mode toggle. By default, Quartz 5 doesn't place any components in the `header`. +> 2. `header` is a set of components that are laid out horizontally and appears _before_ the `beforeBody` section. This enables you to replicate the old Quartz 3 header bar where the title, search bar, and dark mode toggle. By default, Quartz doesn't place any components in the `header`. -Quartz **components**, like plugins, can take in additional properties as configuration options. If you're familiar with React terminology, you can think of them as Higher-order Components. +### Configuration -See [a list of all the components](component.md) for all available components along with their configuration options. Additionally, Quartz provides several built-in higher-order components for layout composition - see [[layout-components]] for more details. +Layout components are imported from two main sources: + +```ts title="quartz.layout.ts" +import * as Component from "./quartz/components" // internal HOC components +import * as Plugin from "./.quartz/plugins" // community component plugins +``` + +Internal components (`Component.Head()`, `Component.Spacer()`, `Component.Flex()`, `Component.MobileOnly()`, `Component.DesktopOnly()`, `Component.ConditionalRender()`) are layout utilities. Community component plugins (`Plugin.Explorer()`, `Plugin.Search()`, `Plugin.Darkmode()`, etc.) provide the actual UI features. + +Here is a simplified example of the layout structure: + +```ts title="quartz.layout.ts" +export const layout = { + defaults: { + head: Component.Head(), + header: [], + afterBody: [], + footer: Plugin.Footer({ links: { ... } }), + }, + byPageType: { + content: { + beforeBody: [Plugin.ArticleTitle(), Plugin.ContentMeta(), Plugin.TagList()], + left: [Plugin.PageTitle(), Plugin.Search(), Plugin.Explorer()], + right: [Plugin.Graph(), Plugin.TableOfContents(), Plugin.Backlinks()], + }, + folder: { + beforeBody: [Plugin.Breadcrumbs(), Plugin.ArticleTitle()], + left: [Plugin.PageTitle(), Plugin.Search(), Plugin.Explorer()], + right: [], + }, + tag: { ... }, + "404": { beforeBody: [], left: [], right: [] }, + }, +} +``` + +Fields defined in `defaults` can be overridden by specific entries in `byPageType`. + +Community component plugins are installed via `npx quartz plugin add github:quartz-community/` and imported from `.quartz/plugins`. See [[layout-components]] for built-in layout utilities (Flex, MobileOnly, DesktopOnly, etc.). You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz. @@ -58,9 +101,9 @@ $breakpoints: ( ### Style -Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz 5, like Quartz 4 and Quartz 3, uses [Sass](https://sass-lang.com/guide/) for styling. +Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz uses [Sass](https://sass-lang.com/guide/) for styling. You can see the base style sheet in `quartz/styles/base.scss` and write your own in `quartz/styles/custom.scss`. > [!note] -> Some components may provide their own styling as well! For example, `quartz/components/Darkmode.tsx` imports styles from `quartz/components/styles/darkmode.scss`. If you'd like to customize styling for a specific component, double check the component definition to see how its styles are defined. +> Some components may provide their own styling as well! Community plugins bundle their own styles. If you'd like to customize styling for a specific component, double check the component definition to see how its styles are defined. diff --git a/docs/migrating from Quartz 4.md b/docs/migrating from Quartz 4.md index 4b64da341..18b2504bf 100644 --- a/docs/migrating from Quartz 4.md +++ b/docs/migrating from Quartz 4.md @@ -2,4 +2,255 @@ title: "Migrating from Quartz 4" --- -stub +## Overview + +Quartz 5 introduces a community plugin system that fundamentally changes how plugins and components are managed. Most plugins that were built into Quartz 4 are now standalone community plugins maintained under the [quartz-community](https://github.com/quartz-community) organization. This guide walks through the changes needed to migrate your configuration. + +## What Changed + +List the key architectural changes: + +- **Plugin system**: Plugins are now standalone Git repositories, installed via `npx quartz plugin add` +- **Import pattern**: Community plugins use `ExternalPlugin.X()` (from `.quartz/plugins`) instead of `Plugin.X()` (from `./quartz/plugins`) +- **Layout structure**: `quartz.layout.ts` now uses `defaults` + `byPageType` instead of `sharedPageComponents` + per-layout objects +- **Page Types**: A new plugin category for page rendering (content, folder, tag pages) +- **Component references**: In layout files, community components use `Plugin.X()` (from `.quartz/plugins`) instead of `Component.X()` (from `./quartz/components`) + +## Step-by-Step Migration + +### 1. Install Community Plugins + +Run the following commands to install the default set of community plugins: + +```shell +npx quartz plugin add github:quartz-community/explorer +npx quartz plugin add github:quartz-community/graph +npx quartz plugin add github:quartz-community/search +npx quartz plugin add github:quartz-community/backlinks +npx quartz plugin add github:quartz-community/table-of-contents +npx quartz plugin add github:quartz-community/article-title +npx quartz plugin add github:quartz-community/tag-list +npx quartz plugin add github:quartz-community/page-title +npx quartz plugin add github:quartz-community/darkmode +npx quartz plugin add github:quartz-community/content-meta +npx quartz plugin add github:quartz-community/footer +npx quartz plugin add github:quartz-community/content-page +npx quartz plugin add github:quartz-community/folder-page +npx quartz plugin add github:quartz-community/tag-page +npx quartz plugin add github:quartz-community/created-modified-date +npx quartz plugin add github:quartz-community/syntax-highlighting +npx quartz plugin add github:quartz-community/obsidian-flavored-markdown +npx quartz plugin add github:quartz-community/github-flavored-markdown +npx quartz plugin add github:quartz-community/crawl-links +npx quartz plugin add github:quartz-community/description +npx quartz plugin add github:quartz-community/latex +npx quartz plugin add github:quartz-community/remove-draft +npx quartz plugin add github:quartz-community/alias-redirects +npx quartz plugin add github:quartz-community/content-index +npx quartz plugin add github:quartz-community/favicon +npx quartz plugin add github:quartz-community/og-image +npx quartz plugin add github:quartz-community/cname +``` + +Also install any optional plugins you were using: + +```shell +# Only if you used these in v4: +npx quartz plugin add github:quartz-community/comments +npx quartz plugin add github:quartz-community/reader-mode +npx quartz plugin add github:quartz-community/breadcrumbs +npx quartz plugin add github:quartz-community/recent-notes +npx quartz plugin add github:quartz-community/hard-line-breaks +npx quartz plugin add github:quartz-community/citations +npx quartz plugin add github:quartz-community/ox-hugo +npx quartz plugin add github:quartz-community/roam +npx quartz plugin add github:quartz-community/explicit-publish +``` + +### 2. Update quartz.config.ts + +Show before (v4) and after (v5) comparison: + +**Before (v4):** + +```ts title="quartz.config.ts" +import * as Plugin from "./quartz/plugins" + +plugins: { + transformers: [ + Plugin.FrontMatter(), + Plugin.CreatedModifiedDate({ priority: ["frontmatter", "git", "filesystem"] }), + Plugin.Latex({ renderEngine: "katex" }), + ], + filters: [Plugin.RemoveDrafts()], + emitters: [ + Plugin.AliasRedirects(), + Plugin.ComponentResources(), + Plugin.ContentPage(), + Plugin.FolderPage(), + Plugin.TagPage(), + Plugin.ContentIndex({ enableSiteMap: true, enableRSS: true }), + Plugin.Assets(), + Plugin.Static(), + Plugin.NotFoundPage(), + ], +} +``` + +**After (v5):** + +```ts title="quartz.config.ts" +import * as Plugin from "./quartz/plugins" +import * as ExternalPlugin from "./.quartz/plugins" +import { layout } from "./quartz.layout" + +plugins: { + transformers: [ + Plugin.FrontMatter(), + ExternalPlugin.CreatedModifiedDate({ priority: ["frontmatter", "git", "filesystem"] }), + ExternalPlugin.Latex({ renderEngine: "katex" }), + ], + filters: [ExternalPlugin.RemoveDrafts()], + emitters: [ + ExternalPlugin.AliasRedirects(), + Plugin.ComponentResources(), + ExternalPlugin.ContentIndex({ enableSiteMap: true, enableRSS: true }), + Plugin.Assets(), + Plugin.Static(), + ExternalPlugin.Favicon(), + Plugin.PageTypes.PageTypeDispatcher({ + defaults: layout.defaults, + byPageType: layout.byPageType, + }), + ExternalPlugin.CustomOgImages(), + ExternalPlugin.CNAME(), + ], + pageTypes: [ + ExternalPlugin.ContentPage(), + ExternalPlugin.FolderPage(), + ExternalPlugin.TagPage(), + Plugin.PageTypes.NotFoundPageType(), + ], +}, +externalPlugins: [ + "github:quartz-community/explorer", + "github:quartz-community/graph", + // ... all your community plugins +], +``` + +Key changes: + +- `Plugin.X()` becomes `ExternalPlugin.X()` for community plugins +- `Plugin.FrontMatter()` stays as `Plugin.X()` (it's internal) +- `Plugin.ComponentResources()`, `Plugin.Assets()`, `Plugin.Static()` stay internal +- Page-rendering emitters (`ContentPage`, `FolderPage`, `TagPage`, `NotFoundPage`) move to new `pageTypes` array +- `Plugin.PageTypes.PageTypeDispatcher()` replaces individual page emitters in the `emitters` array +- New `externalPlugins` array lists all community plugin repos + +### 3. Update quartz.layout.ts + +Show before (v4) and after (v5): + +**Before (v4):** + +```ts title="quartz.layout.ts" +import * as Component from "./quartz/components" + +export const sharedPageComponents: SharedLayout = { + head: Component.Head(), + header: [], + afterBody: [], + footer: Component.Footer({ links: { ... } }), +} + +export const defaultContentPageLayout: PageLayout = { + beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta(), Component.TagList()], + left: [Component.PageTitle(), Component.Search(), Component.Darkmode(), Component.Explorer()], + right: [Component.Graph(), Component.TableOfContents(), Component.Backlinks()], +} +``` + +**After (v5):** + +```ts title="quartz.layout.ts" +import * as Component from "./quartz/components" +import * as Plugin from "./.quartz/plugins" + +export const layout = { + defaults: { + head: Component.Head(), + header: [], + afterBody: [], + footer: Plugin.Footer({ links: { ... } }), + }, + byPageType: { + content: { + beforeBody: [Plugin.Breadcrumbs(), Plugin.ArticleTitle(), Plugin.ContentMeta(), Plugin.TagList()], + left: [Plugin.PageTitle(), Plugin.Search(), Plugin.Darkmode(), Plugin.Explorer()], + right: [Plugin.Graph(), Plugin.TableOfContents(), Plugin.Backlinks()], + }, + folder: { ... }, + tag: { ... }, + "404": { beforeBody: [], left: [], right: [] }, + }, +} +``` + +Key changes: + +- `Component.X()` for community components becomes `Plugin.X()` (imported from `.quartz/plugins`) +- `Component.Head()` and other layout utilities stay as `Component.X()` (from `./quartz/components`) +- `sharedPageComponents` becomes `defaults` +- Per-layout objects become entries in `byPageType` +- Each page type (content, folder, tag, 404) gets its own layout override + +### 4. Update CI/CD + +Add `npx quartz plugin restore` to your build pipeline, before `npx quartz build`. See [[ci-cd]] for detailed examples. + +### 5. Commit and Deploy + +```shell +git add quartz.config.ts quartz.layout.ts quartz.lock.json +git commit -m "chore: migrate to Quartz 5 plugin system" +``` + +## Plugin Reference Table + +Show a table mapping v4 Plugin names to v5 equivalents: + +| v4 | v5 | Type | +| ----------------------------------- | ------------------------------------------- | --------------------- | +| `Plugin.FrontMatter()` | `Plugin.FrontMatter()` (unchanged) | Internal | +| `Plugin.CreatedModifiedDate()` | `ExternalPlugin.CreatedModifiedDate()` | Community | +| `Plugin.SyntaxHighlighting()` | `ExternalPlugin.SyntaxHighlighting()` | Community | +| `Plugin.ObsidianFlavoredMarkdown()` | `ExternalPlugin.ObsidianFlavoredMarkdown()` | Community | +| `Plugin.GitHubFlavoredMarkdown()` | `ExternalPlugin.GitHubFlavoredMarkdown()` | Community | +| `Plugin.CrawlLinks()` | `ExternalPlugin.CrawlLinks()` | Community | +| `Plugin.Description()` | `ExternalPlugin.Description()` | Community | +| `Plugin.Latex()` | `ExternalPlugin.Latex()` | Community | +| `Plugin.RemoveDrafts()` | `ExternalPlugin.RemoveDrafts()` | Community | +| `Plugin.ContentPage()` | `ExternalPlugin.ContentPage()` | Community (pageTypes) | +| `Plugin.FolderPage()` | `ExternalPlugin.FolderPage()` | Community (pageTypes) | +| `Plugin.TagPage()` | `ExternalPlugin.TagPage()` | Community (pageTypes) | +| `Plugin.NotFoundPage()` | `Plugin.PageTypes.NotFoundPageType()` | Internal (pageTypes) | +| `Plugin.ComponentResources()` | `Plugin.ComponentResources()` (unchanged) | Internal | +| `Plugin.Assets()` | `Plugin.Assets()` (unchanged) | Internal | +| `Plugin.Static()` | `Plugin.Static()` (unchanged) | Internal | +| `Plugin.AliasRedirects()` | `ExternalPlugin.AliasRedirects()` | Community | +| `Plugin.ContentIndex()` | `ExternalPlugin.ContentIndex()` | Community | + +And for components in layout: + +| v4 Layout | v5 Layout | +| ----------------------------- | ------------------------------------------ | +| `Component.Explorer()` | `Plugin.Explorer()` | +| `Component.Graph()` | `Plugin.Graph()` | +| `Component.Search()` | `Plugin.Search()` | +| `Component.Backlinks()` | `Plugin.Backlinks()` | +| `Component.Darkmode()` | `Plugin.Darkmode()` | +| `Component.Footer()` | `Plugin.Footer()` | +| `Component.TableOfContents()` | `Plugin.TableOfContents()` | +| `Component.Head()` | `Component.Head()` (unchanged, internal) | +| `Component.Spacer()` | `Component.Spacer()` (unchanged, internal) |