diff --git a/CHANGELOG.md b/CHANGELOG.md index 9111bd8..3e70038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,3 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial Quartz community plugin template. +- `disableBrokenWikilinks` option: when enabled, internal links whose resolved slug is not present in `ctx.allSlugs` gain a `broken` CSS class alongside `internal` so broken links can be styled distinctly. The check lives here (rather than in `ObsidianFlavoredMarkdown`) because this plugin owns link resolution and already computes the canonical slug. + +### Changed + +- **BREAKING (inherited from `@quartz-community/utils`)**: internal link resolution is now case-insensitive. Links resolve to lowercased slugs regardless of how they're typed (`[[MyNote]]`, `[[mynote]]`, `[[MYNOTE]]` all produce href `my-note`). Matches Obsidian's link-matching behavior. diff --git a/README.md b/README.md index 8f58e9d..95fb5c7 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,14 @@ ExternalPlugin.CrawlLinks({ ## Configuration -| Option | Type | Default | Description | -| ------------------------ | ---------------------------------------- | ------------ | ------------------------------------- | -| `markdownLinkResolution` | `"absolute" \| "relative" \| "shortest"` | `"absolute"` | How to resolve internal links. | -| `prettyLinks` | `boolean` | `true` | Whether to use pretty links. | -| `openLinksInNewTab` | `boolean` | `false` | Whether to open links in a new tab. | -| `lazyLoad` | `boolean` | `false` | Whether to lazy load links. | -| `externalLinkIcon` | `boolean` | `true` | Whether to add an external link icon. | +| Option | Type | Default | Description | +| ------------------------ | ---------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| `markdownLinkResolution` | `"absolute" \| "relative" \| "shortest"` | `"absolute"` | How to resolve internal links. | +| `prettyLinks` | `boolean` | `true` | Whether to use pretty links. | +| `openLinksInNewTab` | `boolean` | `false` | Whether to open links in a new tab. | +| `lazyLoad` | `boolean` | `false` | Whether to lazy load links. | +| `externalLinkIcon` | `boolean` | `true` | Whether to add an external link icon. | +| `disableBrokenWikilinks` | `boolean` | `false` | When `true`, internal links whose resolved slug is not in `ctx.allSlugs` gain a `broken` CSS class (alongside `internal`) for styling. | ## Documentation diff --git a/dist/index.d.ts b/dist/index.d.ts index ae1b9bf..ea56cdd 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -10,6 +10,15 @@ interface CrawlLinksOptions { openLinksInNewTab: boolean; lazyLoad: boolean; externalLinkIcon: boolean; + /** + * When `true`, internal links whose resolved slug is not present in + * `ctx.allSlugs` gain a `broken` CSS class (alongside `internal`) so + * broken links can be styled distinctly. Applies to both wikilinks + * (after they've been converted to `` elements by + * ObsidianFlavoredMarkdown) and markdown links, since both are + * indistinguishable `` nodes at this phase of the pipeline. + */ + disableBrokenWikilinks: boolean; } declare const CrawlLinks: QuartzTransformerPlugin>; diff --git a/dist/index.js b/dist/index.js index 15f6f57..e004801 100644 --- a/dist/index.js +++ b/dist/index.js @@ -221,7 +221,8 @@ var defaultOptions = { prettyLinks: true, openLinksInNewTab: false, lazyLoad: false, - externalLinkIcon: true + externalLinkIcon: true, + disableBrokenWikilinks: false }; var isAbsoluteUrlWithOptions = isAbsoluteUrl; var CrawlLinks = (userOpts) => { @@ -289,6 +290,10 @@ var CrawlLinks = (userOpts) => { const simple = simplifySlug(full); outgoing.add(simple); node.properties["data-slug"] = full; + if (opts.disableBrokenWikilinks && !ctx.allSlugs.includes(full)) { + classes.push("broken"); + node.properties.className = classes; + } } if (opts.prettyLinks && isInternal && node.children.length === 1) { const textChild = node.children[0]; diff --git a/dist/index.js.map b/dist/index.js.map index 504b892..4c40aa5 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../node_modules/unist-util-is/lib/index.js","../node_modules/unist-util-visit-parents/lib/color.node.js","../node_modules/unist-util-visit-parents/lib/index.js","../node_modules/unist-util-visit/lib/index.js","../node_modules/is-absolute-url/index.js","../src/transformer.ts"],"names":["index","is","visit"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @import {Node, Parent} from 'unist'\n */\n\n/**\n * @template Fn\n * @template Fallback\n * @typedef {Fn extends (value: any) => value is infer Thing ? Thing : Fallback} Predicate\n */\n\n/**\n * @callback Check\n * Check that an arbitrary value is a node.\n * @param {unknown} this\n * The given context.\n * @param {unknown} [node]\n * Anything (typically a node).\n * @param {number | null | undefined} [index]\n * The node’s position in its parent.\n * @param {Parent | null | undefined} [parent]\n * The node’s parent.\n * @returns {boolean}\n * Whether this is a node and passes a test.\n *\n * @typedef {Record | Node} Props\n * Object to check for equivalence.\n *\n * Note: `Node` is included as it is common but is not indexable.\n *\n * @typedef {Array | ReadonlyArray | Props | TestFunction | string | null | undefined} Test\n * Check for an arbitrary node.\n *\n * @callback TestFunction\n * Check if a node passes a test.\n * @param {unknown} this\n * The given context.\n * @param {Node} node\n * A node.\n * @param {number | undefined} [index]\n * The node’s position in its parent.\n * @param {Parent | undefined} [parent]\n * The node’s parent.\n * @returns {boolean | undefined | void}\n * Whether this node passes the test.\n *\n * Note: `void` is included until TS sees no return as `undefined`.\n */\n\n/**\n * Check if `node` is a `Node` and whether it passes the given test.\n *\n * @param {unknown} node\n * Thing to check, typically `Node`.\n * @param {Test} test\n * A check for a specific node.\n * @param {number | null | undefined} index\n * The node’s position in its parent.\n * @param {Parent | null | undefined} parent\n * The node’s parent.\n * @param {unknown} context\n * Context object (`this`) to pass to `test` functions.\n * @returns {boolean}\n * Whether `node` is a node and passes a test.\n */\nexport const is =\n // Note: overloads in JSDoc can’t yet use different `@template`s.\n /**\n * @type {(\n * (>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &\n * (>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &\n * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &\n * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &\n * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate) &\n * ((node?: null | undefined) => false) &\n * ((node: unknown, test?: null | undefined, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &\n * ((node: unknown, test?: Test, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => boolean)\n * )}\n */\n (\n /**\n * @param {unknown} [node]\n * @param {Test} [test]\n * @param {number | null | undefined} [index]\n * @param {Parent | null | undefined} [parent]\n * @param {unknown} [context]\n * @returns {boolean}\n */\n // eslint-disable-next-line max-params\n function (node, test, index, parent, context) {\n const check = convert(test)\n\n if (\n index !== undefined &&\n index !== null &&\n (typeof index !== 'number' ||\n index < 0 ||\n index === Number.POSITIVE_INFINITY)\n ) {\n throw new Error('Expected positive finite index')\n }\n\n if (\n parent !== undefined &&\n parent !== null &&\n (!is(parent) || !parent.children)\n ) {\n throw new Error('Expected parent node')\n }\n\n if (\n (parent === undefined || parent === null) !==\n (index === undefined || index === null)\n ) {\n throw new Error('Expected both parent and index')\n }\n\n return looksLikeANode(node)\n ? check.call(context, node, index, parent)\n : false\n }\n )\n\n/**\n * Generate an assertion from a test.\n *\n * Useful if you’re going to test many nodes, for example when creating a\n * utility where something else passes a compatible test.\n *\n * The created function is a bit faster because it expects valid input only:\n * a `node`, `index`, and `parent`.\n *\n * @param {Test} test\n * * when nullish, checks if `node` is a `Node`.\n * * when `string`, works like passing `(node) => node.type === test`.\n * * when `function` checks if function passed the node is true.\n * * when `object`, checks that all keys in test are in node, and that they have (strictly) equal values.\n * * when `array`, checks if any one of the subtests pass.\n * @returns {Check}\n * An assertion.\n */\nexport const convert =\n // Note: overloads in JSDoc can’t yet use different `@template`s.\n /**\n * @type {(\n * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &\n * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &\n * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate) &\n * ((test?: null | undefined) => (node?: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &\n * ((test?: Test) => Check)\n * )}\n */\n (\n /**\n * @param {Test} [test]\n * @returns {Check}\n */\n function (test) {\n if (test === null || test === undefined) {\n return ok\n }\n\n if (typeof test === 'function') {\n return castFactory(test)\n }\n\n if (typeof test === 'object') {\n return Array.isArray(test)\n ? anyFactory(test)\n : // Cast because `ReadonlyArray` goes into the above but `isArray`\n // narrows to `Array`.\n propertiesFactory(/** @type {Props} */ (test))\n }\n\n if (typeof test === 'string') {\n return typeFactory(test)\n }\n\n throw new Error('Expected function, string, or object as test')\n }\n )\n\n/**\n * @param {Array} tests\n * @returns {Check}\n */\nfunction anyFactory(tests) {\n /** @type {Array} */\n const checks = []\n let index = -1\n\n while (++index < tests.length) {\n checks[index] = convert(tests[index])\n }\n\n return castFactory(any)\n\n /**\n * @this {unknown}\n * @type {TestFunction}\n */\n function any(...parameters) {\n let index = -1\n\n while (++index < checks.length) {\n if (checks[index].apply(this, parameters)) return true\n }\n\n return false\n }\n}\n\n/**\n * Turn an object into a test for a node with a certain fields.\n *\n * @param {Props} check\n * @returns {Check}\n */\nfunction propertiesFactory(check) {\n const checkAsRecord = /** @type {Record} */ (check)\n\n return castFactory(all)\n\n /**\n * @param {Node} node\n * @returns {boolean}\n */\n function all(node) {\n const nodeAsRecord = /** @type {Record} */ (\n /** @type {unknown} */ (node)\n )\n\n /** @type {string} */\n let key\n\n for (key in check) {\n if (nodeAsRecord[key] !== checkAsRecord[key]) return false\n }\n\n return true\n }\n}\n\n/**\n * Turn a string into a test for a node with a certain type.\n *\n * @param {string} check\n * @returns {Check}\n */\nfunction typeFactory(check) {\n return castFactory(type)\n\n /**\n * @param {Node} node\n */\n function type(node) {\n return node && node.type === check\n }\n}\n\n/**\n * Turn a custom test into a test for a node that passes that test.\n *\n * @param {TestFunction} testFunction\n * @returns {Check}\n */\nfunction castFactory(testFunction) {\n return check\n\n /**\n * @this {unknown}\n * @type {Check}\n */\n function check(value, index, parent) {\n return Boolean(\n looksLikeANode(value) &&\n testFunction.call(\n this,\n value,\n typeof index === 'number' ? index : undefined,\n parent || undefined\n )\n )\n }\n}\n\nfunction ok() {\n return true\n}\n\n/**\n * @param {unknown} value\n * @returns {value is Node}\n */\nfunction looksLikeANode(value) {\n return value !== null && typeof value === 'object' && 'type' in value\n}\n","/**\n * @param {string} d\n * @returns {string}\n */\nexport function color(d) {\n return '\\u001B[33m' + d + '\\u001B[39m'\n}\n","/**\n * @import {Node as UnistNode, Parent as UnistParent} from 'unist'\n */\n\n/**\n * @typedef {Exclude | undefined} Test\n * Test from `unist-util-is`.\n *\n * Note: we have remove and add `undefined`, because otherwise when generating\n * automatic `.d.ts` files, TS tries to flatten paths from a local perspective,\n * which doesn’t work when publishing on npm.\n */\n\n/**\n * @typedef {(\n * Fn extends (value: any) => value is infer Thing\n * ? Thing\n * : Fallback\n * )} Predicate\n * Get the value of a type guard `Fn`.\n * @template Fn\n * Value; typically function that is a type guard (such as `(x): x is Y`).\n * @template Fallback\n * Value to yield if `Fn` is not a type guard.\n */\n\n/**\n * @typedef {(\n * Check extends null | undefined // No test.\n * ? Value\n * : Value extends {type: Check} // String (type) test.\n * ? Value\n * : Value extends Check // Partial test.\n * ? Value\n * : Check extends Function // Function test.\n * ? Predicate extends Value\n * ? Predicate\n * : never\n * : never // Some other test?\n * )} MatchesOne\n * Check whether a node matches a primitive check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test, but not arrays.\n */\n\n/**\n * @typedef {(\n * Check extends ReadonlyArray\n * ? MatchesOne\n * : Check extends Array\n * ? MatchesOne\n * : MatchesOne\n * )} Matches\n * Check whether a node matches a check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test.\n */\n\n/**\n * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint\n * Number; capped reasonably.\n */\n\n/**\n * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment\n * Increment a number in the type system.\n * @template {Uint} [I=0]\n * Index.\n */\n\n/**\n * @typedef {(\n * Node extends UnistParent\n * ? Node extends {children: Array}\n * ? Child extends Children ? Node : never\n * : never\n * : never\n * )} InternalParent\n * Collect nodes that can be parents of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {InternalParent, Child>} Parent\n * Collect nodes in `Tree` that can be parents of `Child`.\n * @template {UnistNode} Tree\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {(\n * Depth extends Max\n * ? never\n * :\n * | InternalParent\n * | InternalAncestor, Max, Increment>\n * )} InternalAncestor\n * Collect nodes in `Tree` that can be ancestors of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @typedef {InternalAncestor, Child>} Ancestor\n * Collect nodes in `Tree` that can be ancestors of `Child`.\n * @template {UnistNode} Tree\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {(\n * Tree extends UnistParent\n * ? Depth extends Max\n * ? Tree\n * : Tree | InclusiveDescendant>\n * : Tree\n * )} InclusiveDescendant\n * Collect all (inclusive) descendants of `Tree`.\n *\n * > 👉 **Note**: for performance reasons, this seems to be the fastest way to\n * > recurse without actually running into an infinite loop, which the\n * > previous version did.\n * >\n * > Practically, a max of `2` is typically enough assuming a `Root` is\n * > passed, but it doesn’t improve performance.\n * > It gets higher with `List > ListItem > Table > TableRow > TableCell`.\n * > Using up to `10` doesn’t hurt or help either.\n * @template {UnistNode} Tree\n * Tree type.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @typedef {'skip' | boolean} Action\n * Union of the action types.\n *\n * @typedef {number} Index\n * Move to the sibling at `index` next (after node itself is completely\n * traversed).\n *\n * Useful if mutating the tree, such as removing the node the visitor is\n * currently on, or any of its previous siblings.\n * Results less than 0 or greater than or equal to `children.length` stop\n * traversing the parent.\n *\n * @typedef {[(Action | null | undefined | void)?, (Index | null | undefined)?]} ActionTuple\n * List with one or two values, the first an action, the second an index.\n *\n * @typedef {Action | ActionTuple | Index | null | undefined | void} VisitorResult\n * Any value that can be returned from a visitor.\n */\n\n/**\n * @callback Visitor\n * Handle a node (matching `test`, if given).\n *\n * Visitors are free to transform `node`.\n * They can also transform the parent of node (the last of `ancestors`).\n *\n * Replacing `node` itself, if `SKIP` is not returned, still causes its\n * descendants to be walked (which is a bug).\n *\n * When adding or removing previous siblings of `node` (or next siblings, in\n * case of reverse), the `Visitor` should return a new `Index` to specify the\n * sibling to traverse after `node` is traversed.\n * Adding or removing next siblings of `node` (or previous siblings, in case\n * of reverse) is handled as expected without needing to return a new `Index`.\n *\n * Removing the children property of an ancestor still results in them being\n * traversed.\n * @param {Visited} node\n * Found node.\n * @param {Array} ancestors\n * Ancestors of `node`.\n * @returns {VisitorResult}\n * What to do next.\n *\n * An `Index` is treated as a tuple of `[CONTINUE, Index]`.\n * An `Action` is treated as a tuple of `[Action]`.\n *\n * Passing a tuple back only makes sense if the `Action` is `SKIP`.\n * When the `Action` is `EXIT`, that action can be returned.\n * When the `Action` is `CONTINUE`, `Index` can be returned.\n * @template {UnistNode} [Visited=UnistNode]\n * Visited node type.\n * @template {UnistParent} [VisitedParents=UnistParent]\n * Ancestor type.\n */\n\n/**\n * @typedef {Visitor, Check>, Ancestor, Check>>>} BuildVisitor\n * Build a typed `Visitor` function from a tree and a test.\n *\n * It will infer which values are passed as `node` and which as `parents`.\n * @template {UnistNode} [Tree=UnistNode]\n * Tree type.\n * @template {Test} [Check=Test]\n * Test type.\n */\n\nimport {convert} from 'unist-util-is'\nimport {color} from 'unist-util-visit-parents/do-not-use-color'\n\n/** @type {Readonly} */\nconst empty = []\n\n/**\n * Continue traversing as normal.\n */\nexport const CONTINUE = true\n\n/**\n * Stop traversing immediately.\n */\nexport const EXIT = false\n\n/**\n * Do not traverse this node’s children.\n */\nexport const SKIP = 'skip'\n\n/**\n * Visit nodes, with ancestral information.\n *\n * This algorithm performs *depth-first* *tree traversal* in *preorder*\n * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).\n *\n * You can choose for which nodes `visitor` is called by passing a `test`.\n * For complex tests, you should test yourself in `visitor`, as it will be\n * faster and will have improved type information.\n *\n * Walking the tree is an intensive task.\n * Make use of the return values of the visitor when possible.\n * Instead of walking a tree multiple times, walk it once, use `unist-util-is`\n * to check if a node matches, and then perform different operations.\n *\n * You can change the tree.\n * See `Visitor` for more info.\n *\n * @overload\n * @param {Tree} tree\n * @param {Check} check\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @overload\n * @param {Tree} tree\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @param {UnistNode} tree\n * Tree to traverse.\n * @param {Visitor | Test} test\n * `unist-util-is`-compatible test\n * @param {Visitor | boolean | null | undefined} [visitor]\n * Handle each node.\n * @param {boolean | null | undefined} [reverse]\n * Traverse in reverse preorder (NRL) instead of the default preorder (NLR).\n * @returns {undefined}\n * Nothing.\n *\n * @template {UnistNode} Tree\n * Node type.\n * @template {Test} Check\n * `unist-util-is`-compatible test.\n */\nexport function visitParents(tree, test, visitor, reverse) {\n /** @type {Test} */\n let check\n\n if (typeof test === 'function' && typeof visitor !== 'function') {\n reverse = visitor\n // @ts-expect-error no visitor given, so `visitor` is test.\n visitor = test\n } else {\n // @ts-expect-error visitor given, so `test` isn’t a visitor.\n check = test\n }\n\n const is = convert(check)\n const step = reverse ? -1 : 1\n\n factory(tree, undefined, [])()\n\n /**\n * @param {UnistNode} node\n * @param {number | undefined} index\n * @param {Array} parents\n */\n function factory(node, index, parents) {\n const value = /** @type {Record} */ (\n node && typeof node === 'object' ? node : {}\n )\n\n if (typeof value.type === 'string') {\n const name =\n // `hast`\n typeof value.tagName === 'string'\n ? value.tagName\n : // `xast`\n typeof value.name === 'string'\n ? value.name\n : undefined\n\n Object.defineProperty(visit, 'name', {\n value:\n 'node (' + color(node.type + (name ? '<' + name + '>' : '')) + ')'\n })\n }\n\n return visit\n\n function visit() {\n /** @type {Readonly} */\n let result = empty\n /** @type {Readonly} */\n let subresult\n /** @type {number} */\n let offset\n /** @type {Array} */\n let grandparents\n\n if (!test || is(node, index, parents[parents.length - 1] || undefined)) {\n // @ts-expect-error: `visitor` is now a visitor.\n result = toResult(visitor(node, parents))\n\n if (result[0] === EXIT) {\n return result\n }\n }\n\n if ('children' in node && node.children) {\n const nodeAsParent = /** @type {UnistParent} */ (node)\n\n if (nodeAsParent.children && result[0] !== SKIP) {\n offset = (reverse ? nodeAsParent.children.length : -1) + step\n grandparents = parents.concat(nodeAsParent)\n\n while (offset > -1 && offset < nodeAsParent.children.length) {\n const child = nodeAsParent.children[offset]\n\n subresult = factory(child, offset, grandparents)()\n\n if (subresult[0] === EXIT) {\n return subresult\n }\n\n offset =\n typeof subresult[1] === 'number' ? subresult[1] : offset + step\n }\n }\n }\n\n return result\n }\n }\n}\n\n/**\n * Turn a return value into a clean result.\n *\n * @param {VisitorResult} value\n * Valid return values from visitors.\n * @returns {Readonly}\n * Clean result.\n */\nfunction toResult(value) {\n if (Array.isArray(value)) {\n return value\n }\n\n if (typeof value === 'number') {\n return [CONTINUE, value]\n }\n\n return value === null || value === undefined ? empty : [value]\n}\n","/**\n * @import {Node as UnistNode, Parent as UnistParent} from 'unist'\n * @import {VisitorResult} from 'unist-util-visit-parents'\n */\n\n/**\n * @typedef {Exclude | undefined} Test\n * Test from `unist-util-is`.\n *\n * Note: we have remove and add `undefined`, because otherwise when generating\n * automatic `.d.ts` files, TS tries to flatten paths from a local perspective,\n * which doesn’t work when publishing on npm.\n */\n\n// To do: use types from `unist-util-visit-parents` when it’s released.\n\n/**\n * @typedef {(\n * Fn extends (value: any) => value is infer Thing\n * ? Thing\n * : Fallback\n * )} Predicate\n * Get the value of a type guard `Fn`.\n * @template Fn\n * Value; typically function that is a type guard (such as `(x): x is Y`).\n * @template Fallback\n * Value to yield if `Fn` is not a type guard.\n */\n\n/**\n * @typedef {(\n * Check extends null | undefined // No test.\n * ? Value\n * : Value extends {type: Check} // String (type) test.\n * ? Value\n * : Value extends Check // Partial test.\n * ? Value\n * : Check extends Function // Function test.\n * ? Predicate extends Value\n * ? Predicate\n * : never\n * : never // Some other test?\n * )} MatchesOne\n * Check whether a node matches a primitive check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test, but not arrays.\n */\n\n/**\n * @typedef {(\n * Check extends ReadonlyArray\n * ? MatchesOne\n * : MatchesOne\n * )} Matches\n * Check whether a node matches a check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test.\n */\n\n/**\n * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint\n * Number; capped reasonably.\n */\n\n/**\n * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment\n * Increment a number in the type system.\n * @template {Uint} [I=0]\n * Index.\n */\n\n/**\n * @typedef {(\n * Node extends UnistParent\n * ? Node extends {children: Array}\n * ? Child extends Children ? Node : never\n * : never\n * : never\n * )} InternalParent\n * Collect nodes that can be parents of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {InternalParent, Child>} Parent\n * Collect nodes in `Tree` that can be parents of `Child`.\n * @template {UnistNode} Tree\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {(\n * Depth extends Max\n * ? never\n * :\n * | InternalParent\n * | InternalAncestor, Max, Increment>\n * )} InternalAncestor\n * Collect nodes in `Tree` that can be ancestors of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @typedef {(\n * Tree extends UnistParent\n * ? Depth extends Max\n * ? Tree\n * : Tree | InclusiveDescendant>\n * : Tree\n * )} InclusiveDescendant\n * Collect all (inclusive) descendants of `Tree`.\n *\n * > 👉 **Note**: for performance reasons, this seems to be the fastest way to\n * > recurse without actually running into an infinite loop, which the\n * > previous version did.\n * >\n * > Practically, a max of `2` is typically enough assuming a `Root` is\n * > passed, but it doesn’t improve performance.\n * > It gets higher with `List > ListItem > Table > TableRow > TableCell`.\n * > Using up to `10` doesn’t hurt or help either.\n * @template {UnistNode} Tree\n * Tree type.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @callback Visitor\n * Handle a node (matching `test`, if given).\n *\n * Visitors are free to transform `node`.\n * They can also transform `parent`.\n *\n * Replacing `node` itself, if `SKIP` is not returned, still causes its\n * descendants to be walked (which is a bug).\n *\n * When adding or removing previous siblings of `node` (or next siblings, in\n * case of reverse), the `Visitor` should return a new `Index` to specify the\n * sibling to traverse after `node` is traversed.\n * Adding or removing next siblings of `node` (or previous siblings, in case\n * of reverse) is handled as expected without needing to return a new `Index`.\n *\n * Removing the children property of `parent` still results in them being\n * traversed.\n * @param {Visited} node\n * Found node.\n * @param {Visited extends UnistNode ? number | undefined : never} index\n * Index of `node` in `parent`.\n * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent\n * Parent of `node`.\n * @returns {VisitorResult}\n * What to do next.\n *\n * An `Index` is treated as a tuple of `[CONTINUE, Index]`.\n * An `Action` is treated as a tuple of `[Action]`.\n *\n * Passing a tuple back only makes sense if the `Action` is `SKIP`.\n * When the `Action` is `EXIT`, that action can be returned.\n * When the `Action` is `CONTINUE`, `Index` can be returned.\n * @template {UnistNode} [Visited=UnistNode]\n * Visited node type.\n * @template {UnistParent} [Ancestor=UnistParent]\n * Ancestor type.\n */\n\n/**\n * @typedef {Visitor>} BuildVisitorFromMatch\n * Build a typed `Visitor` function from a node and all possible parents.\n *\n * It will infer which values are passed as `node` and which as `parent`.\n * @template {UnistNode} Visited\n * Node type.\n * @template {UnistParent} Ancestor\n * Parent type.\n */\n\n/**\n * @typedef {(\n * BuildVisitorFromMatch<\n * Matches,\n * Extract\n * >\n * )} BuildVisitorFromDescendants\n * Build a typed `Visitor` function from a list of descendants and a test.\n *\n * It will infer which values are passed as `node` and which as `parent`.\n * @template {UnistNode} Descendant\n * Node type.\n * @template {Test} Check\n * Test type.\n */\n\n/**\n * @typedef {(\n * BuildVisitorFromDescendants<\n * InclusiveDescendant,\n * Check\n * >\n * )} BuildVisitor\n * Build a typed `Visitor` function from a tree and a test.\n *\n * It will infer which values are passed as `node` and which as `parent`.\n * @template {UnistNode} [Tree=UnistNode]\n * Node type.\n * @template {Test} [Check=Test]\n * Test type.\n */\n\nimport {visitParents} from 'unist-util-visit-parents'\n\nexport {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents'\n\n/**\n * Visit nodes.\n *\n * This algorithm performs *depth-first* *tree traversal* in *preorder*\n * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).\n *\n * You can choose for which nodes `visitor` is called by passing a `test`.\n * For complex tests, you should test yourself in `visitor`, as it will be\n * faster and will have improved type information.\n *\n * Walking the tree is an intensive task.\n * Make use of the return values of the visitor when possible.\n * Instead of walking a tree multiple times, walk it once, use `unist-util-is`\n * to check if a node matches, and then perform different operations.\n *\n * You can change the tree.\n * See `Visitor` for more info.\n *\n * @overload\n * @param {Tree} tree\n * @param {Check} check\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @overload\n * @param {Tree} tree\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @param {UnistNode} tree\n * Tree to traverse.\n * @param {Visitor | Test} testOrVisitor\n * `unist-util-is`-compatible test (optional, omit to pass a visitor).\n * @param {Visitor | boolean | null | undefined} [visitorOrReverse]\n * Handle each node (when test is omitted, pass `reverse`).\n * @param {boolean | null | undefined} [maybeReverse=false]\n * Traverse in reverse preorder (NRL) instead of the default preorder (NLR).\n * @returns {undefined}\n * Nothing.\n *\n * @template {UnistNode} Tree\n * Node type.\n * @template {Test} Check\n * `unist-util-is`-compatible test.\n */\nexport function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) {\n /** @type {boolean | null | undefined} */\n let reverse\n /** @type {Test} */\n let test\n /** @type {Visitor} */\n let visitor\n\n if (\n typeof testOrVisitor === 'function' &&\n typeof visitorOrReverse !== 'function'\n ) {\n test = undefined\n visitor = testOrVisitor\n reverse = visitorOrReverse\n } else {\n // @ts-expect-error: assume the overload with test was given.\n test = testOrVisitor\n // @ts-expect-error: assume the overload with test was given.\n visitor = visitorOrReverse\n reverse = maybeReverse\n }\n\n visitParents(tree, test, overload, reverse)\n\n /**\n * @param {UnistNode} node\n * @param {Array} parents\n */\n function overload(node, parents) {\n const parent = parents[parents.length - 1]\n const index = parent ? parent.children.indexOf(node) : undefined\n return visitor(node, index, parent)\n }\n}\n","// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1\n// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3\nconst ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\\d+\\-.]*?:/;\n\n// Windows paths like `c:\\`\nconst WINDOWS_PATH_REGEX = /^[a-zA-Z]:\\\\/;\n\nexport default function isAbsoluteUrl(url) {\n\tif (typeof url !== 'string') {\n\t\tthrow new TypeError(`Expected a \\`string\\`, got \\`${typeof url}\\``);\n\t}\n\n\tif (WINDOWS_PATH_REGEX.test(url)) {\n\t\treturn false;\n\t}\n\n\treturn ABSOLUTE_URL_REGEX.test(url);\n}\n","import type { QuartzTransformerPlugin, BuildCtx } from \"@quartz-community/types\";\nimport type { FullSlug, RelativeURL, SimpleSlug, TransformOptions } from \"@quartz-community/utils\";\nimport { stripSlashes, simplifySlug, splitAnchor, transformLink } from \"@quartz-community/utils\";\nimport path from \"path\";\nimport { visit } from \"unist-util-visit\";\nimport isAbsoluteUrl from \"is-absolute-url\";\nimport type { Root, Element, Text } from \"hast\";\nimport type { VFile } from \"vfile\";\n\nexport interface CrawlLinksOptions {\n /** How to resolve Markdown paths */\n markdownLinkResolution: TransformOptions[\"strategy\"];\n /** Strips folders from a link so that it looks nice */\n prettyLinks: boolean;\n openLinksInNewTab: boolean;\n lazyLoad: boolean;\n externalLinkIcon: boolean;\n}\n\nconst defaultOptions: CrawlLinksOptions = {\n markdownLinkResolution: \"absolute\",\n prettyLinks: true,\n openLinksInNewTab: false,\n lazyLoad: false,\n externalLinkIcon: true,\n};\n\nconst isAbsoluteUrlWithOptions = isAbsoluteUrl as (\n url: string,\n options?: { httpOnly?: boolean },\n) => boolean;\n\nexport const CrawlLinks: QuartzTransformerPlugin> = (\n userOpts?: Partial,\n) => {\n const opts = { ...defaultOptions, ...userOpts };\n return {\n name: \"LinkProcessing\",\n htmlPlugins(ctx: BuildCtx) {\n return [\n () => {\n return (tree: Root, file: VFile) => {\n const fileSlug = file.data.slug as FullSlug;\n const curSlug = simplifySlug(fileSlug);\n const outgoing: Set = new Set();\n\n const transformOptions: TransformOptions = {\n strategy: opts.markdownLinkResolution,\n allSlugs: ctx.allSlugs,\n };\n\n visit(tree, \"element\", (node: Element) => {\n // rewrite all links\n if (\n node.tagName === \"a\" &&\n node.properties &&\n typeof node.properties.href === \"string\"\n ) {\n let dest = node.properties.href as RelativeURL;\n const classes = (node.properties.className ?? []) as string[];\n const isExternal = isAbsoluteUrlWithOptions(dest, { httpOnly: false });\n classes.push(isExternal ? \"external\" : \"internal\");\n\n if (isExternal && opts.externalLinkIcon) {\n node.children.push({\n type: \"element\",\n tagName: \"svg\",\n properties: {\n \"aria-hidden\": \"true\",\n class: \"external-icon\",\n style: \"max-width:0.8em;max-height:0.8em\",\n viewBox: \"0 0 512 512\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z\",\n },\n children: [],\n },\n ],\n });\n }\n\n // Check if the link has alias text\n const firstChild = node.children[0];\n if (\n node.children.length === 1 &&\n firstChild?.type === \"text\" &&\n firstChild.value !== dest\n ) {\n // Add the 'alias' class if the text content is not the same as the href\n classes.push(\"alias\");\n }\n node.properties.className = classes;\n\n if (isExternal && opts.openLinksInNewTab) {\n node.properties.target = \"_blank\";\n }\n\n // don't process external links or intra-document anchors\n const isInternal = !(\n isAbsoluteUrlWithOptions(dest, { httpOnly: false }) || dest.startsWith(\"#\")\n );\n if (isInternal) {\n dest = node.properties.href = transformLink(fileSlug, dest, transformOptions);\n\n // url.resolve is considered legacy\n // WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to\n const url = new URL(dest, \"https://base.com/\" + stripSlashes(curSlug, true));\n const canonicalDest = url.pathname;\n const [destCanonicalRaw, _destAnchor] = splitAnchor(canonicalDest);\n let destCanonical = destCanonicalRaw;\n if (destCanonical.endsWith(\"/\")) {\n destCanonical += \"index\";\n }\n\n // need to decodeURIComponent here as WHATWG URL percent-encodes everything\n const full = decodeURIComponent(stripSlashes(destCanonical, true)) as FullSlug;\n const simple = simplifySlug(full);\n outgoing.add(simple);\n node.properties[\"data-slug\"] = full;\n }\n\n // rewrite link internals if prettylinks is on\n if (opts.prettyLinks && isInternal && node.children.length === 1) {\n const textChild = node.children[0] as Text | undefined;\n if (textChild?.type === \"text\" && !textChild.value.startsWith(\"#\")) {\n textChild.value = path.basename(textChild.value);\n }\n }\n }\n\n // transform all other resources that may use links\n if (\n [\"img\", \"video\", \"audio\", \"iframe\"].includes(node.tagName) &&\n node.properties &&\n typeof node.properties.src === \"string\"\n ) {\n if (opts.lazyLoad) {\n node.properties.loading = \"lazy\";\n }\n\n if (!isAbsoluteUrlWithOptions(node.properties.src, { httpOnly: false })) {\n let dest = node.properties.src as RelativeURL;\n dest = node.properties.src = transformLink(fileSlug, dest, transformOptions);\n node.properties.src = dest;\n }\n }\n });\n\n file.data.links = [...outgoing];\n };\n },\n ];\n },\n };\n};\n"]} \ No newline at end of file +{"version":3,"sources":["../node_modules/unist-util-is/lib/index.js","../node_modules/unist-util-visit-parents/lib/color.node.js","../node_modules/unist-util-visit-parents/lib/index.js","../node_modules/unist-util-visit/lib/index.js","../node_modules/is-absolute-url/index.js","../src/transformer.ts"],"names":["index","is","visit"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @import {Node, Parent} from 'unist'\n */\n\n/**\n * @template Fn\n * @template Fallback\n * @typedef {Fn extends (value: any) => value is infer Thing ? Thing : Fallback} Predicate\n */\n\n/**\n * @callback Check\n * Check that an arbitrary value is a node.\n * @param {unknown} this\n * The given context.\n * @param {unknown} [node]\n * Anything (typically a node).\n * @param {number | null | undefined} [index]\n * The node’s position in its parent.\n * @param {Parent | null | undefined} [parent]\n * The node’s parent.\n * @returns {boolean}\n * Whether this is a node and passes a test.\n *\n * @typedef {Record | Node} Props\n * Object to check for equivalence.\n *\n * Note: `Node` is included as it is common but is not indexable.\n *\n * @typedef {Array | ReadonlyArray | Props | TestFunction | string | null | undefined} Test\n * Check for an arbitrary node.\n *\n * @callback TestFunction\n * Check if a node passes a test.\n * @param {unknown} this\n * The given context.\n * @param {Node} node\n * A node.\n * @param {number | undefined} [index]\n * The node’s position in its parent.\n * @param {Parent | undefined} [parent]\n * The node’s parent.\n * @returns {boolean | undefined | void}\n * Whether this node passes the test.\n *\n * Note: `void` is included until TS sees no return as `undefined`.\n */\n\n/**\n * Check if `node` is a `Node` and whether it passes the given test.\n *\n * @param {unknown} node\n * Thing to check, typically `Node`.\n * @param {Test} test\n * A check for a specific node.\n * @param {number | null | undefined} index\n * The node’s position in its parent.\n * @param {Parent | null | undefined} parent\n * The node’s parent.\n * @param {unknown} context\n * Context object (`this`) to pass to `test` functions.\n * @returns {boolean}\n * Whether `node` is a node and passes a test.\n */\nexport const is =\n // Note: overloads in JSDoc can’t yet use different `@template`s.\n /**\n * @type {(\n * (>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &\n * (>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &\n * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &\n * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &\n * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate) &\n * ((node?: null | undefined) => false) &\n * ((node: unknown, test?: null | undefined, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &\n * ((node: unknown, test?: Test, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => boolean)\n * )}\n */\n (\n /**\n * @param {unknown} [node]\n * @param {Test} [test]\n * @param {number | null | undefined} [index]\n * @param {Parent | null | undefined} [parent]\n * @param {unknown} [context]\n * @returns {boolean}\n */\n // eslint-disable-next-line max-params\n function (node, test, index, parent, context) {\n const check = convert(test)\n\n if (\n index !== undefined &&\n index !== null &&\n (typeof index !== 'number' ||\n index < 0 ||\n index === Number.POSITIVE_INFINITY)\n ) {\n throw new Error('Expected positive finite index')\n }\n\n if (\n parent !== undefined &&\n parent !== null &&\n (!is(parent) || !parent.children)\n ) {\n throw new Error('Expected parent node')\n }\n\n if (\n (parent === undefined || parent === null) !==\n (index === undefined || index === null)\n ) {\n throw new Error('Expected both parent and index')\n }\n\n return looksLikeANode(node)\n ? check.call(context, node, index, parent)\n : false\n }\n )\n\n/**\n * Generate an assertion from a test.\n *\n * Useful if you’re going to test many nodes, for example when creating a\n * utility where something else passes a compatible test.\n *\n * The created function is a bit faster because it expects valid input only:\n * a `node`, `index`, and `parent`.\n *\n * @param {Test} test\n * * when nullish, checks if `node` is a `Node`.\n * * when `string`, works like passing `(node) => node.type === test`.\n * * when `function` checks if function passed the node is true.\n * * when `object`, checks that all keys in test are in node, and that they have (strictly) equal values.\n * * when `array`, checks if any one of the subtests pass.\n * @returns {Check}\n * An assertion.\n */\nexport const convert =\n // Note: overloads in JSDoc can’t yet use different `@template`s.\n /**\n * @type {(\n * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &\n * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &\n * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate) &\n * ((test?: null | undefined) => (node?: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &\n * ((test?: Test) => Check)\n * )}\n */\n (\n /**\n * @param {Test} [test]\n * @returns {Check}\n */\n function (test) {\n if (test === null || test === undefined) {\n return ok\n }\n\n if (typeof test === 'function') {\n return castFactory(test)\n }\n\n if (typeof test === 'object') {\n return Array.isArray(test)\n ? anyFactory(test)\n : // Cast because `ReadonlyArray` goes into the above but `isArray`\n // narrows to `Array`.\n propertiesFactory(/** @type {Props} */ (test))\n }\n\n if (typeof test === 'string') {\n return typeFactory(test)\n }\n\n throw new Error('Expected function, string, or object as test')\n }\n )\n\n/**\n * @param {Array} tests\n * @returns {Check}\n */\nfunction anyFactory(tests) {\n /** @type {Array} */\n const checks = []\n let index = -1\n\n while (++index < tests.length) {\n checks[index] = convert(tests[index])\n }\n\n return castFactory(any)\n\n /**\n * @this {unknown}\n * @type {TestFunction}\n */\n function any(...parameters) {\n let index = -1\n\n while (++index < checks.length) {\n if (checks[index].apply(this, parameters)) return true\n }\n\n return false\n }\n}\n\n/**\n * Turn an object into a test for a node with a certain fields.\n *\n * @param {Props} check\n * @returns {Check}\n */\nfunction propertiesFactory(check) {\n const checkAsRecord = /** @type {Record} */ (check)\n\n return castFactory(all)\n\n /**\n * @param {Node} node\n * @returns {boolean}\n */\n function all(node) {\n const nodeAsRecord = /** @type {Record} */ (\n /** @type {unknown} */ (node)\n )\n\n /** @type {string} */\n let key\n\n for (key in check) {\n if (nodeAsRecord[key] !== checkAsRecord[key]) return false\n }\n\n return true\n }\n}\n\n/**\n * Turn a string into a test for a node with a certain type.\n *\n * @param {string} check\n * @returns {Check}\n */\nfunction typeFactory(check) {\n return castFactory(type)\n\n /**\n * @param {Node} node\n */\n function type(node) {\n return node && node.type === check\n }\n}\n\n/**\n * Turn a custom test into a test for a node that passes that test.\n *\n * @param {TestFunction} testFunction\n * @returns {Check}\n */\nfunction castFactory(testFunction) {\n return check\n\n /**\n * @this {unknown}\n * @type {Check}\n */\n function check(value, index, parent) {\n return Boolean(\n looksLikeANode(value) &&\n testFunction.call(\n this,\n value,\n typeof index === 'number' ? index : undefined,\n parent || undefined\n )\n )\n }\n}\n\nfunction ok() {\n return true\n}\n\n/**\n * @param {unknown} value\n * @returns {value is Node}\n */\nfunction looksLikeANode(value) {\n return value !== null && typeof value === 'object' && 'type' in value\n}\n","/**\n * @param {string} d\n * @returns {string}\n */\nexport function color(d) {\n return '\\u001B[33m' + d + '\\u001B[39m'\n}\n","/**\n * @import {Node as UnistNode, Parent as UnistParent} from 'unist'\n */\n\n/**\n * @typedef {Exclude | undefined} Test\n * Test from `unist-util-is`.\n *\n * Note: we have remove and add `undefined`, because otherwise when generating\n * automatic `.d.ts` files, TS tries to flatten paths from a local perspective,\n * which doesn’t work when publishing on npm.\n */\n\n/**\n * @typedef {(\n * Fn extends (value: any) => value is infer Thing\n * ? Thing\n * : Fallback\n * )} Predicate\n * Get the value of a type guard `Fn`.\n * @template Fn\n * Value; typically function that is a type guard (such as `(x): x is Y`).\n * @template Fallback\n * Value to yield if `Fn` is not a type guard.\n */\n\n/**\n * @typedef {(\n * Check extends null | undefined // No test.\n * ? Value\n * : Value extends {type: Check} // String (type) test.\n * ? Value\n * : Value extends Check // Partial test.\n * ? Value\n * : Check extends Function // Function test.\n * ? Predicate extends Value\n * ? Predicate\n * : never\n * : never // Some other test?\n * )} MatchesOne\n * Check whether a node matches a primitive check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test, but not arrays.\n */\n\n/**\n * @typedef {(\n * Check extends ReadonlyArray\n * ? MatchesOne\n * : Check extends Array\n * ? MatchesOne\n * : MatchesOne\n * )} Matches\n * Check whether a node matches a check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test.\n */\n\n/**\n * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint\n * Number; capped reasonably.\n */\n\n/**\n * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment\n * Increment a number in the type system.\n * @template {Uint} [I=0]\n * Index.\n */\n\n/**\n * @typedef {(\n * Node extends UnistParent\n * ? Node extends {children: Array}\n * ? Child extends Children ? Node : never\n * : never\n * : never\n * )} InternalParent\n * Collect nodes that can be parents of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {InternalParent, Child>} Parent\n * Collect nodes in `Tree` that can be parents of `Child`.\n * @template {UnistNode} Tree\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {(\n * Depth extends Max\n * ? never\n * :\n * | InternalParent\n * | InternalAncestor, Max, Increment>\n * )} InternalAncestor\n * Collect nodes in `Tree` that can be ancestors of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @typedef {InternalAncestor, Child>} Ancestor\n * Collect nodes in `Tree` that can be ancestors of `Child`.\n * @template {UnistNode} Tree\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {(\n * Tree extends UnistParent\n * ? Depth extends Max\n * ? Tree\n * : Tree | InclusiveDescendant>\n * : Tree\n * )} InclusiveDescendant\n * Collect all (inclusive) descendants of `Tree`.\n *\n * > 👉 **Note**: for performance reasons, this seems to be the fastest way to\n * > recurse without actually running into an infinite loop, which the\n * > previous version did.\n * >\n * > Practically, a max of `2` is typically enough assuming a `Root` is\n * > passed, but it doesn’t improve performance.\n * > It gets higher with `List > ListItem > Table > TableRow > TableCell`.\n * > Using up to `10` doesn’t hurt or help either.\n * @template {UnistNode} Tree\n * Tree type.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @typedef {'skip' | boolean} Action\n * Union of the action types.\n *\n * @typedef {number} Index\n * Move to the sibling at `index` next (after node itself is completely\n * traversed).\n *\n * Useful if mutating the tree, such as removing the node the visitor is\n * currently on, or any of its previous siblings.\n * Results less than 0 or greater than or equal to `children.length` stop\n * traversing the parent.\n *\n * @typedef {[(Action | null | undefined | void)?, (Index | null | undefined)?]} ActionTuple\n * List with one or two values, the first an action, the second an index.\n *\n * @typedef {Action | ActionTuple | Index | null | undefined | void} VisitorResult\n * Any value that can be returned from a visitor.\n */\n\n/**\n * @callback Visitor\n * Handle a node (matching `test`, if given).\n *\n * Visitors are free to transform `node`.\n * They can also transform the parent of node (the last of `ancestors`).\n *\n * Replacing `node` itself, if `SKIP` is not returned, still causes its\n * descendants to be walked (which is a bug).\n *\n * When adding or removing previous siblings of `node` (or next siblings, in\n * case of reverse), the `Visitor` should return a new `Index` to specify the\n * sibling to traverse after `node` is traversed.\n * Adding or removing next siblings of `node` (or previous siblings, in case\n * of reverse) is handled as expected without needing to return a new `Index`.\n *\n * Removing the children property of an ancestor still results in them being\n * traversed.\n * @param {Visited} node\n * Found node.\n * @param {Array} ancestors\n * Ancestors of `node`.\n * @returns {VisitorResult}\n * What to do next.\n *\n * An `Index` is treated as a tuple of `[CONTINUE, Index]`.\n * An `Action` is treated as a tuple of `[Action]`.\n *\n * Passing a tuple back only makes sense if the `Action` is `SKIP`.\n * When the `Action` is `EXIT`, that action can be returned.\n * When the `Action` is `CONTINUE`, `Index` can be returned.\n * @template {UnistNode} [Visited=UnistNode]\n * Visited node type.\n * @template {UnistParent} [VisitedParents=UnistParent]\n * Ancestor type.\n */\n\n/**\n * @typedef {Visitor, Check>, Ancestor, Check>>>} BuildVisitor\n * Build a typed `Visitor` function from a tree and a test.\n *\n * It will infer which values are passed as `node` and which as `parents`.\n * @template {UnistNode} [Tree=UnistNode]\n * Tree type.\n * @template {Test} [Check=Test]\n * Test type.\n */\n\nimport {convert} from 'unist-util-is'\nimport {color} from 'unist-util-visit-parents/do-not-use-color'\n\n/** @type {Readonly} */\nconst empty = []\n\n/**\n * Continue traversing as normal.\n */\nexport const CONTINUE = true\n\n/**\n * Stop traversing immediately.\n */\nexport const EXIT = false\n\n/**\n * Do not traverse this node’s children.\n */\nexport const SKIP = 'skip'\n\n/**\n * Visit nodes, with ancestral information.\n *\n * This algorithm performs *depth-first* *tree traversal* in *preorder*\n * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).\n *\n * You can choose for which nodes `visitor` is called by passing a `test`.\n * For complex tests, you should test yourself in `visitor`, as it will be\n * faster and will have improved type information.\n *\n * Walking the tree is an intensive task.\n * Make use of the return values of the visitor when possible.\n * Instead of walking a tree multiple times, walk it once, use `unist-util-is`\n * to check if a node matches, and then perform different operations.\n *\n * You can change the tree.\n * See `Visitor` for more info.\n *\n * @overload\n * @param {Tree} tree\n * @param {Check} check\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @overload\n * @param {Tree} tree\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @param {UnistNode} tree\n * Tree to traverse.\n * @param {Visitor | Test} test\n * `unist-util-is`-compatible test\n * @param {Visitor | boolean | null | undefined} [visitor]\n * Handle each node.\n * @param {boolean | null | undefined} [reverse]\n * Traverse in reverse preorder (NRL) instead of the default preorder (NLR).\n * @returns {undefined}\n * Nothing.\n *\n * @template {UnistNode} Tree\n * Node type.\n * @template {Test} Check\n * `unist-util-is`-compatible test.\n */\nexport function visitParents(tree, test, visitor, reverse) {\n /** @type {Test} */\n let check\n\n if (typeof test === 'function' && typeof visitor !== 'function') {\n reverse = visitor\n // @ts-expect-error no visitor given, so `visitor` is test.\n visitor = test\n } else {\n // @ts-expect-error visitor given, so `test` isn’t a visitor.\n check = test\n }\n\n const is = convert(check)\n const step = reverse ? -1 : 1\n\n factory(tree, undefined, [])()\n\n /**\n * @param {UnistNode} node\n * @param {number | undefined} index\n * @param {Array} parents\n */\n function factory(node, index, parents) {\n const value = /** @type {Record} */ (\n node && typeof node === 'object' ? node : {}\n )\n\n if (typeof value.type === 'string') {\n const name =\n // `hast`\n typeof value.tagName === 'string'\n ? value.tagName\n : // `xast`\n typeof value.name === 'string'\n ? value.name\n : undefined\n\n Object.defineProperty(visit, 'name', {\n value:\n 'node (' + color(node.type + (name ? '<' + name + '>' : '')) + ')'\n })\n }\n\n return visit\n\n function visit() {\n /** @type {Readonly} */\n let result = empty\n /** @type {Readonly} */\n let subresult\n /** @type {number} */\n let offset\n /** @type {Array} */\n let grandparents\n\n if (!test || is(node, index, parents[parents.length - 1] || undefined)) {\n // @ts-expect-error: `visitor` is now a visitor.\n result = toResult(visitor(node, parents))\n\n if (result[0] === EXIT) {\n return result\n }\n }\n\n if ('children' in node && node.children) {\n const nodeAsParent = /** @type {UnistParent} */ (node)\n\n if (nodeAsParent.children && result[0] !== SKIP) {\n offset = (reverse ? nodeAsParent.children.length : -1) + step\n grandparents = parents.concat(nodeAsParent)\n\n while (offset > -1 && offset < nodeAsParent.children.length) {\n const child = nodeAsParent.children[offset]\n\n subresult = factory(child, offset, grandparents)()\n\n if (subresult[0] === EXIT) {\n return subresult\n }\n\n offset =\n typeof subresult[1] === 'number' ? subresult[1] : offset + step\n }\n }\n }\n\n return result\n }\n }\n}\n\n/**\n * Turn a return value into a clean result.\n *\n * @param {VisitorResult} value\n * Valid return values from visitors.\n * @returns {Readonly}\n * Clean result.\n */\nfunction toResult(value) {\n if (Array.isArray(value)) {\n return value\n }\n\n if (typeof value === 'number') {\n return [CONTINUE, value]\n }\n\n return value === null || value === undefined ? empty : [value]\n}\n","/**\n * @import {Node as UnistNode, Parent as UnistParent} from 'unist'\n * @import {VisitorResult} from 'unist-util-visit-parents'\n */\n\n/**\n * @typedef {Exclude | undefined} Test\n * Test from `unist-util-is`.\n *\n * Note: we have remove and add `undefined`, because otherwise when generating\n * automatic `.d.ts` files, TS tries to flatten paths from a local perspective,\n * which doesn’t work when publishing on npm.\n */\n\n// To do: use types from `unist-util-visit-parents` when it’s released.\n\n/**\n * @typedef {(\n * Fn extends (value: any) => value is infer Thing\n * ? Thing\n * : Fallback\n * )} Predicate\n * Get the value of a type guard `Fn`.\n * @template Fn\n * Value; typically function that is a type guard (such as `(x): x is Y`).\n * @template Fallback\n * Value to yield if `Fn` is not a type guard.\n */\n\n/**\n * @typedef {(\n * Check extends null | undefined // No test.\n * ? Value\n * : Value extends {type: Check} // String (type) test.\n * ? Value\n * : Value extends Check // Partial test.\n * ? Value\n * : Check extends Function // Function test.\n * ? Predicate extends Value\n * ? Predicate\n * : never\n * : never // Some other test?\n * )} MatchesOne\n * Check whether a node matches a primitive check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test, but not arrays.\n */\n\n/**\n * @typedef {(\n * Check extends ReadonlyArray\n * ? MatchesOne\n * : MatchesOne\n * )} Matches\n * Check whether a node matches a check in the type system.\n * @template Value\n * Value; typically unist `Node`.\n * @template Check\n * Value; typically `unist-util-is`-compatible test.\n */\n\n/**\n * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint\n * Number; capped reasonably.\n */\n\n/**\n * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment\n * Increment a number in the type system.\n * @template {Uint} [I=0]\n * Index.\n */\n\n/**\n * @typedef {(\n * Node extends UnistParent\n * ? Node extends {children: Array}\n * ? Child extends Children ? Node : never\n * : never\n * : never\n * )} InternalParent\n * Collect nodes that can be parents of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {InternalParent, Child>} Parent\n * Collect nodes in `Tree` that can be parents of `Child`.\n * @template {UnistNode} Tree\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n */\n\n/**\n * @typedef {(\n * Depth extends Max\n * ? never\n * :\n * | InternalParent\n * | InternalAncestor, Max, Increment>\n * )} InternalAncestor\n * Collect nodes in `Tree` that can be ancestors of `Child`.\n * @template {UnistNode} Node\n * All node types in a tree.\n * @template {UnistNode} Child\n * Node to search for.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @typedef {(\n * Tree extends UnistParent\n * ? Depth extends Max\n * ? Tree\n * : Tree | InclusiveDescendant>\n * : Tree\n * )} InclusiveDescendant\n * Collect all (inclusive) descendants of `Tree`.\n *\n * > 👉 **Note**: for performance reasons, this seems to be the fastest way to\n * > recurse without actually running into an infinite loop, which the\n * > previous version did.\n * >\n * > Practically, a max of `2` is typically enough assuming a `Root` is\n * > passed, but it doesn’t improve performance.\n * > It gets higher with `List > ListItem > Table > TableRow > TableCell`.\n * > Using up to `10` doesn’t hurt or help either.\n * @template {UnistNode} Tree\n * Tree type.\n * @template {Uint} [Max=10]\n * Max; searches up to this depth.\n * @template {Uint} [Depth=0]\n * Current depth.\n */\n\n/**\n * @callback Visitor\n * Handle a node (matching `test`, if given).\n *\n * Visitors are free to transform `node`.\n * They can also transform `parent`.\n *\n * Replacing `node` itself, if `SKIP` is not returned, still causes its\n * descendants to be walked (which is a bug).\n *\n * When adding or removing previous siblings of `node` (or next siblings, in\n * case of reverse), the `Visitor` should return a new `Index` to specify the\n * sibling to traverse after `node` is traversed.\n * Adding or removing next siblings of `node` (or previous siblings, in case\n * of reverse) is handled as expected without needing to return a new `Index`.\n *\n * Removing the children property of `parent` still results in them being\n * traversed.\n * @param {Visited} node\n * Found node.\n * @param {Visited extends UnistNode ? number | undefined : never} index\n * Index of `node` in `parent`.\n * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent\n * Parent of `node`.\n * @returns {VisitorResult}\n * What to do next.\n *\n * An `Index` is treated as a tuple of `[CONTINUE, Index]`.\n * An `Action` is treated as a tuple of `[Action]`.\n *\n * Passing a tuple back only makes sense if the `Action` is `SKIP`.\n * When the `Action` is `EXIT`, that action can be returned.\n * When the `Action` is `CONTINUE`, `Index` can be returned.\n * @template {UnistNode} [Visited=UnistNode]\n * Visited node type.\n * @template {UnistParent} [Ancestor=UnistParent]\n * Ancestor type.\n */\n\n/**\n * @typedef {Visitor>} BuildVisitorFromMatch\n * Build a typed `Visitor` function from a node and all possible parents.\n *\n * It will infer which values are passed as `node` and which as `parent`.\n * @template {UnistNode} Visited\n * Node type.\n * @template {UnistParent} Ancestor\n * Parent type.\n */\n\n/**\n * @typedef {(\n * BuildVisitorFromMatch<\n * Matches,\n * Extract\n * >\n * )} BuildVisitorFromDescendants\n * Build a typed `Visitor` function from a list of descendants and a test.\n *\n * It will infer which values are passed as `node` and which as `parent`.\n * @template {UnistNode} Descendant\n * Node type.\n * @template {Test} Check\n * Test type.\n */\n\n/**\n * @typedef {(\n * BuildVisitorFromDescendants<\n * InclusiveDescendant,\n * Check\n * >\n * )} BuildVisitor\n * Build a typed `Visitor` function from a tree and a test.\n *\n * It will infer which values are passed as `node` and which as `parent`.\n * @template {UnistNode} [Tree=UnistNode]\n * Node type.\n * @template {Test} [Check=Test]\n * Test type.\n */\n\nimport {visitParents} from 'unist-util-visit-parents'\n\nexport {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents'\n\n/**\n * Visit nodes.\n *\n * This algorithm performs *depth-first* *tree traversal* in *preorder*\n * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).\n *\n * You can choose for which nodes `visitor` is called by passing a `test`.\n * For complex tests, you should test yourself in `visitor`, as it will be\n * faster and will have improved type information.\n *\n * Walking the tree is an intensive task.\n * Make use of the return values of the visitor when possible.\n * Instead of walking a tree multiple times, walk it once, use `unist-util-is`\n * to check if a node matches, and then perform different operations.\n *\n * You can change the tree.\n * See `Visitor` for more info.\n *\n * @overload\n * @param {Tree} tree\n * @param {Check} check\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @overload\n * @param {Tree} tree\n * @param {BuildVisitor} visitor\n * @param {boolean | null | undefined} [reverse]\n * @returns {undefined}\n *\n * @param {UnistNode} tree\n * Tree to traverse.\n * @param {Visitor | Test} testOrVisitor\n * `unist-util-is`-compatible test (optional, omit to pass a visitor).\n * @param {Visitor | boolean | null | undefined} [visitorOrReverse]\n * Handle each node (when test is omitted, pass `reverse`).\n * @param {boolean | null | undefined} [maybeReverse=false]\n * Traverse in reverse preorder (NRL) instead of the default preorder (NLR).\n * @returns {undefined}\n * Nothing.\n *\n * @template {UnistNode} Tree\n * Node type.\n * @template {Test} Check\n * `unist-util-is`-compatible test.\n */\nexport function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) {\n /** @type {boolean | null | undefined} */\n let reverse\n /** @type {Test} */\n let test\n /** @type {Visitor} */\n let visitor\n\n if (\n typeof testOrVisitor === 'function' &&\n typeof visitorOrReverse !== 'function'\n ) {\n test = undefined\n visitor = testOrVisitor\n reverse = visitorOrReverse\n } else {\n // @ts-expect-error: assume the overload with test was given.\n test = testOrVisitor\n // @ts-expect-error: assume the overload with test was given.\n visitor = visitorOrReverse\n reverse = maybeReverse\n }\n\n visitParents(tree, test, overload, reverse)\n\n /**\n * @param {UnistNode} node\n * @param {Array} parents\n */\n function overload(node, parents) {\n const parent = parents[parents.length - 1]\n const index = parent ? parent.children.indexOf(node) : undefined\n return visitor(node, index, parent)\n }\n}\n","// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1\n// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3\nconst ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\\d+\\-.]*?:/;\n\n// Windows paths like `c:\\`\nconst WINDOWS_PATH_REGEX = /^[a-zA-Z]:\\\\/;\n\nexport default function isAbsoluteUrl(url) {\n\tif (typeof url !== 'string') {\n\t\tthrow new TypeError(`Expected a \\`string\\`, got \\`${typeof url}\\``);\n\t}\n\n\tif (WINDOWS_PATH_REGEX.test(url)) {\n\t\treturn false;\n\t}\n\n\treturn ABSOLUTE_URL_REGEX.test(url);\n}\n","import type { QuartzTransformerPlugin, BuildCtx } from \"@quartz-community/types\";\nimport type { FullSlug, RelativeURL, SimpleSlug, TransformOptions } from \"@quartz-community/utils\";\nimport { stripSlashes, simplifySlug, splitAnchor, transformLink } from \"@quartz-community/utils\";\nimport path from \"path\";\nimport { visit } from \"unist-util-visit\";\nimport isAbsoluteUrl from \"is-absolute-url\";\nimport type { Root, Element, Text } from \"hast\";\nimport type { VFile } from \"vfile\";\n\nexport interface CrawlLinksOptions {\n /** How to resolve Markdown paths */\n markdownLinkResolution: TransformOptions[\"strategy\"];\n /** Strips folders from a link so that it looks nice */\n prettyLinks: boolean;\n openLinksInNewTab: boolean;\n lazyLoad: boolean;\n externalLinkIcon: boolean;\n /**\n * When `true`, internal links whose resolved slug is not present in\n * `ctx.allSlugs` gain a `broken` CSS class (alongside `internal`) so\n * broken links can be styled distinctly. Applies to both wikilinks\n * (after they've been converted to `` elements by\n * ObsidianFlavoredMarkdown) and markdown links, since both are\n * indistinguishable `` nodes at this phase of the pipeline.\n */\n disableBrokenWikilinks: boolean;\n}\n\nconst defaultOptions: CrawlLinksOptions = {\n markdownLinkResolution: \"absolute\",\n prettyLinks: true,\n openLinksInNewTab: false,\n lazyLoad: false,\n externalLinkIcon: true,\n disableBrokenWikilinks: false,\n};\n\nconst isAbsoluteUrlWithOptions = isAbsoluteUrl as (\n url: string,\n options?: { httpOnly?: boolean },\n) => boolean;\n\nexport const CrawlLinks: QuartzTransformerPlugin> = (\n userOpts?: Partial,\n) => {\n const opts = { ...defaultOptions, ...userOpts };\n return {\n name: \"LinkProcessing\",\n htmlPlugins(ctx: BuildCtx) {\n return [\n () => {\n return (tree: Root, file: VFile) => {\n const fileSlug = file.data.slug as FullSlug;\n const curSlug = simplifySlug(fileSlug);\n const outgoing: Set = new Set();\n\n const transformOptions: TransformOptions = {\n strategy: opts.markdownLinkResolution,\n allSlugs: ctx.allSlugs,\n };\n\n visit(tree, \"element\", (node: Element) => {\n // rewrite all links\n if (\n node.tagName === \"a\" &&\n node.properties &&\n typeof node.properties.href === \"string\"\n ) {\n let dest = node.properties.href as RelativeURL;\n const classes = (node.properties.className ?? []) as string[];\n const isExternal = isAbsoluteUrlWithOptions(dest, { httpOnly: false });\n classes.push(isExternal ? \"external\" : \"internal\");\n\n if (isExternal && opts.externalLinkIcon) {\n node.children.push({\n type: \"element\",\n tagName: \"svg\",\n properties: {\n \"aria-hidden\": \"true\",\n class: \"external-icon\",\n style: \"max-width:0.8em;max-height:0.8em\",\n viewBox: \"0 0 512 512\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z\",\n },\n children: [],\n },\n ],\n });\n }\n\n // Check if the link has alias text\n const firstChild = node.children[0];\n if (\n node.children.length === 1 &&\n firstChild?.type === \"text\" &&\n firstChild.value !== dest\n ) {\n // Add the 'alias' class if the text content is not the same as the href\n classes.push(\"alias\");\n }\n node.properties.className = classes;\n\n if (isExternal && opts.openLinksInNewTab) {\n node.properties.target = \"_blank\";\n }\n\n // don't process external links or intra-document anchors\n const isInternal = !(\n isAbsoluteUrlWithOptions(dest, { httpOnly: false }) || dest.startsWith(\"#\")\n );\n if (isInternal) {\n dest = node.properties.href = transformLink(fileSlug, dest, transformOptions);\n\n // url.resolve is considered legacy\n // WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to\n const url = new URL(dest, \"https://base.com/\" + stripSlashes(curSlug, true));\n const canonicalDest = url.pathname;\n const [destCanonicalRaw, _destAnchor] = splitAnchor(canonicalDest);\n let destCanonical = destCanonicalRaw;\n if (destCanonical.endsWith(\"/\")) {\n destCanonical += \"index\";\n }\n\n // need to decodeURIComponent here as WHATWG URL percent-encodes everything\n const full = decodeURIComponent(stripSlashes(destCanonical, true)) as FullSlug;\n const simple = simplifySlug(full);\n outgoing.add(simple);\n node.properties[\"data-slug\"] = full;\n\n if (opts.disableBrokenWikilinks && !ctx.allSlugs.includes(full)) {\n classes.push(\"broken\");\n node.properties.className = classes;\n }\n }\n\n // rewrite link internals if prettylinks is on\n if (opts.prettyLinks && isInternal && node.children.length === 1) {\n const textChild = node.children[0] as Text | undefined;\n if (textChild?.type === \"text\" && !textChild.value.startsWith(\"#\")) {\n textChild.value = path.basename(textChild.value);\n }\n }\n }\n\n // transform all other resources that may use links\n if (\n [\"img\", \"video\", \"audio\", \"iframe\"].includes(node.tagName) &&\n node.properties &&\n typeof node.properties.src === \"string\"\n ) {\n if (opts.lazyLoad) {\n node.properties.loading = \"lazy\";\n }\n\n if (!isAbsoluteUrlWithOptions(node.properties.src, { httpOnly: false })) {\n let dest = node.properties.src as RelativeURL;\n dest = node.properties.src = transformLink(fileSlug, dest, transformOptions);\n node.properties.src = dest;\n }\n }\n });\n\n file.data.links = [...outgoing];\n };\n },\n ];\n },\n };\n};\n"]} \ No newline at end of file diff --git a/package.json b/package.json index a86cd9a..2797456 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,8 @@ "defaultOrder": 60, "defaultEnabled": true, "defaultOptions": { - "markdownLinkResolution": "shortest" + "markdownLinkResolution": "shortest", + "disableBrokenWikilinks": false }, "optionSchema": { "markdownLinkResolution": { @@ -101,6 +102,10 @@ "absolute", "relative" ] + }, + "disableBrokenWikilinks": { + "type": "boolean", + "description": "When true, internal links whose resolved slug is not in ctx.allSlugs gain a 'broken' CSS class (alongside 'internal') for styling." } } } diff --git a/src/transformer.ts b/src/transformer.ts index d57ed4a..d1c8bfc 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -15,6 +15,15 @@ export interface CrawlLinksOptions { openLinksInNewTab: boolean; lazyLoad: boolean; externalLinkIcon: boolean; + /** + * When `true`, internal links whose resolved slug is not present in + * `ctx.allSlugs` gain a `broken` CSS class (alongside `internal`) so + * broken links can be styled distinctly. Applies to both wikilinks + * (after they've been converted to `` elements by + * ObsidianFlavoredMarkdown) and markdown links, since both are + * indistinguishable `` nodes at this phase of the pipeline. + */ + disableBrokenWikilinks: boolean; } const defaultOptions: CrawlLinksOptions = { @@ -23,6 +32,7 @@ const defaultOptions: CrawlLinksOptions = { openLinksInNewTab: false, lazyLoad: false, externalLinkIcon: true, + disableBrokenWikilinks: false, }; const isAbsoluteUrlWithOptions = isAbsoluteUrl as ( @@ -122,6 +132,11 @@ export const CrawlLinks: QuartzTransformerPlugin> = ( const simple = simplifySlug(full); outgoing.add(simple); node.properties["data-slug"] = full; + + if (opts.disableBrokenWikilinks && !ctx.allSlugs.includes(full)) { + classes.push("broken"); + node.properties.className = classes; + } } // rewrite link internals if prettylinks is on diff --git a/test/broken-links.test.ts b/test/broken-links.test.ts new file mode 100644 index 0000000..2d38079 --- /dev/null +++ b/test/broken-links.test.ts @@ -0,0 +1,177 @@ +import { describe, it, expect } from "vitest"; +import type { Root, Element } from "hast"; +import { VFile } from "vfile"; +import type { BuildCtx } from "@quartz-community/types"; +import type { FullSlug } from "@quartz-community/utils"; +import { CrawlLinks } from "../src/transformer"; +import type { CrawlLinksOptions } from "../src/transformer"; + +function makeAnchorTree(href: string, text = "link"): Root { + const link: Element = { + type: "element", + tagName: "a", + properties: { href }, + children: [{ type: "text", value: text }], + }; + return { type: "root", children: [link] }; +} + +function runTransform( + tree: Root, + opts: Partial, + allSlugs: string[], + fileSlug: string, +): Element { + const plugin = CrawlLinks(opts); + const ctx = { allSlugs } as unknown as BuildCtx; + const plugins = plugin.htmlPlugins!(ctx); + const factory = plugins[0] as () => (tree: Root, file: VFile) => void; + const transformer = factory(); + + const file = new VFile(); + file.data.slug = fileSlug as FullSlug; + + transformer(tree, file); + return tree.children[0] as Element; +} + +function hasClass(el: Element, className: string): boolean { + const classes = (el.properties?.className ?? []) as string[]; + return classes.includes(className); +} + +describe("disableBrokenWikilinks - default (off)", () => { + it("does not add 'broken' class to unresolved internal links when option is off", () => { + const tree = makeAnchorTree("missing"); + const link = runTransform(tree, { disableBrokenWikilinks: false }, [], "test/page"); + expect(hasClass(link, "internal")).toBe(true); + expect(hasClass(link, "broken")).toBe(false); + }); +}); + +describe("disableBrokenWikilinks - shortest strategy", () => { + const opts: Partial = { + disableBrokenWikilinks: true, + markdownLinkResolution: "shortest", + }; + const allSlugs = ["a/b/c", "a/b/d", "a/b/index", "e/f", "e/g/h", "index"]; + const fileSlug = "a/b/c"; + + it("does not mark a bare filename that resolves via basename as broken", () => { + const tree = makeAnchorTree("d"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(false); + }); + + it("does not mark a multi-segment partial path that resolves via suffix as broken", () => { + const tree = makeAnchorTree("g/h"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(false); + }); + + it("does not mark an absolute path that exists in allSlugs as broken", () => { + const tree = makeAnchorTree("e/f"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(false); + }); + + it("marks a completely missing wikilink as broken", () => { + const tree = makeAnchorTree("does-not-exist"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "internal")).toBe(true); + expect(hasClass(link, "broken")).toBe(true); + }); + + it("marks a missing multi-segment path as broken", () => { + const tree = makeAnchorTree("x/y/z"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(true); + }); +}); + +describe("disableBrokenWikilinks - absolute strategy", () => { + const opts: Partial = { + disableBrokenWikilinks: true, + markdownLinkResolution: "absolute", + }; + const allSlugs = ["a/b/c", "a/b/d", "e/f"]; + const fileSlug = "a/b/c"; + + it("marks a bare filename as broken under absolute resolution", () => { + const tree = makeAnchorTree("d"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(true); + }); + + it("does not mark a valid absolute path as broken", () => { + const tree = makeAnchorTree("a/b/d"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(false); + }); + + it("marks a missing absolute path as broken", () => { + const tree = makeAnchorTree("nowhere/at/all"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(true); + }); +}); + +describe("disableBrokenWikilinks - relative strategy", () => { + const opts: Partial = { + disableBrokenWikilinks: true, + markdownLinkResolution: "relative", + }; + const allSlugs = ["a/b/c", "a/b/d", "a/sibling"]; + const fileSlug = "a/b/c"; + + it("does not mark a valid same-folder sibling as broken", () => { + const tree = makeAnchorTree("d"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(false); + }); + + it("does not mark a valid parent-folder link via ../ as broken", () => { + const tree = makeAnchorTree("../sibling"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(false); + }); + + it("marks an unresolved relative link as broken", () => { + const tree = makeAnchorTree("../nonexistent"); + const link = runTransform(tree, opts, allSlugs, fileSlug); + expect(hasClass(link, "broken")).toBe(true); + }); +}); + +describe("disableBrokenWikilinks - scope", () => { + const opts: Partial = { + disableBrokenWikilinks: true, + markdownLinkResolution: "shortest", + }; + + it("does not mark external links as broken", () => { + const tree = makeAnchorTree("https://example.com"); + const link = runTransform(tree, opts, [], "test/page"); + expect(hasClass(link, "external")).toBe(true); + expect(hasClass(link, "broken")).toBe(false); + }); + + it("does not mark intra-document anchor links as broken", () => { + const tree = makeAnchorTree("#section"); + const link = runTransform(tree, opts, [], "test/page"); + expect(hasClass(link, "broken")).toBe(false); + }); +}); + +describe("disableBrokenWikilinks - empty allSlugs", () => { + it("marks every internal link as broken when allSlugs is empty", () => { + const tree = makeAnchorTree("any-page"); + const link = runTransform( + tree, + { disableBrokenWikilinks: true, markdownLinkResolution: "shortest" }, + [], + "test/page", + ); + expect(hasClass(link, "broken")).toBe(true); + }); +});