mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
feat: add TreeTransform hook, fix multi-category plugins, and resolve cross-plugin dependencies
- Add TreeTransform type and treeTransforms hook to pageType plugins, enabling render-time HAST tree mutations (e.g. bases-page inline codeblock resolution) - Fix config-loader to push multi-category plugins into ALL matching processing buckets instead of only the first match - Add side-effect import for component-only plugins so view registrations (e.g. leaflet-map via globalThis ViewRegistry) execute at load time - Add npm prune --omit=dev and cross-plugin peer dependency symlinking to buildPlugin() to prevent duplicate-singleton issues from nested node_modules
This commit is contained in:
parent
28fe1d55d3
commit
472b337d92
@ -241,6 +241,10 @@ plugins:
|
|||||||
position: beforeBody
|
position: beforeBody
|
||||||
priority: 15
|
priority: 15
|
||||||
display: all
|
display: all
|
||||||
|
- source: github:quartz-community/external-quartz-leaflet-map-plugin
|
||||||
|
enabled: true
|
||||||
|
options: {}
|
||||||
|
order: 50
|
||||||
layout:
|
layout:
|
||||||
groups:
|
groups:
|
||||||
toolbar:
|
toolbar:
|
||||||
|
|||||||
@ -22,8 +22,8 @@
|
|||||||
"bases-page": {
|
"bases-page": {
|
||||||
"source": "github:quartz-community/bases-page",
|
"source": "github:quartz-community/bases-page",
|
||||||
"resolved": "https://github.com/quartz-community/bases-page.git",
|
"resolved": "https://github.com/quartz-community/bases-page.git",
|
||||||
"commit": "7f6439ed711f4aa9d38c5017e88a7a9757cd2d5c",
|
"commit": "c00c56b7d1aef0c07846b8772a3b9303ad03d816",
|
||||||
"installedAt": "2026-02-28T20:09:14.396Z"
|
"installedAt": "2026-03-07T23:21:54.535Z"
|
||||||
},
|
},
|
||||||
"breadcrumbs": {
|
"breadcrumbs": {
|
||||||
"source": "github:quartz-community/breadcrumbs",
|
"source": "github:quartz-community/breadcrumbs",
|
||||||
@ -109,6 +109,12 @@
|
|||||||
"commit": "b70d50a96c00e4726fb08614dc4fab116b1c5ca9",
|
"commit": "b70d50a96c00e4726fb08614dc4fab116b1c5ca9",
|
||||||
"installedAt": "2026-02-28T20:09:21.150Z"
|
"installedAt": "2026-02-28T20:09:21.150Z"
|
||||||
},
|
},
|
||||||
|
"external-quartz-leaflet-map-plugin": {
|
||||||
|
"source": "github:quartz-community/external-quartz-leaflet-map-plugin",
|
||||||
|
"resolved": "https://github.com/quartz-community/external-quartz-leaflet-map-plugin.git",
|
||||||
|
"commit": "e77e11ea8948b9cb78a7ec57977325af2a9b6617",
|
||||||
|
"installedAt": "2026-03-07T23:22:48.300Z"
|
||||||
|
},
|
||||||
"favicon": {
|
"favicon": {
|
||||||
"source": "github:quartz-community/favicon",
|
"source": "github:quartz-community/favicon",
|
||||||
"resolved": "https://github.com/quartz-community/favicon.git",
|
"resolved": "https://github.com/quartz-community/favicon.git",
|
||||||
|
|||||||
@ -23,6 +23,13 @@ function buildPlugin(pluginDir, name) {
|
|||||||
execSync("npm install", { cwd: pluginDir, stdio: "ignore" })
|
execSync("npm install", { cwd: pluginDir, stdio: "ignore" })
|
||||||
console.log(styleText("cyan", ` → ${name}: building...`))
|
console.log(styleText("cyan", ` → ${name}: building...`))
|
||||||
execSync("npm run build", { cwd: pluginDir, stdio: "ignore" })
|
execSync("npm run build", { cwd: pluginDir, stdio: "ignore" })
|
||||||
|
// Remove devDependencies after build — they are no longer needed and their
|
||||||
|
// presence can cause duplicate-singleton issues when a plugin ships its own
|
||||||
|
// copy of a shared dependency (e.g. bases-page's ViewRegistry).
|
||||||
|
execSync("npm prune --omit=dev", { cwd: pluginDir, stdio: "ignore" })
|
||||||
|
// Symlink any peerDependencies that are co-installed Quartz plugins so that
|
||||||
|
// Node's module resolution finds the host copy instead of a stale nested one.
|
||||||
|
linkPeerPlugins(pluginDir)
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(styleText("red", ` ✗ ${name}: build failed`))
|
console.log(styleText("red", ` ✗ ${name}: build failed`))
|
||||||
@ -35,6 +42,66 @@ function needsBuild(pluginDir) {
|
|||||||
return !fs.existsSync(distDir)
|
return !fs.existsSync(distDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After pruning devDependencies, peerDependencies that reference other Quartz
|
||||||
|
* plugins (e.g. @quartz-community/bases-page) won't be installed as npm
|
||||||
|
* packages — they're loaded by v5 as sibling plugins. To make Node's module
|
||||||
|
* resolution work, we symlink those peers to the co-installed plugin directory.
|
||||||
|
*/
|
||||||
|
function linkPeerPlugins(pluginDir) {
|
||||||
|
const pkgPath = path.join(pluginDir, "package.json")
|
||||||
|
if (!fs.existsSync(pkgPath)) return
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
||||||
|
const peers = pkg.peerDependencies ?? {}
|
||||||
|
|
||||||
|
for (const peerName of Object.keys(peers)) {
|
||||||
|
// Only handle @quartz-community scoped packages — those are Quartz plugins
|
||||||
|
if (!peerName.startsWith("@quartz-community/")) continue
|
||||||
|
|
||||||
|
// Check if this peer is already satisfied (e.g. installed as a regular dep)
|
||||||
|
const peerNodeModulesPath = path.join(pluginDir, "node_modules", ...peerName.split("/"))
|
||||||
|
if (fs.existsSync(peerNodeModulesPath)) continue
|
||||||
|
|
||||||
|
// Find the sibling plugin by its npm package name
|
||||||
|
const siblingPlugin = findPluginByPackageName(peerName)
|
||||||
|
if (!siblingPlugin) continue
|
||||||
|
|
||||||
|
// Create the scoped directory if needed
|
||||||
|
const scopeDir = path.join(pluginDir, "node_modules", peerName.split("/")[0])
|
||||||
|
fs.mkdirSync(scopeDir, { recursive: true })
|
||||||
|
|
||||||
|
// Create a relative symlink to the sibling plugin
|
||||||
|
const target = path.relative(scopeDir, siblingPlugin)
|
||||||
|
fs.symlinkSync(target, peerNodeModulesPath, "dir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search installed plugins for one whose package.json "name" matches the given
|
||||||
|
* npm package name (e.g. "@quartz-community/bases-page").
|
||||||
|
*/
|
||||||
|
function findPluginByPackageName(packageName) {
|
||||||
|
if (!fs.existsSync(PLUGINS_DIR)) return null
|
||||||
|
|
||||||
|
const plugins = fs.readdirSync(PLUGINS_DIR).filter((entry) => {
|
||||||
|
const entryPath = path.join(PLUGINS_DIR, entry)
|
||||||
|
return fs.statSync(entryPath).isDirectory()
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const pluginDirName of plugins) {
|
||||||
|
const pkgPath = path.join(PLUGINS_DIR, pluginDirName, "package.json")
|
||||||
|
if (!fs.existsSync(pkgPath)) continue
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
||||||
|
if (pkg.name === packageName) {
|
||||||
|
return path.join(PLUGINS_DIR, pluginDirName)
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
function parseExportsFromDts(content) {
|
function parseExportsFromDts(content) {
|
||||||
const exports = []
|
const exports = []
|
||||||
const exportMatches = content.matchAll(/export\s*{\s*([^}]+)\s*}(?:\s*from\s*['"]([^'"]+)['"])?/g)
|
const exportMatches = content.matchAll(/export\s*{\s*([^}]+)\s*}(?:\s*from\s*['"]([^'"]+)['"])?/g)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { GlobalConfiguration } from "../cfg"
|
|||||||
import { i18n } from "../i18n"
|
import { i18n } from "../i18n"
|
||||||
import { styleText } from "util"
|
import { styleText } from "util"
|
||||||
import { resolveFrame } from "./frames"
|
import { resolveFrame } from "./frames"
|
||||||
|
import type { TreeTransform } from "../plugins/types"
|
||||||
|
|
||||||
interface RenderComponents {
|
interface RenderComponents {
|
||||||
head: QuartzComponent
|
head: QuartzComponent
|
||||||
@ -231,6 +232,7 @@ export function renderPage(
|
|||||||
componentData: QuartzComponentProps,
|
componentData: QuartzComponentProps,
|
||||||
components: RenderComponents,
|
components: RenderComponents,
|
||||||
pageResources: StaticResources,
|
pageResources: StaticResources,
|
||||||
|
treeTransforms?: TreeTransform[],
|
||||||
): string {
|
): string {
|
||||||
// make a deep copy of the tree so we don't remove the transclusion references
|
// make a deep copy of the tree so we don't remove the transclusion references
|
||||||
// for the file cached in contentMap in build.ts
|
// for the file cached in contentMap in build.ts
|
||||||
@ -238,6 +240,13 @@ export function renderPage(
|
|||||||
const visited = new Set<FullSlug>([slug])
|
const visited = new Set<FullSlug>([slug])
|
||||||
renderTranscludes(root, cfg, slug, componentData, visited)
|
renderTranscludes(root, cfg, slug, componentData, visited)
|
||||||
|
|
||||||
|
// Run plugin-provided tree transforms (e.g. resolving inline bases codeblocks)
|
||||||
|
if (treeTransforms) {
|
||||||
|
for (const transform of treeTransforms) {
|
||||||
|
transform(root, slug, componentData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// set componentData.tree to the edited html that has transclusions rendered
|
// set componentData.tree to the edited html that has transclusions rendered
|
||||||
componentData.tree = root
|
componentData.tree = root
|
||||||
|
|
||||||
|
|||||||
@ -264,58 +264,54 @@ export async function loadQuartzConfig(
|
|||||||
for (const entry of enabledEntries) {
|
for (const entry of enabledEntries) {
|
||||||
const manifest = manifests.get(entry.source)
|
const manifest = manifests.get(entry.source)
|
||||||
const category = manifest?.category
|
const category = manifest?.category
|
||||||
// Resolve processing category: for array categories (e.g. ["transformer", "component"]),
|
// Resolve processing categories: for array categories (e.g. ["transformer", "pageType", "component"]),
|
||||||
// find the first processing category. "component" is handled separately via loadComponentsFromPackage.
|
// push the plugin into ALL matching processing category buckets.
|
||||||
|
// "component" is handled separately via loadComponentsFromPackage during instantiation.
|
||||||
const processingCategories = ["transformer", "filter", "emitter", "pageType"] as const
|
const processingCategories = ["transformer", "filter", "emitter", "pageType"] as const
|
||||||
let resolvedCategory: string | undefined
|
const categoryMap: Record<string, typeof transformers> = {
|
||||||
if (Array.isArray(category)) {
|
transformer: transformers,
|
||||||
resolvedCategory = category.find((c) =>
|
filter: filters,
|
||||||
(processingCategories as readonly string[]).includes(c),
|
emitter: emitters,
|
||||||
)
|
pageType: pageTypes,
|
||||||
} else {
|
|
||||||
resolvedCategory = category
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (resolvedCategory) {
|
const categories = Array.isArray(category) ? category : category ? [category] : []
|
||||||
case "transformer":
|
const matchedProcessing = categories.filter((c) =>
|
||||||
transformers.push({ entry, manifest })
|
(processingCategories as readonly string[]).includes(c),
|
||||||
break
|
)
|
||||||
case "filter":
|
|
||||||
filters.push({ entry, manifest })
|
|
||||||
break
|
|
||||||
case "emitter":
|
|
||||||
emitters.push({ entry, manifest })
|
|
||||||
break
|
|
||||||
case "pageType":
|
|
||||||
pageTypes.push({ entry, manifest })
|
|
||||||
break
|
|
||||||
default: {
|
|
||||||
const gitSpec = parsePluginSource(entry.source)
|
|
||||||
const isComponentOnly =
|
|
||||||
resolvedCategory === "component" ||
|
|
||||||
(Array.isArray(category) && category.every((c) => c === "component"))
|
|
||||||
|
|
||||||
if (isComponentOnly) {
|
if (matchedProcessing.length > 0) {
|
||||||
if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
for (const cat of matchedProcessing) {
|
||||||
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
categoryMap[cat].push({ entry, manifest })
|
||||||
}
|
}
|
||||||
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
} else {
|
||||||
await loadFramesFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
const gitSpec = parsePluginSource(entry.source)
|
||||||
}
|
const isComponentOnly =
|
||||||
break
|
categories.length > 0 && categories.every((c) => c === "component")
|
||||||
|
|
||||||
|
if (isComponentOnly) {
|
||||||
|
// Always import the main entry point for component-only plugins.
|
||||||
|
// Some plugins (e.g. Bases view registrations) rely on side effects
|
||||||
|
// in their index module to register functionality.
|
||||||
|
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
||||||
|
try {
|
||||||
|
await import(toFileUrl(entryPoint))
|
||||||
|
} catch (e) {
|
||||||
|
// Side-effect import failed — continue with manifest-based loading
|
||||||
}
|
}
|
||||||
|
if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
||||||
|
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
||||||
|
}
|
||||||
|
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
||||||
|
await loadFramesFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
||||||
try {
|
try {
|
||||||
const module = await import(toFileUrl(entryPoint))
|
const module = await import(toFileUrl(entryPoint))
|
||||||
const detected = detectCategoryFromModule(module)
|
const detected = detectCategoryFromModule(module)
|
||||||
if (detected) {
|
if (detected) {
|
||||||
const target = {
|
categoryMap[detected].push({ entry, manifest })
|
||||||
transformer: transformers,
|
|
||||||
filter: filters,
|
|
||||||
emitter: emitters,
|
|
||||||
pageType: pageTypes,
|
|
||||||
}[detected]
|
|
||||||
target.push({ entry, manifest })
|
|
||||||
} else if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
} else if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
||||||
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
||||||
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { QuartzEmitterPlugin, QuartzPageTypePluginInstance } from "../types"
|
import { QuartzEmitterPlugin, QuartzPageTypePluginInstance, TreeTransform } from "../types"
|
||||||
import { QuartzComponent, QuartzComponentProps } from "../../components/types"
|
import { QuartzComponent, QuartzComponentProps } from "../../components/types"
|
||||||
import { pageResources, renderPage } from "../../components/renderPage"
|
import { pageResources, renderPage } from "../../components/renderPage"
|
||||||
import { FullPageLayout } from "../../cfg"
|
import { FullPageLayout } from "../../cfg"
|
||||||
@ -74,6 +74,7 @@ async function emitPage(
|
|||||||
allFiles: ProcessedContent[1]["data"][],
|
allFiles: ProcessedContent[1]["data"][],
|
||||||
layout: FullPageLayout,
|
layout: FullPageLayout,
|
||||||
resources: StaticResources,
|
resources: StaticResources,
|
||||||
|
treeTransforms?: TreeTransform[],
|
||||||
) {
|
) {
|
||||||
const cfg = ctx.cfg.configuration
|
const cfg = ctx.cfg.configuration
|
||||||
// For the 404 page, use an absolute base path so assets resolve correctly
|
// For the 404 page, use an absolute base path so assets resolve correctly
|
||||||
@ -95,7 +96,7 @@ async function emitPage(
|
|||||||
|
|
||||||
return write({
|
return write({
|
||||||
ctx,
|
ctx,
|
||||||
content: renderPage(cfg, slug, componentData, layout, externalResources),
|
content: renderPage(cfg, slug, componentData, layout, externalResources, treeTransforms),
|
||||||
slug,
|
slug,
|
||||||
ext: ".html",
|
ext: ".html",
|
||||||
})
|
})
|
||||||
@ -157,6 +158,11 @@ export const PageTypeDispatcher: QuartzEmitterPlugin<Partial<DispatcherOptions>>
|
|||||||
const cfg = ctx.cfg.configuration
|
const cfg = ctx.cfg.configuration
|
||||||
const allFiles = content.map((c) => c[1].data)
|
const allFiles = content.map((c) => c[1].data)
|
||||||
|
|
||||||
|
// Collect tree transforms from all page type plugins
|
||||||
|
const treeTransforms: TreeTransform[] = pageTypes.flatMap(
|
||||||
|
(pt) => pt.treeTransforms?.(ctx) ?? [],
|
||||||
|
)
|
||||||
|
|
||||||
// Ensure trie is available for components that need folder hierarchy (e.g. FolderContent)
|
// Ensure trie is available for components that need folder hierarchy (e.g. FolderContent)
|
||||||
ctx.trie ??= trieFromAllFiles(allFiles)
|
ctx.trie ??= trieFromAllFiles(allFiles)
|
||||||
|
|
||||||
@ -201,7 +207,7 @@ export const PageTypeDispatcher: QuartzEmitterPlugin<Partial<DispatcherOptions>>
|
|||||||
for (const pt of pageTypes) {
|
for (const pt of pageTypes) {
|
||||||
if (pt.match({ slug, fileData, cfg })) {
|
if (pt.match({ slug, fileData, cfg })) {
|
||||||
const layout = resolveLayout(pt, defaults, byPageType)
|
const layout = resolveLayout(pt, defaults, byPageType)
|
||||||
yield emitPage(ctx, slug, tree, fileData, allFilesWithVirtual, layout, resources)
|
yield emitPage(ctx, slug, tree, fileData, allFilesWithVirtual, layout, resources, treeTransforms)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,6 +223,7 @@ export const PageTypeDispatcher: QuartzEmitterPlugin<Partial<DispatcherOptions>>
|
|||||||
allFilesWithVirtual,
|
allFilesWithVirtual,
|
||||||
ve.layout,
|
ve.layout,
|
||||||
resources,
|
resources,
|
||||||
|
treeTransforms,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -225,6 +232,11 @@ export const PageTypeDispatcher: QuartzEmitterPlugin<Partial<DispatcherOptions>>
|
|||||||
const cfg = ctx.cfg.configuration
|
const cfg = ctx.cfg.configuration
|
||||||
const allFiles = content.map((c) => c[1].data)
|
const allFiles = content.map((c) => c[1].data)
|
||||||
|
|
||||||
|
// Collect tree transforms from all page type plugins
|
||||||
|
const treeTransforms: TreeTransform[] = pageTypes.flatMap(
|
||||||
|
(pt) => pt.treeTransforms?.(ctx) ?? [],
|
||||||
|
)
|
||||||
|
|
||||||
// Rebuild trie on partial emit to reflect file changes
|
// Rebuild trie on partial emit to reflect file changes
|
||||||
ctx.trie = trieFromAllFiles(allFiles)
|
ctx.trie = trieFromAllFiles(allFiles)
|
||||||
|
|
||||||
@ -278,7 +290,7 @@ export const PageTypeDispatcher: QuartzEmitterPlugin<Partial<DispatcherOptions>>
|
|||||||
for (const pt of pageTypes) {
|
for (const pt of pageTypes) {
|
||||||
if (pt.match({ slug, fileData, cfg })) {
|
if (pt.match({ slug, fileData, cfg })) {
|
||||||
const layout = resolveLayout(pt, defaults, byPageType)
|
const layout = resolveLayout(pt, defaults, byPageType)
|
||||||
yield emitPage(ctx, slug, tree, fileData, allFilesWithVirtual, layout, resources)
|
yield emitPage(ctx, slug, tree, fileData, allFilesWithVirtual, layout, resources, treeTransforms)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,6 +306,7 @@ export const PageTypeDispatcher: QuartzEmitterPlugin<Partial<DispatcherOptions>>
|
|||||||
allFilesWithVirtual,
|
allFilesWithVirtual,
|
||||||
ve.layout,
|
ve.layout,
|
||||||
resources,
|
resources,
|
||||||
|
treeTransforms,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { PluggableList } from "unified"
|
import { PluggableList } from "unified"
|
||||||
import { StaticResources } from "../util/resources"
|
import { StaticResources } from "../util/resources"
|
||||||
import { ProcessedContent, QuartzPluginData } from "./vfile"
|
import { ProcessedContent, QuartzPluginData } from "./vfile"
|
||||||
import { QuartzComponent, QuartzComponentConstructor } from "../components/types"
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../components/types"
|
||||||
import { FilePath } from "../util/path"
|
import { FilePath, FullSlug } from "../util/path"
|
||||||
import { BuildCtx } from "../util/ctx"
|
import { BuildCtx } from "../util/ctx"
|
||||||
import { GlobalConfiguration } from "../cfg"
|
import { GlobalConfiguration } from "../cfg"
|
||||||
import { VFile } from "vfile"
|
import { VFile } from "vfile"
|
||||||
|
import { Root } from "hast"
|
||||||
|
|
||||||
export interface PluginTypes {
|
export interface PluginTypes {
|
||||||
transformers: QuartzTransformerPluginInstance[]
|
transformers: QuartzTransformerPluginInstance[]
|
||||||
@ -90,6 +91,9 @@ export type PageGenerator = (args: {
|
|||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}) => VirtualPage[]
|
}) => VirtualPage[]
|
||||||
|
|
||||||
|
/** A function that mutates a HAST tree at render time, when allFiles is available. */
|
||||||
|
export type TreeTransform = (root: Root, slug: FullSlug, componentData: QuartzComponentProps) => void
|
||||||
|
|
||||||
export type QuartzPageTypePlugin<Options extends OptionType = undefined> = (
|
export type QuartzPageTypePlugin<Options extends OptionType = undefined> = (
|
||||||
opts?: Options,
|
opts?: Options,
|
||||||
) => QuartzPageTypePluginInstance
|
) => QuartzPageTypePluginInstance
|
||||||
@ -104,6 +108,8 @@ export interface QuartzPageTypePluginInstance {
|
|||||||
/** Optional page frame name (e.g. "default", "full-width", "minimal"). Defaults to "default". */
|
/** Optional page frame name (e.g. "default", "full-width", "minimal"). Defaults to "default". */
|
||||||
frame?: string
|
frame?: string
|
||||||
body: QuartzComponentConstructor
|
body: QuartzComponentConstructor
|
||||||
|
/** Optional render-time HAST tree transforms (e.g. resolving inline codeblocks). */
|
||||||
|
treeTransforms?: (ctx: BuildCtx) => TreeTransform[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structural supertype accepted in plugin configuration arrays.
|
// Structural supertype accepted in plugin configuration arrays.
|
||||||
@ -122,4 +128,5 @@ export interface PageTypePluginEntry {
|
|||||||
/** Optional page frame name (e.g. "default", "full-width", "minimal"). Defaults to "default". */
|
/** Optional page frame name (e.g. "default", "full-width", "minimal"). Defaults to "default". */
|
||||||
frame?: string
|
frame?: string
|
||||||
body: QuartzComponentConstructor
|
body: QuartzComponentConstructor
|
||||||
|
treeTransforms?: (...args: never[]) => TreeTransform[]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user