OG cover image support for blog post meta

This commit is contained in:
Erica Marigold 2024-07-06 21:07:15 +05:30
parent 88b5366c92
commit 40f3fb1c8e
No known key found for this signature in database
GPG key ID: 2768CC0C23D245D1
5 changed files with 44 additions and 25 deletions

View file

@ -1,19 +1,21 @@
--- ---
import path from "path";
interface Props { interface Props {
id?: string; id?: string;
src: string; src: ImageMetadata | string;
class?: string; class?: string;
alt?: string; alt?: string;
position?: string; position?: string;
basePath?: string; basePath?: string;
} }
import { Image } from "astro:assets";
import path from "path";
import { url } from "../../utils/url-utils"; import { url } from "../../utils/url-utils";
const { id, src, alt, position = "center", basePath = "/" } = Astro.props; const { id, src: img, alt, position = "center", basePath = "/" } = Astro.props;
const className = Astro.props.class; const className = Astro.props.class;
const src = typeof img === "string" ? img : img.src;
const isLocal = !( const isLocal = !(
src.startsWith("/") || src.startsWith("/") ||
src.startsWith("http") || src.startsWith("http") ||
@ -24,23 +26,23 @@ const isPublic = src.startsWith("/");
// TODO temporary workaround for images dynamic import // TODO temporary workaround for images dynamic import
// https://github.com/withastro/astro/issues/3373 // https://github.com/withastro/astro/issues/3373
let img: ImageMetadata; let optimizedImg: ImageMetadata;
if (isLocal) { if (isLocal && typeof img === "string") {
const files = import.meta.glob<ImageMetadata>("../../**", { const files = import.meta.glob<ImageMetadata>("../../**", {
import: "default", import: "default",
}); });
const normalizedPath = path const normalizedPath = path
.normalize(path.join("../../", basePath, src)) .normalize(path.join("../../", basePath, src))
.replace(/\\/g, "/"); .replace(/\\/g, "/");
img = await files[normalizedPath](); optimizedImg = await files[normalizedPath]();
} }
const imageClass = "w-full h-full object-cover"; const imageClass = "w-full h-full object-cover";
const imageStyle = `object-position: ${position}`; const imageStyle = `object-position: ${position}`;
--- ---
<div class:list={[className, 'overflow-hidden relative']}> <div id="image-wrapper" class:list={[className, 'overflow-hidden relative']}>
<div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div>
{isLocal && <Image src={img!} alt={alt || ""} class={imageClass} style={imageStyle}/>} {isLocal && <img src={optimizedImg!.src} alt={alt || ""} class={imageClass} style={imageStyle}/>}
{!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle}/>} {!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle}/>}
</div> </div>

View file

@ -1,12 +1,13 @@
import { defineCollection, z } from "astro:content"; import { defineCollection, z } from "astro:content";
const postsCollection = defineCollection({ const postsCollection = defineCollection({
schema: z.object({ schema: ({ image }) =>
z.object({
title: z.string(), title: z.string(),
published: z.date(), published: z.date(),
draft: z.boolean().optional(), draft: z.boolean().optional(),
description: z.string().optional(), description: z.string().optional(),
image: z.string().optional(), image: image().optional(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
category: z.string().optional(), category: z.string().optional(),
}), }),

View file

@ -22,9 +22,10 @@ interface Props {
title: string; title: string;
banner: string; banner: string;
description?: string; description?: string;
cover?: string;
} }
let { title, banner, description } = Astro.props; let { title, banner, description, cover } = Astro.props;
const isHomePage = pathsEqual(Astro.url.pathname, "/"); const isHomePage = pathsEqual(Astro.url.pathname, "/");
@ -56,11 +57,11 @@ const myFade = {
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757 // why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
const configHue = siteConfig.themeColor.hue; const configHue = siteConfig.themeColor.hue;
if (!banner || typeof banner !== "string" || banner.trim() === "") { if (!banner || typeof banner !== "string" || banner.trim() === "") {
banner = siteConfig.banner.src; banner = cover ?? siteConfig.banner.src;
} }
// TODO don't use post cover as banner for now // TODO don't use post cover as banner for now
banner = siteConfig.banner.src; // banner = siteConfig.banner.src;
const enableBanner = siteConfig.banner.enable; const enableBanner = siteConfig.banner.enable;
@ -91,6 +92,7 @@ const siteLang = siteConfig.lang.replace("_", "-");
<meta property="og:url" content={Astro.url}> <meta property="og:url" content={Astro.url}>
<meta property="og:title" content={pageTitle}> <meta property="og:title" content={pageTitle}>
<meta property="og:description" content={description || pageTitle}> <meta property="og:description" content={description || pageTitle}>
<meta property="og:image" content={banner}>
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta property="twitter:url" content={Astro.url}> <meta property="twitter:url" content={Astro.url}>

View file

@ -11,14 +11,15 @@ interface Props {
title: string; title: string;
banner?: string; banner?: string;
description?: string; description?: string;
cover?: string;
} }
const { title, banner, description } = Astro.props; const { title, banner, description, cover } = Astro.props;
const isHomePage = pathsEqual(Astro.url.pathname, "/"); const isHomePage = pathsEqual(Astro.url.pathname, "/");
const enableBanner = siteConfig.banner.enable; const enableBanner = siteConfig.banner.enable;
--- ---
<Layout title={title} banner={banner} description={description}> <Layout title={title} banner={banner ?? siteConfig.banner.src} cover={cover} description={description}>
<slot slot="head" name="head"></slot> <slot slot="head" name="head"></slot>
<div class="max-w-[var(--page-width)] min-h-screen grid grid-cols-[17.5rem_auto] grid-rows-[auto_auto_1fr_auto] lg:grid-rows-[auto_1fr_auto] <div class="max-w-[var(--page-width)] min-h-screen grid grid-cols-[17.5rem_auto] grid-rows-[auto_auto_1fr_auto] lg:grid-rows-[auto_1fr_auto]
mx-auto gap-4 relative px-0 md:px-4" mx-auto gap-4 relative px-0 md:px-4"

View file

@ -1,5 +1,6 @@
--- ---
import path from "path"; import path from "path";
import { getImage } from "astro:assets";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import License from "@components/misc/License.astro"; import License from "@components/misc/License.astro";
import Markdown from "@components/misc/Markdown.astro"; import Markdown from "@components/misc/Markdown.astro";
@ -43,8 +44,20 @@ const jsonLd = {
datePublished: formatDateToYYYYMMDD(entry.data.published), datePublished: formatDateToYYYYMMDD(entry.data.published),
// TODO include cover image here // TODO include cover image here
}; };
let coverImageUrl: string | undefined;
if (entry.data.image) {
coverImageUrl = await getImage({
src: entry.data.image,
width: 1200,
height: 630,
}).then(img => img.src);
}
console.log(coverImageUrl);
--- ---
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description}> <MainGridLayout banner={coverImageUrl} cover={coverImageUrl} title={entry.data.title} description={entry.data.description}>
<script slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script> <script slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>
<div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative mb-4"> <div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative mb-4">
<div id="post-container" class:list={["card-base z-10 px-6 md:px-9 pt-6 pb-4 relative w-full ", <div id="post-container" class:list={["card-base z-10 px-6 md:px-9 pt-6 pb-4 relative w-full ",
@ -94,7 +107,7 @@ const jsonLd = {
<!-- always show cover as long as it has one --> <!-- always show cover as long as it has one -->
{entry.data.image && {entry.data.image &&
<ImageWrapper src={entry.data.image} basePath={path.join("content/posts/", getDir(entry.id))} class="mb-8 rounded-xl banner-container onload-animation"/> <ImageWrapper src={entry.data.image} basePath={path.join("content/posts/", getDir(entry.id))} id="cover" class="mb-8 rounded-xl banner-container onload-animation"/>
} }