WIP: copying over the v0 components
This commit is contained in:
parent
beed20be4a
commit
fd5137528d
4 changed files with 189 additions and 0 deletions
91
packages/astro/components/SEO.astro
Normal file
91
packages/astro/components/SEO.astro
Normal 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} />
|
||||
)}
|
16
packages/astro/components/Schema.astro
Normal file
16
packages/astro/components/Schema.astro
Normal 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>
|
|
@ -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';
|
||||
|
|
80
packages/astro/components/schema.ts
Normal file
80
packages/astro/components/schema.ts
Normal 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({
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
});
|
||||
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);
|
||||
}
|
Loading…
Reference in a new issue