mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-24 06:27:02 +02:00
refactor(theme-{classic,common}): change how site/page/search metadata is handled (#6925)
This commit is contained in:
parent
74e37e86ba
commit
74f653dd82
36 changed files with 808 additions and 625 deletions
|
@ -92,8 +92,6 @@ declare module '@theme/Layout' {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly children?: ReactNode;
|
readonly children?: ReactNode;
|
||||||
readonly title?: string;
|
|
||||||
readonly description?: string;
|
|
||||||
}
|
}
|
||||||
export default function Layout(props: Props): JSX.Element;
|
export default function Layout(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
@ -117,6 +115,10 @@ declare module '@theme/Root' {
|
||||||
export default function Root({children}: Props): JSX.Element;
|
export default function Root({children}: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@theme/SiteMetadata' {
|
||||||
|
export default function SiteMetadata(): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@docusaurus/constants' {
|
declare module '@docusaurus/constants' {
|
||||||
export const DEFAULT_PLUGIN_ID: 'default';
|
export const DEFAULT_PLUGIN_ID: 'default';
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,31 +364,17 @@ declare module '@theme/Layout' {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly children?: ReactNode;
|
readonly children?: ReactNode;
|
||||||
readonly title?: string;
|
|
||||||
readonly noFooter?: boolean;
|
readonly noFooter?: boolean;
|
||||||
readonly description?: string;
|
|
||||||
readonly image?: string;
|
|
||||||
readonly keywords?: string | string[];
|
|
||||||
readonly permalink?: string;
|
|
||||||
readonly wrapperClassName?: string;
|
readonly wrapperClassName?: string;
|
||||||
readonly pageClassName?: string;
|
|
||||||
readonly searchMetadata?: {
|
// Not really layout-related, but kept for convenience/retro-compatibility
|
||||||
readonly version?: string;
|
readonly title?: string;
|
||||||
readonly tag?: string;
|
readonly description?: string;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Layout(props: Props): JSX.Element;
|
export default function Layout(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/LayoutHead' {
|
|
||||||
import type {Props as LayoutProps} from '@theme/Layout';
|
|
||||||
|
|
||||||
export interface Props extends Omit<LayoutProps, 'children'> {}
|
|
||||||
|
|
||||||
export default function LayoutHead(props: Props): JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@theme/LayoutProviders' {
|
declare module '@theme/LayoutProviders' {
|
||||||
import type {ReactNode} from 'react';
|
import type {ReactNode} from 'react';
|
||||||
|
|
||||||
|
@ -480,7 +466,7 @@ declare module '@theme/Navbar/Content' {
|
||||||
|
|
||||||
declare module '@theme/Navbar/Layout' {
|
declare module '@theme/Navbar/Layout' {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
children: React.ReactNode;
|
readonly children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NavbarLayout(props: Props): JSX.Element;
|
export default function NavbarLayout(props: Props): JSX.Element;
|
||||||
|
@ -927,17 +913,3 @@ declare module '@theme/prism-include-languages' {
|
||||||
PrismObject: typeof PrismNamespace,
|
PrismObject: typeof PrismNamespace,
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/Seo' {
|
|
||||||
import type {ReactNode} from 'react';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
readonly title?: string;
|
|
||||||
readonly description?: string;
|
|
||||||
readonly keywords?: readonly string[] | string;
|
|
||||||
readonly image?: string;
|
|
||||||
readonly children?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Seo(props: Props): JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Layout from '@theme/Layout';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import type {ArchiveBlogPost, Props} from '@theme/BlogArchivePage';
|
import type {ArchiveBlogPost, Props} from '@theme/BlogArchivePage';
|
||||||
import {translate} from '@docusaurus/Translate';
|
import {translate} from '@docusaurus/Translate';
|
||||||
|
import {PageMetadata} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
type YearProp = {
|
type YearProp = {
|
||||||
year: string;
|
year: string;
|
||||||
|
@ -75,14 +76,17 @@ export default function BlogArchive({archive}: Props): JSX.Element {
|
||||||
});
|
});
|
||||||
const years = listPostsByYears(archive.blogPosts);
|
const years = listPostsByYears(archive.blogPosts);
|
||||||
return (
|
return (
|
||||||
<Layout title={title} description={description}>
|
<>
|
||||||
<header className="hero hero--primary">
|
<PageMetadata title={title} description={description} />
|
||||||
<div className="container">
|
<Layout>
|
||||||
<h1 className="hero__title">{title}</h1>
|
<header className="hero hero--primary">
|
||||||
<p className="hero__subtitle">{description}</p>
|
<div className="container">
|
||||||
</div>
|
<h1 className="hero__title">{title}</h1>
|
||||||
</header>
|
<p className="hero__subtitle">{description}</p>
|
||||||
<main>{years.length > 0 && <YearsSection years={years} />}</main>
|
</div>
|
||||||
</Layout>
|
</header>
|
||||||
|
<main>{years.length > 0 && <YearsSection years={years} />}</main>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,28 +12,34 @@ import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogPostItem from '@theme/BlogPostItem';
|
import BlogPostItem from '@theme/BlogPostItem';
|
||||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
import type {Props} from '@theme/BlogListPage';
|
import type {Props} from '@theme/BlogListPage';
|
||||||
import {ThemeClassNames} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export default function BlogListPage(props: Props): JSX.Element {
|
function BlogListPageMetadata(props: Props): JSX.Element {
|
||||||
const {metadata, items, sidebar} = props;
|
const {metadata} = props;
|
||||||
const {
|
const {
|
||||||
siteConfig: {title: siteTitle},
|
siteConfig: {title: siteTitle},
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
const {blogDescription, blogTitle, permalink} = metadata;
|
const {blogDescription, blogTitle, permalink} = metadata;
|
||||||
const isBlogOnlyMode = permalink === '/';
|
const isBlogOnlyMode = permalink === '/';
|
||||||
const title = isBlogOnlyMode ? siteTitle : blogTitle;
|
const title = isBlogOnlyMode ? siteTitle : blogTitle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlogLayout
|
<>
|
||||||
title={title}
|
<PageMetadata title={title} description={blogDescription} />
|
||||||
description={blogDescription}
|
<SearchMetadata tag="blog_posts_list" />
|
||||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
</>
|
||||||
pageClassName={ThemeClassNames.page.blogListPage}
|
);
|
||||||
searchMetadata={{
|
}
|
||||||
// assign unique search tag to exclude this page from search results!
|
|
||||||
tag: 'blog_posts_list',
|
function BlogListPageContent(props: Props): JSX.Element {
|
||||||
}}
|
const {metadata, items, sidebar} = props;
|
||||||
sidebar={sidebar}>
|
return (
|
||||||
|
<BlogLayout sidebar={sidebar}>
|
||||||
{items.map(({content: BlogPostContent}) => (
|
{items.map(({content: BlogPostContent}) => (
|
||||||
<BlogPostItem
|
<BlogPostItem
|
||||||
key={BlogPostContent.metadata.permalink}
|
key={BlogPostContent.metadata.permalink}
|
||||||
|
@ -48,3 +54,16 @@ export default function BlogListPage(props: Props): JSX.Element {
|
||||||
</BlogLayout>
|
</BlogLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function BlogListPage(props: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<HtmlClassNameProvider
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.wrapper.blogPages,
|
||||||
|
ThemeClassNames.page.blogListPage,
|
||||||
|
)}>
|
||||||
|
<BlogListPageMetadata {...props} />
|
||||||
|
<BlogListPageContent {...props} />
|
||||||
|
</HtmlClassNameProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -6,40 +6,63 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Seo from '@theme/Seo';
|
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogPostItem from '@theme/BlogPostItem';
|
import BlogPostItem from '@theme/BlogPostItem';
|
||||||
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
||||||
import type {Props} from '@theme/BlogPostPage';
|
import type {Props} from '@theme/BlogPostPage';
|
||||||
import {ThemeClassNames} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export default function BlogPostPage(props: Props): JSX.Element {
|
function BlogPostPageMetadata(props: Props): JSX.Element {
|
||||||
|
const {content: BlogPostContents} = props;
|
||||||
|
const {assets, metadata} = BlogPostContents;
|
||||||
|
const {title, description, date, tags, authors, frontMatter} = metadata;
|
||||||
|
const {keywords} = frontMatter;
|
||||||
|
const image = assets.image ?? frontMatter.image;
|
||||||
|
return (
|
||||||
|
<PageMetadata
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
keywords={keywords}
|
||||||
|
image={image}>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="article:published_time" content={date} />
|
||||||
|
{/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */}
|
||||||
|
{authors.some((author) => author.url) && (
|
||||||
|
<meta
|
||||||
|
property="article:author"
|
||||||
|
content={authors
|
||||||
|
.map((author) => author.url)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(',')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<meta
|
||||||
|
property="article:tag"
|
||||||
|
content={tags.map((tag) => tag.label).join(',')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PageMetadata>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BlogPostPageContent(props: Props): JSX.Element {
|
||||||
const {content: BlogPostContents, sidebar} = props;
|
const {content: BlogPostContents, sidebar} = props;
|
||||||
const {assets, metadata} = BlogPostContents;
|
const {assets, metadata} = BlogPostContents;
|
||||||
const {
|
const {nextItem, prevItem, frontMatter} = metadata;
|
||||||
title,
|
|
||||||
description,
|
|
||||||
nextItem,
|
|
||||||
prevItem,
|
|
||||||
date,
|
|
||||||
tags,
|
|
||||||
authors,
|
|
||||||
frontMatter,
|
|
||||||
} = metadata;
|
|
||||||
const {
|
const {
|
||||||
hide_table_of_contents: hideTableOfContents,
|
hide_table_of_contents: hideTableOfContents,
|
||||||
keywords,
|
|
||||||
toc_min_heading_level: tocMinHeadingLevel,
|
toc_min_heading_level: tocMinHeadingLevel,
|
||||||
toc_max_heading_level: tocMaxHeadingLevel,
|
toc_max_heading_level: tocMaxHeadingLevel,
|
||||||
} = frontMatter;
|
} = frontMatter;
|
||||||
|
|
||||||
const image = assets.image ?? frontMatter.image;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlogLayout
|
<BlogLayout
|
||||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
|
||||||
pageClassName={ThemeClassNames.page.blogPostPage}
|
|
||||||
sidebar={sidebar}
|
sidebar={sidebar}
|
||||||
toc={
|
toc={
|
||||||
!hideTableOfContents &&
|
!hideTableOfContents &&
|
||||||
|
@ -52,35 +75,6 @@ export default function BlogPostPage(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}>
|
}>
|
||||||
<Seo
|
|
||||||
// TODO refactor needed: it's a bit annoying but Seo MUST be inside
|
|
||||||
// BlogLayout, otherwise default image (set by BlogLayout) would shadow
|
|
||||||
// the custom blog post image
|
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
keywords={keywords}
|
|
||||||
image={image}>
|
|
||||||
<meta property="og:type" content="article" />
|
|
||||||
<meta property="article:published_time" content={date} />
|
|
||||||
|
|
||||||
{/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */}
|
|
||||||
{authors.some((author) => author.url) && (
|
|
||||||
<meta
|
|
||||||
property="article:author"
|
|
||||||
content={authors
|
|
||||||
.map((author) => author.url)
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(',')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{tags.length > 0 && (
|
|
||||||
<meta
|
|
||||||
property="article:tag"
|
|
||||||
content={tags.map((tag) => tag.label).join(',')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Seo>
|
|
||||||
|
|
||||||
<BlogPostItem
|
<BlogPostItem
|
||||||
frontMatter={frontMatter}
|
frontMatter={frontMatter}
|
||||||
assets={assets}
|
assets={assets}
|
||||||
|
@ -95,3 +89,16 @@ export default function BlogPostPage(props: Props): JSX.Element {
|
||||||
</BlogLayout>
|
</BlogLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function BlogPostPage(props: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<HtmlClassNameProvider
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.wrapper.blogPages,
|
||||||
|
ThemeClassNames.page.blogPostPage,
|
||||||
|
)}>
|
||||||
|
<BlogPostPageMetadata {...props} />
|
||||||
|
<BlogPostPageContent {...props} />
|
||||||
|
</HtmlClassNameProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -11,25 +11,29 @@ import BlogLayout from '@theme/BlogLayout';
|
||||||
import TagsListByLetter from '@theme/TagsListByLetter';
|
import TagsListByLetter from '@theme/TagsListByLetter';
|
||||||
import type {Props} from '@theme/BlogTagsListPage';
|
import type {Props} from '@theme/BlogTagsListPage';
|
||||||
import {
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
ThemeClassNames,
|
ThemeClassNames,
|
||||||
translateTagsPageTitle,
|
translateTagsPageTitle,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
|
import SearchMetadata from '../SearchMetadata';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export default function BlogTagsListPage(props: Props): JSX.Element {
|
export default function BlogTagsListPage(props: Props): JSX.Element {
|
||||||
const {tags, sidebar} = props;
|
const {tags, sidebar} = props;
|
||||||
const title = translateTagsPageTitle();
|
const title = translateTagsPageTitle();
|
||||||
return (
|
return (
|
||||||
<BlogLayout
|
<HtmlClassNameProvider
|
||||||
title={title}
|
className={clsx(
|
||||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
ThemeClassNames.wrapper.blogPages,
|
||||||
pageClassName={ThemeClassNames.page.blogTagsListPage}
|
ThemeClassNames.page.blogTagsListPage,
|
||||||
searchMetadata={{
|
)}>
|
||||||
// assign unique search tag to exclude this page from search results!
|
<PageMetadata title={title} />
|
||||||
tag: 'blog_tags_list',
|
<SearchMetadata tag="blog_tags_list" />
|
||||||
}}
|
<BlogLayout sidebar={sidebar}>
|
||||||
sidebar={sidebar}>
|
<h1>{title}</h1>
|
||||||
<h1>{title}</h1>
|
<TagsListByLetter tags={Object.values(tags)} />
|
||||||
<TagsListByLetter tags={Object.values(tags)} />
|
</BlogLayout>
|
||||||
</BlogLayout>
|
</HtmlClassNameProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,15 @@ import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogPostItem from '@theme/BlogPostItem';
|
import BlogPostItem from '@theme/BlogPostItem';
|
||||||
import type {Props} from '@theme/BlogTagsPostsPage';
|
import type {Props} from '@theme/BlogTagsPostsPage';
|
||||||
import Translate, {translate} from '@docusaurus/Translate';
|
import Translate, {translate} from '@docusaurus/Translate';
|
||||||
import {ThemeClassNames, usePluralForm} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
usePluralForm,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
// Very simple pluralization: probably good enough for now
|
// Very simple pluralization: probably good enough for now
|
||||||
function useBlogPostsPlural() {
|
function useBlogPostsPlural() {
|
||||||
|
@ -47,38 +54,38 @@ export default function BlogTagsPostsPage(props: Props): JSX.Element {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlogLayout
|
<HtmlClassNameProvider
|
||||||
title={title}
|
className={clsx(
|
||||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
ThemeClassNames.wrapper.blogPages,
|
||||||
pageClassName={ThemeClassNames.page.blogTagPostListPage}
|
ThemeClassNames.page.blogTagPostListPage,
|
||||||
searchMetadata={{
|
)}>
|
||||||
// assign unique search tag to exclude this page from search results!
|
<PageMetadata title={title} />
|
||||||
tag: 'blog_tags_posts',
|
<SearchMetadata tag="blog_tags_posts" />
|
||||||
}}
|
<BlogLayout sidebar={sidebar}>
|
||||||
sidebar={sidebar}>
|
<header className="margin-bottom--xl">
|
||||||
<header className="margin-bottom--xl">
|
<h1>{title}</h1>
|
||||||
<h1>{title}</h1>
|
|
||||||
|
|
||||||
<Link href={allTagsPath}>
|
<Link href={allTagsPath}>
|
||||||
<Translate
|
<Translate
|
||||||
id="theme.tags.tagsPageLink"
|
id="theme.tags.tagsPageLink"
|
||||||
description="The label of the link targeting the tag list page">
|
description="The label of the link targeting the tag list page">
|
||||||
View All Tags
|
View All Tags
|
||||||
</Translate>
|
</Translate>
|
||||||
</Link>
|
</Link>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{items.map(({content: BlogPostContent}) => (
|
{items.map(({content: BlogPostContent}) => (
|
||||||
<BlogPostItem
|
<BlogPostItem
|
||||||
key={BlogPostContent.metadata.permalink}
|
key={BlogPostContent.metadata.permalink}
|
||||||
frontMatter={BlogPostContent.frontMatter}
|
frontMatter={BlogPostContent.frontMatter}
|
||||||
assets={BlogPostContent.assets}
|
assets={BlogPostContent.assets}
|
||||||
metadata={BlogPostContent.metadata}
|
metadata={BlogPostContent.metadata}
|
||||||
truncated>
|
truncated>
|
||||||
<BlogPostContent />
|
<BlogPostContent />
|
||||||
</BlogPostItem>
|
</BlogPostItem>
|
||||||
))}
|
))}
|
||||||
<BlogListPaginator metadata={listMetadata} />
|
<BlogListPaginator metadata={listMetadata} />
|
||||||
</BlogLayout>
|
</BlogLayout>
|
||||||
|
</HtmlClassNameProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
useCurrentSidebarCategory,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import type {Props} from '@theme/DocCategoryGeneratedIndexPage';
|
import type {Props} from '@theme/DocCategoryGeneratedIndexPage';
|
||||||
import DocCardList from '@theme/DocCardList';
|
import DocCardList from '@theme/DocCardList';
|
||||||
import DocPaginator from '@theme/DocPaginator';
|
import DocPaginator from '@theme/DocPaginator';
|
||||||
import Seo from '@theme/Seo';
|
|
||||||
import DocVersionBanner from '@theme/DocVersionBanner';
|
import DocVersionBanner from '@theme/DocVersionBanner';
|
||||||
import DocVersionBadge from '@theme/DocVersionBadge';
|
import DocVersionBadge from '@theme/DocVersionBadge';
|
||||||
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
||||||
|
@ -19,13 +21,27 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function DocCategoryGeneratedIndexPage({
|
function DocCategoryGeneratedIndexPageMetadata({
|
||||||
|
categoryGeneratedIndex,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<PageMetadata
|
||||||
|
title={categoryGeneratedIndex.title}
|
||||||
|
description={categoryGeneratedIndex.description}
|
||||||
|
keywords={categoryGeneratedIndex.keywords}
|
||||||
|
// TODO `require` this?
|
||||||
|
image={useBaseUrl(categoryGeneratedIndex.image)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DocCategoryGeneratedIndexPageContent({
|
||||||
categoryGeneratedIndex,
|
categoryGeneratedIndex,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const category = useCurrentSidebarCategory();
|
const category = useCurrentSidebarCategory();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Seo
|
<PageMetadata
|
||||||
title={categoryGeneratedIndex.title}
|
title={categoryGeneratedIndex.title}
|
||||||
description={categoryGeneratedIndex.description}
|
description={categoryGeneratedIndex.description}
|
||||||
keywords={categoryGeneratedIndex.keywords}
|
keywords={categoryGeneratedIndex.keywords}
|
||||||
|
@ -57,3 +73,14 @@ export default function DocCategoryGeneratedIndexPage({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function DocCategoryGeneratedIndexPage(
|
||||||
|
props: Props,
|
||||||
|
): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DocCategoryGeneratedIndexPageMetadata {...props} />
|
||||||
|
<DocCategoryGeneratedIndexPageContent {...props} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import clsx from 'clsx';
|
||||||
import DocPaginator from '@theme/DocPaginator';
|
import DocPaginator from '@theme/DocPaginator';
|
||||||
import DocVersionBanner from '@theme/DocVersionBanner';
|
import DocVersionBanner from '@theme/DocVersionBanner';
|
||||||
import DocVersionBadge from '@theme/DocVersionBadge';
|
import DocVersionBadge from '@theme/DocVersionBadge';
|
||||||
import Seo from '@theme/Seo';
|
|
||||||
import type {Props} from '@theme/DocItem';
|
import type {Props} from '@theme/DocItem';
|
||||||
import DocItemFooter from '@theme/DocItemFooter';
|
import DocItemFooter from '@theme/DocItemFooter';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
|
@ -18,6 +17,7 @@ import TOCCollapsible from '@theme/TOCCollapsible';
|
||||||
import Heading from '@theme/Heading';
|
import Heading from '@theme/Heading';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
import {
|
import {
|
||||||
|
PageMetadata,
|
||||||
HtmlClassNameProvider,
|
HtmlClassNameProvider,
|
||||||
ThemeClassNames,
|
ThemeClassNames,
|
||||||
useWindowSize,
|
useWindowSize,
|
||||||
|
@ -25,18 +25,26 @@ import {
|
||||||
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
||||||
import MDXContent from '@theme/MDXContent';
|
import MDXContent from '@theme/MDXContent';
|
||||||
|
|
||||||
export default function DocItem(props: Props): JSX.Element {
|
function DocItemMetadata(props: Props): JSX.Element {
|
||||||
const {content: DocContent} = props;
|
const {content: DocContent} = props;
|
||||||
const {metadata, frontMatter, assets} = DocContent;
|
const {metadata, frontMatter, assets} = DocContent;
|
||||||
|
const {keywords} = frontMatter;
|
||||||
|
const {description, title} = metadata;
|
||||||
|
const image = assets.image ?? frontMatter.image;
|
||||||
|
|
||||||
|
return <PageMetadata {...{title, description, keywords, image}} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DocItemContent(props: Props): JSX.Element {
|
||||||
|
const {content: DocContent} = props;
|
||||||
|
const {metadata, frontMatter} = DocContent;
|
||||||
const {
|
const {
|
||||||
keywords,
|
|
||||||
hide_title: hideTitle,
|
hide_title: hideTitle,
|
||||||
hide_table_of_contents: hideTableOfContents,
|
hide_table_of_contents: hideTableOfContents,
|
||||||
toc_min_heading_level: tocMinHeadingLevel,
|
toc_min_heading_level: tocMinHeadingLevel,
|
||||||
toc_max_heading_level: tocMaxHeadingLevel,
|
toc_max_heading_level: tocMaxHeadingLevel,
|
||||||
} = frontMatter;
|
} = frontMatter;
|
||||||
const {description, title} = metadata;
|
const {title} = metadata;
|
||||||
const image = assets.image ?? frontMatter.image;
|
|
||||||
|
|
||||||
// We only add a title if:
|
// We only add a title if:
|
||||||
// - user asks to hide it with front matter
|
// - user asks to hide it with front matter
|
||||||
|
@ -53,64 +61,69 @@ export default function DocItem(props: Props): JSX.Element {
|
||||||
canRenderTOC && (windowSize === 'desktop' || windowSize === 'ssr');
|
canRenderTOC && (windowSize === 'desktop' || windowSize === 'ssr');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HtmlClassNameProvider className={`docs-doc-id-${metadata.unversionedId}`}>
|
<div className="row">
|
||||||
<Seo {...{title, description, keywords, image}} />
|
<div className={clsx('col', !hideTableOfContents && styles.docItemCol)}>
|
||||||
|
<DocVersionBanner />
|
||||||
|
<div className={styles.docItemContainer}>
|
||||||
|
<article>
|
||||||
|
<DocBreadcrumbs />
|
||||||
|
<DocVersionBadge />
|
||||||
|
|
||||||
<div className="row">
|
{canRenderTOC && (
|
||||||
<div className={clsx('col', !hideTableOfContents && styles.docItemCol)}>
|
<TOCCollapsible
|
||||||
<DocVersionBanner />
|
toc={DocContent.toc}
|
||||||
<div className={styles.docItemContainer}>
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
<article>
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
<DocBreadcrumbs />
|
className={clsx(
|
||||||
<DocVersionBadge />
|
ThemeClassNames.docs.docTocMobile,
|
||||||
|
styles.tocMobile,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{canRenderTOC && (
|
<div className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
|
||||||
<TOCCollapsible
|
{/*
|
||||||
toc={DocContent.toc}
|
|
||||||
minHeadingLevel={tocMinHeadingLevel}
|
|
||||||
maxHeadingLevel={tocMaxHeadingLevel}
|
|
||||||
className={clsx(
|
|
||||||
ThemeClassNames.docs.docTocMobile,
|
|
||||||
styles.tocMobile,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
|
|
||||||
{/*
|
|
||||||
Title can be declared inside md content or declared through
|
Title can be declared inside md content or declared through
|
||||||
front matter and added manually. To make both cases consistent,
|
front matter and added manually. To make both cases consistent,
|
||||||
the added title is added under the same div.markdown block
|
the added title is added under the same div.markdown block
|
||||||
See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
|
See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
|
||||||
*/}
|
*/}
|
||||||
{shouldAddTitle && (
|
{shouldAddTitle && (
|
||||||
<header>
|
<header>
|
||||||
<Heading as="h1">{title}</Heading>
|
<Heading as="h1">{title}</Heading>
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
<MDXContent>
|
<MDXContent>
|
||||||
<DocContent />
|
<DocContent />
|
||||||
</MDXContent>
|
</MDXContent>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DocItemFooter {...props} />
|
<DocItemFooter {...props} />
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<DocPaginator previous={metadata.previous} next={metadata.next} />
|
<DocPaginator previous={metadata.previous} next={metadata.next} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{renderTocDesktop && (
|
|
||||||
<div className="col col--3">
|
|
||||||
<TOC
|
|
||||||
toc={DocContent.toc}
|
|
||||||
minHeadingLevel={tocMinHeadingLevel}
|
|
||||||
maxHeadingLevel={tocMaxHeadingLevel}
|
|
||||||
className={ThemeClassNames.docs.docTocDesktop}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{renderTocDesktop && (
|
||||||
|
<div className="col col--3">
|
||||||
|
<TOC
|
||||||
|
toc={DocContent.toc}
|
||||||
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
|
className={ThemeClassNames.docs.docTocDesktop}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocItem(props: Props): JSX.Element {
|
||||||
|
const docHtmlClassName = `docs-doc-id-${props.content.metadata.unversionedId}`;
|
||||||
|
return (
|
||||||
|
<HtmlClassNameProvider className={docHtmlClassName}>
|
||||||
|
<DocItemMetadata {...props} />
|
||||||
|
<DocItemContent {...props} />
|
||||||
</HtmlClassNameProvider>
|
</HtmlClassNameProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
useDocsSidebar,
|
useDocsSidebar,
|
||||||
DocsVersionProvider,
|
DocsVersionProvider,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
|
|
||||||
type DocPageContentProps = {
|
type DocPageContentProps = {
|
||||||
readonly currentDocRoute: DocumentRoute;
|
readonly currentDocRoute: DocumentRoute;
|
||||||
|
@ -56,87 +57,89 @@ function DocPageContent({
|
||||||
}, [hiddenSidebar]);
|
}, [hiddenSidebar]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<>
|
||||||
wrapperClassName={ThemeClassNames.wrapper.docsPages}
|
<SearchMetadata
|
||||||
pageClassName={ThemeClassNames.page.docsDocPage}
|
version={version}
|
||||||
searchMetadata={{
|
tag={docVersionSearchTag(pluginId, version)}
|
||||||
version,
|
/>
|
||||||
tag: docVersionSearchTag(pluginId, version),
|
<Layout>
|
||||||
}}>
|
<div className={styles.docPage}>
|
||||||
<div className={styles.docPage}>
|
<BackToTopButton />
|
||||||
<BackToTopButton />
|
|
||||||
|
|
||||||
{sidebar && (
|
{sidebar && (
|
||||||
<aside
|
<aside
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.docs.docSidebarContainer,
|
||||||
|
styles.docSidebarContainer,
|
||||||
|
hiddenSidebarContainer && styles.docSidebarContainerHidden,
|
||||||
|
)}
|
||||||
|
onTransitionEnd={(e) => {
|
||||||
|
if (
|
||||||
|
!e.currentTarget.classList.contains(
|
||||||
|
styles.docSidebarContainer!,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiddenSidebarContainer) {
|
||||||
|
setHiddenSidebar(true);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<DocSidebar
|
||||||
|
key={
|
||||||
|
// Reset sidebar state on sidebar changes
|
||||||
|
// See https://github.com/facebook/docusaurus/issues/3414
|
||||||
|
sidebarName
|
||||||
|
}
|
||||||
|
sidebar={sidebar}
|
||||||
|
path={currentDocRoute.path}
|
||||||
|
onCollapse={toggleSidebar}
|
||||||
|
isHidden={hiddenSidebar}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hiddenSidebar && (
|
||||||
|
<div
|
||||||
|
className={styles.collapsedDocSidebar}
|
||||||
|
title={translate({
|
||||||
|
id: 'theme.docs.sidebar.expandButtonTitle',
|
||||||
|
message: 'Expand sidebar',
|
||||||
|
description:
|
||||||
|
'The ARIA label and title attribute for expand button of doc sidebar',
|
||||||
|
})}
|
||||||
|
aria-label={translate({
|
||||||
|
id: 'theme.docs.sidebar.expandButtonAriaLabel',
|
||||||
|
message: 'Expand sidebar',
|
||||||
|
description:
|
||||||
|
'The ARIA label and title attribute for expand button of doc sidebar',
|
||||||
|
})}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
onKeyDown={toggleSidebar}
|
||||||
|
onClick={toggleSidebar}>
|
||||||
|
<IconArrow className={styles.expandSidebarButtonIcon} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</aside>
|
||||||
|
)}
|
||||||
|
<main
|
||||||
className={clsx(
|
className={clsx(
|
||||||
ThemeClassNames.docs.docSidebarContainer,
|
styles.docMainContainer,
|
||||||
styles.docSidebarContainer,
|
(hiddenSidebarContainer || !sidebar) &&
|
||||||
hiddenSidebarContainer && styles.docSidebarContainerHidden,
|
styles.docMainContainerEnhanced,
|
||||||
)}
|
|
||||||
onTransitionEnd={(e) => {
|
|
||||||
if (
|
|
||||||
!e.currentTarget.classList.contains(styles.docSidebarContainer!)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hiddenSidebarContainer) {
|
|
||||||
setHiddenSidebar(true);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<DocSidebar
|
|
||||||
key={
|
|
||||||
// Reset sidebar state on sidebar changes
|
|
||||||
// See https://github.com/facebook/docusaurus/issues/3414
|
|
||||||
sidebarName
|
|
||||||
}
|
|
||||||
sidebar={sidebar}
|
|
||||||
path={currentDocRoute.path}
|
|
||||||
onCollapse={toggleSidebar}
|
|
||||||
isHidden={hiddenSidebar}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hiddenSidebar && (
|
|
||||||
<div
|
|
||||||
className={styles.collapsedDocSidebar}
|
|
||||||
title={translate({
|
|
||||||
id: 'theme.docs.sidebar.expandButtonTitle',
|
|
||||||
message: 'Expand sidebar',
|
|
||||||
description:
|
|
||||||
'The ARIA label and title attribute for expand button of doc sidebar',
|
|
||||||
})}
|
|
||||||
aria-label={translate({
|
|
||||||
id: 'theme.docs.sidebar.expandButtonAriaLabel',
|
|
||||||
message: 'Expand sidebar',
|
|
||||||
description:
|
|
||||||
'The ARIA label and title attribute for expand button of doc sidebar',
|
|
||||||
})}
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
onKeyDown={toggleSidebar}
|
|
||||||
onClick={toggleSidebar}>
|
|
||||||
<IconArrow className={styles.expandSidebarButtonIcon} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</aside>
|
|
||||||
)}
|
|
||||||
<main
|
|
||||||
className={clsx(
|
|
||||||
styles.docMainContainer,
|
|
||||||
(hiddenSidebarContainer || !sidebar) &&
|
|
||||||
styles.docMainContainerEnhanced,
|
|
||||||
)}>
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
'container padding-top--md padding-bottom--lg',
|
|
||||||
styles.docItemWrapper,
|
|
||||||
hiddenSidebarContainer && styles.docItemWrapperEnhanced,
|
|
||||||
)}>
|
)}>
|
||||||
{children}
|
<div
|
||||||
</div>
|
className={clsx(
|
||||||
</main>
|
'container padding-top--md padding-bottom--lg',
|
||||||
</div>
|
styles.docItemWrapper,
|
||||||
</Layout>
|
hiddenSidebarContainer && styles.docItemWrapperEnhanced,
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +164,12 @@ export default function DocPage(props: Props): JSX.Element {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HtmlClassNameProvider className={versionMetadata.className}>
|
<HtmlClassNameProvider
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.wrapper.docsPages,
|
||||||
|
ThemeClassNames.page.docsDocPage,
|
||||||
|
versionMetadata.className,
|
||||||
|
)}>
|
||||||
<DocsVersionProvider version={versionMetadata}>
|
<DocsVersionProvider version={versionMetadata}>
|
||||||
<DocsSidebarProvider sidebar={sidebar ?? null}>
|
<DocsSidebarProvider sidebar={sidebar ?? null}>
|
||||||
<DocPageContent
|
<DocPageContent
|
||||||
|
|
|
@ -10,10 +10,6 @@
|
||||||
--doc-sidebar-hidden-width: 30px;
|
--doc-sidebar-hidden-width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.docs-wrapper) {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.docPage,
|
.docPage,
|
||||||
.docMainContainer {
|
.docMainContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -9,10 +9,17 @@ import React from 'react';
|
||||||
|
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import {ThemeClassNames, usePluralForm} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
usePluralForm,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import type {PropTagDocListDoc} from '@docusaurus/plugin-content-docs';
|
import type {PropTagDocListDoc} from '@docusaurus/plugin-content-docs';
|
||||||
import Translate, {translate} from '@docusaurus/Translate';
|
import Translate, {translate} from '@docusaurus/Translate';
|
||||||
import type {Props} from '@theme/DocTagDocListPage';
|
import type {Props} from '@theme/DocTagDocListPage';
|
||||||
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
// Very simple pluralization: probably good enough for now
|
// Very simple pluralization: probably good enough for now
|
||||||
function useNDocsTaggedPlural() {
|
function useNDocsTaggedPlural() {
|
||||||
|
@ -55,35 +62,36 @@ export default function DocTagDocListPage({tag}: Props): JSX.Element {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<HtmlClassNameProvider
|
||||||
title={title}
|
className={clsx(
|
||||||
wrapperClassName={ThemeClassNames.wrapper.docsPages}
|
ThemeClassNames.wrapper.docsPages,
|
||||||
pageClassName={ThemeClassNames.page.docsTagDocListPage}
|
ThemeClassNames.page.docsTagDocListPage,
|
||||||
searchMetadata={{
|
)}>
|
||||||
// assign unique search tag to exclude this page from search results!
|
<PageMetadata title={title} />
|
||||||
tag: 'doc_tag_doc_list',
|
<SearchMetadata tag="doc_tag_doc_list" />
|
||||||
}}>
|
<Layout>
|
||||||
<div className="container margin-vert--lg">
|
<div className="container margin-vert--lg">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<main className="col col--8 col--offset-2">
|
<main className="col col--8 col--offset-2">
|
||||||
<header className="margin-bottom--xl">
|
<header className="margin-bottom--xl">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
<Link href={tag.allTagsPath}>
|
<Link href={tag.allTagsPath}>
|
||||||
<Translate
|
<Translate
|
||||||
id="theme.tags.tagsPageLink"
|
id="theme.tags.tagsPageLink"
|
||||||
description="The label of the link targeting the tag list page">
|
description="The label of the link targeting the tag list page">
|
||||||
View All Tags
|
View All Tags
|
||||||
</Translate>
|
</Translate>
|
||||||
</Link>
|
</Link>
|
||||||
</header>
|
</header>
|
||||||
<section className="margin-vert--lg">
|
<section className="margin-vert--lg">
|
||||||
{tag.docs.map((doc) => (
|
{tag.docs.map((doc) => (
|
||||||
<DocItem key={doc.id} doc={doc} />
|
<DocItem key={doc.id} doc={doc} />
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Layout>
|
||||||
</Layout>
|
</HtmlClassNameProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,31 +9,36 @@ import React from 'react';
|
||||||
|
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import {
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
ThemeClassNames,
|
ThemeClassNames,
|
||||||
translateTagsPageTitle,
|
translateTagsPageTitle,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import TagsListByLetter from '@theme/TagsListByLetter';
|
import TagsListByLetter from '@theme/TagsListByLetter';
|
||||||
import type {Props} from '@theme/DocTagsListPage';
|
import type {Props} from '@theme/DocTagsListPage';
|
||||||
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export default function DocTagsListPage({tags}: Props): JSX.Element {
|
export default function DocTagsListPage({tags}: Props): JSX.Element {
|
||||||
const title = translateTagsPageTitle();
|
const title = translateTagsPageTitle();
|
||||||
return (
|
return (
|
||||||
<Layout
|
<HtmlClassNameProvider
|
||||||
title={title}
|
className={clsx(
|
||||||
wrapperClassName={ThemeClassNames.wrapper.docsPages}
|
ThemeClassNames.wrapper.docsPages,
|
||||||
pageClassName={ThemeClassNames.page.docsTagsListPage}
|
ThemeClassNames.page.docsTagsListPage,
|
||||||
searchMetadata={{
|
)}>
|
||||||
// assign unique search tag to exclude this page from search results!
|
<PageMetadata title={title} />
|
||||||
tag: 'doc_tags_list',
|
<SearchMetadata tag="doc_tags_list" />
|
||||||
}}>
|
<Layout>
|
||||||
<div className="container margin-vert--lg">
|
<div className="container margin-vert--lg">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<main className="col col--8 col--offset-2">
|
<main className="col col--8 col--offset-2">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
<TagsListByLetter tags={tags} />
|
<TagsListByLetter tags={tags} />
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Layout>
|
||||||
</Layout>
|
</HtmlClassNameProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,20 +13,30 @@ import AnnouncementBar from '@theme/AnnouncementBar';
|
||||||
import Navbar from '@theme/Navbar';
|
import Navbar from '@theme/Navbar';
|
||||||
import Footer from '@theme/Footer';
|
import Footer from '@theme/Footer';
|
||||||
import LayoutProviders from '@theme/LayoutProviders';
|
import LayoutProviders from '@theme/LayoutProviders';
|
||||||
import LayoutHead from '@theme/LayoutHead';
|
|
||||||
import type {Props} from '@theme/Layout';
|
import type {Props} from '@theme/Layout';
|
||||||
import {ThemeClassNames, useKeyboardNavigation} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
ThemeClassNames,
|
||||||
|
useKeyboardNavigation,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import ErrorPageContent from '@theme/ErrorPageContent';
|
import ErrorPageContent from '@theme/ErrorPageContent';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
export default function Layout(props: Props): JSX.Element {
|
export default function Layout(props: Props): JSX.Element {
|
||||||
const {children, noFooter, wrapperClassName, pageClassName} = props;
|
const {
|
||||||
|
children,
|
||||||
|
noFooter,
|
||||||
|
wrapperClassName,
|
||||||
|
// not really layout-related, but kept for convenience/retro-compatibility
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
} = props;
|
||||||
|
|
||||||
useKeyboardNavigation();
|
useKeyboardNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutProviders>
|
<LayoutProviders>
|
||||||
<LayoutHead {...props} />
|
<PageMetadata title={title} description={description} />
|
||||||
|
|
||||||
<SkipToContent />
|
<SkipToContent />
|
||||||
|
|
||||||
|
@ -34,12 +44,7 @@ export default function Layout(props: Props): JSX.Element {
|
||||||
|
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<div
|
<div className={clsx(ThemeClassNames.wrapper.main, wrapperClassName)}>
|
||||||
className={clsx(
|
|
||||||
ThemeClassNames.wrapper.main,
|
|
||||||
wrapperClassName,
|
|
||||||
pageClassName,
|
|
||||||
)}>
|
|
||||||
<ErrorBoundary fallback={ErrorPageContent}>{children}</ErrorBoundary>
|
<ErrorBoundary fallback={ErrorPageContent}>{children}</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -11,43 +11,49 @@ import Layout from '@theme/Layout';
|
||||||
import MDXContent from '@theme/MDXContent';
|
import MDXContent from '@theme/MDXContent';
|
||||||
import type {Props} from '@theme/MDXPage';
|
import type {Props} from '@theme/MDXPage';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
import {ThemeClassNames} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function MDXPage(props: Props): JSX.Element {
|
export default function MDXPage(props: Props): JSX.Element {
|
||||||
const {content: MDXPageContent} = props;
|
const {content: MDXPageContent} = props;
|
||||||
const {
|
const {
|
||||||
metadata: {title, description, permalink, frontMatter},
|
metadata: {title, description, frontMatter},
|
||||||
} = MDXPageContent;
|
} = MDXPageContent;
|
||||||
const {wrapperClassName, hide_table_of_contents: hideTableOfContents} =
|
const {wrapperClassName, hide_table_of_contents: hideTableOfContents} =
|
||||||
frontMatter;
|
frontMatter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<HtmlClassNameProvider
|
||||||
title={title}
|
className={clsx(
|
||||||
description={description}
|
wrapperClassName ?? ThemeClassNames.wrapper.mdxPages,
|
||||||
permalink={permalink}
|
ThemeClassNames.page.mdxPage,
|
||||||
wrapperClassName={wrapperClassName ?? ThemeClassNames.wrapper.mdxPages}
|
)}>
|
||||||
pageClassName={ThemeClassNames.page.mdxPage}>
|
<PageMetadata title={title} description={description} />
|
||||||
<main className="container container--fluid margin-vert--lg">
|
<Layout>
|
||||||
<div className={clsx('row', styles.mdxPageWrapper)}>
|
<main className="container container--fluid margin-vert--lg">
|
||||||
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
<div className={clsx('row', styles.mdxPageWrapper)}>
|
||||||
<MDXContent>
|
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
||||||
<MDXPageContent />
|
<MDXContent>
|
||||||
</MDXContent>
|
<MDXPageContent />
|
||||||
</div>
|
</MDXContent>
|
||||||
{!hideTableOfContents && MDXPageContent.toc && (
|
|
||||||
<div className="col col--2">
|
|
||||||
<TOC
|
|
||||||
toc={MDXPageContent.toc}
|
|
||||||
minHeadingLevel={frontMatter.toc_min_heading_level}
|
|
||||||
maxHeadingLevel={frontMatter.toc_max_heading_level}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{!hideTableOfContents && MDXPageContent.toc && (
|
||||||
</div>
|
<div className="col col--2">
|
||||||
</main>
|
<TOC
|
||||||
</Layout>
|
toc={MDXPageContent.toc}
|
||||||
|
minHeadingLevel={frontMatter.toc_min_heading_level}
|
||||||
|
maxHeadingLevel={frontMatter.toc_max_heading_level}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
|
</HtmlClassNameProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,42 +8,47 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import Translate, {translate} from '@docusaurus/Translate';
|
import Translate, {translate} from '@docusaurus/Translate';
|
||||||
|
import {PageMetadata} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
export default function NotFound(): JSX.Element {
|
export default function NotFound(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Layout
|
<>
|
||||||
title={translate({
|
<PageMetadata
|
||||||
id: 'theme.NotFound.title',
|
title={translate({
|
||||||
message: 'Page Not Found',
|
id: 'theme.NotFound.title',
|
||||||
})}>
|
message: 'Page Not Found',
|
||||||
<main className="container margin-vert--xl">
|
})}
|
||||||
<div className="row">
|
/>
|
||||||
<div className="col col--6 col--offset-3">
|
<Layout>
|
||||||
<h1 className="hero__title">
|
<main className="container margin-vert--xl">
|
||||||
<Translate
|
<div className="row">
|
||||||
id="theme.NotFound.title"
|
<div className="col col--6 col--offset-3">
|
||||||
description="The title of the 404 page">
|
<h1 className="hero__title">
|
||||||
Page Not Found
|
<Translate
|
||||||
</Translate>
|
id="theme.NotFound.title"
|
||||||
</h1>
|
description="The title of the 404 page">
|
||||||
<p>
|
Page Not Found
|
||||||
<Translate
|
</Translate>
|
||||||
id="theme.NotFound.p1"
|
</h1>
|
||||||
description="The first paragraph of the 404 page">
|
<p>
|
||||||
We could not find what you were looking for.
|
<Translate
|
||||||
</Translate>
|
id="theme.NotFound.p1"
|
||||||
</p>
|
description="The first paragraph of the 404 page">
|
||||||
<p>
|
We could not find what you were looking for.
|
||||||
<Translate
|
</Translate>
|
||||||
id="theme.NotFound.p2"
|
</p>
|
||||||
description="The 2nd paragraph of the 404 page">
|
<p>
|
||||||
Please contact the owner of the site that linked you to the
|
<Translate
|
||||||
original URL and let them know their link is broken.
|
id="theme.NotFound.p2"
|
||||||
</Translate>
|
description="The 2nd paragraph of the 404 page">
|
||||||
</p>
|
Please contact the owner of the site that linked you to the
|
||||||
|
original URL and let them know their link is broken.
|
||||||
|
</Translate>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</main>
|
</Layout>
|
||||||
</Layout>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 {useTitleFormatter} from '@docusaurus/theme-common';
|
|
||||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
|
||||||
|
|
||||||
import type {Props} from '@theme/Seo';
|
|
||||||
|
|
||||||
export default function Seo({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
keywords,
|
|
||||||
image,
|
|
||||||
children,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const pageTitle = useTitleFormatter(title);
|
|
||||||
const {withBaseUrl} = useBaseUrlUtils();
|
|
||||||
const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Head>
|
|
||||||
{title && <title>{pageTitle}</title>}
|
|
||||||
{title && <meta property="og:title" content={pageTitle} />}
|
|
||||||
|
|
||||||
{description && <meta name="description" content={description} />}
|
|
||||||
{description && <meta property="og:description" content={description} />}
|
|
||||||
|
|
||||||
{keywords && (
|
|
||||||
<meta
|
|
||||||
name="keywords"
|
|
||||||
content={
|
|
||||||
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{pageImage && <meta property="og:image" content={pageImage} />}
|
|
||||||
{pageImage && <meta name="twitter:image" content={pageImage} />}
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</Head>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -9,19 +9,18 @@ import React from 'react';
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||||
import type {Props} from '@theme/Layout';
|
|
||||||
import SearchMetadata from '@theme/SearchMetadata';
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
import Seo from '@theme/Seo';
|
|
||||||
import {
|
import {
|
||||||
|
PageMetadata,
|
||||||
DEFAULT_SEARCH_TAG,
|
DEFAULT_SEARCH_TAG,
|
||||||
useTitleFormatter,
|
|
||||||
useAlternatePageUtils,
|
useAlternatePageUtils,
|
||||||
useThemeConfig,
|
useThemeConfig,
|
||||||
keyboardFocusedClassName,
|
keyboardFocusedClassName,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import {useLocation} from '@docusaurus/router';
|
import {useLocation} from '@docusaurus/router';
|
||||||
|
|
||||||
// Useful for SEO
|
// TODO move to SiteMetadataDefaults or theme-common ?
|
||||||
|
// Useful for i18n/SEO
|
||||||
// See https://developers.google.com/search/docs/advanced/crawling/localized-versions
|
// See https://developers.google.com/search/docs/advanced/crawling/localized-versions
|
||||||
// See https://github.com/facebook/docusaurus/issues/3317
|
// See https://github.com/facebook/docusaurus/issues/3317
|
||||||
function AlternateLangHeaders(): JSX.Element {
|
function AlternateLangHeaders(): JSX.Element {
|
||||||
|
@ -66,6 +65,7 @@ function useDefaultCanonicalUrl() {
|
||||||
return siteUrl + useBaseUrl(pathname);
|
return siteUrl + useBaseUrl(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO move to SiteMetadataDefaults or theme-common ?
|
||||||
function CanonicalUrlHeaders({permalink}: {permalink?: string}) {
|
function CanonicalUrlHeaders({permalink}: {permalink?: string}) {
|
||||||
const {
|
const {
|
||||||
siteConfig: {url: siteUrl},
|
siteConfig: {url: siteUrl},
|
||||||
|
@ -83,45 +83,31 @@ function CanonicalUrlHeaders({permalink}: {permalink?: string}) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LayoutHead(props: Props): JSX.Element {
|
export default function SiteMetadata(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
siteConfig: {favicon},
|
i18n: {currentLocale},
|
||||||
i18n: {currentLocale, localeConfigs},
|
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
|
|
||||||
|
// TODO maybe move these 2 themeConfig to siteConfig?
|
||||||
|
// These seems useful for other themes as well
|
||||||
const {metadata, image: defaultImage} = useThemeConfig();
|
const {metadata, image: defaultImage} = useThemeConfig();
|
||||||
const {title, description, image, keywords, searchMetadata} = props;
|
|
||||||
const faviconUrl = useBaseUrl(favicon);
|
|
||||||
const pageTitle = useTitleFormatter(title);
|
|
||||||
const {htmlLang, direction: htmlDir} = localeConfigs[currentLocale]!;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<html lang={htmlLang} dir={htmlDir} />
|
|
||||||
{favicon && <link rel="icon" href={faviconUrl} />}
|
|
||||||
<title>{pageTitle}</title>
|
|
||||||
<meta property="og:title" content={pageTitle} />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
{/* The keyboard focus class name need to be applied when SSR so links
|
{/* The keyboard focus class name need to be applied when SSR so links
|
||||||
are outlined when JS is disabled */}
|
are outlined when JS is disabled */}
|
||||||
<body className={keyboardFocusedClassName} />
|
<body className={keyboardFocusedClassName} />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
{/* image can override the default image */}
|
{defaultImage && <PageMetadata image={defaultImage} />}
|
||||||
{defaultImage && <Seo image={defaultImage} />}
|
|
||||||
{image && <Seo image={image} />}
|
|
||||||
|
|
||||||
<Seo description={description} keywords={keywords} />
|
|
||||||
|
|
||||||
<CanonicalUrlHeaders />
|
<CanonicalUrlHeaders />
|
||||||
|
|
||||||
<AlternateLangHeaders />
|
<AlternateLangHeaders />
|
||||||
|
|
||||||
<SearchMetadata
|
<SearchMetadata tag={DEFAULT_SEARCH_TAG} locale={currentLocale} />
|
||||||
tag={DEFAULT_SEARCH_TAG}
|
|
||||||
locale={currentLocale}
|
|
||||||
{...searchMetadata}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Head
|
<Head
|
||||||
// it's important to have an additional <Head> element here,
|
// it's important to have an additional <Head> element here,
|
|
@ -129,9 +129,10 @@ export {isRegexpStringMatch} from './utils/regexpUtils';
|
||||||
export {useHomePageRoute} from './utils/routesUtils';
|
export {useHomePageRoute} from './utils/routesUtils';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
PageMetadata,
|
||||||
HtmlClassNameProvider,
|
HtmlClassNameProvider,
|
||||||
PluginHtmlClassNameProvider,
|
PluginHtmlClassNameProvider,
|
||||||
} from './utils/metadataUtilsTemp';
|
} from './utils/metadataUtils';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useColorMode,
|
useColorMode,
|
||||||
|
|
|
@ -9,6 +9,53 @@ import React, {type ReactNode} from 'react';
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import useRouteContext from '@docusaurus/useRouteContext';
|
import useRouteContext from '@docusaurus/useRouteContext';
|
||||||
|
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||||
|
import {useTitleFormatter} from './generalUtils';
|
||||||
|
|
||||||
|
interface PageMetadataProps {
|
||||||
|
readonly title?: string;
|
||||||
|
readonly description?: string;
|
||||||
|
readonly keywords?: readonly string[] | string;
|
||||||
|
readonly image?: string;
|
||||||
|
readonly children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper component to manipulate page metadata and override site defaults
|
||||||
|
export function PageMetadata({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
keywords,
|
||||||
|
image,
|
||||||
|
children,
|
||||||
|
}: PageMetadataProps): JSX.Element {
|
||||||
|
const pageTitle = useTitleFormatter(title);
|
||||||
|
const {withBaseUrl} = useBaseUrlUtils();
|
||||||
|
const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Head>
|
||||||
|
{title && <title>{pageTitle}</title>}
|
||||||
|
{title && <meta property="og:title" content={pageTitle} />}
|
||||||
|
|
||||||
|
{description && <meta name="description" content={description} />}
|
||||||
|
{description && <meta property="og:description" content={description} />}
|
||||||
|
|
||||||
|
{keywords && (
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content={
|
||||||
|
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{pageImage && <meta property="og:image" content={pageImage} />}
|
||||||
|
{pageImage && <meta name="twitter:image" content={pageImage} />}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</Head>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const HtmlClassNameContext = React.createContext<string | undefined>(undefined);
|
const HtmlClassNameContext = React.createContext<string | undefined>(undefined);
|
||||||
|
|
|
@ -17,6 +17,7 @@ import Head from '@docusaurus/Head';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||||
import {
|
import {
|
||||||
|
HtmlClassNameProvider,
|
||||||
useTitleFormatter,
|
useTitleFormatter,
|
||||||
usePluralForm,
|
usePluralForm,
|
||||||
isRegexpStringMatch,
|
isRegexpStringMatch,
|
||||||
|
@ -149,7 +150,7 @@ type ResultDispatcher =
|
||||||
| {type: 'update'; value: ResultDispatcherState}
|
| {type: 'update'; value: ResultDispatcherState}
|
||||||
| {type: 'advance'; value?: undefined};
|
| {type: 'advance'; value?: undefined};
|
||||||
|
|
||||||
export default function SearchPage(): JSX.Element {
|
function SearchPageContent(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
siteConfig: {themeConfig},
|
siteConfig: {themeConfig},
|
||||||
i18n: {currentLocale},
|
i18n: {currentLocale},
|
||||||
|
@ -356,7 +357,7 @@ export default function SearchPage(): JSX.Element {
|
||||||
}, [makeSearch, searchResultState.lastPage]);
|
}, [makeSearch, searchResultState.lastPage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout wrapperClassName="search-page-wrapper">
|
<Layout>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{useTitleFormatter(getTitle())}</title>
|
<title>{useTitleFormatter(getTitle())}</title>
|
||||||
{/*
|
{/*
|
||||||
|
@ -516,3 +517,11 @@ export default function SearchPage(): JSX.Element {
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function SearchPage(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<HtmlClassNameProvider className="search-page-wrapper">
|
||||||
|
<SearchPageContent />
|
||||||
|
</HtmlClassNameProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,9 @@ import {BrowserContextProvider} from './exports/browserContext';
|
||||||
import {DocusaurusContextProvider} from './exports/docusaurusContext';
|
import {DocusaurusContextProvider} from './exports/docusaurusContext';
|
||||||
import PendingNavigation from './PendingNavigation';
|
import PendingNavigation from './PendingNavigation';
|
||||||
import BaseUrlIssueBanner from './baseUrlIssueBanner/BaseUrlIssueBanner';
|
import BaseUrlIssueBanner from './baseUrlIssueBanner/BaseUrlIssueBanner';
|
||||||
|
import SiteMetadataDefaults from './SiteMetadataDefaults';
|
||||||
import Root from '@theme/Root';
|
import Root from '@theme/Root';
|
||||||
|
import SiteMetadata from '@theme/SiteMetadata';
|
||||||
|
|
||||||
import './client-lifecycles-dispatcher';
|
import './client-lifecycles-dispatcher';
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ export default function App(): JSX.Element {
|
||||||
<DocusaurusContextProvider>
|
<DocusaurusContextProvider>
|
||||||
<BrowserContextProvider>
|
<BrowserContextProvider>
|
||||||
<Root>
|
<Root>
|
||||||
|
<SiteMetadataDefaults />
|
||||||
|
<SiteMetadata />
|
||||||
<BaseUrlIssueBanner />
|
<BaseUrlIssueBanner />
|
||||||
<PendingNavigation routes={routes} delay={1000}>
|
<PendingNavigation routes={routes} delay={1000}>
|
||||||
{renderRoutes(routes)}
|
{renderRoutes(routes)}
|
||||||
|
|
27
packages/docusaurus/src/client/SiteMetadataDefaults.tsx
Normal file
27
packages/docusaurus/src/client/SiteMetadataDefaults.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* 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 useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||||
|
|
||||||
|
export default function SiteMetadataDefaults(): JSX.Element {
|
||||||
|
const {
|
||||||
|
siteConfig: {favicon, tagline, title},
|
||||||
|
i18n: {currentLocale, localeConfigs},
|
||||||
|
} = useDocusaurusContext();
|
||||||
|
const faviconUrl = useBaseUrl(favicon);
|
||||||
|
const {htmlLang, direction: htmlDir} = localeConfigs[currentLocale]!;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Head defaultTitle={`${title}${tagline ? ` · ${tagline}` : ''}`}>
|
||||||
|
<html lang={htmlLang} dir={htmlDir} />
|
||||||
|
{favicon && <link rel="icon" href={faviconUrl} />}
|
||||||
|
</Head>
|
||||||
|
);
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import ErrorBoundary from '@docusaurus/ErrorBoundary';
|
import ErrorBoundary from '@docusaurus/ErrorBoundary';
|
||||||
import type {Props} from '@theme/Error';
|
import type {Props} from '@theme/Error';
|
||||||
|
import Head from '@docusaurus/Head';
|
||||||
|
|
||||||
function ErrorDisplay({error, tryAgain}: Props): JSX.Element {
|
function ErrorDisplay({error, tryAgain}: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
@ -40,7 +41,10 @@ export default function Error({error, tryAgain}: Props): JSX.Element {
|
||||||
// Note: we display the original error here, not the error that we
|
// Note: we display the original error here, not the error that we
|
||||||
// captured in this extra error boundary
|
// captured in this extra error boundary
|
||||||
fallback={() => <ErrorDisplay error={error} tryAgain={tryAgain} />}>
|
fallback={() => <ErrorDisplay error={error} tryAgain={tryAgain} />}>
|
||||||
<Layout title="Page Error">
|
<Head>
|
||||||
|
<title>Page Error</title>
|
||||||
|
</Head>
|
||||||
|
<Layout>
|
||||||
<ErrorDisplay error={error} tryAgain={tryAgain} />
|
<ErrorDisplay error={error} tryAgain={tryAgain} />
|
||||||
</Layout>
|
</Layout>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|
|
@ -6,31 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Head from '@docusaurus/Head';
|
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
||||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
|
||||||
import type {Props} from '@theme/Layout';
|
import type {Props} from '@theme/Layout';
|
||||||
|
|
||||||
export default function Layout({
|
export default function Layout({children}: Props): JSX.Element {
|
||||||
children,
|
return <>{children}</>;
|
||||||
title,
|
|
||||||
description,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const context = useDocusaurusContext();
|
|
||||||
const {siteConfig} = context;
|
|
||||||
const {favicon, tagline, title: defaultTitle} = siteConfig;
|
|
||||||
const faviconUrl = useBaseUrl(favicon);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head defaultTitle={`${defaultTitle}${tagline ? ` · ${tagline}` : ''}`}>
|
|
||||||
{title && <title>{`${title} · ${tagline}`}</title>}
|
|
||||||
{favicon && <link rel="icon" href={faviconUrl} />}
|
|
||||||
{description && <meta name="description" content={description} />}
|
|
||||||
{description && (
|
|
||||||
<meta property="og:description" content={description} />
|
|
||||||
)}
|
|
||||||
</Head>
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,26 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
|
import Head from '@docusaurus/Head';
|
||||||
|
|
||||||
export default function NotFound(): JSX.Element {
|
export default function NotFound(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Layout title="Page Not Found">
|
<>
|
||||||
<div
|
<Head>
|
||||||
style={{
|
<title>Page Not Found</title>
|
||||||
display: 'flex',
|
</Head>
|
||||||
justifyContent: 'center',
|
<Layout>
|
||||||
alignItems: 'center',
|
<div
|
||||||
height: '50vh',
|
style={{
|
||||||
fontSize: '20px',
|
display: 'flex',
|
||||||
}}>
|
justifyContent: 'center',
|
||||||
<h1>Oops, page not found </h1>
|
alignItems: 'center',
|
||||||
</div>
|
height: '50vh',
|
||||||
</Layout>
|
fontSize: '20px',
|
||||||
|
}}>
|
||||||
|
<h1>Oops, page not found </h1>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {type ReactNode} from 'react';
|
import React from 'react';
|
||||||
|
import type {Props} from '@theme/Root';
|
||||||
|
|
||||||
// Wrapper at the very top of the app, that is applied constantly
|
// Wrapper at the very top of the app, that is applied constantly
|
||||||
// and does not depend on current route (unlike the layout)
|
// and does not depend on current route (unlike the layout)
|
||||||
|
@ -14,6 +15,6 @@ import React, {type ReactNode} from 'react';
|
||||||
// and these providers won't reset state when we navigate
|
// and these providers won't reset state when we navigate
|
||||||
//
|
//
|
||||||
// See https://github.com/facebook/docusaurus/issues/3919
|
// See https://github.com/facebook/docusaurus/issues/3919
|
||||||
export default function Root({children}: {children: ReactNode}): JSX.Element {
|
export default function Root({children}: Props): JSX.Element {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// To be implemented by the theme with <Head>
|
||||||
|
export default function SiteMetadata(): JSX.Element | null {
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ exports[`base webpack config creates webpack aliases 1`] = `
|
||||||
"@theme-original/PluginThemeComponentEnhanced": "secondPluginThemeFolder/PluginThemeComponentEnhanced.js",
|
"@theme-original/PluginThemeComponentEnhanced": "secondPluginThemeFolder/PluginThemeComponentEnhanced.js",
|
||||||
"@theme-original/PluginThemeComponentOverriddenByUser": "pluginThemeFolder/PluginThemeComponentOverriddenByUser.js",
|
"@theme-original/PluginThemeComponentOverriddenByUser": "pluginThemeFolder/PluginThemeComponentOverriddenByUser.js",
|
||||||
"@theme-original/Root": "../../../../client/theme-fallback/Root/index.tsx",
|
"@theme-original/Root": "../../../../client/theme-fallback/Root/index.tsx",
|
||||||
|
"@theme-original/SiteMetadata": "../../../../client/theme-fallback/SiteMetadata/index.tsx",
|
||||||
"@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
|
"@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
|
||||||
"@theme/Error": "../../../../client/theme-fallback/Error/index.tsx",
|
"@theme/Error": "../../../../client/theme-fallback/Error/index.tsx",
|
||||||
"@theme/Layout": "../../../../client/theme-fallback/Layout/index.tsx",
|
"@theme/Layout": "../../../../client/theme-fallback/Layout/index.tsx",
|
||||||
|
@ -42,6 +43,7 @@ exports[`base webpack config creates webpack aliases 1`] = `
|
||||||
"@theme/PluginThemeComponentEnhanced": "src/theme/PluginThemeComponentEnhanced.js",
|
"@theme/PluginThemeComponentEnhanced": "src/theme/PluginThemeComponentEnhanced.js",
|
||||||
"@theme/PluginThemeComponentOverriddenByUser": "src/theme/PluginThemeComponentOverriddenByUser.js",
|
"@theme/PluginThemeComponentOverriddenByUser": "src/theme/PluginThemeComponentOverriddenByUser.js",
|
||||||
"@theme/Root": "../../../../client/theme-fallback/Root/index.tsx",
|
"@theme/Root": "../../../../client/theme-fallback/Root/index.tsx",
|
||||||
|
"@theme/SiteMetadata": "../../../../client/theme-fallback/SiteMetadata/index.tsx",
|
||||||
"@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js",
|
"@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js",
|
||||||
"@theme/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
|
"@theme/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js",
|
||||||
"@theme/subfolder/UserThemeComponent2": "src/theme/subfolder/UserThemeComponent2.js",
|
"@theme/subfolder/UserThemeComponent2": "src/theme/subfolder/UserThemeComponent2.js",
|
||||||
|
|
22
website/_dogfooding/dogfooding.css
Normal file
22
website/_dogfooding/dogfooding.css
Normal file
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
html.plugin-docs.plugin-id-docs-tests .red > a {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.plugin-docs.plugin-id-docs-tests .navbar {
|
||||||
|
border-bottom: solid thin cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.plugin-blog.plugin-id-blog-tests .navbar {
|
||||||
|
border-bottom: solid thin lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.plugin-pages.plugin-id-pages-tests .navbar {
|
||||||
|
border-bottom: solid thin yellow;
|
||||||
|
}
|
|
@ -35,9 +35,9 @@ Create a file `/src/pages/helloReact.js`:
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
|
|
||||||
function Hello() {
|
export default function Hello() {
|
||||||
return (
|
return (
|
||||||
<Layout title="Hello">
|
<Layout title="Hello" description="Hello React Page">
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -53,8 +53,6 @@ function Hello() {
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Hello;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Once you save the file, the development server will automatically reload the changes. Now open `http://localhost:3000/helloReact` and you will see the new page you just created.
|
Once you save the file, the development server will automatically reload the changes. Now open `http://localhost:3000/helloReact` and you will see the new page you just created.
|
||||||
|
|
|
@ -42,22 +42,6 @@ Similar to [global metadata](#global-metadata), Docusaurus also allows for the a
|
||||||
Some content...
|
Some content...
|
||||||
```
|
```
|
||||||
|
|
||||||
```jsx title="my-react-page.jsx"
|
|
||||||
import React from 'react';
|
|
||||||
import Head from '@docusaurus/Head';
|
|
||||||
|
|
||||||
export default function page() {
|
|
||||||
return (
|
|
||||||
<Layout title="Page" description="A React page demo">
|
|
||||||
<Head>
|
|
||||||
<meta property="og:image" content="image.png" />
|
|
||||||
</Head>
|
|
||||||
{/* ... */}
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Docusaurus automatically adds `description`, `title`, canonical URL links, and other useful metadata to each Markdown page. They are configurable through front matter:
|
Docusaurus automatically adds `description`, `title`, canonical URL links, and other useful metadata to each Markdown page. They are configurable through front matter:
|
||||||
|
|
||||||
```md
|
```md
|
||||||
|
@ -77,6 +61,31 @@ Prefer to use front matter for fields like `description` and `keywords`: Docusau
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
For JSX pages, you can use the Docusaurus [`<Head>`](docusaurus-core.md#head) component.
|
||||||
|
|
||||||
|
```jsx title="my-react-page.jsx"
|
||||||
|
import React from 'react';
|
||||||
|
import Layout from '@theme/Layout';
|
||||||
|
import Head from '@docusaurus/Head';
|
||||||
|
|
||||||
|
export default function page() {
|
||||||
|
return (
|
||||||
|
<Layout title="Page" description="A React page demo">
|
||||||
|
<Head>
|
||||||
|
<meta property="og:image" content="image.png" />
|
||||||
|
</Head>
|
||||||
|
{/* ... */}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
For convenience, the default theme `<Layout>` component accept `title` and `description` as props.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Static HTML generation {#static-html-generation}
|
## Static HTML generation {#static-html-generation}
|
||||||
|
|
||||||
Docusaurus is a static site generator—HTML files are statically generated for every URL route, which helps search engines discover your content more easily.
|
Docusaurus is a static site generator—HTML files are statically generated for every URL route, which helps search engines discover your content more easily.
|
||||||
|
|
|
@ -320,7 +320,10 @@ const config = {
|
||||||
remarkPlugins: [npm2yarn],
|
remarkPlugins: [npm2yarn],
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
customCss: [require.resolve('./src/css/custom.css')],
|
customCss: [
|
||||||
|
require.resolve('./src/css/custom.css'),
|
||||||
|
require.resolve('./_dogfooding/dogfooding.css'),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
gtag: !isDeployPreview
|
gtag: !isDeployPreview
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -166,10 +166,6 @@ div[class^='announcementBar_'] {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.red > a {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.screen-reader-only {
|
.screen-reader-only {
|
||||||
border: 0;
|
border: 0;
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
|
|
|
@ -9,27 +9,35 @@ import React from 'react';
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
import type {Props} from '@theme/BlogListPage';
|
import type {Props} from '@theme/BlogListPage';
|
||||||
import {ThemeClassNames} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import ChangelogItem from '@theme/ChangelogItem';
|
import ChangelogItem from '@theme/ChangelogItem';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export default function ChangelogList(props: Props): JSX.Element {
|
function ChangelogListMetadata(props: Props): JSX.Element {
|
||||||
|
const {metadata} = props;
|
||||||
|
const {blogTitle, blogDescription} = metadata;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageMetadata title={blogTitle} description={blogDescription} />
|
||||||
|
<SearchMetadata tag="blog_posts_list" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangelogListContent(props: Props): JSX.Element {
|
||||||
const {metadata, items, sidebar} = props;
|
const {metadata, items, sidebar} = props;
|
||||||
const {blogDescription, blogTitle} = metadata;
|
const {blogTitle} = metadata;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlogLayout
|
<BlogLayout sidebar={sidebar}>
|
||||||
title={blogTitle}
|
|
||||||
description={blogDescription}
|
|
||||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
|
||||||
pageClassName={ThemeClassNames.page.blogListPage}
|
|
||||||
searchMetadata={{
|
|
||||||
// assign unique search tag to exclude this page from search results!
|
|
||||||
tag: 'blog_posts_list',
|
|
||||||
}}
|
|
||||||
sidebar={sidebar}>
|
|
||||||
<header className="margin-bottom--lg">
|
<header className="margin-bottom--lg">
|
||||||
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
|
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -88,3 +96,16 @@ export default function ChangelogList(props: Props): JSX.Element {
|
||||||
</BlogLayout>
|
</BlogLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function ChangelogList(props: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<HtmlClassNameProvider
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.wrapper.blogPages,
|
||||||
|
ThemeClassNames.page.blogListPage,
|
||||||
|
)}>
|
||||||
|
<ChangelogListMetadata {...props} />
|
||||||
|
<ChangelogListContent {...props} />
|
||||||
|
</HtmlClassNameProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -6,96 +6,116 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Seo from '@theme/Seo';
|
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import ChangelogItem from '@theme/ChangelogItem';
|
import ChangelogItem from '@theme/ChangelogItem';
|
||||||
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
||||||
import type {Props} from '@theme/BlogPostPage';
|
import type {Props} from '@theme/BlogPostPage';
|
||||||
import {ThemeClassNames} from '@docusaurus/theme-common';
|
import {
|
||||||
|
PageMetadata,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
// This page doesn't change anything. It's just swapping BlogPostItem with our
|
function ChangelogPageMetadata(props: Props): JSX.Element {
|
||||||
// own ChangelogItem. We don't want to apply the swizzled item to the actual
|
const {content: BlogPostContents} = props;
|
||||||
// blog.
|
const {assets, metadata} = BlogPostContents;
|
||||||
export default function BlogPostPage(props: Props): JSX.Element {
|
const {title, description, date, tags, authors, frontMatter} = metadata;
|
||||||
|
const {keywords} = frontMatter;
|
||||||
|
|
||||||
|
const image = assets.image ?? frontMatter.image;
|
||||||
|
return (
|
||||||
|
<PageMetadata
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
keywords={keywords}
|
||||||
|
image={image}>
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="article:published_time" content={date} />
|
||||||
|
|
||||||
|
{authors.some((author) => author.url) && (
|
||||||
|
<meta
|
||||||
|
property="article:author"
|
||||||
|
content={authors
|
||||||
|
.map((author) => author.url)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(',')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<meta
|
||||||
|
property="article:tag"
|
||||||
|
content={tags.map((tag) => tag.label).join(',')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PageMetadata>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangelogPageContent(props: Props): JSX.Element {
|
||||||
const {content: BlogPostContents, sidebar} = props;
|
const {content: BlogPostContents, sidebar} = props;
|
||||||
const {assets, metadata} = BlogPostContents;
|
const {assets, metadata} = BlogPostContents;
|
||||||
const {
|
const {
|
||||||
title,
|
|
||||||
description,
|
|
||||||
nextItem,
|
nextItem,
|
||||||
prevItem,
|
prevItem,
|
||||||
date,
|
|
||||||
tags,
|
|
||||||
authors,
|
|
||||||
frontMatter,
|
frontMatter,
|
||||||
// @ts-expect-error: we injected this
|
// @ts-expect-error: we injected this
|
||||||
listPageLink,
|
listPageLink,
|
||||||
} = metadata;
|
} = metadata;
|
||||||
const {
|
const {
|
||||||
hide_table_of_contents: hideTableOfContents,
|
hide_table_of_contents: hideTableOfContents,
|
||||||
keywords,
|
|
||||||
toc_min_heading_level: tocMinHeadingLevel,
|
toc_min_heading_level: tocMinHeadingLevel,
|
||||||
toc_max_heading_level: tocMaxHeadingLevel,
|
toc_max_heading_level: tocMaxHeadingLevel,
|
||||||
} = frontMatter;
|
} = frontMatter;
|
||||||
|
|
||||||
const image = assets.image ?? frontMatter.image;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlogLayout
|
<>
|
||||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
<PageMetadata />
|
||||||
pageClassName={ThemeClassNames.page.blogPostPage}
|
<BlogLayout
|
||||||
sidebar={sidebar}
|
sidebar={sidebar}
|
||||||
toc={
|
toc={
|
||||||
!hideTableOfContents &&
|
!hideTableOfContents &&
|
||||||
BlogPostContents.toc &&
|
BlogPostContents.toc &&
|
||||||
BlogPostContents.toc.length > 0 ? (
|
BlogPostContents.toc.length > 0 ? (
|
||||||
<TOC
|
<TOC
|
||||||
toc={BlogPostContents.toc}
|
toc={BlogPostContents.toc}
|
||||||
minHeadingLevel={tocMinHeadingLevel}
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
maxHeadingLevel={tocMaxHeadingLevel}
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}>
|
}>
|
||||||
<Seo
|
<Link to={listPageLink}>← Back to index page</Link>
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
keywords={keywords}
|
|
||||||
image={image}>
|
|
||||||
<meta property="og:type" content="article" />
|
|
||||||
<meta property="article:published_time" content={date} />
|
|
||||||
|
|
||||||
{authors.some((author) => author.url) && (
|
<ChangelogItem
|
||||||
<meta
|
frontMatter={frontMatter}
|
||||||
property="article:author"
|
assets={assets}
|
||||||
content={authors
|
metadata={metadata}
|
||||||
.map((author) => author.url)
|
isBlogPostPage>
|
||||||
.filter(Boolean)
|
<BlogPostContents />
|
||||||
.join(',')}
|
</ChangelogItem>
|
||||||
/>
|
|
||||||
|
{(nextItem || prevItem) && (
|
||||||
|
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
|
||||||
)}
|
)}
|
||||||
{tags.length > 0 && (
|
</BlogLayout>
|
||||||
<meta
|
</>
|
||||||
property="article:tag"
|
);
|
||||||
content={tags.map((tag) => tag.label).join(',')}
|
}
|
||||||
/>
|
|
||||||
)}
|
// This page doesn't change anything. It's just swapping BlogPostItem with our
|
||||||
</Seo>
|
// own ChangelogItem. We don't want to apply the swizzled item to the actual
|
||||||
|
// blog.
|
||||||
<Link to={listPageLink}>← Back to index page</Link>
|
export default function ChangelogPage(props: Props): JSX.Element {
|
||||||
|
return (
|
||||||
<ChangelogItem
|
<HtmlClassNameProvider
|
||||||
frontMatter={frontMatter}
|
className={clsx(
|
||||||
assets={assets}
|
ThemeClassNames.wrapper.blogPages,
|
||||||
metadata={metadata}
|
ThemeClassNames.page.blogPostPage,
|
||||||
isBlogPostPage>
|
)}>
|
||||||
<BlogPostContents />
|
<ChangelogPageMetadata {...props} />
|
||||||
</ChangelogItem>
|
<ChangelogPageContent {...props} />
|
||||||
|
</HtmlClassNameProvider>
|
||||||
{(nextItem || prevItem) && (
|
|
||||||
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
|
|
||||||
)}
|
|
||||||
</BlogLayout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue