fix: parse parallelization chunk arg, inline b64 for og image
| | |
| | | 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" |
| | |
| | | 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", |
| | |
| | | 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, |
| | |
| | | workerType: "thread", |
| | | }) |
| | | const errorHandler = (err: any) => { |
| | | console.error(`${err}`.replace(/^error:\s*/i, "")) |
| | | console.error(err) |
| | | process.exit(1) |
| | | } |
| | | |
| | |
| | | |
| | | 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( |
| | |
| | | const truncated = truncate(output, columns) |
| | | process.stdout.write(truncated) |
| | | this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length |
| | | }, 20) |
| | | }, 50) |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | 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[] = ( |
| | |
| | | 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 |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // 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) |
| | |
| | | marginBottom: "0.5rem", |
| | | }} |
| | | > |
| | | {iconBase64 && ( |
| | | <img |
| | | src={iconPath} |
| | | src={iconBase64} |
| | | width={56} |
| | | height={56} |
| | | style={{ |
| | | borderRadius: "50%", |
| | | }} |
| | | /> |
| | | )} |
| | | <div |
| | | style={{ |
| | | display: "flex", |