feat: add FrontmatterFields component
Some checks are pending
Docker build & push image / build (push) Waiting to run
continuous-integration/drone/tag Build is passing

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:
Struchkov Mark
2026-03-16 14:57:32 +03:00
parent d1a31de131
commit 6b58728e7c
3 changed files with 90 additions and 0 deletions

View 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

View File

@@ -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
}

View 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);
}