From ec00a40aefca73596ab76e3ebe3a8e1129b43688 Mon Sep 17 00:00:00 2001
From: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 27 Jan 2026 18:27:17 +0000
Subject: [PATCH] chore(deps): bump the production-dependencies group with 4 updates (#2289)

---
 quartz/util/og.tsx |  165 ++++++++++++++++++++++++++++++++----------------------
 1 files changed, 98 insertions(+), 67 deletions(-)

diff --git a/quartz/util/og.tsx b/quartz/util/og.tsx
index c8ad663..2afd606 100644
--- a/quartz/util/og.tsx
+++ b/quartz/util/og.tsx
@@ -3,14 +3,17 @@
 import { GlobalConfiguration } from "../cfg"
 import { QuartzPluginData } from "../plugins/vfile"
 import { JSXInternal } from "preact/src/jsx"
-import { FontSpecification, ThemeKey } from "./theme"
+import { FontSpecification, getFontSpecificationName, ThemeKey } from "./theme"
 import path from "path"
 import { QUARTZ } from "./path"
-import { formatDate } from "../components/Date"
-import { getDate } from "../components/Date"
+import { formatDate, getDate } from "../components/Date"
+import readingTime from "reading-time"
+import { i18n } from "../i18n"
+import { styleText } from "util"
 
 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[] = (
@@ -25,29 +28,38 @@
   const headerFontName = typeof headerFont === "string" ? headerFont : headerFont.name
   const bodyFontName = typeof bodyFont === "string" ? bodyFont : bodyFont.name
 
-  // Fetch fonts for all weights
-  const headerFontPromises = headerWeights.map((weight) => fetchTtf(headerFontName, weight))
-  const bodyFontPromises = bodyWeights.map((weight) => fetchTtf(bodyFontName, weight))
+  // Fetch fonts for all weights and convert to satori format in one go
+  const headerFontPromises = headerWeights.map(async (weight) => {
+    const data = await fetchTtf(headerFontName, weight)
+    if (!data) return null
+    return {
+      name: headerFontName,
+      data,
+      weight,
+      style: "normal" as const,
+    }
+  })
 
-  const [headerFontData, bodyFontData] = await Promise.all([
+  const bodyFontPromises = bodyWeights.map(async (weight) => {
+    const data = await fetchTtf(bodyFontName, weight)
+    if (!data) return null
+    return {
+      name: bodyFontName,
+      data,
+      weight,
+      style: "normal" as const,
+    }
+  })
+
+  const [headerFonts, bodyFonts] = await Promise.all([
     Promise.all(headerFontPromises),
     Promise.all(bodyFontPromises),
   ])
 
-  // Convert fonts to satori font format and return
+  // Filter out any failed fetches and combine header and body fonts
   const fonts: SatoriOptions["fonts"] = [
-    ...headerFontData.map((data, idx) => ({
-      name: headerFontName,
-      data,
-      weight: headerWeights[idx],
-      style: "normal" as const,
-    })),
-    ...bodyFontData.map((data, idx) => ({
-      name: bodyFontName,
-      data,
-      weight: bodyWeights[idx],
-      style: "normal" as const,
-    })),
+    ...headerFonts.filter((font): font is NonNullable<typeof font> => font !== null),
+    ...bodyFonts.filter((font): font is NonNullable<typeof font> => font !== null),
   ]
 
   return fonts
@@ -60,10 +72,11 @@
  * @returns `.ttf` file of google font
  */
 export async function fetchTtf(
-  fontName: string,
+  rawFontName: string,
   weight: FontWeight,
-): Promise<Buffer<ArrayBufferLike>> {
-  const cacheKey = `${fontName.replaceAll(" ", "-")}-${weight}`
+): Promise<Buffer<ArrayBufferLike> | undefined> {
+  const fontName = rawFontName.replaceAll(" ", "+")
+  const cacheKey = `${fontName}-${weight}`
   const cacheDir = path.join(QUARTZ, ".quartz-cache", "fonts")
   const cachePath = path.join(cacheDir, cacheKey)
 
@@ -86,20 +99,20 @@
   const match = urlRegex.exec(css)
 
   if (!match) {
-    throw new Error("Could not fetch font")
+    console.log(
+      styleText(
+        "yellow",
+        `\nWarning: Failed to fetch font ${rawFontName} with weight ${weight}, got ${cssResponse.statusText}`,
+      ),
+    )
+    return
   }
 
   // fontData is an ArrayBuffer containing the .ttf file data
   const fontResponse = await fetch(match[1])
   const fontData = Buffer.from(await fontResponse.arrayBuffer())
-
-  try {
-    await fs.mkdir(cacheDir, { recursive: true })
-    await fs.writeFile(cachePath, fontData)
-  } catch (error) {
-    console.warn(`Failed to cache font: ${error}`)
-    // Continue even if caching fails
-  }
+  await fs.mkdir(cacheDir, { recursive: true })
+  await fs.writeFile(cachePath, fontData)
 
   return fontData
 }
@@ -123,21 +136,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
 }
 
@@ -167,24 +171,32 @@
 }
 
 // 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)
   const date = rawDate ? formatDate(rawDate, cfg.locale) : null
 
+  // Calculate reading time
+  const { minutes } = readingTime(fileData.text ?? "")
+  const readingTimeText = i18n(cfg.locale).components.contentMeta.readingTime({
+    minutes: Math.ceil(minutes),
+  })
+
   // Get tags if available
   const tags = fileData.frontmatter?.tags ?? []
+  const bodyFont = getFontSpecificationName(cfg.theme.typography.body)
+  const headerFont = getFontSpecificationName(cfg.theme.typography.header)
 
   return (
     <div
@@ -195,7 +207,7 @@
         width: "100%",
         backgroundColor: cfg.theme.colors[colorScheme].light,
         padding: "2.5rem",
-        fontFamily: fonts[1].name,
+        fontFamily: bodyFont,
       }}
     >
       {/* Header Section */}
@@ -207,20 +219,22 @@
           marginBottom: "0.5rem",
         }}
       >
-        <img
-          src={iconPath}
-          width={56}
-          height={56}
-          style={{
-            borderRadius: "50%",
-          }}
-        />
+        {iconBase64 && (
+          <img
+            src={iconBase64}
+            width={56}
+            height={56}
+            style={{
+              borderRadius: "50%",
+            }}
+          />
+        )}
         <div
           style={{
             display: "flex",
             fontSize: 32,
             color: cfg.theme.colors[colorScheme].gray,
-            fontFamily: fonts[1].name,
+            fontFamily: bodyFont,
           }}
         >
           {cfg.baseUrl}
@@ -239,7 +253,7 @@
           style={{
             margin: 0,
             fontSize: useSmallerFont ? 64 : 72,
-            fontFamily: fonts[0].name,
+            fontFamily: headerFont,
             fontWeight: 700,
             color: cfg.theme.colors[colorScheme].dark,
             lineHeight: 1.2,
@@ -247,6 +261,7 @@
             WebkitBoxOrient: "vertical",
             WebkitLineClamp: 2,
             overflow: "hidden",
+            textOverflow: "ellipsis",
           }}
         >
           {title}
@@ -268,8 +283,9 @@
             margin: 0,
             display: "-webkit-box",
             WebkitBoxOrient: "vertical",
-            WebkitLineClamp: 4,
+            WebkitLineClamp: 5,
             overflow: "hidden",
+            textOverflow: "ellipsis",
           }}
         >
           {description}
@@ -287,11 +303,12 @@
           borderTop: `1px solid ${cfg.theme.colors[colorScheme].lightgray}`,
         }}
       >
-        {/* Left side - Date */}
+        {/* Left side - Date and Reading Time */}
         <div
           style={{
             display: "flex",
             alignItems: "center",
+            gap: "2rem",
             color: cfg.theme.colors[colorScheme].gray,
             fontSize: 28,
           }}
@@ -314,6 +331,20 @@
               {date}
             </div>
           )}
+          <div style={{ display: "flex", alignItems: "center" }}>
+            <svg
+              style={{ marginRight: "0.5rem" }}
+              width="28"
+              height="28"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+            >
+              <circle cx="12" cy="12" r="10"></circle>
+              <polyline points="12 6 12 12 16 14"></polyline>
+            </svg>
+            {readingTimeText}
+          </div>
         </div>
 
         {/* Right side - Tags */}

--
Gitblit v1.10.0