From 6ef6d837da5bb0dc05cc811d93ac9caba8300269 Mon Sep 17 00:00:00 2001 From: David Tobolik Date: Mon, 22 Dec 2025 12:20:53 +0100 Subject: [PATCH 1/2] feat: move broken wikilinks to CrawlLinks --- quartz/plugins/transformers/links.ts | 34 +++++++++++++++++++++++++++- quartz/plugins/transformers/ofm.ts | 16 +------------ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/quartz/plugins/transformers/links.ts b/quartz/plugins/transformers/links.ts index f4451d927..46958fa4d 100644 --- a/quartz/plugins/transformers/links.ts +++ b/quartz/plugins/transformers/links.ts @@ -17,6 +17,10 @@ import { Root } from "hast" interface Options { /** How to resolve Markdown paths */ markdownLinkResolution: TransformOptions["strategy"] + /** Check for broken wikilinks */ + checkBrokenWikilinks: boolean + /** What to do with broken wikilinks */ + onBrokenWikilink: "disable" | "remove" | "error" /** Strips folders from a link so that it looks nice */ prettyLinks: boolean openLinksInNewTab: boolean @@ -26,6 +30,8 @@ interface Options { const defaultOptions: Options = { markdownLinkResolution: "absolute", + checkBrokenWikilinks: true, + onBrokenWikilink: "disable", prettyLinks: true, openLinksInNewTab: false, lazyLoad: false, @@ -103,6 +109,7 @@ export const CrawlLinks: QuartzTransformerPlugin> = (userOpts) isAbsoluteUrl(dest, { httpOnly: false }) || dest.startsWith("#") ) if (isInternal) { + const preResolve = dest dest = node.properties.href = transformLink( file.data.slug!, dest, @@ -121,7 +128,32 @@ export const CrawlLinks: QuartzTransformerPlugin> = (userOpts) // need to decodeURIComponent here as WHATWG URL percent-encodes everything const full = decodeURIComponent(stripSlashes(destCanonical, true)) as FullSlug const simple = simplifySlug(full) - outgoing.add(simple) + + const allSimpleSlugs = ctx.allSlugs.map((slug) => simplifySlug(slug)) + if ( + opts.checkBrokenWikilinks && + !allSimpleSlugs.includes(simple) && + !preResolve.startsWith(".") && + !preResolve.startsWith("/") + ) { + const action = opts.onBrokenWikilink + if (action === "error") { + throw new Error( + `Broken wikilink found in ${file.data.slug}: ${preResolve} resolves to ${simple}`, + ) + } else if (action === "remove") { + // Remove the link but keep the text + if (_parent && _index !== undefined) { + _parent.children.splice(_index, 1, ...node.children) + } + } else { + // Add "broken" class + classes.push("broken") + node.properties.className = classes + } + } else { + outgoing.add(simple) + } node.properties["data-slug"] = full } diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 7a523aa59..f36bcb3cc 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -41,7 +41,6 @@ export interface Options { enableYouTubeEmbed: boolean enableVideoEmbed: boolean enableCheckbox: boolean - disableBrokenWikilinks: boolean } const defaultOptions: Options = { @@ -57,7 +56,6 @@ const defaultOptions: Options = { enableYouTubeEmbed: true, enableVideoEmbed: true, enableCheckbox: false, - disableBrokenWikilinks: false, } const calloutMapping = { @@ -208,7 +206,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> return src }, - markdownPlugins(ctx) { + markdownPlugins() { const plugins: PluggableList = [] // regex replacements @@ -277,18 +275,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> // otherwise, fall through to regular link } - // treat as broken link if slug not in ctx.allSlugs - if (opts.disableBrokenWikilinks) { - const slug = slugifyFilePath(fp as FilePath) - const exists = ctx.allSlugs && ctx.allSlugs.includes(slug) - if (!exists) { - return { - type: "html", - value: `${alias ?? fp}`, - } - } - } - // internal link const url = fp + anchor From 8f7c8b4a89d96f8eab00d144f4e4ddde5aeb29bc Mon Sep 17 00:00:00 2001 From: David Tobolik Date: Mon, 22 Dec 2025 12:54:33 +0100 Subject: [PATCH 2/2] docs: update plugin documentation for broken link changes --- docs/plugins/CrawlLinks.md | 5 +++++ docs/plugins/ObsidianFlavoredMarkdown.md | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/plugins/CrawlLinks.md b/docs/plugins/CrawlLinks.md index 47b7bdd77..1a44a4f1a 100644 --- a/docs/plugins/CrawlLinks.md +++ b/docs/plugins/CrawlLinks.md @@ -15,6 +15,11 @@ This plugin accepts the following configuration options: - `absolute`: Path relative to the root of the content folder. - `relative`: Path relative to the file you are linking from. - `shortest`: Name of the file. If this isn't enough to identify the file, use the full absolute path. +- `checkBrokenWikilinks`: Whether to check for broken wikilinks. Defaults to `true`. +- `onBrokenWikilink`: What to do with broken wikilinks. Can be `"disable"` (default), `"remove"`, or `"error"`. + - `disable`: Keep the link as is, but add the `broken` class. + - `remove`: Remove the link, leaving only the text. + - `error`: Throw an error and stop the build. - `prettyLinks`: If `true` (default), simplifies links by removing folder paths, making them more user friendly (e.g. `folder/deeply/nested/note` becomes `note`). - `openLinksInNewTab`: If `true`, configures external links to open in a new tab. Defaults to `false`. - `lazyLoad`: If `true`, adds lazy loading to resource elements (`img`, `video`, etc.) to improve page load performance. Defaults to `false`. diff --git a/docs/plugins/ObsidianFlavoredMarkdown.md b/docs/plugins/ObsidianFlavoredMarkdown.md index 277c7720b..414f743b8 100644 --- a/docs/plugins/ObsidianFlavoredMarkdown.md +++ b/docs/plugins/ObsidianFlavoredMarkdown.md @@ -23,7 +23,6 @@ This plugin accepts the following configuration options: - `enableYouTubeEmbed`: If `true` (default), enables the embedding of YouTube videos and playlists using external image Markdown syntax. - `enableVideoEmbed`: If `true` (default), enables the embedding of video files. - `enableCheckbox`: If `true`, adds support for interactive checkboxes in content. Defaults to `false`. -- `disableBrokenWikilinks`: If `true`, replaces links to non-existent notes with a dimmed, disabled link. Defaults to `false`. > [!warning] > Don't remove this plugin if you're using [[Obsidian compatibility|Obsidian]] to author the content!