mirror of
https://gitlab.com/MisterBiggs/brain-quartz.git
synced 2025-07-22 14:21:24 +00:00
.github
content
docs
quartz
cli
components
pages
scripts
styles
ArticleTitle.tsx
Backlinks.tsx
Body.tsx
Breadcrumbs.tsx
Comments.tsx
ContentMeta.tsx
Darkmode.tsx
Date.tsx
DesktopOnly.tsx
Explorer.tsx
ExplorerNode.tsx
Footer.tsx
Graph.tsx
Head.tsx
Header.tsx
MobileOnly.tsx
PageList.tsx
PageTitle.tsx
RecentNotes.tsx
Search.tsx
Spacer.tsx
TableOfContents.tsx
TagList.tsx
index.ts
renderPage.tsx
types.ts
i18n
plugins
processors
static
styles
util
bootstrap-cli.mjs
bootstrap-worker.mjs
build.ts
cfg.ts
depgraph.test.ts
depgraph.ts
worker.ts
.gitattributes
.gitignore
.npmrc
.prettierignore
.prettierrc
CODE_OF_CONDUCT.md
Dockerfile
LICENSE.txt
README.md
globals.d.ts
index.d.ts
package-lock.json
package.json
quartz.config.ts
quartz.layout.ts
tsconfig.json
140 lines
4.0 KiB
TypeScript
140 lines
4.0 KiB
TypeScript
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 { classNames } from "../util/lang"
|
||
|
||
type CrumbData = {
|
||
displayName: string
|
||
path: string
|
||
}
|
||
|
||
interface BreadcrumbOptions {
|
||
/**
|
||
* Symbol between crumbs
|
||
*/
|
||
spacerSymbol: string
|
||
/**
|
||
* Name of first crumb
|
||
*/
|
||
rootName: string
|
||
/**
|
||
* 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.
|
||
*/
|
||
showCurrentPage: boolean
|
||
}
|
||
|
||
const defaultOptions: BreadcrumbOptions = {
|
||
spacerSymbol: "❯",
|
||
rootName: "Home",
|
||
resolveFrontmatterTitle: true,
|
||
hideOnRoot: true,
|
||
showCurrentPage: true,
|
||
}
|
||
|
||
function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData {
|
||
return {
|
||
displayName: displayName.replaceAll("-", " "),
|
||
path: resolveRelative(baseSlug, currentSlug),
|
||
}
|
||
}
|
||
|
||
export default ((opts?: Partial<BreadcrumbOptions>) => {
|
||
// Merge options with defaults
|
||
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
|
||
|
||
// computed index of folder name to its associated file data
|
||
let folderIndex: Map<string, QuartzPluginData> | undefined
|
||
|
||
const Breadcrumbs: QuartzComponent = ({
|
||
fileData,
|
||
allFiles,
|
||
displayClass,
|
||
}: QuartzComponentProps) => {
|
||
// Hide crumbs on root if enabled
|
||
if (options.hideOnRoot && fileData.slug === "index") {
|
||
return <></>
|
||
}
|
||
|
||
// 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 < 1
|
||
|
||
// Format and add current crumb
|
||
const crumb = formatCrumb(
|
||
curPathSegment,
|
||
fileData.slug!,
|
||
(currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug,
|
||
)
|
||
crumbs.push(crumb)
|
||
}
|
||
|
||
// Add current file to crumb (can directly use frontmatter title)
|
||
if (options.showCurrentPage && slugParts.at(-1) !== "index") {
|
||
crumbs.push({
|
||
displayName: fileData.frontmatter!.title,
|
||
path: "",
|
||
})
|
||
}
|
||
}
|
||
|
||
return (
|
||
<nav class={classNames(displayClass, "breadcrumb-container")} aria-label="breadcrumbs">
|
||
{crumbs.map((crumb, index) => (
|
||
<div class="breadcrumb-element">
|
||
<a href={crumb.path}>{crumb.displayName}</a>
|
||
{index !== crumbs.length - 1 && <p>{` ${options.spacerSymbol} `}</p>}
|
||
</div>
|
||
))}
|
||
</nav>
|
||
)
|
||
}
|
||
Breadcrumbs.css = breadcrumbsStyle
|
||
|
||
return Breadcrumbs
|
||
}) satisfies QuartzComponentConstructor
|