import type { CollectionEntry } from "astro:content"; import type { ImageMetadata } from "astro:assets"; export const SITE_URL = "https://mariosant.dev"; export const TWITTER_HANDLE = "@marios_ant"; export const AUTHOR_NAME = "Marios Antonoudiou"; export const AUTHOR_JOB_TITLE = "AI Product Engineer"; export const SOCIALS = { github: "https://github.com/mariosant", linkedin: "https://www.linkedin.com/in/mariosant/", bluesky: "https://bsky.app/profile/mariosant.bsky.social", email: "mailto:mariosant@sent.com", calendar: "https://cal.com/mariosant/30min", } as const; export const PERSON_ID = `${SITE_URL}/#person`; export const AUTHOR_DESCRIPTION = "AI product engineer building AI-powered products that feel simple, useful, and ready for real users."; export const stripMarkdown = (body: string): string => body .replace(/```[\s\S]*?```/g, " ") .replace(/`[^`]*`/g, " ") .replace(/^#{1,6}\s+.*$/gm, "") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") .replace(/!\[[^\]]*\]\([^)]+\)/g, "") .replace(/^>\s*/gm, "") .replace(/^\s*[-*+]\s+/gm, "") .replace(/^\s*\d+\.\s+/gm, "") .replace(/[*_~]+/g, "") .replace(/\s+/g, " ") .trim(); export const deriveDescription = (body: string, max = 155): string => { const plain = stripMarkdown(body); if (plain.length <= max) return plain; const cut = plain.slice(0, max); const lastSpace = cut.lastIndexOf(" "); const safeCut = lastSpace > 80 ? cut.slice(0, lastSpace) : cut; return `${safeCut.trimEnd()}…`; }; type ArticleEntry = CollectionEntry<"articles">; export const imageSrc = (img: ImageMetadata | string): string => typeof img === "string" ? img : img.src; export const personJsonLd = (): string => JSON.stringify({ "@context": "https://schema.org", "@type": "Person", "@id": PERSON_ID, name: AUTHOR_NAME, jobTitle: AUTHOR_JOB_TITLE, url: SITE_URL, image: `${SITE_URL}/mariosant.webp`, description: AUTHOR_DESCRIPTION, sameAs: [SOCIALS.github, SOCIALS.linkedin, SOCIALS.bluesky], }); export const articleJsonLd = (entry: ArticleEntry): string => { const description = entry.data.description ?? deriveDescription(entry.body ?? ""); return JSON.stringify({ "@context": "https://schema.org", "@type": "Article", headline: entry.data.title, description, image: imageSrc(entry.data.coverImage.url), datePublished: entry.data.date.toISOString(), author: { "@id": PERSON_ID, "@type": "Person", name: AUTHOR_NAME, }, publisher: { "@id": PERSON_ID, "@type": "Person", name: AUTHOR_NAME, }, mainEntityOfPage: { "@type": "WebPage", "@id": `${SITE_URL}/articles/${entry.id}`, }, }); }; export const breadcrumbJsonLd = (entry: ArticleEntry): string => JSON.stringify({ "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [ { "@type": "ListItem", position: 1, name: "Home", item: SITE_URL, }, { "@type": "ListItem", position: 2, name: "Articles", item: `${SITE_URL}/articles`, }, { "@type": "ListItem", position: 3, name: entry.data.title, item: `${SITE_URL}/articles/${entry.id}`, }, ], }); export const articleListJsonLd = (entries: readonly ArticleEntry[]): string => JSON.stringify({ "@context": "https://schema.org", "@type": "ItemList", name: "Articles by Marios Antonoudiou", itemListElement: entries.map((entry, index) => ({ "@type": "ListItem", position: index + 1, url: `${SITE_URL}/articles/${entry.id}`, name: entry.data.title, })), }); export const renderJsonLd = (data: unknown): string => { if (Array.isArray(data)) { return data.map((item) => JSON.stringify(item)).join("\n"); } return JSON.stringify(data); };