mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-05-19 06:54:18 +02:00
Compare commits
No commits in common. "de727b4686f20ef589b34c0a9e069265cf2728d8" and "f301eca9a72953aee5a65e55dfeaaa9c4b1b2516" have entirely different histories.
de727b4686
...
f301eca9a7
10
package-lock.json
generated
10
package-lock.json
generated
@ -75,6 +75,7 @@
|
|||||||
"quartz": "quartz/bootstrap-cli.mjs"
|
"quartz": "quartz/bootstrap-cli.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cli-spinner": "^0.2.3",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
@ -1584,6 +1585,15 @@
|
|||||||
"integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==",
|
"integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cli-spinner": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cli-spinner/-/cli-spinner-0.2.3.tgz",
|
||||||
|
"integrity": "sha512-TMO6mWltW0lCu1de8DMRq9+59OP/tEjghS+rs8ZEQ2EgYP5yV3bGw0tS14TMyJGqFaoVChNvhkVzv9RC1UgX+w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/css-font-loading-module": {
|
"node_modules/@types/css-font-loading-module": {
|
||||||
"version": "0.0.12",
|
"version": "0.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz",
|
||||||
|
@ -98,6 +98,7 @@
|
|||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cli-spinner": "^0.2.3",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
@ -87,7 +87,6 @@ const config: QuartzConfig = {
|
|||||||
Plugin.Assets(),
|
Plugin.Assets(),
|
||||||
Plugin.Static(),
|
Plugin.Static(),
|
||||||
Plugin.NotFoundPage(),
|
Plugin.NotFoundPage(),
|
||||||
Plugin.CustomOgImages(),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { i18n } from "../../i18n"
|
|||||||
import { unescapeHTML } from "../../util/escape"
|
import { unescapeHTML } from "../../util/escape"
|
||||||
import { FullSlug, getFileExtension } from "../../util/path"
|
import { FullSlug, getFileExtension } from "../../util/path"
|
||||||
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
|
import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og"
|
||||||
|
import { getFontSpecificationName } from "../../util/theme"
|
||||||
import sharp from "sharp"
|
import sharp from "sharp"
|
||||||
import satori from "satori"
|
import satori from "satori"
|
||||||
import { loadEmoji, getIconCode } from "../../util/emoji"
|
import { loadEmoji, getIconCode } from "../../util/emoji"
|
||||||
|
@ -4,7 +4,6 @@ import { ProcessedContent } from "../plugins/vfile"
|
|||||||
import { QuartzLogger } from "../util/log"
|
import { QuartzLogger } from "../util/log"
|
||||||
import { trace } from "../util/trace"
|
import { trace } from "../util/trace"
|
||||||
import { BuildCtx } from "../util/ctx"
|
import { BuildCtx } from "../util/ctx"
|
||||||
import chalk from "chalk"
|
|
||||||
|
|
||||||
export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
|
export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
|
||||||
const { argv, cfg } = ctx
|
const { argv, cfg } = ctx
|
||||||
@ -25,18 +24,14 @@ export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
|
|||||||
emittedFiles++
|
emittedFiles++
|
||||||
if (ctx.argv.verbose) {
|
if (ctx.argv.verbose) {
|
||||||
console.log(`[emit:${emitter.name}] ${file}`)
|
console.log(`[emit:${emitter.name}] ${file}`)
|
||||||
} else {
|
|
||||||
log.updateText(`Emitting output files: ${chalk.gray(file)}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Array case
|
// Array case
|
||||||
emittedFiles += emitted.length
|
emittedFiles += emitted.length
|
||||||
for (const file of emitted) {
|
if (ctx.argv.verbose) {
|
||||||
if (ctx.argv.verbose) {
|
for (const file of emitted) {
|
||||||
console.log(`[emit:${emitter.name}] ${file}`)
|
console.log(`[emit:${emitter.name}] ${file}`)
|
||||||
} else {
|
|
||||||
log.updateText(`Emitting output files: ${chalk.gray(file)}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,26 @@
|
|||||||
import readline from "readline"
|
import { Spinner } from "cli-spinner"
|
||||||
|
|
||||||
export class QuartzLogger {
|
export class QuartzLogger {
|
||||||
verbose: boolean
|
verbose: boolean
|
||||||
private spinnerInterval: NodeJS.Timeout | undefined
|
spinner: Spinner | undefined
|
||||||
private spinnerText: string = ""
|
|
||||||
private spinnerIndex: number = 0
|
|
||||||
private readonly spinnerChars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
||||||
|
|
||||||
constructor(verbose: boolean) {
|
constructor(verbose: boolean) {
|
||||||
this.verbose = verbose
|
this.verbose = verbose
|
||||||
}
|
}
|
||||||
|
|
||||||
start(text: string) {
|
start(text: string) {
|
||||||
this.spinnerText = text
|
|
||||||
if (this.verbose) {
|
if (this.verbose) {
|
||||||
console.log(text)
|
console.log(text)
|
||||||
} else {
|
} else {
|
||||||
this.spinnerIndex = 0
|
this.spinner = new Spinner(`%s ${text}`)
|
||||||
this.spinnerInterval = setInterval(() => {
|
this.spinner.setSpinnerString(18)
|
||||||
readline.clearLine(process.stdout, 0)
|
this.spinner.start()
|
||||||
readline.cursorTo(process.stdout, 0)
|
|
||||||
process.stdout.write(`${this.spinnerChars[this.spinnerIndex]} ${this.spinnerText}`)
|
|
||||||
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerChars.length
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateText(text: string) {
|
|
||||||
this.spinnerText = text
|
|
||||||
}
|
|
||||||
|
|
||||||
end(text?: string) {
|
end(text?: string) {
|
||||||
if (!this.verbose && this.spinnerInterval) {
|
if (!this.verbose) {
|
||||||
clearInterval(this.spinnerInterval)
|
this.spinner!.stop(true)
|
||||||
this.spinnerInterval = undefined
|
|
||||||
readline.clearLine(process.stdout, 0)
|
|
||||||
readline.cursorTo(process.stdout, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
console.log(text)
|
console.log(text)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { promises as fs } from "fs"
|
|
||||||
import { FontWeight, SatoriOptions } from "satori/wasm"
|
import { FontWeight, SatoriOptions } from "satori/wasm"
|
||||||
import { GlobalConfiguration } from "../cfg"
|
import { GlobalConfiguration } from "../cfg"
|
||||||
import { QuartzPluginData } from "../plugins/vfile"
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
import { JSXInternal } from "preact/src/jsx"
|
import { JSXInternal } from "preact/src/jsx"
|
||||||
import { FontSpecification, ThemeKey } from "./theme"
|
import { FontSpecification, ThemeKey } from "./theme"
|
||||||
import path from "path"
|
|
||||||
import { QUARTZ } from "./path"
|
|
||||||
|
|
||||||
const defaultHeaderWeight = [700]
|
const defaultHeaderWeight = [700]
|
||||||
const defaultBodyWeight = [400]
|
const defaultBodyWeight = [400]
|
||||||
@ -51,55 +48,48 @@ export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: Fo
|
|||||||
return fonts
|
return fonts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache for memoizing font data
|
||||||
|
const fontCache = new Map<string, Promise<ArrayBuffer>>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the `.ttf` file of a google font
|
* Get the `.ttf` file of a google font
|
||||||
* @param fontName name of google font
|
* @param fontName name of google font
|
||||||
* @param weight what font weight to fetch font
|
* @param weight what font weight to fetch font
|
||||||
* @returns `.ttf` file of google font
|
* @returns `.ttf` file of google font
|
||||||
*/
|
*/
|
||||||
export async function fetchTtf(
|
export async function fetchTtf(fontName: string, weight: FontWeight): Promise<ArrayBuffer> {
|
||||||
fontName: string,
|
const cacheKey = `${fontName}-${weight}`
|
||||||
weight: FontWeight,
|
if (fontCache.has(cacheKey)) {
|
||||||
): Promise<Buffer<ArrayBufferLike>> {
|
return fontCache.get(cacheKey)!
|
||||||
const cacheKey = `${fontName.replaceAll(" ", "-")}-${weight}`
|
|
||||||
const cacheDir = path.join(QUARTZ, ".quartz-cache", "fonts")
|
|
||||||
const cachePath = path.join(cacheDir, cacheKey)
|
|
||||||
|
|
||||||
// Check if font exists in cache
|
|
||||||
try {
|
|
||||||
await fs.access(cachePath)
|
|
||||||
return fs.readFile(cachePath)
|
|
||||||
} catch (error) {
|
|
||||||
// ignore errors and fetch font
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get css file from google fonts
|
// If not in cache, fetch and store the promise
|
||||||
const cssResponse = await fetch(
|
const fontPromise = (async () => {
|
||||||
`https://fonts.googleapis.com/css2?family=${fontName}:wght@${weight}`,
|
try {
|
||||||
)
|
// Get css file from google fonts
|
||||||
const css = await cssResponse.text()
|
const cssResponse = await fetch(
|
||||||
|
`https://fonts.googleapis.com/css2?family=${fontName}:wght@${weight}`,
|
||||||
|
)
|
||||||
|
const css = await cssResponse.text()
|
||||||
|
|
||||||
// Extract .ttf url from css file
|
// Extract .ttf url from css file
|
||||||
const urlRegex = /url\((https:\/\/fonts.gstatic.com\/s\/.*?.ttf)\)/g
|
const urlRegex = /url\((https:\/\/fonts.gstatic.com\/s\/.*?.ttf)\)/g
|
||||||
const match = urlRegex.exec(css)
|
const match = urlRegex.exec(css)
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new Error("Could not fetch font")
|
throw new Error("Could not fetch font")
|
||||||
}
|
}
|
||||||
|
|
||||||
// fontData is an ArrayBuffer containing the .ttf file data
|
// fontData is an ArrayBuffer containing the .ttf file data (get match[1] due to google fonts response format, always contains link twice, but second entry is the "raw" link)
|
||||||
const fontResponse = await fetch(match[1])
|
const fontResponse = await fetch(match[1])
|
||||||
const fontData = Buffer.from(await fontResponse.arrayBuffer())
|
return await fontResponse.arrayBuffer()
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error fetching font: ${error}`)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
try {
|
fontCache.set(cacheKey, fontPromise)
|
||||||
await fs.mkdir(cacheDir, { recursive: true })
|
return fontPromise
|
||||||
await fs.writeFile(cachePath, fontData)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Failed to cache font: ${error}`)
|
|
||||||
// Continue even if caching fails
|
|
||||||
}
|
|
||||||
|
|
||||||
return fontData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SocialImageOptions = {
|
export type SocialImageOptions = {
|
||||||
@ -171,7 +161,7 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
|
|||||||
title: string,
|
title: string,
|
||||||
description: string,
|
description: string,
|
||||||
fonts: SatoriOptions["fonts"],
|
fonts: SatoriOptions["fonts"],
|
||||||
fileData: QuartzPluginData,
|
_fileData: QuartzPluginData,
|
||||||
) => {
|
) => {
|
||||||
const fontBreakPoint = 22
|
const fontBreakPoint = 22
|
||||||
const useSmallerFont = title.length > fontBreakPoint
|
const useSmallerFont = title.length > fontBreakPoint
|
||||||
@ -187,8 +177,8 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
backgroundColor: cfg.theme.colors[colorScheme].light,
|
backgroundColor: cfg.theme.colors[colorScheme].light,
|
||||||
gap: "1rem",
|
gap: "2rem",
|
||||||
padding: "3rem 3rem",
|
padding: "1.5rem 5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -197,36 +187,31 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: "2rem",
|
gap: "2.5rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<img src={iconPath} width={135} height={135} />
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
border: "1px solid red",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img src={iconPath} width={135} height={135} />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
color: cfg.theme.colors[colorScheme].dark,
|
color: cfg.theme.colors[colorScheme].dark,
|
||||||
maxWidth: "80%",
|
fontSize: useSmallerFont ? 70 : 82,
|
||||||
|
fontFamily: fonts[0].name,
|
||||||
|
maxWidth: "70%",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1
|
<p
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
fontSize: useSmallerFont ? 64 : 72,
|
|
||||||
fontFamily: fonts[0].name,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -236,7 +221,7 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
|
|||||||
fontSize: 44,
|
fontSize: 44,
|
||||||
fontFamily: fonts[1].name,
|
fontFamily: fonts[1].name,
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
maxHeight: "60%",
|
maxHeight: "40%",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -245,7 +230,7 @@ export const defaultImage: SocialImageOptions["imageStructure"] = (
|
|||||||
margin: 0,
|
margin: 0,
|
||||||
display: "-webkit-box",
|
display: "-webkit-box",
|
||||||
WebkitBoxOrient: "vertical",
|
WebkitBoxOrient: "vertical",
|
||||||
WebkitLineClamp: 5,
|
WebkitLineClamp: 3,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
}}
|
}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user