Jacky Zhao
2023-06-08 317cce9314ad78d90714dc55aa82a2c3dfa75d1a
generic quartz component for layout
2 files added
9 files modified
135 ■■■■■ changed files
quartz.config.ts 10 ●●●●● patch | view | raw | blame | history
quartz/components/Body.tsx 15 ●●●● patch | view | raw | blame | history
quartz/components/Head.tsx 15 ●●●●● patch | view | raw | blame | history
quartz/components/Header.tsx 18 ●●●● patch | view | raw | blame | history
quartz/components/PageTitle.tsx 9 ●●●●● patch | view | raw | blame | history
quartz/components/Spacer.tsx 3 ●●●●● patch | view | raw | blame | history
quartz/components/types.ts 14 ●●●● patch | view | raw | blame | history
quartz/plugins/emitters/contentPage.tsx 41 ●●●●● patch | view | raw | blame | history
quartz/plugins/index.ts 2 ●●● patch | view | raw | blame | history
quartz/plugins/transformers/description.ts 6 ●●●● patch | view | raw | blame | history
quartz/plugins/types.ts 2 ●●● patch | view | raw | blame | history
quartz.config.ts
@@ -1,7 +1,9 @@
import { QuartzConfig } from "./quartz/cfg"
import Body from "./quartz/components/Body"
import Darkmode from "./quartz/components/Darkmode"
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 {
  ContentPage,
  CreatedModifiedDate,
@@ -68,9 +70,9 @@
    ],
    emitters: [
      new ContentPage({
        Head: Head,
        Header: Header,
        Body: Body
        head: Head,
        header: [PageTitle, Spacer, Darkmode],
        body: Body
      })
    ]
  },
quartz/components/Body.tsx
@@ -1,15 +1,14 @@
import { ComponentChildren } from "preact"
import clipboardScript from './scripts/clipboard.inline'
import clipboardStyle from './styles/clipboard.scss'
import { QuartzComponentProps } from "./types"
export interface BodyProps {
  title?: string
  children: ComponentChildren
}
export default function Body({ title, children }: BodyProps) {
export default function Body({ fileData, children }: QuartzComponentProps) {
  const title = fileData.frontmatter?.title
  const displayTitle = fileData.slug === "index" ? undefined : title
  return <article>
    {title && <h1>{title}</h1>}
    <div class="top-section">
      {displayTitle && <h1>{displayTitle}</h1>}
    </div>
    {children}
  </article>
}
quartz/components/Head.tsx
@@ -1,18 +1,15 @@
import { resolveToRoot } from "../path"
import { StaticResources } from "../resources"
import { QuartzComponentProps } from "./types"
export interface HeadProps {
  title: string
  description: string
  slug: string
  externalResources: StaticResources
}
export default function Head({ title, description, slug, externalResources }: HeadProps) {
export default function Head({ fileData, externalResources }: QuartzComponentProps) {
  const slug = fileData.slug!
  const title = fileData.frontmatter?.title ?? "Untitled"
  const description = fileData.description ?? "No description provided"
  const { css, js } = externalResources
  const baseDir = resolveToRoot(slug)
  const iconPath = baseDir + "/static/icon.png"
  const ogImagePath = baseDir + "/static/og-image.png"
  return <head>
    <title>{title}</title>
    <meta charSet="utf-8" />
quartz/components/Header.tsx
@@ -1,20 +1,10 @@
import { resolveToRoot } from "../path"
import Darkmode from "./Darkmode"
import style from './styles/header.scss'
import { QuartzComponentProps } from "./types"
export interface HeaderProps {
  title: string
  slug: string
}
export default function Header({ title, slug }: HeaderProps) {
  const baseDir = resolveToRoot(slug)
export default function Header({ children }: QuartzComponentProps) {
  return <header>
    <h1><a href={baseDir}>{title}</a></h1>
    <div class="spacer"></div>
    <Darkmode />
    {children}
  </header>
}
Header.beforeDOMLoaded = Darkmode.beforeDOMLoaded
Header.css = style + Darkmode.css
Header.css = style
quartz/components/PageTitle.tsx
New 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>
}
quartz/components/Spacer.tsx
New file
@@ -0,0 +1,3 @@
export default function() {
  return <div class="spacer"></div>
}
quartz/components/types.ts
@@ -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,
  beforeDOMLoaded?: string,
  afterDOMLoaded?: string,
quartz/plugins/emitters/contentPage.tsx
@@ -4,17 +4,16 @@
import { ProcessedContent } from "../vfile"
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
import { render } from "preact-render-to-string"
import { HeadProps } from "../../components/Head"
import { GlobalConfiguration } from "../../cfg"
import { HeaderProps } from "../../components/Header"
import { QuartzComponent } from "../../components/types"
import { resolveToRoot } from "../../path"
import { BodyProps } from "../../components/Body"
import Header from "../../components/Header"
import { QuartzComponentProps } from "../../components/types"
interface Options {
  Head: QuartzComponent<HeadProps>
  Header: QuartzComponent<HeaderProps>
  Body: QuartzComponent<BodyProps>
  head: QuartzComponent
  header: QuartzComponent[],
  body: QuartzComponent
}
export class ContentPage extends QuartzEmitterPlugin {
@@ -26,21 +25,21 @@
    this.opts = opts
  }
  getQuartzComponents(): QuartzComponent<any>[] {
    return [...Object.values(this.opts)]
  getQuartzComponents(): QuartzComponent[] {
    return [this.opts.head, Header, ...this.opts.header, this.opts.body]
  }
  async emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emit: EmitCallback): Promise<string[]> {
    const fps: string[] = []
    const { Head, Header, Body } = this.opts
    const { head: Head, header, body: Body } = this.opts
    for (const [tree, file] of content) {
      // @ts-ignore (preact makes it angry)
      const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
      const baseDir = resolveToRoot(file.data.slug!)
      const pageResources: StaticResources = {
        css: [baseDir + "/index.css", ...resources.css,],
        css: [baseDir + "/index.css", ...resources.css],
        js: [
          { src: baseDir + "/prescript.js", loadTime: "beforeDOMReady" },
          ...resources.js,
@@ -48,17 +47,23 @@
        ]
      }
      const title = file.data.frontmatter?.title
      const componentData: QuartzComponentProps = {
        fileData: file.data,
        externalResources: pageResources,
        cfg,
        children: [content]
      }
      const doc = <html>
        <Head
          title={title ?? "Untitled"}
          description={file.data.description ?? "No description provided"}
          slug={file.data.slug!}
          externalResources={pageResources} />
        <Head {...componentData} />
        <body>
          <div id="quartz-root" class="page">
            <Header title={cfg.siteTitle} slug={file.data.slug!} />
            <Body title={file.data.slug === "index" ? undefined : title}>{content}</Body>
            <Header {...componentData} >
              {header.map(HeaderComponent => <HeaderComponent {...componentData}/>)}
            </Header>
            <Body {...componentData}>
              {content}
            </Body>
          </div>
        </body>
        {pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(resource => <script key={resource.src} {...resource} />)}
quartz/plugins/index.ts
@@ -17,7 +17,7 @@
export function emitComponentResources(cfg: GlobalConfiguration, resources: StaticResources, plugins: PluginTypes, emit: EmitCallback) {
  const fps: string[] = []
  const allComponents: Set<QuartzComponent<any>> = new Set()
  const allComponents: Set<QuartzComponent> = new Set()
  for (const emitter of plugins.emitters) {
    const components = emitter.getQuartzComponents()
    for (const component of components) {
quartz/plugins/transformers/description.ts
@@ -29,7 +29,9 @@
      () => {
        return async (tree: HTMLRoot, file) => {
          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('.')
          let finalDesc = ""
          let sentenceIdx = 0
@@ -40,6 +42,7 @@
          }
          file.data.description = finalDesc
          file.data.text = text
        }
      }
    ]
@@ -49,6 +52,7 @@
declare module 'vfile' {
  interface DataMap {
    description: string
    text: string
  }
}
quartz/plugins/types.ts
@@ -26,7 +26,7 @@
export abstract class QuartzEmitterPlugin {
  abstract name: string
  abstract emit(cfg: GlobalConfiguration, content: ProcessedContent[], resources: StaticResources, emitCallback: EmitCallback): Promise<string[]>
  abstract getQuartzComponents(): QuartzComponent<any>[]
  abstract getQuartzComponents(): QuartzComponent[]
}
export interface PluginTypes {