fix(build): remove extension-stripped slug aliases that pollute allSlugs
addVirtualPageSlugAliases injected extension-stripped aliases for every PageType-registered extension into ctx.allSlugs (e.g. for `Canvas.canvas` it added an alias `canvas`). Intended to help `![[file.canvas]]` transclusions resolve to virtual pages, but: 1. CrawlLinks' shortest strategy treats allSlugs entries as real files. When an author writes `[[Canvas]]` in a note, shortest lookup matches BOTH the alias `canvas` and real basenames like `features/canvas`. With matchingFileNames.length === 2, the uniqueness check fails and transformLink falls back to absolute resolution, producing a dead href `./canvas` that points to no emitted file. 2. The aliases were never actually needed for transclusion. renderPage.tsx already has its own extension-stripping fallback (lines 107-119) that consults allFiles directly. Virtual pages (canvas-page, bases-page) emit at slugs that KEEP their extension (e.g. `canvas.canvas`), so `![[Canvas.canvas]]` resolves naturally without any alias. 3. The aliases mispresent Obsidian semantics. In Obsidian, `[[Canvas]]` resolves to `Canvas.md`; non-md files require the explicit extension (`[[Canvas.canvas]]`). Injecting extension-stripped aliases into allSlugs made Quartz claim bare names can refer to non-md virtual pages, which is wrong. Reproduction: docs/features/index.md contains `[[Canvas]]` which should resolve to docs/features/Canvas.md (slug features/canvas). Under the old behavior it produced href="../canvas" with data-slug="canvas" pointing nowhere. After this fix it produces href="../features/canvas" with data-slug="features/canvas" correctly. Verified with fresh `npx quartz build -d docs`: - [[Canvas]] → ./features/canvas ✓ - ![[Canvas.canvas]] transclusion still works ✓ - ![[Base.base]] virtual page still emits ✓ - 0 internal broken links across all output ✓ - all 91 existing tests pass
This commit is contained in:
@@ -9,7 +9,7 @@ import { parseMarkdown } from "./processors/parse"
|
||||
import { filterContent } from "./processors/filter"
|
||||
import { emitContent } from "./processors/emit"
|
||||
import cfg from "../quartz"
|
||||
import { FilePath, FullSlug, joinSegments, slugifyFilePath } from "./util/path"
|
||||
import { FilePath, joinSegments, slugifyFilePath } from "./util/path"
|
||||
import { detectSlugCollisions, formatCollisionWarning } from "./util/slugCollisions"
|
||||
import chokidar from "chokidar"
|
||||
import { ProcessedContent } from "./plugins/vfile"
|
||||
@@ -20,7 +20,7 @@ import { options } from "./util/sourcemap"
|
||||
import { Mutex } from "async-mutex"
|
||||
import { getStaticResourcesFromPlugins } from "./plugins"
|
||||
import { randomIdNonSecure } from "./util/random"
|
||||
import { ChangeEvent, QuartzPageTypePluginInstance } from "./plugins/types"
|
||||
import { ChangeEvent } from "./plugins/types"
|
||||
import { minimatch } from "minimatch"
|
||||
|
||||
function reportSlugCollisions(content: ProcessedContent[]): void {
|
||||
@@ -29,37 +29,6 @@ function reportSlugCollisions(content: ProcessedContent[]): void {
|
||||
console.warn(styleText("yellow", formatCollisionWarning(collisions)))
|
||||
}
|
||||
|
||||
function getPageTypeExtensions(ctx: BuildCtx): Set<string> {
|
||||
const extensions = new Set<string>()
|
||||
const pageTypes = (ctx.cfg.plugins.pageTypes ?? []) as unknown as QuartzPageTypePluginInstance[]
|
||||
for (const pt of pageTypes) {
|
||||
if (pt.fileExtensions) {
|
||||
for (const ext of pt.fileExtensions) {
|
||||
extensions.add(ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
return extensions
|
||||
}
|
||||
|
||||
// For files whose extensions are handled by PageType plugins (e.g. .canvas, .base),
|
||||
// add extension-stripped slug aliases so that wikilink resolution (CrawlLinks) maps
|
||||
// `![[file.canvas]]` to the virtual-page slug `file` instead of the raw `file.canvas`.
|
||||
function addVirtualPageSlugAliases(allSlugs: FullSlug[], extensions: Set<string>): FullSlug[] {
|
||||
const extra: FullSlug[] = []
|
||||
for (const slug of allSlugs) {
|
||||
for (const ext of extensions) {
|
||||
if (slug.endsWith(ext)) {
|
||||
const stripped = slug.slice(0, -ext.length) as FullSlug
|
||||
if (!allSlugs.includes(stripped) && !extra.includes(stripped)) {
|
||||
extra.push(stripped)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return extra
|
||||
}
|
||||
|
||||
type ContentMap = Map<
|
||||
FilePath,
|
||||
| {
|
||||
@@ -121,14 +90,6 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
|
||||
ctx.allFiles = allFiles
|
||||
ctx.allSlugs = allFiles.map((fp) => slugifyFilePath(fp as FilePath))
|
||||
|
||||
// Add extension-stripped slug aliases for PageType-registered extensions
|
||||
// so that wikilinks like ![[file.canvas]] resolve to virtual page slugs
|
||||
const ptExtensions = getPageTypeExtensions(ctx)
|
||||
if (ptExtensions.size > 0) {
|
||||
const aliases = addVirtualPageSlugAliases(ctx.allSlugs, ptExtensions)
|
||||
ctx.allSlugs.push(...aliases)
|
||||
}
|
||||
|
||||
const parsedFiles = await parseMarkdown(ctx, filePaths)
|
||||
reportSlugCollisions(parsedFiles)
|
||||
const filteredContent = filterContent(ctx, parsedFiles)
|
||||
@@ -305,12 +266,6 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD
|
||||
ctx.allFiles = Array.from(contentMap.keys())
|
||||
ctx.allSlugs = ctx.allFiles.map((fp) => slugifyFilePath(fp as FilePath))
|
||||
|
||||
// Add extension-stripped slug aliases for PageType-registered extensions
|
||||
const ptExtensions = getPageTypeExtensions(ctx)
|
||||
if (ptExtensions.size > 0) {
|
||||
const aliases = addVirtualPageSlugAliases(ctx.allSlugs, ptExtensions)
|
||||
ctx.allSlugs.push(...aliases)
|
||||
}
|
||||
const markdownContent = Array.from(contentMap.values())
|
||||
.filter((file) => file.type === "markdown")
|
||||
.map((file) => file.content)
|
||||
|
||||
Reference in New Issue
Block a user