Add OG images

This commit is contained in:
Jade Ellis
2024-07-23 21:20:42 +01:00
parent dfa8f2c5be
commit 2bf9699285
4 changed files with 328 additions and 12 deletions
@@ -12,14 +12,24 @@
// }
$: canonical = SITE_URL + "/blog/" + data.post.canonical;
$: webShareAPISupported = browser && typeof navigator.share !== 'undefined';
function calcOgURL(slug: string, date: string, width?: number): URL {
let url = new URL(SITE_URL + "/blog/image");
url.searchParams.set("slug", slug);
url.searchParams.set("date", date);
if (width) {
url.searchParams.set("width", width.toString());
}
return url;
}
$: webShareAPISupported = browser && typeof navigator.share !== "undefined";
// let webShareAPISupported = true;
$: handleWebShare;
const handleWebShare = async () => {
try {
let url = new URL(canonical)
url.searchParams.set("utm_medium", "share")
let url = new URL(canonical);
url.searchParams.set("utm_medium", "share");
navigator.share({
title: data.post.title,
text: data.post.description,
@@ -33,15 +43,25 @@
const defaultAuthor = {
name: "Jade Ellis",
url: "https://jade.ellis.link",
fediverse: "@JadedBlueEyes@tech.lgbt"
}
fediverse: "@JadedBlueEyes@tech.lgbt",
};
</script>
<svelte:head>
<link rel="alternate" type="application/rss+xml" title={SITE_TITLE} href={SITE_URL + "/blog/rss.xml"}>
<link rel="alternate" type="application/feed+json" title={SITE_TITLE} href={SITE_URL + "/blog/feed.json"}>
<link
rel="alternate"
type="application/rss+xml"
title={SITE_TITLE}
href={SITE_URL + "/blog/rss.xml"}
/>
<link
rel="alternate"
type="application/feed+json"
title={SITE_TITLE}
href={SITE_URL + "/blog/feed.json"}
/>
{#if defaultAuthor?.fediverse}
<meta name="fediverse:creator" content={defaultAuthor?.fediverse}>
<meta name="fediverse:creator" content={defaultAuthor?.fediverse} />
{/if}
</svelte:head>
@@ -59,6 +79,13 @@
openGraph={{
title: data.post.title,
description: data.post.description,
images: [
{
url: calcOgURL(data.post.slug, data.post.date, 1200).toString(),
width: 1200,
height: 1200 / 2,
},
],
}}
/>
@@ -70,11 +97,15 @@
>{new Date(data.post.date).toLocaleDateString()}</time
></a
>
· <span>By <a class="p-author h-card" href={defaultAuthor.url}>{defaultAuthor.name}</a></span>
·
<span
>By <a class="p-author h-card" href={defaultAuthor.url}
>{defaultAuthor.name}</a
></span
>
· <span>{data.post.readingTime.text}</span>
{#if webShareAPISupported} · <button class="link" on:click={handleWebShare}
>Share</button
>
{#if webShareAPISupported}
· <button class="link" on:click={handleWebShare}>Share</button>
{/if}
</aside>
<Toc headings={data.post.headings} />
@@ -0,0 +1,144 @@
import { pages } from '../posts'
import { error } from '@sveltejs/kit'
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import { SITE_DOMAIN } from '$lib/metadata';
import TTLCache, { } from "@isaacs/ttlcache";
import { format } from "@tusbar/cache-control";
const cache = new TTLCache({ max: 10000, ttl: 1000 * 60 * 60 })
// import type { Endpoints } from "@octokit/types";
// let repoRegex = new RegExp("https?://github\.com/(?<repo>[a-zA-Z0-9]+/[a-zA-Z0-9]+)/?")
const fontFile = await fetch('https://og-playground.vercel.app/inter-latin-ext-700-normal.woff');
const fontData: ArrayBuffer = await fontFile.arrayBuffer();
// const height = 630;
// const width = 1200;
const defaultHeight = 400;
const defaultWidth = 800;
const h = (type: any, props: any) => { return { type, props } }
/** @type {import('./$types').RequestHandler} */
export async function GET({ url }) {
const slug = url.searchParams.get('slug')
let dateParts = url.searchParams.get('date')?.split(/[\/-]/)?.map((p: string) => parseInt(p, 10))
if (dateParts && dateParts.length > 3) {
throw error(404, 'Post not found (bad date)')
}
const width = Number(url.searchParams.get('width'))
if (width > 10000) {
throw error(400, 'Image too big')
}
if (!cache.has(slug + "/" + dateParts?.join("-") + "/" + width)) {
// let start = new Date(dateParts[0] || 1, dateParts[1] || 0, dateParts[2] || 0);
// // @ts-ignore
// let end = new Date(...dateParts);
// console.log(dateParts)
// get post with metadata
const page = pages
.filter((post) => slug === post.slug)
.filter((post) => {
if (dateParts) {
let date = new Date(post.date)
return (
(!dateParts[0] || date.getFullYear() == dateParts[0]) &&
(!dateParts[1] || date.getMonth() + 1 == dateParts[1]) &&
(!dateParts[2] || date.getDate() == dateParts[2])
)
} else { return true }
})[0]
if (!page) {
throw error(404, 'Post not found')
}
let template = h("div", {
style: {
display: 'flex',
height: '100%',
width: '100%',
padding: '10px 20px',
// alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundImage: 'linear-gradient(to bottom, #dbf4ff, #eff3fc)',
fontSize: 60,
// letterSpacing: -2,
fontWeight: 700
// textAlign: 'center',
},
children: [h("div", {
style: {
fontSize: 15,
fontWeight: 600,
textTransform: 'uppercase',
letterSpacing: 1,
margin: '25px 0 10px',
color: 'gray'
},
children: SITE_DOMAIN
}), h("div", {
style: {
backgroundImage: 'linear-gradient(90deg, rgb(22, 61, 120), rgb(30, 42, 85))',
backgroundClip: 'text',
'-webkit-background-clip': 'text',
color: 'transparent'
},
children: page.title
}), h("aside", {
style: {
fontSize: 20,
fontWeight: 500,
color: '#202020',
margin: '10px 0 10px'
},
children: `Published on ${new Date(page.date).toLocaleDateString()} \xB7 By Jade Ellis \xB7 ${page.readingTime.text}`
})]
});
const svg = await satori(template, {
fonts: [
{
name: 'Inter Latin',
data: fontData,
style: 'normal'
}
],
height: defaultHeight,
width: defaultWidth,
});
const resvg = new Resvg(svg, {
fitTo: {
mode: 'width',
value: width || defaultWidth
}
});
const image = resvg.render().asPng();
cache.set(slug + "/" + dateParts?.join("-") + "/" + width, image)
return new Response(image, {
headers: {
'content-type': 'image/png'
}
});
;
} else {
return new Response(cache.get(slug + "/" + dateParts?.join("-") + "/" + width), {
headers: {
'Content-Type': 'image/png',
'Cache-Control': format({
public: true,
// immutable: true
maxAge: 60 * 60 * 24
})
}
});
}
}