mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-02-03 22:15:42 -06:00
Merge 466994f698 into ec00a40aef
This commit is contained in:
commit
73e37a3de1
@ -33,3 +33,59 @@ Want to customize it even more?
|
|||||||
- Component: `quartz/components/Breadcrumbs.tsx`
|
- Component: `quartz/components/Breadcrumbs.tsx`
|
||||||
- Style: `quartz/components/styles/breadcrumbs.scss`
|
- Style: `quartz/components/styles/breadcrumbs.scss`
|
||||||
- Script: inline at `quartz/components/Breadcrumbs.tsx`
|
- Script: inline at `quartz/components/Breadcrumbs.tsx`
|
||||||
|
|
||||||
|
## Using A Frontmatter Prop
|
||||||
|
|
||||||
|
ParentBreadcrumbs` is an alternative breadcrumbs component that derives its hierarchy **entirely from frontmatter-defined parent relationships**, rather than folder structure. This is useful for knowledge-base–style sites, wikis, or any content where pages may belong to multiple logical hierarchies.
|
||||||
|
|
||||||
|
Unlike the default `Breadcrumbs` component, `ParentBreadcrumbs` supports:
|
||||||
|
|
||||||
|
- Explicit parent chains via frontmatter
|
||||||
|
- Multiple parents per level
|
||||||
|
- Wiki-style links (`[[Page Name]]`)
|
||||||
|
- Customizable frontmatter keys
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
`ParentBreadcrumbs` walks upward through a parent chain starting from the current page, following a configurable frontmatter field.
|
||||||
|
At each level, **all parents are rendered**, while one unvisited parent is chosen to continue the chain upward.
|
||||||
|
|
||||||
|
Example frontmatter:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
title: "Advanced Topics"
|
||||||
|
parent: Basics
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
Wiki links are supported:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parent: [[Basics]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple parents:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parent:
|
||||||
|
- [[Basics]]
|
||||||
|
- [[Reference]]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
You can configure `ParentBreadcrumbs` by passing options into `Component.ParentBreadcrumbs()`.
|
||||||
|
|
||||||
|
Default configuration:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
Component.ParentBreadcrumbs({
|
||||||
|
spacerSymbol: "❯", // symbol displayed between breadcrumb levels
|
||||||
|
rootName: "Home", // label for the root (index) page
|
||||||
|
resolveFrontmatterTitle: true, // use frontmatter.title instead of slug
|
||||||
|
parentKey: "parent", // frontmatter key used to resolve parents
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
All options are optional; omitted values fall back to the defaults.
|
||||||
|
|||||||
@ -21,3 +21,4 @@ Want to see what Quartz can do? Here are some cool community gardens:
|
|||||||
- [Ellie's Notes](https://ellie.wtf)
|
- [Ellie's Notes](https://ellie.wtf)
|
||||||
- [Eledah's Crystalline](https://blog.eledah.ir/)
|
- [Eledah's Crystalline](https://blog.eledah.ir/)
|
||||||
- [🌓 Projects & Privacy - FOSS, tech, law](https://be-far.com)
|
- [🌓 Projects & Privacy - FOSS, tech, law](https://be-far.com)
|
||||||
|
- [🌲Stefan Genov's Garden](https://garden.sgenov.dev)
|
||||||
|
|||||||
130
quartz/components/ParentBreadcrumbs.tsx
Normal file
130
quartz/components/ParentBreadcrumbs.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
import { classNames } from "../util/lang"
|
||||||
|
import { resolveRelative, simplifySlug, FullSlug, SimpleSlug } from "../util/path"
|
||||||
|
import style from "./styles/breadcrumbs.scss"
|
||||||
|
|
||||||
|
interface ParentBreadcrumbsOptions {
|
||||||
|
spacerSymbol?: string
|
||||||
|
rootName?: string
|
||||||
|
resolveFrontmatterTitle?: boolean
|
||||||
|
frontmatterProp?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: ParentBreadcrumbsOptions = {
|
||||||
|
spacerSymbol: "❯",
|
||||||
|
rootName: "Home",
|
||||||
|
resolveFrontmatterTitle: true,
|
||||||
|
frontmatterProp: "parent",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ((opts?: ParentBreadcrumbsOptions) => {
|
||||||
|
const options = { ...defaultOptions, ...opts }
|
||||||
|
const parentKey = options.frontmatterProp
|
||||||
|
|
||||||
|
const ParentBreadcrumbs: QuartzComponent = ({
|
||||||
|
fileData,
|
||||||
|
allFiles,
|
||||||
|
displayClass,
|
||||||
|
}: QuartzComponentProps) => {
|
||||||
|
const parseWikiLink = (content: string): string => {
|
||||||
|
if (!content) return ""
|
||||||
|
let clean = content.trim().replace(/^["']|["']$/g, "")
|
||||||
|
clean = clean.replace(/^\[\[|\]\]$/g, "")
|
||||||
|
return clean.split("|")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const findFile = (name: string) => {
|
||||||
|
const targetSlug = simplifySlug(name as FullSlug)
|
||||||
|
return allFiles.find((f: QuartzPluginData) => {
|
||||||
|
const fSlug = simplifySlug(f.slug!)
|
||||||
|
return (
|
||||||
|
fSlug === targetSlug ||
|
||||||
|
fSlug.normalize() == targetSlug.normalize() ||
|
||||||
|
f.frontmatter?.title === name
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type BreadcrumbNode = { displayName: string; path: string }
|
||||||
|
const crumbs: Array<BreadcrumbNode[]> = []
|
||||||
|
|
||||||
|
let current = fileData
|
||||||
|
const visited = new Set<string>()
|
||||||
|
if (current.slug) visited.add(current.slug)
|
||||||
|
|
||||||
|
while (current && current.frontmatter?.[parentKey!]) {
|
||||||
|
const rawParent = current.frontmatter[parentKey!]
|
||||||
|
const parentList = Array.isArray(rawParent) ? rawParent : [rawParent]
|
||||||
|
|
||||||
|
const currentLevelNodes: BreadcrumbNode[] = []
|
||||||
|
let nextParent: QuartzPluginData | undefined = undefined
|
||||||
|
|
||||||
|
for (const p of parentList) {
|
||||||
|
const linkStr = parseWikiLink(p as string)
|
||||||
|
const parentFile = findFile(linkStr)
|
||||||
|
|
||||||
|
if (parentFile && parentFile.slug) {
|
||||||
|
currentLevelNodes.push({
|
||||||
|
displayName: options.resolveFrontmatterTitle
|
||||||
|
? (parentFile.frontmatter?.title ?? parentFile.slug)
|
||||||
|
: parentFile.slug,
|
||||||
|
path: resolveRelative(fileData.slug!, parentFile.slug!),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!nextParent && !visited.has(parentFile.slug)) {
|
||||||
|
nextParent = parentFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLevelNodes.length > 0) {
|
||||||
|
crumbs.push(currentLevelNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextParent) {
|
||||||
|
visited.add(nextParent.slug!)
|
||||||
|
current = nextParent
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.slug !== "index") {
|
||||||
|
crumbs.push([
|
||||||
|
{
|
||||||
|
displayName: options.rootName!,
|
||||||
|
path: resolveRelative(fileData.slug!, "index" as SimpleSlug),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
crumbs.reverse()
|
||||||
|
|
||||||
|
if (crumbs.length === 0 && fileData.slug === "index") {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav class={classNames(displayClass, "breadcrumb-container")} aria-label="breadcrumbs">
|
||||||
|
{crumbs.map((crumbLevel, levelIndex) => (
|
||||||
|
<div class="breadcrumb-element">
|
||||||
|
{crumbLevel.map((node, nodeIndex) => (
|
||||||
|
<>
|
||||||
|
<a href={node.path}>{node.displayName}</a>
|
||||||
|
{nodeIndex < crumbLevel.length - 1 && <span style={{ opacity: 0.5 }}> / </span>}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
{levelIndex !== crumbs.length && <p>{options.spacerSymbol}</p>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div class="breadcrumb-element">
|
||||||
|
<p>{fileData.frontmatter?.title}</p>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ParentBreadcrumbs.css = style
|
||||||
|
return ParentBreadcrumbs
|
||||||
|
}) satisfies QuartzComponentConstructor
|
||||||
@ -23,8 +23,10 @@ import Breadcrumbs from "./Breadcrumbs"
|
|||||||
import Comments from "./Comments"
|
import Comments from "./Comments"
|
||||||
import Flex from "./Flex"
|
import Flex from "./Flex"
|
||||||
import ConditionalRender from "./ConditionalRender"
|
import ConditionalRender from "./ConditionalRender"
|
||||||
|
import ParentBreadcrumbs from "./ParentBreadcrumbs"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
ParentBreadcrumbs,
|
||||||
ArticleTitle,
|
ArticleTitle,
|
||||||
Content,
|
Content,
|
||||||
TagContent,
|
TagContent,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user