From 9717940c38324b65626f8aeaae7aa37a70681428 Mon Sep 17 00:00:00 2001 From: Shane Loeffler Date: Wed, 18 Dec 2024 16:32:55 -0600 Subject: [PATCH 01/10] use dynamic og image for posts --- src/post.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/post.js b/src/post.js index afd45ee..1c24fd2 100644 --- a/src/post.js +++ b/src/post.js @@ -69,13 +69,13 @@ const Post = ({ back = '/blog', children, meta, number, ...props }) => { } }) + const ogImageUrl = `/api/og?title=${meta.title}&date=${ + meta.date + }&authors=${meta.authors.map((author) => author.name ?? author).join(',')}` + return ( Date: Tue, 7 Jan 2025 14:35:38 -0600 Subject: [PATCH 02/10] set url with id --- src/post.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/post.js b/src/post.js index 1c24fd2..4d38139 100644 --- a/src/post.js +++ b/src/post.js @@ -57,7 +57,7 @@ const Authors = ({ authors }) => { ) } -const Post = ({ back = '/blog', children, meta, number, ...props }) => { +const Post = ({ back = '/blog', children, meta, number, id, ...props }) => { const colors = ['red', 'orange', 'yellow', 'pink'] const avatars = meta.authors.map((d, i) => { const color = colors[(number + i) % 4] @@ -68,14 +68,13 @@ const Post = ({ back = '/blog', children, meta, number, ...props }) => { return { id: name, src, color } } }) - - const ogImageUrl = `/api/og?title=${meta.title}&date=${ - meta.date - }&authors=${meta.authors.map((author) => author.name ?? author).join(',')}` + const cardUrl = meta.card + ? `${prefix}/social/blog/${meta.card}.png` + : `/api/og?id=${id}` return ( Date: Tue, 7 Jan 2025 15:55:07 -0600 Subject: [PATCH 03/10] move card generation to layouts --- src/index.js | 1 + src/og/blog-post.js | 183 ++++++++++++++++++++++++++++++++++++++++++++ src/og/index.js | 1 + 3 files changed, 185 insertions(+) create mode 100644 src/og/blog-post.js create mode 100644 src/og/index.js diff --git a/src/index.js b/src/index.js index 1776551..def0782 100644 --- a/src/index.js +++ b/src/index.js @@ -15,3 +15,4 @@ export { default as Post } from './post' export { default as PullQuote } from './pull-quote' export { default as Supplement } from './supplement' export { default as Tool } from './tool' +export * from './og' diff --git a/src/og/blog-post.js b/src/og/blog-post.js new file mode 100644 index 0000000..50d6096 --- /dev/null +++ b/src/og/blog-post.js @@ -0,0 +1,183 @@ +import React from 'react' +import { formatDate } from '@carbonplan/components' +import theme from '@carbonplan/theme' + +const BLOG_POST_FONTS = [ + { path: 'relative-medium-pro.woff', type: 'medium', name: 'heading' }, + { path: 'relative-faux-book-pro.woff', type: 'faux', name: 'faux' }, + { path: 'relative-mono-11-pitch-pro.woff', type: 'mono', name: 'mono' }, +] + +const fetchFont = async (fontPath, fontType) => { + const headers = new Headers({ Referer: 'https://carbonplan.org/' }) + const res = await fetch(`https://fonts.carbonplan.org/relative/${fontPath}`, { + cache: 'force-cache', + headers, + }) + + if (!res.ok) throw new Error(`Failed to load ${fontType} font: ${res.status}`) + return res.arrayBuffer() +} + +export const getBlogPostFonts = async () => { + try { + const fontData = await Promise.all( + BLOG_POST_FONTS.map(({ path, type }) => fetchFont(path, type)) + ) + + return BLOG_POST_FONTS.map(({ name }, index) => ({ + name, + data: fontData[index], + })) + } catch (error) { + console.error('Error loading blog post fonts:', error) + throw error + } +} + +export const BlogPostOG = ({ title, date, authors, authorColors }) => { + const wrapAuthors = authors.length > 3 + + return ( +
+
+
+
+ blog / carbonplan +
+

+ {title} +

+
+
+ {authors.map((author, i) => ( +
+ {author} + {i < authors.length - 1 && ( + + + + + )} +
+ ))} +
+
+ +
+ ) +} diff --git a/src/og/index.js b/src/og/index.js new file mode 100644 index 0000000..7114357 --- /dev/null +++ b/src/og/index.js @@ -0,0 +1 @@ +export { BlogPostOG, getBlogPostFonts } from './blog-post' From fc1c93105bd5b65acd2f0084fcf8932beb7e6139 Mon Sep 17 00:00:00 2001 From: Shane Loeffler Date: Tue, 7 Jan 2025 16:43:11 -0600 Subject: [PATCH 04/10] move author colors into component --- src/og/blog-post.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/og/blog-post.js b/src/og/blog-post.js index 50d6096..f591a5d 100644 --- a/src/og/blog-post.js +++ b/src/og/blog-post.js @@ -8,6 +8,8 @@ const BLOG_POST_FONTS = [ { path: 'relative-mono-11-pitch-pro.woff', type: 'mono', name: 'mono' }, ] +export const AUTHOR_COLORS = ['red', 'orange', 'yellow', 'pink'] + const fetchFont = async (fontPath, fontType) => { const headers = new Headers({ Referer: 'https://carbonplan.org/' }) const res = await fetch(`https://fonts.carbonplan.org/relative/${fontPath}`, { @@ -35,7 +37,7 @@ export const getBlogPostFonts = async () => { } } -export const BlogPostOG = ({ title, date, authors, authorColors }) => { +export const BlogPostOG = ({ title, date, authors }) => { const wrapAuthors = authors.length > 3 return ( @@ -116,7 +118,7 @@ export const BlogPostOG = ({ title, date, authors, authorColors }) => { key={author} style={{ display: 'flex', - color: theme.colors[authorColors[i % 4]], + color: theme.colors[AUTHOR_COLORS[i % 4]], }} > {author} From 369e4ad0ce87762dadedfa662963a1662b4e3b75 Mon Sep 17 00:00:00 2001 From: Shane Loeffler Date: Thu, 9 Jan 2025 11:07:35 -0600 Subject: [PATCH 05/10] don't clip titles --- src/og/blog-post.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/og/blog-post.js b/src/og/blog-post.js index f591a5d..27ec6c9 100644 --- a/src/og/blog-post.js +++ b/src/og/blog-post.js @@ -90,10 +90,6 @@ export const BlogPostOG = ({ title, date, authors }) => { fontFamily: 'heading', letterSpacing: '-0.015em', lineHeight: '1.05', - display: '-webkit-box', - WebkitLineClamp: 4, - WebkitBoxOrient: 'vertical', - textOverflow: 'ellipsis', }} > {title} From 189397c0fbb72e94323bdd3ab68a72d3228816c7 Mon Sep 17 00:00:00 2001 From: Shane Loeffler Date: Thu, 9 Jan 2025 11:08:29 -0600 Subject: [PATCH 06/10] respect forceWrapAuthors prop --- src/og/blog-post.js | 4 ++-- src/post.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/og/blog-post.js b/src/og/blog-post.js index 27ec6c9..b38750f 100644 --- a/src/og/blog-post.js +++ b/src/og/blog-post.js @@ -37,8 +37,8 @@ export const getBlogPostFonts = async () => { } } -export const BlogPostOG = ({ title, date, authors }) => { - const wrapAuthors = authors.length > 3 +export const BlogPostOG = ({ title, date, authors, forceWrapAuthors }) => { + const wrapAuthors = forceWrapAuthors || authors.length > 3 return (
{ return { id: name, src, color } } }) + const forceWrapAuthors = meta.forceWrapAuthors || false const cardUrl = meta.card ? `${prefix}/social/blog/${meta.card}.png` - : `/api/og?id=${id}` + : `/api/og?id=${id}${forceWrapAuthors ? '&forceWrapAuthors=true' : ''}` return ( Date: Thu, 9 Jan 2025 11:58:01 -0600 Subject: [PATCH 07/10] create full url to og endpoint --- src/post.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/post.js b/src/post.js index 4fb473c..6e084cf 100644 --- a/src/post.js +++ b/src/post.js @@ -71,7 +71,9 @@ const Post = ({ back = '/blog', children, meta, number, id, ...props }) => { const forceWrapAuthors = meta.forceWrapAuthors || false const cardUrl = meta.card ? `${prefix}/social/blog/${meta.card}.png` - : `/api/og?id=${id}${forceWrapAuthors ? '&forceWrapAuthors=true' : ''}` + : `https://blog.carbonplan.org/api/og?id=${id}${ + forceWrapAuthors ? '&forceWrapAuthors=true' : '' + }` return ( Date: Thu, 9 Jan 2025 15:29:27 -0600 Subject: [PATCH 08/10] allow card generation on preview deployments --- src/post.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/post.js b/src/post.js index 6e084cf..3e36c34 100644 --- a/src/post.js +++ b/src/post.js @@ -68,11 +68,14 @@ const Post = ({ back = '/blog', children, meta, number, id, ...props }) => { return { id: name, src, color } } }) - const forceWrapAuthors = meta.forceWrapAuthors || false + const isPreviewOrDev = + process.env.VERCEL_ENV === 'preview' || + process.env.VERCEL_ENV === 'development' + const baseUrl = isPreviewOrDev ? '' : 'https://blog.carbonplan.org' const cardUrl = meta.card ? `${prefix}/social/blog/${meta.card}.png` - : `https://blog.carbonplan.org/api/og?id=${id}${ - forceWrapAuthors ? '&forceWrapAuthors=true' : '' + : `${baseUrl}/api/og?id=${id}${ + meta.forceWrapAuthors ? '&forceWrapAuthors=true' : '' }` return ( From b0df9ce1db40e7e1c6169abb8115126f60136f71 Mon Sep 17 00:00:00 2001 From: Shane Loeffler Date: Thu, 9 Jan 2025 15:32:28 -0600 Subject: [PATCH 09/10] cleaner prod check --- src/post.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/post.js b/src/post.js index 3e36c34..4da893b 100644 --- a/src/post.js +++ b/src/post.js @@ -68,10 +68,8 @@ const Post = ({ back = '/blog', children, meta, number, id, ...props }) => { return { id: name, src, color } } }) - const isPreviewOrDev = - process.env.VERCEL_ENV === 'preview' || - process.env.VERCEL_ENV === 'development' - const baseUrl = isPreviewOrDev ? '' : 'https://blog.carbonplan.org' + const notProduction = process.env.VERCEL_ENV !== 'production' + const baseUrl = notProduction ? '' : 'https://blog.carbonplan.org' const cardUrl = meta.card ? `${prefix}/social/blog/${meta.card}.png` : `${baseUrl}/api/og?id=${id}${ From c6fbab4cdb4f758d81bb02d7c21db218293f55bd Mon Sep 17 00:00:00 2001 From: Shane Loeffler Date: Thu, 9 Jan 2025 16:12:24 -0600 Subject: [PATCH 10/10] refactor og font fetching --- src/index.js | 2 +- src/og/blog-post.js | 65 ++++++++++++++++++------------------ src/og/index.js | 1 - src/og/utils/get-og-fonts.js | 24 +++++++++++++ 4 files changed, 57 insertions(+), 35 deletions(-) delete mode 100644 src/og/index.js create mode 100644 src/og/utils/get-og-fonts.js diff --git a/src/index.js b/src/index.js index def0782..5fa7b8e 100644 --- a/src/index.js +++ b/src/index.js @@ -15,4 +15,4 @@ export { default as Post } from './post' export { default as PullQuote } from './pull-quote' export { default as Supplement } from './supplement' export { default as Tool } from './tool' -export * from './og' +export { getBlogPostCard } from './og/blog-post' diff --git a/src/og/blog-post.js b/src/og/blog-post.js index b38750f..1818bbf 100644 --- a/src/og/blog-post.js +++ b/src/og/blog-post.js @@ -1,41 +1,15 @@ import React from 'react' import { formatDate } from '@carbonplan/components' import theme from '@carbonplan/theme' +import { getFonts } from './utils/get-og-fonts' -const BLOG_POST_FONTS = [ - { path: 'relative-medium-pro.woff', type: 'medium', name: 'heading' }, - { path: 'relative-faux-book-pro.woff', type: 'faux', name: 'faux' }, - { path: 'relative-mono-11-pitch-pro.woff', type: 'mono', name: 'mono' }, +const FONTS = [ + { path: 'relative-medium-pro.woff', name: 'medium' }, + { path: 'relative-faux-book-pro.woff', name: 'faux' }, + { path: 'relative-mono-11-pitch-pro.woff', name: 'mono' }, ] -export const AUTHOR_COLORS = ['red', 'orange', 'yellow', 'pink'] - -const fetchFont = async (fontPath, fontType) => { - const headers = new Headers({ Referer: 'https://carbonplan.org/' }) - const res = await fetch(`https://fonts.carbonplan.org/relative/${fontPath}`, { - cache: 'force-cache', - headers, - }) - - if (!res.ok) throw new Error(`Failed to load ${fontType} font: ${res.status}`) - return res.arrayBuffer() -} - -export const getBlogPostFonts = async () => { - try { - const fontData = await Promise.all( - BLOG_POST_FONTS.map(({ path, type }) => fetchFont(path, type)) - ) - - return BLOG_POST_FONTS.map(({ name }, index) => ({ - name, - data: fontData[index], - })) - } catch (error) { - console.error('Error loading blog post fonts:', error) - throw error - } -} +const AUTHOR_COLORS = ['red', 'orange', 'yellow', 'pink'] export const BlogPostOG = ({ title, date, authors, forceWrapAuthors }) => { const wrapAuthors = forceWrapAuthors || authors.length > 3 @@ -87,7 +61,7 @@ export const BlogPostOG = ({ title, date, authors, forceWrapAuthors }) => { fontSize: '64px', marginTop: '44px', color: theme.colors.primary, - fontFamily: 'heading', + fontFamily: 'medium', letterSpacing: '-0.015em', lineHeight: '1.05', }} @@ -179,3 +153,28 @@ export const BlogPostOG = ({ title, date, authors, forceWrapAuthors }) => {
) } + +export const getBlogPostCard = async ({ + title, + date, + authors, + forceWrapAuthors, +}) => { + const fonts = await getFonts(FONTS) + + return { + component: ( + + ), + fonts, + options: { + width: 1200, + height: 630, + }, + } +} diff --git a/src/og/index.js b/src/og/index.js deleted file mode 100644 index 7114357..0000000 --- a/src/og/index.js +++ /dev/null @@ -1 +0,0 @@ -export { BlogPostOG, getBlogPostFonts } from './blog-post' diff --git a/src/og/utils/get-og-fonts.js b/src/og/utils/get-og-fonts.js new file mode 100644 index 0000000..bbdae57 --- /dev/null +++ b/src/og/utils/get-og-fonts.js @@ -0,0 +1,24 @@ +const fetchFont = async (fontPath) => { + const headers = new Headers({ Referer: 'https://carbonplan.org/' }) + const res = await fetch(`https://fonts.carbonplan.org/relative/${fontPath}`, { + cache: 'force-cache', + headers, + }) + + if (!res.ok) throw new Error(`Failed to load ${fontPath} font: ${res.status}`) + return res.arrayBuffer() +} + +export const getFonts = async (fonts) => { + try { + const fontData = await Promise.all(fonts.map(({ path }) => fetchFont(path))) + + return fonts.map(({ name }, index) => ({ + name, + data: fontData[index], + })) + } catch (error) { + console.error('Error loading fonts:', error) + throw error + } +}