feat: add FrontmatterFields component
Configurable component that displays frontmatter fields (area, parent, etc.) with auto-detection of markdown links and published page resolution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
quartz/components/_FrontmatterFields.tsx
Normal file
70
quartz/components/_FrontmatterFields.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
import { resolveRelative } from "../util/path"
|
||||
import { JSX } from "preact"
|
||||
// @ts-ignore
|
||||
import style from "./styles/_frontmatterFields.scss"
|
||||
|
||||
interface FieldConfig {
|
||||
/** frontmatter key */
|
||||
key: string
|
||||
/** display label */
|
||||
label: string
|
||||
}
|
||||
|
||||
interface FrontmatterFieldsOptions {
|
||||
fields: FieldConfig[]
|
||||
}
|
||||
|
||||
/** Parse markdown link `[Title](<File.md>)` or `[Title](File.md)` */
|
||||
function parseMdLink(value: string): { title: string; filename: string } | null {
|
||||
const match = value.match(/^\[([^\]]+)\]\(<?([^>)]+)>?\)$/)
|
||||
if (!match) return null
|
||||
return { title: match[1], filename: match[2].replace(/\.md$/, "") }
|
||||
}
|
||||
|
||||
export default ((opts: Partial<FrontmatterFieldsOptions>) => {
|
||||
const fields = opts?.fields ?? []
|
||||
|
||||
function FrontmatterFields({ fileData, allFiles, displayClass }: QuartzComponentProps) {
|
||||
const fm = fileData.frontmatter
|
||||
if (!fm || fields.length === 0) return null
|
||||
|
||||
const titleToSlug = new Map(
|
||||
allFiles
|
||||
.filter((f) => f.frontmatter?.title && f.slug)
|
||||
.map((f) => [f.frontmatter!.title as string, f.slug!]),
|
||||
)
|
||||
|
||||
const items: JSX.Element[] = []
|
||||
for (const field of fields) {
|
||||
const raw = fm[field.key]
|
||||
if (!raw || (typeof raw === "string" && raw.trim() === "")) continue
|
||||
|
||||
const value = Array.isArray(raw) ? raw.join(", ") : String(raw)
|
||||
const parsed = parseMdLink(value)
|
||||
const displayText = parsed ? parsed.title : value
|
||||
const targetSlug = parsed ? titleToSlug.get(parsed.title) : undefined
|
||||
|
||||
items.push(
|
||||
<div class="frontmatter-field">
|
||||
<span class="frontmatter-field-label">{field.label}:</span>{" "}
|
||||
{targetSlug ? (
|
||||
<a href={resolveRelative(fileData.slug!, targetSlug)} class="internal">
|
||||
{displayText}
|
||||
</a>
|
||||
) : (
|
||||
<span class="frontmatter-field-value">{displayText}</span>
|
||||
)}
|
||||
</div>,
|
||||
)
|
||||
}
|
||||
|
||||
if (items.length === 0) return null
|
||||
|
||||
return <div class={`frontmatter-fields ${displayClass ?? ""}`}>{items}</div>
|
||||
}
|
||||
|
||||
FrontmatterFields.css = style
|
||||
|
||||
return FrontmatterFields
|
||||
}) satisfies QuartzComponentConstructor
|
||||
@@ -29,6 +29,7 @@ import GithubSource from "./_GithubSource"
|
||||
import RandomPageButton from "./_RandomPageButton"
|
||||
import Ads from "./_Ads"
|
||||
import YandexMetrika from "./_YandexMetrika"
|
||||
import FrontmatterFields from "./_FrontmatterFields"
|
||||
// import SocialShare from "./_SocialShare"
|
||||
|
||||
export {
|
||||
@@ -63,5 +64,6 @@ export {
|
||||
RandomPageButton,
|
||||
Ads,
|
||||
YandexMetrika,
|
||||
FrontmatterFields,
|
||||
// SocialShare
|
||||
}
|
||||
|
||||
18
quartz/components/styles/_frontmatterFields.scss
Normal file
18
quartz/components/styles/_frontmatterFields.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
.frontmatter-fields {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--darkgray);
|
||||
}
|
||||
|
||||
.frontmatter-field {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.frontmatter-field-label {
|
||||
font-weight: 600;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.frontmatter-field-value {
|
||||
color: var(--dark);
|
||||
}
|
||||
Reference in New Issue
Block a user