fix: parse parallelization chunk arg, inline b64 for og image
This commit is contained in:
parent
a737207981
commit
e86544064c
4 changed files with 49 additions and 35 deletions
|
@ -1,7 +1,7 @@
|
||||||
import { QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
import { i18n } from "../../i18n"
|
import { i18n } from "../../i18n"
|
||||||
import { unescapeHTML } from "../../util/escape"
|
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 { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
|
||||||
import sharp from "sharp"
|
import sharp from "sharp"
|
||||||
import satori, { SatoriOptions } from "satori"
|
import satori, { SatoriOptions } from "satori"
|
||||||
|
@ -10,6 +10,8 @@ import { Readable } from "stream"
|
||||||
import { write } from "./helpers"
|
import { write } from "./helpers"
|
||||||
import { BuildCtx } from "../../util/ctx"
|
import { BuildCtx } from "../../util/ctx"
|
||||||
import { QuartzPluginData } from "../vfile"
|
import { QuartzPluginData } from "../vfile"
|
||||||
|
import fs from "node:fs/promises"
|
||||||
|
import chalk from "chalk"
|
||||||
|
|
||||||
const defaultOptions: SocialImageOptions = {
|
const defaultOptions: SocialImageOptions = {
|
||||||
colorScheme: "lightMode",
|
colorScheme: "lightMode",
|
||||||
|
@ -28,7 +30,25 @@ async function generateSocialImage(
|
||||||
userOpts: SocialImageOptions,
|
userOpts: SocialImageOptions,
|
||||||
): Promise<Readable> {
|
): Promise<Readable> {
|
||||||
const { width, height } = userOpts
|
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, {
|
const svg = await satori(imageComponent, {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
|
|
@ -172,7 +172,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
|
||||||
workerType: "thread",
|
workerType: "thread",
|
||||||
})
|
})
|
||||||
const errorHandler = (err: any) => {
|
const errorHandler = (err: any) => {
|
||||||
console.error(`${err}`.replace(/^error:\s*/i, ""))
|
console.error(err)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
|
||||||
|
|
||||||
const markdownToHtmlPromises: WorkerPromise<ProcessedContent[]>[] = []
|
const markdownToHtmlPromises: WorkerPromise<ProcessedContent[]>[] = []
|
||||||
processedFiles = 0
|
processedFiles = 0
|
||||||
for (const [mdChunk, _] of mdResults) {
|
for (const mdChunk of mdResults) {
|
||||||
markdownToHtmlPromises.push(pool.exec("processHtml", [serializableCtx, mdChunk]))
|
markdownToHtmlPromises.push(pool.exec("processHtml", [serializableCtx, mdChunk]))
|
||||||
}
|
}
|
||||||
const results: ProcessedContent[][] = await Promise.all(
|
const results: ProcessedContent[][] = await Promise.all(
|
||||||
|
|
|
@ -35,7 +35,7 @@ export class QuartzLogger {
|
||||||
const truncated = truncate(output, columns)
|
const truncated = truncate(output, columns)
|
||||||
process.stdout.write(truncated)
|
process.stdout.write(truncated)
|
||||||
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length
|
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length
|
||||||
}, 20)
|
}, 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import chalk from "chalk"
|
||||||
|
|
||||||
const defaultHeaderWeight = [700]
|
const defaultHeaderWeight = [700]
|
||||||
const defaultBodyWeight = [400]
|
const defaultBodyWeight = [400]
|
||||||
|
|
||||||
export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) {
|
export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) {
|
||||||
// Get all weights for header and body fonts
|
// Get all weights for header and body fonts
|
||||||
const headerWeights: FontWeight[] = (
|
const headerWeights: FontWeight[] = (
|
||||||
|
@ -134,21 +135,12 @@ export type SocialImageOptions = {
|
||||||
excludeRoot: boolean
|
excludeRoot: boolean
|
||||||
/**
|
/**
|
||||||
* JSX to use for generating image. See satori docs for more info (https://github.com/vercel/satori)
|
* 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: (
|
imageStructure: (
|
||||||
cfg: GlobalConfiguration,
|
options: ImageOptions & {
|
||||||
userOpts: UserOpts,
|
userOpts: UserOpts
|
||||||
title: string,
|
iconBase64?: string
|
||||||
description: string,
|
},
|
||||||
fonts: SatoriOptions["fonts"],
|
|
||||||
fileData: QuartzPluginData,
|
|
||||||
) => JSXInternal.Element
|
) => JSXInternal.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,17 +170,17 @@ export type ImageOptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the default template for generated social image.
|
// This is the default template for generated social image.
|
||||||
export const defaultImage: SocialImageOptions["imageStructure"] = (
|
export const defaultImage: SocialImageOptions["imageStructure"] = ({
|
||||||
cfg: GlobalConfiguration,
|
cfg,
|
||||||
{ colorScheme }: UserOpts,
|
userOpts,
|
||||||
title: string,
|
title,
|
||||||
description: string,
|
description,
|
||||||
_fonts: SatoriOptions["fonts"],
|
fileData,
|
||||||
fileData: QuartzPluginData,
|
iconBase64,
|
||||||
) => {
|
}) => {
|
||||||
|
const { colorScheme } = userOpts
|
||||||
const fontBreakPoint = 32
|
const fontBreakPoint = 32
|
||||||
const useSmallerFont = title.length > fontBreakPoint
|
const useSmallerFont = title.length > fontBreakPoint
|
||||||
const iconPath = `https://${cfg.baseUrl}/static/icon.png`
|
|
||||||
|
|
||||||
// Format date if available
|
// Format date if available
|
||||||
const rawDate = getDate(cfg, fileData)
|
const rawDate = getDate(cfg, fileData)
|
||||||
|
@ -226,14 +218,16 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
|
||||||
marginBottom: "0.5rem",
|
marginBottom: "0.5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
{iconBase64 && (
|
||||||
src={iconPath}
|
<img
|
||||||
width={56}
|
src={iconBase64}
|
||||||
height={56}
|
width={56}
|
||||||
style={{
|
height={56}
|
||||||
borderRadius: "50%",
|
style={{
|
||||||
}}
|
borderRadius: "50%",
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|
Loading…
Add table
Reference in a new issue