mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 16:47:26 +02:00
refactor(theme): use JSON-LD instead of microdata for blog structured data (#9669)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com> Co-authored-by: sebastien <lorber.sebastien@gmail.com>
This commit is contained in:
parent
8abd1899a6
commit
60d9346965
23 changed files with 348 additions and 68 deletions
|
@ -4,9 +4,21 @@
|
|||
"description": "Blog plugin for Docusaurus.",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/plugin-content-blog.d.ts",
|
||||
"exports": {
|
||||
"./lib/*": "./lib/*",
|
||||
"./src/*": "./src/*",
|
||||
"./client": {
|
||||
"type": "./lib/client/index.d.ts",
|
||||
"default": "./lib/client/index.js"
|
||||
},
|
||||
".": {
|
||||
"types": "./src/plugin-content-blog.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc --build",
|
||||
"watch": "tsc --build --watch",
|
||||
"test:generate-build-snap": "yarn docusaurus build src/__tests__/__fixtures__/website --out-dir build-snap && yarn rimraf src/__tests__/__fixtures__/website/.docusaurus && yarn rimraf src/__tests__/__fixtures__/website/build-snap/assets && git add src/__tests__/__fixtures__/website/build-snap"
|
||||
},
|
||||
"repository": {
|
||||
|
|
20
packages/docusaurus-plugin-content-blog/src/client/index.ts
Normal file
20
packages/docusaurus-plugin-content-blog/src/client/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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 useRouteContext from '@docusaurus/useRouteContext';
|
||||
import type {BlogMetadata} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
export function useBlogMetadata(): BlogMetadata {
|
||||
const routeContext = useRouteContext();
|
||||
const blogMetadata = routeContext?.data?.blogMetadata;
|
||||
if (!blogMetadata) {
|
||||
throw new Error(
|
||||
"useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context",
|
||||
);
|
||||
}
|
||||
return blogMetadata as BlogMetadata;
|
||||
}
|
|
@ -42,6 +42,7 @@ import type {
|
|||
BlogTags,
|
||||
BlogContent,
|
||||
BlogPaginated,
|
||||
BlogMetadata,
|
||||
} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
export default async function pluginContentBlog(
|
||||
|
@ -182,6 +183,7 @@ export default async function pluginContentBlog(
|
|||
blogArchiveComponent,
|
||||
routeBasePath,
|
||||
archiveBasePath,
|
||||
blogTitle,
|
||||
} = options;
|
||||
|
||||
const {addRoute, createData} = actions;
|
||||
|
@ -257,6 +259,15 @@ export default async function pluginContentBlog(
|
|||
),
|
||||
);
|
||||
|
||||
const blogMetadata: BlogMetadata = {
|
||||
blogBasePath: normalizeUrl([baseUrl, routeBasePath]),
|
||||
blogTitle,
|
||||
};
|
||||
const blogMetadataPath = await createData(
|
||||
`blogMetadata-${pluginId}.json`,
|
||||
JSON.stringify(blogMetadata, null, 2),
|
||||
);
|
||||
|
||||
// Create routes for blog entries.
|
||||
await Promise.all(
|
||||
blogPosts.map(async (blogPost) => {
|
||||
|
@ -276,6 +287,9 @@ export default async function pluginContentBlog(
|
|||
sidebar: aliasedSource(sidebarProp),
|
||||
content: metadata.source,
|
||||
},
|
||||
context: {
|
||||
blogMetadata: aliasedSource(blogMetadataPath),
|
||||
},
|
||||
});
|
||||
|
||||
blogItemsToMetadata[id] = metadata;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/// <reference types="@docusaurus/module-type-aliases" />
|
||||
|
||||
declare module '@docusaurus/plugin-content-blog' {
|
||||
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
|
||||
import type {MDXOptions} from '@docusaurus/mdx-loader';
|
||||
|
@ -466,6 +468,13 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|||
blogTagsListPath: string;
|
||||
};
|
||||
|
||||
export type BlogMetadata = {
|
||||
/** the path to the base of the blog */
|
||||
blogBasePath: string;
|
||||
/** title of the overall blog */
|
||||
blogTitle: string;
|
||||
};
|
||||
|
||||
export type BlogTags = {
|
||||
[permalink: string]: BlogTag;
|
||||
};
|
||||
|
@ -537,6 +546,7 @@ declare module '@theme/BlogPostPage' {
|
|||
BlogPostFrontMatter,
|
||||
BlogSidebar,
|
||||
PropBlogPostContent,
|
||||
BlogMetadata,
|
||||
} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
export type FrontMatter = BlogPostFrontMatter;
|
||||
|
@ -548,6 +558,8 @@ declare module '@theme/BlogPostPage' {
|
|||
readonly sidebar: BlogSidebar;
|
||||
/** Content of this post as an MDX component, with useful metadata. */
|
||||
readonly content: Content;
|
||||
/** Metadata about the blog. */
|
||||
readonly blogMetadata: BlogMetadata;
|
||||
}
|
||||
|
||||
export default function BlogPostPage(props: Props): JSX.Element;
|
||||
|
@ -557,6 +569,10 @@ declare module '@theme/BlogPostPage/Metadata' {
|
|||
export default function BlogPostPageMetadata(): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/BlogPostPage/StructuredData' {
|
||||
export default function BlogPostStructuredData(): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/BlogListPage' {
|
||||
import type {Content} from '@theme/BlogPostPage';
|
||||
import type {
|
||||
|
@ -579,6 +595,28 @@ declare module '@theme/BlogListPage' {
|
|||
export default function BlogListPage(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/BlogListPage/StructuredData' {
|
||||
import type {Content} from '@theme/BlogPostPage';
|
||||
import type {
|
||||
BlogSidebar,
|
||||
BlogPaginatedMetadata,
|
||||
} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
export interface Props {
|
||||
/** Blog sidebar. */
|
||||
readonly sidebar: BlogSidebar;
|
||||
/** Metadata of the current listing page. */
|
||||
readonly metadata: BlogPaginatedMetadata;
|
||||
/**
|
||||
* Array of blog posts included on this page. Every post's metadata is also
|
||||
* available.
|
||||
*/
|
||||
readonly items: readonly {readonly content: Content}[];
|
||||
}
|
||||
|
||||
export default function BlogListPageStructuredData(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/BlogTagsListPage' {
|
||||
import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
|
||||
import type {TagsListItem} from '@docusaurus/utils';
|
||||
|
|
16
packages/docusaurus-plugin-content-blog/tsconfig.client.json
Normal file
16
packages/docusaurus-plugin-content-blog/tsconfig.client.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo-client",
|
||||
"moduleResolution": "bundler",
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src/client", "src/*.d.ts"],
|
||||
"exclude": ["**/__tests__/**"]
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"references": [{"path": "./tsconfig.client.json"}],
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"incremental": true,
|
||||
|
@ -8,5 +9,5 @@
|
|||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/__tests__/**"]
|
||||
"exclude": ["src/client", "**/__tests__/**"]
|
||||
}
|
||||
|
|
|
@ -25,9 +25,7 @@ export default function BlogLayout(props: Props): JSX.Element {
|
|||
className={clsx('col', {
|
||||
'col--7': hasSidebar,
|
||||
'col--9 col--offset-1': !hasSidebar,
|
||||
})}
|
||||
itemScope
|
||||
itemType="https://schema.org/Blog">
|
||||
})}>
|
||||
{children}
|
||||
</main>
|
||||
{toc && <div className="col col--2">{toc}</div>}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import Head from '@docusaurus/Head';
|
||||
import {useBlogListPageStructuredData} from '@docusaurus/theme-common';
|
||||
import type {Props} from '@theme/BlogListPage/StructuredData';
|
||||
|
||||
export default function BlogListPageStructuredData(props: Props): JSX.Element {
|
||||
const structuredData = useBlogListPageStructuredData(props);
|
||||
return (
|
||||
<Head>
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(structuredData)}
|
||||
</script>
|
||||
</Head>
|
||||
);
|
||||
}
|
|
@ -19,6 +19,7 @@ import BlogListPaginator from '@theme/BlogListPaginator';
|
|||
import SearchMetadata from '@theme/SearchMetadata';
|
||||
import type {Props} from '@theme/BlogListPage';
|
||||
import BlogPostItems from '@theme/BlogPostItems';
|
||||
import BlogListPageStructuredData from '@theme/BlogListPage/StructuredData';
|
||||
|
||||
function BlogListPageMetadata(props: Props): JSX.Element {
|
||||
const {metadata} = props;
|
||||
|
@ -54,6 +55,7 @@ export default function BlogListPage(props: Props): JSX.Element {
|
|||
ThemeClassNames.page.blogListPage,
|
||||
)}>
|
||||
<BlogListPageMetadata {...props} />
|
||||
<BlogListPageStructuredData {...props} />
|
||||
<BlogListPageContent {...props} />
|
||||
</HtmlClassNameProvider>
|
||||
);
|
||||
|
|
|
@ -6,36 +6,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||
import type {Props} from '@theme/BlogPostItem/Container';
|
||||
|
||||
export default function BlogPostItemContainer({
|
||||
children,
|
||||
className,
|
||||
}: Props): JSX.Element {
|
||||
const {
|
||||
frontMatter,
|
||||
assets,
|
||||
metadata: {description},
|
||||
} = useBlogPost();
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
const image = assets.image ?? frontMatter.image;
|
||||
const keywords = frontMatter.keywords ?? [];
|
||||
return (
|
||||
<article
|
||||
className={className}
|
||||
itemProp="blogPost"
|
||||
itemScope
|
||||
itemType="https://schema.org/BlogPosting">
|
||||
{description && <meta itemProp="description" content={description} />}
|
||||
{image && (
|
||||
<link itemProp="image" href={withBaseUrl(image, {absolute: true})} />
|
||||
)}
|
||||
{keywords.length > 0 && (
|
||||
<meta itemProp="keywords" content={keywords.join(',')} />
|
||||
)}
|
||||
{children}
|
||||
</article>
|
||||
);
|
||||
return <article className={className}>{children}</article>;
|
||||
}
|
||||
|
|
|
@ -21,8 +21,7 @@ export default function BlogPostItemContent({
|
|||
<div
|
||||
// This ID is used for the feed generation to locate the main content
|
||||
id={isBlogPostPage ? blogPostContainerID : undefined}
|
||||
className={clsx('markdown', className)}
|
||||
itemProp="articleBody">
|
||||
className={clsx('markdown', className)}>
|
||||
<MDXContent>{children}</MDXContent>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -28,31 +28,18 @@ export default function BlogPostItemHeaderAuthor({
|
|||
<div className={clsx('avatar margin-bottom--sm', className)}>
|
||||
{imageURL && (
|
||||
<MaybeLink href={link} className="avatar__photo-link">
|
||||
<img
|
||||
className="avatar__photo"
|
||||
src={imageURL}
|
||||
alt={name}
|
||||
itemProp="image"
|
||||
/>
|
||||
<img className="avatar__photo" src={imageURL} alt={name} />
|
||||
</MaybeLink>
|
||||
)}
|
||||
|
||||
{name && (
|
||||
<div
|
||||
className="avatar__intro"
|
||||
itemProp="author"
|
||||
itemScope
|
||||
itemType="https://schema.org/Person">
|
||||
<div className="avatar__intro">
|
||||
<div className="avatar__name">
|
||||
<MaybeLink href={link} itemProp="url">
|
||||
<span itemProp="name">{name}</span>
|
||||
<MaybeLink href={link}>
|
||||
<span>{name}</span>
|
||||
</MaybeLink>
|
||||
</div>
|
||||
{title && (
|
||||
<small className="avatar__subtitle" itemProp="description">
|
||||
{title}
|
||||
</small>
|
||||
)}
|
||||
{title && <small className="avatar__subtitle">{title}</small>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -40,11 +40,7 @@ function ReadingTime({readingTime}: {readingTime: number}) {
|
|||
}
|
||||
|
||||
function Date({date, formattedDate}: {date: string; formattedDate: string}) {
|
||||
return (
|
||||
<time dateTime={date} itemProp="datePublished">
|
||||
{formattedDate}
|
||||
</time>
|
||||
);
|
||||
return <time dateTime={date}>{formattedDate}</time>;
|
||||
}
|
||||
|
||||
function Spacer() {
|
||||
|
|
|
@ -20,14 +20,8 @@ export default function BlogPostItemHeaderTitle({
|
|||
const {permalink, title} = metadata;
|
||||
const TitleHeading = isBlogPostPage ? 'h1' : 'h2';
|
||||
return (
|
||||
<TitleHeading className={clsx(styles.title, className)} itemProp="headline">
|
||||
{isBlogPostPage ? (
|
||||
title
|
||||
) : (
|
||||
<Link itemProp="url" to={permalink}>
|
||||
{title}
|
||||
</Link>
|
||||
)}
|
||||
<TitleHeading className={clsx(styles.title, className)}>
|
||||
{isBlogPostPage ? title : <Link to={permalink}>{title}</Link>}
|
||||
</TitleHeading>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import Head from '@docusaurus/Head';
|
||||
import {useBlogPostStructuredData} from '@docusaurus/theme-common';
|
||||
|
||||
export default function BlogPostStructuredData(): JSX.Element {
|
||||
const structuredData = useBlogPostStructuredData();
|
||||
return (
|
||||
<Head>
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(structuredData)}
|
||||
</script>
|
||||
</Head>
|
||||
);
|
||||
}
|
|
@ -13,6 +13,7 @@ import BlogLayout from '@theme/BlogLayout';
|
|||
import BlogPostItem from '@theme/BlogPostItem';
|
||||
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
||||
import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
|
||||
import BlogPostPageStructuredData from '@theme/BlogPostPage/StructuredData';
|
||||
import TOC from '@theme/TOC';
|
||||
import type {Props} from '@theme/BlogPostPage';
|
||||
import Unlisted from '@theme/Unlisted';
|
||||
|
@ -45,6 +46,7 @@ function BlogPostPageContent({
|
|||
) : undefined
|
||||
}>
|
||||
{unlisted && <Unlisted />}
|
||||
|
||||
<BlogPostItem>{children}</BlogPostItem>
|
||||
|
||||
{(nextItem || prevItem) && (
|
||||
|
@ -64,6 +66,7 @@ export default function BlogPostPage(props: Props): JSX.Element {
|
|||
ThemeClassNames.page.blogPostPage,
|
||||
)}>
|
||||
<BlogPostPageMetadata />
|
||||
<BlogPostPageStructuredData />
|
||||
<BlogPostPageContent sidebar={props.sidebar}>
|
||||
<BlogPostContent />
|
||||
</BlogPostPageContent>
|
||||
|
|
|
@ -50,7 +50,8 @@
|
|||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"lodash": "^4.17.21"
|
||||
"lodash": "^4.17.21",
|
||||
"schema-dts": "^1.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
|
|
|
@ -39,6 +39,11 @@ export {
|
|||
filterDocCardListItems,
|
||||
} from './utils/docsUtils';
|
||||
|
||||
export {
|
||||
useBlogListPageStructuredData,
|
||||
useBlogPostStructuredData,
|
||||
} from './utils/structuredDataUtils';
|
||||
|
||||
export {usePluralForm} from './utils/usePluralForm';
|
||||
|
||||
export {useCollapsible, Collapsible} from './components/Collapsible';
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
function getBlogPost(
|
||||
blogPostContent: PropBlogPostContent,
|
||||
siteConfig: DocusaurusConfig,
|
||||
withBaseUrl: BaseUrlUtils['withBaseUrl'],
|
||||
) {
|
||||
const {assets, frontMatter, metadata} = blogPostContent;
|
||||
const {date, title, description} = metadata;
|
||||
|
||||
const image = assets.image ?? frontMatter.image;
|
||||
const keywords = frontMatter.keywords ?? [];
|
||||
|
||||
const blogUrl = `${siteConfig.url}${metadata.permalink}`;
|
||||
|
||||
return {
|
||||
'@type': 'BlogPosting',
|
||||
'@id': blogUrl,
|
||||
mainEntityOfPage: blogUrl,
|
||||
url: blogUrl,
|
||||
headline: title,
|
||||
name: title,
|
||||
description,
|
||||
datePublished: date,
|
||||
...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} = metadata;
|
||||
|
||||
const image = assets.image ?? frontMatter.image;
|
||||
const keywords = frontMatter.keywords ?? [];
|
||||
|
||||
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,
|
||||
...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,
|
||||
};
|
||||
}
|
2
packages/docusaurus-types/src/routing.d.ts
vendored
2
packages/docusaurus-types/src/routing.d.ts
vendored
|
@ -75,7 +75,7 @@ export type RouteContext = {
|
|||
/**
|
||||
* Plugin-specific context data.
|
||||
*/
|
||||
data?: object | undefined;
|
||||
data?: {[key: string]: unknown};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -60,7 +60,7 @@ To read more about types of meta tags, visit [the MDN docs](https://developer.mo
|
|||
|
||||
Similar to [global metadata](#global-metadata), Docusaurus also allows for the addition of meta-information to individual pages. Follow [this guide](./guides/markdown-features/markdown-features-head-metadata.mdx) for configuring the `<head>` tag. In short:
|
||||
|
||||
```md title="my-markdown-page.md"
|
||||
```md title="my-markdown-page.mdx"
|
||||
# A cooking guide
|
||||
|
||||
<head>
|
||||
|
|
|
@ -453,6 +453,8 @@ export default async function createConfigAsync() {
|
|||
type: 'all',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
|
||||
},
|
||||
blogTitle: 'Docusaurus blog',
|
||||
blogDescription: 'Read blog posts about Docusaurus from the team',
|
||||
blogSidebarCount: 'ALL',
|
||||
blogSidebarTitle: 'All our posts',
|
||||
} satisfies BlogOptions,
|
||||
|
|
|
@ -14539,6 +14539,11 @@ scheduler@^0.23.0:
|
|||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
schema-dts@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/schema-dts/-/schema-dts-1.1.2.tgz#82ccf71b5dcb80065a1cc5941888507a4ce1e44b"
|
||||
integrity sha512-MpNwH0dZJHinVxk9bT8XUdjKTxMYrA5bLtrrGmFA6PTLwlOKnhi67XoRd6/ty+Djt6ZC0slR57qFhZDNMI6DhQ==
|
||||
|
||||
schema-utils@2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue