generic quartz component for layout

This commit is contained in:
Jacky Zhao 2023-06-07 22:27:32 -07:00
parent dde36fa558
commit 317cce9314
11 changed files with 77 additions and 58 deletions

View File

@ -1,7 +1,9 @@
import { QuartzConfig } from "./quartz/cfg" import { QuartzConfig } from "./quartz/cfg"
import Body from "./quartz/components/Body" import Body from "./quartz/components/Body"
import Darkmode from "./quartz/components/Darkmode"
import Head from "./quartz/components/Head" import Head from "./quartz/components/Head"
import Header from "./quartz/components/Header" import PageTitle from "./quartz/components/PageTitle"
import Spacer from "./quartz/components/Spacer"
import { import {
ContentPage, ContentPage,
CreatedModifiedDate, CreatedModifiedDate,
@ -68,9 +70,9 @@ const config: QuartzConfig = {
], ],
emitters: [ emitters: [
new ContentPage({ new ContentPage({
Head: Head, head: Head,
Header: Header, header: [PageTitle, Spacer, Darkmode],
Body: Body body: Body
}) })
] ]
}, },

View File

@ -1,15 +1,14 @@
import { ComponentChildren } from "preact"
import clipboardScript from './scripts/clipboard.inline' import clipboardScript from './scripts/clipboard.inline'
import clipboardStyle from './styles/clipboard.scss' import clipboardStyle from './styles/clipboard.scss'
import { QuartzComponentProps } from "./types"
export interface BodyProps { export default function Body({ fileData, children }: QuartzComponentProps) {
title?: string const title = fileData.frontmatter?.title
children: ComponentChildren const displayTitle = fileData.slug === "index" ? undefined : title
}
export default function Body({ title, children }: BodyProps) {
return <article> return <article>
{title && <h1>{title}</h1>} <div class="top-section">
{displayTitle && <h1>{displayTitle}</h1>}
</div>
{children} {children}
</article> </article>
} }

View File

@ -1,18 +1,15 @@
import { resolveToRoot } from "../path" import { resolveToRoot } from "../path"
import { StaticResources } from "../resources" import { QuartzComponentProps } from "./types"
export interface HeadProps { export default function Head({ fileData, externalResources }: QuartzComponentProps) {
title: string const slug = fileData.slug!
description: string const title = fileData.frontmatter?.title ?? "Untitled"
slug: string const description = fileData.description ?? "No description provided"
externalResources: StaticResources
}
export default function Head({ title, description, slug, externalResources }: HeadProps) {
const { css, js } = externalResources const { css, js } = externalResources
const baseDir = resolveToRoot(slug) const baseDir = resolveToRoot(slug)
const iconPath = baseDir + "/static/icon.png" const iconPath = baseDir + "/static/icon.png"
const ogImagePath = baseDir + "/static/og-image.png" const ogImagePath = baseDir + "/static/og-image.png"
return <head> return <head>
<title>{title}</title> <title>{title}</title>
<meta charSet="utf-8" /> <meta charSet="utf-8" />

View File

@ -1,20 +1,10 @@
import { resolveToRoot } from "../path"
import Darkmode from "./Darkmode"
import style from './styles/header.scss' import style from './styles/header.scss'
import { QuartzComponentProps } from "./types"
export interface HeaderProps { export default function Header({ children }: QuartzComponentProps) {
title: string
slug: string
}
export default function Header({ title, slug }: HeaderProps) {
const baseDir = resolveToRoot(slug)
return <header> return <header>
<h1><a href={baseDir}>{title}</a></h1> {children}
<div class="spacer"></div>
<Darkmode />
</header> </header>
} }
Header.beforeDOMLoaded = Darkmode.beforeDOMLoaded Header.css = style
Header.css = style + Darkmode.css

View File

@ -0,0 +1,9 @@
import { resolveToRoot } from "../path"
import { QuartzComponentProps } from "./types"
export default function({ cfg, fileData }: QuartzComponentProps) {
const title = cfg.siteTitle
const slug = fileData.slug!
const baseDir = resolveToRoot(slug)
return <h1><a href={baseDir}>{title}</a></h1>
}

View File

@ -0,0 +1,3 @@
export default function() {
return <div class="spacer"></div>
}

View File

@ -1,6 +1,16 @@
import { ComponentType } from "preact" import { ComponentType, JSX } from "preact"
import { StaticResources } from "../resources"
import { QuartzPluginData } from "../plugins/vfile"
import { GlobalConfiguration } from "../cfg"
export type QuartzComponent<Props> = ComponentType<Props> & { export type QuartzComponentProps = {
externalResources: StaticResources
fileData: QuartzPluginData
cfg: GlobalConfiguration
children: QuartzComponent[] | JSX.Element[]
}
export type QuartzComponent = ComponentType<QuartzComponentProps> & {
css?: string, css?: string,
beforeDOMLoaded?: string, beforeDOMLoaded?: string,
afterDOMLoaded?: string, afterDOMLoaded?: string,

View File

@ -4,17 +4,16 @@ import { EmitCallback, QuartzEmitterPlugin } from "../types"
import { ProcessedContent } from "../vfile" import { ProcessedContent } from "../vfile"
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime' import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
import { render } from "preact-render-to-string" import { render } from "preact-render-to-string"
import { HeadProps } from "../../components/Head"
import { GlobalConfiguration } from "../../cfg" import { GlobalConfiguration } from "../../cfg"
import { HeaderProps } from "../../components/Header"
import { QuartzComponent } from "../../components/types" import { QuartzComponent } from "../../components/types"
import { resolveToRoot } from "../../path" import { resolveToRoot } from "../../path"
import { BodyProps } from "../../components/Body" import Header from "../../components/Header"
import { QuartzComponentProps } from "../../components/types"
interface Options { interface Options {
Head: QuartzComponent<HeadProps> head: QuartzComponent
Header: QuartzComponent<HeaderProps> header: QuartzComponent[],
Body: QuartzComponent<BodyProps> body: QuartzComponent
} }
export class ContentPage extends QuartzEmitterPlugin { export class ContentPage extends QuartzEmitterPlugin {
@ -26,21 +25,21 @@ export class ContentPage extends QuartzEmitterPlugin {
this.opts = opts this.opts = opts
} }
getQuartzComponents(): QuartzComponent<any>[] { getQuartzComponents(): QuartzComponent[] {
return [...Object.values(this.opts)] return [this.opts.head, Header, ...this.opts.header, this.opts.body]
} }
async emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emit: EmitCallback): Promise<string[]> { async emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emit: EmitCallback): Promise<string[]> {
const fps: string[] = [] const fps: string[] = []
const { Head, Header, Body } = this.opts const { head: Head, header, body: Body } = this.opts
for (const [tree, file] of content) { for (const [tree, file] of content) {
// @ts-ignore (preact makes it angry) // @ts-ignore (preact makes it angry)
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' }) const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
const baseDir = resolveToRoot(file.data.slug!) const baseDir = resolveToRoot(file.data.slug!)
const pageResources: StaticResources = { const pageResources: StaticResources = {
css: [baseDir + "/index.css", ...resources.css,], css: [baseDir + "/index.css", ...resources.css],
js: [ js: [
{ src: baseDir + "/prescript.js", loadTime: "beforeDOMReady" }, { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady" },
...resources.js, ...resources.js,
@ -48,17 +47,23 @@ export class ContentPage extends QuartzEmitterPlugin {
] ]
} }
const title = file.data.frontmatter?.title const componentData: QuartzComponentProps = {
fileData: file.data,
externalResources: pageResources,
cfg,
children: [content]
}
const doc = <html> const doc = <html>
<Head <Head {...componentData} />
title={title ?? "Untitled"}
description={file.data.description ?? "No description provided"}
slug={file.data.slug!}
externalResources={pageResources} />
<body> <body>
<div id="quartz-root" class="page"> <div id="quartz-root" class="page">
<Header title={cfg.siteTitle} slug={file.data.slug!} /> <Header {...componentData} >
<Body title={file.data.slug === "index" ? undefined : title}>{content}</Body> {header.map(HeaderComponent => <HeaderComponent {...componentData}/>)}
</Header>
<Body {...componentData}>
{content}
</Body>
</div> </div>
</body> </body>
{pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} {...resource} />)} {pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} {...resource} />)}

View File

@ -17,7 +17,7 @@ function joinScripts(scripts: string[]): string {
export function emitComponentResources(cfg: GlobalConfiguration, resources: StaticResources, plugins: PluginTypes, emit: EmitCallback) { export function emitComponentResources(cfg: GlobalConfiguration, resources: StaticResources, plugins: PluginTypes, emit: EmitCallback) {
const fps: string[] = [] const fps: string[] = []
const allComponents: Set<QuartzComponent<any>> = new Set() const allComponents: Set<QuartzComponent> = new Set()
for (const emitter of plugins.emitters) { for (const emitter of plugins.emitters) {
const components = emitter.getQuartzComponents() const components = emitter.getQuartzComponents()
for (const component of components) { for (const component of components) {

View File

@ -29,7 +29,9 @@ export class Description extends QuartzTransformerPlugin {
() => { () => {
return async (tree: HTMLRoot, file) => { return async (tree: HTMLRoot, file) => {
const frontMatterDescription = file.data.frontmatter?.description const frontMatterDescription = file.data.frontmatter?.description
const desc = frontMatterDescription ?? toString(tree) const text = toString(tree)
const desc = frontMatterDescription ?? text
const sentences = desc.replace(/\s+/g, ' ').split('.') const sentences = desc.replace(/\s+/g, ' ').split('.')
let finalDesc = "" let finalDesc = ""
let sentenceIdx = 0 let sentenceIdx = 0
@ -40,6 +42,7 @@ export class Description extends QuartzTransformerPlugin {
} }
file.data.description = finalDesc file.data.description = finalDesc
file.data.text = text
} }
} }
] ]
@ -49,6 +52,7 @@ export class Description extends QuartzTransformerPlugin {
declare module 'vfile' { declare module 'vfile' {
interface DataMap { interface DataMap {
description: string description: string
text: string
} }
} }

View File

@ -26,7 +26,7 @@ export type EmitCallback = (data: EmitOptions) => Promise<string>
export abstract class QuartzEmitterPlugin { export abstract class QuartzEmitterPlugin {
abstract name: string abstract name: string
abstract emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emitCallback: EmitCallback): Promise<string[]> abstract emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emitCallback: EmitCallback): Promise<string[]>
abstract getQuartzComponents(): QuartzComponent<any>[] abstract getQuartzComponents(): QuartzComponent[]
} }
export interface PluginTypes { export interface PluginTypes {