feat: add config option for social images

This commit is contained in:
Ben Schlegel 2023-09-23 14:32:08 +02:00
parent b207477248
commit 997044d6d1
No known key found for this signature in database
GPG Key ID: 8BDB8891C1575E22
2 changed files with 45 additions and 15 deletions

View File

@ -1,4 +1,5 @@
import { ValidDateType } from "./components/Date"
import { SocialImageOptions } from "./components/Head"
import { QuartzComponent } from "./components/types"
import { PluginTypes } from "./plugins/types"
import { Theme } from "./util/theme"
@ -33,6 +34,10 @@ export interface GlobalConfiguration {
* Quartz will avoid using this as much as possible and use relative URLs most of the time
*/
baseUrl?: string
/**
* Wether to generate and use social images (Open Graph and Twitter standard) for link previews
*/
generateSocialImages: boolean | SocialImageOptions
theme: Theme
}

View File

@ -7,6 +7,13 @@ import { getSatoriFont } from "../util/fonts"
import { GlobalConfiguration } from "../cfg"
import sharp from "sharp"
export type SocialImageOptions = {
/**
* What color scheme to use for image generation (uses colors from config theme)
*/
colorScheme?: "lightMode" | "darkMode"
}
/**
* Generates social image (OG/twitter standard) and saves it as `.web` inside the public folder
* @param title what title to use
@ -22,13 +29,18 @@ async function generateSocialImage(
fileName: string,
fontsPromise: Promise<SatoriOptions["fonts"]>,
cfg: GlobalConfiguration,
colorScheme: "lightMode" | "darkMode",
) {
const fonts = await fontsPromise
// How many characters are allowed before switching to smaller font
const fontBreakPoint = 22
const useSmallerFont = title.length > fontBreakPoint
// Get color scheme preference from config (use lightMode by default)
let colorScheme: SocialImageOptions["colorScheme"] = "lightMode"
if (typeof cfg.generateSocialImages !== "boolean" && cfg.generateSocialImages.colorScheme) {
colorScheme = cfg.generateSocialImages.colorScheme
}
const svg = await satori(
<div
style={{
@ -119,11 +131,16 @@ export default (() => {
const title = fileData.frontmatter?.title ?? "Untitled"
const description = fileData.description?.trim() ?? "No description provided"
generateSocialImage(title, description, filePath as string, fontsPromise, cfg, "lightMode")
if (cfg.generateSocialImages) {
// Generate folders for social images (if they dont exist yet)
if (!fs.existsSync(imageDir)) {
fs.mkdirSync(imageDir, { recursive: true })
}
if (!fs.existsSync(imageDir)) {
fs.mkdirSync(imageDir, { recursive: true })
// Generate social image (happens async)
generateSocialImage(title, description, filePath as string, fontsPromise, cfg)
}
const { css, js } = externalResources
const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
@ -131,13 +148,16 @@ export default (() => {
const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!)
const iconPath = joinSegments(baseDir, "static/icon.png")
const useDefaultOgImage = filePath === undefined
const ogImageDefaultPath = `https://${cfg.baseUrl}/static/og-image.png`
const ogImageGeneratedPath = `https://${cfg.baseUrl}/${imageDir.replace(
"public/",
"",
)}/${filePath}.${extension}`
// Use default og image if filePath doesnt exist (for autogenerated paths with no .md file)
const useDefaultOgImage = filePath === undefined || !cfg.generateSocialImages
const ogImagePath = useDefaultOgImage ? ogImageDefaultPath : ogImageGeneratedPath
return (
@ -145,25 +165,30 @@ export default (() => {
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{/* OG/Twitter meta tags */}
<meta name="og:site_name" content={cfg.pageTitle}></meta>
<meta property="og:url" content={`https://${cfg.baseUrl}/${fileData.slug}`}></meta>
<meta property="og:title" content={title} />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta property="og:description" content={description} />
<meta property="og:image:type" content={`image/${extension}`} />
<meta property="og:image:alt" content={fileData.description} />
<meta property="og:image:width" content={"" + ogWidth} />
<meta property="og:image:height" content={"" + ogHeight} />
<meta property="og:image:url" content={ogImagePath} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta property="twitter:url" content={`https://${cfg.baseUrl}/${fileData.slug}`}></meta>
<meta property="twitter:domain" content={cfg.baseUrl}></meta>
{cfg.baseUrl && <meta name="twitter:image" content={ogImagePath} />}
{cfg.baseUrl && <meta property="og:image" content={ogImagePath} />}
<meta property="og:width" content="1200" />
<meta property="og:height" content="675" />
<meta property="og:width" content={"" + ogWidth} />
<meta property="og:height" content={"" + ogHeight} />
{cfg.baseUrl && (
<>
<meta name="twitter:image" content={ogImagePath} />
<meta property="og:image" content={ogImagePath} />
<meta property="twitter:domain" content={cfg.baseUrl}></meta>
<meta property="og:url" content={`https://${cfg.baseUrl}/${fileData.slug}`}></meta>
<meta property="twitter:url" content={`https://${cfg.baseUrl}/${fileData.slug}`}></meta>
</>
)}
<link rel="icon" href={iconPath} />
<meta name="description" content={description} />
<meta name="generator" content="Quartz" />