Karim
2025-03-21 3ce6aa49bf25b26a4cb1bf18e9770271d132772d
fix(ogImage): update socialImage path to include base URL if defined (#1858)

* fix(ogImage): update socialImage path to include base URL if defined

* feat(path): add function to check if a file path is absolute

* fix(ogImage): handle absolute paths for user defined og image paths

* docs(CustomOgImages): update socialImage property to accept full URLs

* fix(ogImage): typo

* fix(ogImage): improve user-defined OG image path handling

* Update docs/plugins/CustomOgImages.md

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Update quartz/plugins/emitters/ogImage.tsx

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* refactor(path): remove isAbsoluteFilePath function

* fix(ogImage): update user-defined OG image path handling to support relative URLs

* feat(ogImage): enhance user-defined OG image path handling with absolute URL support

* refactor(ogImage): remove debug log for ogImagePath

* feat(path): add isAbsoluteURL function and corresponding tests

* refactor(path): remove unused URL import for isomorphic compatibility

---------

Co-authored-by: Karim H <karimh96@hotmail.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
4 files modified
35 ■■■■ changed files
docs/plugins/CustomOgImages.md 2 ●●● patch | view | raw | blame | history
quartz/plugins/emitters/ogImage.tsx 12 ●●●● patch | view | raw | blame | history
quartz/util/path.test.ts 11 ●●●●● patch | view | raw | blame | history
quartz/util/path.ts 10 ●●●●● patch | view | raw | blame | history
docs/plugins/CustomOgImages.md
@@ -62,7 +62,7 @@
| `socialDescription` | `description`    | Description to be used for preview. |
| `socialImage`       | `image`, `cover` | Link to preview image.              |
The `socialImage` property should contain a link to an image relative to `quartz/static`. If you have a folder for all your images in `quartz/static/my-images`, an example for `socialImage` could be `"my-images/cover.png"`.
The `socialImage` property should contain a link to an image either relative to `quartz/static`, or a full URL. If you have a folder for all your images in `quartz/static/my-images`, an example for `socialImage` could be `"my-images/cover.png"`. Alternatively, you can use a fully qualified URL like `"https://example.com/cover.png"`.
> [!info] Info
>
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, joinSegments, QUARTZ } from "../../util/path"
import { FullSlug, getFileExtension, isAbsoluteURL, joinSegments, QUARTZ } from "../../util/path"
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
import sharp from "sharp"
import satori, { SatoriOptions } from "satori"
@@ -144,13 +144,19 @@
        additionalHead: [
          (pageData) => {
            const isRealFile = pageData.filePath !== undefined
            const userDefinedOgImagePath = pageData.frontmatter?.socialImage
            let userDefinedOgImagePath = pageData.frontmatter?.socialImage
            if (userDefinedOgImagePath) {
              userDefinedOgImagePath = isAbsoluteURL(userDefinedOgImagePath)
                ? userDefinedOgImagePath
                : `https://${baseUrl}/static/${userDefinedOgImagePath}`
            }
            const generatedOgImagePath = isRealFile
              ? `https://${baseUrl}/${pageData.slug!}-og-image.webp`
              : undefined
            const defaultOgImagePath = `https://${baseUrl}/static/og-image.png`
            const ogImagePath = userDefinedOgImagePath ?? generatedOgImagePath ?? defaultOgImagePath
            const ogImageMimeType = `image/${getFileExtension(ogImagePath) ?? "png"}`
            return (
              <>
quartz/util/path.test.ts
@@ -38,6 +38,17 @@
    assert(!path.isRelativeURL("./abc/def.md"))
  })
  test("isAbsoluteURL", () => {
    assert(path.isAbsoluteURL("https://example.com"))
    assert(path.isAbsoluteURL("http://example.com"))
    assert(path.isAbsoluteURL("ftp://example.com/a/b/c"))
    assert(path.isAbsoluteURL("http://host/%25"))
    assert(path.isAbsoluteURL("file://host/twoslashes?more//slashes"))
    assert(!path.isAbsoluteURL("example.com/abc/def"))
    assert(!path.isAbsoluteURL("abc"))
  })
  test("isFullSlug", () => {
    assert(path.isFullSlug("index"))
    assert(path.isFullSlug("abc/def"))
quartz/util/path.ts
@@ -1,6 +1,7 @@
import { slug as slugAnchor } from "github-slugger"
import type { Element as HastElement } from "hast"
import { clone } from "./clone"
// this file must be isomorphic so it can't use node libs (e.g. path)
export const QUARTZ = "quartz"
@@ -39,6 +40,15 @@
  return validStart && validEnding && ![".md", ".html"].includes(getFileExtension(s) ?? "")
}
export function isAbsoluteURL(s: string): boolean {
  try {
    new URL(s)
  } catch {
    return false
  }
  return true
}
export function getFullSlug(window: Window): FullSlug {
  const res = window.document.body.dataset.slug! as FullSlug
  return res