mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-23 14:06:59 +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 {
|
||||
readonly children?: ReactNode;
|
||||
readonly title?: string;
|
||||
readonly description?: string;
|
||||
}
|
||||
export default function Layout(props: Props): JSX.Element;
|
||||
}
|
||||
|
@ -117,6 +115,10 @@ declare module '@theme/Root' {
|
|||
export default function Root({children}: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/SiteMetadata' {
|
||||
export default function SiteMetadata(): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@docusaurus/constants' {
|
||||
export const DEFAULT_PLUGIN_ID: 'default';
|
||||
}
|
||||
|
|
|
@ -364,31 +364,17 @@ declare module '@theme/Layout' {
|
|||
|
||||
export interface Props {
|
||||
readonly children?: ReactNode;
|
||||
readonly title?: string;
|
||||
readonly noFooter?: boolean;
|
||||
readonly description?: string;
|
||||
readonly image?: string;
|
||||
readonly keywords?: string | string[];
|
||||
readonly permalink?: string;
|
||||
readonly wrapperClassName?: string;
|
||||
readonly pageClassName?: string;
|
||||
readonly searchMetadata?: {
|
||||
readonly version?: string;
|
||||
readonly tag?: string;
|
||||
};
|
||||
|
||||
// Not really layout-related, but kept for convenience/retro-compatibility
|
||||
readonly title?: string;
|
||||
readonly description?: string;
|
||||
}
|
||||
|
||||
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' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
|
@ -480,7 +466,7 @@ declare module '@theme/Navbar/Content' {
|
|||
|
||||
declare module '@theme/Navbar/Layout' {
|
||||
export interface Props {
|
||||
children: React.ReactNode;
|
||||
readonly children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function NavbarLayout(props: Props): JSX.Element;
|
||||
|
@ -927,17 +913,3 @@ declare module '@theme/prism-include-languages' {
|
|||
PrismObject: typeof PrismNamespace,
|
||||
): 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 type {ArchiveBlogPost, Props} from '@theme/BlogArchivePage';
|
||||
import {translate} from '@docusaurus/Translate';
|
||||
import {PageMetadata} from '@docusaurus/theme-common';
|
||||
|
||||
type YearProp = {
|
||||
year: string;
|
||||
|
@ -75,14 +76,17 @@ export default function BlogArchive({archive}: Props): JSX.Element {
|
|||
});
|
||||
const years = listPostsByYears(archive.blogPosts);
|
||||
return (
|
||||
<Layout title={title} description={description}>
|
||||
<header className="hero hero--primary">
|
||||
<div className="container">
|
||||
<h1 className="hero__title">{title}</h1>
|
||||
<p className="hero__subtitle">{description}</p>
|
||||
</div>
|
||||
</header>
|
||||
<main>{years.length > 0 && <YearsSection years={years} />}</main>
|
||||
</Layout>
|
||||
<>
|
||||
<PageMetadata title={title} description={description} />
|
||||
<Layout>
|
||||
<header className="hero hero--primary">
|
||||
<div className="container">
|
||||
<h1 className="hero__title">{title}</h1>
|
||||
<p className="hero__subtitle">{description}</p>
|
||||
</div>
|
||||
</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 BlogListPaginator from '@theme/BlogListPaginator';
|
||||
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 {
|
||||
const {metadata, items, sidebar} = props;
|
||||
function BlogListPageMetadata(props: Props): JSX.Element {
|
||||
const {metadata} = props;
|
||||
const {
|
||||
siteConfig: {title: siteTitle},
|
||||
} = useDocusaurusContext();
|
||||
const {blogDescription, blogTitle, permalink} = metadata;
|
||||
const isBlogOnlyMode = permalink === '/';
|
||||
const title = isBlogOnlyMode ? siteTitle : blogTitle;
|
||||
|
||||
return (
|
||||
<BlogLayout
|
||||
title={title}
|
||||
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}>
|
||||
<>
|
||||
<PageMetadata title={title} description={blogDescription} />
|
||||
<SearchMetadata tag="blog_posts_list" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function BlogListPageContent(props: Props): JSX.Element {
|
||||
const {metadata, items, sidebar} = props;
|
||||
return (
|
||||
<BlogLayout sidebar={sidebar}>
|
||||
{items.map(({content: BlogPostContent}) => (
|
||||
<BlogPostItem
|
||||
key={BlogPostContent.metadata.permalink}
|
||||
|
@ -48,3 +54,16 @@ export default function BlogListPage(props: Props): JSX.Element {
|
|||
</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 Seo from '@theme/Seo';
|
||||
import BlogLayout from '@theme/BlogLayout';
|
||||
import BlogPostItem from '@theme/BlogPostItem';
|
||||
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
||||
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 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 {assets, metadata} = BlogPostContents;
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
nextItem,
|
||||
prevItem,
|
||||
date,
|
||||
tags,
|
||||
authors,
|
||||
frontMatter,
|
||||
} = metadata;
|
||||
const {nextItem, prevItem, frontMatter} = metadata;
|
||||
const {
|
||||
hide_table_of_contents: hideTableOfContents,
|
||||
keywords,
|
||||
toc_min_heading_level: tocMinHeadingLevel,
|
||||
toc_max_heading_level: tocMaxHeadingLevel,
|
||||
} = frontMatter;
|
||||
|
||||
const image = assets.image ?? frontMatter.image;
|
||||
|
||||
return (
|
||||
<BlogLayout
|
||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
||||
pageClassName={ThemeClassNames.page.blogPostPage}
|
||||
sidebar={sidebar}
|
||||
toc={
|
||||
!hideTableOfContents &&
|
||||
|
@ -52,35 +75,6 @@ export default function BlogPostPage(props: Props): JSX.Element {
|
|||
/>
|
||||
) : 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
|
||||
frontMatter={frontMatter}
|
||||
assets={assets}
|
||||
|
@ -95,3 +89,16 @@ export default function BlogPostPage(props: Props): JSX.Element {
|
|||
</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 type {Props} from '@theme/BlogTagsListPage';
|
||||
import {
|
||||
PageMetadata,
|
||||
HtmlClassNameProvider,
|
||||
ThemeClassNames,
|
||||
translateTagsPageTitle,
|
||||
} from '@docusaurus/theme-common';
|
||||
import SearchMetadata from '../SearchMetadata';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export default function BlogTagsListPage(props: Props): JSX.Element {
|
||||
const {tags, sidebar} = props;
|
||||
const title = translateTagsPageTitle();
|
||||
return (
|
||||
<BlogLayout
|
||||
title={title}
|
||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
||||
pageClassName={ThemeClassNames.page.blogTagsListPage}
|
||||
searchMetadata={{
|
||||
// assign unique search tag to exclude this page from search results!
|
||||
tag: 'blog_tags_list',
|
||||
}}
|
||||
sidebar={sidebar}>
|
||||
<h1>{title}</h1>
|
||||
<TagsListByLetter tags={Object.values(tags)} />
|
||||
</BlogLayout>
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.blogPages,
|
||||
ThemeClassNames.page.blogTagsListPage,
|
||||
)}>
|
||||
<PageMetadata title={title} />
|
||||
<SearchMetadata tag="blog_tags_list" />
|
||||
<BlogLayout sidebar={sidebar}>
|
||||
<h1>{title}</h1>
|
||||
<TagsListByLetter tags={Object.values(tags)} />
|
||||
</BlogLayout>
|
||||
</HtmlClassNameProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,8 +12,15 @@ import BlogLayout from '@theme/BlogLayout';
|
|||
import BlogPostItem from '@theme/BlogPostItem';
|
||||
import type {Props} from '@theme/BlogTagsPostsPage';
|
||||
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 SearchMetadata from '@theme/SearchMetadata';
|
||||
import clsx from 'clsx';
|
||||
|
||||
// Very simple pluralization: probably good enough for now
|
||||
function useBlogPostsPlural() {
|
||||
|
@ -47,38 +54,38 @@ export default function BlogTagsPostsPage(props: Props): JSX.Element {
|
|||
);
|
||||
|
||||
return (
|
||||
<BlogLayout
|
||||
title={title}
|
||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
||||
pageClassName={ThemeClassNames.page.blogTagPostListPage}
|
||||
searchMetadata={{
|
||||
// assign unique search tag to exclude this page from search results!
|
||||
tag: 'blog_tags_posts',
|
||||
}}
|
||||
sidebar={sidebar}>
|
||||
<header className="margin-bottom--xl">
|
||||
<h1>{title}</h1>
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.blogPages,
|
||||
ThemeClassNames.page.blogTagPostListPage,
|
||||
)}>
|
||||
<PageMetadata title={title} />
|
||||
<SearchMetadata tag="blog_tags_posts" />
|
||||
<BlogLayout sidebar={sidebar}>
|
||||
<header className="margin-bottom--xl">
|
||||
<h1>{title}</h1>
|
||||
|
||||
<Link href={allTagsPath}>
|
||||
<Translate
|
||||
id="theme.tags.tagsPageLink"
|
||||
description="The label of the link targeting the tag list page">
|
||||
View All Tags
|
||||
</Translate>
|
||||
</Link>
|
||||
</header>
|
||||
<Link href={allTagsPath}>
|
||||
<Translate
|
||||
id="theme.tags.tagsPageLink"
|
||||
description="The label of the link targeting the tag list page">
|
||||
View All Tags
|
||||
</Translate>
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
{items.map(({content: BlogPostContent}) => (
|
||||
<BlogPostItem
|
||||
key={BlogPostContent.metadata.permalink}
|
||||
frontMatter={BlogPostContent.frontMatter}
|
||||
assets={BlogPostContent.assets}
|
||||
metadata={BlogPostContent.metadata}
|
||||
truncated>
|
||||
<BlogPostContent />
|
||||
</BlogPostItem>
|
||||
))}
|
||||
<BlogListPaginator metadata={listMetadata} />
|
||||
</BlogLayout>
|
||||
{items.map(({content: BlogPostContent}) => (
|
||||
<BlogPostItem
|
||||
key={BlogPostContent.metadata.permalink}
|
||||
frontMatter={BlogPostContent.frontMatter}
|
||||
assets={BlogPostContent.assets}
|
||||
metadata={BlogPostContent.metadata}
|
||||
truncated>
|
||||
<BlogPostContent />
|
||||
</BlogPostItem>
|
||||
))}
|
||||
<BlogListPaginator metadata={listMetadata} />
|
||||
</BlogLayout>
|
||||
</HtmlClassNameProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
||||
import {
|
||||
PageMetadata,
|
||||
useCurrentSidebarCategory,
|
||||
} from '@docusaurus/theme-common';
|
||||
import type {Props} from '@theme/DocCategoryGeneratedIndexPage';
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocPaginator from '@theme/DocPaginator';
|
||||
import Seo from '@theme/Seo';
|
||||
import DocVersionBanner from '@theme/DocVersionBanner';
|
||||
import DocVersionBadge from '@theme/DocVersionBadge';
|
||||
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
||||
|
@ -19,13 +21,27 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
|
|||
|
||||
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,
|
||||
}: Props): JSX.Element {
|
||||
const category = useCurrentSidebarCategory();
|
||||
return (
|
||||
<>
|
||||
<Seo
|
||||
<PageMetadata
|
||||
title={categoryGeneratedIndex.title}
|
||||
description={categoryGeneratedIndex.description}
|
||||
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 DocVersionBanner from '@theme/DocVersionBanner';
|
||||
import DocVersionBadge from '@theme/DocVersionBadge';
|
||||
import Seo from '@theme/Seo';
|
||||
import type {Props} from '@theme/DocItem';
|
||||
import DocItemFooter from '@theme/DocItemFooter';
|
||||
import TOC from '@theme/TOC';
|
||||
|
@ -18,6 +17,7 @@ import TOCCollapsible from '@theme/TOCCollapsible';
|
|||
import Heading from '@theme/Heading';
|
||||
import styles from './styles.module.css';
|
||||
import {
|
||||
PageMetadata,
|
||||
HtmlClassNameProvider,
|
||||
ThemeClassNames,
|
||||
useWindowSize,
|
||||
|
@ -25,18 +25,26 @@ import {
|
|||
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
||||
import MDXContent from '@theme/MDXContent';
|
||||
|
||||
export default function DocItem(props: Props): JSX.Element {
|
||||
function DocItemMetadata(props: Props): JSX.Element {
|
||||
const {content: DocContent} = props;
|
||||
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 {
|
||||
keywords,
|
||||
hide_title: hideTitle,
|
||||
hide_table_of_contents: hideTableOfContents,
|
||||
toc_min_heading_level: tocMinHeadingLevel,
|
||||
toc_max_heading_level: tocMaxHeadingLevel,
|
||||
} = frontMatter;
|
||||
const {description, title} = metadata;
|
||||
const image = assets.image ?? frontMatter.image;
|
||||
const {title} = metadata;
|
||||
|
||||
// We only add a title if:
|
||||
// - 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');
|
||||
|
||||
return (
|
||||
<HtmlClassNameProvider className={`docs-doc-id-${metadata.unversionedId}`}>
|
||||
<Seo {...{title, description, keywords, image}} />
|
||||
<div className="row">
|
||||
<div className={clsx('col', !hideTableOfContents && styles.docItemCol)}>
|
||||
<DocVersionBanner />
|
||||
<div className={styles.docItemContainer}>
|
||||
<article>
|
||||
<DocBreadcrumbs />
|
||||
<DocVersionBadge />
|
||||
|
||||
<div className="row">
|
||||
<div className={clsx('col', !hideTableOfContents && styles.docItemCol)}>
|
||||
<DocVersionBanner />
|
||||
<div className={styles.docItemContainer}>
|
||||
<article>
|
||||
<DocBreadcrumbs />
|
||||
<DocVersionBadge />
|
||||
{canRenderTOC && (
|
||||
<TOCCollapsible
|
||||
toc={DocContent.toc}
|
||||
minHeadingLevel={tocMinHeadingLevel}
|
||||
maxHeadingLevel={tocMaxHeadingLevel}
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docTocMobile,
|
||||
styles.tocMobile,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canRenderTOC && (
|
||||
<TOCCollapsible
|
||||
toc={DocContent.toc}
|
||||
minHeadingLevel={tocMinHeadingLevel}
|
||||
maxHeadingLevel={tocMaxHeadingLevel}
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docTocMobile,
|
||||
styles.tocMobile,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
|
||||
{/*
|
||||
<div className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
|
||||
{/*
|
||||
Title can be declared inside md content or declared through
|
||||
front matter and added manually. To make both cases consistent,
|
||||
the added title is added under the same div.markdown block
|
||||
See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
|
||||
*/}
|
||||
{shouldAddTitle && (
|
||||
<header>
|
||||
<Heading as="h1">{title}</Heading>
|
||||
</header>
|
||||
)}
|
||||
<MDXContent>
|
||||
<DocContent />
|
||||
</MDXContent>
|
||||
</div>
|
||||
{shouldAddTitle && (
|
||||
<header>
|
||||
<Heading as="h1">{title}</Heading>
|
||||
</header>
|
||||
)}
|
||||
<MDXContent>
|
||||
<DocContent />
|
||||
</MDXContent>
|
||||
</div>
|
||||
|
||||
<DocItemFooter {...props} />
|
||||
</article>
|
||||
<DocItemFooter {...props} />
|
||||
</article>
|
||||
|
||||
<DocPaginator previous={metadata.previous} next={metadata.next} />
|
||||
</div>
|
||||
<DocPaginator previous={metadata.previous} next={metadata.next} />
|
||||
</div>
|
||||
{renderTocDesktop && (
|
||||
<div className="col col--3">
|
||||
<TOC
|
||||
toc={DocContent.toc}
|
||||
minHeadingLevel={tocMinHeadingLevel}
|
||||
maxHeadingLevel={tocMaxHeadingLevel}
|
||||
className={ThemeClassNames.docs.docTocDesktop}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
useDocsSidebar,
|
||||
DocsVersionProvider,
|
||||
} from '@docusaurus/theme-common';
|
||||
import SearchMetadata from '@theme/SearchMetadata';
|
||||
|
||||
type DocPageContentProps = {
|
||||
readonly currentDocRoute: DocumentRoute;
|
||||
|
@ -56,87 +57,89 @@ function DocPageContent({
|
|||
}, [hiddenSidebar]);
|
||||
|
||||
return (
|
||||
<Layout
|
||||
wrapperClassName={ThemeClassNames.wrapper.docsPages}
|
||||
pageClassName={ThemeClassNames.page.docsDocPage}
|
||||
searchMetadata={{
|
||||
version,
|
||||
tag: docVersionSearchTag(pluginId, version),
|
||||
}}>
|
||||
<div className={styles.docPage}>
|
||||
<BackToTopButton />
|
||||
<>
|
||||
<SearchMetadata
|
||||
version={version}
|
||||
tag={docVersionSearchTag(pluginId, version)}
|
||||
/>
|
||||
<Layout>
|
||||
<div className={styles.docPage}>
|
||||
<BackToTopButton />
|
||||
|
||||
{sidebar && (
|
||||
<aside
|
||||
{sidebar && (
|
||||
<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(
|
||||
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(
|
||||
styles.docMainContainer,
|
||||
(hiddenSidebarContainer || !sidebar) &&
|
||||
styles.docMainContainerEnhanced,
|
||||
)}>
|
||||
<div
|
||||
className={clsx(
|
||||
'container padding-top--md padding-bottom--lg',
|
||||
styles.docItemWrapper,
|
||||
hiddenSidebarContainer && styles.docItemWrapperEnhanced,
|
||||
styles.docMainContainer,
|
||||
(hiddenSidebarContainer || !sidebar) &&
|
||||
styles.docMainContainerEnhanced,
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</Layout>
|
||||
<div
|
||||
className={clsx(
|
||||
'container padding-top--md padding-bottom--lg',
|
||||
styles.docItemWrapper,
|
||||
hiddenSidebarContainer && styles.docItemWrapperEnhanced,
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -161,7 +164,12 @@ export default function DocPage(props: Props): JSX.Element {
|
|||
: null;
|
||||
|
||||
return (
|
||||
<HtmlClassNameProvider className={versionMetadata.className}>
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.docsPages,
|
||||
ThemeClassNames.page.docsDocPage,
|
||||
versionMetadata.className,
|
||||
)}>
|
||||
<DocsVersionProvider version={versionMetadata}>
|
||||
<DocsSidebarProvider sidebar={sidebar ?? null}>
|
||||
<DocPageContent
|
||||
|
|
|
@ -10,10 +10,6 @@
|
|||
--doc-sidebar-hidden-width: 30px;
|
||||
}
|
||||
|
||||
:global(.docs-wrapper) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.docPage,
|
||||
.docMainContainer {
|
||||
display: flex;
|
||||
|
|
|
@ -9,10 +9,17 @@ import React from 'react';
|
|||
|
||||
import Layout from '@theme/Layout';
|
||||
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 Translate, {translate} from '@docusaurus/Translate';
|
||||
import type {Props} from '@theme/DocTagDocListPage';
|
||||
import SearchMetadata from '@theme/SearchMetadata';
|
||||
import clsx from 'clsx';
|
||||
|
||||
// Very simple pluralization: probably good enough for now
|
||||
function useNDocsTaggedPlural() {
|
||||
|
@ -55,35 +62,36 @@ export default function DocTagDocListPage({tag}: Props): JSX.Element {
|
|||
);
|
||||
|
||||
return (
|
||||
<Layout
|
||||
title={title}
|
||||
wrapperClassName={ThemeClassNames.wrapper.docsPages}
|
||||
pageClassName={ThemeClassNames.page.docsTagDocListPage}
|
||||
searchMetadata={{
|
||||
// assign unique search tag to exclude this page from search results!
|
||||
tag: 'doc_tag_doc_list',
|
||||
}}>
|
||||
<div className="container margin-vert--lg">
|
||||
<div className="row">
|
||||
<main className="col col--8 col--offset-2">
|
||||
<header className="margin-bottom--xl">
|
||||
<h1>{title}</h1>
|
||||
<Link href={tag.allTagsPath}>
|
||||
<Translate
|
||||
id="theme.tags.tagsPageLink"
|
||||
description="The label of the link targeting the tag list page">
|
||||
View All Tags
|
||||
</Translate>
|
||||
</Link>
|
||||
</header>
|
||||
<section className="margin-vert--lg">
|
||||
{tag.docs.map((doc) => (
|
||||
<DocItem key={doc.id} doc={doc} />
|
||||
))}
|
||||
</section>
|
||||
</main>
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.docsPages,
|
||||
ThemeClassNames.page.docsTagDocListPage,
|
||||
)}>
|
||||
<PageMetadata title={title} />
|
||||
<SearchMetadata tag="doc_tag_doc_list" />
|
||||
<Layout>
|
||||
<div className="container margin-vert--lg">
|
||||
<div className="row">
|
||||
<main className="col col--8 col--offset-2">
|
||||
<header className="margin-bottom--xl">
|
||||
<h1>{title}</h1>
|
||||
<Link href={tag.allTagsPath}>
|
||||
<Translate
|
||||
id="theme.tags.tagsPageLink"
|
||||
description="The label of the link targeting the tag list page">
|
||||
View All Tags
|
||||
</Translate>
|
||||
</Link>
|
||||
</header>
|
||||
<section className="margin-vert--lg">
|
||||
{tag.docs.map((doc) => (
|
||||
<DocItem key={doc.id} doc={doc} />
|
||||
))}
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</HtmlClassNameProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,31 +9,36 @@ import React from 'react';
|
|||
|
||||
import Layout from '@theme/Layout';
|
||||
import {
|
||||
PageMetadata,
|
||||
HtmlClassNameProvider,
|
||||
ThemeClassNames,
|
||||
translateTagsPageTitle,
|
||||
} from '@docusaurus/theme-common';
|
||||
import TagsListByLetter from '@theme/TagsListByLetter';
|
||||
import type {Props} from '@theme/DocTagsListPage';
|
||||
import SearchMetadata from '@theme/SearchMetadata';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export default function DocTagsListPage({tags}: Props): JSX.Element {
|
||||
const title = translateTagsPageTitle();
|
||||
return (
|
||||
<Layout
|
||||
title={title}
|
||||
wrapperClassName={ThemeClassNames.wrapper.docsPages}
|
||||
pageClassName={ThemeClassNames.page.docsTagsListPage}
|
||||
searchMetadata={{
|
||||
// assign unique search tag to exclude this page from search results!
|
||||
tag: 'doc_tags_list',
|
||||
}}>
|
||||
<div className="container margin-vert--lg">
|
||||
<div className="row">
|
||||
<main className="col col--8 col--offset-2">
|
||||
<h1>{title}</h1>
|
||||
<TagsListByLetter tags={tags} />
|
||||
</main>
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.docsPages,
|
||||
ThemeClassNames.page.docsTagsListPage,
|
||||
)}>
|
||||
<PageMetadata title={title} />
|
||||
<SearchMetadata tag="doc_tags_list" />
|
||||
<Layout>
|
||||
<div className="container margin-vert--lg">
|
||||
<div className="row">
|
||||
<main className="col col--8 col--offset-2">
|
||||
<h1>{title}</h1>
|
||||
<TagsListByLetter tags={tags} />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</HtmlClassNameProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,20 +13,30 @@ import AnnouncementBar from '@theme/AnnouncementBar';
|
|||
import Navbar from '@theme/Navbar';
|
||||
import Footer from '@theme/Footer';
|
||||
import LayoutProviders from '@theme/LayoutProviders';
|
||||
import LayoutHead from '@theme/LayoutHead';
|
||||
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 './styles.css';
|
||||
|
||||
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();
|
||||
|
||||
return (
|
||||
<LayoutProviders>
|
||||
<LayoutHead {...props} />
|
||||
<PageMetadata title={title} description={description} />
|
||||
|
||||
<SkipToContent />
|
||||
|
||||
|
@ -34,12 +44,7 @@ export default function Layout(props: Props): JSX.Element {
|
|||
|
||||
<Navbar />
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.main,
|
||||
wrapperClassName,
|
||||
pageClassName,
|
||||
)}>
|
||||
<div className={clsx(ThemeClassNames.wrapper.main, wrapperClassName)}>
|
||||
<ErrorBoundary fallback={ErrorPageContent}>{children}</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -11,43 +11,49 @@ import Layout from '@theme/Layout';
|
|||
import MDXContent from '@theme/MDXContent';
|
||||
import type {Props} from '@theme/MDXPage';
|
||||
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';
|
||||
|
||||
export default function MDXPage(props: Props): JSX.Element {
|
||||
const {content: MDXPageContent} = props;
|
||||
const {
|
||||
metadata: {title, description, permalink, frontMatter},
|
||||
metadata: {title, description, frontMatter},
|
||||
} = MDXPageContent;
|
||||
const {wrapperClassName, hide_table_of_contents: hideTableOfContents} =
|
||||
frontMatter;
|
||||
|
||||
return (
|
||||
<Layout
|
||||
title={title}
|
||||
description={description}
|
||||
permalink={permalink}
|
||||
wrapperClassName={wrapperClassName ?? ThemeClassNames.wrapper.mdxPages}
|
||||
pageClassName={ThemeClassNames.page.mdxPage}>
|
||||
<main className="container container--fluid margin-vert--lg">
|
||||
<div className={clsx('row', styles.mdxPageWrapper)}>
|
||||
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
||||
<MDXContent>
|
||||
<MDXPageContent />
|
||||
</MDXContent>
|
||||
</div>
|
||||
{!hideTableOfContents && MDXPageContent.toc && (
|
||||
<div className="col col--2">
|
||||
<TOC
|
||||
toc={MDXPageContent.toc}
|
||||
minHeadingLevel={frontMatter.toc_min_heading_level}
|
||||
maxHeadingLevel={frontMatter.toc_max_heading_level}
|
||||
/>
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
wrapperClassName ?? ThemeClassNames.wrapper.mdxPages,
|
||||
ThemeClassNames.page.mdxPage,
|
||||
)}>
|
||||
<PageMetadata title={title} description={description} />
|
||||
<Layout>
|
||||
<main className="container container--fluid margin-vert--lg">
|
||||
<div className={clsx('row', styles.mdxPageWrapper)}>
|
||||
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
||||
<MDXContent>
|
||||
<MDXPageContent />
|
||||
</MDXContent>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
{!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>
|
||||
</main>
|
||||
</Layout>
|
||||
</HtmlClassNameProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,42 +8,47 @@
|
|||
import React from 'react';
|
||||
import Layout from '@theme/Layout';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
import {PageMetadata} from '@docusaurus/theme-common';
|
||||
|
||||
export default function NotFound(): JSX.Element {
|
||||
return (
|
||||
<Layout
|
||||
title={translate({
|
||||
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">
|
||||
<h1 className="hero__title">
|
||||
<Translate
|
||||
id="theme.NotFound.title"
|
||||
description="The title of the 404 page">
|
||||
Page Not Found
|
||||
</Translate>
|
||||
</h1>
|
||||
<p>
|
||||
<Translate
|
||||
id="theme.NotFound.p1"
|
||||
description="The first paragraph of the 404 page">
|
||||
We could not find what you were looking for.
|
||||
</Translate>
|
||||
</p>
|
||||
<p>
|
||||
<Translate
|
||||
id="theme.NotFound.p2"
|
||||
description="The 2nd paragraph of the 404 page">
|
||||
Please contact the owner of the site that linked you to the
|
||||
original URL and let them know their link is broken.
|
||||
</Translate>
|
||||
</p>
|
||||
<>
|
||||
<PageMetadata
|
||||
title={translate({
|
||||
id: 'theme.NotFound.title',
|
||||
message: 'Page Not Found',
|
||||
})}
|
||||
/>
|
||||
<Layout>
|
||||
<main className="container margin-vert--xl">
|
||||
<div className="row">
|
||||
<div className="col col--6 col--offset-3">
|
||||
<h1 className="hero__title">
|
||||
<Translate
|
||||
id="theme.NotFound.title"
|
||||
description="The title of the 404 page">
|
||||
Page Not Found
|
||||
</Translate>
|
||||
</h1>
|
||||
<p>
|
||||
<Translate
|
||||
id="theme.NotFound.p1"
|
||||
description="The first paragraph of the 404 page">
|
||||
We could not find what you were looking for.
|
||||
</Translate>
|
||||
</p>
|
||||
<p>
|
||||
<Translate
|
||||
id="theme.NotFound.p2"
|
||||
description="The 2nd paragraph of the 404 page">
|
||||
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>
|
||||
</main>
|
||||
</Layout>
|
||||
</main>
|
||||
</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 useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import type {Props} from '@theme/Layout';
|
||||
import SearchMetadata from '@theme/SearchMetadata';
|
||||
import Seo from '@theme/Seo';
|
||||
import {
|
||||
PageMetadata,
|
||||
DEFAULT_SEARCH_TAG,
|
||||
useTitleFormatter,
|
||||
useAlternatePageUtils,
|
||||
useThemeConfig,
|
||||
keyboardFocusedClassName,
|
||||
} from '@docusaurus/theme-common';
|
||||
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://github.com/facebook/docusaurus/issues/3317
|
||||
function AlternateLangHeaders(): JSX.Element {
|
||||
|
@ -66,6 +65,7 @@ function useDefaultCanonicalUrl() {
|
|||
return siteUrl + useBaseUrl(pathname);
|
||||
}
|
||||
|
||||
// TODO move to SiteMetadataDefaults or theme-common ?
|
||||
function CanonicalUrlHeaders({permalink}: {permalink?: string}) {
|
||||
const {
|
||||
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 {
|
||||
siteConfig: {favicon},
|
||||
i18n: {currentLocale, localeConfigs},
|
||||
i18n: {currentLocale},
|
||||
} = useDocusaurusContext();
|
||||
|
||||
// TODO maybe move these 2 themeConfig to siteConfig?
|
||||
// These seems useful for other themes as well
|
||||
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 (
|
||||
<>
|
||||
<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" />
|
||||
{/* The keyboard focus class name need to be applied when SSR so links
|
||||
are outlined when JS is disabled */}
|
||||
<body className={keyboardFocusedClassName} />
|
||||
</Head>
|
||||
|
||||
{/* image can override the default image */}
|
||||
{defaultImage && <Seo image={defaultImage} />}
|
||||
{image && <Seo image={image} />}
|
||||
|
||||
<Seo description={description} keywords={keywords} />
|
||||
{defaultImage && <PageMetadata image={defaultImage} />}
|
||||
|
||||
<CanonicalUrlHeaders />
|
||||
|
||||
<AlternateLangHeaders />
|
||||
|
||||
<SearchMetadata
|
||||
tag={DEFAULT_SEARCH_TAG}
|
||||
locale={currentLocale}
|
||||
{...searchMetadata}
|
||||
/>
|
||||
<SearchMetadata tag={DEFAULT_SEARCH_TAG} locale={currentLocale} />
|
||||
|
||||
<Head
|
||||
// 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 {
|
||||
PageMetadata,
|
||||
HtmlClassNameProvider,
|
||||
PluginHtmlClassNameProvider,
|
||||
} from './utils/metadataUtilsTemp';
|
||||
} from './utils/metadataUtils';
|
||||
|
||||
export {
|
||||
useColorMode,
|
||||
|
|
|
@ -9,6 +9,53 @@ import React, {type ReactNode} from 'react';
|
|||
import Head from '@docusaurus/Head';
|
||||
import clsx from 'clsx';
|
||||
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);
|
||||
|
|
@ -17,6 +17,7 @@ import Head from '@docusaurus/Head';
|
|||
import Link from '@docusaurus/Link';
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import {
|
||||
HtmlClassNameProvider,
|
||||
useTitleFormatter,
|
||||
usePluralForm,
|
||||
isRegexpStringMatch,
|
||||
|
@ -149,7 +150,7 @@ type ResultDispatcher =
|
|||
| {type: 'update'; value: ResultDispatcherState}
|
||||
| {type: 'advance'; value?: undefined};
|
||||
|
||||
export default function SearchPage(): JSX.Element {
|
||||
function SearchPageContent(): JSX.Element {
|
||||
const {
|
||||
siteConfig: {themeConfig},
|
||||
i18n: {currentLocale},
|
||||
|
@ -356,7 +357,7 @@ export default function SearchPage(): JSX.Element {
|
|||
}, [makeSearch, searchResultState.lastPage]);
|
||||
|
||||
return (
|
||||
<Layout wrapperClassName="search-page-wrapper">
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>{useTitleFormatter(getTitle())}</title>
|
||||
{/*
|
||||
|
@ -516,3 +517,11 @@ export default function SearchPage(): JSX.Element {
|
|||
</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 PendingNavigation from './PendingNavigation';
|
||||
import BaseUrlIssueBanner from './baseUrlIssueBanner/BaseUrlIssueBanner';
|
||||
import SiteMetadataDefaults from './SiteMetadataDefaults';
|
||||
import Root from '@theme/Root';
|
||||
import SiteMetadata from '@theme/SiteMetadata';
|
||||
|
||||
import './client-lifecycles-dispatcher';
|
||||
|
||||
|
@ -27,6 +29,8 @@ export default function App(): JSX.Element {
|
|||
<DocusaurusContextProvider>
|
||||
<BrowserContextProvider>
|
||||
<Root>
|
||||
<SiteMetadataDefaults />
|
||||
<SiteMetadata />
|
||||
<BaseUrlIssueBanner />
|
||||
<PendingNavigation routes={routes} delay={1000}>
|
||||
{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 ErrorBoundary from '@docusaurus/ErrorBoundary';
|
||||
import type {Props} from '@theme/Error';
|
||||
import Head from '@docusaurus/Head';
|
||||
|
||||
function ErrorDisplay({error, tryAgain}: Props): JSX.Element {
|
||||
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
|
||||
// captured in this extra error boundary
|
||||
fallback={() => <ErrorDisplay error={error} tryAgain={tryAgain} />}>
|
||||
<Layout title="Page Error">
|
||||
<Head>
|
||||
<title>Page Error</title>
|
||||
</Head>
|
||||
<Layout>
|
||||
<ErrorDisplay error={error} tryAgain={tryAgain} />
|
||||
</Layout>
|
||||
</ErrorBoundary>
|
||||
|
|
|
@ -6,31 +6,8 @@
|
|||
*/
|
||||
|
||||
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';
|
||||
|
||||
export default function Layout({
|
||||
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}
|
||||
</>
|
||||
);
|
||||
export default function Layout({children}: Props): JSX.Element {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
|
|
@ -7,20 +7,26 @@
|
|||
|
||||
import React from 'react';
|
||||
import Layout from '@theme/Layout';
|
||||
import Head from '@docusaurus/Head';
|
||||
|
||||
export default function NotFound(): JSX.Element {
|
||||
return (
|
||||
<Layout title="Page Not Found">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '50vh',
|
||||
fontSize: '20px',
|
||||
}}>
|
||||
<h1>Oops, page not found </h1>
|
||||
</div>
|
||||
</Layout>
|
||||
<>
|
||||
<Head>
|
||||
<title>Page Not Found</title>
|
||||
</Head>
|
||||
<Layout>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '50vh',
|
||||
fontSize: '20px',
|
||||
}}>
|
||||
<h1>Oops, page not found </h1>
|
||||
</div>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 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
|
||||
// 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
|
||||
//
|
||||
// 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}</>;
|
||||
}
|
||||
|
|
|
@ -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/PluginThemeComponentOverriddenByUser": "pluginThemeFolder/PluginThemeComponentOverriddenByUser.js",
|
||||
"@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/Error": "../../../../client/theme-fallback/Error/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/PluginThemeComponentOverriddenByUser": "src/theme/PluginThemeComponentOverriddenByUser.js",
|
||||
"@theme/Root": "../../../../client/theme-fallback/Root/index.tsx",
|
||||
"@theme/SiteMetadata": "../../../../client/theme-fallback/SiteMetadata/index.tsx",
|
||||
"@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js",
|
||||
"@theme/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.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 Layout from '@theme/Layout';
|
||||
|
||||
function Hello() {
|
||||
export default function Hello() {
|
||||
return (
|
||||
<Layout title="Hello">
|
||||
<Layout title="Hello" description="Hello React Page">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
@ -53,8 +53,6 @@ function Hello() {
|
|||
</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.
|
||||
|
|
|
@ -42,22 +42,6 @@ Similar to [global metadata](#global-metadata), Docusaurus also allows for the a
|
|||
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:
|
||||
|
||||
```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}
|
||||
|
||||
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],
|
||||
},
|
||||
theme: {
|
||||
customCss: [require.resolve('./src/css/custom.css')],
|
||||
customCss: [
|
||||
require.resolve('./src/css/custom.css'),
|
||||
require.resolve('./_dogfooding/dogfooding.css'),
|
||||
],
|
||||
},
|
||||
gtag: !isDeployPreview
|
||||
? {
|
||||
|
|
|
@ -166,10 +166,6 @@ div[class^='announcementBar_'] {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.red > a {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.screen-reader-only {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
|
|
|
@ -9,27 +9,35 @@ import React from 'react';
|
|||
import BlogLayout from '@theme/BlogLayout';
|
||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||
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 ChangelogItem from '@theme/ChangelogItem';
|
||||
|
||||
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 {blogDescription, blogTitle} = metadata;
|
||||
const {blogTitle} = metadata;
|
||||
|
||||
return (
|
||||
<BlogLayout
|
||||
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}>
|
||||
<BlogLayout sidebar={sidebar}>
|
||||
<header className="margin-bottom--lg">
|
||||
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
|
||||
<p>
|
||||
|
@ -88,3 +96,16 @@ export default function ChangelogList(props: Props): JSX.Element {
|
|||
</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 Seo from '@theme/Seo';
|
||||
import BlogLayout from '@theme/BlogLayout';
|
||||
import ChangelogItem from '@theme/ChangelogItem';
|
||||
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
||||
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 Link from '@docusaurus/Link';
|
||||
import clsx from 'clsx';
|
||||
|
||||
// This page doesn't change anything. It's just swapping BlogPostItem with our
|
||||
// own ChangelogItem. We don't want to apply the swizzled item to the actual
|
||||
// blog.
|
||||
export default function BlogPostPage(props: Props): JSX.Element {
|
||||
function ChangelogPageMetadata(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} />
|
||||
|
||||
{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 {assets, metadata} = BlogPostContents;
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
nextItem,
|
||||
prevItem,
|
||||
date,
|
||||
tags,
|
||||
authors,
|
||||
frontMatter,
|
||||
// @ts-expect-error: we injected this
|
||||
listPageLink,
|
||||
} = metadata;
|
||||
const {
|
||||
hide_table_of_contents: hideTableOfContents,
|
||||
keywords,
|
||||
toc_min_heading_level: tocMinHeadingLevel,
|
||||
toc_max_heading_level: tocMaxHeadingLevel,
|
||||
} = frontMatter;
|
||||
|
||||
const image = assets.image ?? frontMatter.image;
|
||||
|
||||
return (
|
||||
<BlogLayout
|
||||
wrapperClassName={ThemeClassNames.wrapper.blogPages}
|
||||
pageClassName={ThemeClassNames.page.blogPostPage}
|
||||
sidebar={sidebar}
|
||||
toc={
|
||||
!hideTableOfContents &&
|
||||
BlogPostContents.toc &&
|
||||
BlogPostContents.toc.length > 0 ? (
|
||||
<TOC
|
||||
toc={BlogPostContents.toc}
|
||||
minHeadingLevel={tocMinHeadingLevel}
|
||||
maxHeadingLevel={tocMaxHeadingLevel}
|
||||
/>
|
||||
) : undefined
|
||||
}>
|
||||
<Seo
|
||||
title={title}
|
||||
description={description}
|
||||
keywords={keywords}
|
||||
image={image}>
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="article:published_time" content={date} />
|
||||
<>
|
||||
<PageMetadata />
|
||||
<BlogLayout
|
||||
sidebar={sidebar}
|
||||
toc={
|
||||
!hideTableOfContents &&
|
||||
BlogPostContents.toc &&
|
||||
BlogPostContents.toc.length > 0 ? (
|
||||
<TOC
|
||||
toc={BlogPostContents.toc}
|
||||
minHeadingLevel={tocMinHeadingLevel}
|
||||
maxHeadingLevel={tocMaxHeadingLevel}
|
||||
/>
|
||||
) : undefined
|
||||
}>
|
||||
<Link to={listPageLink}>← Back to index page</Link>
|
||||
|
||||
{authors.some((author) => author.url) && (
|
||||
<meta
|
||||
property="article:author"
|
||||
content={authors
|
||||
.map((author) => author.url)
|
||||
.filter(Boolean)
|
||||
.join(',')}
|
||||
/>
|
||||
<ChangelogItem
|
||||
frontMatter={frontMatter}
|
||||
assets={assets}
|
||||
metadata={metadata}
|
||||
isBlogPostPage>
|
||||
<BlogPostContents />
|
||||
</ChangelogItem>
|
||||
|
||||
{(nextItem || prevItem) && (
|
||||
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
|
||||
)}
|
||||
{tags.length > 0 && (
|
||||
<meta
|
||||
property="article:tag"
|
||||
content={tags.map((tag) => tag.label).join(',')}
|
||||
/>
|
||||
)}
|
||||
</Seo>
|
||||
|
||||
<Link to={listPageLink}>← Back to index page</Link>
|
||||
|
||||
<ChangelogItem
|
||||
frontMatter={frontMatter}
|
||||
assets={assets}
|
||||
metadata={metadata}
|
||||
isBlogPostPage>
|
||||
<BlogPostContents />
|
||||
</ChangelogItem>
|
||||
|
||||
{(nextItem || prevItem) && (
|
||||
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
|
||||
)}
|
||||
</BlogLayout>
|
||||
</BlogLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// This page doesn't change anything. It's just swapping BlogPostItem with our
|
||||
// own ChangelogItem. We don't want to apply the swizzled item to the actual
|
||||
// blog.
|
||||
export default function ChangelogPage(props: Props): JSX.Element {
|
||||
return (
|
||||
<HtmlClassNameProvider
|
||||
className={clsx(
|
||||
ThemeClassNames.wrapper.blogPages,
|
||||
ThemeClassNames.page.blogPostPage,
|
||||
)}>
|
||||
<ChangelogPageMetadata {...props} />
|
||||
<ChangelogPageContent {...props} />
|
||||
</HtmlClassNameProvider>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue