diff options
Diffstat (limited to 'src/utils')
| -rw-r--r-- | src/utils/createOgImage.ts | 52 | ||||
| -rw-r--r-- | src/utils/ogResources.ts | 15 | ||||
| -rw-r--r-- | src/utils/schemas/blogPostSchema.ts | 37 | ||||
| -rw-r--r-- | src/utils/schemas/blogSchema.ts | 26 | ||||
| -rw-r--r-- | src/utils/schemas/pageSchema.ts | 23 |
5 files changed, 153 insertions, 0 deletions
diff --git a/src/utils/createOgImage.ts b/src/utils/createOgImage.ts new file mode 100644 index 0000000..da2cece --- /dev/null +++ b/src/utils/createOgImage.ts @@ -0,0 +1,52 @@ +import { config } from "../config"; +import { html } from "satori-html"; +import { resources } from "./ogResources"; +import { Resvg } from "@resvg/resvg-js"; +import dayjs from "dayjs"; +import satori from "satori"; + +export async function createOgImage(title: string, datePublished: Date): Promise<Buffer> { + const formattedDate = dayjs(datePublished).format("MMMM DD, YYYY"); + + const markup = await satori( + html(` +<div tw="flex flex-col w-full h-full" style="background-color: ${config.og.color.bg}"> + <div tw="flex flex-col w-full h-4/5 p-10 justify-center"> + <div tw="text-2xl mb-6" style="color: ${config.og.color.text}">${formattedDate}</div> + <div tw="flex text-6xl w-full font-bold" style="color: ${config.og.color.text}">${title}</div> + </div> + <div tw="w-full h-1/5 flex p-10 items-center justify-between text-2xl" style="border-top: 1px solid ${config.og.color.bgCode}"> + <div tw="flex items-center"> + <span tw="ml-3" style="color: ${config.og.color.text}">${config.og.website.toLocaleUpperCase()}</span> + </div> + <div tw="flex items-center"> + <img src="${resources.photoBase64}" tw="w-15 h-15 rounded-full" /> + <div tw="flex flex-col ml-4"> + <span style="color: ${config.og.color.text}">${config.author.name}</span> + <span style="color: ${config.og.color.blossom}">${config.author.email}</span> + </div> + </div> + </div> +</div> +`), + { + width: config.og.dimensions.width, + height: config.og.dimensions.height, + fonts: [ + { + name: "Inter", + data: resources.fonts.regular, + weight: 400, + }, + { + name: "Inter", + data: resources.fonts.bold, + weight: 700, + }, + ], + } + ); + + const image = new Resvg(markup, { fitTo: { mode: "width", value: config.og.dimensions.width } }); + return image.render().asPng(); +} diff --git a/src/utils/ogResources.ts b/src/utils/ogResources.ts new file mode 100644 index 0000000..8049fb2 --- /dev/null +++ b/src/utils/ogResources.ts @@ -0,0 +1,15 @@ +import { config } from "../config"; +import fs from "fs/promises"; +import path from "path"; +import sharp from "sharp"; + +export const resources = { + fonts: { + regular: await fs.readFile(path.resolve(config.og.fonts.regular)), + bold: await fs.readFile(path.resolve(config.og.fonts.bold)), + }, + photoBase64: await (async () => { + const buf = await fs.readFile(path.resolve(config.og.photo)); + return "data:image/png;base64," + (await sharp(buf).resize(120, 120).png({ quality: 95 }).toBuffer()).toString("base64"); + })(), +}; diff --git a/src/utils/schemas/blogPostSchema.ts b/src/utils/schemas/blogPostSchema.ts new file mode 100644 index 0000000..87e1bf2 --- /dev/null +++ b/src/utils/schemas/blogPostSchema.ts @@ -0,0 +1,37 @@ +import type { WithContext, BlogPosting } from "schema-dts"; +import { config } from "../../config"; + +export type BlogPostSchemaParams = { + readonly dateModified: string; + readonly datePublished: string; + readonly description: string; + readonly isBasedOn?: string; + readonly lang: string; + readonly preview: string; + readonly siteUrl: string; + readonly slug: string; + readonly title: string; +}; + +export default ({ siteUrl, slug, title, description, preview, datePublished, dateModified, lang, isBasedOn }: BlogPostSchemaParams): WithContext<BlogPosting> => ({ + "@context": "https://schema.org", + "@type": "BlogPosting", + "url": new URL(`/blog/${slug}`, siteUrl).toString(), + "headline": title, + "description": description, + "image": new URL(preview, siteUrl).toString(), + "datePublished": datePublished, + "dateModified": dateModified, + "inLanguage": lang, + "author": { + "@type": "Person", + "name": config.author.name, + "url": config.author.url, + "sameAs": config.author.sameAs, + }, + "mainEntityOfPage": { + "@type": "WebPage", + "@id": new URL(`/blog/${slug}`, siteUrl).toString(), + }, + ...(isBasedOn && { isBasedOn: isBasedOn }), +}); diff --git a/src/utils/schemas/blogSchema.ts b/src/utils/schemas/blogSchema.ts new file mode 100644 index 0000000..77f4632 --- /dev/null +++ b/src/utils/schemas/blogSchema.ts @@ -0,0 +1,26 @@ +import type { WithContext, CollectionPage } from "schema-dts"; +import type { CollectionEntry } from "astro:content"; + +export type BlogSchemaParams = { + readonly posts: CollectionEntry<"blog">[]; + readonly siteUrl: string; + readonly title: string; +}; + +export default ({ siteUrl, title, posts }: BlogSchemaParams): WithContext<CollectionPage> => ({ + "@context": "https://schema.org", + "@type": "CollectionPage", + "url": new URL("/blog/", siteUrl).toString(), + "name": title, + "mainEntity": { + "@type": "ItemList", + "itemListOrder": "https://schema.org/ItemListOrderDescending", + "numberOfItems": posts.length, + "itemListElement": posts.map((post, index) => ({ + "@type": "ListItem", + "position": index + 1, + "url": new URL(`/blog/${post.slug}`, siteUrl).toString(), + "name": post.data.title, + })), + }, +}); diff --git a/src/utils/schemas/pageSchema.ts b/src/utils/schemas/pageSchema.ts new file mode 100644 index 0000000..606488b --- /dev/null +++ b/src/utils/schemas/pageSchema.ts @@ -0,0 +1,23 @@ +import type { WithContext, WebPage } from "schema-dts"; + +export type WebsiteSchemaParams = { + readonly description: string; + readonly page: string; + readonly siteUrl: string; + readonly title: string; + readonly lang: string; +}; + +export default ({ siteUrl, page, title, description, lang }: WebsiteSchemaParams): WithContext<WebPage> => ({ + "@context": "https://schema.org", + "@type": "WebPage", + "@id": new URL(page, siteUrl).toString(), + "url": new URL(page, siteUrl).toString(), + "name": title, + "description": description, + "inLanguage": lang, + "mainEntity": { + "@type": "WebSite", + "@id": new URL("/", siteUrl).toString(), + }, +}); |
