WIP: copying over the v0 components

This commit is contained in:
Tony Sullivan 2022-08-19 15:09:44 -04:00
parent beed20be4a
commit fd5137528d
4 changed files with 189 additions and 0 deletions

View file

@ -0,0 +1,91 @@
---
export interface Image {
src: string;
alt: string;
}
export interface SEOMetadata {
name: string;
title: string;
description: string;
image: Image;
canonicalURL?: URL | string;
locale?: string;
}
export interface OpenGraph extends Partial<SEOMetadata> {
type?: 'website' | 'article';
}
export interface Twitter extends Partial<SEOMetadata> {
handle?: string;
card?: 'summary_large_image';
}
export interface Props extends SEOMetadata {
og?: OpenGraph;
twitter?: Twitter;
}
const {
name,
title,
description,
image,
locale = 'en',
canonicalURL = new URL(Astro.url.pathname, Astro.site),
og: _og = { },
twitter: _twitter = { }
} = Astro.props as Props;
const og: OpenGraph = {
name,
title,
description,
canonicalURL,
image,
locale,
type: 'website',
..._og,
}
const twitter: Twitter = {
name,
title,
description,
canonicalURL,
image,
locale,
card: 'summary_large_image',
..._twitter,
};
---
<!-- Page Metadata -->
<meta name="generator" content={Astro.generator} />
<link rel="canonical" href={canonicalURL} />
<title>{title}</title>
<!-- OpenGraph Tags -->
<meta property="og:title" content={og.title} />
<meta property="og:type" content={og.type} />
<meta property="og:url" content={og.canonicalURL} />
<meta property="og:locale" content={og.locale} />
<meta property="og:description" content={og.description} />
<meta property="og:site_name" content={og.name} />
{og.image && (
<meta property="og:image" content={og.image.src} />
<meta property="og:image:alt" content={og.image.alt} />
)}
<!-- Twitter Tags -->
<meta name="twitter:card" content="summary_large_image" />
{twitter.handle && (
<meta name="twitter:site" content={twitter.handle} />
)}
<meta name="twitter:title" content={twitter.title} />
<meta name="twitter:description" content={twitter.description} />
{twitter.image && (
<meta name="twitter:image" content={twitter.image.src} />
<meta name="twitter:image:alt" content={twitter.image.alt} />
)}

View file

@ -0,0 +1,16 @@
---
import type { Thing } from 'schema-dts';
import { ldToString } from './schema.js';
export interface Props {
/** Adds indentation, white space, and line break characters to JSON-LD output. {@link JSON.stringify} */
space?: string | number;
json: Thing | Thing[];
}
const { space, json } = Astro.props as Props;
const children = ldToString(json, space);
---
<script type="application/ld+json" set:html={children}></script>

View file

@ -1,2 +1,4 @@
export { default as Code } from './Code.astro';
export { default as Debug } from './Debug.astro';
export { default as Schema } from './Schema.astro';
export { default as SEO } from './SEO.astro';

View file

@ -0,0 +1,80 @@
import type { Graph, Thing, WithContext } from 'schema-dts';
type JsonValueScalar = string | boolean | number;
type JsonValue =
| JsonValueScalar
| Array<JsonValue>
| { [key: string]: JsonValue };
type JsonReplacer = (_: string, value: JsonValue) => JsonValue | undefined;
const ESCAPE_ENTITIES = Object.freeze({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&apos;",
});
const ESCAPE_REGEX = new RegExp(
`[${Object.keys(ESCAPE_ENTITIES).join("")}]`,
"g"
);
const ESCAPE_REPLACER = (t: string): string =>
ESCAPE_ENTITIES[t as keyof typeof ESCAPE_ENTITIES];
/**
* A replacer for JSON.stringify to strip JSON-LD of illegal HTML entities
* per https://www.w3.org/TR/json-ld11/#restrictions-for-contents-of-json-ld-script-elements
*/
const safeJsonLdReplacer: JsonReplacer = (() => {
// Replace per https://www.w3.org/TR/json-ld11/#restrictions-for-contents-of-json-ld-script-elements
// Solution from https://stackoverflow.com/a/5499821/864313
return (_: string, value: JsonValue): JsonValue | undefined => {
switch (typeof value) {
case "object":
// Omit null values.
if (value === null) {
return undefined;
}
return value; // JSON.stringify will recursively call replacer.
case "number":
case "boolean":
case "bigint":
return value; // These values are not risky.
case "string":
return value.replace(ESCAPE_REGEX, ESCAPE_REPLACER);
default: {
// We shouldn't expect other types.
isNever(value);
// JSON.stringify will remove this element.
return undefined;
}
}
};
})();
// Utility: Assert never
function isNever(_: never): void {}
function withContext<T extends Thing>(thing: T): WithContext<T> {
return {
'@context': 'https://schema.org',
...(thing as Object)
} as WithContext<T>;
}
function asGraph(things: Thing[]): Graph {
return {
'@context': 'https://schema.org',
'@graph': things
}
}
export function ldToString(json: Thing | Thing[], space?: number | string) {
const ld = Array.isArray(json)
? asGraph(json)
: withContext(json);
return JSON.stringify(ld, safeJsonLdReplacer, space);
}