From d36643d4a80b38f7051e00fa9de50e5a18a344cb Mon Sep 17 00:00:00 2001 From: joostdecock Date: Fri, 3 Nov 2023 13:34:47 +0100 Subject: [PATCH] feat(shared): Add OG images to prebuild --- .gitignore | 1 + sites/backend/src/controllers/img.mjs | 4 +- sites/dev/prebuild.mjs | 2 +- sites/dev/public/img/og/.gitkeep | 0 sites/org/public/img/og/.gitkeep | 0 sites/shared/prebuild/og.mjs | 39 +++++++ sites/shared/prebuild/og/index.mjs | 156 -------------------------- sites/shared/prebuild/runner.mjs | 5 +- 8 files changed, 45 insertions(+), 162 deletions(-) create mode 100644 sites/dev/public/img/og/.gitkeep create mode 100644 sites/org/public/img/og/.gitkeep create mode 100644 sites/shared/prebuild/og.mjs delete mode 100644 sites/shared/prebuild/og/index.mjs diff --git a/.gitignore b/.gitignore index 64bbf3c3f3b..081057434ae 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ sites/*/public/feeds/* packages/new-design/shared/public/locales/*/*.json packages/new-design/shared/.gitignore packages/new-design/lib/banner.mjs +sites/*/public/img/og/* # Lab auto-generated content sites/lab/lib diff --git a/sites/backend/src/controllers/img.mjs b/sites/backend/src/controllers/img.mjs index 26913877a31..1c7c787ed4c 100644 --- a/sites/backend/src/controllers/img.mjs +++ b/sites/backend/src/controllers/img.mjs @@ -127,7 +127,7 @@ export function ImgController() {} * Generate an Open Graph image * See: https://freesewing.dev/reference/backend/api */ -ImgController.prototype.generate = async (req, res, tools) => { +ImgController.prototype.generate = async (req, res) => { /* * Extract body parameters */ @@ -160,7 +160,7 @@ ImgController.prototype.generate = async (req, res, tools) => { */ sharp(Buffer.from(svg, 'utf-8')) .resize({ width: imgConfig.templates.sizes[type] }) - .toBuffer((err, data, info) => { + .toBuffer((err, data) => { if (err) console.log(err) return res.type('png').send(data) }) diff --git a/sites/dev/prebuild.mjs b/sites/dev/prebuild.mjs index af54f181d12..0669f097336 100644 --- a/sites/dev/prebuild.mjs +++ b/sites/dev/prebuild.mjs @@ -75,7 +75,7 @@ prebuildRunner({ * Only prebuild the Open Graph (og) images in production * Will be skipped in development mode to save time */ - ogImages: 'productionOnly', + ogImages: true, //'productionOnly', /* * Only prebuild the patron info in production diff --git a/sites/dev/public/img/og/.gitkeep b/sites/dev/public/img/og/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sites/org/public/img/og/.gitkeep b/sites/org/public/img/og/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sites/shared/prebuild/og.mjs b/sites/shared/prebuild/og.mjs new file mode 100644 index 00000000000..80ab5488948 --- /dev/null +++ b/sites/shared/prebuild/og.mjs @@ -0,0 +1,39 @@ +import axios from 'axios' +import { freeSewingConfig as config } from '../config/freesewing.config.mjs' +import get from 'lodash.get' +import fs from 'fs' +import path from 'path' + +const slugToImg = (slug, language) => `${language}_${slug.split('/').join('_')}.png` + +export const generateImage = async ({ title, intro, site, slug, language }) => { + let result + try { + result = await axios.post( + `${config.backend}/img`, + { title, intro, site, type: 'wide' }, + { responseType: 'arraybuffer' } + ) + const file = path.resolve('..', site, 'public', 'img', 'og', slugToImg(slug, language)) + await fs.promises.writeFile(file, result.data) + } catch (err) { + console.log(err) + } +} + +export const prebuildOgImages = async (store, mock) => { + if (mock) return + for (const [language, nav] of Object.entries(store.navigation.sitenav)) { + for (const slug of store.navigation.sluglut) { + await generateImage({ + title: get(nav, slug.split('/'))?.t || 'FIXME: No title', + intro: '', + site: store.site, + slug, + language, + }) + } + } + + return +} diff --git a/sites/shared/prebuild/og/index.mjs b/sites/shared/prebuild/og/index.mjs deleted file mode 100644 index 94b8cb40b3b..00000000000 --- a/sites/shared/prebuild/og/index.mjs +++ /dev/null @@ -1,156 +0,0 @@ -import fs_ from 'fs' -import path from 'path' -import { Buffer } from 'buffer' -import sharp from 'sharp' -import { capitalize } from '../../utils.mjs' - -const fs = fs_.promises - -const config = { - template: ['..', '..', 'artwork', 'og', 'template.svg'], - chars: { - title_1: 18, - title_2: 19, - title_3: 20, - intro: 34, - sub: 42, - }, -} - -// Load template once at startup -const template = fs_.readFileSync(path.resolve(...config.template), 'utf-8') - -/* Find longest possible place to split a string */ -const splitLine = (line, chars) => { - const words = line.split(' ') - if (words[0].length > chars) { - // Force a word break - return [line.slice(0, chars - 1) + '-', line.slice(chars - 1)] - } - // Glue chunks together until it's too long - let firstLine = '' - let max = false - for (const word of words) { - if (!max && `${firstLine}${word}`.length <= chars) firstLine += `${word} ` - else max = true - } - - return [firstLine, words.join(' ').slice(firstLine.length)] -} - -/* Divide title into lines to fit on image */ -const titleAsLines = (title) => { - // Does it fit on one line? - if (title.length <= config.chars.title_1) return [title] - // Does it fit on two lines? - let lines = splitLine(title, config.chars.title_1) - if (lines[1].length <= config.chars.title_2) return lines - // Three lines it is - return [lines[0], ...splitLine(lines[1], config.chars.title_2)] -} - -/* Divive intro into lines to fit on image */ -const introAsLines = (intro) => { - // Does it fit on one line? - if (intro.length <= config.chars.intro) return [intro] - // Two lines it is - return splitLine(intro, config.chars.intro) -} - -// Get title and intro -const getMetaData = async ({ slug, title, intro, site, lead = false }) => { - const chunks = slug.split('/') - if (site === 'dev') { - // Home page - if (chunks.length === 1 && chunks[0] === '') { - return { - title: ['FreeSewing.dev'], - intro: introAsLines('FreeSewing documentation for developers and contributors'), - sub: ['With guides, tutorials,', "How-to's and more"], - lead: '.dev', - } - } else { - // MDX page - return { - title: titleAsLines(title), - intro: introAsLines(intro), - sub: ['https://freesewing.dev/', slug], - lead: lead || capitalize(chunks[0]), - } - } - } -} - -/* Hide unused placeholders */ -const hidePlaceholders = (list) => { - let svg = template - for (const i of list) { - svg = svg.replace(`${i}title_1`, '').replace(`${i}title_2`, '').replace(`${i}title_3`, '') - } - - return svg -} - -/* Place text in SVG template */ -const decorateSvg = (data) => { - let svg - // Single title line - if (data.title.length === 1) { - svg = hidePlaceholders([2, 3]).replace(`1title_1`, data.title[0]) - } - // Double title line - else if (data.title.length === 2) { - svg = hidePlaceholders([1, 3]) - .replace(`2title_1`, data.title[0]) - .replace(`2title_2`, data.title[1]) - } - // Triple title line - else if (data.title.length === 3) { - svg = hidePlaceholders([1, 2]) - .replace(`3title_1`, data.title[0]) - .replace(`3title_2`, data.title[1]) - .replace(`3title_3`, data.title[2]) - } - - return svg - .replace('sub_1', data.sub[0] || '') - .replace('sub_2', data.sub[1] || '') - .replace(`intro_1`, data.intro[0] || '') - .replace(`intro_2`, data.intro[1] || '') - .replace('lead_1', data.lead || '') -} - -const writeAsPng = async (svg, site, slug) => { - const dir = path.resolve(path.join('..', '..', 'sites', site, 'public', 'og', ...slug.split('/'))) - await fs.mkdir(dir, { recursive: true }) - // Turn into PNG - sharp(Buffer.from(svg, 'utf-8')) - .resize({ width: 1200 }) - .toBuffer(async (err, data) => { - if (err) console.log(err) - if (data) return await fs.writeFile(path.join(dir, 'og.png'), data) - else console.log('No data for', slug) - }) -} - -/* This generates open graph images - * - * data holds { - * lang, - * site, - * title, - * intro, - * slug - * } - */ - -export const prebuildOgImages = async (data) => { - // Inject into SVG - const meta = await getMetaData(data) - const svg = decorateSvg(meta) - try { - await writeAsPng(svg, data.site, data.slug) - } catch (err) { - console.log('Could not write PNG for', data, meta) - } -} diff --git a/sites/shared/prebuild/runner.mjs b/sites/shared/prebuild/runner.mjs index c416876f7cf..466f884e818 100644 --- a/sites/shared/prebuild/runner.mjs +++ b/sites/shared/prebuild/runner.mjs @@ -14,7 +14,7 @@ import { prebuildCrowdin as crowdin } from './crowdin.mjs' import { prebuildOrg as orgPageTemplates } from './org.mjs' import { prebuildSearch as search } from './search.mjs' //import { prebuildLab as lab} from './lab.mjs' -//import { prebuildOgImages as ogImages } from './og/index.mjs' +import { prebuildOgImages as ogImages } from './og.mjs' /* * Are we running in production? @@ -37,8 +37,7 @@ const handlers = { git, 'Page Templates': true, search, - // FIXME: This needs work, but perhaps after v3 - //ogImages, + ogImages, } /*