sitegen/framework/meta/render.ts
chloe caruso af60d1172f i accidentally deleted the repo, but recovered it. i'll start committing
it was weird. i pressed delete on a subfolder, i think one of the
pages.off folders that i was using. and then, suddenly, nvim on windows
7 decided to delete every file in the directory. they weren't shred off
the space time continuum, but just marked deleted. i had to pay $80 to
get access to a software that could see them. bleh!

just seeing all my work, a little over a week, was pretty heart
shattering. but i remembered that long ago, a close friend said i could
call them whenever i was feeling sad. i finally took them up on that
offer. the first time i've ever called someone for emotional support.
but it's ok. i got it back. and the site framework is better than ever.

i'm gonna commit and push more often. the repo is private anyways.
2025-06-06 23:38:02 -07:00

565 lines
17 KiB
TypeScript

import type { Icon, ResolvedMetadata } from "./types";
import { escapeHTML as esc } from "./utils";
function Meta(name: string, content: any) {
return `<meta name="${esc(name)}" content="${esc(content)}">`;
}
function MetaProp(name: string, content: any) {
return `<meta property="${esc(name)}" content="${esc(content)}">`;
}
function MetaMedia(name: string, content: any, media: string) {
return `<meta name="${esc(name)}" content="${esc(content)}" media="${
esc(media)
}">`;
}
function Link(rel: string, href: any) {
return `<link rel="${esc(rel)}" href="${esc(href)}" />`;
}
function LinkMedia(rel: string, href: any, media: string) {
return `<link rel="${esc(rel)}" href="${esc(href)}" media="${esc(media)}">`;
}
const resolveUrl = (
url: string | URL,
) => (typeof url === "string" ? url : url.toString());
function IconLink(rel: string, icon: Icon) {
if (typeof icon === "object" && !(icon instanceof URL)) {
const { url, rel: _, ...props } = icon;
return `<link rel="${esc(rel)}" href="${esc(resolveUrl(url))}"${
Object.keys(props)
.map((key) => ` ${key}="${esc(props[key])}"`)
.join("")
}>`;
} else {
const href = resolveUrl(icon);
return Link(rel, href);
}
}
function ExtendMeta(prefix: string, content: any) {
if (
typeof content === "string" || typeof content === "number" ||
content instanceof URL
) {
return MetaProp(prefix, content);
} else {
let str = "";
for (const [prop, value] of Object.entries(content)) {
if (value) {
str += MetaProp(
prefix === "og:image" && prop === "url"
? "og:image"
: prefix + ":" + prop,
value,
);
}
}
return str;
}
}
const formatDetectionKeys = [
"telephone",
"date",
"address",
"email",
"url",
] as const;
export function renderMetadata(meta: ResolvedMetadata): string {
var str = "";
// <BasicMetadata/>
if (meta.title?.absolute) str += `<title>${esc(meta.title.absolute)}</title>`;
if (meta.description) str += Meta("description", meta.description);
if (meta.applicationName) {
str += Meta("application-name", meta.applicationName);
}
if (meta.authors) {
for (var author of meta.authors) {
if (author.url) str += Link("author", author.url);
if (author.name) str += Meta("author", author.name);
}
}
if (meta.manifest) str += Link("manifest", meta.manifest);
if (meta.generator) str += Meta("generator", meta.generator);
if (meta.referrer) str += Meta("referrer", meta.referrer);
if (meta.themeColor) {
for (var themeColor of meta.themeColor) {
str += !themeColor.media
? Meta("theme-color", themeColor.color)
: MetaMedia("theme-color", themeColor.color, themeColor.media);
}
}
if (meta.colorScheme) str += Meta("color-scheme", meta.colorScheme);
if (meta.viewport) str += Meta("viewport", meta.viewport);
if (meta.creator) str += Meta("creator", meta.creator);
if (meta.publisher) str += Meta("publisher", meta.publisher);
if (meta.robots?.basic) str += Meta("robots", meta.robots.basic);
if (meta.robots?.googleBot) str += Meta("googlebot", meta.robots.googleBot);
if (meta.abstract) str += Meta("abstract", meta.abstract);
if (meta.archives) {
for (var archive of meta.archives) {
str += Link("archives", archive);
}
}
if (meta.assets) {
for (var asset of meta.assets) {
str += Link("assets", asset);
}
}
if (meta.bookmarks) {
for (var bookmark of meta.bookmarks) {
str += Link("bookmarks", bookmark);
}
}
if (meta.category) str += Meta("category", meta.category);
if (meta.classification) str += Meta("classification", meta.classification);
if (meta.other) {
for (var [name, content] of Object.entries(meta.other)) {
if (content) {
str += Meta(name, Array.isArray(content) ? content.join(",") : content);
}
}
}
// <AlternatesMetadata />
var alternates = meta.alternates;
if (alternates) {
if (alternates.canonical) {
str += Link("canonical", alternates.canonical.url);
}
if (alternates.languages) {
for (var [locale, urls] of Object.entries(alternates.languages)) {
for (var { url, title } of urls) {
str += `<link rel="alternate" hreflang="${esc(locale)}" href="${
esc(url.toString())
}"${title ? ` title="${esc(title)}"` : ""}>`;
}
}
}
if (alternates.media) {
for (var [media, urls2] of Object.entries(alternates.media)) {
if (urls2) {
for (var { url, title } of urls2) {
str += `<link rel="alternate" media="${esc(media)}" href="${
esc(url.toString())
}"${title ? ` title="${esc(title)}"` : ""}>`;
}
}
}
}
if (alternates.types) {
for (var [type, urls2] of Object.entries(alternates.types)) {
if (urls2) {
for (var { url, title } of urls2) {
str += `<link rel="alternate" type="${esc(type)}" href="${
esc(url.toString())
}"${title ? ` title="${esc(title)}"` : ""}>`;
}
}
}
}
}
// <ItunesMeta />
if (meta.itunes) {
str += Meta(
"apple-itunes-app",
`app-id=${meta.itunes.appId}${
meta.itunes.appArgument
? `, app-argument=${meta.itunes.appArgument}`
: ""
}`,
);
}
// <FormatDetectionMeta />
if (meta.formatDetection) {
var contentStr = "";
for (var key of formatDetectionKeys) {
if (key in meta.formatDetection) {
if (contentStr) contentStr += ", ";
contentStr += `${key}=no`;
}
}
str += Meta("format-detection", contentStr);
}
// <VerificationMeta />
if (meta.verification) {
if (meta.verification.google) {
for (var verificationKey of meta.verification.google) {
str += Meta("google-site-verification", verificationKey);
}
}
if (meta.verification.yahoo) {
for (var verificationKey of meta.verification.yahoo) {
str += Meta("y_key", verificationKey);
}
}
if (meta.verification.yandex) {
for (var verificationKey of meta.verification.yandex) {
str += Meta("yandex-verification", verificationKey);
}
}
if (meta.verification.me) {
for (var verificationKey of meta.verification.me) {
str += Meta("me", verificationKey);
}
}
if (meta.verification.other) {
for (
var [verificationKey2, values] of Object.entries(
meta.verification.other,
)
) {
for (var value of values) {
str += Meta(verificationKey2, value);
}
}
}
}
// <AppleWebAppMeta />
if (meta.appleWebApp) {
const { capable, title, startupImage, statusBarStyle } = meta.appleWebApp;
if (capable) {
str += '<meta name="apple-mobile-web-app-capable" content="yes" />';
}
if (title) str += Meta("apple-mobile-web-app-title", title);
if (startupImage) {
for (const img of startupImage) {
str += !img.media
? Link("apple-touch-startup-image", img.url)
: LinkMedia("apple-touch-startup-image", img.url, img.media);
}
}
if (statusBarStyle) {
str += Meta("apple-mobile-web-app-status-bar-style", statusBarStyle);
}
}
// <OpenGraphMetadata />
if (meta.openGraph) {
const og = meta.openGraph;
if (og.determiner) str += MetaProp("og:determiner", og.determiner);
if (og.title?.absolute) str += MetaProp("og:title", og.title.absolute);
if (og.description) str += MetaProp("og:description", og.description);
if (og.url) str += MetaProp("og:url", og.url.toString());
if (og.siteName) str += MetaProp("og:site_name", og.siteName);
if (og.locale) str += MetaProp("og:locale", og.locale);
if (og.countryName) str += MetaProp("og:country_name", og.countryName);
if (og.ttl) str += MetaProp("og:ttl", og.ttl);
if (og.images) {
for (const item of og.images) {
str += ExtendMeta("og:image", item);
}
}
if (og.videos) {
for (const item of og.videos) {
str += ExtendMeta("og:video", item);
}
}
if (og.audio) {
for (const item of og.audio) {
str += ExtendMeta("og:audio", item);
}
}
if (og.emails) {
for (const item of og.emails) {
str += ExtendMeta("og:email", item);
}
}
if (og.phoneNumbers) {
for (const item of og.phoneNumbers) {
str += MetaProp("og:phone_number", item);
}
}
if (og.faxNumbers) {
for (const item of og.faxNumbers) {
str += MetaProp("og:fax_number", item);
}
}
if (og.alternateLocale) {
for (const item of og.alternateLocale) {
str += MetaProp("og:locale:alternate", item);
}
}
if ("type" in og) {
str += MetaProp("og:type", og.type);
switch (og.type) {
case "website":
break;
case "article":
if (og.publishedTime) {
str += MetaProp("article:published_time", og.publishedTime);
}
if (og.modifiedTime) {
str += MetaProp("article:modified_time", og.modifiedTime);
}
if (og.expirationTime) {
str += MetaProp("article:expiration_time", og.expirationTime);
}
if (og.authors) {
for (const item of og.authors) {
str += MetaProp("article:author", item);
}
}
if (og.section) str += MetaProp("article:section", og.section);
if (og.tags) {
for (const item of og.tags) {
str += MetaProp("article:tag", item);
}
}
break;
case "book":
if (og.isbn) str += MetaProp("book:isbn", og.isbn);
if (og.releaseDate) {
str += MetaProp("book:release_date", og.releaseDate);
}
if (og.authors) {
for (const item of og.authors) {
str += MetaProp("article:author", item);
}
}
if (og.tags) {
for (const item of og.tags) {
str += MetaProp("article:tag", item);
}
}
break;
case "profile":
if (og.firstName) str += MetaProp("profile:first_name", og.firstName);
if (og.lastName) str += MetaProp("profile:last_name", og.lastName);
if (og.username) str += MetaProp("profile:first_name", og.username);
if (og.gender) str += MetaProp("profile:first_name", og.gender);
break;
case "music.song":
if (og.duration) str += MetaProp("music:duration", og.duration);
if (og.albums) {
for (const item of og.albums) {
str += ExtendMeta("music:albums", item);
}
}
if (og.musicians) {
for (const item of og.musicians) {
str += MetaProp("music:musician", item);
}
}
break;
case "music.album":
if (og.songs) {
for (const item of og.songs) {
str += ExtendMeta("music:song", item);
}
}
if (og.musicians) {
for (const item of og.musicians) {
str += MetaProp("music:musician", item);
}
}
if (og.releaseDate) {
str += MetaProp("music:release_date", og.releaseDate);
}
break;
case "music.playlist":
if (og.songs) {
for (const item of og.songs) {
str += ExtendMeta("music:song", item);
}
}
if (og.creators) {
for (const item of og.creators) {
str += MetaProp("music:creator", item);
}
}
break;
case "music.radio_station":
if (og.creators) {
for (const item of og.creators) {
str += MetaProp("music:creator", item);
}
}
break;
case "video.movie":
if (og.actors) {
for (const item of og.actors) {
str += ExtendMeta("video:actor", item);
}
}
if (og.directors) {
for (const item of og.directors) {
str += MetaProp("video:director", item);
}
}
if (og.writers) {
for (const item of og.writers) {
str += MetaProp("video:writer", item);
}
}
if (og.duration) str += MetaProp("video:duration", og.duration);
if (og.releaseDate) {
str += MetaProp("video:release_date", og.releaseDate);
}
if (og.tags) {
for (const item of og.tags) {
str += MetaProp("video:tag", item);
}
}
break;
case "video.episode":
if (og.actors) {
for (const item of og.actors) {
str += ExtendMeta("video:actor", item);
}
}
if (og.directors) {
for (const item of og.directors) {
str += MetaProp("video:director", item);
}
}
if (og.writers) {
for (const item of og.writers) {
str += MetaProp("video:writer", item);
}
}
if (og.duration) str += MetaProp("video:duration", og.duration);
if (og.releaseDate) {
str += MetaProp("video:release_date", og.releaseDate);
}
if (og.tags) {
for (const item of og.tags) {
str += MetaProp("video:tag", item);
}
}
if (og.series) str += MetaProp("video:series", og.series);
break;
case "video.other":
case "video.tv_show":
default:
throw new Error("Invalid OpenGraph type: " + og.type);
}
}
}
// <TwitterMetadata />
if (meta.twitter) {
const twitter = meta.twitter;
if (twitter.card) str += Meta("twitter:card", twitter.card);
if (twitter.site) str += Meta("twitter:site", twitter.site);
if (twitter.siteId) str += Meta("twitter:site:id", twitter.siteId);
if (twitter.creator) str += Meta("twitter:creator", twitter.creator);
if (twitter.creatorId) str += Meta("twitter:creator:id", twitter.creatorId);
if (twitter.title?.absolute) {
str += Meta("twitter:title", twitter.title.absolute);
}
if (twitter.description) {
str += Meta("twitter:description", twitter.description);
}
if (twitter.images) {
for (const img of twitter.images) {
str += Meta("twitter:image", img.url);
if (img.alt) str += Meta("twitter:image:alt", img.alt);
}
}
if (twitter.card === "player") {
for (const player of twitter.players) {
if (player.playerUrl) str += Meta("twitter:player", player.playerUrl);
if (player.streamUrl) {
str += Meta("twitter:player:stream", player.streamUrl);
}
if (player.width) str += Meta("twitter:player:width", player.width);
if (player.height) str += Meta("twitter:player:height", player.height);
}
}
if (twitter.card === "app") {
for (const type of ["iphone", "ipad", "googleplay"]) {
if (twitter.app.id[type]) {
str += Meta(`twitter:app:name:${type}`, twitter.app.name);
str += Meta(`twitter:app:id:${type}`, twitter.app.id[type]);
}
if (twitter.app.url?.[type]) {
str += Meta(`twitter:app:url:${type}`, twitter.app.url[type]);
}
}
}
}
// <AppLinksMeta />
if (meta.appLinks) {
if (meta.appLinks.ios) {
for (var item of meta.appLinks.ios) {
str += ExtendMeta("al:ios", item);
}
}
if (meta.appLinks.iphone) {
for (var item of meta.appLinks.iphone) {
str += ExtendMeta("al:iphone", item);
}
}
if (meta.appLinks.ipad) {
for (var item of meta.appLinks.ipad) {
str += ExtendMeta("al:ipad", item);
}
}
if (meta.appLinks.android) {
for (var item2 of meta.appLinks.android) {
str += ExtendMeta("al:android", item2);
}
}
if (meta.appLinks.windows_phone) {
for (var item3 of meta.appLinks.windows_phone) {
str += ExtendMeta("al:windows_phone", item3);
}
}
if (meta.appLinks.windows) {
for (var item3 of meta.appLinks.windows) {
str += ExtendMeta("al:windows", item3);
}
}
if (meta.appLinks.windows_universal) {
for (var item4 of meta.appLinks.windows_universal) {
str += ExtendMeta("al:windows_universal", item4);
}
}
if (meta.appLinks.web) {
for (const item of meta.appLinks.web) {
str += ExtendMeta("al:web", item);
}
}
}
// <IconsMetadata />
if (meta.icons) {
if (meta.icons.shortcut) {
for (var icon of meta.icons.shortcut) {
str += IconLink("shortcut icon", icon);
}
}
if (meta.icons.icon) {
for (var icon of meta.icons.icon) {
str += IconLink("icon", icon);
}
}
if (meta.icons.apple) {
for (var icon of meta.icons.apple) {
str += IconLink("apple-touch-icon", icon);
}
}
if (meta.icons.other) {
for (var icon of meta.icons.other) {
str += IconLink(icon.rel ?? "icon", icon);
}
}
}
return str;
}