mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 10:54:06 -06:00
* Initial plan * docs: format design document for decoupling strategy Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * feat: implement Phase 1-4 of plugin decoupling strategy - Created centralized vfile-schema.ts with all VFile data type definitions - Created plugin-context.ts with PluginUtilities abstraction layer - Created test-helpers.ts for plugin testing utilities - Made BuildCtx readonly to prevent plugin mutations - Added MutableBuildCtx for build orchestration layer - Updated components to not mutate ctx.trie - Updated FrontMatter plugin with temporary cast for backward compatibility - Updated TransformOptions to accept ReadonlyArray Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * feat: add plugin utilities to BuildCtx and document plugin decoupling - Added utils property to BuildCtx with createPluginUtilities() - Documented plugin data dependencies with JSDoc comments on transformers and filters - Created comprehensive plugin migration guide in docs/PLUGIN_MIGRATION.md - Updated plugin types.ts with guidance on using PluginContext - All plugins now have access to ctx.utils for path, resource, and escape utilities Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * docs: add implementation and security summaries - Created IMPLEMENTATION_SUMMARY.md documenting all phases completed - Created SECURITY_SUMMARY.md with CodeQL scan results - All security checks passed with 0 vulnerabilities - Complete documentation of changes, decisions, and impact Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * docs: add guidance for custom plugins extending vfile data - Added section "Adding Custom VFile Fields" to PLUGIN_MIGRATION.md - Demonstrates TypeScript module augmentation for custom fields - Shows best practices for extending vfile data in third-party plugins - Clarifies that centralized schema doesn't prevent custom extensions Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> * refactor: address code review feedback - Remove `any` type annotations from component map callbacks (Breadcrumbs, FolderContent) - Fix test-helpers: change `config` to `cfg` to match BuildCtx interface - Add `trie: undefined` to mock context for completeness - Add missing vfile fields to schema: text, blocks, htmlAst, hasMermaidDiagram - Make Argv properties truly readonly with ReadonlyArgv type - Add documentation explaining module augmentation pattern is intentional - Document FrontMatter mutation as known technical debt with timeline Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: saberzero1 <8161064+saberzero1@users.noreply.github.com>
85 lines
2.2 KiB
TypeScript
85 lines
2.2 KiB
TypeScript
import { QuartzTransformerPlugin } from "../types"
|
|
import { Root } from "mdast"
|
|
import { visit } from "unist-util-visit"
|
|
import { toString } from "mdast-util-to-string"
|
|
import Slugger from "github-slugger"
|
|
|
|
export interface Options {
|
|
maxDepth: 1 | 2 | 3 | 4 | 5 | 6
|
|
minEntries: number
|
|
showByDefault: boolean
|
|
collapseByDefault: boolean
|
|
}
|
|
|
|
const defaultOptions: Options = {
|
|
maxDepth: 3,
|
|
minEntries: 1,
|
|
showByDefault: true,
|
|
collapseByDefault: false,
|
|
}
|
|
|
|
interface TocEntry {
|
|
depth: number
|
|
text: string
|
|
slug: string // this is just the anchor (#some-slug), not the canonical slug
|
|
}
|
|
|
|
const slugAnchor = new Slugger()
|
|
|
|
/**
|
|
* @plugin TableOfContents
|
|
* @category Transformer
|
|
*
|
|
* @reads vfile.data.frontmatter.enableToc
|
|
* @writes vfile.data.toc
|
|
* @writes vfile.data.collapseToc
|
|
*
|
|
* @dependencies None
|
|
*/
|
|
export const TableOfContents: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
|
|
const opts = { ...defaultOptions, ...userOpts }
|
|
return {
|
|
name: "TableOfContents",
|
|
markdownPlugins() {
|
|
return [
|
|
() => {
|
|
return async (tree: Root, file) => {
|
|
const display = file.data.frontmatter?.enableToc ?? opts.showByDefault
|
|
if (display) {
|
|
slugAnchor.reset()
|
|
const toc: TocEntry[] = []
|
|
let highestDepth: number = opts.maxDepth
|
|
visit(tree, "heading", (node) => {
|
|
if (node.depth <= opts.maxDepth) {
|
|
const text = toString(node)
|
|
highestDepth = Math.min(highestDepth, node.depth)
|
|
toc.push({
|
|
depth: node.depth,
|
|
text,
|
|
slug: slugAnchor.slug(text),
|
|
})
|
|
}
|
|
})
|
|
|
|
if (toc.length > 0 && toc.length > opts.minEntries) {
|
|
file.data.toc = toc.map((entry) => ({
|
|
...entry,
|
|
depth: entry.depth - highestDepth,
|
|
}))
|
|
file.data.collapseToc = opts.collapseByDefault
|
|
}
|
|
}
|
|
}
|
|
},
|
|
]
|
|
},
|
|
}
|
|
}
|
|
|
|
declare module "vfile" {
|
|
interface DataMap {
|
|
toc: TocEntry[]
|
|
collapseToc: boolean
|
|
}
|
|
}
|