From 2472ab18246cfdfa9b6985300ddfe98f1aa9db94 Mon Sep 17 00:00:00 2001 From: joostdecock Date: Fri, 3 Nov 2023 15:36:09 +0100 Subject: [PATCH] feat(shared): Add dynamic OG images --- artwork/img/square.svg | 16 ++-- artwork/img/tall.svg | 36 ++++----- artwork/img/wide.svg | 18 ++--- .../org/docs/about/pledge/motivation/en.md | 6 +- sites/backend/build.mjs | 2 +- sites/backend/src/controllers/img.mjs | 73 +++++++++++-------- sites/backend/src/routes/img.mjs | 3 + sites/dev/pages/404.mjs | 19 +---- sites/dev/pages/[...slug].mjs | 20 +---- sites/dev/pages/index.mjs | 25 ++----- sites/dev/pages/search.mjs | 2 +- sites/dev/prebuild.mjs | 2 +- sites/org/components/layouts/docs.mjs | 27 ------- sites/org/components/layouts/post.mjs | 2 - sites/org/pages/blog/[dir].mjs | 1 + sites/org/pages/index.mjs | 11 +-- sites/org/pages/showcase/[dir].mjs | 1 + sites/org/pages/showcase/index.mjs | 2 +- sites/org/prebuild.mjs | 2 +- sites/shared/components/wrappers/page.mjs | 31 +++++++- sites/shared/prebuild/og.mjs | 5 +- sites/shared/utils.mjs | 6 ++ 22 files changed, 141 insertions(+), 169 deletions(-) diff --git a/artwork/img/square.svg b/artwork/img/square.svg index e9206059893..edbdbbb0e3a 100644 --- a/artwork/img/square.svg +++ b/artwork/img/square.svg @@ -28,16 +28,16 @@ - intro_1 - intro_2 + __intro_1__ + __intro_2__ - 1title_1 - 2title_1 - 2title_2 - 3title_1 - 3title_2 - 3title_3 + __1title_1__ + __2title_1__ + __2title_2__ + __3title_1__ + __3title_2__ + __3title_3__ F diff --git a/artwork/img/tall.svg b/artwork/img/tall.svg index fbc960790c7..fe916f6a5e6 100644 --- a/artwork/img/tall.svg +++ b/artwork/img/tall.svg @@ -28,29 +28,29 @@ - intro_1 - intro_2 + __intro_1__ + __intro_2__ - 1title_1 + __1title_1__ - 2title_1 - 2title_2 + __2title_1__ + __2title_2__ - 3title_1 - 3title_2 - 3title_3 + __3title_1__ + __3title_2__ + __3title_3__ - 4title_1 - 4title_2 - 4title_3 - 4title_4 + __4title_1__ + __4title_2__ + __4title_3__ + __4title_4__ - 5title_1 - 5title_2 - 5title_3 - 5title_4 - 5title_5 + __5title_1__ + __5title_2__ + __5title_3__ + __5title_4__ + __5title_5__ F @@ -65,7 +65,7 @@ g - site + __site__ diff --git a/artwork/img/wide.svg b/artwork/img/wide.svg index 1c933139e10..cbeed76a0c7 100644 --- a/artwork/img/wide.svg +++ b/artwork/img/wide.svg @@ -27,16 +27,16 @@ - intro_1 - intro_2 + __intro_1__ + __intro_2__ - 1title_1 - 2title_1 - 2title_2 - 3title_1 - 3title_2 - 3title_3 + __1title_1__ + __2title_1__ + __2title_2__ + __3title_1__ + __3title_2__ + __3title_3__ F @@ -51,7 +51,7 @@ g - site + __site__ diff --git a/markdown/org/docs/about/pledge/motivation/en.md b/markdown/org/docs/about/pledge/motivation/en.md index b077cc7579b..71750320d3c 100644 --- a/markdown/org/docs/about/pledge/motivation/en.md +++ b/markdown/org/docs/about/pledge/motivation/en.md @@ -1,5 +1,5 @@ --- -title: Motivation +title: My reasoning behind FreeSewing's Revenue Pledge --- @@ -9,7 +9,7 @@ his motivations for [the FreeSewing revenue pledge](/docs/about/pledge/) -You probably assume that we ask for money to keep the servers running. But that's not exactly true. +You probably assume that I ask for money to keep the servers running. But that's not exactly true. I don't know if you're familiar with the phrase **noblesse oblige** but it essentially means that privilege entails responsibility. @@ -49,7 +49,7 @@ because FreeSewing is a force for good. Here's the tricky part: People give less once they know the money goes to charity. I wish it wasn't the case, but it is. -So we're presenting [our subscription options](/community/join) like you would see on a for-profit site. +So I'm presenting [the subscription options](/patrons/join) like you would see on a for-profit site. It seems more intuitive this way, and also just works better. Yes, everything is free, and the money doesn't actually go to paying the server bills diff --git a/sites/backend/build.mjs b/sites/backend/build.mjs index a6fc608505b..21d3737eae3 100644 --- a/sites/backend/build.mjs +++ b/sites/backend/build.mjs @@ -28,7 +28,7 @@ ${banner} entryPoints: ['src/index.mjs'], format: 'esm', outfile: 'dist/index.mjs', - external: ['./local-config.mjs'], + external: ['./local-config.mjs', 'sharp'], metafile: process.env.VERBOSE ? true : false, minify: process.env.NO_MINIFY ? false : true, sourcemap: true, diff --git a/sites/backend/src/controllers/img.mjs b/sites/backend/src/controllers/img.mjs index 1c7c787ed4c..60ab591d384 100644 --- a/sites/backend/src/controllers/img.mjs +++ b/sites/backend/src/controllers/img.mjs @@ -59,7 +59,11 @@ const introAsLines = (intro, type) => { // Does it fit on one line? if (intro.length <= imgConfig.templates.chars[type].intro) return [intro] // Two lines it is - return splitLine(intro, imgConfig.templates.chars[type].intro) + const lines = splitLine(intro, imgConfig.templates.chars[type].intro) + if (lines[1].length > imgConfig.templates.chars[type].intro) + lines[1] = lines[1].slice(0, imgConfig.templates.chars[type].intro) + '…' + + return lines } /* Hide unused placeholders */ @@ -67,11 +71,11 @@ const hidePlaceholders = (list, type) => { let svg = templates[type] for (const i of list) { svg = svg - .replace(`${i}title_1`, '') - .replace(`${i}title_2`, '') - .replace(`${i}title_3`, '') - .replace(`${i}title_4`, '') - .replace(`${i}title_5`, '') + .replace(`__${i}title_1__`, '') + .replace(`__${i}title_2__`, '') + .replace(`__${i}title_3__`, '') + .replace(`__${i}title_4__`, '') + .replace(`__${i}title_5__`, '') } return svg @@ -82,61 +86,70 @@ const decorateSvg = (data) => { let svg // Single title line if (data.title.length === 1) { - svg = hidePlaceholders([2, 3, 4, 5], data.type).replace(`1title_1`, data.title[0]) + svg = hidePlaceholders([2, 3, 4, 5], data.type).replace(`__1title_1__`, data.title[0]) } // Double title line else if (data.title.length === 2) { svg = hidePlaceholders([1, 3, 4, 5], data.type) - .replace(`2title_1`, data.title[0]) - .replace(`2title_2`, data.title[1]) + .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, 4, 5], data.type) - .replace(`3title_1`, data.title[0]) - .replace(`3title_2`, data.title[1]) - .replace(`3title_3`, data.title[2]) + .replace(`__3title_1__`, data.title[0]) + .replace(`__3title_2__`, data.title[1]) + .replace(`__3title_3__`, data.title[2]) } // Quadruple title line else if (data.title.length === 4) { svg = hidePlaceholders([1, 2, 3, 5], data.type) - .replace(`4title_1`, data.title[0]) - .replace(`4title_2`, data.title[1]) - .replace(`4title_3`, data.title[2]) - .replace(`4title_4`, data.title[3]) + .replace(`__4title_1__`, data.title[0]) + .replace(`__4title_2__`, data.title[1]) + .replace(`__4title_3__`, data.title[2]) + .replace(`__4title_4__`, data.title[3]) } // Quintuple title line else if (data.title.length === 5) { svg = hidePlaceholders([1, 2, 3, 4], data.type) - .replace(`5title_1`, data.title[0]) - .replace(`5title_2`, data.title[1]) - .replace(`5title_3`, data.title[2]) - .replace(`5title_4`, data.title[3]) - .replace(`5title_5`, data.title[4]) + .replace(`__5title_1__`, data.title[0]) + .replace(`__5title_2__`, data.title[1]) + .replace(`__5title_3__`, data.title[2]) + .replace(`__5title_4__`, data.title[3]) + .replace(`__5title_5__`, data.title[4]) } return svg - .replace(`intro_1`, data.intro[0] || '') - .replace(`intro_2`, data.intro[1] || '') - .replace('site', data.site || '') + .replace(`__intro_1__`, data.intro[0] || '') + .replace(`__intro_2__`, data.intro[1] || '') + .replace('__site__', data.site || '') } export function ImgController() {} +const parseInput = (req) => { + let input = {} + if (req.params.data) { + try { + input = JSON.parse(decodeURIComponent(req.params.data)) + } catch (err) { + console.log(err) + } + } else input = req.body + + return input +} + /* * Generate an Open Graph image * See: https://freesewing.dev/reference/backend/api */ ImgController.prototype.generate = async (req, res) => { + const input = parseInput(req) /* * Extract body parameters */ - const { - site = false, - title = 'Please provide a title', - intro = 'Please provide an intro', - type = 'wide', - } = req.body + const { site = false, title = '', intro = '', type = 'wide' } = input if (site && imgConfig.sites.indexOf(site) === -1) return res.status(400).send({ error: 'invalidSite' }) diff --git a/sites/backend/src/routes/img.mjs b/sites/backend/src/routes/img.mjs index 77c8b35cf53..bde34ff2aff 100644 --- a/sites/backend/src/routes/img.mjs +++ b/sites/backend/src/routes/img.mjs @@ -7,4 +7,7 @@ export function imgRoutes(tools) { // Generate an image app.post('/img', (req, res) => Img.generate(req, res, tools)) + + // Generate an image + app.get('/img/:data', (req, res) => Img.generate(req, res, tools)) } diff --git a/sites/dev/pages/404.mjs b/sites/dev/pages/404.mjs index 417149a536e..fd9cbb45026 100644 --- a/sites/dev/pages/404.mjs +++ b/sites/dev/pages/404.mjs @@ -1,7 +1,6 @@ // Dependencies import { serverSideTranslations } from 'next-i18next/serverSideTranslations' // Components -import Head from 'next/head' import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { Robot } from 'shared/components/robot/index.mjs' import { Popout } from 'shared/components/popout/index.mjs' @@ -12,23 +11,7 @@ import { NavLinks, MainSections } from 'shared/components/navigation/sitenav.mjs const namespaces = [...pageNs] const Page404 = () => ( - - - - - - - - - - - - - + diff --git a/sites/dev/pages/[...slug].mjs b/sites/dev/pages/[...slug].mjs index d26ebec67ad..5cf151083af 100644 --- a/sites/dev/pages/[...slug].mjs +++ b/sites/dev/pages/[...slug].mjs @@ -3,8 +3,8 @@ import { pages } from 'site/prebuild/docs.en.mjs' // Dependencies import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { loadMdxAsStaticProps } from 'shared/mdx/load.mjs' +import { freeSewingConfig as config } from 'shared/config/freesewing.config.mjs' // Components -import Head from 'next/head' import { PageWrapper, ns } from 'shared/components/wrappers/page.mjs' //import { components } from 'shared/components/mdx/index.mjs' import { MdxWrapper } from 'shared/components/wrappers/mdx.mjs' @@ -27,24 +27,6 @@ import { */ const DocsPage = ({ page, slug, frontmatter, mdx, mdxSlug }) => ( - - - - - - - - - - - - - {frontmatter.title} - diff --git a/sites/dev/pages/index.mjs b/sites/dev/pages/index.mjs index ca6f25cf5d7..3c03a58ef5f 100644 --- a/sites/dev/pages/index.mjs +++ b/sites/dev/pages/index.mjs @@ -1,7 +1,6 @@ // Dependencies import { serverSideTranslations } from 'next-i18next/serverSideTranslations' // Components -import Head from 'next/head' import { PageWrapper } from 'shared/components/wrappers/page.mjs' import { PageLink } from 'shared/components/link.mjs' import { Highlight } from 'shared/components/mdx/highlight.mjs' @@ -28,25 +27,11 @@ const Card = ({ bg = 'bg-base-200', textColor = 'text-base-content', title, chil * or set them manually. */ const HomePage = ({ page }) => ( - - - - - - - - - - - - - {title} - - +

diff --git a/sites/dev/pages/search.mjs b/sites/dev/pages/search.mjs index 9343bc7d305..6e8b3eddf1a 100644 --- a/sites/dev/pages/search.mjs +++ b/sites/dev/pages/search.mjs @@ -25,7 +25,7 @@ const SearchPage = ({ page }) => { ) return ( - + diff --git a/sites/dev/prebuild.mjs b/sites/dev/prebuild.mjs index 0669f097336..9e2c201b5d6 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: true, //'productionOnly', + ogImages: false, // We currently don't prebuild these images /* * Only prebuild the patron info in production diff --git a/sites/org/components/layouts/docs.mjs b/sites/org/components/layouts/docs.mjs index 4e72166b8d1..4a3a2513cde 100644 --- a/sites/org/components/layouts/docs.mjs +++ b/sites/org/components/layouts/docs.mjs @@ -3,7 +3,6 @@ import { nsMerge } from 'shared/utils.mjs' // Hooks import { useContext } from 'react' // Components -import Head from 'next/head' import { BaseLayout, BaseLayoutLeft, @@ -22,37 +21,11 @@ import { PrevNext } from 'shared/components/prev-next.mjs' export const ns = nsMerge(navNs, 'docs', metaNs) -export const FrontmatterHead = ({ frontmatter, slug, locale }) => ( - - - - - - - - - - - - - {frontmatter.title + '- FreeSewing.org'} - -) - export const DocsLayout = ({ children = [], frontmatter }) => { const { slug, locale } = useContext(NavigationContext) return ( <> - diff --git a/sites/org/components/layouts/post.mjs b/sites/org/components/layouts/post.mjs index ebcfca49352..e81a1528a13 100644 --- a/sites/org/components/layouts/post.mjs +++ b/sites/org/components/layouts/post.mjs @@ -5,7 +5,6 @@ import { Lightbox } from 'shared/components/lightbox.mjs' import { ImageWrapper } from 'shared/components/wrappers/img.mjs' import { TimeAgo, ns as timeagoNs } from 'shared/components/timeago/index.mjs' import { useTranslation } from 'next-i18next' -import { FrontmatterHead } from './docs.mjs' import { BaseLayout, BaseLayoutLeft, @@ -81,7 +80,6 @@ export const PostLayout = ({ mdx, slug, frontmatter, locale, type, dir }) => { return ( - diff --git a/sites/org/pages/blog/[dir].mjs b/sites/org/pages/blog/[dir].mjs index eb65566e6d2..3c02aa4df59 100644 --- a/sites/org/pages/blog/[dir].mjs +++ b/sites/org/pages/blog/[dir].mjs @@ -11,6 +11,7 @@ const BlogPage = ({ dir, page, mdx, frontmatter }) => { return ( ( { }, [account.username]) return ( - + - - FreeSewing.org - -

diff --git a/sites/org/pages/showcase/[dir].mjs b/sites/org/pages/showcase/[dir].mjs index 02b4aa49c24..482d214d531 100644 --- a/sites/org/pages/showcase/[dir].mjs +++ b/sites/org/pages/showcase/[dir].mjs @@ -12,6 +12,7 @@ const ShowcasePage = ({ dir, page, mdx, frontmatter }) => { ( { : Object.keys(posts[page.locale]) return ( - +

FreeSewing - {t('sections:showcase')}

diff --git a/sites/org/prebuild.mjs b/sites/org/prebuild.mjs index 1c494f73dd0..a6b2c966ff2 100644 --- a/sites/org/prebuild.mjs +++ b/sites/org/prebuild.mjs @@ -85,7 +85,7 @@ prebuildRunner({ * Only prebuild the Open Graph (og) images in production * Will be skipped in development mode to save time */ - ogImages: 'productionOnly', + ogImages: false, // We do not currently prebuild these /* * Only prebuild the patron info in production diff --git a/sites/shared/components/wrappers/page.mjs b/sites/shared/components/wrappers/page.mjs index 962b3801a23..ded01be4bf2 100644 --- a/sites/shared/components/wrappers/page.mjs +++ b/sites/shared/components/wrappers/page.mjs @@ -1,7 +1,8 @@ // __SDEFILE__ - This file is a dependency for the stand-alone environment // Dependencies import React, { useState, useEffect, useContext } from 'react' -import { nsMerge } from 'shared/utils.mjs' +import { ogUrl, nsMerge } from 'shared/utils.mjs' +import { siteConfig } from 'site/site.config.mjs' // Context import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' // Hooks @@ -21,7 +22,14 @@ export const PageWrapper = (props) => { /* * Deconstruct props */ - const { layout = DefaultLayout, footer = true, header = true, children = [], path = [] } = props + const { + layout = DefaultLayout, + footer = true, + header = true, + locale = 'en', + children = [], + path = [], + } = props // Title is typically set in props.t but check props.title too const pageTitle = props.t ? props.t : props.title ? props.title : null @@ -60,6 +68,25 @@ export const PageWrapper = (props) => { {props.intro && ( )} + + + + + + + + + + + {pageTitle} )}
`${language}_${slug.split('/').join('_')}.png` - export const generateImage = async ({ title, intro, site, slug, language }) => { let result try { @@ -14,7 +13,7 @@ export const generateImage = async ({ title, intro, site, slug, language }) => { { title, intro, site, type: 'wide' }, { responseType: 'arraybuffer' } ) - const file = path.resolve('..', site, 'public', 'img', 'og', slugToImg(slug, language)) + const file = path.resolve('..', site, 'public', 'img', 'og', slugToOgImg(slug, language)) await fs.promises.writeFile(file, result.data) } catch (err) { console.log(err) diff --git a/sites/shared/utils.mjs b/sites/shared/utils.mjs index fe69a6ab30c..949f87de74b 100644 --- a/sites/shared/utils.mjs +++ b/sites/shared/utils.mjs @@ -7,6 +7,7 @@ import orderBy from 'lodash.orderby' import unset from 'lodash.unset' import { cloudflareConfig } from './config/cloudflare.mjs' import { mergeOptions } from '@freesewing/core' +import { freeSewingConfig as config } from './config/freesewing.config.mjs' const slugifyConfig = { replacement: '-', // replace spaces with replacement character, defaults to `-` @@ -491,3 +492,8 @@ export const workbenchHash = ({ settings = {}, view = 'draft' }) => export const getSearchParam = (name = 'id') => typeof window === 'undefined' ? undefined : new URLSearchParams(window.location.search).get(name) + +export const slugToOgImg = (slug, language) => `${language}_${slug.split('/').join('_')}.png` + +export const ogUrl = ({ site = false, title, intro }) => + `${config.backend}/img/${encodeURIComponent(JSON.stringify({ site, title, intro }))}`