From 39231fd8a3b477ec7045b7df91898da9c919d134 Mon Sep 17 00:00:00 2001 From: saberzero1 Date: Wed, 3 Jun 2026 15:23:07 +0200 Subject: [PATCH] build: bundle dependencies and ship pre-built dist --- dist/index.js.map | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/index.js.map b/dist/index.js.map index 54ff3a0..0b63a38 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../node_modules/@quartz-community/types/src/index.ts","../node_modules/preact/src/component.js","../node_modules/preact/src/diff/props.js","../node_modules/preact/src/create-context.js","../node_modules/@quartz-community/utils/src/path.ts","../src/emitter.ts"],"names":["Promise","prototype","then","bind","resolve","setTimeout","Math","random","toString","s","joinSegments","slug","x","_"],"mappings":"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","file":"index.js","sourcesContent":["import type { Root as HtmlRoot } from \"hast\";\nimport type { Root as MdRoot } from \"mdast\";\nimport type { Data, VFile } from \"vfile\";\nimport type { PluggableList } from \"unified\";\n\n// ============================================================================\n// Path Types - Branded string types for type-safe file paths\n// ============================================================================\n\nexport type FilePath = string & { _brand: \"FilePath\" };\nexport type FullSlug = string & { _brand: \"FullSlug\" };\nexport type SimpleSlug = string & { _brand: \"SimpleSlug\" };\n\nexport function joinSegments(...segments: string[]): FilePath {\n return segments\n .filter((segment) => segment.length > 0)\n .join(\"/\")\n .replace(/\\/+/g, \"/\") as FilePath;\n}\n\n// ============================================================================\n// Resource Types - CSS/JS resources that plugins can inject\n// ============================================================================\n\nexport type JSResource =\n | {\n loadTime: \"beforeDOMReady\" | \"afterDOMReady\";\n moduleType?: \"module\";\n spaPreserve?: boolean;\n src: string;\n contentType: \"external\";\n }\n | {\n loadTime: \"beforeDOMReady\" | \"afterDOMReady\";\n moduleType?: \"module\";\n spaPreserve?: boolean;\n script: string;\n contentType: \"inline\";\n };\n\nexport type CSSResource = {\n content: string;\n inline?: boolean;\n spaPreserve?: boolean;\n};\n\nexport interface StaticResources {\n css: CSSResource[];\n js: JSResource[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n additionalHead: any[];\n}\n\n// ============================================================================\n// Config Types - Quartz configuration\n// ============================================================================\n\n/**\n * Flat configuration object that components receive as `cfg` prop.\n * This matches Quartz's `GlobalConfiguration` — the resolved config\n * passed to components at render time (i.e. `ctx.cfg.configuration`).\n */\nexport interface GlobalConfiguration {\n pageTitle?: string;\n pageTitleSuffix?: string;\n enableSPA?: boolean;\n enablePopovers?: boolean;\n analytics?: unknown;\n ignorePatterns?: string[];\n defaultDateType?: string;\n baseUrl?: string;\n theme?: unknown;\n locale?: string;\n}\n\n/**\n * Full Quartz config with nested `configuration` property.\n * This is what `BuildCtx.cfg` holds — used in plugin hooks\n * (transformers, filters, emitters), NOT in component props.\n */\nexport interface QuartzConfig {\n configuration: GlobalConfiguration;\n plugins?: unknown;\n externalPlugins?: unknown;\n}\n\n// ============================================================================\n// Context Types - Build context passed to plugin hooks\n// ============================================================================\n\nexport interface Argv {\n directory: string;\n verbose: boolean;\n output: string;\n serve: boolean;\n watch: boolean;\n port: number;\n wsPort: number;\n remoteDevHost?: string;\n concurrency?: number;\n}\n\nexport interface BuildCtx {\n buildId: string;\n argv: Argv;\n cfg: QuartzConfig;\n allSlugs: FullSlug[];\n allFiles: FilePath[];\n incremental: boolean;\n}\n\n// ============================================================================\n// VFile Types - Content data types\n// ============================================================================\n\nexport type QuartzPluginData = Data;\nexport type MarkdownContent = [MdRoot, VFile];\nexport type ProcessedContent = [HtmlRoot, VFile];\n\n// ============================================================================\n// Core Component Types - The contract between Quartz and plugins\n// ============================================================================\n\nexport type QuartzComponentProps = {\n ctx: unknown;\n externalResources: StaticResources;\n fileData: QuartzPluginData & Record;\n cfg: GlobalConfiguration;\n children: unknown;\n tree: unknown;\n allFiles: (QuartzPluginData & Record)[];\n displayClass?: \"mobile-only\" | \"desktop-only\";\n [key: string]: unknown;\n};\n\nexport type QuartzComponent = ((props: QuartzComponentProps) => unknown) & {\n css?: string | string[] | undefined;\n beforeDOMLoaded?: string | string[] | undefined;\n afterDOMLoaded?: string | string[] | undefined;\n};\n\nexport type QuartzComponentConstructor<\n Options extends object | undefined = undefined,\n> = (opts?: Options) => QuartzComponent;\n\nexport type StringResource = string | string[];\n\n// ============================================================================\n// Plugin Types - What Quartz expects from plugins\n// ============================================================================\n\ntype OptionType = object | undefined;\ntype ExternalResourcesFn = (\n ctx: BuildCtx,\n) => Partial | undefined;\n\nexport type { ExternalResourcesFn };\n\nexport interface PluginTypes {\n transformers: QuartzTransformerPluginInstance[];\n filters: QuartzFilterPluginInstance[];\n emitters: QuartzEmitterPluginInstance[];\n pageTypes: QuartzPageTypePluginInstance[];\n}\n\nexport type QuartzTransformerPlugin = (\n opts?: Options,\n) => QuartzTransformerPluginInstance;\n\nexport type QuartzTransformerPluginInstance = {\n name: string;\n textTransform?: (ctx: BuildCtx, src: string) => string;\n markdownPlugins?: (ctx: BuildCtx) => PluggableList;\n htmlPlugins?: (ctx: BuildCtx) => PluggableList;\n externalResources?: ExternalResourcesFn;\n};\n\nexport type QuartzFilterPlugin = (\n opts?: Options,\n) => QuartzFilterPluginInstance;\n\nexport type QuartzFilterPluginInstance = {\n name: string;\n shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean;\n};\n\nexport type ChangeEvent = {\n type: \"add\" | \"change\" | \"delete\";\n path: FilePath;\n file?: VFile;\n};\n\nexport type QuartzEmitterPlugin = (\n opts?: Options,\n) => QuartzEmitterPluginInstance;\n\nexport type QuartzEmitterPluginInstance = {\n name: string;\n emit: (\n ctx: BuildCtx,\n content: ProcessedContent[],\n resources: StaticResources,\n ) => Promise | AsyncGenerator;\n partialEmit?: (\n ctx: BuildCtx,\n content: ProcessedContent[],\n resources: StaticResources,\n changeEvents: ChangeEvent[],\n ) => Promise | AsyncGenerator | null;\n getQuartzComponents?: (ctx: BuildCtx) => QuartzComponent[];\n externalResources?: ExternalResourcesFn;\n};\n\n// ============================================================================\n// PageType Plugin Types - Declarative page rendering plugins\n// ============================================================================\n\n/**\n * Matcher function: determines if a source file belongs to a page type.\n * Returns true if the page type should own this file.\n */\nexport type PageMatcher = (args: {\n slug: FullSlug;\n fileData: QuartzPluginData & Record;\n cfg: GlobalConfiguration;\n}) => boolean;\n\n/**\n * Virtual page descriptor for page types that generate pages\n * from aggregated data (e.g., tag indexes, folder listings).\n */\nexport interface VirtualPage {\n slug: FullSlug;\n title: string;\n data: Partial & Record;\n}\n\n/**\n * Generator function: produces virtual pages from all processed content.\n * Used by page types that don't match source files but instead create\n * synthetic pages (e.g., one page per tag, one page per folder).\n */\nexport type PageGenerator = (args: {\n content: ProcessedContent[];\n cfg: GlobalConfiguration;\n ctx: BuildCtx;\n}) => VirtualPage[];\n\n/** A function that mutates a HAST tree at render time, when allFiles is available. */\nexport type TreeTransform = (\n root: HtmlRoot,\n slug: FullSlug,\n componentData: QuartzComponentProps,\n) => void;\n\n/**\n * A PageType plugin definition.\n *\n * PageTypes are a declarative abstraction over page-rendering emitters.\n * Each PageType declares which files it owns (via `match`), optionally\n * generates virtual pages (via `generate`), and provides a body component\n * and layout reference for rendering.\n */\nexport type QuartzPageTypePlugin = (\n opts?: Options,\n) => QuartzPageTypePluginInstance;\n\nexport type QuartzPageTypePluginInstance = {\n name: string;\n /** Higher priority wins when multiple page types match the same file. Default: 0. */\n priority?: number;\n /** File extensions this page type handles (e.g. [\".canvas\"]). Files with these extensions are excluded from the default asset emitter and processed by generate() instead. */\n fileExtensions?: string[];\n /** Determines which source files this page type owns. */\n match: PageMatcher;\n /** Produces virtual pages from aggregated content data. */\n generate?: PageGenerator;\n /** Layout key — references a key in `layout.byPageType`. */\n layout: string;\n /** Optional page frame name (e.g. \"default\", \"full-width\", \"canvas\"). Defaults to \"default\". */\n frame?: string;\n /** The body component constructor for this page type. */\n body: QuartzComponentConstructor;\n /** Optional render-time HAST tree transforms (e.g. resolving inline codeblocks). */\n treeTransforms?: (ctx: BuildCtx) => TreeTransform[];\n};\n\n// ============================================================================\n// PageFrame Types — Custom page layout frames\n// ============================================================================\n\n/**\n * Props passed to a PageFrame's render function.\n * Contains the resolved layout components and the shared component data.\n */\nexport interface PageFrameProps {\n /** Component data shared across all components on the page */\n componentData: QuartzComponentProps;\n /** The Head component (rendered in ) — NOT used by frames, included for completeness */\n head: QuartzComponent;\n /** Header slot components (rendered inside
) */\n header: QuartzComponent[];\n /** Components rendered before the page body */\n beforeBody: QuartzComponent[];\n /** The page body component (Content) */\n pageBody: QuartzComponent;\n /** Components rendered after the page body */\n afterBody: QuartzComponent[];\n /** Left sidebar components */\n left: QuartzComponent[];\n /** Right sidebar components */\n right: QuartzComponent[];\n /** Footer component */\n footer: QuartzComponent;\n}\n\n/**\n * A PageFrame defines the inner HTML structure of a page inside the\n * `
` shell. Different frames can produce completely\n * different layouts (e.g. with/without sidebars, horizontal scroll, etc.)\n * while the outer shell (html, head, body, quartz-root) remains stable\n * for SPA navigation.\n *\n * The `render` function returns `unknown` to avoid coupling to a specific\n * JSX runtime (Preact, React, etc.). Quartz core casts this to JSX.Element.\n */\nexport interface PageFrame {\n /** Unique name for this frame (e.g. \"default\", \"full-width\", \"canvas\") */\n name: string;\n /** Render the inner page structure. Returns a JSX tree to be placed inside Body > #quartz-body. */\n render: (props: PageFrameProps) => unknown;\n /** Optional CSS string to include when this frame is active */\n css?: string;\n}\n\nexport type HTMLAttributes = Record<\n string,\n string | number | boolean | undefined\n>;\n\nexport type EventHandler = (event: Event) => void;\n\nexport type CleanupFunction = () => void;\n\nexport type ClassValue =\n | string\n | number\n | boolean\n | undefined\n | null\n | ClassValue[];\n\n// ============================================================================\n// Content Index Types - Data shape for contentIndex.json\n// ============================================================================\n\nexport type ContentDetails = {\n slug: FullSlug;\n filePath: FilePath;\n title: string;\n links: string[];\n tags: string[];\n content: string;\n richContent?: string;\n date?: Date;\n description?: string;\n};\n\nexport type ContentIndex = Record;\n\n// ============================================================================\n// Sort & Theme Types\n// ============================================================================\n\nexport type SortFn = (f1: QuartzPluginData, f2: QuartzPluginData) => number;\n\nexport type ThemeKey = \"lightMode\" | \"darkMode\";\n\nexport type ValidDateType = \"created\" | \"modified\" | \"published\";\n\n// ============================================================================\n// VFile DataMap Augmentation — canonical vfile shape for Quartz\n// ============================================================================\n\ndeclare module \"vfile\" {\n interface DataMap {\n slug: FullSlug;\n filePath: FilePath;\n relativePath: FilePath;\n description: string;\n text: string;\n links: SimpleSlug[];\n toc: { depth: number; text: string; slug: string }[];\n collapseToc: boolean;\n blocks: Record;\n htmlAst: import(\"hast\").Root;\n hasMermaidDiagram: boolean | undefined;\n frontmatter: { [key: string]: unknown } & {\n title: string;\n } & Partial<{\n tags: string[];\n aliases: string[];\n modified: string;\n created: string;\n published: string;\n description: string;\n socialDescription: string;\n publish: boolean | string;\n draft: boolean | string;\n lang: string;\n enableToc: string;\n cssclasses: string[];\n socialImage: string;\n comments: boolean | string;\n }>;\n dates: {\n created: Date;\n modified: Date;\n published: Date;\n };\n defaultDateType?: ValidDateType;\n }\n}\n","import { assign } from './util';\nimport { diff, commitRoot } from './diff/index';\nimport options from './options';\nimport { Fragment } from './create-element';\nimport { MODE_HYDRATE, NULL } from './constants';\n\n/**\n * Base Component class. Provides `setState()` and `forceUpdate()`, which\n * trigger rendering\n * @param {object} props The initial component props\n * @param {object} context The initial context from parent components'\n * getChildContext\n */\nexport function BaseComponent(props, context) {\n\tthis.props = props;\n\tthis.context = context;\n}\n\n/**\n * Update component state and schedule a re-render.\n * @this {import('./internal').Component}\n * @param {object | ((s: object, p: object) => object)} update A hash of state\n * properties to update with new values or a function that given the current\n * state and props returns a new partial state\n * @param {() => void} [callback] A function to be called once component state is\n * updated\n */\nBaseComponent.prototype.setState = function (update, callback) {\n\t// only clone state when copying to nextState the first time.\n\tlet s;\n\tif (this._nextState != NULL && this._nextState != this.state) {\n\t\ts = this._nextState;\n\t} else {\n\t\ts = this._nextState = assign({}, this.state);\n\t}\n\n\tif (typeof update == 'function') {\n\t\t// Some libraries like `immer` mark the current state as readonly,\n\t\t// preventing us from mutating it, so we need to clone it. See #2716\n\t\tupdate = update(assign({}, s), this.props);\n\t}\n\n\tif (update) {\n\t\tassign(s, update);\n\t}\n\n\t// Skip update if updater function returned null\n\tif (update == NULL) return;\n\n\tif (this._vnode) {\n\t\tif (callback) {\n\t\t\tthis._stateCallbacks.push(callback);\n\t\t}\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Immediately perform a synchronous re-render of the component\n * @this {import('./internal').Component}\n * @param {() => void} [callback] A function to be called after component is\n * re-rendered\n */\nBaseComponent.prototype.forceUpdate = function (callback) {\n\tif (this._vnode) {\n\t\t// Set render mode so that we can differentiate where the render request\n\t\t// is coming from. We need this because forceUpdate should never call\n\t\t// shouldComponentUpdate\n\t\tthis._force = true;\n\t\tif (callback) this._renderCallbacks.push(callback);\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Accepts `props` and `state`, and returns a new Virtual DOM tree to build.\n * Virtual DOM is generally constructed via [JSX](https://jasonformat.com/wtf-is-jsx).\n * @param {object} props Props (eg: JSX attributes) received from parent\n * element/component\n * @param {object} state The component's current state\n * @param {object} context Context object, as returned by the nearest\n * ancestor's `getChildContext()`\n * @returns {ComponentChildren | void}\n */\nBaseComponent.prototype.render = Fragment;\n\n/**\n * @param {import('./internal').VNode} vnode\n * @param {number | null} [childIndex]\n */\nexport function getDomSibling(vnode, childIndex) {\n\tif (childIndex == NULL) {\n\t\t// Use childIndex==null as a signal to resume the search from the vnode's sibling\n\t\treturn vnode._parent\n\t\t\t? getDomSibling(vnode._parent, vnode._index + 1)\n\t\t\t: NULL;\n\t}\n\n\tlet sibling;\n\tfor (; childIndex < vnode._children.length; childIndex++) {\n\t\tsibling = vnode._children[childIndex];\n\n\t\tif (sibling != NULL && sibling._dom != NULL) {\n\t\t\t// Since updateParentDomPointers keeps _dom pointer correct,\n\t\t\t// we can rely on _dom to tell us if this subtree contains a\n\t\t\t// rendered DOM node, and what the first rendered DOM node is\n\t\t\treturn sibling._dom;\n\t\t}\n\t}\n\n\t// If we get here, we have not found a DOM node in this vnode's children.\n\t// We must resume from this vnode's sibling (in it's parent _children array)\n\t// Only climb up and search the parent if we aren't searching through a DOM\n\t// VNode (meaning we reached the DOM parent of the original vnode that began\n\t// the search)\n\treturn typeof vnode.type == 'function' ? getDomSibling(vnode) : NULL;\n}\n\n/**\n * Trigger in-place re-rendering of a component.\n * @param {import('./internal').Component} component The component to rerender\n */\nfunction renderComponent(component) {\n\tif (component._parentDom && component._dirty) {\n\t\tlet oldVNode = component._vnode,\n\t\t\toldDom = oldVNode._dom,\n\t\t\tcommitQueue = [],\n\t\t\trefQueue = [],\n\t\t\tnewVNode = assign({}, oldVNode);\n\t\tnewVNode._original = oldVNode._original + 1;\n\t\tif (options.vnode) options.vnode(newVNode);\n\n\t\tdiff(\n\t\t\tcomponent._parentDom,\n\t\t\tnewVNode,\n\t\t\toldVNode,\n\t\t\tcomponent._globalContext,\n\t\t\tcomponent._parentDom.namespaceURI,\n\t\t\toldVNode._flags & MODE_HYDRATE ? [oldDom] : NULL,\n\t\t\tcommitQueue,\n\t\t\toldDom == NULL ? getDomSibling(oldVNode) : oldDom,\n\t\t\t!!(oldVNode._flags & MODE_HYDRATE),\n\t\t\trefQueue\n\t\t);\n\n\t\tnewVNode._original = oldVNode._original;\n\t\tnewVNode._parent._children[newVNode._index] = newVNode;\n\t\tcommitRoot(commitQueue, newVNode, refQueue);\n\t\toldVNode._dom = oldVNode._parent = null;\n\n\t\tif (newVNode._dom != oldDom) {\n\t\t\tupdateParentDomPointers(newVNode);\n\t\t}\n\t}\n}\n\n/**\n * @param {import('./internal').VNode} vnode\n */\nfunction updateParentDomPointers(vnode) {\n\tif ((vnode = vnode._parent) != NULL && vnode._component != NULL) {\n\t\tvnode._dom = vnode._component.base = NULL;\n\t\tvnode._children.some(child => {\n\t\t\tif (child != NULL && child._dom != NULL) {\n\t\t\t\treturn (vnode._dom = vnode._component.base = child._dom);\n\t\t\t}\n\t\t});\n\n\t\treturn updateParentDomPointers(vnode);\n\t}\n}\n\n/**\n * The render queue\n * @type {Array}\n */\nlet rerenderQueue = [];\n\n/*\n * The value of `Component.debounce` must asynchronously invoke the passed in callback. It is\n * important that contributors to Preact can consistently reason about what calls to `setState`, etc.\n * do, and when their effects will be applied. See the links below for some further reading on designing\n * asynchronous APIs.\n * * [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony)\n * * [Callbacks synchronous and asynchronous](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)\n */\n\nlet prevDebounce;\n\nconst defer =\n\ttypeof Promise == 'function'\n\t\t? Promise.prototype.then.bind(Promise.resolve())\n\t\t: setTimeout;\n\n/**\n * Enqueue a rerender of a component\n * @param {import('./internal').Component} c The component to rerender\n */\nexport function enqueueRender(c) {\n\tif (\n\t\t(!c._dirty &&\n\t\t\t(c._dirty = true) &&\n\t\t\trerenderQueue.push(c) &&\n\t\t\t!process._rerenderCount++) ||\n\t\tprevDebounce != options.debounceRendering\n\t) {\n\t\tprevDebounce = options.debounceRendering;\n\t\t(prevDebounce || defer)(process);\n\t}\n}\n\n/**\n * @param {import('./internal').Component} a\n * @param {import('./internal').Component} b\n */\nconst depthSort = (a, b) => a._vnode._depth - b._vnode._depth;\n\n/** Flush the render queue by rerendering all queued components */\nfunction process() {\n\ttry {\n\t\tlet c,\n\t\t\tl = 1;\n\n\t\t// Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary\n\t\t// process() calls from getting scheduled while `queue` is still being consumed.\n\t\twhile (rerenderQueue.length) {\n\t\t\t// Keep the rerender queue sorted by (depth, insertion order). The queue\n\t\t\t// will initially be sorted on the first iteration only if it has more than 1 item.\n\t\t\t//\n\t\t\t// New items can be added to the queue e.g. when rerendering a provider, so we want to\n\t\t\t// keep the order from top to bottom with those new items so we can handle them in a\n\t\t\t// single pass\n\t\t\tif (rerenderQueue.length > l) {\n\t\t\t\trerenderQueue.sort(depthSort);\n\t\t\t}\n\n\t\t\tc = rerenderQueue.shift();\n\t\t\tl = rerenderQueue.length;\n\n\t\t\trenderComponent(c);\n\t\t}\n\t} finally {\n\t\trerenderQueue.length = process._rerenderCount = 0;\n\t}\n}\n\nprocess._rerenderCount = 0;\n","import { IS_NON_DIMENSIONAL, NULL, SVG_NAMESPACE } from '../constants';\nimport options from '../options';\n\n// Per-instance unique key for event clock stamps. Each Preact copy on the page\n// gets its own random suffix so that `_dispatched` / `_attached` properties on\n// shared event objects and handler functions cannot collide across instances.\n// ~1 in 60M collision odds - if you have that many praect versions on the page,\n// you deserve some weird bugs.\n// In 11 we can replace this with a\n// Symbol\nlet _id = Math.random().toString(8),\n\tEVENT_DISPATCHED = '__d' + _id,\n\tEVENT_ATTACHED = '__a' + _id;\n\nfunction setStyle(style, key, value) {\n\tif (key[0] == '-') {\n\t\tstyle.setProperty(key, value == NULL ? '' : value);\n\t} else if (value == NULL) {\n\t\tstyle[key] = '';\n\t} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {\n\t\tstyle[key] = value;\n\t} else {\n\t\tstyle[key] = value + 'px';\n\t}\n}\n\nconst CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;\n\n// A logical clock to solve issues like https://github.com/preactjs/preact/issues/3927.\n// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that\n// an event can trigger on a newly reated DOM-node while the event bubbles up.\n//\n// Originally inspired by Vue\n// (https://github.com/vuejs/core/blob/caeb8a68811a1b0f79/packages/runtime-dom/src/modules/events.ts#L90-L101),\n// but modified to use a logical clock instead of Date.now() in case event handlers get attached\n// and events get dispatched during the same millisecond.\n//\n// The clock is incremented after each new event dispatch. This allows 1 000 000 new events\n// per second for over 280 years before the value reaches Number.MAX_SAFE_INTEGER (2**53 - 1).\nlet eventClock = 0;\n\n/**\n * Set a property value on a DOM node\n * @param {import('../internal').PreactElement} dom The DOM node to modify\n * @param {string} name The name of the property to set\n * @param {*} value The value to set the property to\n * @param {*} oldValue The old value the property had\n * @param {string} namespace Whether or not this DOM node is an SVG node or not\n */\nexport function setProperty(dom, name, value, oldValue, namespace) {\n\tlet useCapture;\n\n\to: if (name == 'style') {\n\t\tif (typeof value == 'string') {\n\t\t\tdom.style.cssText = value;\n\t\t} else {\n\t\t\tif (typeof oldValue == 'string') {\n\t\t\t\tdom.style.cssText = oldValue = '';\n\t\t\t}\n\n\t\t\tif (oldValue) {\n\t\t\t\tfor (name in oldValue) {\n\t\t\t\t\tif (!(value && name in value)) {\n\t\t\t\t\t\tsetStyle(dom.style, name, '');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (value) {\n\t\t\t\tfor (name in value) {\n\t\t\t\t\tif (!oldValue || value[name] != oldValue[name]) {\n\t\t\t\t\t\tsetStyle(dom.style, name, value[name]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6\n\telse if (name[0] == 'o' && name[1] == 'n') {\n\t\tuseCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));\n\t\tconst lowerCaseName = name.toLowerCase();\n\n\t\t// Infer correct casing for DOM built-in events:\n\t\tif (lowerCaseName in dom || name == 'onFocusOut' || name == 'onFocusIn')\n\t\t\tname = lowerCaseName.slice(2);\n\t\telse name = name.slice(2);\n\n\t\tif (!dom._listeners) dom._listeners = {};\n\t\tdom._listeners[name + useCapture] = value;\n\n\t\tif (value) {\n\t\t\tif (!oldValue) {\n\t\t\t\tvalue[EVENT_ATTACHED] = eventClock;\n\t\t\t\tdom.addEventListener(\n\t\t\t\t\tname,\n\t\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\t\tuseCapture\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tvalue[EVENT_ATTACHED] = oldValue[EVENT_ATTACHED];\n\t\t\t}\n\t\t} else {\n\t\t\tdom.removeEventListener(\n\t\t\t\tname,\n\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\tuseCapture\n\t\t\t);\n\t\t}\n\t} else {\n\t\tif (namespace == SVG_NAMESPACE) {\n\t\t\t// Normalize incorrect prop usage for SVG:\n\t\t\t// - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)\n\t\t\t// - className --> class\n\t\t\tname = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');\n\t\t} else if (\n\t\t\tname != 'width' &&\n\t\t\tname != 'height' &&\n\t\t\tname != 'href' &&\n\t\t\tname != 'list' &&\n\t\t\tname != 'form' &&\n\t\t\t// Default value in browsers is `-1` and an empty string is\n\t\t\t// cast to `0` instead\n\t\t\tname != 'tabIndex' &&\n\t\t\tname != 'download' &&\n\t\t\tname != 'rowSpan' &&\n\t\t\tname != 'colSpan' &&\n\t\t\tname != 'role' &&\n\t\t\tname != 'popover' &&\n\t\t\tname in dom\n\t\t) {\n\t\t\ttry {\n\t\t\t\tdom[name] = value == NULL ? '' : value;\n\t\t\t\t// labelled break is 1b smaller here than a return statement (sorry)\n\t\t\t\tbreak o;\n\t\t\t} catch (e) {}\n\t\t}\n\n\t\t// aria- and data- attributes have no boolean representation.\n\t\t// A `false` value is different from the attribute not being\n\t\t// present, so we can't remove it. For non-boolean aria\n\t\t// attributes we could treat false as a removal, but the\n\t\t// amount of exceptions would cost too many bytes. On top of\n\t\t// that other frameworks generally stringify `false`.\n\n\t\tif (typeof value == 'function') {\n\t\t\t// never serialize functions as attribute values\n\t\t} else if (value != NULL && (value !== false || name[4] == '-')) {\n\t\t\tdom.setAttribute(name, name == 'popover' && value == true ? '' : value);\n\t\t} else {\n\t\t\tdom.removeAttribute(name);\n\t\t}\n\t}\n}\n\n/**\n * Create an event proxy function.\n * @param {boolean} useCapture Is the event handler for the capture phase.\n * @private\n */\nfunction createEventProxy(useCapture) {\n\t/**\n\t * Proxy an event to hooked event handlers\n\t * @param {import('../internal').PreactEvent} e The event object from the browser\n\t * @private\n\t */\n\treturn function (e) {\n\t\tif (this._listeners) {\n\t\t\tconst eventHandler = this._listeners[e.type + useCapture];\n\t\t\tif (e[EVENT_DISPATCHED] == NULL) {\n\t\t\t\te[EVENT_DISPATCHED] = eventClock++;\n\n\t\t\t\t// When `e[EVENT_DISPATCHED]` is smaller than the time when the targeted event\n\t\t\t\t// handler was attached we know we have bubbled up to an element that was added\n\t\t\t\t// during patching the DOM.\n\t\t\t} else if (e[EVENT_DISPATCHED] < eventHandler[EVENT_ATTACHED]) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn eventHandler(options.event ? options.event(e) : e);\n\t\t}\n\t};\n}\n\nconst eventProxy = createEventProxy(false);\nconst eventProxyCapture = createEventProxy(true);\n","import { enqueueRender } from './component';\nimport { NULL } from './constants';\n\nexport let i = 0;\n\nexport function createContext(defaultValue) {\n\tfunction Context(props) {\n\t\tif (!this.getChildContext) {\n\t\t\t/** @type {Set | null} */\n\t\t\tlet subs = new Set();\n\t\t\tlet ctx = {};\n\t\t\tctx[Context._id] = this;\n\n\t\t\tthis.getChildContext = () => ctx;\n\n\t\t\tthis.componentWillUnmount = () => {\n\t\t\t\tsubs = NULL;\n\t\t\t};\n\n\t\t\tthis.shouldComponentUpdate = function (_props) {\n\t\t\t\t// @ts-expect-error even\n\t\t\t\tif (this.props.value != _props.value) {\n\t\t\t\t\tsubs.forEach(c => {\n\t\t\t\t\t\tc._force = true;\n\t\t\t\t\t\tenqueueRender(c);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.sub = c => {\n\t\t\t\tsubs.add(c);\n\t\t\t\tlet old = c.componentWillUnmount;\n\t\t\t\tc.componentWillUnmount = () => {\n\t\t\t\t\tif (subs) {\n\t\t\t\t\t\tsubs.delete(c);\n\t\t\t\t\t}\n\t\t\t\t\tif (old) old.call(c);\n\t\t\t\t};\n\t\t\t};\n\t\t}\n\n\t\treturn props.children;\n\t}\n\n\tContext._id = '__cC' + i++;\n\tContext._defaultValue = defaultValue;\n\n\t/** @type {import('./internal').FunctionComponent} */\n\tContext.Consumer = (props, contextValue) => {\n\t\treturn props.children(contextValue);\n\t};\n\n\t// we could also get rid of _contextRef entirely\n\tContext.Provider =\n\t\tContext._contextRef =\n\t\tContext.Consumer.contextType =\n\t\t\tContext;\n\n\treturn Context;\n}\n","import { slug as slugAnchor } from \"github-slugger\";\nimport type { Element as HastElement } from \"hast\";\nimport type { FilePath, FullSlug } from \"@quartz-community/types\";\n\nexport type { FilePath, FullSlug };\n\n/** No '/index' ending, no file extension, can have trailing slash for folders. */\nexport type SimpleSlug = string & { _brand: \"SimpleSlug\" };\n\n/** Starts with './' or '../', used for navigation. */\nexport type RelativeURL = string & { _brand: \"RelativeURL\" };\n\nexport interface TransformOptions {\n strategy: \"absolute\" | \"relative\" | \"shortest\";\n allSlugs: FullSlug[];\n}\n\nexport function isFilePath(s: string): s is FilePath {\n const validStart = !s.startsWith(\".\");\n return validStart && _hasFileExtension(s);\n}\n\nexport function isFullSlug(s: string): s is FullSlug {\n const validStart = !(s.startsWith(\".\") || s.startsWith(\"/\"));\n const validEnding = !s.endsWith(\"/\");\n return validStart && validEnding && !_containsForbiddenCharacters(s);\n}\n\nexport function isSimpleSlug(s: string): s is SimpleSlug {\n const validStart = !(s.startsWith(\".\") || (s.length > 1 && s.startsWith(\"/\")));\n const validEnding = !endsWith(s, \"index\");\n return validStart && !_containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s);\n}\n\nexport function isRelativeURL(s: string): s is RelativeURL {\n const validStart = /^\\.{1,2}/.test(s);\n const validEnding = !endsWith(s, \"index\");\n return validStart && validEnding && ![\".md\", \".html\"].includes(getFileExtension(s) ?? \"\");\n}\n\nexport function isAbsoluteURL(s: string): boolean {\n try {\n new URL(s);\n } catch {\n return false;\n }\n return true;\n}\n\nexport function simplifySlug(fp: FullSlug | string): SimpleSlug {\n const res = stripSlashes(trimSuffix(fp, \"index\"), true);\n return (res.length === 0 ? \"/\" : res) as SimpleSlug;\n}\n\nexport function getFullSlug(window: Window): FullSlug {\n const res = window.document.body.dataset.slug! as FullSlug;\n return res;\n}\n\nexport function getFullSlugFromUrl(): FullSlug {\n let rawSlug = window.location.pathname;\n if (rawSlug.endsWith(\"/\")) rawSlug = rawSlug.slice(0, -1);\n if (rawSlug.startsWith(\"/\")) rawSlug = rawSlug.slice(1);\n return rawSlug as FullSlug;\n}\n\nexport function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {\n fp = stripSlashes(fp) as FilePath;\n const ext = getFileExtension(fp);\n const withoutFileExt = fp.replace(new RegExp(ext + \"$\"), \"\");\n const finalExt = excludeExt || [\".md\", \".html\", undefined].includes(ext) ? \"\" : ext;\n\n let slug = _sluggify(withoutFileExt);\n\n if (endsWith(slug, \"_index\")) {\n slug = slug.replace(/_index$/, \"index\");\n }\n\n // Obsidian \"Folder Notes\" convention (insideFolder storage): a file named the\n // same as its parent folder is treated as the folder's landing page. Rewrite\n // `folder/folder` → `folder/index` so every downstream consumer (trie, link\n // resolver, simplifySlug, isFolderPath) sees the canonical Quartz slug shape\n // and Just Works. A top-level single-segment slug is NOT rewritten — that\n // represents Obsidian's `parentFolder` storage mode, which Quartz already\n // routes as a regular top-level page.\n const segments = slug.split(\"/\");\n if (segments.length >= 2 && segments[segments.length - 1] === segments[segments.length - 2]) {\n segments[segments.length - 1] = \"index\";\n slug = segments.join(\"/\");\n }\n\n return (slug + (finalExt ?? \"\")) as FullSlug;\n}\n\nexport function joinSegments(...args: string[]): string {\n if (args.length === 0) {\n return \"\";\n }\n\n let joined = args\n .filter((segment) => segment !== \"\" && segment !== \"/\")\n .map((segment) => stripSlashes(segment))\n .join(\"/\");\n\n const first = args[0];\n const last = args[args.length - 1];\n\n if (first?.startsWith(\"/\")) {\n joined = \"/\" + joined;\n }\n\n if (last?.endsWith(\"/\")) {\n joined = joined + \"/\";\n }\n\n return joined;\n}\n\nexport function resolvePath(to: string): string {\n if (to.startsWith(\"/\")) return to;\n return \"/\" + to;\n}\n\n/** Read the base path injected at build time via `data-basepath` on ``.\n * Returns `\"\"` for root deployments, e.g. `\"/repository\"` for subdirectory. */\nexport function getBasePath(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.body?.dataset?.basepath ?? \"\";\n}\n\n/** Resolve a slug to an absolute URL path, prepending the site's base path.\n * e.g. `resolveBasePath(\"features/Callouts\")` → `\"/repository/features/Callouts\"` */\nexport function resolveBasePath(to: string, basePath?: string): string {\n const base = basePath ?? getBasePath();\n const slug = to.startsWith(\"/\") ? to : \"/\" + to;\n return base + slug;\n}\n\nexport function endsWith(s: string, suffix: string): boolean {\n return s === suffix || s.endsWith(\"/\" + suffix);\n}\n\nexport function trimSuffix(s: string, suffix: string): string {\n if (endsWith(s, suffix)) {\n s = s.slice(0, -suffix.length);\n }\n return s;\n}\n\nexport function stripSlashes(s: string, onlyStripPrefix?: boolean): string {\n if (s.startsWith(\"/\")) {\n s = s.substring(1);\n }\n\n if (!onlyStripPrefix && s.endsWith(\"/\")) {\n s = s.slice(0, -1);\n }\n\n return s;\n}\n\nexport function getFileExtension(s: string): string | undefined {\n return s.match(/\\.[A-Za-z0-9]+$/)?.[0];\n}\n\nexport function isFolderPath(fplike: string): boolean {\n return (\n fplike.endsWith(\"/\") ||\n endsWith(fplike, \"index\") ||\n endsWith(fplike, \"index.md\") ||\n endsWith(fplike, \"index.html\")\n );\n}\n\nexport function getAllSegmentPrefixes(path: string): string[] {\n const segments = path.split(\"/\");\n const results: string[] = [];\n for (let i = 0; i < segments.length; i++) {\n results.push(segments.slice(0, i + 1).join(\"/\"));\n }\n return results;\n}\n\nexport function pathToRoot(slug: FullSlug): RelativeURL {\n let rootPath = slug\n .split(\"/\")\n .filter((x) => x !== \"\")\n .slice(0, -1)\n .map((_) => \"..\")\n .join(\"/\");\n\n if (rootPath.length === 0) {\n rootPath = \".\";\n }\n\n return rootPath as RelativeURL;\n}\n\nexport function resolveRelative(current: FullSlug, target: FullSlug | SimpleSlug): RelativeURL {\n const res = joinSegments(pathToRoot(current), simplifySlug(target as FullSlug)) as RelativeURL;\n return res;\n}\n\nexport function splitAnchor(link: string): [string, string] {\n const [fp, anchor] = link.split(\"#\", 2);\n if (fp!.endsWith(\".pdf\")) {\n return [fp!, anchor === undefined ? \"\" : `#${anchor}`];\n }\n const slugged = anchor === undefined ? \"\" : \"#\" + slugAnchor(anchor);\n return [fp!, slugged];\n}\n\nexport function slugTag(tag: string): string {\n return tag\n .split(\"/\")\n .map((tagSegment) => _sluggify(tagSegment))\n .join(\"/\");\n}\n\nexport function transformInternalLink(link: string): RelativeURL {\n const [fplike, anchor] = splitAnchor(decodeURI(link));\n\n const segments = fplike.split(\"/\").filter((x) => x.length > 0);\n const prefix = segments.filter(_isRelativeSegment).join(\"/\");\n const fp = segments.filter((seg) => !_isRelativeSegment(seg) && seg !== \"\").join(\"/\");\n\n const slugged = slugifyFilePath(fp as FilePath);\n const simpleSlug = simplifySlug(slugged);\n const folderPath = isFolderPath(fplike) || isFolderPath(slugged);\n const joined = joinSegments(stripSlashes(prefix), stripSlashes(simpleSlug));\n const trail = folderPath ? \"/\" : \"\";\n const res = (_addRelativeToStart(joined) + trail + anchor) as RelativeURL;\n return res;\n}\n\nexport function transformLink(src: FullSlug, target: string, opts: TransformOptions): RelativeURL {\n const targetSlug = transformInternalLink(target);\n\n if (opts.strategy === \"relative\") {\n return targetSlug as RelativeURL;\n } else {\n // When src is a folder page (e.g. \"Compendium/Spells\" backed by index.md),\n // the browser serves it at \"/Compendium/Spells/\" with a trailing slash.\n // pathToRoot needs the \"/index\" suffix to compute the correct depth.\n const effectiveSrc =\n !endsWith(src, \"index\") && opts.allSlugs.includes(`${src}/index` as FullSlug)\n ? (`${src}/index` as FullSlug)\n : src;\n\n const folderTail = isFolderPath(targetSlug) ? \"/\" : \"\";\n const canonicalSlug = stripSlashes(targetSlug.slice(\".\".length));\n const [targetCanonical, targetAnchor] = splitAnchor(canonicalSlug);\n\n if (opts.strategy === \"shortest\") {\n const isMultiSegment = targetCanonical.includes(\"/\");\n const isFolderTarget = isFolderPath(targetSlug);\n const matchingFileNames = opts.allSlugs.filter((slug) => {\n if (isMultiSegment) {\n // Multi-segment partial path: match by suffix\n if (slug === targetCanonical || slug.endsWith(\"/\" + targetCanonical)) {\n return true;\n }\n // Folder targets have \"/index\" stripped — also match the \"/index\" variant\n if (isFolderTarget) {\n const withIndex = targetCanonical + \"/index\";\n return slug === withIndex || slug.endsWith(\"/\" + withIndex);\n }\n return false;\n }\n const parts = slug.split(\"/\");\n const fileName = parts.at(-1);\n return targetCanonical === fileName;\n });\n\n if (matchingFileNames.length === 1) {\n const matchedSlug = matchingFileNames[0]!;\n return (resolveRelative(effectiveSrc, matchedSlug) + targetAnchor) as RelativeURL;\n }\n }\n\n return (joinSegments(pathToRoot(effectiveSrc), canonicalSlug) + folderTail) as RelativeURL;\n }\n}\n\n/**\n * Slugify a file path for use as an href.\n * Replaces whitespace→hyphens, &→-and-, %→-percent, removes ? and #, and\n * lowercases the result so that link matching is case-insensitive (matching\n * Obsidian's link-resolution semantics). Operates per segment so directory\n * separators are preserved.\n */\nexport function slugifyPath(s: string): string {\n return s\n .split(\"/\")\n .map((segment) =>\n segment\n .replace(/\\s/g, \"-\")\n .replace(/&/g, \"-and-\")\n .replace(/%/g, \"-percent\")\n .replace(/\\?/g, \"\")\n .replace(/#/g, \"\")\n .toLowerCase(),\n )\n .join(\"/\")\n .replace(/\\/$/, \"\");\n}\n\n/**\n * Re-base the relative `href` and `src` attributes of a HAST element so that\n * links inside content originally resolved relative to `newBase` remain valid\n * when the element is embedded at a page with slug `curBase`.\n *\n * Used for transclusions and cross-slug HAST embedding (Quartz core's\n * `renderPage` transclude expansion, canvas-page's embedded file-nodes, and\n * any plugin that takes `vfile.data.htmlAst` from one page and renders it\n * inside another). Absolute URLs pass through unchanged.\n *\n * The element (and its children) are deep-cloned via `structuredClone`, so\n * the original HAST tree is never mutated.\n */\nexport function normalizeHastElement(\n rawEl: T,\n curBase: FullSlug,\n newBase: FullSlug,\n): T {\n const el = structuredClone(rawEl);\n _rebaseHastElement(el, \"src\", curBase, newBase);\n _rebaseHastElement(el, \"href\", curBase, newBase);\n if (el.children) {\n el.children = el.children.map((child) =>\n child.type === \"element\"\n ? (normalizeHastElement(child as HastElement, curBase, newBase) as typeof child)\n : child,\n );\n }\n return el;\n}\n\n/** @internal */\nfunction _rebaseHastElement(\n el: HastElement,\n attr: \"href\" | \"src\",\n curBase: FullSlug,\n newBase: FullSlug,\n): void {\n const value = el.properties?.[attr];\n if (value === undefined || value === null) return;\n const href = String(value);\n if (!isRelativeURL(href)) return;\n el.properties![attr] = joinSegments(resolveRelative(curBase, newBase), \"..\", href);\n}\n\n/** @internal */\nfunction _sluggify(s: string): string {\n return slugifyPath(s);\n}\n\nfunction _containsForbiddenCharacters(s: string): boolean {\n return s.includes(\" \") || s.includes(\"#\") || s.includes(\"?\") || s.includes(\"&\");\n}\n\nfunction _hasFileExtension(s: string): boolean {\n return getFileExtension(s) !== undefined;\n}\n\nfunction _isRelativeSegment(s: string): boolean {\n return /^\\.{0,2}$/.test(s);\n}\n\nfunction _addRelativeToStart(s: string): string {\n if (s === \"\") {\n s = \".\";\n }\n\n if (!s.startsWith(\".\")) {\n s = joinSegments(\".\", s);\n }\n\n return s;\n}\n","import path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport type { QuartzEmitterPlugin, BuildCtx, FilePath, FullSlug } from \"@quartz-community/types\";\nimport { joinSegments } from \"@quartz-community/types\";\nimport { simplifySlug, resolveRelative, isRelativeURL } from \"@quartz-community/utils\";\nimport type { VFile } from \"vfile\";\n\nconst write = async (\n ctx: BuildCtx,\n slug: FullSlug,\n ext: string,\n content: string,\n): Promise => {\n const pathToPage = joinSegments(ctx.argv.output, slug + ext) as FilePath;\n const dir = path.dirname(pathToPage);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(pathToPage, content);\n return pathToPage;\n};\n\nasync function* processFile(ctx: BuildCtx, file: VFile) {\n const ogSlug = simplifySlug(file.data.slug! as FullSlug);\n\n for (const aliasTarget of ((file.data as Record).aliases as string[]) ?? []) {\n const aliasTargetSlug = (\n isRelativeURL(aliasTarget)\n ? path.normalize(path.join(ogSlug, \"..\", aliasTarget))\n : aliasTarget\n ) as FullSlug;\n\n const redirUrl = resolveRelative(aliasTargetSlug, ogSlug);\n yield write(\n ctx,\n aliasTargetSlug,\n \".html\",\n `\n \n \n \n ${ogSlug}\n \n \n \n \n \n \n `,\n );\n }\n}\n\nexport const AliasRedirects: QuartzEmitterPlugin = () => ({\n name: \"AliasRedirects\",\n async *emit(ctx, content) {\n for (const [_tree, file] of content) {\n yield* processFile(ctx, file);\n }\n },\n async *partialEmit(ctx, _content, _resources, changeEvents) {\n for (const changeEvent of changeEvents) {\n if (!changeEvent.file) continue;\n if (changeEvent.type === \"add\" || changeEvent.type === \"change\") {\n yield* processFile(ctx, changeEvent.file);\n }\n }\n },\n});\n"]} \ No newline at end of file +{"version":3,"sources":["../node_modules/@quartz-community/types/src/index.ts","../node_modules/preact/src/component.js","../node_modules/preact/src/diff/props.js","../node_modules/preact/src/create-context.js","../node_modules/@quartz-community/utils/src/path.ts","../src/emitter.ts"],"names":["Promise","prototype","then","bind","resolve","setTimeout","Math","random","toString","s","joinSegments","slug","x","_","slugAnchor"],"mappings":"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","file":"index.js","sourcesContent":["import type { Root as HtmlRoot } from \"hast\";\nimport type { Root as MdRoot } from \"mdast\";\nimport type { Data, VFile } from \"vfile\";\nimport type { PluggableList } from \"unified\";\n\n// ============================================================================\n// Path Types - Branded string types for type-safe file paths\n// ============================================================================\n\nexport type FilePath = string & { _brand: \"FilePath\" };\nexport type FullSlug = string & { _brand: \"FullSlug\" };\nexport type SimpleSlug = string & { _brand: \"SimpleSlug\" };\n\nexport function joinSegments(...segments: string[]): FilePath {\n return segments\n .filter((segment) => segment.length > 0)\n .join(\"/\")\n .replace(/\\/+/g, \"/\") as FilePath;\n}\n\n// ============================================================================\n// Resource Types - CSS/JS resources that plugins can inject\n// ============================================================================\n\nexport type JSResource =\n | {\n loadTime: \"beforeDOMReady\" | \"afterDOMReady\";\n moduleType?: \"module\";\n spaPreserve?: boolean;\n src: string;\n contentType: \"external\";\n }\n | {\n loadTime: \"beforeDOMReady\" | \"afterDOMReady\";\n moduleType?: \"module\";\n spaPreserve?: boolean;\n script: string;\n contentType: \"inline\";\n };\n\nexport type CSSResource = {\n content: string;\n inline?: boolean;\n spaPreserve?: boolean;\n};\n\nexport interface StaticResources {\n css: CSSResource[];\n js: JSResource[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n additionalHead: any[];\n}\n\n// ============================================================================\n// Config Types - Quartz configuration\n// ============================================================================\n\n/**\n * Flat configuration object that components receive as `cfg` prop.\n * This matches Quartz's `GlobalConfiguration` — the resolved config\n * passed to components at render time (i.e. `ctx.cfg.configuration`).\n */\nexport interface GlobalConfiguration {\n pageTitle?: string;\n pageTitleSuffix?: string;\n enableSPA?: boolean;\n enablePopovers?: boolean;\n analytics?: unknown;\n ignorePatterns?: string[];\n defaultDateType?: string;\n baseUrl?: string;\n theme?: unknown;\n locale?: string;\n}\n\n/**\n * Full Quartz config with nested `configuration` property.\n * This is what `BuildCtx.cfg` holds — used in plugin hooks\n * (transformers, filters, emitters), NOT in component props.\n */\nexport interface QuartzConfig {\n configuration: GlobalConfiguration;\n plugins?: unknown;\n externalPlugins?: unknown;\n}\n\n// ============================================================================\n// Context Types - Build context passed to plugin hooks\n// ============================================================================\n\nexport interface Argv {\n directory: string;\n verbose: boolean;\n output: string;\n serve: boolean;\n watch: boolean;\n port: number;\n wsPort: number;\n remoteDevHost?: string;\n concurrency?: number;\n}\n\nexport interface BuildCtx {\n buildId: string;\n argv: Argv;\n cfg: QuartzConfig;\n allSlugs: FullSlug[];\n allFiles: FilePath[];\n incremental: boolean;\n}\n\n// ============================================================================\n// VFile Types - Content data types\n// ============================================================================\n\nexport type QuartzPluginData = Data;\nexport type MarkdownContent = [MdRoot, VFile];\nexport type ProcessedContent = [HtmlRoot, VFile];\n\n// ============================================================================\n// Core Component Types - The contract between Quartz and plugins\n// ============================================================================\n\nexport type QuartzComponentProps = {\n ctx: unknown;\n externalResources: StaticResources;\n fileData: QuartzPluginData & Record;\n cfg: GlobalConfiguration;\n children: unknown;\n tree: unknown;\n allFiles: (QuartzPluginData & Record)[];\n displayClass?: \"mobile-only\" | \"desktop-only\";\n [key: string]: unknown;\n};\n\nexport type QuartzComponent = ((props: QuartzComponentProps) => unknown) & {\n css?: string | string[] | undefined;\n beforeDOMLoaded?: string | string[] | undefined;\n afterDOMLoaded?: string | string[] | undefined;\n};\n\nexport type QuartzComponentConstructor<\n Options extends object | undefined = undefined,\n> = (opts?: Options) => QuartzComponent;\n\nexport type StringResource = string | string[];\n\n// ============================================================================\n// Plugin Types - What Quartz expects from plugins\n// ============================================================================\n\ntype OptionType = object | undefined;\ntype ExternalResourcesFn = (\n ctx: BuildCtx,\n) => Partial | undefined;\n\nexport type { ExternalResourcesFn };\n\nexport interface PluginTypes {\n transformers: QuartzTransformerPluginInstance[];\n filters: QuartzFilterPluginInstance[];\n emitters: QuartzEmitterPluginInstance[];\n pageTypes: QuartzPageTypePluginInstance[];\n}\n\nexport type QuartzTransformerPlugin = (\n opts?: Options,\n) => QuartzTransformerPluginInstance;\n\nexport type QuartzTransformerPluginInstance = {\n name: string;\n textTransform?: (ctx: BuildCtx, src: string) => string;\n markdownPlugins?: (ctx: BuildCtx) => PluggableList;\n htmlPlugins?: (ctx: BuildCtx) => PluggableList;\n externalResources?: ExternalResourcesFn;\n};\n\nexport type QuartzFilterPlugin = (\n opts?: Options,\n) => QuartzFilterPluginInstance;\n\nexport type QuartzFilterPluginInstance = {\n name: string;\n shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean;\n};\n\nexport type ChangeEvent = {\n type: \"add\" | \"change\" | \"delete\";\n path: FilePath;\n file?: VFile;\n};\n\nexport type QuartzEmitterPlugin = (\n opts?: Options,\n) => QuartzEmitterPluginInstance;\n\nexport type QuartzEmitterPluginInstance = {\n name: string;\n emit: (\n ctx: BuildCtx,\n content: ProcessedContent[],\n resources: StaticResources,\n ) => Promise | AsyncGenerator;\n partialEmit?: (\n ctx: BuildCtx,\n content: ProcessedContent[],\n resources: StaticResources,\n changeEvents: ChangeEvent[],\n ) => Promise | AsyncGenerator | null;\n getQuartzComponents?: (ctx: BuildCtx) => QuartzComponent[];\n externalResources?: ExternalResourcesFn;\n};\n\n// ============================================================================\n// PageType Plugin Types - Declarative page rendering plugins\n// ============================================================================\n\n/**\n * Matcher function: determines if a source file belongs to a page type.\n * Returns true if the page type should own this file.\n */\nexport type PageMatcher = (args: {\n slug: FullSlug;\n fileData: QuartzPluginData & Record;\n cfg: GlobalConfiguration;\n}) => boolean;\n\n/**\n * Virtual page descriptor for page types that generate pages\n * from aggregated data (e.g., tag indexes, folder listings).\n */\nexport interface VirtualPage {\n slug: FullSlug;\n title: string;\n data: Partial & Record;\n}\n\n/**\n * Generator function: produces virtual pages from all processed content.\n * Used by page types that don't match source files but instead create\n * synthetic pages (e.g., one page per tag, one page per folder).\n */\nexport type PageGenerator = (args: {\n content: ProcessedContent[];\n cfg: GlobalConfiguration;\n ctx: BuildCtx;\n}) => VirtualPage[];\n\n/** A function that mutates a HAST tree at render time, when allFiles is available. */\nexport type TreeTransform = (\n root: HtmlRoot,\n slug: FullSlug,\n componentData: QuartzComponentProps,\n) => void;\n\n/**\n * A PageType plugin definition.\n *\n * PageTypes are a declarative abstraction over page-rendering emitters.\n * Each PageType declares which files it owns (via `match`), optionally\n * generates virtual pages (via `generate`), and provides a body component\n * and layout reference for rendering.\n */\nexport type QuartzPageTypePlugin = (\n opts?: Options,\n) => QuartzPageTypePluginInstance;\n\nexport type QuartzPageTypePluginInstance = {\n name: string;\n /** Higher priority wins when multiple page types match the same file. Default: 0. */\n priority?: number;\n /** File extensions this page type handles (e.g. [\".canvas\"]). Files with these extensions are excluded from the default asset emitter and processed by generate() instead. */\n fileExtensions?: string[];\n /** Determines which source files this page type owns. */\n match: PageMatcher;\n /** Produces virtual pages from aggregated content data. */\n generate?: PageGenerator;\n /** Layout key — references a key in `layout.byPageType`. */\n layout: string;\n /** Optional page frame name (e.g. \"default\", \"full-width\", \"canvas\"). Defaults to \"default\". */\n frame?: string;\n /** The body component constructor for this page type. */\n body: QuartzComponentConstructor;\n /** Optional render-time HAST tree transforms (e.g. resolving inline codeblocks). */\n treeTransforms?: (ctx: BuildCtx) => TreeTransform[];\n};\n\n// ============================================================================\n// PageFrame Types — Custom page layout frames\n// ============================================================================\n\n/**\n * Props passed to a PageFrame's render function.\n * Contains the resolved layout components and the shared component data.\n */\nexport interface PageFrameProps {\n /** Component data shared across all components on the page */\n componentData: QuartzComponentProps;\n /** The Head component (rendered in ) — NOT used by frames, included for completeness */\n head: QuartzComponent;\n /** Header slot components (rendered inside
) */\n header: QuartzComponent[];\n /** Components rendered before the page body */\n beforeBody: QuartzComponent[];\n /** The page body component (Content) */\n pageBody: QuartzComponent;\n /** Components rendered after the page body */\n afterBody: QuartzComponent[];\n /** Left sidebar components */\n left: QuartzComponent[];\n /** Right sidebar components */\n right: QuartzComponent[];\n /** Footer component */\n footer: QuartzComponent;\n}\n\n/**\n * A PageFrame defines the inner HTML structure of a page inside the\n * `
` shell. Different frames can produce completely\n * different layouts (e.g. with/without sidebars, horizontal scroll, etc.)\n * while the outer shell (html, head, body, quartz-root) remains stable\n * for SPA navigation.\n *\n * The `render` function returns `unknown` to avoid coupling to a specific\n * JSX runtime (Preact, React, etc.). Quartz core casts this to JSX.Element.\n */\nexport interface PageFrame {\n /** Unique name for this frame (e.g. \"default\", \"full-width\", \"canvas\") */\n name: string;\n /** Render the inner page structure. Returns a JSX tree to be placed inside Body > #quartz-body. */\n render: (props: PageFrameProps) => unknown;\n /** Optional CSS string to include when this frame is active */\n css?: string;\n}\n\nexport type HTMLAttributes = Record<\n string,\n string | number | boolean | undefined\n>;\n\nexport type EventHandler = (event: Event) => void;\n\nexport type CleanupFunction = () => void;\n\nexport type ClassValue =\n | string\n | number\n | boolean\n | undefined\n | null\n | ClassValue[];\n\n// ============================================================================\n// Content Index Types - Data shape for contentIndex.json\n// ============================================================================\n\nexport type ContentDetails = {\n slug: FullSlug;\n filePath: FilePath;\n title: string;\n links: string[];\n tags: string[];\n content: string;\n richContent?: string;\n date?: Date;\n description?: string;\n};\n\nexport type ContentIndex = Record;\n\n// ============================================================================\n// Sort & Theme Types\n// ============================================================================\n\nexport type SortFn = (f1: QuartzPluginData, f2: QuartzPluginData) => number;\n\nexport type ThemeKey = \"lightMode\" | \"darkMode\";\n\nexport type ValidDateType = \"created\" | \"modified\" | \"published\";\n\n// ============================================================================\n// VFile DataMap Augmentation — canonical vfile shape for Quartz\n// ============================================================================\n\ndeclare module \"vfile\" {\n interface DataMap {\n slug: FullSlug;\n filePath: FilePath;\n relativePath: FilePath;\n description: string;\n text: string;\n links: SimpleSlug[];\n toc: { depth: number; text: string; slug: string }[];\n collapseToc: boolean;\n blocks: Record;\n htmlAst: import(\"hast\").Root;\n hasMermaidDiagram: boolean | undefined;\n frontmatter: { [key: string]: unknown } & {\n title: string;\n } & Partial<{\n tags: string[];\n aliases: string[];\n modified: string;\n created: string;\n published: string;\n description: string;\n socialDescription: string;\n publish: boolean | string;\n draft: boolean | string;\n lang: string;\n enableToc: string;\n cssclasses: string[];\n socialImage: string;\n comments: boolean | string;\n }>;\n dates: {\n created: Date;\n modified: Date;\n published: Date;\n };\n defaultDateType?: ValidDateType;\n }\n}\n","import { assign } from './util';\nimport { diff, commitRoot } from './diff/index';\nimport options from './options';\nimport { Fragment } from './create-element';\nimport { MODE_HYDRATE, NULL } from './constants';\n\n/**\n * Base Component class. Provides `setState()` and `forceUpdate()`, which\n * trigger rendering\n * @param {object} props The initial component props\n * @param {object} context The initial context from parent components'\n * getChildContext\n */\nexport function BaseComponent(props, context) {\n\tthis.props = props;\n\tthis.context = context;\n}\n\n/**\n * Update component state and schedule a re-render.\n * @this {import('./internal').Component}\n * @param {object | ((s: object, p: object) => object)} update A hash of state\n * properties to update with new values or a function that given the current\n * state and props returns a new partial state\n * @param {() => void} [callback] A function to be called once component state is\n * updated\n */\nBaseComponent.prototype.setState = function (update, callback) {\n\t// only clone state when copying to nextState the first time.\n\tlet s;\n\tif (this._nextState != NULL && this._nextState != this.state) {\n\t\ts = this._nextState;\n\t} else {\n\t\ts = this._nextState = assign({}, this.state);\n\t}\n\n\tif (typeof update == 'function') {\n\t\t// Some libraries like `immer` mark the current state as readonly,\n\t\t// preventing us from mutating it, so we need to clone it. See #2716\n\t\tupdate = update(assign({}, s), this.props);\n\t}\n\n\tif (update) {\n\t\tassign(s, update);\n\t}\n\n\t// Skip update if updater function returned null\n\tif (update == NULL) return;\n\n\tif (this._vnode) {\n\t\tif (callback) {\n\t\t\tthis._stateCallbacks.push(callback);\n\t\t}\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Immediately perform a synchronous re-render of the component\n * @this {import('./internal').Component}\n * @param {() => void} [callback] A function to be called after component is\n * re-rendered\n */\nBaseComponent.prototype.forceUpdate = function (callback) {\n\tif (this._vnode) {\n\t\t// Set render mode so that we can differentiate where the render request\n\t\t// is coming from. We need this because forceUpdate should never call\n\t\t// shouldComponentUpdate\n\t\tthis._force = true;\n\t\tif (callback) this._renderCallbacks.push(callback);\n\t\tenqueueRender(this);\n\t}\n};\n\n/**\n * Accepts `props` and `state`, and returns a new Virtual DOM tree to build.\n * Virtual DOM is generally constructed via [JSX](https://jasonformat.com/wtf-is-jsx).\n * @param {object} props Props (eg: JSX attributes) received from parent\n * element/component\n * @param {object} state The component's current state\n * @param {object} context Context object, as returned by the nearest\n * ancestor's `getChildContext()`\n * @returns {ComponentChildren | void}\n */\nBaseComponent.prototype.render = Fragment;\n\n/**\n * @param {import('./internal').VNode} vnode\n * @param {number | null} [childIndex]\n */\nexport function getDomSibling(vnode, childIndex) {\n\tif (childIndex == NULL) {\n\t\t// Use childIndex==null as a signal to resume the search from the vnode's sibling\n\t\treturn vnode._parent\n\t\t\t? getDomSibling(vnode._parent, vnode._index + 1)\n\t\t\t: NULL;\n\t}\n\n\tlet sibling;\n\tfor (; childIndex < vnode._children.length; childIndex++) {\n\t\tsibling = vnode._children[childIndex];\n\n\t\tif (sibling != NULL && sibling._dom != NULL) {\n\t\t\t// Since updateParentDomPointers keeps _dom pointer correct,\n\t\t\t// we can rely on _dom to tell us if this subtree contains a\n\t\t\t// rendered DOM node, and what the first rendered DOM node is\n\t\t\treturn sibling._dom;\n\t\t}\n\t}\n\n\t// If we get here, we have not found a DOM node in this vnode's children.\n\t// We must resume from this vnode's sibling (in it's parent _children array)\n\t// Only climb up and search the parent if we aren't searching through a DOM\n\t// VNode (meaning we reached the DOM parent of the original vnode that began\n\t// the search)\n\treturn typeof vnode.type == 'function' ? getDomSibling(vnode) : NULL;\n}\n\n/**\n * Trigger in-place re-rendering of a component.\n * @param {import('./internal').Component} component The component to rerender\n */\nfunction renderComponent(component) {\n\tif (component._parentDom && component._dirty) {\n\t\tlet oldVNode = component._vnode,\n\t\t\toldDom = oldVNode._dom,\n\t\t\tcommitQueue = [],\n\t\t\trefQueue = [],\n\t\t\tnewVNode = assign({}, oldVNode);\n\t\tnewVNode._original = oldVNode._original + 1;\n\t\tif (options.vnode) options.vnode(newVNode);\n\n\t\tdiff(\n\t\t\tcomponent._parentDom,\n\t\t\tnewVNode,\n\t\t\toldVNode,\n\t\t\tcomponent._globalContext,\n\t\t\tcomponent._parentDom.namespaceURI,\n\t\t\toldVNode._flags & MODE_HYDRATE ? [oldDom] : NULL,\n\t\t\tcommitQueue,\n\t\t\toldDom == NULL ? getDomSibling(oldVNode) : oldDom,\n\t\t\t!!(oldVNode._flags & MODE_HYDRATE),\n\t\t\trefQueue\n\t\t);\n\n\t\tnewVNode._original = oldVNode._original;\n\t\tnewVNode._parent._children[newVNode._index] = newVNode;\n\t\tcommitRoot(commitQueue, newVNode, refQueue);\n\t\toldVNode._dom = oldVNode._parent = null;\n\n\t\tif (newVNode._dom != oldDom) {\n\t\t\tupdateParentDomPointers(newVNode);\n\t\t}\n\t}\n}\n\n/**\n * @param {import('./internal').VNode} vnode\n */\nfunction updateParentDomPointers(vnode) {\n\tif ((vnode = vnode._parent) != NULL && vnode._component != NULL) {\n\t\tvnode._dom = vnode._component.base = NULL;\n\t\tvnode._children.some(child => {\n\t\t\tif (child != NULL && child._dom != NULL) {\n\t\t\t\treturn (vnode._dom = vnode._component.base = child._dom);\n\t\t\t}\n\t\t});\n\n\t\treturn updateParentDomPointers(vnode);\n\t}\n}\n\n/**\n * The render queue\n * @type {Array}\n */\nlet rerenderQueue = [];\n\n/*\n * The value of `Component.debounce` must asynchronously invoke the passed in callback. It is\n * important that contributors to Preact can consistently reason about what calls to `setState`, etc.\n * do, and when their effects will be applied. See the links below for some further reading on designing\n * asynchronous APIs.\n * * [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony)\n * * [Callbacks synchronous and asynchronous](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)\n */\n\nlet prevDebounce;\n\nconst defer =\n\ttypeof Promise == 'function'\n\t\t? Promise.prototype.then.bind(Promise.resolve())\n\t\t: setTimeout;\n\n/**\n * Enqueue a rerender of a component\n * @param {import('./internal').Component} c The component to rerender\n */\nexport function enqueueRender(c) {\n\tif (\n\t\t(!c._dirty &&\n\t\t\t(c._dirty = true) &&\n\t\t\trerenderQueue.push(c) &&\n\t\t\t!process._rerenderCount++) ||\n\t\tprevDebounce != options.debounceRendering\n\t) {\n\t\tprevDebounce = options.debounceRendering;\n\t\t(prevDebounce || defer)(process);\n\t}\n}\n\n/**\n * @param {import('./internal').Component} a\n * @param {import('./internal').Component} b\n */\nconst depthSort = (a, b) => a._vnode._depth - b._vnode._depth;\n\n/** Flush the render queue by rerendering all queued components */\nfunction process() {\n\ttry {\n\t\tlet c,\n\t\t\tl = 1;\n\n\t\t// Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary\n\t\t// process() calls from getting scheduled while `queue` is still being consumed.\n\t\twhile (rerenderQueue.length) {\n\t\t\t// Keep the rerender queue sorted by (depth, insertion order). The queue\n\t\t\t// will initially be sorted on the first iteration only if it has more than 1 item.\n\t\t\t//\n\t\t\t// New items can be added to the queue e.g. when rerendering a provider, so we want to\n\t\t\t// keep the order from top to bottom with those new items so we can handle them in a\n\t\t\t// single pass\n\t\t\tif (rerenderQueue.length > l) {\n\t\t\t\trerenderQueue.sort(depthSort);\n\t\t\t}\n\n\t\t\tc = rerenderQueue.shift();\n\t\t\tl = rerenderQueue.length;\n\n\t\t\trenderComponent(c);\n\t\t}\n\t} finally {\n\t\trerenderQueue.length = process._rerenderCount = 0;\n\t}\n}\n\nprocess._rerenderCount = 0;\n","import { IS_NON_DIMENSIONAL, NULL, SVG_NAMESPACE } from '../constants';\nimport options from '../options';\n\n// Per-instance unique key for event clock stamps. Each Preact copy on the page\n// gets its own random suffix so that `_dispatched` / `_attached` properties on\n// shared event objects and handler functions cannot collide across instances.\n// ~1 in 60M collision odds - if you have that many praect versions on the page,\n// you deserve some weird bugs.\n// In 11 we can replace this with a\n// Symbol\nlet _id = Math.random().toString(8),\n\tEVENT_DISPATCHED = '__d' + _id,\n\tEVENT_ATTACHED = '__a' + _id;\n\nfunction setStyle(style, key, value) {\n\tif (key[0] == '-') {\n\t\tstyle.setProperty(key, value == NULL ? '' : value);\n\t} else if (value == NULL) {\n\t\tstyle[key] = '';\n\t} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {\n\t\tstyle[key] = value;\n\t} else {\n\t\tstyle[key] = value + 'px';\n\t}\n}\n\nconst CAPTURE_REGEX = /(PointerCapture)$|Capture$/i;\n\n// A logical clock to solve issues like https://github.com/preactjs/preact/issues/3927.\n// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that\n// an event can trigger on a newly reated DOM-node while the event bubbles up.\n//\n// Originally inspired by Vue\n// (https://github.com/vuejs/core/blob/caeb8a68811a1b0f79/packages/runtime-dom/src/modules/events.ts#L90-L101),\n// but modified to use a logical clock instead of Date.now() in case event handlers get attached\n// and events get dispatched during the same millisecond.\n//\n// The clock is incremented after each new event dispatch. This allows 1 000 000 new events\n// per second for over 280 years before the value reaches Number.MAX_SAFE_INTEGER (2**53 - 1).\nlet eventClock = 0;\n\n/**\n * Set a property value on a DOM node\n * @param {import('../internal').PreactElement} dom The DOM node to modify\n * @param {string} name The name of the property to set\n * @param {*} value The value to set the property to\n * @param {*} oldValue The old value the property had\n * @param {string} namespace Whether or not this DOM node is an SVG node or not\n */\nexport function setProperty(dom, name, value, oldValue, namespace) {\n\tlet useCapture;\n\n\to: if (name == 'style') {\n\t\tif (typeof value == 'string') {\n\t\t\tdom.style.cssText = value;\n\t\t} else {\n\t\t\tif (typeof oldValue == 'string') {\n\t\t\t\tdom.style.cssText = oldValue = '';\n\t\t\t}\n\n\t\t\tif (oldValue) {\n\t\t\t\tfor (name in oldValue) {\n\t\t\t\t\tif (!(value && name in value)) {\n\t\t\t\t\t\tsetStyle(dom.style, name, '');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (value) {\n\t\t\t\tfor (name in value) {\n\t\t\t\t\tif (!oldValue || value[name] != oldValue[name]) {\n\t\t\t\t\t\tsetStyle(dom.style, name, value[name]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6\n\telse if (name[0] == 'o' && name[1] == 'n') {\n\t\tuseCapture = name != (name = name.replace(CAPTURE_REGEX, '$1'));\n\t\tconst lowerCaseName = name.toLowerCase();\n\n\t\t// Infer correct casing for DOM built-in events:\n\t\tif (lowerCaseName in dom || name == 'onFocusOut' || name == 'onFocusIn')\n\t\t\tname = lowerCaseName.slice(2);\n\t\telse name = name.slice(2);\n\n\t\tif (!dom._listeners) dom._listeners = {};\n\t\tdom._listeners[name + useCapture] = value;\n\n\t\tif (value) {\n\t\t\tif (!oldValue) {\n\t\t\t\tvalue[EVENT_ATTACHED] = eventClock;\n\t\t\t\tdom.addEventListener(\n\t\t\t\t\tname,\n\t\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\t\tuseCapture\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tvalue[EVENT_ATTACHED] = oldValue[EVENT_ATTACHED];\n\t\t\t}\n\t\t} else {\n\t\t\tdom.removeEventListener(\n\t\t\t\tname,\n\t\t\t\tuseCapture ? eventProxyCapture : eventProxy,\n\t\t\t\tuseCapture\n\t\t\t);\n\t\t}\n\t} else {\n\t\tif (namespace == SVG_NAMESPACE) {\n\t\t\t// Normalize incorrect prop usage for SVG:\n\t\t\t// - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)\n\t\t\t// - className --> class\n\t\t\tname = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');\n\t\t} else if (\n\t\t\tname != 'width' &&\n\t\t\tname != 'height' &&\n\t\t\tname != 'href' &&\n\t\t\tname != 'list' &&\n\t\t\tname != 'form' &&\n\t\t\t// Default value in browsers is `-1` and an empty string is\n\t\t\t// cast to `0` instead\n\t\t\tname != 'tabIndex' &&\n\t\t\tname != 'download' &&\n\t\t\tname != 'rowSpan' &&\n\t\t\tname != 'colSpan' &&\n\t\t\tname != 'role' &&\n\t\t\tname != 'popover' &&\n\t\t\tname in dom\n\t\t) {\n\t\t\ttry {\n\t\t\t\tdom[name] = value == NULL ? '' : value;\n\t\t\t\t// labelled break is 1b smaller here than a return statement (sorry)\n\t\t\t\tbreak o;\n\t\t\t} catch (e) {}\n\t\t}\n\n\t\t// aria- and data- attributes have no boolean representation.\n\t\t// A `false` value is different from the attribute not being\n\t\t// present, so we can't remove it. For non-boolean aria\n\t\t// attributes we could treat false as a removal, but the\n\t\t// amount of exceptions would cost too many bytes. On top of\n\t\t// that other frameworks generally stringify `false`.\n\n\t\tif (typeof value == 'function') {\n\t\t\t// never serialize functions as attribute values\n\t\t} else if (value != NULL && (value !== false || name[4] == '-')) {\n\t\t\tdom.setAttribute(name, name == 'popover' && value == true ? '' : value);\n\t\t} else {\n\t\t\tdom.removeAttribute(name);\n\t\t}\n\t}\n}\n\n/**\n * Create an event proxy function.\n * @param {boolean} useCapture Is the event handler for the capture phase.\n * @private\n */\nfunction createEventProxy(useCapture) {\n\t/**\n\t * Proxy an event to hooked event handlers\n\t * @param {import('../internal').PreactEvent} e The event object from the browser\n\t * @private\n\t */\n\treturn function (e) {\n\t\tif (this._listeners) {\n\t\t\tconst eventHandler = this._listeners[e.type + useCapture];\n\t\t\tif (e[EVENT_DISPATCHED] == NULL) {\n\t\t\t\te[EVENT_DISPATCHED] = eventClock++;\n\n\t\t\t\t// When `e[EVENT_DISPATCHED]` is smaller than the time when the targeted event\n\t\t\t\t// handler was attached we know we have bubbled up to an element that was added\n\t\t\t\t// during patching the DOM.\n\t\t\t} else if (e[EVENT_DISPATCHED] < eventHandler[EVENT_ATTACHED]) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn eventHandler(options.event ? options.event(e) : e);\n\t\t}\n\t};\n}\n\nconst eventProxy = createEventProxy(false);\nconst eventProxyCapture = createEventProxy(true);\n","import { enqueueRender } from './component';\nimport { NULL } from './constants';\n\nexport let i = 0;\n\nexport function createContext(defaultValue) {\n\tfunction Context(props) {\n\t\tif (!this.getChildContext) {\n\t\t\t/** @type {Set | null} */\n\t\t\tlet subs = new Set();\n\t\t\tlet ctx = {};\n\t\t\tctx[Context._id] = this;\n\n\t\t\tthis.getChildContext = () => ctx;\n\n\t\t\tthis.componentWillUnmount = () => {\n\t\t\t\tsubs = NULL;\n\t\t\t};\n\n\t\t\tthis.shouldComponentUpdate = function (_props) {\n\t\t\t\t// @ts-expect-error even\n\t\t\t\tif (this.props.value != _props.value) {\n\t\t\t\t\tsubs.forEach(c => {\n\t\t\t\t\t\tc._force = true;\n\t\t\t\t\t\tenqueueRender(c);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.sub = c => {\n\t\t\t\tsubs.add(c);\n\t\t\t\tlet old = c.componentWillUnmount;\n\t\t\t\tc.componentWillUnmount = () => {\n\t\t\t\t\tif (subs) {\n\t\t\t\t\t\tsubs.delete(c);\n\t\t\t\t\t}\n\t\t\t\t\tif (old) old.call(c);\n\t\t\t\t};\n\t\t\t};\n\t\t}\n\n\t\treturn props.children;\n\t}\n\n\tContext._id = '__cC' + i++;\n\tContext._defaultValue = defaultValue;\n\n\t/** @type {import('./internal').FunctionComponent} */\n\tContext.Consumer = (props, contextValue) => {\n\t\treturn props.children(contextValue);\n\t};\n\n\t// we could also get rid of _contextRef entirely\n\tContext.Provider =\n\t\tContext._contextRef =\n\t\tContext.Consumer.contextType =\n\t\t\tContext;\n\n\treturn Context;\n}\n","import { slug as slugAnchor } from \"github-slugger\";\nimport type { Element as HastElement } from \"hast\";\nimport type { FilePath, FullSlug } from \"@quartz-community/types\";\n\nexport type { FilePath, FullSlug };\n\n/** No '/index' ending, no file extension, can have trailing slash for folders. */\nexport type SimpleSlug = string & { _brand: \"SimpleSlug\" };\n\n/** Starts with './' or '../', used for navigation. */\nexport type RelativeURL = string & { _brand: \"RelativeURL\" };\n\nexport interface TransformOptions {\n strategy: \"absolute\" | \"relative\" | \"shortest\";\n allSlugs: FullSlug[];\n}\n\nexport function isFilePath(s: string): s is FilePath {\n const validStart = !s.startsWith(\".\");\n return validStart && _hasFileExtension(s);\n}\n\nexport function isFullSlug(s: string): s is FullSlug {\n const validStart = !(s.startsWith(\".\") || s.startsWith(\"/\"));\n const validEnding = !s.endsWith(\"/\");\n return validStart && validEnding && !_containsForbiddenCharacters(s);\n}\n\nexport function isSimpleSlug(s: string): s is SimpleSlug {\n const validStart = !(s.startsWith(\".\") || (s.length > 1 && s.startsWith(\"/\")));\n const validEnding = !endsWith(s, \"index\");\n return validStart && !_containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s);\n}\n\nexport function isRelativeURL(s: string): s is RelativeURL {\n const validStart = /^\\.{1,2}/.test(s);\n const validEnding = !endsWith(s, \"index\");\n return validStart && validEnding && ![\".md\", \".html\"].includes(getFileExtension(s) ?? \"\");\n}\n\nexport function isAbsoluteURL(s: string): boolean {\n try {\n new URL(s);\n } catch {\n return false;\n }\n return true;\n}\n\nexport function simplifySlug(fp: FullSlug | string): SimpleSlug {\n const res = stripSlashes(trimSuffix(fp, \"index\"), true);\n return (res.length === 0 ? \"/\" : res) as SimpleSlug;\n}\n\nexport function getFullSlug(window: Window): FullSlug {\n const res = window.document.body.dataset.slug! as FullSlug;\n return res;\n}\n\nexport function getFullSlugFromUrl(): FullSlug {\n let rawSlug = window.location.pathname;\n if (rawSlug.endsWith(\"/\")) rawSlug = rawSlug.slice(0, -1);\n if (rawSlug.startsWith(\"/\")) rawSlug = rawSlug.slice(1);\n return rawSlug as FullSlug;\n}\n\nexport function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {\n fp = stripSlashes(fp) as FilePath;\n const ext = getFileExtension(fp);\n const withoutFileExt = fp.replace(new RegExp(ext + \"$\"), \"\");\n const finalExt = excludeExt || [\".md\", \".html\", undefined].includes(ext) ? \"\" : ext;\n\n let slug = _sluggify(withoutFileExt);\n\n if (endsWith(slug, \"_index\")) {\n slug = slug.replace(/_index$/, \"index\");\n }\n\n // Obsidian \"Folder Notes\" convention (insideFolder storage): a file named the\n // same as its parent folder is treated as the folder's landing page. Rewrite\n // `folder/folder` → `folder/index` so every downstream consumer (trie, link\n // resolver, simplifySlug, isFolderPath) sees the canonical Quartz slug shape\n // and Just Works. A top-level single-segment slug is NOT rewritten — that\n // represents Obsidian's `parentFolder` storage mode, which Quartz already\n // routes as a regular top-level page.\n const segments = slug.split(\"/\");\n if (segments.length >= 2 && segments[segments.length - 1] === segments[segments.length - 2]) {\n segments[segments.length - 1] = \"index\";\n slug = segments.join(\"/\");\n }\n\n return (slug + (finalExt ?? \"\")) as FullSlug;\n}\n\nexport function joinSegments(...args: string[]): string {\n if (args.length === 0) {\n return \"\";\n }\n\n let joined = args\n .filter((segment) => segment !== \"\" && segment !== \"/\")\n .map((segment) => stripSlashes(segment))\n .join(\"/\");\n\n const first = args[0];\n const last = args[args.length - 1];\n\n if (first?.startsWith(\"/\")) {\n joined = \"/\" + joined;\n }\n\n if (last?.endsWith(\"/\")) {\n joined = joined + \"/\";\n }\n\n return joined;\n}\n\nexport function resolvePath(to: string): string {\n if (to.startsWith(\"/\")) return to;\n return \"/\" + to;\n}\n\n/** Read the base path injected at build time via `data-basepath` on ``.\n * Returns `\"\"` for root deployments, e.g. `\"/repository\"` for subdirectory. */\nexport function getBasePath(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.body?.dataset?.basepath ?? \"\";\n}\n\n/** Resolve a slug to an absolute URL path, prepending the site's base path.\n * e.g. `resolveBasePath(\"features/Callouts\")` → `\"/repository/features/Callouts\"` */\nexport function resolveBasePath(to: string, basePath?: string): string {\n const base = basePath ?? getBasePath();\n const slug = to.startsWith(\"/\") ? to : \"/\" + to;\n return base + slug;\n}\n\nexport function endsWith(s: string, suffix: string): boolean {\n return s === suffix || s.endsWith(\"/\" + suffix);\n}\n\nexport function trimSuffix(s: string, suffix: string): string {\n if (endsWith(s, suffix)) {\n s = s.slice(0, -suffix.length);\n }\n return s;\n}\n\nexport function stripSlashes(s: string, onlyStripPrefix?: boolean): string {\n if (s.startsWith(\"/\")) {\n s = s.substring(1);\n }\n\n if (!onlyStripPrefix && s.endsWith(\"/\")) {\n s = s.slice(0, -1);\n }\n\n return s;\n}\n\nexport function getFileExtension(s: string): string | undefined {\n return s.match(/\\.[A-Za-z0-9]+$/)?.[0];\n}\n\nexport function isFolderPath(fplike: string): boolean {\n return (\n fplike.endsWith(\"/\") ||\n endsWith(fplike, \"index\") ||\n endsWith(fplike, \"index.md\") ||\n endsWith(fplike, \"index.html\")\n );\n}\n\nexport function getAllSegmentPrefixes(path: string): string[] {\n const segments = path.split(\"/\");\n const results: string[] = [];\n for (let i = 0; i < segments.length; i++) {\n results.push(segments.slice(0, i + 1).join(\"/\"));\n }\n return results;\n}\n\nexport function pathToRoot(slug: FullSlug): RelativeURL {\n let rootPath = slug\n .split(\"/\")\n .filter((x) => x !== \"\")\n .slice(0, -1)\n .map((_) => \"..\")\n .join(\"/\");\n\n if (rootPath.length === 0) {\n rootPath = \".\";\n }\n\n return rootPath as RelativeURL;\n}\n\nexport function resolveRelative(current: FullSlug, target: FullSlug | SimpleSlug): RelativeURL {\n const res = joinSegments(pathToRoot(current), simplifySlug(target as FullSlug)) as RelativeURL;\n return res;\n}\n\nexport function splitAnchor(link: string): [string, string] {\n const [fp, anchor] = link.split(\"#\", 2);\n if (fp!.endsWith(\".pdf\")) {\n return [fp!, anchor === undefined ? \"\" : `#${anchor}`];\n }\n if (anchor === undefined) {\n return [fp!, \"\"];\n }\n const bare = anchor.startsWith(\"^\") ? anchor.slice(1) : anchor;\n const slugged = \"#\" + slugAnchor(bare);\n return [fp!, slugged];\n}\n\nexport function slugTag(tag: string): string {\n return tag\n .split(\"/\")\n .map((tagSegment) => _sluggify(tagSegment))\n .join(\"/\");\n}\n\nexport function transformInternalLink(link: string): RelativeURL {\n const [fplike, anchor] = splitAnchor(decodeURI(link));\n\n const segments = fplike.split(\"/\").filter((x) => x.length > 0);\n const prefix = segments.filter(_isRelativeSegment).join(\"/\");\n const fp = segments.filter((seg) => !_isRelativeSegment(seg) && seg !== \"\").join(\"/\");\n\n const slugged = slugifyFilePath(fp as FilePath);\n const simpleSlug = simplifySlug(slugged);\n const folderPath = isFolderPath(fplike) || isFolderPath(slugged);\n const joined = joinSegments(stripSlashes(prefix), stripSlashes(simpleSlug));\n const trail = folderPath ? \"/\" : \"\";\n const res = (_addRelativeToStart(joined) + trail + anchor) as RelativeURL;\n return res;\n}\n\nexport function transformLink(src: FullSlug, target: string, opts: TransformOptions): RelativeURL {\n const targetSlug = transformInternalLink(target);\n\n if (opts.strategy === \"relative\") {\n return targetSlug as RelativeURL;\n } else {\n // When src is a folder page (e.g. \"Compendium/Spells\" backed by index.md),\n // the browser serves it at \"/Compendium/Spells/\" with a trailing slash.\n // pathToRoot needs the \"/index\" suffix to compute the correct depth.\n const effectiveSrc =\n !endsWith(src, \"index\") && opts.allSlugs.includes(`${src}/index` as FullSlug)\n ? (`${src}/index` as FullSlug)\n : src;\n\n const folderTail = isFolderPath(targetSlug) ? \"/\" : \"\";\n const canonicalSlug = stripSlashes(targetSlug.slice(\".\".length));\n const [targetCanonical, targetAnchor] = splitAnchor(canonicalSlug);\n\n if (opts.strategy === \"shortest\") {\n const isMultiSegment = targetCanonical.includes(\"/\");\n const isFolderTarget = isFolderPath(targetSlug);\n const matchingFileNames = opts.allSlugs.filter((slug) => {\n if (isMultiSegment) {\n // Multi-segment partial path: match by suffix\n if (slug === targetCanonical || slug.endsWith(\"/\" + targetCanonical)) {\n return true;\n }\n // Folder targets have \"/index\" stripped — also match the \"/index\" variant\n if (isFolderTarget) {\n const withIndex = targetCanonical + \"/index\";\n return slug === withIndex || slug.endsWith(\"/\" + withIndex);\n }\n return false;\n }\n const parts = slug.split(\"/\");\n const fileName = parts.at(-1);\n return targetCanonical === fileName;\n });\n\n if (matchingFileNames.length === 1) {\n const matchedSlug = matchingFileNames[0]!;\n return (resolveRelative(effectiveSrc, matchedSlug) + targetAnchor) as RelativeURL;\n }\n }\n\n return (joinSegments(pathToRoot(effectiveSrc), canonicalSlug) + folderTail) as RelativeURL;\n }\n}\n\n/**\n * Slugify a file path for use as an href.\n * Replaces whitespace→hyphens, &→-and-, %→-percent, removes ? and #, and\n * lowercases the result so that link matching is case-insensitive (matching\n * Obsidian's link-resolution semantics). Operates per segment so directory\n * separators are preserved.\n */\nexport function slugifyPath(s: string): string {\n return s\n .split(\"/\")\n .map((segment) =>\n segment\n .replace(/\\s/g, \"-\")\n .replace(/&/g, \"-and-\")\n .replace(/%/g, \"-percent\")\n .replace(/\\?/g, \"\")\n .replace(/#/g, \"\")\n .replace(/[<>:\"|*]/g, \"\")\n .toLowerCase(),\n )\n .join(\"/\")\n .replace(/\\/$/, \"\");\n}\n\n/**\n * Re-base the relative `href` and `src` attributes of a HAST element so that\n * links inside content originally resolved relative to `newBase` remain valid\n * when the element is embedded at a page with slug `curBase`.\n *\n * Used for transclusions and cross-slug HAST embedding (Quartz core's\n * `renderPage` transclude expansion, canvas-page's embedded file-nodes, and\n * any plugin that takes `vfile.data.htmlAst` from one page and renders it\n * inside another). Absolute URLs pass through unchanged.\n *\n * The element (and its children) are deep-cloned via `structuredClone`, so\n * the original HAST tree is never mutated.\n */\nexport function normalizeHastElement(\n rawEl: T,\n curBase: FullSlug,\n newBase: FullSlug,\n): T {\n const el = structuredClone(rawEl);\n _rebaseHastElement(el, \"src\", curBase, newBase);\n _rebaseHastElement(el, \"href\", curBase, newBase);\n if (el.children) {\n el.children = el.children.map((child) =>\n child.type === \"element\"\n ? (normalizeHastElement(child as HastElement, curBase, newBase) as typeof child)\n : child,\n );\n }\n return el;\n}\n\n/** @internal */\nfunction _rebaseHastElement(\n el: HastElement,\n attr: \"href\" | \"src\",\n curBase: FullSlug,\n newBase: FullSlug,\n): void {\n const value = el.properties?.[attr];\n if (value === undefined || value === null) return;\n const href = String(value);\n if (!isRelativeURL(href)) return;\n el.properties![attr] = joinSegments(resolveRelative(curBase, newBase), \"..\", href);\n}\n\n/** @internal */\nfunction _sluggify(s: string): string {\n return slugifyPath(s);\n}\n\nfunction _containsForbiddenCharacters(s: string): boolean {\n return s.includes(\" \") || s.includes(\"#\") || s.includes(\"?\") || s.includes(\"&\");\n}\n\nfunction _hasFileExtension(s: string): boolean {\n return getFileExtension(s) !== undefined;\n}\n\nfunction _isRelativeSegment(s: string): boolean {\n return /^\\.{0,2}$/.test(s);\n}\n\nfunction _addRelativeToStart(s: string): string {\n if (s === \"\") {\n s = \".\";\n }\n\n if (!s.startsWith(\".\")) {\n s = joinSegments(\".\", s);\n }\n\n return s;\n}\n","import path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport type { QuartzEmitterPlugin, BuildCtx, FilePath, FullSlug } from \"@quartz-community/types\";\nimport { joinSegments } from \"@quartz-community/types\";\nimport { simplifySlug, resolveRelative, isRelativeURL } from \"@quartz-community/utils\";\nimport type { VFile } from \"vfile\";\n\nconst write = async (\n ctx: BuildCtx,\n slug: FullSlug,\n ext: string,\n content: string,\n): Promise => {\n const pathToPage = joinSegments(ctx.argv.output, slug + ext) as FilePath;\n const dir = path.dirname(pathToPage);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(pathToPage, content);\n return pathToPage;\n};\n\nasync function* processFile(ctx: BuildCtx, file: VFile) {\n const ogSlug = simplifySlug(file.data.slug! as FullSlug);\n\n for (const aliasTarget of ((file.data as Record).aliases as string[]) ?? []) {\n const aliasTargetSlug = (\n isRelativeURL(aliasTarget)\n ? path.normalize(path.join(ogSlug, \"..\", aliasTarget))\n : aliasTarget\n ) as FullSlug;\n\n const redirUrl = resolveRelative(aliasTargetSlug, ogSlug);\n yield write(\n ctx,\n aliasTargetSlug,\n \".html\",\n `\n \n \n \n ${ogSlug}\n \n \n \n \n \n \n `,\n );\n }\n}\n\nexport const AliasRedirects: QuartzEmitterPlugin = () => ({\n name: \"AliasRedirects\",\n async *emit(ctx, content) {\n for (const [_tree, file] of content) {\n yield* processFile(ctx, file);\n }\n },\n async *partialEmit(ctx, _content, _resources, changeEvents) {\n for (const changeEvent of changeEvents) {\n if (!changeEvent.file) continue;\n if (changeEvent.type === \"add\" || changeEvent.type === \"change\") {\n yield* processFile(ctx, changeEvent.file);\n }\n }\n },\n});\n"]} \ No newline at end of file