ID: I202602211303
Status: idea
Tags: Plugins, quartz
WikiLinkValidator
NOTE
This plugin is a custom plugin made by me. It is not an official plugin (yet?). I might make a PR in the future
This plugin validates internal wikilinks and handles broken links (links to pages that don’t exist) by converting them to plain text, bold text, or leaving them as-is based on your configuration.
This plugin accepts the following configuration options:
brokenLinkFormat: How to display broken wikilinks. Can be one of:"text"(default): Convert broken links to plain text"bold": Convert broken links to bold text"ignore": Leave broken links unchanged
API
- Category: Transformer
- Function name:
Plugin.WikiLinkValidator().
Added files:
quartz/plugins/transformers/wikiLinkValidator.ts:
import { QuartzTransformerPlugin } from "../types"
import { Root as HastRoot } from "hast"
import { Root as MdastRoot } from "mdast"
import { visit } from "unist-util-visit"
import {
FullSlug,
RelativeURL,
TransformOptions,
simplifySlug,
transformLink,
stripSlashes,
} from "../../util/path"
import isAbsoluteUrl from "is-absolute-url"
// import { FrontMatter } from "./frontmatter"
interface Options {
/** How to display broken wikilinks: as plain text, bold text, or leave as-is */
brokenLinkFormat: "text" | "bold" | "ignore"
}
const defaultOptions: Options = {
brokenLinkFormat: "text",
}
interface FileMetadata {
frontmatter: any
slug: string
}
/**
* Validates internal wikilinks and converts broken/private links to plain text, bold text,
* or leaves them as-is based on configuration.
*/
export const WikiLinkValidator: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
// Shared cache across markdown and html phases
const fileMetadataCache = new Map<string, FileMetadata>()
return {
name: "WikiLinkValidator",
markdownPlugins(_ctx) {
return [
() => {
return (_tree: MdastRoot, file) => {
// Cache frontmatter from markdown phase so it's available in html phase
const slug = file.data.slug as string
if (slug && file.data.frontmatter) {
fileMetadataCache.set(slug, {
frontmatter: file.data.frontmatter,
slug,
})
}
}
},
]
},
htmlPlugins(ctx) {
return [
() => {
return (tree: HastRoot, file) => {
const nodesToReplace: Array<{
node: any
parent: any
index: number
text: string
}> = []
visit(tree, "element", (node, index, parent) => {
// Process all links
if (
node.tagName === "a" &&
node.properties &&
typeof node.properties.href === "string"
) {
const href = node.properties.href as RelativeURL
const isExternal = isAbsoluteUrl(href)
const isAnchor = href.startsWith("#")
// Only check internal links (not external URLs or anchors)
if (!isExternal && !isAnchor) {
const transformOptions: TransformOptions = {
strategy: "absolute",
allSlugs: ctx.allSlugs,
}
// Transform the link to get the canonical destination
const dest = transformLink(
file.data.slug! as FullSlug,
href,
transformOptions,
)
const url = new URL(
dest,
"https://base.com/" +
stripSlashes(simplifySlug(file.data.slug! as FullSlug), true),
)
let destCanonical = url.pathname
if (destCanonical.endsWith("/")) {
destCanonical += "index"
}
const full = decodeURIComponent(stripSlashes(destCanonical, true)) as FullSlug
// Check if target page exists in the list of all slugs
const targetExists = ctx.allSlugs.includes(full)
if (!targetExists) {
// Handle broken link
if (opts.brokenLinkFormat === "ignore") {
return
}
// Extract the link display text
const linkText = node.children
.filter((child): child is any => child.type === "text")
.map((child) => child.value)
.join("")
if (opts.brokenLinkFormat === "bold") {
node.tagName = "strong"
node.properties = {}
} else {
if (parent && index !== undefined) {
nodesToReplace.push({
node,
parent,
index,
text: linkText,
})
}
}
}
}
}
})
// Replace marked nodes in reverse order to avoid index shifting
for (let i = nodesToReplace.length - 1; i >= 0; i--) {
const { parent, index, text } = nodesToReplace[i]
if (parent && "children" in parent && parent.children[index]) {
parent.children[index] = {
type: "text",
value: text,
}
}
}
}
},
]
},
}
}Changed Files:
quartz/plugins/transformers/index.tsquartz.config.ts
quartz/plugins/transformers/index.ts: Just add the following line:
export { WikiLinkValidator } from "./wikiLinkValidator"quartz.config.ts: Add the following in the transformer plugins list:
Plugin.WikiLinkValidator({
brokenLinkFormat: "bold"
}),References
I was annoyed at how many 404 pages were being indexed by Google. Cause this would ruin my SEO. So I decided to make my own quartz plugin to fix this issue.