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 RandomPageButton from "./_RandomPageButton"
|
||||||
import Ads from "./_Ads"
|
import Ads from "./_Ads"
|
||||||
import YandexMetrika from "./_YandexMetrika"
|
import YandexMetrika from "./_YandexMetrika"
|
||||||
|
import FrontmatterFields from "./_FrontmatterFields"
|
||||||
// import SocialShare from "./_SocialShare"
|
// import SocialShare from "./_SocialShare"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -63,5 +64,6 @@ export {
|
|||||||
RandomPageButton,
|
RandomPageButton,
|
||||||
Ads,
|
Ads,
|
||||||
YandexMetrika,
|
YandexMetrika,
|
||||||
|
FrontmatterFields,
|
||||||
// SocialShare
|
// 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