diff --git a/quartz/build.ts b/quartz/build.ts index 7cf4405..032063f 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -21,6 +21,7 @@ import { getStaticResourcesFromPlugins } from "./plugins" import { randomIdNonSecure } from "./util/random" import { ChangeEvent } from "./plugins/types" import { minimatch } from "minimatch" +import { FileTrieNode } from "./util/fileTrie" type ContentMap = Map< FilePath, diff --git a/quartz/components/Breadcrumbs.tsx b/quartz/components/Breadcrumbs.tsx index d0faeab..5144a31 100644 --- a/quartz/components/Breadcrumbs.tsx +++ b/quartz/components/Breadcrumbs.tsx @@ -1,8 +1,8 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" import breadcrumbsStyle from "./styles/breadcrumbs.scss" -import { FullSlug, SimpleSlug, joinSegments, resolveRelative } from "../util/path" -import { QuartzPluginData } from "../plugins/vfile" +import { FullSlug, SimpleSlug, resolveRelative, simplifySlug } from "../util/path" import { classNames } from "../util/lang" +import { trieFromAllFiles } from "../util/ctx" type CrumbData = { displayName: string @@ -22,10 +22,6 @@ interface BreadcrumbOptions { * Whether to look up frontmatter title for folders (could cause performance problems with big vaults) */ resolveFrontmatterTitle: boolean - /** - * Whether to display breadcrumbs on root `index.md` - */ - hideOnRoot: boolean /** * Whether to display the current page in the breadcrumbs. */ @@ -36,7 +32,6 @@ const defaultOptions: BreadcrumbOptions = { spacerSymbol: "❯", rootName: "Home", resolveFrontmatterTitle: true, - hideOnRoot: true, showCurrentPage: true, } @@ -48,78 +43,37 @@ function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: Simpl } export default ((opts?: Partial) => { - // Merge options with defaults const options: BreadcrumbOptions = { ...defaultOptions, ...opts } - - // computed index of folder name to its associated file data - let folderIndex: Map | undefined - const Breadcrumbs: QuartzComponent = ({ fileData, allFiles, displayClass, + ctx, }: QuartzComponentProps) => { - // Hide crumbs on root if enabled - if (options.hideOnRoot && fileData.slug === "index") { - return <> + const trie = (ctx.trie ??= trieFromAllFiles(allFiles)) + const slugParts = fileData.slug!.split("/") + const pathNodes = trie.ancestryChain(slugParts) + + if (!pathNodes) { + return null } - // Format entry for root element - const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug) - const crumbs: CrumbData[] = [firstEntry] - - if (!folderIndex && options.resolveFrontmatterTitle) { - folderIndex = new Map() - // construct the index for the first time - for (const file of allFiles) { - const folderParts = file.slug?.split("/") - if (folderParts?.at(-1) === "index") { - folderIndex.set(folderParts.slice(0, -1).join("/"), file) - } - } - } - - // Split slug into hierarchy/parts - const slugParts = fileData.slug?.split("/") - if (slugParts) { - // is tag breadcrumb? - const isTagPath = slugParts[0] === "tags" - - // full path until current part - let currentPath = "" - - for (let i = 0; i < slugParts.length - 1; i++) { - let curPathSegment = slugParts[i] - - // Try to resolve frontmatter folder title - const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/")) - if (currentFile) { - const title = currentFile.frontmatter!.title - if (title !== "index") { - curPathSegment = title - } - } - - // Add current slug to full path - currentPath = joinSegments(currentPath, slugParts[i]) - const includeTrailingSlash = !isTagPath || i < slugParts.length - 1 - - // Format and add current crumb - const crumb = formatCrumb( - curPathSegment, - fileData.slug!, - (currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug, - ) - crumbs.push(crumb) + const crumbs: CrumbData[] = pathNodes.map((node, idx) => { + const crumb = formatCrumb(node.displayName, fileData.slug!, simplifySlug(node.slug)) + if (idx === 0) { + crumb.displayName = options.rootName } - // Add current file to crumb (can directly use frontmatter title) - if (options.showCurrentPage && slugParts.at(-1) !== "index") { - crumbs.push({ - displayName: fileData.frontmatter!.title, - path: "", - }) + // For last node (current page), set empty path + if (idx === pathNodes.length - 1) { + crumb.path = "" } + + return crumb + }) + + if (!options.showCurrentPage) { + crumbs.pop() } return ( diff --git a/quartz/components/TagList.tsx b/quartz/components/TagList.tsx index 4a89fbd..c73ed39 100644 --- a/quartz/components/TagList.tsx +++ b/quartz/components/TagList.tsx @@ -1,15 +1,14 @@ -import { pathToRoot, slugTag } from "../util/path" +import { FullSlug, resolveRelative } from "../util/path" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" import { classNames } from "../util/lang" const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => { const tags = fileData.frontmatter?.tags - const baseDir = pathToRoot(fileData.slug!) if (tags && tags.length > 0) { return (