mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-02-03 22:15:42 -06:00
feat: Added ability to configure frontmatter parent key
This commit is contained in:
parent
5432777c51
commit
918286b95f
@ -33,3 +33,59 @@ Want to customize it even more?
|
||||
- Component: `quartz/components/Breadcrumbs.tsx`
|
||||
- Style: `quartz/components/styles/breadcrumbs.scss`
|
||||
- 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.
|
||||
|
||||
@ -8,16 +8,19 @@ 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,
|
||||
@ -34,41 +37,61 @@ export default ((opts?: ParentBreadcrumbsOptions) => {
|
||||
|
||||
const findFile = (name: string) => {
|
||||
const targetSlug = simplifySlug(name as FullSlug);
|
||||
|
||||
return allFiles.find((f: QuartzPluginData) => {
|
||||
const fSlug = simplifySlug(f.slug!);
|
||||
return fSlug === targetSlug || fSlug.endsWith(targetSlug) || f.frontmatter?.title === name;
|
||||
});
|
||||
};
|
||||
|
||||
const crumbs: Array<{ displayName: string; path: string; }> = [];
|
||||
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?.parent) {
|
||||
const parentLink = parseWikiLink(current.frontmatter.parent as string);
|
||||
const parentFile = findFile(parentLink);
|
||||
while (current && current.frontmatter?.[parentKey!]) {
|
||||
const rawParent = current.frontmatter[parentKey!];
|
||||
const parentList = Array.isArray(rawParent) ? rawParent : [rawParent];
|
||||
|
||||
if (parentFile && parentFile.slug && !visited.has(parentFile.slug)) {
|
||||
visited.add(parentFile.slug);
|
||||
crumbs.push({
|
||||
displayName: options.resolveFrontmatterTitle
|
||||
? parentFile.frontmatter?.title ?? parentFile.slug
|
||||
: parentFile.slug,
|
||||
path: resolveRelative(fileData.slug!, parentFile.slug!)
|
||||
});
|
||||
current = parentFile;
|
||||
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({
|
||||
crumbs.push([{
|
||||
displayName: options.rootName!,
|
||||
path: resolveRelative(fileData.slug!, "index" as SimpleSlug)
|
||||
});
|
||||
}]);
|
||||
}
|
||||
|
||||
crumbs.reverse();
|
||||
@ -79,10 +102,15 @@ export default ((opts?: ParentBreadcrumbsOptions) => {
|
||||
|
||||
return (
|
||||
<nav class={classNames(displayClass, "breadcrumb-container")} aria-label="breadcrumbs">
|
||||
{crumbs.map((crumb, index) => (
|
||||
{crumbs.map((crumbLevel, levelIndex) => (
|
||||
<div class="breadcrumb-element">
|
||||
<a href={crumb.path}>{crumb.displayName}</a>
|
||||
{index !== crumbs.length && <p>{options.spacerSymbol}</p>}
|
||||
{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">
|
||||
@ -94,4 +122,4 @@ export default ((opts?: ParentBreadcrumbsOptions) => {
|
||||
|
||||
ParentBreadcrumbs.css = style;
|
||||
return ParentBreadcrumbs;
|
||||
}) satisfies QuartzComponentConstructor
|
||||
}) satisfies QuartzComponentConstructor;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user