Jacky Zhao
2025-03-16 e86544064cf37e7cdb7cac302cfb40fdb728de6d
fix: parse parallelization chunk arg, inline b64 for og image
4 files modified
70 ■■■■■ changed files
quartz/plugins/emitters/ogImage.tsx 24 ●●●●● patch | view | raw | blame | history
quartz/processors/parse.ts 4 ●●●● patch | view | raw | blame | history
quartz/util/log.ts 2 ●●● patch | view | raw | blame | history
quartz/util/og.tsx 40 ●●●●● patch | view | raw | blame | history
quartz/plugins/emitters/ogImage.tsx
@@ -1,7 +1,7 @@
import { QuartzEmitterPlugin } from "../types"
import { i18n } from "../../i18n"
import { unescapeHTML } from "../../util/escape"
import { FullSlug, getFileExtension } from "../../util/path"
import { FullSlug, getFileExtension, joinSegments, QUARTZ } from "../../util/path"
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
import sharp from "sharp"
import satori, { SatoriOptions } from "satori"
@@ -10,6 +10,8 @@
import { write } from "./helpers"
import { BuildCtx } from "../../util/ctx"
import { QuartzPluginData } from "../vfile"
import fs from "node:fs/promises"
import chalk from "chalk"
const defaultOptions: SocialImageOptions = {
  colorScheme: "lightMode",
@@ -28,7 +30,25 @@
  userOpts: SocialImageOptions,
): Promise<Readable> {
  const { width, height } = userOpts
  const imageComponent = userOpts.imageStructure(cfg, userOpts, title, description, fonts, fileData)
  const iconPath = joinSegments(QUARTZ, "static", "icon.png")
  let iconBase64: string | undefined = undefined
  try {
    const iconData = await fs.readFile(iconPath)
    iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`
  } catch (err) {
    console.warn(chalk.yellow(`Warning: Could not find icon at ${iconPath}`))
  }
  const imageComponent = userOpts.imageStructure({
    cfg,
    userOpts,
    title,
    description,
    fonts,
    fileData,
    iconBase64,
  })
  const svg = await satori(imageComponent, {
    width,
    height,
quartz/processors/parse.ts
@@ -172,7 +172,7 @@
      workerType: "thread",
    })
    const errorHandler = (err: any) => {
      console.error(`${err}`.replace(/^error:\s*/i, ""))
      console.error(err)
      process.exit(1)
    }
@@ -201,7 +201,7 @@
    const markdownToHtmlPromises: WorkerPromise<ProcessedContent[]>[] = []
    processedFiles = 0
    for (const [mdChunk, _] of mdResults) {
    for (const mdChunk of mdResults) {
      markdownToHtmlPromises.push(pool.exec("processHtml", [serializableCtx, mdChunk]))
    }
    const results: ProcessedContent[][] = await Promise.all(
quartz/util/log.ts
@@ -35,7 +35,7 @@
        const truncated = truncate(output, columns)
        process.stdout.write(truncated)
        this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length
      }, 20)
      }, 50)
    }
  }
quartz/util/og.tsx
@@ -13,6 +13,7 @@
const defaultHeaderWeight = [700]
const defaultBodyWeight = [400]
export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) {
  // Get all weights for header and body fonts
  const headerWeights: FontWeight[] = (
@@ -134,21 +135,12 @@
  excludeRoot: boolean
  /**
   * JSX to use for generating image. See satori docs for more info (https://github.com/vercel/satori)
   * @param cfg global quartz config
   * @param userOpts options that can be set by user
   * @param title title of current page
   * @param description description of current page
   * @param fonts global font that can be used for styling
   * @param fileData full fileData of current page
   * @returns prepared jsx to be used for generating image
   */
  imageStructure: (
    cfg: GlobalConfiguration,
    userOpts: UserOpts,
    title: string,
    description: string,
    fonts: SatoriOptions["fonts"],
    fileData: QuartzPluginData,
    options: ImageOptions & {
      userOpts: UserOpts
      iconBase64?: string
    },
  ) => JSXInternal.Element
}
@@ -178,17 +170,17 @@
}
// This is the default template for generated social image.
export const defaultImage: SocialImageOptions["imageStructure"] = (
  cfg: GlobalConfiguration,
  { colorScheme }: UserOpts,
  title: string,
  description: string,
  _fonts: SatoriOptions["fonts"],
  fileData: QuartzPluginData,
) => {
export const defaultImage: SocialImageOptions["imageStructure"] = ({
  cfg,
  userOpts,
  title,
  description,
  fileData,
  iconBase64,
}) => {
  const { colorScheme } = userOpts
  const fontBreakPoint = 32
  const useSmallerFont = title.length > fontBreakPoint
  const iconPath = `https://${cfg.baseUrl}/static/icon.png`
  // Format date if available
  const rawDate = getDate(cfg, fileData)
@@ -226,14 +218,16 @@
          marginBottom: "0.5rem",
        }}
      >
        {iconBase64 && (
        <img
          src={iconPath}
            src={iconBase64}
          width={56}
          height={56}
          style={{
            borderRadius: "50%",
          }}
        />
        )}
        <div
          style={{
            display: "flex",