From 198f9e79a763e3afb8c98c982db5b2b178d6d364 Mon Sep 17 00:00:00 2001 From: Amir Pourmand Date: Wed, 10 Sep 2025 00:30:57 +0330 Subject: [PATCH] feat(contentPage): add dead link detection for missing pages This update introduces functionality to identify and mark links to non-existent pages as "dead links" within the content page emitter. The implementation utilizes the `unist-util-visit` library to traverse the document tree and modify link elements accordingly. Links that do not correspond to existing slugs are converted to spans with a "dead-link" class for better user feedback. --- quartz/plugins/emitters/contentPage.tsx | 45 ++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index c3410ecc3..dfac4a7ec 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -5,15 +5,18 @@ import HeaderConstructor from "../../components/Header" import BodyConstructor from "../../components/Body" import { pageResources, renderPage } from "../../components/renderPage" import { FullPageLayout } from "../../cfg" -import { pathToRoot } from "../../util/path" +import { pathToRoot, resolveRelative } from "../../util/path" import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout" import { Content } from "../../components" import { styleText } from "util" import { write } from "./helpers" import { BuildCtx } from "../../util/ctx" import { Node } from "unist" +import { Root } from "hast" import { StaticResources } from "../../util/resources" import { QuartzPluginData } from "../vfile" +import { visit } from "unist-util-visit" +import { RelativeURL } from "../../util/path" async function processContent( ctx: BuildCtx, @@ -25,6 +28,46 @@ async function processContent( ) { const slug = fileData.slug! const cfg = ctx.cfg.configuration + + /** Until the end of visit(), this code snippet is from + * https://github.com/jackyzha0/quartz/issues/454#issuecomment-2408792538 + * by auctumnus + * + * It removes all the links that would lead to missing pages, ie. + * + * [[Missing link]] when Missing link.md does not exist. + */ + const allSlugs = allFiles.map((f) => (f.slug ? resolveRelative(slug, f.slug) : "")) + + visit(tree as Root, "element", (elem) => { + if (elem.tagName === "a" && elem.properties.href) { + const href = elem.properties.href.toString() + + if (href.startsWith("#")) { + return + } + + if (!allSlugs.includes(href as RelativeURL)) { + if (elem.properties.className === undefined) { + elem.properties.className = "dead-link" + } else if (Array.isArray(elem.properties.className)) { + if (elem.properties.className.includes("external")) { + return + } + elem.properties.className.push("dead-link") + } else if (typeof elem.properties.className === "string") { + if (elem.properties.className.includes("external")) { + return + } + elem.properties.className += " dead-link" + } else { + return + } + elem.tagName = "span" + } + } + }) + const externalResources = pageResources(pathToRoot(slug), resources) const componentData: QuartzComponentProps = { ctx,