docusaurus/packages/docusaurus-theme-common/src/utils/structuredDataUtils.ts
ozaki 32fa7e0a8d feat(blog): add LastUpdateAuthor & LastUpdateTime (#9912)
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-03-15 14:54:38 +01:00

177 lines
5 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {useBaseUrlUtils, type BaseUrlUtils} from '@docusaurus/useBaseUrl';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {useBlogMetadata} from '@docusaurus/plugin-content-blog/client';
import type {Props as BlogListPageStructuredDataProps} from '@theme/BlogListPage/StructuredData';
import {useBlogPost} from '../contexts/blogPost';
import type {
Blog,
BlogPosting,
WithContext,
Person,
ImageObject,
} from 'schema-dts';
import type {
Author,
PropBlogPostContent,
} from '@docusaurus/plugin-content-blog';
import type {DocusaurusConfig} from '@docusaurus/types';
const convertDate = (dateMs: number) => new Date(dateMs * 1000).toISOString();
function getBlogPost(
blogPostContent: PropBlogPostContent,
siteConfig: DocusaurusConfig,
withBaseUrl: BaseUrlUtils['withBaseUrl'],
): BlogPosting {
const {assets, frontMatter, metadata} = blogPostContent;
const {date, title, description, lastUpdatedAt} = metadata;
const image = assets.image ?? frontMatter.image;
const keywords = frontMatter.keywords ?? [];
const blogUrl = `${siteConfig.url}${metadata.permalink}`;
const dateModified = lastUpdatedAt ? convertDate(lastUpdatedAt) : undefined;
return {
'@type': 'BlogPosting',
'@id': blogUrl,
mainEntityOfPage: blogUrl,
url: blogUrl,
headline: title,
name: title,
description,
datePublished: date,
...(dateModified ? {dateModified} : {}),
...getAuthor(metadata.authors),
...getImage(image, withBaseUrl, title),
...(keywords ? {keywords} : {}),
};
}
function getAuthor(authors: Author[]) {
const authorsStructuredData = authors.map(createPersonStructuredData);
return {
author:
authorsStructuredData.length === 1
? authorsStructuredData[0]
: authorsStructuredData,
};
}
function getImage(
image: string | undefined,
withBaseUrl: BaseUrlUtils['withBaseUrl'],
title: string,
) {
return image
? {
image: createImageStructuredData({
imageUrl: withBaseUrl(image, {absolute: true}),
caption: `title image for the blog post: ${title}`,
}),
}
: {};
}
export function useBlogListPageStructuredData(
props: BlogListPageStructuredDataProps,
): WithContext<Blog> {
const {siteConfig} = useDocusaurusContext();
const {withBaseUrl} = useBaseUrlUtils();
const {
metadata: {blogDescription, blogTitle, permalink},
} = props;
const url = `${siteConfig.url}${permalink}`;
// details on structured data support: https://schema.org/Blog
return {
'@context': 'https://schema.org',
'@type': 'Blog',
'@id': url,
mainEntityOfPage: url,
headline: blogTitle,
description: blogDescription,
blogPost: props.items.map((blogItem) =>
getBlogPost(blogItem.content, siteConfig, withBaseUrl),
),
};
}
export function useBlogPostStructuredData(): WithContext<BlogPosting> {
const blogMetadata = useBlogMetadata();
const {assets, metadata} = useBlogPost();
const {siteConfig} = useDocusaurusContext();
const {withBaseUrl} = useBaseUrlUtils();
const {date, title, description, frontMatter, lastUpdatedAt} = metadata;
const image = assets.image ?? frontMatter.image;
const keywords = frontMatter.keywords ?? [];
const dateModified = lastUpdatedAt ? convertDate(lastUpdatedAt) : undefined;
const url = `${siteConfig.url}${metadata.permalink}`;
// details on structured data support: https://schema.org/BlogPosting
// BlogPosting is one of the structured data types that Google explicitly
// supports: https://developers.google.com/search/docs/appearance/structured-data/article#structured-data-type-definitions
return {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
'@id': url,
mainEntityOfPage: url,
url,
headline: title,
name: title,
description,
datePublished: date,
...(dateModified ? {dateModified} : {}),
...getAuthor(metadata.authors),
...getImage(image, withBaseUrl, title),
...(keywords ? {keywords} : {}),
isPartOf: {
'@type': 'Blog',
'@id': `${siteConfig.url}${blogMetadata.blogBasePath}`,
name: blogMetadata.blogTitle,
},
};
}
/** @returns A {@link https://schema.org/Person} constructed from the {@link Author} */
function createPersonStructuredData(author: Author): Person {
return {
'@type': 'Person',
...(author.name ? {name: author.name} : {}),
...(author.title ? {description: author.title} : {}),
...(author.url ? {url: author.url} : {}),
...(author.email ? {email: author.email} : {}),
...(author.imageURL ? {image: author.imageURL} : {}),
};
}
/** @returns A {@link https://schema.org/ImageObject} */
function createImageStructuredData({
imageUrl,
caption,
}: {
imageUrl: string;
caption: string;
}): ImageObject {
return {
'@type': 'ImageObject',
'@id': imageUrl,
url: imageUrl,
contentUrl: imageUrl,
caption,
};
}