Jacky Zhao
2025-03-06 5b13ff21992a61eb8b03670ae1742a72703c2afe
feat: support emitters defining external resources, emit link from contentindex directly
7 files modified
1 files renamed
42 ■■■■ changed files
docs/advanced/making plugins.md 2 ●●●●● patch | view | raw | blame | history
quartz/components/Head.tsx 11 ●●●● patch | view | raw | blame | history
quartz/components/renderPage.tsx 1 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/contentIndex.tsx 14 ●●●●● patch | view | raw | blame | history
quartz/plugins/index.ts 6 ●●●● patch | view | raw | blame | history
quartz/plugins/transformers/latex.ts 2 ●●●●● patch | view | raw | blame | history
quartz/plugins/types.ts 4 ●●● patch | view | raw | blame | history
quartz/util/resources.tsx 2 ●●●●● patch | view | raw | blame | history
docs/advanced/making plugins.md
@@ -99,8 +99,6 @@
            },
          ],
        }
      } else {
        return {}
      }
    },
  }
quartz/components/Head.tsx
@@ -127,7 +127,7 @@
      }
    }
    const { css, js } = externalResources
    const { css, js, additionalHead } = externalResources
    const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
    const path = url.pathname as FullSlug
@@ -177,7 +177,7 @@
            <link rel="stylesheet" href={googleFontHref(cfg.theme)} />
          </>
        )}
        <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin={"anonymous"} />
        <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin="anonymous" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        {/* OG/Twitter meta tags */}
        <meta name="og:site_name" content={cfg.pageTitle}></meta>
@@ -213,6 +213,13 @@
        {js
          .filter((resource) => resource.loadTime === "beforeDOMReady")
          .map((res) => JSResourceToScriptElement(res, true))}
        {additionalHead.map((resource) => {
          if (typeof resource === "function") {
            return resource(fileData)
          } else {
            return resource
          }
        })}
      </head>
    )
  }
quartz/components/renderPage.tsx
@@ -54,6 +54,7 @@
      },
      ...staticResources.js,
    ],
    additionalHead: staticResources.additionalHead,
  }
  if (fileData.hasMermaidDiagram) {
quartz/plugins/emitters/contentIndex.tsx
File was renamed from quartz/plugins/emitters/contentIndex.ts
@@ -182,6 +182,20 @@
      return emitted
    },
    externalResources: (ctx) => {
      if (opts?.enableRSS) {
        return {
          additionalHead: [
            <link
              rel="alternate"
              type="application/rss+xml"
              title="RSS Feed"
              href={`https://${ctx.cfg.configuration.baseUrl}/index.xml`}
            />,
          ],
        }
      }
    },
    getQuartzComponents: () => [],
  }
}
quartz/plugins/index.ts
@@ -6,9 +6,10 @@
  const staticResources: StaticResources = {
    css: [],
    js: [],
    additionalHead: [],
  }
  for (const transformer of ctx.cfg.plugins.transformers) {
  for (const transformer of [...ctx.cfg.plugins.transformers, ...ctx.cfg.plugins.emitters]) {
    const res = transformer.externalResources ? transformer.externalResources(ctx) : {}
    if (res?.js) {
      staticResources.js.push(...res.js)
@@ -16,6 +17,9 @@
    if (res?.css) {
      staticResources.css.push(...res.css)
    }
    if (res?.additionalHead) {
      staticResources.additionalHead.push(...res.additionalHead)
    }
  }
  // if serving locally, listen for rebuilds and reload the page
quartz/plugins/transformers/latex.ts
@@ -59,8 +59,6 @@
              },
            ],
          }
        default:
          return { css: [], js: [] }
      }
    },
  }
quartz/plugins/types.ts
@@ -13,6 +13,7 @@
}
type OptionType = object | undefined
type ExternalResourcesFn = (ctx: BuildCtx) => Partial<StaticResources> | undefined
export type QuartzTransformerPlugin<Options extends OptionType = undefined> = (
  opts?: Options,
) => QuartzTransformerPluginInstance
@@ -21,7 +22,7 @@
  textTransform?: (ctx: BuildCtx, src: string) => string
  markdownPlugins?: (ctx: BuildCtx) => PluggableList
  htmlPlugins?: (ctx: BuildCtx) => PluggableList
  externalResources?: (ctx: BuildCtx) => Partial<StaticResources>
  externalResources?: ExternalResourcesFn
}
export type QuartzFilterPlugin<Options extends OptionType = undefined> = (
@@ -44,4 +45,5 @@
    content: ProcessedContent[],
    resources: StaticResources,
  ): Promise<DepGraph<FilePath>>
  externalResources?: ExternalResourcesFn
}
quartz/util/resources.tsx
@@ -1,5 +1,6 @@
import { randomUUID } from "crypto"
import { JSX } from "preact/jsx-runtime"
import { QuartzPluginData } from "../plugins/vfile"
export type JSResource = {
  loadTime: "beforeDOMReady" | "afterDOMReady"
@@ -62,4 +63,5 @@
export interface StaticResources {
  css: CSSResource[]
  js: JSResource[]
  additionalHead: (JSX.Element | ((pageData: QuartzPluginData) => JSX.Element))[]
}