mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
chore(site): rename website to site
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* JSON-LD Schema Helpers
|
||||
*
|
||||
* Centralized functions for generating Schema.org JSON-LD structured data.
|
||||
* Uses schema-dts for type safety.
|
||||
*/
|
||||
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import type {
|
||||
BlogPosting,
|
||||
CollectionPage,
|
||||
Person,
|
||||
ProfilePage,
|
||||
TechArticle,
|
||||
WithContext,
|
||||
} from 'schema-dts';
|
||||
|
||||
/**
|
||||
* Create a TechArticle schema for documentation pages.
|
||||
*/
|
||||
export function createTechArticleSchema(params: {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
updatedDate?: Date;
|
||||
wordCount?: number;
|
||||
readingTime?: number;
|
||||
}): WithContext<TechArticle> {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'TechArticle',
|
||||
headline: params.title,
|
||||
description: params.description,
|
||||
url: params.url,
|
||||
...(params.updatedDate && { dateModified: params.updatedDate.toISOString() }),
|
||||
...(params.wordCount && { wordCount: params.wordCount }),
|
||||
...(params.readingTime && { timeRequired: `PT${params.readingTime}M` }),
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'Video.js',
|
||||
url: 'https://v10.videojs.org',
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Video.js',
|
||||
url: 'https://v10.videojs.org',
|
||||
},
|
||||
inLanguage: 'en-US',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a BlogPosting schema for individual blog posts.
|
||||
*/
|
||||
export function createBlogPostingSchema(params: {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
pubDate: Date;
|
||||
updatedDate?: Date;
|
||||
wordCount?: number;
|
||||
readingTime?: number;
|
||||
authors: CollectionEntry<'authors'>[];
|
||||
siteUrl: string;
|
||||
}): WithContext<BlogPosting> {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
headline: params.title,
|
||||
description: params.description,
|
||||
url: params.url,
|
||||
datePublished: params.pubDate.toISOString(),
|
||||
...(params.updatedDate && { dateModified: params.updatedDate.toISOString() }),
|
||||
...(params.wordCount && { wordCount: params.wordCount }),
|
||||
...(params.readingTime && { timeRequired: `PT${params.readingTime}M` }),
|
||||
author: params.authors.map(
|
||||
(author): Person => ({
|
||||
'@type': 'Person',
|
||||
name: author.data.name,
|
||||
url: author.data.socialLinks?.website || `${params.siteUrl}blog/authors/${author.id}`,
|
||||
...(author.data.bio && { description: author.data.bio }),
|
||||
...(author.data.avatar && { image: author.data.avatar }),
|
||||
...(author.data.socialLinks && {
|
||||
sameAs: [
|
||||
author.data.socialLinks.x,
|
||||
author.data.socialLinks.bluesky,
|
||||
author.data.socialLinks.mastodon,
|
||||
author.data.socialLinks.github,
|
||||
author.data.socialLinks.linkedin,
|
||||
author.data.socialLinks.website,
|
||||
].filter(Boolean) as string[],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Video.js',
|
||||
url: 'https://v10.videojs.org',
|
||||
},
|
||||
inLanguage: 'en-US',
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': params.url,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CollectionPage schema for the blog index page.
|
||||
*/
|
||||
export function createBlogCollectionSchema(params: {
|
||||
url: string;
|
||||
posts: CollectionEntry<'blog'>[];
|
||||
siteUrl: string;
|
||||
}): WithContext<CollectionPage> {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'Video.js Blog',
|
||||
description: 'Articles, announcements, news, updates, and more',
|
||||
url: params.url,
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
numberOfItems: params.posts.length,
|
||||
itemListElement: params.posts.map((post, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
url: `${params.siteUrl}blog/${post.id}`,
|
||||
})),
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Video.js',
|
||||
url: 'https://v10.videojs.org',
|
||||
},
|
||||
inLanguage: 'en-US',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CollectionPage schema for the blog author index page.
|
||||
*/
|
||||
export function createAuthorCollectionSchema(params: {
|
||||
url: string;
|
||||
authors: CollectionEntry<'authors'>[];
|
||||
siteUrl: string;
|
||||
}): WithContext<CollectionPage> {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'Authors',
|
||||
description: 'Folks who write sick content for the Video.js blog',
|
||||
url: params.url,
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
numberOfItems: params.authors.length,
|
||||
itemListElement: params.authors.map((author, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
item: {
|
||||
'@type': 'Person',
|
||||
name: author.data.name,
|
||||
url: `${params.siteUrl}blog/authors/${author.id}`,
|
||||
...(author.data.bio && { description: author.data.bio }),
|
||||
...(author.data.avatar && { image: author.data.avatar }),
|
||||
...(author.data.socialLinks && {
|
||||
sameAs: [
|
||||
author.data.socialLinks.x,
|
||||
author.data.socialLinks.bluesky,
|
||||
author.data.socialLinks.mastodon,
|
||||
author.data.socialLinks.github,
|
||||
author.data.socialLinks.linkedin,
|
||||
author.data.socialLinks.website,
|
||||
].filter(Boolean) as string[],
|
||||
}),
|
||||
} as Person,
|
||||
})),
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Video.js',
|
||||
url: 'https://v10.videojs.org',
|
||||
},
|
||||
inLanguage: 'en-US',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ProfilePage schema for individual author pages.
|
||||
*/
|
||||
export function createProfilePageSchema(params: {
|
||||
url: string;
|
||||
author: CollectionEntry<'authors'>;
|
||||
posts: CollectionEntry<'blog'>[];
|
||||
siteUrl: string;
|
||||
}): WithContext<ProfilePage> {
|
||||
const personSchema: Person = {
|
||||
'@type': 'Person',
|
||||
name: params.author.data.name,
|
||||
url: params.url,
|
||||
...(params.author.data.bio && { description: params.author.data.bio }),
|
||||
...(params.author.data.avatar && { image: params.author.data.avatar }),
|
||||
...(params.author.data.socialLinks && {
|
||||
sameAs: [
|
||||
params.author.data.socialLinks.x,
|
||||
params.author.data.socialLinks.bluesky,
|
||||
params.author.data.socialLinks.mastodon,
|
||||
params.author.data.socialLinks.github,
|
||||
params.author.data.socialLinks.linkedin,
|
||||
params.author.data.socialLinks.website,
|
||||
].filter(Boolean) as string[],
|
||||
}),
|
||||
// Include authored articles
|
||||
...(params.posts.length > 0 && {
|
||||
publishedWorks: params.posts.map(post => ({
|
||||
'@type': 'BlogPosting',
|
||||
headline: post.data.title,
|
||||
url: `${params.siteUrl}blog/${post.id}`,
|
||||
datePublished: post.data.pubDate.toISOString(),
|
||||
})),
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ProfilePage',
|
||||
name: `${params.author.data.name} - Author Profile`,
|
||||
description: `Posts by ${params.author.data.name} on the Video.js blog`,
|
||||
url: params.url,
|
||||
mainEntity: personSchema,
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Video.js',
|
||||
url: 'https://v10.videojs.org',
|
||||
},
|
||||
inLanguage: 'en-US',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user