-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add automatic og:image generation to docs (#5867)
## Summary This pr includes changes to dynamically build OG Images for every single subpage in docs. ## How does it work? The algorithm runs after docusaurus build. It reads titles from markdown files in docs dictionary, generates images and place .png files in the build dictionary. Docusaurus has been configured to automatically link to generated OG Images. ## How to test this feature? Command to change current working dictionary: > cd docs Command to download libraries: > yarn install Command to build project and generete OG Images: > yarn build Command to see the result: > yarn serve --------- Co-authored-by: Kacper Kapuściak <39658211+kacperkapusciak@users.noreply.github.com> Co-authored-by: Patrycja Kalińska <59940332+patrycjakalinska@users.noreply.github.com>
- Loading branch information
1 parent
41ff27a
commit 7531e83
Showing
6 changed files
with
520 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { createWriteStream } from 'fs'; | ||
import { pipeline } from 'stream'; | ||
import { promisify } from 'util'; | ||
import React from 'react'; | ||
import path from 'path'; | ||
import fs from 'fs'; | ||
import OGImageStream from './og-image-stream'; | ||
|
||
const getMarkdownHeader = (path) => { | ||
const content = fs.readFileSync(path, 'utf-8'); | ||
const headers = content | ||
.split('\n') | ||
.map((line) => line.trim()) | ||
.filter((line) => line.startsWith('#')) | ||
.map((line, index) => ({ | ||
level: line.match(/#/g).length, | ||
title: line.replace(/#/g, '').trim(), | ||
index, | ||
})) | ||
.sort((a, b) => a.level - b.level || a.index - b.index); | ||
|
||
return headers[0]?.title || 'React Native Reanimated'; | ||
}; | ||
|
||
async function saveStreamToFile(stream, path) { | ||
const writeStream = createWriteStream(path); | ||
await promisify(pipeline)(stream, writeStream); | ||
} | ||
|
||
async function buildOGImages() { | ||
const baseDirPath = path.resolve(__dirname, '../docs'); | ||
const dirs = await Promise.all( | ||
( | ||
await fs.promises.readdir(baseDirPath) | ||
).map(async (dir) => { | ||
const files = await fs.promises.readdir(path.resolve(baseDirPath, dir)); | ||
return { | ||
dir, | ||
files: files.filter( | ||
(file) => file.endsWith('.md') || file.endsWith('.mdx') | ||
), | ||
}; | ||
}) | ||
); | ||
|
||
const ogImageTargets = path.resolve(__dirname, '../build/img/og'); | ||
|
||
if (fs.existsSync(ogImageTargets)) { | ||
fs.rmSync(ogImageTargets, { recursive: true }); | ||
} | ||
|
||
fs.mkdirSync(ogImageTargets, { recursive: true }); | ||
|
||
console.log('Generating OG images for docs...'); | ||
|
||
const imagePath = path.resolve(__dirname, '../unproccessed/og-image.png'); | ||
const imageBuffer = fs.readFileSync(imagePath); | ||
const base64Image = `data:image/png;base64,${imageBuffer.toString('base64')}`; | ||
|
||
dirs.map(async ({ dir, files }) => { | ||
files.map(async (file) => { | ||
const header = getMarkdownHeader(path.resolve(baseDirPath, dir, file)); | ||
|
||
const ogImageStream = OGImageStream(header, base64Image); | ||
|
||
await saveStreamToFile( | ||
await ogImageStream, | ||
path.resolve( | ||
ogImageTargets, | ||
`${header.replace(/ /g, '-').replace('/', '-').toLowerCase()}.png` | ||
) | ||
); | ||
}); | ||
}); | ||
} | ||
|
||
buildOGImages(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import React from 'react'; | ||
import { ImageResponse } from '@vercel/og'; | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
|
||
export default async function OGImageStream(header, base64Image) { | ||
return new ImageResponse( | ||
( | ||
<div | ||
style={{ | ||
display: 'flex', | ||
fontSize: 40, | ||
color: 'black', | ||
background: 'white', | ||
width: '100%', | ||
height: '100%', | ||
padding: '50px 200px', | ||
textAlign: 'center', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
}}> | ||
<img | ||
style={{ | ||
width: 1200, | ||
height: 630, | ||
objectFit: 'cover', | ||
position: 'absolute', | ||
top: 0, | ||
left: 0, | ||
}} | ||
src={base64Image} | ||
alt="" | ||
/> | ||
<div | ||
style={{ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
width: 1200, | ||
gap: -20, | ||
padding: '0 201px 0 67px', | ||
}}> | ||
<p | ||
style={{ | ||
fontSize: 72, | ||
fontWeight: 'bold', | ||
color: '#001A72', | ||
textAlign: 'left', | ||
fontFamily: 'Aeonik Bold', | ||
textWrap: 'wrap', | ||
}}> | ||
{header} | ||
</p> | ||
<pre | ||
style={{ | ||
fontSize: 40, | ||
fontWeight: 'normal', | ||
color: '#001A72', | ||
textAlign: 'left', | ||
fontFamily: 'Aeonik Regular', | ||
textWrap: 'wrap', | ||
}}> | ||
{'Check out the React Native\nReanimated documentation.'} | ||
</pre> | ||
</div> | ||
</div> | ||
), | ||
{ | ||
width: 1200, | ||
height: 630, | ||
fonts: [ | ||
{ | ||
name: 'Aeonik Bold', | ||
data: fs.readFileSync( | ||
path.resolve(__dirname, '../static/font/Aeonik-Bold.otf') | ||
), | ||
style: 'normal', | ||
}, | ||
{ | ||
name: 'Aeonik Regular', | ||
data: fs.readFileSync( | ||
path.resolve(__dirname, '../static/font/Aeonik-Regular.otf') | ||
), | ||
style: 'normal', | ||
}, | ||
], | ||
} | ||
).body; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React from 'react'; | ||
import { PageMetadata } from '@docusaurus/theme-common'; | ||
import { useDoc } from '@docusaurus/theme-common/internal'; | ||
import useBaseUrl from '@docusaurus/useBaseUrl'; | ||
export default function DocItemMetadata() { | ||
const { metadata, frontMatter, assets } = useDoc(); | ||
|
||
if (!metadata.title) { | ||
return null; | ||
} | ||
|
||
const ogImageName = metadata.title | ||
.replace(/ /g, '-') | ||
.replace('/', '-') | ||
.toLowerCase(); | ||
|
||
return ( | ||
<PageMetadata | ||
title={metadata.title} | ||
description={metadata.description} | ||
keywords={frontMatter.keywords} | ||
image={`img/og/${ | ||
ogImageName === '' || !ogImageName | ||
? 'React Native Reanimated' | ||
: ogImageName | ||
}.png`} | ||
/> | ||
); | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.