Skip to content

Commit

Permalink
feat: use user image in auto-generated preview images
Browse files Browse the repository at this point in the history
  • Loading branch information
jeangovil committed Jun 21, 2023
1 parent a69e6e1 commit 650fd8c
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 25 deletions.
3 changes: 3 additions & 0 deletions packages/logos-docusaurus-theme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@
"@react-three/drei": "8.20.2",
"@react-three/fiber": "6.2.3",
"@tryghost/content-api": "^1.11.4",
"axios": "^1.4.0",
"boolean": "^3.2.0",
"clsx": "^1.2.1",
"copy-text-to-clipboard": "^3.0.1",
"copy-to-clipboard": "^3.3.2",
"date-fns": "^2.30.0",
"docusaurus-plugin-sass": "^0.2.3",
"dotenv": "^16.0.3",
"lodash": "^4.17.21",
"object-hash": "^3.0.0",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
import {
BlogPageData,
DocsPageData,
ImageGeneratorOptions,
imageRendererFactory,
PageData,
} from '@acid-info/docusaurus-og'
import { BlogPostFrontMatter } from '@docusaurus/plugin-content-blog'
import { DocFrontMatter } from '@docusaurus/plugin-content-docs'
import { MDXPageMetadata } from '@docusaurus/plugin-content-pages'
import { DocusaurusConfig } from '@docusaurus/types'
import axios from 'axios'
import { boolean } from 'boolean'
import { readFileSync } from 'fs'
import * as fsp from 'fs/promises'
import _ from 'lodash'
import hashObject from 'object-hash'
import path from 'path'
import React from 'react'
import sharp from 'sharp'
import sharp, { ResizeOptions } from 'sharp'

const shouldSkip = (
frontMatter:
| DocFrontMatter
| BlogPostFrontMatter
| MDXPageMetadata['frontMatter']
| undefined
| null,
) =>
frontMatter &&
typeof frontMatter['og:generate_image'] !== 'undefined' &&
!boolean(frontMatter['og:generate_image'])

const options: ImageGeneratorOptions = {
width: 1200,
Expand All @@ -26,9 +49,10 @@ const options: ImageGeneratorOptions = {

const Layout: React.FC<{
logo?: React.ReactNode
image?: React.ReactNode
title: React.ReactNode
footer: React.ReactNode | Array<React.ReactNode | undefined | false>
}> = ({ title, footer, logo }) => {
}> = ({ title, footer, logo, image }) => {
const dot = (
<div
style={{
Expand All @@ -55,6 +79,69 @@ const Layout: React.FC<{
? footer.filter((item) => !!item)
: [footer]

if (image) {
return (
<div
style={{
width: '100%',
height: '100%',
color: 'white',
background: 'black',
display: 'flex',
flexDirection: 'row',
}}
>
<div
style={{
width: '50%',
height: '100%',
color: 'white',
background: 'black',
display: 'flex',
flexDirection: 'column',
padding: '48px 40px',
gap: '16px',
justifyContent: 'space-between',
flexBasis: '50%',
overflow: 'hidden',
}}
>
<div style={{ display: 'flex', height: '120px', overflow: 'hidden' }}>
{logo}
</div>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ fontSize: '54px', display: 'flex' }}>{title}</div>
{footerItems.length > 0 && (
<div
style={{
marginTop: '24px',
fontSize: '32px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '0 24px',
}}
>
<div style={{ display: 'flex' }}>{footerItems[0]}</div>
</div>
)}
</div>
</div>
<div
style={{
display: 'flex',
width: '50%',
height: '100%',
flexBasis: '50%',
overflow: 'hidden',
}}
>
{image}
</div>
</div>
)
}

return (
<div
style={{
Expand Down Expand Up @@ -99,9 +186,10 @@ const Layout: React.FC<{
const docsImageRenderer = imageRendererFactory(
'docusaurus-plugin-content-docs',
async (doc, { outDir, siteConfig }) => {
if (doc.metadata.frontMatter.image) return
if (shouldSkip(doc.metadata.frontMatter)) return

const logo = await getLogo(siteConfig, outDir)
const image = await getPageImage('docs', outDir, doc)

return [
<Layout
Expand All @@ -114,7 +202,16 @@ const docsImageRenderer = imageRendererFactory(
<span>{doc.version.label}</span>
),
]}
logo={<img src={logo.src} style={{ height: 120 }} />}
logo={logo && <img src={logo.src as any} style={{ height: 120 }} />}
image={
image && (
<img
src={image.src as any}
width={image.width}
height={image.height}
/>
)
}
/>,
options,
]
Expand All @@ -124,12 +221,12 @@ const docsImageRenderer = imageRendererFactory(
const blogImageRenderer = imageRendererFactory(
'docusaurus-plugin-content-blog',
async (page, { outDir, siteConfig }) => {
if (page.pageType === 'post' && !!page.data.metadata.frontMatter.image)
return

const { pageType, data } = page
if (pageType === 'post' && shouldSkip(page.data.metadata.frontMatter))
return

const logo = await getLogo(siteConfig, outDir)
const image = await getPageImage('blog', outDir, page)

return [
<Layout
Expand All @@ -153,7 +250,16 @@ const blogImageRenderer = imageRendererFactory(
.map((author) => author.name)
.join(', ')}`,
]}
logo={<img src={logo.src} style={{ height: 120 }} />}
logo={logo && <img src={logo.src as any} style={{ height: 120 }} />}
image={
image && (
<img
src={image.src as any}
width={image.width}
height={image.height}
/>
)
}
/>,
options,
]
Expand All @@ -164,43 +270,110 @@ const pagesImageRenderer = imageRendererFactory(
'docusaurus-plugin-content-pages',
async (page, { outDir, siteConfig }) => {
const { metadata, plugin } = page
if (shouldSkip(_.get(metadata, 'frontMatter'))) return

const url = new URL(siteConfig.url)

const logo = await getLogo(siteConfig, outDir)
const image = await getPageImage('page', outDir, page)

return [
<Layout
title={metadata.title}
footer={url.host}
logo={<img src={logo.src} style={{ height: 120 }} />}
logo={logo && <img src={logo.src as any} style={{ height: 120 }} />}
image={
image && (
<img
src={image.src as any}
width={image.width}
height={image.height}
/>
)
}
/>,
options,
]
},
)

const getLogo = (() => {
const cache: Record<string, any> = {}
const getLogo = async (siteConfig: DocusaurusConfig, outDir: string) => {
const logo: any = (siteConfig.themeConfig as any).navbar.logo

return await loadImage(outDir, logo.src, { height: 120 })
}

const getPageImage = async (
type: 'docs' | 'blog' | 'page',
outDir: string,
page: DocsPageData | BlogPageData | PageData,
) => {
const frontMatter:
| DocFrontMatter
| BlogPostFrontMatter
| MDXPageMetadata['frontMatter']
| null
| undefined =
type === 'docs'
? (page as DocsPageData).metadata.frontMatter
: type === 'blog'
? _.get(page, 'data.metadata.frontMatter')
: _.get(page, 'metadata.frontMatter')

const image = frontMatter?.image

if (typeof image === 'string' && image.length > 0)
return await loadImage(outDir, image, {
width: options.width / 2,
height: options.height,
fit: 'cover',
})

return null
}

type LoadedImage = {
src: string | ArrayBuffer
width: number | undefined
height: number | undefined
}

const loadImage = (() => {
const cache: Record<string, LoadedImage> = {}

const download = async (url: string) =>
axios
.get(url, {
responseType: 'arraybuffer',
})
.then((response) => Buffer.from(response.data, 'binary'))

return async (
siteConfig: DocusaurusConfig,
outDir: string,
): Promise<{ src: any }> => {
const logo: any = (siteConfig.themeConfig as any).navbar.logo
return async (outDir: string, url: string, resize?: ResizeOptions) => {
const cacheKey = hashObject({ url, resize })

if (cache[logo.src]) return cache[logo.src]
if (cache[cacheKey]) return cache[cacheKey]

const isUrl = logo.src.startsWith('http')
const buffer: Buffer = url.startsWith('data:')
? Buffer.from(url, 'base64')
: url.startsWith('http:')
? await download(url)
: await fsp.readFile(path.join(outDir, url))

if (isUrl) return { src: logo.src }
let img = sharp(buffer)
if (resize) img = img.resize({ ...resize })

const logoPath = path.join(outDir, logo.src)
const output = await img.toBuffer()
const outputImage = sharp(output)
const outputMetadata = await outputImage.metadata()
const arrayBuffer = output.buffer

const img = sharp(logoPath)
const buffer = await img.resize({ height: 120 }).toBuffer()
const loadedImage: LoadedImage = (cache[cacheKey] = {
src: arrayBuffer,
width: outputMetadata.width,
height: outputMetadata.height,
})

cache[logo.src] = { src: buffer.buffer }
return cache[logo.src]
return loadedImage
}
})()

Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4501,7 +4501,7 @@ axios@^0.27.0:
follow-redirects "^1.14.9"
form-data "^4.0.0"

axios@^1.0.0:
axios@^1.0.0, axios@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
Expand Down Expand Up @@ -4840,6 +4840,11 @@ boolbase@^1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==

boolean@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b"
integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==

boxen@^5.0.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
Expand Down

0 comments on commit 650fd8c

Please sign in to comment.