mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Add OG images
This commit is contained in:
@@ -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
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user