npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eclecticpassions/quartz-image-to-figure-caption

v0.2.1

Published

Convert Markdown image syntax to HTML figure with figcaption support, and add image size attributes.

Readme

Markdown Image Syntax Support for Quartz

This Quartz plugin adds the following features:

  • Use Markdown image syntax to generate <figure> and <figcaption> for HTML output
  • Differentiates between alt text and caption text as separate entities
  • Supports Markdown link syntax inside caption text

Usage Examples

  1. Add figure caption to an image:

    ![Alt text here.](/path/to/image.webp "Caption text here")

    HTML output:

    <figure class="rehype-figure-title">
      <img src="/path/to/image.webp" alt="Alt text here." loading="lazy"/>
      <figcaption>
        <p>Caption text here</p>
      </figcaption>
    </figure>
  2. Add figure caption that contains Markdown link:

    ![Alt text here.](/path/to/image.webp "Caption text here but this time [contains a link](https://example.com)")

    HTML output:

    <figure class="rehype-figure-title">
      <img src="/path/to/image.webp" alt="Alt text here." loading="lazy"/>
      <figcaption>
        <p>Caption text here but this time <a href="https://example.com" target="_blank" rel="noreferrer noopener" class="external external-link">contains a link</a></p>
      </figcaption>
    </figure>
  3. You can then style the <figure> and <figcaption>. For example, in quartz/styles/custom.scss:

figure {
  text-align: center;
  img {
    max-width: 100%;
    height: auto;
    margin: 0 auto;
  }
  figcaption * {
    margin: 0;
    font-size: 0.833rem;
    color: var(--text-muted);
  }
}

Install

  1. To install this plugin to your Quartz site, run:

    npx quartz plugin install github:eclecticpassions/image-to-figure-caption

  2. To update the plugin, run it with the --latest flag like:

    npx quartz plugin install --latest github:eclecticpassions/image-to-figure-caption

  3. Change the quartz.config.yaml file to the following:

      - source: github:eclecticpassions/image-to-figure-caption
        enabled: true

    Remove any addition config lines like layout, position, priority, etc.

Disclaimer

[!WARNING] I used a few LLMs to help troubleshoot to get the index.ts file working. I am not a professional coder. Please review the code and packages before use. Check node package status by running npm audit regularly to check for vulnerabilities.

  • The README and all documentation is written fully by me; only edits in the code are assisted by LLMs.
  • This plugin uses the official Quartz-community plugin template
  • Only Quartz v5.0.0 has been tested to work (if you run an older version of Quartz and want to test it, please open an issue, thanks!)

Dependencies

Below is the README included in the Quartz plugin template.


Quartz Community Plugin Template

Production-ready template for building, testing, and publishing Quartz community plugins. It mirrors Quartz's native plugin patterns and uses a factory-function API similar to Astro integrations: plugins are created by functions that return objects with name and lifecycle hooks.

Highlights

  • ✅ Quartz-compatible transformer/filter/emitter examples
  • ✅ TypeScript-first with exported types for consumers
  • ✅ tsup bundling + declaration output
  • ✅ Pre-built dist/ ships in the repo — instant installation for users
  • ✅ Vitest testing setup with example tests
  • ✅ Linting/formatting with ESLint + Prettier
  • ✅ CI workflow for checks and npm publishing
  • ✅ Demonstrates CSS/JS resource injection and remark/rehype usage

Getting started

npm install
npm run build

[!important] After building, the dist/ directory should be committed to the repository. It is not gitignored, as Quartz uses it for pre-built distribution.

Build and Distribution

The template is configured to bundle all dependencies by default via noExternal: [/.*/] in tsup.config.ts. This ensures that users don't need to install any dependencies when using your plugin.

  • Singleton Externals: Certain packages (preact, vfile, unified, @jackyzha0/quartz) are kept external to ensure only one instance of them exists across all plugins.
  • Native Dependencies: If your plugin uses native dependencies (like sharp, @napi-rs/simple-git, etc.), you must exclude them from bundling. Use a regex pattern in noExternal to exclude them, for example: noExternal: [/^(?!sharp)/].
  • CI Verification: The included CI workflow verifies that dist/ is up to date on every push.

Usage in Quartz

Install your plugin into a Quartz v5 site:

npx quartz plugin add github:quartz-community/plugin-template

Then register it in quartz.config.yaml:

plugins:
  - source: github:quartz-community/plugin-template
    enabled: true
    options:
      highlightToken: "=="

If you need to use the plugin in quartz.ts for advanced overrides:

import * as ExternalPlugin from "./.quartz/plugins";

export default {
  plugins: {
    transformers: [ExternalPlugin.ExampleTransformer({ highlightToken: "==" })],
  },
};

Plugin factory pattern (Astro-style)

Quartz plugins are factory functions that return an object with a name and hook implementations. This mirrors Astro's integration pattern (a function returning an object of hooks), which makes composition and configuration explicit and predictable.

import type { QuartzTransformerPlugin } from "@quartz-community/types";

export const MyTransformer: QuartzTransformerPlugin<{ enabled: boolean }> = (opts) => {
  return {
    name: "MyTransformer",
    markdownPlugins() {
      return [];
    },
  };
};

Examples included

Transformer

ExampleTransformer shows how to:

  • apply a custom remark plugin
  • run a rehype plugin
  • inject CSS/JS resources
  • perform a text transform hook
import { ExampleTransformer } from "@quartz-community/plugin-template";

ExampleTransformer({
  highlightToken: "==",
  headingClass: "example-plugin-heading",
  enableGfm: true,
  addHeadingSlugs: true,
});

The transformer uses a custom remark plugin to convert ==highlight== into bold text and a rehype plugin to attach a class to all headings. It also injects a small inline CSS/JS snippet.

Filter

ExampleFilter demonstrates frontmatter-driven filtering:

ExampleFilter({
  allowDrafts: false,
  excludeTags: ["private", "wip"],
  excludePathPrefixes: ["_drafts/", "_private/"],
});

Emitter

ExampleEmitter emits a JSON manifest of all pages:

ExampleEmitter({
  manifestSlug: "plugin-manifest",
  includeFrontmatter: true,
  metadata: { project: "My Garden" },
  transformManifest: (json) => json.replace("My Garden", "Quartz"),
});

API reference

ExampleTransformer(options)

| Option | Type | Default | Description | | ----------------- | --------- | -------------------------- | ----------------------------- | | highlightToken | string | "==" | Token used to highlight text. | | headingClass | string | "example-plugin-heading" | Class added to headings. | | enableGfm | boolean | true | Enables remark-gfm. | | addHeadingSlugs | boolean | true | Enables rehype-slug. |

ExampleFilter(options)

| Option | Type | Default | Description | | --------------------- | ---------- | --------------------------- | ------------------------- | | allowDrafts | boolean | false | Publish draft pages. | | excludeTags | string[] | ["private"] | Tags to exclude. | | excludePathPrefixes | string[] | ["_drafts/", "_private/"] | Path prefixes to exclude. |

ExampleEmitter(options)

| Option | Type | Default | Description | | --------------------- | -------------------------- | ----------------------------------------- | ----------------------------------------- | | manifestSlug | string | "plugin-manifest" | Output filename (without extension). | | includeFrontmatter | boolean | true | Include frontmatter in output. | | metadata | Record<string, unknown> | { generator: "Quartz Plugin Template" } | Extra metadata in manifest. | | transformManifest | (json: string) => string | undefined | Custom transformer for emitted JSON. | | manifestScriptClass | string | undefined | Optional CSS class if rendered into HTML. |

Testing

npm test

Build and lint

npm run build
npm run lint
npm run format

Publishing

Tags matching v* trigger the GitHub Actions publish workflow. Ensure NPM_TOKEN is set in the repository secrets.

Component Plugins (UI Components)

In addition to transformer/filter/emitter plugins, you can create component plugins that provide UI elements for Quartz layouts. See src/components/ExampleComponent.tsx for a reference.

Component Pattern

import type { QuartzComponent, QuartzComponentConstructor } from "@quartz-community/types";
import style from "./styles/example.scss";
import script from "./scripts/example.inline.ts";

export default ((opts?: MyComponentOptions) => {
  const Component: QuartzComponent = (props) => {
    return <div class="my-component">...</div>;
  };

  Component.css = style;
  Component.afterDOMLoaded = script;

  return Component;
}) satisfies QuartzComponentConstructor;

Receiving YAML Options in Component-Only Plugins

Processing plugins (transformers, filters, emitters, page types) receive options automatically through their factory function. Component-only plugins (those with "category": ["component"]) are loaded via side-effect import and need an extra step to receive YAML options.

Export an init function from your plugin's entry point. Quartz's config-loader will call it with the merged options from package.json defaultOptions and the user's quartz.config.yaml:

// src/index.ts
export function init(options?: Record<string, unknown>): void {
  // Use the options to configure your plugin
  const myOption = (options?.myOption as boolean) ?? false;
  // e.g. register a view, set global state, etc.
}

Then declare default values in your package.json manifest:

{
  "quartz": {
    "category": ["component"],
    "defaultOptions": {
      "myOption": false
    }
  }
}

Users configure options in quartz.config.yaml:

plugins:
  - source: github:your-username/my-component-plugin
    enabled: true
    options:
      myOption: true

Quartz merges defaultOptions with the user's options (user values take precedence) and passes the result to init(). If no init export exists, the plugin is loaded via side-effect import as before — no breaking change for existing plugins.

Client-Side Scripts

Component scripts run in the browser and must handle Quartz's SPA navigation. Key patterns:

  1. Use @ts-nocheck - Client scripts run in a different context than build-time code
  2. Listen to nav event - Fires after each page navigation (including initial load)
  3. Listen to prenav event - Fires before navigation, use for saving state
  4. Use window.addCleanup() - Register cleanup functions for event listeners
  5. Use fetchData global - Access page metadata via the fetchData promise (handles base path correctly)

See src/components/scripts/example.inline.ts for a complete example with all patterns.

Common Helper Functions

These utilities are commonly needed in component plugins:

function removeAllChildren(element) {
  while (element.firstChild) element.removeChild(element.firstChild);
}

function simplifySlug(slug) {
  return slug.endsWith("/index") ? slug.slice(0, -6) : slug;
}

function getCurrentSlug() {
  let slug = window.location.pathname;
  if (slug.startsWith("/")) slug = slug.slice(1);
  if (slug.endsWith("/")) slug = slug.slice(0, -1);
  return slug || "index";
}

State Persistence

Use localStorage for persistent state (survives browser close) and sessionStorage for temporary state (like scroll positions):

localStorage.setItem("myPlugin-state", JSON.stringify(state));
sessionStorage.setItem("myPlugin-scrollTop", element.scrollTop.toString());

Migration Guide (from Quartz v4)

When migrating a v4 component to a standalone plugin:

  1. Replace Quartz imports with @quartz-community/types
  2. Copy utility functions (path helpers, DOM utils) into your plugin
  3. Use @ts-nocheck for inline scripts that can't be type-checked
  4. Use the fetchData global to access contentIndex.json with the correct base path
  5. Test with both local and production builds

License

MIT