UNPKG

@studiocms/ui

Version:

The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.

176 lines (175 loc) 5.15 kB
import { z } from "astro/zod"; const HeadConfigSchema = () => z.array( z.object({ /** Name of the HTML tag to add to `<head>`, e.g. `'meta'`, `'link'`, or `'script'`. */ tag: z.enum(["title", "base", "link", "style", "meta", "script", "noscript", "template"]), /** Attributes to set on the tag, e.g. `{ rel: 'stylesheet', href: '/custom.css' }`. */ attrs: z.record(z.string(), z.union([z.string(), z.boolean(), z.undefined()])).default({}), /** Content to place inside the tag (optional). */ content: z.string().default("") }) ).default([]); const headDefaults = (title, description, Astro, ogImage, canonical) => { const headDefaults2 = [ { tag: "meta", attrs: { charset: "utf-8" } }, { tag: "meta", attrs: { name: "viewport", content: "width=device-width, initial-scale=1" } }, { tag: "title", content: `${title}` }, { tag: "meta", attrs: { name: "title", content: title } }, { tag: "meta", attrs: { name: "description", content: description } }, { tag: "link", attrs: { rel: "canonical", href: canonical?.href } }, { tag: "meta", attrs: { name: "generator", content: Astro.generator } }, // Favicon { tag: "link", attrs: { rel: "apple-touch-icon", href: "/apple-touch-icon.png", sizes: "180x180" } }, { tag: "link", attrs: { rel: "icon", href: "/favicon-32x32.png", type: "image/png", sizes: "32x32" } }, { tag: "link", attrs: { rel: "icon", href: "/favicon-16x16.png", type: "image/png", sizes: "16x16" } }, { tag: "link", attrs: { rel: "icon", href: "/favicon.png", type: "image/png" } }, { tag: "link", attrs: { rel: "manifest", href: "/site.webmanifest" } }, { tag: "link", attrs: { rel: "mask-icon", href: "/safari-pinned-tab.svg", color: "#5bbad5" } }, { tag: "link", attrs: { rel: "shortcut icon", href: "/favicon.ico" } }, { tag: "meta", attrs: { name: "msapplication-TileColor", content: "#da532c" } }, { tag: "meta", attrs: { name: "msapplication-config", content: "/browserconfig.xml" } }, { tag: "meta", attrs: { name: "theme-color", content: "#aa87f4" } }, // OpenGraph Tags { tag: "meta", attrs: { property: "og:title", content: title } }, { tag: "meta", attrs: { property: "og:type", content: "website" } }, { tag: "meta", attrs: { property: "og:url", content: canonical?.href } }, { tag: "meta", attrs: { property: "og:description", content: description } }, { tag: "meta", attrs: { property: "og:site_name", content: title } }, // Twitter Tags { tag: "meta", attrs: { name: "twitter:card", content: "summary_large_image" } }, { tag: "meta", attrs: { name: "twitter:url", content: canonical?.href } }, { tag: "meta", attrs: { name: "twitter:title", content: title } }, { tag: "meta", attrs: { name: "twitter:description", content: description } } ]; if (ogImage) { headDefaults2.push( { tag: "meta", attrs: { property: "og:image", content: ogImage } }, { tag: "meta", attrs: { name: "twitter:image", content: ogImage } } ); } return headDefaults2; }; const HeadSchema = HeadConfigSchema(); function hasTag(head, entry) { switch (entry.tag) { case "title": return head.some(({ tag }) => tag === "title"); case "meta": return hasOneOf(head, entry, ["name", "property", "http-equiv"]); default: return false; } } function hasOneOf(head, entry, keys) { const attr = getAttr(keys, entry); if (!attr) return false; const [key, val] = attr; return head.some(({ tag, attrs }) => tag === entry.tag && attrs[key] === val); } function getAttr(keys, entry) { let attr; for (const key of keys) { const val = entry.attrs[key]; if (val) { attr = [key, val]; break; } } return attr; } function mergeHead(oldHead, newHead) { return [...oldHead.filter((tag) => !hasTag(newHead, tag)), ...newHead]; } function sortHead(head) { return head.sort((a, b) => { const aImportance = getImportance(a); const bImportance = getImportance(b); return aImportance > bImportance ? -1 : bImportance > aImportance ? 1 : 0; }); } function getImportance(entry) { if (entry.tag === "meta" && ("charset" in entry.attrs || "http-equiv" in entry.attrs || entry.attrs.name === "viewport")) { return 100; } if (entry.tag === "title") return 90; if (entry.tag !== "meta") { if (entry.tag === "link" && "rel" in entry.attrs && entry.attrs.rel === "shortcut icon") { return 70; } return 80; } return 0; } function createHead(defaultHeaders, ...heads) { let head = HeadSchema.parse(defaultHeaders); for (const next of heads) { head = mergeHead(head, next); } return sortHead(head); } export { HeadConfigSchema, HeadSchema, createHead, getAttr, getImportance, hasOneOf, hasTag, headDefaults, mergeHead, sortHead };