Skip to content

Commit

Permalink
feat: auto generate opengraph images
Browse files Browse the repository at this point in the history
  • Loading branch information
jeangovil committed Jun 19, 2023
1 parent 344d9f9 commit c442f5d
Show file tree
Hide file tree
Showing 43 changed files with 1,486 additions and 152 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ coverage
.cache-loader

packages/logos-*/lib/*
packages/docusaurus-*/lib/*
packages/logos-docusaurus-preset/static/common/data/team.json

lerna-debug.log
35 changes: 35 additions & 0 deletions packages/docusaurus-og/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@acid-info/docusaurus-og",
"version": "1.0.0-alpha.44",
"description": "Docusaurus local search plugin",
"main": "lib/index.js",
"types": "src/plugin.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/acid-info/logos-docusaurus-plugins.git",
"directory": "packages/docusaurus-search-local"
},
"license": "MIT",
"scripts": {
"build": "tsc --build",
"watch": "tsc --build --watch",
"prepublishOnly": "yarn build"
},
"dependencies": {
"@docusaurus/core": "^2.4.1",
"@docusaurus/module-type-aliases": "^2.4.1",
"@docusaurus/types": "^2.4.1",
"@docusaurus/utils": "^2.4.1",
"@docusaurus/utils-common": "^2.4.1",
"@docusaurus/utils-validation": "^2.4.1",
"@easyops-cn/docusaurus-search-local": "^0.33.6",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=16.14"
},
"devDependencies": {
"@types/lodash": "^4.14.186"
},
"gitHead": "d2ee08c6c0678f78ad70f5d04183f7f781d11563"
}
19 changes: 19 additions & 0 deletions packages/docusaurus-og/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { LoadContext, Plugin } from '@docusaurus/types'
import { postBuildFactory } from './server/index'
import { PluginOptions } from './server/types/plugin.types'
export { imageRendererFactory } from './server/imageRenderer.factory'
export * from './server/types'
export type { PluginOptions }

export default function logosTheme(
context: LoadContext,
options: PluginOptions,
): Plugin<any> {
return {
name: 'docusaurus-og',

async postBuild(props) {
await postBuildFactory(options)(props)
},
}
}
1 change: 1 addition & 0 deletions packages/docusaurus-og/src/plugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type * from './index'
125 changes: 125 additions & 0 deletions packages/docusaurus-og/src/server/blog.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {
BlogContent,
PluginOptions as BlogPluginOptions,
} from '@docusaurus/plugin-content-blog'
import { LoadedPlugin, Props } from '@docusaurus/types'
import * as fs from 'fs'
import * as path from 'path'
import { Document } from './document'
import { ImageGenerator } from './imageGenerator'
import { BlogPageData } from './types/blog.types'
import { ImageRenderer } from './types/image.types'
import { PluginOptions } from './types/plugin.types'

export class BlogPlugin {
static plugin = 'docusaurus-plugin-content-blog'

pages: BlogPageData[] = []

constructor(
private context: Props,
private options: PluginOptions,
private imageGenerator: ImageGenerator,
private imageRenderer: ImageRenderer,
) {}

process = async () => {
await this.loadData()
await this.generate()
}

loadData = async () => {
const plugins = this.context.plugins.filter(
(plugin) => plugin.name === BlogPlugin.plugin,
)

for (const plugin of plugins) {
await this.loadInstance(plugin)
}
}

loadInstance = async (plugin: LoadedPlugin) => {
const content = plugin.content as BlogContent
const options = plugin.options as BlogPluginOptions

content.blogListPaginated.forEach((value) => {
this.pages.push({
data: value,
plugin: options,
pageType: 'list',
permalink: value.metadata.permalink,
})
})

content.blogPosts.forEach((post) => {
this.pages.push({
data: post,
plugin: options,
pageType: 'post',
permalink: post.metadata.permalink,
})
})

if (content.blogTagsListPath) {
const filePath = this.getHtmlPath(content.blogTagsListPath)
fs.existsSync(filePath) &&
this.pages.push({
pageType: 'tags',
plugin: options,
data: {
permalink: content.blogTagsListPath,
},
permalink: content.blogTagsListPath,
})
}

if (options.archiveBasePath) {
this.pages.push({
plugin: options,
pageType: 'archive',
data: { permalink: options.archiveBasePath },
permalink: options.archiveBasePath,
})
}

{
Object.entries(content.blogTags).map(([key, value]) => {
value.pages.forEach((page) => {
this.pages.push({
pageType: 'tag',
plugin: options,
data: { ...page.metadata, label: value.label },
permalink: page.metadata.permalink,
})
})
})
}
}

generate = async () => {
for (const page of this.pages) {
const image = await this.imageRenderer(
{
...page,
websiteOutDir: this.context.outDir,
},
this.context,
)

if (!image) continue

const generated = await this.imageGenerator.generate(...image)
const document = new Document(this.getHtmlPath(page.permalink))

await document.load()
if (!document.loaded) continue

await document.setImage(generated.url)

await document.write()
}
}

getHtmlPath = (permalink: string) =>
path.join(this.context.outDir, permalink, 'index.html')
}
87 changes: 87 additions & 0 deletions packages/docusaurus-og/src/server/docs.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
LoadedContent,
LoadedVersion,
PluginOptions as DocsPluginOptions,
} from '@docusaurus/plugin-content-docs'
import { LoadedPlugin, Props } from '@docusaurus/types'
import * as path from 'path'
import { Document } from './document'
import { ImageGenerator } from './imageGenerator'
import { DocsPageData } from './types/docs.types'
import { ImageRenderer } from './types/image.types'
import { PluginOptions } from './types/plugin.types'

export class DocsPlugin {
static plugin = 'docusaurus-plugin-content-docs'

docs: DocsPageData[] = []

constructor(
private context: Props,
private options: PluginOptions,
private imageGenerator: ImageGenerator,
private imageRenderer: ImageRenderer,
) {}

process = async () => {
await this.loadData()
await this.generate()
}

loadData = async () => {
const { plugins = [] } = this.context

const docPlugins = plugins.filter(
(plugin) => plugin.name === DocsPlugin.plugin,
)

for (const plugin of docPlugins) {
await this.loadInstance(plugin)
}
}

loadInstance = async (plugin: LoadedPlugin) => {
const content = plugin.content as LoadedContent
const options = plugin.options as DocsPluginOptions

const { loadedVersions } = content

for (const version of loadedVersions) {
await this.loadVersion(options, version)
}
}

loadVersion = async (options: DocsPluginOptions, version: LoadedVersion) => {
this.docs.push(
...version.docs.map((doc) => ({
version,
metadata: doc,
plugin: options,
})),
)
}

generate = async () => {
for (const doc of this.docs) {
const image = await this.imageRenderer(
{
...doc,
websiteOutDir: this.context.outDir,
},
this.context,
)

if (!image) continue

const generated = await this.imageGenerator.generate(...image)
const document = new Document(this.getHtmlPath(doc))
await document.load()
await document.setImage(generated.url)

await document.write()
}
}

getHtmlPath = (doc: DocsPageData) =>
path.join(this.context.outDir, doc.metadata.permalink, 'index.html')
}
57 changes: 57 additions & 0 deletions packages/docusaurus-og/src/server/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as fsp from 'fs/promises'
import { HTMLElement, parse as parseHTML } from 'node-html-parser'

export class Document {
root: HTMLElement
loaded = false

constructor(private path: string) {}

load = async () => {
const htmlString = await fsp.readFile(this.path, 'utf-8')
this.root = parseHTML(htmlString)
this.loaded = true
}

write = async () => {
await fsp.writeFile(this.path, Buffer.from(this.root.outerHTML))
}

setImage = async (url: string) => {
this.updateMeta('property', 'og:image', {
content: url,
})
this.updateMeta('property', 'image', {
content: url,
})
}

get head() {
return this.root.querySelector('head')!
}

private getMeta(attr: string, value: string) {
const { head } = this

let meta = head.querySelector(`meta[${attr}=${value}]`)

if (!meta) {
meta = new HTMLElement('meta', {}, '', null, [0, 0])
meta.setAttribute(attr, value)
head.appendChild(meta)
}

return meta
}

private updateMeta = (
attr: string,
value: string,
attrs: Record<string, any>,
) => {
const el = this.getMeta(attr, value)
Object.entries(attrs).forEach(([key, value]) => el.setAttribute(key, value))

return el
}
}

0 comments on commit c442f5d

Please sign in to comment.