import type { Icon, ResolvedMetadata } from "./types"; import { escapeHTML as esc } from "./utils"; function Meta(name: string, content: any) { return ``; } function MetaProp(name: string, content: any) { return ``; } function MetaMedia(name: string, content: any, media: string) { return ``; } function Link(rel: string, href: any) { return ``; } function LinkMedia(rel: string, href: any, media: string) { return ``; } 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 ` ` ${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 = ""; // if (meta.title?.absolute) str += `${esc(meta.title.absolute)}`; 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); } } } // 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 += ``; } } } if (alternates.media) { for (var [media, urls2] of Object.entries(alternates.media)) { if (urls2) { for (var { url, title } of urls2) { str += ``; } } } } if (alternates.types) { for (var [type, urls2] of Object.entries(alternates.types)) { if (urls2) { for (var { url, title } of urls2) { str += ``; } } } } } // if (meta.itunes) { str += Meta( "apple-itunes-app", `app-id=${meta.itunes.appId}${ meta.itunes.appArgument ? `, app-argument=${meta.itunes.appArgument}` : "" }`, ); } // 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); } // 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); } } } } // if (meta.appleWebApp) { const { capable, title, startupImage, statusBarStyle } = meta.appleWebApp; if (capable) { str += ''; } 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); } } // 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); } } } // 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]); } } } } // 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); } } } // 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; }