From 74f653dd82d9db9f34dc28ecc0cf4340b4e309b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Fri, 18 Mar 2022 18:53:00 +0100 Subject: [PATCH] refactor(theme-{classic,common}): change how site/page/search metadata is handled (#6925) --- .../src/index.d.ts | 6 +- .../src/theme-classic.d.ts | 38 +--- .../src/theme/BlogArchivePage/index.tsx | 22 ++- .../src/theme/BlogListPage/index.tsx | 47 +++-- .../src/theme/BlogPostPage/index.tsx | 103 ++++++----- .../src/theme/BlogTagsListPage/index.tsx | 28 +-- .../src/theme/BlogTagsPostsPage/index.tsx | 71 ++++---- .../DocCategoryGeneratedIndexPage/index.tsx | 35 +++- .../src/theme/DocItem/index.tsx | 117 ++++++------ .../src/theme/DocPage/index.tsx | 166 +++++++++--------- .../src/theme/DocPage/styles.module.css | 4 - .../src/theme/DocTagDocListPage/index.tsx | 68 +++---- .../src/theme/DocTagsListPage/index.tsx | 37 ++-- .../src/theme/Layout/index.tsx | 25 +-- .../src/theme/MDXPage/index.tsx | 58 +++--- .../src/theme/NotFound.tsx | 71 ++++---- .../src/theme/Seo/index.tsx | 49 ------ .../{LayoutHead => SiteMetadata}/index.tsx | 36 ++-- packages/docusaurus-theme-common/src/index.ts | 3 +- ...etadataUtilsTemp.tsx => metadataUtils.tsx} | 47 +++++ .../src/theme/SearchPage/index.tsx | 13 +- packages/docusaurus/src/client/App.tsx | 4 + .../src/client/SiteMetadataDefaults.tsx | 27 +++ .../src/client/theme-fallback/Error/index.tsx | 6 +- .../client/theme-fallback/Layout/index.tsx | 27 +-- .../client/theme-fallback/NotFound/index.tsx | 30 ++-- .../src/client/theme-fallback/Root/index.tsx | 5 +- .../theme-fallback/SiteMetadata/index.tsx | 11 ++ .../__tests__/__snapshots__/base.test.ts.snap | 2 + website/_dogfooding/dogfooding.css | 22 +++ website/docs/guides/creating-pages.md | 6 +- website/docs/seo.md | 41 +++-- website/docusaurus.config.js | 5 +- website/src/css/custom.css | 4 - .../changelog/theme/ChangelogList/index.tsx | 47 +++-- .../changelog/theme/ChangelogPage/index.tsx | 152 +++++++++------- 36 files changed, 808 insertions(+), 625 deletions(-) delete mode 100644 packages/docusaurus-theme-classic/src/theme/Seo/index.tsx rename packages/docusaurus-theme-classic/src/theme/{LayoutHead => SiteMetadata}/index.tsx (76%) rename packages/docusaurus-theme-common/src/utils/{metadataUtilsTemp.tsx => metadataUtils.tsx} (54%) create mode 100644 packages/docusaurus/src/client/SiteMetadataDefaults.tsx create mode 100644 packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx create mode 100644 website/_dogfooding/dogfooding.css diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index d8fbaa1b4b..d8d8b53f42 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -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'; } diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 8fb54747d0..dffa79e8d3 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -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 {} - - 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; -} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogArchivePage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogArchivePage/index.tsx index f10c59b191..a05afdf43f 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogArchivePage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogArchivePage/index.tsx @@ -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 ( - -
-
-

{title}

-

{description}

-
-
-
{years.length > 0 && }
-
+ <> + + +
+
+

{title}

+

{description}

+
+
+
{years.length > 0 && }
+
+ ); } diff --git a/packages/docusaurus-theme-classic/src/theme/BlogListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogListPage/index.tsx index 088d6e67f1..910328b214 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogListPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogListPage/index.tsx @@ -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 ( - + <> + + + + ); +} + +function BlogListPageContent(props: Props): JSX.Element { + const {metadata, items, sidebar} = props; + return ( + {items.map(({content: BlogPostContent}) => ( ); } + +export default function BlogListPage(props: Props): JSX.Element { + return ( + + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx index 84918a3019..c7b9ec88e5 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx @@ -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 ( + + + + {/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */} + {authors.some((author) => author.url) && ( + author.url) + .filter(Boolean) + .join(',')} + /> + )} + {tags.length > 0 && ( + tag.label).join(',')} + /> + )} + + ); +} + +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 ( ) : undefined }> - - - - - {/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */} - {authors.some((author) => author.url) && ( - author.url) - .filter(Boolean) - .join(',')} - /> - )} - {tags.length > 0 && ( - tag.label).join(',')} - /> - )} - - ); } + +export default function BlogPostPage(props: Props): JSX.Element { + return ( + + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx index 9b354c1f71..1124e06aaa 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogTagsListPage/index.tsx @@ -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 ( - -

{title}

- -
+ + + + +

{title}

+ +
+
); } diff --git a/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx index 6791d74f9d..fa3bf26a24 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogTagsPostsPage/index.tsx @@ -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 ( - -
-

{title}

+ + + + +
+

{title}

- - - View All Tags - - -
+ + + View All Tags + + +
- {items.map(({content: BlogPostContent}) => ( - - - - ))} - -
+ {items.map(({content: BlogPostContent}) => ( + + + + ))} + +
+ ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx index 0d6f3e5ffe..179839ac09 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx @@ -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 ( + + ); +} + +function DocCategoryGeneratedIndexPageContent({ categoryGeneratedIndex, }: Props): JSX.Element { const category = useCurrentSidebarCategory(); return ( <> - ); } + +export default function DocCategoryGeneratedIndexPage( + props: Props, +): JSX.Element { + return ( + <> + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx index 45b8dd52a7..aa675efa2c 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx @@ -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 ; +} + +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 ( - - +
+
+ +
+
+ + -
-
- -
-
- - + {canRenderTOC && ( + + )} - {canRenderTOC && ( - - )} - -
- {/* +
+ {/* 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 && ( -
- {title} -
- )} - - - -
+ {shouldAddTitle && ( +
+ {title} +
+ )} + + + +
- -
+ +
- -
+
- {renderTocDesktop && ( -
- -
- )}
+ {renderTocDesktop && ( +
+ +
+ )} + + ); +} + +export default function DocItem(props: Props): JSX.Element { + const docHtmlClassName = `docs-doc-id-${props.content.metadata.unversionedId}`; + return ( + + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx index be8ef387c7..d75ffa55a5 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx @@ -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 ( - -
- + <> + + +
+ - {sidebar && ( - + )} +
{ - if ( - !e.currentTarget.classList.contains(styles.docSidebarContainer!) - ) { - return; - } - - if (hiddenSidebarContainer) { - setHiddenSidebar(true); - } - }}> - - - {hiddenSidebar && ( -
- -
- )} - - )} -
-
- {children} -
-
-
-
+
+ {children} +
+ +
+
+ ); } @@ -161,7 +164,12 @@ export default function DocPage(props: Props): JSX.Element { : null; return ( - + -
-
-
-
-

{title}

- - - View All Tags - - -
-
- {tag.docs.map((doc) => ( - - ))} -
-
+ + + + +
+
+
+
+

{title}

+ + + View All Tags + + +
+
+ {tag.docs.map((doc) => ( + + ))} +
+
+
-
- + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx index 9443de0841..5ab4f5a44d 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx @@ -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 ( - -
-
-
-

{title}

- -
+ + + + +
+
+
+

{title}

+ +
+
-
- + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx index ce56fe8d01..35592ef869 100644 --- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx @@ -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 ( - + @@ -34,12 +44,7 @@ export default function Layout(props: Props): JSX.Element { -
+
{children}
diff --git a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx index 7a1876a5f6..44ba2a5c0f 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx @@ -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 ( - -
-
-
- - - -
- {!hideTableOfContents && MDXPageContent.toc && ( -
- + + + +
+
+
+ + +
- )} -
-
-
+ {!hideTableOfContents && MDXPageContent.toc && ( +
+ +
+ )} +
+
+
+ ); } diff --git a/packages/docusaurus-theme-classic/src/theme/NotFound.tsx b/packages/docusaurus-theme-classic/src/theme/NotFound.tsx index 6d4b7a0706..2213158d52 100644 --- a/packages/docusaurus-theme-classic/src/theme/NotFound.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NotFound.tsx @@ -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 ( - -
-
-
-

- - Page Not Found - -

-

- - We could not find what you were looking for. - -

-

- - Please contact the owner of the site that linked you to the - original URL and let them know their link is broken. - -

+ <> + + +
+
+
+

+ + Page Not Found + +

+

+ + We could not find what you were looking for. + +

+

+ + Please contact the owner of the site that linked you to the + original URL and let them know their link is broken. + +

+
-
-
-
+ + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Seo/index.tsx b/packages/docusaurus-theme-classic/src/theme/Seo/index.tsx deleted file mode 100644 index 6acdbe0864..0000000000 --- a/packages/docusaurus-theme-classic/src/theme/Seo/index.tsx +++ /dev/null @@ -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 ( - - {title && {pageTitle}} - {title && } - - {description && } - {description && } - - {keywords && ( - - )} - - {pageImage && } - {pageImage && } - - {children} - - ); -} diff --git a/packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx b/packages/docusaurus-theme-classic/src/theme/SiteMetadata/index.tsx similarity index 76% rename from packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx rename to packages/docusaurus-theme-classic/src/theme/SiteMetadata/index.tsx index a3f5204ce5..e21668aa90 100644 --- a/packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/SiteMetadata/index.tsx @@ -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 ( <> - - {favicon && } - {pageTitle} - {/* The keyboard focus class name need to be applied when SSR so links are outlined when JS is disabled */} - {/* image can override the default image */} - {defaultImage && } - {image && } - - + {defaultImage && } - + element here, diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index a670df39a2..b479c3089a 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -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, diff --git a/packages/docusaurus-theme-common/src/utils/metadataUtilsTemp.tsx b/packages/docusaurus-theme-common/src/utils/metadataUtils.tsx similarity index 54% rename from packages/docusaurus-theme-common/src/utils/metadataUtilsTemp.tsx rename to packages/docusaurus-theme-common/src/utils/metadataUtils.tsx index f55899063b..9de3163715 100644 --- a/packages/docusaurus-theme-common/src/utils/metadataUtilsTemp.tsx +++ b/packages/docusaurus-theme-common/src/utils/metadataUtils.tsx @@ -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 ( + + {title && {pageTitle}} + {title && } + + {description && } + {description && } + + {keywords && ( + + )} + + {pageImage && } + {pageImage && } + + {children} + + ); +} const HtmlClassNameContext = React.createContext(undefined); diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index a9227bdff4..8ce17fa12a 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -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 ( - + {useTitleFormatter(getTitle())} {/* @@ -516,3 +517,11 @@ export default function SearchPage(): JSX.Element { ); } + +export default function SearchPage(): JSX.Element { + return ( + + + + ); +} diff --git a/packages/docusaurus/src/client/App.tsx b/packages/docusaurus/src/client/App.tsx index 9378463a20..6d434b2c61 100644 --- a/packages/docusaurus/src/client/App.tsx +++ b/packages/docusaurus/src/client/App.tsx @@ -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 { + + {renderRoutes(routes)} diff --git a/packages/docusaurus/src/client/SiteMetadataDefaults.tsx b/packages/docusaurus/src/client/SiteMetadataDefaults.tsx new file mode 100644 index 0000000000..bd2f953aab --- /dev/null +++ b/packages/docusaurus/src/client/SiteMetadataDefaults.tsx @@ -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 ( + + + {favicon && } + + ); +} diff --git a/packages/docusaurus/src/client/theme-fallback/Error/index.tsx b/packages/docusaurus/src/client/theme-fallback/Error/index.tsx index 858e42c4de..de8ded15b7 100644 --- a/packages/docusaurus/src/client/theme-fallback/Error/index.tsx +++ b/packages/docusaurus/src/client/theme-fallback/Error/index.tsx @@ -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={() => }> - + + Page Error + + diff --git a/packages/docusaurus/src/client/theme-fallback/Layout/index.tsx b/packages/docusaurus/src/client/theme-fallback/Layout/index.tsx index 1c3de03a60..e897396623 100644 --- a/packages/docusaurus/src/client/theme-fallback/Layout/index.tsx +++ b/packages/docusaurus/src/client/theme-fallback/Layout/index.tsx @@ -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 ( - <> - - {title && {`${title} · ${tagline}`}} - {favicon && } - {description && } - {description && ( - - )} - - {children} - - ); +export default function Layout({children}: Props): JSX.Element { + return <>{children}; } diff --git a/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx b/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx index 48e11300f7..15ccfaefe6 100644 --- a/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx +++ b/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx @@ -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 ( - -
-

Oops, page not found

-
-
+ <> + + Page Not Found + + +
+

Oops, page not found

+
+
+ ); } diff --git a/packages/docusaurus/src/client/theme-fallback/Root/index.tsx b/packages/docusaurus/src/client/theme-fallback/Root/index.tsx index 0e09dc1f42..27ef85e88a 100644 --- a/packages/docusaurus/src/client/theme-fallback/Root/index.tsx +++ b/packages/docusaurus/src/client/theme-fallback/Root/index.tsx @@ -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}; } diff --git a/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx b/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx new file mode 100644 index 0000000000..600acae8f2 --- /dev/null +++ b/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx @@ -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 +export default function SiteMetadata(): JSX.Element | null { + return null; +} diff --git a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap index 1e46aa3c5e..a0a080259f 100644 --- a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap +++ b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap @@ -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", diff --git a/website/_dogfooding/dogfooding.css b/website/_dogfooding/dogfooding.css new file mode 100644 index 0000000000..be6cf970b6 --- /dev/null +++ b/website/_dogfooding/dogfooding.css @@ -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; +} diff --git a/website/docs/guides/creating-pages.md b/website/docs/guides/creating-pages.md index bf4c277d4a..160b4dbf1d 100644 --- a/website/docs/guides/creating-pages.md +++ b/website/docs/guides/creating-pages.md @@ -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 ( - +
); } - -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. diff --git a/website/docs/seo.md b/website/docs/seo.md index 111560af28..3c61f7fae5 100644 --- a/website/docs/seo.md +++ b/website/docs/seo.md @@ -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 ( - - - - - {/* ... */} - - ); -} -``` - 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 [``](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 ( + + + + + {/* ... */} + + ); +} +``` + +:::tip + +For convenience, the default theme `` 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. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 7d029dbe20..495072183f 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -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 ? { diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 8c774d3fb1..728c5d628a 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -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); diff --git a/website/src/plugins/changelog/theme/ChangelogList/index.tsx b/website/src/plugins/changelog/theme/ChangelogList/index.tsx index 1d16dda586..f05400ab57 100644 --- a/website/src/plugins/changelog/theme/ChangelogList/index.tsx +++ b/website/src/plugins/changelog/theme/ChangelogList/index.tsx @@ -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 ( + <> + + + + ); +} + +function ChangelogListContent(props: Props): JSX.Element { const {metadata, items, sidebar} = props; - const {blogDescription, blogTitle} = metadata; + const {blogTitle} = metadata; return ( - +

{blogTitle}

@@ -88,3 +96,16 @@ export default function ChangelogList(props: Props): JSX.Element { ); } + +export default function ChangelogList(props: Props): JSX.Element { + return ( + + + + + ); +} diff --git a/website/src/plugins/changelog/theme/ChangelogPage/index.tsx b/website/src/plugins/changelog/theme/ChangelogPage/index.tsx index 355bfaa58b..186b4aac7c 100644 --- a/website/src/plugins/changelog/theme/ChangelogPage/index.tsx +++ b/website/src/plugins/changelog/theme/ChangelogPage/index.tsx @@ -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 ( + + + + + {authors.some((author) => author.url) && ( + author.url) + .filter(Boolean) + .join(',')} + /> + )} + {tags.length > 0 && ( + tag.label).join(',')} + /> + )} + + ); +} + +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 ( - 0 ? ( - - ) : undefined - }> - - - + <> + + 0 ? ( + + ) : undefined + }> + ← Back to index page - {authors.some((author) => author.url) && ( - author.url) - .filter(Boolean) - .join(',')} - /> + + + + + {(nextItem || prevItem) && ( + )} - {tags.length > 0 && ( - tag.label).join(',')} - /> - )} - - - ← Back to index page - - - - - - {(nextItem || prevItem) && ( - - )} - + + + ); +} + +// 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 ( + + + + ); }