Skip to content

Commit

Permalink
feat(open-graph): generate OG images + further OG support (jackyzha0#740
Browse files Browse the repository at this point in the history
)

* Quartz sync: Aug 29, 2023, 10:17 PM

* feat: add basic satori og image generation

* Squashed commit of the following:

commit fa69c2a
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Thu Sep 21 19:35:11 2023 +0200

    fix(explorer): increase consistency, explicitly use font-family (jackyzha0#496)

    * fix(explorer): display name for folders without `index` file

    * docs(explorer): add section for folder display names

    * docs(explorer): fix broken wikilink

    * fix(consistency): explicitly set font + label/link fix

    Use consistent styling between folders with `folderClickBehavior: "link"` and `"collapse`

    * Update quartz/components/styles/explorer.scss

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

    * Update quartz/components/styles/explorer.scss

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

    ---------

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

commit 8eb1554
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Thu Sep 21 18:54:33 2023 +0200

    fix(explorer): display names for folders without frontmatter (jackyzha0#494)

    * fix(explorer): display name for folders without `index` file

    * docs(explorer): add section for folder display names

commit dcdeae4
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Thu Sep 21 18:53:19 2023 +0200

    docs(explorer): update default config + new example (jackyzha0#493)

commit 4845223
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 16:09:18 2023 -0700

    perf: memoize filetree computation (jackyzha0#490)

    * perf: memoize filetree computation

    * format

    * var -> let

commit 16d33fb
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 16:08:54 2023 -0700

    feat: display name for folders, expand explorer a little bit (jackyzha0#489)

    * feat: display name for folders, expand explorer a little bit

    * update docs

commit b029eea
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Wed Sep 20 22:55:29 2023 +0200

    feat(explorer): improve accessibility and consistency (+ bug fix) (jackyzha0#488)

    * feat(consistency): use `all: unset` on button

    * style: improve accessibility and consistency for explorer

    * fix: localStorage bug with folder name changes

    * chore: bump quartz version

commit 6a9e635
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 13:52:45 2023 -0700

    Revert "feat: Making Quartz available offline by making it a PWA (jackyzha0#465)"

    This reverts commit d6301fa.

commit 70e029d
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 13:52:29 2023 -0700

    Revert "docs: wording changes for offline support"

    This reverts commit 52a172d.

commit 0bad3ce
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 11:58:52 2023 -0700

    docs: document enableToc

commit 52a172d
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Wed Sep 20 11:40:36 2023 -0700

    docs: wording changes for offline support

commit d6301fa
Author: Adam Brangenberg <adambrangenberg@proton.me>
Date:   Wed Sep 20 20:38:13 2023 +0200

    feat: Making Quartz available offline by making it a PWA (jackyzha0#465)

    * Adding PWA and chaching for offline aviability

    * renamed workbox config to fit Quartz' scheme

    * Documenting new configuration

    * Added missig umami documentation

    * Fixed formatting so the build passes, thank you prettier :)

    * specified caching strategies to improve performance

    * formatting...

    * fixing "404 manifest.json not found" on subdirectories by adding a / to manifestpath

    * turning it into a plugin

    * Removed Workbox-cli and updated @types/node

    * Added Serviceworkercode to offline.ts

    * formatting

    * Removing workbox from docs

    * applied suggestions

    * Removed path.join for sw path

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

    * Removed path.join for manifest path

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

    * Removing path module import

    * Added absolute path to manifests start_url and manifest "import" using baseUrl

    * Adding protocol to baseurl

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

    * Adding protocol to start_url too then

    * formatting...

    * Adding fallback page

    * Documenting offline plugin

    * formatting...

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * merge suggestion

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

    * formatting...

    * Fixing manifest path, all these nits hiding the actual issues .-.

    * Offline fallback page through plugins, most things taken from 404 Plugin

    * adding Offline Plugin to config

    * formatting...

    * Turned offline off as default and removed offline.md

    ---------

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

commit 27a6087
Author: rwutscher <richard.wutscher@gmail.com>
Date:   Tue Sep 19 21:26:30 2023 +0200

    fix: tag regex no longer includes purely numerical 'tags' (jackyzha0#485)

    * fix: tag regex no longer includes purely numerical 'tags'

    * fix: formatting

    * fix: use guard in findAndReplace() instead of expanding the regex

commit 1bf7e3d
Author: Jacky Zhao <j.zhao2k19@gmail.com>
Date:   Tue Sep 19 10:22:39 2023 -0700

    fix(nit): make defaultOptions on explorer not a function

commit cc31a40
Author: David Fischer <david@konst.fish>
Date:   Tue Sep 19 18:25:51 2023 +0200

    feat: support changes in system theme (jackyzha0#484)

    * feat: support changes in system theme

    * fix: run prettier

    * fix: add content/.gitkeep

commit 0d3cf29
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com>
Date:   Mon Sep 18 23:32:00 2023 +0200

    docs: fix explorer example (jackyzha0#483)

* feat: dynamically generate og images, write to fs as png

* fix: og preview on discord

* feat: use `sharp` to convert to webp, add content headers

* feat: add config for theme (light or dark)

* feat: improve image margins, add font breakpoint

* feat: use config header + body fonts for satori

* perf: memoize fonts

* feat: use default og image if no path exists

* feat: add config option for social images

* feat: support custom og images via frontmatter

* refactor: clean font helpers, rename fonts helper

* refactor: make image generation cleaner

* refactor: move default image to own component

* chore: add todos

* fix: only set width/height header if known

* feat: remove html from description

* feat: make image dimensions configurable

* feat: pass userOpts to image generator

* feat: option for users to provide own image struct (satori)

This allows users to pass their own jsx for generating the default og image

* refactor: rename `defaultImage.tsx` > `socialImage.tsx`

* chore: improve comments + types

* refactor: rename socialImage frontmatter property

* feat: add frontmatter aliases for cover image

* feat: add frontmatter alias for obsidian publish

* docs: add documentation for social images

* feat: add `generateSocialImages` prop to config

* chore: update lock file

* fix: fix type error

* chore: update package.json

* chore: update package-lock.json

* docs: update docs

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

* fix: clean url for use in metadata

* refactor: clean function signature

* feat: pass `fileData` to image generator

* CI: run format

* fix: file system import

* fix: merge paths using `joinSegments`

* fix: get output dir via `ctx.argv.output`

* chore: add explanation to font regex

* Squashed commit of the following:

commit 7164857
Author: Aaron Pham <29749331+aarnphm@users.noreply.github.com>
Date:   Fri Mar 15 21:17:42 2024 -0400

    chore(ofm): remove unused (jackyzha0#999)

    Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

commit 4702402
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Fri Mar 15 18:29:14 2024 -0400

    chore(deps-dev): bump @types/node from 20.11.24 to 20.11.25 (jackyzha0#990)

    Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.24 to 20.11.25.
    - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
    - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

    ---
    updated-dependencies:
    - dependency-name: "@types/node"
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit b98e4be
Author: Mara-Li <lili.simonetti@outlook.fr>
Date:   Fri Mar 15 23:28:31 2024 +0100

    feat(i18n): Add French translation for reading time (jackyzha0#998)

    Signed-off-by: Mara-Li <lili.simonetti@outlook.fr>

commit 8be51a0
Author: catcodeme <1020082805@qq.com>
Date:   Fri Mar 15 14:25:01 2024 +0800

    fix: wikiLink in table (jackyzha0#993)

    * fix: wikiLink in table

    - update regexp to make '\' to group in alias
    - handle alias using block_id

    * style: format with prettier

    * style: add comment for block_ref(without alias) in table

    ---------

    Co-authored-by: hulinjiang <hulinjiang@58.com>

commit 92cc23d
Author: Linus Sehn <37184648+linozen@users.noreply.github.com>
Date:   Wed Mar 13 08:59:37 2024 +0100

    feat(plugin): citations (jackyzha0#984)

    * feat: add rehype-citations

    * feat: add citations transformer plugin

    * feat: add rehype-rewrite

    * feat: add csl option and add no-popover to citation links

    * revert: add rehype-rewrite

    04b2692 'feat: add rehype-rewrite'

    * feat: use existing package for html manipulation

    * fix: remove `console.log()`

commit 097abc3
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Mon Mar 11 13:41:48 2024 -0700

    chore(deps): bump async-mutex from 0.4.1 to 0.5.0 (jackyzha0#991)

    Bumps [async-mutex](https://github.com/DirtyHairy/async-mutex) from 0.4.1 to 0.5.0.
    - [Changelog](https://github.com/DirtyHairy/async-mutex/blob/master/CHANGELOG.md)
    - [Commits](DirtyHairy/async-mutex@v0.4.1...v0.5.0)

    ---
    updated-dependencies:
    - dependency-name: async-mutex
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit a00324d
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Mon Mar 11 13:41:41 2024 -0700

    chore(deps-dev): bump typescript from 5.3.3 to 5.4.2 (jackyzha0#989)

    Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.3.3 to 5.4.2.
    - [Release notes](https://github.com/Microsoft/TypeScript/releases)
    - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
    - [Commits](microsoft/TypeScript@v5.3.3...v5.4.2)

    ---
    updated-dependencies:
    - dependency-name: typescript
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 9fff6d7
Author: Mara-Li <lili.simonetti@outlook.fr>
Date:   Mon Mar 11 17:46:53 2024 +0100

    fix: spelling error (jackyzha0#987)

    I really don't know why I translated this like that into "pas trouvé", and it bugged me a lot. I finally fixed it…

    Signed-off-by: Mara-Li <lili.simonetti@outlook.fr>

commit 0f5a9d7
Author: Matt Vogel <mainmoniker@googlemail.com>
Date:   Sun Mar 10 12:57:10 2024 -0400

    feat: separated content meta (jackyzha0#929)

    to allow for CSS styling

commit b4236e5
Author: kabirgh <15871468+kabirgh@users.noreply.github.com>
Date:   Sun Mar 10 00:42:23 2024 +0000

    feat(perf:fast-rebuilds): Stop mutating resources param in ComponentResources emitter (jackyzha0#977)

    * Stop mutating resources param in ComponentResources emitter

    * Add done rebuilding log for fast rebuilds

    * Move google font loading to Head component

    * Simplify code and fix comment

commit 6e0c102
Author: Emile Bangma <ewjbangma@hotmail.com>
Date:   Sun Mar 10 01:14:31 2024 +0100

    fix(transclusion): prevent duplicate transclusion if multiple transclusions are present. (jackyzha0#982)

commit 94a5469
Author: Emile Bangma <ewjbangma@hotmail.com>
Date:   Sat Mar 9 17:59:55 2024 +0100

    fix(resources): Use full path to font when cdnCache is false (jackyzha0#976)

commit 2e9a0c2
Author: Emile Bangma <ewjbangma@hotmail.com>
Date:   Sat Mar 9 17:43:40 2024 +0100

    fix(description): first sentence no longer repeats until max length (jackyzha0#981)

commit b30a200
Author: Aaron Pham <29749331+aarnphm@users.noreply.github.com>
Date:   Fri Mar 8 12:14:22 2024 -0500

    fix(i18n): make sure to use correct fileData for manual localization (jackyzha0#975)

    Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

commit 6d59aa8
Author: Emile Bangma <ewjbangma@hotmail.com>
Date:   Fri Mar 8 10:04:44 2024 +0100

    fix(description): counts characters instead of words (jackyzha0#972)

    * fix(description): make sure description counts characters instead of words

    * ref: removed duplicate ternary

* CI: fix package log post merge

* CI: fix more merge artifacts

* CI: fix package-lock.json

* feat: add new default image template

* feat: use icon.png for image generation

* chore: update satori and sharp version

* feat(image-generator): add new default template

* Update quartz/components/Head.tsx

* Update quartz/components/Head.tsx

* Update quartz/components/Head.tsx

* Update docs/features/social images.md

* Update quartz/components/Head.tsx

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

* feat(og-image): add config option to use default og image for root path

* docs(og-image): add `excludeRoot` config + update preview images

* docs(open-graph): add examples section

* chore: remove unused `socialImage2.tsx` component

* feat(open-graph): add frontmatter aliases for socialImage/cover/image

* fix(open-graph): only load satori font if config option is enabled

* refactor(open-graph): dont use async promise inside `fetchTtf()`

* chore: renaming and finished copywriting

Signed-off-by: Aaron Pham <contact@aarnphm.xyz>

* chore: update typo

Signed-off-by: Aaron Pham <contact@aarnphm.xyz>

* chore: update hinting for socialImage

Signed-off-by: Aaron Pham <contact@aarnphm.xyz>

---------

Signed-off-by: Aaron Pham <contact@aarnphm.xyz>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
Co-authored-by: Emile Bangma <ewjbangma@hotmail.com>
Co-authored-by: Emile Bangma <github@emilebangma.com>
Co-authored-by: Aaron Pham <contact@aarnphm.xyz>
  • Loading branch information
5 people authored Nov 12, 2024
1 parent 9188939 commit 137d55e
Show file tree
Hide file tree
Showing 14 changed files with 1,446 additions and 8 deletions.
401 changes: 401 additions & 0 deletions docs/features/social images.md

Large diffs are not rendered by default.

Binary file added docs/images/custom-social-image-preview-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/social-image-preview-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/social-image-preview-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
648 changes: 648 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@
"remark-smartypants": "^3.0.2",
"rfdc": "^1.4.1",
"rimraf": "^6.0.1",
"satori": "^0.10.14",
"serve-handler": "^6.1.6",
"sharp": "^0.33.5",
"shiki": "^1.22.2",
"source-map-support": "^0.5.21",
"to-vfile": "^8.0.0",
Expand Down
1 change: 1 addition & 0 deletions quartz.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const config: QuartzConfig = {
baseUrl: "quartz.jzhao.xyz",
ignorePatterns: ["private", "templates", ".obsidian"],
defaultDateType: "created",
generateSocialImages: false,
theme: {
fontOrigin: "googleFonts",
cdnCaching: true,
Expand Down
7 changes: 6 additions & 1 deletion quartz/cfg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ValidDateType } from "./components/Date"
import { QuartzComponent } from "./components/types"
import { ValidLocale } from "./i18n"
import { PluginTypes } from "./plugins/types"
import { SocialImageOptions } from "./util/og"
import { Theme } from "./util/theme"

export type Analytics =
Expand Down Expand Up @@ -60,11 +61,15 @@ export interface GlobalConfiguration {
* Quartz will avoid using this as much as possible and use relative URLs most of the time
*/
baseUrl?: string
/**
* Whether to generate social images (Open Graph and Twitter standard) for link previews
*/
generateSocialImages: boolean | Partial<SocialImageOptions>
theme: Theme
/**
* Allow to translate the date in the language of your choice.
* Also used for UI translation (default: en-US)
* Need to be formated following BCP 47: https://en.wikipedia.org/wiki/IETF_language_tag
* Need to be formatted following BCP 47: https://en.wikipedia.org/wiki/IETF_language_tag
* The first part is the language (en) and the second part is the script/region (US)
* Language Codes: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
* Region Codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
Expand Down
9 changes: 9 additions & 0 deletions quartz/cli/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,15 @@ export async function handleBuild(argv) {
source: "**/*.*",
headers: [{ key: "Content-Disposition", value: "inline" }],
},
{
source: "**/*.webp",
headers: [{ key: "Content-Type", value: "image/webp" }],
},
// fixes bug where avif images are displayed as text instead of images (future proof)
{
source: "**/*.avif",
headers: [{ key: "Content-Type", value: "image/avif" }],
},
],
})
const status = res.statusCode
Expand Down
172 changes: 165 additions & 7 deletions quartz/components/Head.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,156 @@ import { FullSlug, joinSegments, pathToRoot } from "../util/path"
import { CSSResourceToStyleElement, JSResourceToScriptElement } from "../util/resources"
import { googleFontHref } from "../util/theme"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import satori, { SatoriOptions } from "satori"
import fs from "fs"
import sharp from "sharp"
import { ImageOptions, SocialImageOptions, getSatoriFont, defaultImage } from "../util/og"
import { unescapeHTML } from "../util/escape"

/**
* Generates social image (OG/twitter standard) and saves it as `.webp` inside the public folder
* @param opts options for generating image
*/
async function generateSocialImage(
{ cfg, description, fileName, fontsPromise, title, fileData }: ImageOptions,
userOpts: SocialImageOptions,
imageDir: string,
) {
const fonts = await fontsPromise
const { width, height } = userOpts

// JSX that will be used to generate satori svg
const imageComponent = userOpts.imageStructure(cfg, userOpts, title, description, fonts, fileData)

const svg = await satori(imageComponent, { width, height, fonts })

// Convert svg directly to webp (with additional compression)
const compressed = await sharp(Buffer.from(svg)).webp({ quality: 40 }).toBuffer()

// Write to file system
const filePath = joinSegments(imageDir, `${fileName}.${extension}`)
fs.writeFileSync(filePath, compressed)
}

const extension = "webp"

const defaultOptions: SocialImageOptions = {
colorScheme: "lightMode",
width: 1200,
height: 630,
imageStructure: defaultImage,
excludeRoot: false,
}

export default (() => {
const Head: QuartzComponent = ({ cfg, fileData, externalResources }: QuartzComponentProps) => {
let fontsPromise: Promise<SatoriOptions["fonts"]>

let fullOptions: SocialImageOptions
const Head: QuartzComponent = ({
cfg,
fileData,
externalResources,
ctx,
}: QuartzComponentProps) => {
// Initialize options if not set
if (!fullOptions) {
if (typeof cfg.generateSocialImages !== "boolean") {
fullOptions = { ...defaultOptions, ...cfg.generateSocialImages }
} else {
fullOptions = defaultOptions
}
}

// Memoize google fonts
if (!fontsPromise && cfg.generateSocialImages) {
fontsPromise = getSatoriFont(cfg.theme.typography.header, cfg.theme.typography.body)
}

const slug = fileData.filePath
// since "/" is not a valid character in file names, replace with "-"
const fileName = slug?.replaceAll("/", "-")

// Get file description (priority: frontmatter > fileData > default)
const fdDescription =
fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description
const titleSuffix = cfg.pageTitleSuffix ?? ""
const title =
(fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title) + titleSuffix
const description =
fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description
let description = ""
if (fdDescription) {
description = unescapeHTML(fdDescription)
}

if (fileData.frontmatter?.socialDescription) {
description = fileData.frontmatter?.socialDescription as string
} else if (fileData.frontmatter?.description) {
description = fileData.frontmatter?.description
}

const fileDir = joinSegments(ctx.argv.output, "static", "social-images")
if (cfg.generateSocialImages) {
// Generate folders for social images (if they dont exist yet)
if (!fs.existsSync(fileDir)) {
fs.mkdirSync(fileDir, { recursive: true })
}

if (fileName) {
// Generate social image (happens async)
generateSocialImage(
{
title,
description,
fileName,
fileDir,
fileExt: extension,
fontsPromise,
cfg,
fileData,
},
fullOptions,
fileDir,
)
}
}

const { css, js } = externalResources

const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
const path = url.pathname as FullSlug
const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!)

const iconPath = joinSegments(baseDir, "static/icon.png")
const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png`

const ogImageDefaultPath = `https://${cfg.baseUrl}/static/og-image.png`
// "static/social-images/slug-filename.md.webp"
const ogImageGeneratedPath = `https://${cfg.baseUrl}/${fileDir.replace(
`${ctx.argv.output}/`,
"",
)}/${fileName}.${extension}`

// Use default og image if filePath doesnt exist (for autogenerated paths with no .md file)
const useDefaultOgImage = fileName === undefined || !cfg.generateSocialImages

// Path to og/social image (priority: frontmatter > generated image (if enabled) > default image)
let ogImagePath = useDefaultOgImage ? ogImageDefaultPath : ogImageGeneratedPath

// TODO: could be improved to support external images in the future
// Aliases for image and cover handled in `frontmatter.ts`
const frontmatterImgUrl = fileData.frontmatter?.socialImage

// Override with default og image if config option is set
if (fileData.slug === "index") {
ogImagePath = ogImageDefaultPath
}

// Override with frontmatter url if existing
if (frontmatterImgUrl) {
ogImagePath = `https://${cfg.baseUrl}/static/${frontmatterImgUrl}`
}

// Url of current page
const socialUrl =
fileData.slug === "404" ? url.toString() : joinSegments(url.toString(), fileData.slug!)

return (
<head>
Expand All @@ -32,11 +166,35 @@ export default (() => {
</>
)}
<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: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} />
{cfg.baseUrl && <meta property="og:image" content={ogImagePath} />}
<meta property="og:width" content="1200" />
<meta property="og:height" content="675" />
<meta property="og:image:type" content={`image/${extension}`} />
<meta property="og:image:alt" content={description} />
{/* Dont set width and height if unknown (when using custom frontmatter image) */}
{!frontmatterImgUrl && (
<>
<meta property="og:image:width" content={fullOptions.width.toString()} />
<meta property="og:image:height" content={fullOptions.height.toString()} />
<meta property="og:width" content={fullOptions.width.toString()} />
<meta property="og:height" content={fullOptions.height.toString()} />
</>
)}
<meta property="og:image:url" content={ogImagePath} />
{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={socialUrl}></meta>
<meta property="twitter:url" content={socialUrl}></meta>
</>
)}
<link rel="icon" href={iconPath} />
<meta name="description" content={description} />
<meta name="generator" content="Quartz" />
Expand Down
5 changes: 5 additions & 0 deletions quartz/plugins/transformers/frontmatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options>> = (userOpts)
const cssclasses = coerceToArray(coalesceAliases(data, ["cssclasses", "cssclass"]))
if (cssclasses) data.cssclasses = cssclasses

const socialImage = coalesceAliases(data, ["socialImage", "image", "cover"])

if (socialImage) data.socialImage = socialImage

// fill in frontmatter
file.data.frontmatter = data as QuartzPluginData["frontmatter"]
}
Expand All @@ -93,6 +97,7 @@ declare module "vfile" {
lang: string
enableToc: string
cssclasses: string[]
socialImage: string
comments: boolean | string
}>
}
Expand Down
9 changes: 9 additions & 0 deletions quartz/util/escape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ export const escapeHTML = (unsafe: string) => {
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;")
}

export const unescapeHTML = (html: string) => {
return html
.replaceAll("&amp;", "&")
.replaceAll("&lt;", "<")
.replaceAll("&gt;", ">")
.replaceAll("&quot;", '"')
.replaceAll("&#039;", "'")
}
Loading

0 comments on commit 137d55e

Please sign in to comment.