mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-11 08:07:26 +02:00
refactor(theme): split BlogPostItem into smaller theme subcomponents (#7716)
This commit is contained in:
parent
c7f18801da
commit
c3ff131110
43 changed files with 938 additions and 600 deletions
|
@ -52,11 +52,11 @@ exports[`translateContent falls back when translation is incomplete 1`] = `
|
||||||
"description": "/blog/2021/06/19/hello",
|
"description": "/blog/2021/06/19/hello",
|
||||||
"formattedDate": "June 19, 2021",
|
"formattedDate": "June 19, 2021",
|
||||||
"frontMatter": {},
|
"frontMatter": {},
|
||||||
|
"hasTruncateMarker": true,
|
||||||
"permalink": "/blog/2021/06/19/hello",
|
"permalink": "/blog/2021/06/19/hello",
|
||||||
"source": "/blog/2021/06/19/hello",
|
"source": "/blog/2021/06/19/hello",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"title": "Hello",
|
"title": "Hello",
|
||||||
"truncated": true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -96,11 +96,11 @@ exports[`translateContent returns translated loaded 1`] = `
|
||||||
"description": "/blog/2021/06/19/hello",
|
"description": "/blog/2021/06/19/hello",
|
||||||
"formattedDate": "June 19, 2021",
|
"formattedDate": "June 19, 2021",
|
||||||
"frontMatter": {},
|
"frontMatter": {},
|
||||||
|
"hasTruncateMarker": true,
|
||||||
"permalink": "/blog/2021/06/19/hello",
|
"permalink": "/blog/2021/06/19/hello",
|
||||||
"source": "/blog/2021/06/19/hello",
|
"source": "/blog/2021/06/19/hello",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"title": "Hello",
|
"title": "Hello",
|
||||||
"truncated": true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -210,7 +210,7 @@ describe('linkify', () => {
|
||||||
permalink: '/blog/2019/01/01/date-matter',
|
permalink: '/blog/2019/01/01/date-matter',
|
||||||
title: 'date-matter',
|
title: 'date-matter',
|
||||||
},
|
},
|
||||||
truncated: false,
|
hasTruncateMarker: false,
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
authors: [],
|
authors: [],
|
||||||
formattedDate: '',
|
formattedDate: '',
|
||||||
|
|
|
@ -171,7 +171,7 @@ describe('blog plugin', () => {
|
||||||
permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash',
|
permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash',
|
||||||
title: 'Happy 1st Birthday Slash! (translated)',
|
title: 'Happy 1st Birthday Slash! (translated)',
|
||||||
},
|
},
|
||||||
truncated: false,
|
hasTruncateMarker: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -214,7 +214,7 @@ describe('blog plugin', () => {
|
||||||
permalink: '/blog/date-matter',
|
permalink: '/blog/date-matter',
|
||||||
title: 'date-matter',
|
title: 'date-matter',
|
||||||
},
|
},
|
||||||
truncated: false,
|
hasTruncateMarker: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
|
@ -251,7 +251,7 @@ describe('blog plugin', () => {
|
||||||
permalink: '/blog/tags/complex',
|
permalink: '/blog/tags/complex',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
truncated: false,
|
hasTruncateMarker: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
|
@ -288,7 +288,7 @@ describe('blog plugin', () => {
|
||||||
title: 'Simple Slug',
|
title: 'Simple Slug',
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
truncated: false,
|
hasTruncateMarker: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
|
@ -313,7 +313,7 @@ describe('blog plugin', () => {
|
||||||
permalink: '/blog/date-matter',
|
permalink: '/blog/date-matter',
|
||||||
title: 'date-matter',
|
title: 'date-matter',
|
||||||
},
|
},
|
||||||
truncated: false,
|
hasTruncateMarker: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -470,7 +470,7 @@ describe('blog plugin', () => {
|
||||||
tags: [],
|
tags: [],
|
||||||
prevItem: undefined,
|
prevItem: undefined,
|
||||||
nextItem: undefined,
|
nextItem: undefined,
|
||||||
truncated: false,
|
hasTruncateMarker: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ const sampleBlogPosts: BlogPost[] = [
|
||||||
formattedDate: 'June 19, 2021',
|
formattedDate: 'June 19, 2021',
|
||||||
tags: [],
|
tags: [],
|
||||||
title: 'Hello',
|
title: 'Hello',
|
||||||
truncated: true,
|
hasTruncateMarker: true,
|
||||||
authors: [],
|
authors: [],
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
},
|
},
|
||||||
|
|
|
@ -323,7 +323,7 @@ async function processBlogSourceFile(
|
||||||
defaultReadingTime,
|
defaultReadingTime,
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
truncated: truncateMarker.test(content),
|
hasTruncateMarker: truncateMarker.test(content),
|
||||||
authors,
|
authors,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
},
|
},
|
||||||
|
|
|
@ -195,6 +195,21 @@ export default async function pluginContentBlog(
|
||||||
? blogPosts
|
? blogPosts
|
||||||
: blogPosts.slice(0, options.blogSidebarCount);
|
: blogPosts.slice(0, options.blogSidebarCount);
|
||||||
|
|
||||||
|
function blogPostItemsModule(items: string[]) {
|
||||||
|
return items.map((postId) => {
|
||||||
|
const blogPostMetadata = blogItemsToMetadata[postId]!;
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
__import: true,
|
||||||
|
path: blogPostMetadata.source,
|
||||||
|
query: {
|
||||||
|
truncated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (archiveBasePath && blogPosts.length) {
|
if (archiveBasePath && blogPosts.length) {
|
||||||
const archiveUrl = normalizeUrl([
|
const archiveUrl = normalizeUrl([
|
||||||
baseUrl,
|
baseUrl,
|
||||||
|
@ -275,15 +290,7 @@ export default async function pluginContentBlog(
|
||||||
exact: true,
|
exact: true,
|
||||||
modules: {
|
modules: {
|
||||||
sidebar: aliasedSource(sidebarProp),
|
sidebar: aliasedSource(sidebarProp),
|
||||||
items: items.map((postID) => ({
|
items: blogPostItemsModule(items),
|
||||||
content: {
|
|
||||||
__import: true,
|
|
||||||
path: blogItemsToMetadata[postID]!.source,
|
|
||||||
query: {
|
|
||||||
truncated: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
metadata: aliasedSource(pageMetadataPath),
|
metadata: aliasedSource(pageMetadataPath),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -344,18 +351,7 @@ export default async function pluginContentBlog(
|
||||||
exact: true,
|
exact: true,
|
||||||
modules: {
|
modules: {
|
||||||
sidebar: aliasedSource(sidebarProp),
|
sidebar: aliasedSource(sidebarProp),
|
||||||
items: items.map((postID) => {
|
items: blogPostItemsModule(items),
|
||||||
const blogPostMetadata = blogItemsToMetadata[postID]!;
|
|
||||||
return {
|
|
||||||
content: {
|
|
||||||
__import: true,
|
|
||||||
path: blogPostMetadata.source,
|
|
||||||
query: {
|
|
||||||
truncated: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
tag: aliasedSource(tagPropPath),
|
tag: aliasedSource(tagPropPath),
|
||||||
listMetadata: aliasedSource(listMetadataPath),
|
listMetadata: aliasedSource(listMetadataPath),
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare module '@docusaurus/plugin-content-blog' {
|
declare module '@docusaurus/plugin-content-blog' {
|
||||||
|
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
|
||||||
import type {MDXOptions} from '@docusaurus/mdx-loader';
|
import type {MDXOptions} from '@docusaurus/mdx-loader';
|
||||||
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
|
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
|
||||||
import type {Plugin, LoadContext} from '@docusaurus/types';
|
import type {Plugin, LoadContext} from '@docusaurus/types';
|
||||||
|
@ -201,7 +202,7 @@ declare module '@docusaurus/plugin-content-blog' {
|
||||||
/**
|
/**
|
||||||
* Whether the truncate marker exists in the post's content.
|
* Whether the truncate marker exists in the post's content.
|
||||||
*/
|
*/
|
||||||
readonly truncated?: boolean;
|
readonly hasTruncateMarker: boolean;
|
||||||
/**
|
/**
|
||||||
* Used in pagination. Generated after the other metadata, so not readonly.
|
* Used in pagination. Generated after the other metadata, so not readonly.
|
||||||
* Content is just a subset of another post's metadata.
|
* Content is just a subset of another post's metadata.
|
||||||
|
@ -462,25 +463,7 @@ declare module '@docusaurus/plugin-content-blog' {
|
||||||
items: string[];
|
items: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function pluginContentBlog(
|
type PropBlogPostMetadata = Overwrite<
|
||||||
context: LoadContext,
|
|
||||||
options: PluginOptions,
|
|
||||||
): Promise<Plugin<BlogContent>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@theme/BlogPostPage' {
|
|
||||||
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
|
|
||||||
import type {
|
|
||||||
BlogPostFrontMatter,
|
|
||||||
BlogPostMetadata,
|
|
||||||
Assets,
|
|
||||||
BlogSidebar,
|
|
||||||
} from '@docusaurus/plugin-content-blog';
|
|
||||||
import type {Overwrite} from 'utility-types';
|
|
||||||
|
|
||||||
export type FrontMatter = BlogPostFrontMatter;
|
|
||||||
|
|
||||||
export type Metadata = Overwrite<
|
|
||||||
BlogPostMetadata,
|
BlogPostMetadata,
|
||||||
{
|
{
|
||||||
/** The publish date of the post. Serialized from the `Date` object. */
|
/** The publish date of the post. Serialized from the `Date` object. */
|
||||||
|
@ -488,7 +471,28 @@ declare module '@theme/BlogPostPage' {
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type Content = LoadedMDXContent<FrontMatter, Metadata, Assets>;
|
export type PropBlogPostContent = LoadedMDXContent<
|
||||||
|
BlogPostFrontMatter,
|
||||||
|
PropBlogPostMetadata,
|
||||||
|
Assets
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default function pluginContentBlog(
|
||||||
|
context: LoadContext,
|
||||||
|
options: PluginOptions,
|
||||||
|
): Promise<Plugin<BlogContent>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostPage' {
|
||||||
|
import type {
|
||||||
|
BlogPostFrontMatter,
|
||||||
|
BlogSidebar,
|
||||||
|
PropBlogPostContent,
|
||||||
|
} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
export type FrontMatter = BlogPostFrontMatter;
|
||||||
|
|
||||||
|
export type Content = PropBlogPostContent;
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
/** Blog sidebar. */
|
/** Blog sidebar. */
|
||||||
|
@ -500,6 +504,10 @@ declare module '@theme/BlogPostPage' {
|
||||||
export default function BlogPostPage(props: Props): JSX.Element;
|
export default function BlogPostPage(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostPage/Metadata' {
|
||||||
|
export default function BlogPostPageMetadata(): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@theme/BlogListPage' {
|
declare module '@theme/BlogListPage' {
|
||||||
import type {Content} from '@theme/BlogPostPage';
|
import type {Content} from '@theme/BlogPostPage';
|
||||||
import type {
|
import type {
|
||||||
|
|
|
@ -95,41 +95,103 @@ declare module '@theme/BlogSidebar' {
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/BlogPostItem' {
|
declare module '@theme/BlogPostItem' {
|
||||||
import type {FrontMatter, Metadata} from '@theme/BlogPostPage';
|
import type {ReactNode} from 'react';
|
||||||
import type {Assets} from '@docusaurus/plugin-content-blog';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly frontMatter: FrontMatter;
|
children: ReactNode;
|
||||||
readonly assets: Assets;
|
className?: string;
|
||||||
readonly metadata: Metadata;
|
|
||||||
readonly truncated?: string | boolean;
|
|
||||||
readonly isBlogPostPage?: boolean;
|
|
||||||
readonly children: JSX.Element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogPostItem(props: Props): JSX.Element;
|
export default function BlogPostItem(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/BlogPostAuthor' {
|
declare module '@theme/BlogPostItems' {
|
||||||
import type {Metadata} from '@theme/BlogPostPage';
|
import type {ComponentType, ReactNode} from 'react';
|
||||||
|
import type {PropBlogPostContent} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly author: Metadata['authors'][number];
|
items: readonly {content: PropBlogPostContent}[];
|
||||||
|
component?: ComponentType<{children: ReactNode}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogPostAuthor(props: Props): JSX.Element;
|
export default function BlogPostItem(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/BlogPostAuthors' {
|
declare module '@theme/BlogPostItem/Container' {
|
||||||
import type {Metadata} from '@theme/BlogPostPage';
|
import type {ReactNode} from 'react';
|
||||||
import type {Assets} from '@docusaurus/plugin-content-blog';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly authors: Metadata['authors'];
|
children: ReactNode;
|
||||||
readonly assets: Assets;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogPostAuthors(props: Props): JSX.Element;
|
export default function BlogPostItemContainer(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Header' {
|
||||||
|
export default function BlogPostItemHeader(): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Header/Title' {
|
||||||
|
export interface Props {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogPostItemHeaderTitle(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Header/Info' {
|
||||||
|
export interface Props {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogPostItemHeaderInfo(): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Header/Author' {
|
||||||
|
import type {PropBlogPostContent} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
readonly author: PropBlogPostContent['metadata']['authors'][number];
|
||||||
|
readonly className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogPostItemHeaderAuthor(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Header/Authors' {
|
||||||
|
export interface Props {
|
||||||
|
readonly className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogPostItemHeaderAuthors(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Content' {
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogPostItemContent(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Footer' {
|
||||||
|
export default function BlogPostItemFooter(): JSX.Element | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/BlogPostItem/Footer/ReadMoreLink' {
|
||||||
|
import type {Props as LinkProps} from '@docusaurus/Link';
|
||||||
|
|
||||||
|
export type Props = LinkProps & {
|
||||||
|
blogPostTitle: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function BlogPostItemFooterReadMoreLink(
|
||||||
|
props: Props,
|
||||||
|
): JSX.Element | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@theme/BlogPostPaginator' {
|
declare module '@theme/BlogPostPaginator' {
|
||||||
|
|
|
@ -15,10 +15,10 @@ import {
|
||||||
ThemeClassNames,
|
ThemeClassNames,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogPostItem from '@theme/BlogPostItem';
|
|
||||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
import SearchMetadata from '@theme/SearchMetadata';
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
import type {Props} from '@theme/BlogListPage';
|
import type {Props} from '@theme/BlogListPage';
|
||||||
|
import BlogPostItems from '@theme/BlogPostItems';
|
||||||
|
|
||||||
function BlogListPageMetadata(props: Props): JSX.Element {
|
function BlogListPageMetadata(props: Props): JSX.Element {
|
||||||
const {metadata} = props;
|
const {metadata} = props;
|
||||||
|
@ -40,16 +40,7 @@ function BlogListPageContent(props: Props): JSX.Element {
|
||||||
const {metadata, items, sidebar} = props;
|
const {metadata, items, sidebar} = props;
|
||||||
return (
|
return (
|
||||||
<BlogLayout sidebar={sidebar}>
|
<BlogLayout sidebar={sidebar}>
|
||||||
{items.map(({content: BlogPostContent}) => (
|
<BlogPostItems items={items} />
|
||||||
<BlogPostItem
|
|
||||||
key={BlogPostContent.metadata.permalink}
|
|
||||||
frontMatter={BlogPostContent.frontMatter}
|
|
||||||
assets={BlogPostContent.assets}
|
|
||||||
metadata={BlogPostContent.metadata}
|
|
||||||
truncated={BlogPostContent.metadata.truncated}>
|
|
||||||
<BlogPostContent />
|
|
||||||
</BlogPostItem>
|
|
||||||
))}
|
|
||||||
<BlogListPaginator metadata={metadata} />
|
<BlogListPaginator metadata={metadata} />
|
||||||
</BlogLayout>
|
</BlogLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* 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 {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||||||
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
import type {Props} from '@theme/BlogPostItem/Container';
|
||||||
|
|
||||||
|
export default function BlogPostItemContainer({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const {frontMatter, assets} = useBlogPost();
|
||||||
|
const {withBaseUrl} = useBaseUrlUtils();
|
||||||
|
const image = assets.image ?? frontMatter.image;
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
className={className}
|
||||||
|
itemProp="blogPost"
|
||||||
|
itemScope
|
||||||
|
itemType="http://schema.org/BlogPosting">
|
||||||
|
{image && (
|
||||||
|
<meta itemProp="image" content={withBaseUrl(image, {absolute: true})} />
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* 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 clsx from 'clsx';
|
||||||
|
import {blogPostContainerID} from '@docusaurus/utils-common';
|
||||||
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
import MDXContent from '@theme/MDXContent';
|
||||||
|
import type {Props} from '@theme/BlogPostItem/Content';
|
||||||
|
|
||||||
|
export default function BlogPostItemContent({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const {isBlogPostPage} = useBlogPost();
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
// This ID is used for the feed generation to locate the main content
|
||||||
|
id={isBlogPostPage ? blogPostContainerID : undefined}
|
||||||
|
className={clsx('markdown', className)}
|
||||||
|
itemProp="articleBody">
|
||||||
|
<MDXContent>{children}</MDXContent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* 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 Translate, {translate} from '@docusaurus/Translate';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import type {Props} from '@theme/BlogPostItem/Footer/ReadMoreLink';
|
||||||
|
|
||||||
|
function ReadMoreLabel() {
|
||||||
|
return (
|
||||||
|
<b>
|
||||||
|
<Translate
|
||||||
|
id="theme.blog.post.readMore"
|
||||||
|
description="The label used in blog post item excerpts to link to full blog posts">
|
||||||
|
Read More
|
||||||
|
</Translate>
|
||||||
|
</b>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogPostItemFooterReadMoreLink(
|
||||||
|
props: Props,
|
||||||
|
): JSX.Element {
|
||||||
|
const {blogPostTitle, ...linkProps} = props;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
aria-label={translate(
|
||||||
|
{
|
||||||
|
message: 'Read more about {title}',
|
||||||
|
id: 'theme.blog.post.readMoreLabel',
|
||||||
|
description:
|
||||||
|
'The ARIA label for the link to full blog posts from excerpts',
|
||||||
|
},
|
||||||
|
{title: blogPostTitle},
|
||||||
|
)}
|
||||||
|
{...linkProps}>
|
||||||
|
<ReadMoreLabel />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* 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 clsx from 'clsx';
|
||||||
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
import EditThisPage from '@theme/EditThisPage';
|
||||||
|
import TagsListInline from '@theme/TagsListInline';
|
||||||
|
import ReadMoreLink from '@theme/BlogPostItem/Footer/ReadMoreLink';
|
||||||
|
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
export default function BlogPostItemFooter(): JSX.Element | null {
|
||||||
|
const {metadata, isBlogPostPage} = useBlogPost();
|
||||||
|
const {tags, title, editUrl, hasTruncateMarker} = metadata;
|
||||||
|
|
||||||
|
// A post is truncated if it's in the "list view" and it has a truncate marker
|
||||||
|
const truncatedPost = !isBlogPostPage && hasTruncateMarker;
|
||||||
|
|
||||||
|
const tagsExists = tags.length > 0;
|
||||||
|
|
||||||
|
const renderFooter = tagsExists || truncatedPost || editUrl;
|
||||||
|
|
||||||
|
if (!renderFooter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer
|
||||||
|
className={clsx(
|
||||||
|
'row docusaurus-mt-lg',
|
||||||
|
isBlogPostPage && styles.blogPostFooterDetailsFull,
|
||||||
|
)}>
|
||||||
|
{tagsExists && (
|
||||||
|
<div className={clsx('col', {'col--9': truncatedPost})}>
|
||||||
|
<TagsListInline tags={tags} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isBlogPostPage && editUrl && (
|
||||||
|
<div className="col margin-top--sm">
|
||||||
|
<EditThisPage editUrl={editUrl} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{truncatedPost && (
|
||||||
|
<div
|
||||||
|
className={clsx('col text--right', {
|
||||||
|
'col--3': tagsExists,
|
||||||
|
})}>
|
||||||
|
<ReadMoreLink blogPostTitle={title} to={metadata.permalink} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.blogPostFooterDetailsFull {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
|
@ -6,8 +6,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
import Link, {type Props as LinkProps} from '@docusaurus/Link';
|
import Link, {type Props as LinkProps} from '@docusaurus/Link';
|
||||||
import type {Props} from '@theme/BlogPostAuthor';
|
|
||||||
|
import type {Props} from '@theme/BlogPostItem/Header/Author';
|
||||||
|
|
||||||
function MaybeLink(props: LinkProps): JSX.Element {
|
function MaybeLink(props: LinkProps): JSX.Element {
|
||||||
if (props.href) {
|
if (props.href) {
|
||||||
|
@ -16,11 +18,14 @@ function MaybeLink(props: LinkProps): JSX.Element {
|
||||||
return <>{props.children}</>;
|
return <>{props.children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogPostAuthor({author}: Props): JSX.Element {
|
export default function BlogPostItemHeaderAuthor({
|
||||||
|
author,
|
||||||
|
className,
|
||||||
|
}: Props): JSX.Element {
|
||||||
const {name, title, url, imageURL, email} = author;
|
const {name, title, url, imageURL, email} = author;
|
||||||
const link = url || (email && `mailto:${email}`) || undefined;
|
const link = url || (email && `mailto:${email}`) || undefined;
|
||||||
return (
|
return (
|
||||||
<div className="avatar margin-bottom--sm">
|
<div className={clsx('avatar margin-bottom--sm', className)}>
|
||||||
{imageURL && (
|
{imageURL && (
|
||||||
<MaybeLink href={link} className="avatar__photo-link">
|
<MaybeLink href={link} className="avatar__photo-link">
|
||||||
<img className="avatar__photo" src={imageURL} alt={name} />
|
<img className="avatar__photo" src={imageURL} alt={name} />
|
|
@ -7,16 +7,19 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import BlogPostAuthor from '@theme/BlogPostAuthor';
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
import type {Props} from '@theme/BlogPostAuthors';
|
import BlogPostItemHeaderAuthor from '@theme/BlogPostItem/Header/Author';
|
||||||
|
import type {Props} from '@theme/BlogPostItem/Header/Authors';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
// Component responsible for the authors layout
|
// Component responsible for the authors layout
|
||||||
export default function BlogPostAuthors({
|
export default function BlogPostItemHeaderAuthors({
|
||||||
authors,
|
className,
|
||||||
assets,
|
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
|
const {
|
||||||
|
metadata: {authors},
|
||||||
|
assets,
|
||||||
|
} = useBlogPost();
|
||||||
const authorsCount = authors.length;
|
const authorsCount = authors.length;
|
||||||
if (authorsCount === 0) {
|
if (authorsCount === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -27,6 +30,7 @@ export default function BlogPostAuthors({
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'margin-top--md margin-bottom--sm',
|
'margin-top--md margin-bottom--sm',
|
||||||
imageOnly ? styles.imageOnlyAuthorRow : 'row',
|
imageOnly ? styles.imageOnlyAuthorRow : 'row',
|
||||||
|
className,
|
||||||
)}>
|
)}>
|
||||||
{authors.map((author, idx) => (
|
{authors.map((author, idx) => (
|
||||||
<div
|
<div
|
||||||
|
@ -35,7 +39,7 @@ export default function BlogPostAuthors({
|
||||||
imageOnly ? styles.imageOnlyAuthorCol : styles.authorCol,
|
imageOnly ? styles.imageOnlyAuthorCol : styles.authorCol,
|
||||||
)}
|
)}
|
||||||
key={idx}>
|
key={idx}>
|
||||||
<BlogPostAuthor
|
<BlogPostItemHeaderAuthor
|
||||||
author={{
|
author={{
|
||||||
...author,
|
...author,
|
||||||
// Handle author images using relative paths
|
// Handle author images using relative paths
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* 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 clsx from 'clsx';
|
||||||
|
import {translate} from '@docusaurus/Translate';
|
||||||
|
import {usePluralForm} from '@docusaurus/theme-common';
|
||||||
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
import type {Props} from '@theme/BlogPostItem/Header/Info';
|
||||||
|
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
// Very simple pluralization: probably good enough for now
|
||||||
|
function useReadingTimePlural() {
|
||||||
|
const {selectMessage} = usePluralForm();
|
||||||
|
return (readingTimeFloat: number) => {
|
||||||
|
const readingTime = Math.ceil(readingTimeFloat);
|
||||||
|
return selectMessage(
|
||||||
|
readingTime,
|
||||||
|
translate(
|
||||||
|
{
|
||||||
|
id: 'theme.blog.post.readingTime.plurals',
|
||||||
|
description:
|
||||||
|
'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
|
||||||
|
message: 'One min read|{readingTime} min read',
|
||||||
|
},
|
||||||
|
{readingTime},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReadingTime({readingTime}: {readingTime: number}) {
|
||||||
|
const readingTimePlural = useReadingTimePlural();
|
||||||
|
return <>{readingTimePlural(readingTime)}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Date({date, formattedDate}: {date: string; formattedDate: string}) {
|
||||||
|
return (
|
||||||
|
<time dateTime={date} itemProp="datePublished">
|
||||||
|
{formattedDate}
|
||||||
|
</time>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Spacer() {
|
||||||
|
return <>{' · '}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlogPostItemHeaderInfo({
|
||||||
|
className,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const {metadata} = useBlogPost();
|
||||||
|
const {date, formattedDate, readingTime} = metadata;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(styles.container, 'margin-vert--md', className)}>
|
||||||
|
<Date date={date} formattedDate={formattedDate} />
|
||||||
|
{typeof readingTime !== 'undefined' && (
|
||||||
|
<>
|
||||||
|
<Spacer />
|
||||||
|
<ReadingTime readingTime={readingTime} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* 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 clsx from 'clsx';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
import type {Props} from '@theme/BlogPostItem/Header/Title';
|
||||||
|
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
export default function BlogPostItemHeaderTitle({
|
||||||
|
className,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const {metadata, isBlogPostPage} = useBlogPost();
|
||||||
|
const {permalink, title} = metadata;
|
||||||
|
const TitleHeading = isBlogPostPage ? 'h1' : 'h2';
|
||||||
|
return (
|
||||||
|
<TitleHeading className={clsx(styles.title, className)} itemProp="headline">
|
||||||
|
{isBlogPostPage ? (
|
||||||
|
title
|
||||||
|
) : (
|
||||||
|
<Link itemProp="url" to={permalink}>
|
||||||
|
{title}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</TitleHeading>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.blogPostTitle {
|
.title {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,15 +13,7 @@
|
||||||
Blog post title should be smaller on smaller devices
|
Blog post title should be smaller on smaller devices
|
||||||
**/
|
**/
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
.blogPostTitle {
|
.title {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.blogPostData {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blogPostDetailsFull {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import BlogPostItemHeaderTitle from '@theme/BlogPostItem/Header/Title';
|
||||||
|
import BlogPostItemHeaderInfo from '@theme/BlogPostItem/Header/Info';
|
||||||
|
import BlogPostItemHeaderAuthors from '@theme/BlogPostItem/Header/Authors';
|
||||||
|
|
||||||
|
export default function BlogPostItemHeader(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<BlogPostItemHeaderTitle />
|
||||||
|
<BlogPostItemHeaderInfo />
|
||||||
|
<BlogPostItemHeaderAuthors />
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
|
@ -7,155 +7,29 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Translate, {translate} from '@docusaurus/Translate';
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
import Link from '@docusaurus/Link';
|
import BlogPostItemContainer from '@theme/BlogPostItem/Container';
|
||||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
import BlogPostItemHeader from '@theme/BlogPostItem/Header';
|
||||||
import {usePluralForm} from '@docusaurus/theme-common';
|
import BlogPostItemContent from '@theme/BlogPostItem/Content';
|
||||||
import {blogPostContainerID} from '@docusaurus/utils-common';
|
import BlogPostItemFooter from '@theme/BlogPostItem/Footer';
|
||||||
import MDXContent from '@theme/MDXContent';
|
|
||||||
import EditThisPage from '@theme/EditThisPage';
|
|
||||||
import TagsListInline from '@theme/TagsListInline';
|
|
||||||
import BlogPostAuthors from '@theme/BlogPostAuthors';
|
|
||||||
import type {Props} from '@theme/BlogPostItem';
|
import type {Props} from '@theme/BlogPostItem';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
// apply a bottom margin in list view
|
||||||
|
function useContainerClassName() {
|
||||||
// Very simple pluralization: probably good enough for now
|
const {isBlogPostPage} = useBlogPost();
|
||||||
function useReadingTimePlural() {
|
return !isBlogPostPage ? 'margin-bottom--xl' : undefined;
|
||||||
const {selectMessage} = usePluralForm();
|
|
||||||
return (readingTimeFloat: number) => {
|
|
||||||
const readingTime = Math.ceil(readingTimeFloat);
|
|
||||||
return selectMessage(
|
|
||||||
readingTime,
|
|
||||||
translate(
|
|
||||||
{
|
|
||||||
id: 'theme.blog.post.readingTime.plurals',
|
|
||||||
description:
|
|
||||||
'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
|
|
||||||
message: 'One min read|{readingTime} min read',
|
|
||||||
},
|
|
||||||
{readingTime},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogPostItem(props: Props): JSX.Element {
|
export default function BlogPostItem({
|
||||||
const readingTimePlural = useReadingTimePlural();
|
children,
|
||||||
const {withBaseUrl} = useBaseUrlUtils();
|
className,
|
||||||
const {
|
}: Props): JSX.Element {
|
||||||
children,
|
const containerClassName = useContainerClassName();
|
||||||
frontMatter,
|
|
||||||
assets,
|
|
||||||
metadata,
|
|
||||||
truncated,
|
|
||||||
isBlogPostPage = false,
|
|
||||||
} = props;
|
|
||||||
const {
|
|
||||||
date,
|
|
||||||
formattedDate,
|
|
||||||
permalink,
|
|
||||||
tags,
|
|
||||||
readingTime,
|
|
||||||
title,
|
|
||||||
editUrl,
|
|
||||||
authors,
|
|
||||||
} = metadata;
|
|
||||||
|
|
||||||
const image = assets.image ?? frontMatter.image;
|
|
||||||
const truncatedPost = !isBlogPostPage && truncated;
|
|
||||||
const tagsExists = tags.length > 0;
|
|
||||||
const TitleHeading = isBlogPostPage ? 'h1' : 'h2';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<BlogPostItemContainer className={clsx(containerClassName, className)}>
|
||||||
className={!isBlogPostPage ? 'margin-bottom--xl' : undefined}
|
<BlogPostItemHeader />
|
||||||
itemProp="blogPost"
|
<BlogPostItemContent>{children}</BlogPostItemContent>
|
||||||
itemScope
|
<BlogPostItemFooter />
|
||||||
itemType="http://schema.org/BlogPosting">
|
</BlogPostItemContainer>
|
||||||
<header>
|
|
||||||
<TitleHeading className={styles.blogPostTitle} itemProp="headline">
|
|
||||||
{isBlogPostPage ? (
|
|
||||||
title
|
|
||||||
) : (
|
|
||||||
<Link itemProp="url" to={permalink}>
|
|
||||||
{title}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</TitleHeading>
|
|
||||||
<div className={clsx(styles.blogPostData, 'margin-vert--md')}>
|
|
||||||
<time dateTime={date} itemProp="datePublished">
|
|
||||||
{formattedDate}
|
|
||||||
</time>
|
|
||||||
|
|
||||||
{typeof readingTime !== 'undefined' && (
|
|
||||||
<>
|
|
||||||
{' · '}
|
|
||||||
{readingTimePlural(readingTime)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<BlogPostAuthors authors={authors} assets={assets} />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{image && (
|
|
||||||
<meta itemProp="image" content={withBaseUrl(image, {absolute: true})} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
// This ID is used for the feed generation to locate the main content
|
|
||||||
id={isBlogPostPage ? blogPostContainerID : undefined}
|
|
||||||
className="markdown"
|
|
||||||
itemProp="articleBody">
|
|
||||||
<MDXContent>{children}</MDXContent>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(tagsExists || truncated || editUrl) && (
|
|
||||||
<footer
|
|
||||||
className={clsx(
|
|
||||||
'row docusaurus-mt-lg',
|
|
||||||
isBlogPostPage && styles.blogPostDetailsFull,
|
|
||||||
)}>
|
|
||||||
{tagsExists && (
|
|
||||||
<div className={clsx('col', {'col--9': truncatedPost})}>
|
|
||||||
<TagsListInline tags={tags} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isBlogPostPage && editUrl && (
|
|
||||||
<div className="col margin-top--sm">
|
|
||||||
<EditThisPage editUrl={editUrl} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{truncatedPost && (
|
|
||||||
<div
|
|
||||||
className={clsx('col text--right', {
|
|
||||||
'col--3': tagsExists,
|
|
||||||
})}>
|
|
||||||
<Link
|
|
||||||
to={metadata.permalink}
|
|
||||||
aria-label={translate(
|
|
||||||
{
|
|
||||||
message: 'Read more about {title}',
|
|
||||||
id: 'theme.blog.post.readMoreLabel',
|
|
||||||
description:
|
|
||||||
'The ARIA label for the link to full blog posts from excerpts',
|
|
||||||
},
|
|
||||||
{title},
|
|
||||||
)}>
|
|
||||||
<b>
|
|
||||||
<Translate
|
|
||||||
id="theme.blog.post.readMore"
|
|
||||||
description="The label used in blog post item excerpts to link to full blog posts">
|
|
||||||
Read More
|
|
||||||
</Translate>
|
|
||||||
</b>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</footer>
|
|
||||||
)}
|
|
||||||
</article>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* 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 {BlogPostProvider} from '@docusaurus/theme-common/internal';
|
||||||
|
import BlogPostItem from '@theme/BlogPostItem';
|
||||||
|
import type {Props} from '@theme/BlogPostItems';
|
||||||
|
|
||||||
|
export default function BlogPostItems({
|
||||||
|
items,
|
||||||
|
component: BlogPostItemComponent = BlogPostItem,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{items.map(({content: BlogPostContent}) => (
|
||||||
|
<BlogPostProvider
|
||||||
|
key={BlogPostContent.metadata.permalink}
|
||||||
|
content={BlogPostContent}>
|
||||||
|
<BlogPostItemComponent>
|
||||||
|
<BlogPostContent />
|
||||||
|
</BlogPostItemComponent>
|
||||||
|
</BlogPostProvider>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* 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 {PageMetadata} from '@docusaurus/theme-common';
|
||||||
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
|
||||||
|
export default function BlogPostPageMetadata(): JSX.Element {
|
||||||
|
const {assets, metadata} = useBlogPost();
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,56 +5,26 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {type ReactNode} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import {
|
import {HtmlClassNameProvider, ThemeClassNames} from '@docusaurus/theme-common';
|
||||||
PageMetadata,
|
import {BlogPostProvider, useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
HtmlClassNameProvider,
|
|
||||||
ThemeClassNames,
|
|
||||||
} from '@docusaurus/theme-common';
|
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogPostItem from '@theme/BlogPostItem';
|
import BlogPostItem from '@theme/BlogPostItem';
|
||||||
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
import BlogPostPaginator from '@theme/BlogPostPaginator';
|
||||||
|
import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
import type {Props} from '@theme/BlogPostPage';
|
import type {Props} from '@theme/BlogPostPage';
|
||||||
|
import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
function BlogPostPageMetadata(props: Props): JSX.Element {
|
function BlogPostPageContent({
|
||||||
const {content: BlogPostContents} = props;
|
sidebar,
|
||||||
const {assets, metadata} = BlogPostContents;
|
children,
|
||||||
const {title, description, date, tags, authors, frontMatter} = metadata;
|
}: {
|
||||||
const {keywords} = frontMatter;
|
sidebar: BlogSidebar;
|
||||||
const image = assets.image ?? frontMatter.image;
|
children: ReactNode;
|
||||||
return (
|
}): JSX.Element {
|
||||||
<PageMetadata
|
const {metadata, toc} = useBlogPost();
|
||||||
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 {nextItem, prevItem, frontMatter} = metadata;
|
const {nextItem, prevItem, frontMatter} = metadata;
|
||||||
const {
|
const {
|
||||||
hide_table_of_contents: hideTableOfContents,
|
hide_table_of_contents: hideTableOfContents,
|
||||||
|
@ -65,21 +35,15 @@ function BlogPostPageContent(props: Props): JSX.Element {
|
||||||
<BlogLayout
|
<BlogLayout
|
||||||
sidebar={sidebar}
|
sidebar={sidebar}
|
||||||
toc={
|
toc={
|
||||||
!hideTableOfContents && BlogPostContents.toc.length > 0 ? (
|
!hideTableOfContents && toc.length > 0 ? (
|
||||||
<TOC
|
<TOC
|
||||||
toc={BlogPostContents.toc}
|
toc={toc}
|
||||||
minHeadingLevel={tocMinHeadingLevel}
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
maxHeadingLevel={tocMaxHeadingLevel}
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}>
|
}>
|
||||||
<BlogPostItem
|
<BlogPostItem>{children}</BlogPostItem>
|
||||||
frontMatter={frontMatter}
|
|
||||||
assets={assets}
|
|
||||||
metadata={metadata}
|
|
||||||
isBlogPostPage>
|
|
||||||
<BlogPostContents />
|
|
||||||
</BlogPostItem>
|
|
||||||
|
|
||||||
{(nextItem || prevItem) && (
|
{(nextItem || prevItem) && (
|
||||||
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
|
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
|
||||||
|
@ -89,14 +53,19 @@ function BlogPostPageContent(props: Props): JSX.Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogPostPage(props: Props): JSX.Element {
|
export default function BlogPostPage(props: Props): JSX.Element {
|
||||||
|
const BlogPostContent = props.content;
|
||||||
return (
|
return (
|
||||||
<HtmlClassNameProvider
|
<BlogPostProvider content={props.content} isBlogPostPage>
|
||||||
className={clsx(
|
<HtmlClassNameProvider
|
||||||
ThemeClassNames.wrapper.blogPages,
|
className={clsx(
|
||||||
ThemeClassNames.page.blogPostPage,
|
ThemeClassNames.wrapper.blogPages,
|
||||||
)}>
|
ThemeClassNames.page.blogPostPage,
|
||||||
<BlogPostPageMetadata {...props} />
|
)}>
|
||||||
<BlogPostPageContent {...props} />
|
<BlogPostPageMetadata />
|
||||||
</HtmlClassNameProvider>
|
<BlogPostPageContent sidebar={props.sidebar}>
|
||||||
|
<BlogPostContent />
|
||||||
|
</BlogPostPageContent>
|
||||||
|
</HtmlClassNameProvider>
|
||||||
|
</BlogPostProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,10 @@ import {
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogPostItem from '@theme/BlogPostItem';
|
|
||||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
import SearchMetadata from '@theme/SearchMetadata';
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
import type {Props} from '@theme/BlogTagsPostsPage';
|
import type {Props} from '@theme/BlogTagsPostsPage';
|
||||||
|
import BlogPostItems from '@theme/BlogPostItems';
|
||||||
|
|
||||||
// Very simple pluralization: probably good enough for now
|
// Very simple pluralization: probably good enough for now
|
||||||
function useBlogPostsPlural() {
|
function useBlogPostsPlural() {
|
||||||
|
@ -39,14 +39,9 @@ function useBlogPostsPlural() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogTagsPostsPage({
|
function useBlogTagsPostsPageTitle(tag: Props['tag']): string {
|
||||||
tag,
|
|
||||||
items,
|
|
||||||
sidebar,
|
|
||||||
listMetadata,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const blogPostsPlural = useBlogPostsPlural();
|
const blogPostsPlural = useBlogPostsPlural();
|
||||||
const title = translate(
|
return translate(
|
||||||
{
|
{
|
||||||
id: 'theme.blog.tagTitle',
|
id: 'theme.blog.tagTitle',
|
||||||
description: 'The title of the page for a blog tag',
|
description: 'The title of the page for a blog tag',
|
||||||
|
@ -54,40 +49,52 @@ export default function BlogTagsPostsPage({
|
||||||
},
|
},
|
||||||
{nPosts: blogPostsPlural(tag.count), tagName: tag.label},
|
{nPosts: blogPostsPlural(tag.count), tagName: tag.label},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BlogTagsPostsPageMetadata({tag}: Props): JSX.Element {
|
||||||
|
const title = useBlogTagsPostsPageTitle(tag);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageMetadata title={title} />
|
||||||
|
<SearchMetadata tag="blog_tags_posts" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BlogTagsPostsPageContent({
|
||||||
|
tag,
|
||||||
|
items,
|
||||||
|
sidebar,
|
||||||
|
listMetadata,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const title = useBlogTagsPostsPageTitle(tag);
|
||||||
|
return (
|
||||||
|
<BlogLayout sidebar={sidebar}>
|
||||||
|
<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>
|
||||||
|
<BlogPostItems items={items} />
|
||||||
|
<BlogListPaginator metadata={listMetadata} />
|
||||||
|
</BlogLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default function BlogTagsPostsPage(props: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<HtmlClassNameProvider
|
<HtmlClassNameProvider
|
||||||
className={clsx(
|
className={clsx(
|
||||||
ThemeClassNames.wrapper.blogPages,
|
ThemeClassNames.wrapper.blogPages,
|
||||||
ThemeClassNames.page.blogTagPostListPage,
|
ThemeClassNames.page.blogTagPostListPage,
|
||||||
)}>
|
)}>
|
||||||
<PageMetadata title={title} />
|
<BlogTagsPostsPageMetadata {...props} />
|
||||||
<SearchMetadata tag="blog_tags_posts" />
|
<BlogTagsPostsPageContent {...props} />
|
||||||
<BlogLayout sidebar={sidebar}>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{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>
|
</HtmlClassNameProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
80
packages/docusaurus-theme-common/src/contexts/blogPost.tsx
Normal file
80
packages/docusaurus-theme-common/src/contexts/blogPost.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* 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, {useMemo, type ReactNode, useContext} from 'react';
|
||||||
|
import {ReactContextError} from '../utils/reactUtils';
|
||||||
|
|
||||||
|
import type {PropBlogPostContent} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The React context value returned by the `useBlogPost()` hook.
|
||||||
|
* It contains useful data related to the currently browsed blog post.
|
||||||
|
*/
|
||||||
|
export type BlogPostContextValue = Pick<
|
||||||
|
PropBlogPostContent,
|
||||||
|
'metadata' | 'frontMatter' | 'assets' | 'toc'
|
||||||
|
> & {
|
||||||
|
readonly isBlogPostPage: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Context = React.createContext<BlogPostContextValue | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: we don't use `PropBlogPostContent` as context value on purpose. Metadata is
|
||||||
|
* currently stored inside the MDX component, but we may want to change that in
|
||||||
|
* the future.
|
||||||
|
*/
|
||||||
|
function useContextValue({
|
||||||
|
content,
|
||||||
|
isBlogPostPage,
|
||||||
|
}: {
|
||||||
|
content: PropBlogPostContent;
|
||||||
|
isBlogPostPage: boolean;
|
||||||
|
}): BlogPostContextValue {
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
metadata: content.metadata,
|
||||||
|
frontMatter: content.frontMatter,
|
||||||
|
assets: content.assets,
|
||||||
|
toc: content.toc,
|
||||||
|
isBlogPostPage,
|
||||||
|
}),
|
||||||
|
[content, isBlogPostPage],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a very thin layer around the `content` received from the MDX loader.
|
||||||
|
* It provides metadata about the blog post to the children tree.
|
||||||
|
*/
|
||||||
|
export function BlogPostProvider({
|
||||||
|
children,
|
||||||
|
content,
|
||||||
|
isBlogPostPage = false,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
content: PropBlogPostContent;
|
||||||
|
isBlogPostPage?: boolean;
|
||||||
|
}): JSX.Element {
|
||||||
|
const contextValue = useContextValue({content, isBlogPostPage});
|
||||||
|
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data of the currently browsed blog post. Gives access to
|
||||||
|
* front matter, metadata, TOC, etc.
|
||||||
|
* When swizzling a low-level component (e.g. the "Edit this page" link)
|
||||||
|
* and you need some extra metadata, you don't have to drill the props
|
||||||
|
* all the way through the component tree: simply use this hook instead.
|
||||||
|
*/
|
||||||
|
export function useBlogPost(): BlogPostContextValue {
|
||||||
|
const blogPost = useContext(Context);
|
||||||
|
if (blogPost === null) {
|
||||||
|
throw new ReactContextError('BlogPostProvider');
|
||||||
|
}
|
||||||
|
return blogPost;
|
||||||
|
}
|
|
@ -42,8 +42,7 @@ function useContextValue(content: PropDocContent): DocContextValue {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a very thin layer around the `content` received from the MDX loader.
|
* This is a very thin layer around the `content` received from the MDX loader.
|
||||||
* It provides the component to be rendered and other metadata about the doc to
|
* It provides metadata about the doc to the children tree.
|
||||||
* the children.
|
|
||||||
*/
|
*/
|
||||||
export function DocProvider({
|
export function DocProvider({
|
||||||
children,
|
children,
|
||||||
|
|
|
@ -24,7 +24,13 @@ export {
|
||||||
} from './contexts/docSidebarItemsExpandedState';
|
} from './contexts/docSidebarItemsExpandedState';
|
||||||
export {DocsVersionProvider, useDocsVersion} from './contexts/docsVersion';
|
export {DocsVersionProvider, useDocsVersion} from './contexts/docsVersion';
|
||||||
export {DocsSidebarProvider, useDocsSidebar} from './contexts/docsSidebar';
|
export {DocsSidebarProvider, useDocsSidebar} from './contexts/docsSidebar';
|
||||||
|
|
||||||
export {DocProvider, useDoc, type DocContextValue} from './contexts/doc';
|
export {DocProvider, useDoc, type DocContextValue} from './contexts/doc';
|
||||||
|
export {
|
||||||
|
BlogPostProvider,
|
||||||
|
useBlogPost,
|
||||||
|
type BlogPostContextValue,
|
||||||
|
} from './contexts/blogPost';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useDocsPreferredVersionByPluginId,
|
useDocsPreferredVersionByPluginId,
|
||||||
|
|
|
@ -6,15 +6,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import type {Props} from '@theme/BlogPostAuthor';
|
import type {Props} from '@theme/BlogPostItem/Header/Author';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function ChangelogAuthor({author}: Props): JSX.Element {
|
export default function ChangelogAuthor({
|
||||||
|
author,
|
||||||
|
className,
|
||||||
|
}: Props): JSX.Element {
|
||||||
const {name, url, imageURL} = author;
|
const {name, url, imageURL} = author;
|
||||||
return (
|
return (
|
||||||
<div className="avatar margin-bottom--sm">
|
<div className={clsx('avatar margin-bottom--sm', className)}>
|
||||||
{imageURL && (
|
{imageURL && (
|
||||||
<Link className="avatar__photo-link avatar__photo" href={url}>
|
<Link className="avatar__photo-link avatar__photo" href={url}>
|
||||||
<img
|
<img
|
|
@ -7,17 +7,21 @@
|
||||||
|
|
||||||
import React, {useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import ChangelogAuthor from '@theme/ChangelogAuthor';
|
import {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
import BlogPostItemHeaderAuthor from '@theme/BlogPostItem/Header/Author';
|
||||||
import IconExpand from '@theme/IconExpand';
|
import IconExpand from '@theme/IconExpand';
|
||||||
import type {Props} from '@theme/BlogPostAuthors';
|
import type {Props} from '@theme/BlogPostItem/Header/Authors';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
// Component responsible for the authors layout
|
// Component responsible for the authors layout
|
||||||
export default function BlogPostAuthors({
|
export default function BlogPostAuthors({
|
||||||
authors,
|
className,
|
||||||
assets,
|
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
|
const {
|
||||||
|
metadata: {authors},
|
||||||
|
assets,
|
||||||
|
} = useBlogPost();
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const authorsCount = authors.length;
|
const authorsCount = authors.length;
|
||||||
if (authorsCount === 0) {
|
if (authorsCount === 0) {
|
||||||
|
@ -29,10 +33,11 @@ export default function BlogPostAuthors({
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'margin-top--md margin-bottom--sm',
|
'margin-top--md margin-bottom--sm',
|
||||||
styles.imageOnlyAuthorRow,
|
styles.imageOnlyAuthorRow,
|
||||||
|
className,
|
||||||
)}>
|
)}>
|
||||||
{filteredAuthors.map((author, idx) => (
|
{filteredAuthors.map((author, idx) => (
|
||||||
<div className={styles.imageOnlyAuthorCol} key={idx}>
|
<div className={styles.imageOnlyAuthorCol} key={idx}>
|
||||||
<ChangelogAuthor
|
<BlogPostItemHeaderAuthor
|
||||||
author={{
|
author={{
|
||||||
...author,
|
...author,
|
||||||
// Handle author images using relative paths
|
// Handle author images using relative paths
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* 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 {useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
|
|
||||||
|
import BlogPostItemHeaderTitle from '@theme/BlogPostItem/Header/Title';
|
||||||
|
import BlogPostItemHeaderInfo from '@theme/BlogPostItem/Header/Info';
|
||||||
|
import BlogPostItemHeaderAuthors from '@theme/BlogPostItem/Header/Authors';
|
||||||
|
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
// Reduce changelog title size, but only on list view
|
||||||
|
function ChangelogTitle() {
|
||||||
|
const {isBlogPostPage} = useBlogPost();
|
||||||
|
return (
|
||||||
|
<BlogPostItemHeaderTitle
|
||||||
|
className={isBlogPostPage ? undefined : styles.changelogItemTitleList}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChangelogItemHeader(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<ChangelogTitle />
|
||||||
|
<BlogPostItemHeaderInfo />
|
||||||
|
<BlogPostItemHeaderAuthors />
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.changelogItemTitleList {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
|
@ -6,71 +6,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import ChangelogItemHeader from '@theme/ChangelogItem/Header';
|
||||||
import {MDXProvider} from '@mdx-js/react';
|
|
||||||
import Link from '@docusaurus/Link';
|
|
||||||
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
|
||||||
import {blogPostContainerID} from '@docusaurus/utils-common';
|
|
||||||
import MDXComponents from '@theme/MDXComponents';
|
|
||||||
import ChangelogAuthors from '@theme/ChangelogAuthors';
|
|
||||||
import type {Props} from '@theme/BlogPostItem';
|
import type {Props} from '@theme/BlogPostItem';
|
||||||
|
import BlogPostItemContainer from '@theme/BlogPostItem/Container';
|
||||||
|
import BlogPostItemContent from '@theme/BlogPostItem/Content';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function ChangelogItem(props: Props): JSX.Element {
|
export default function ChangelogItem({children}: Props): JSX.Element {
|
||||||
const {withBaseUrl} = useBaseUrlUtils();
|
|
||||||
const {
|
|
||||||
children,
|
|
||||||
frontMatter,
|
|
||||||
assets,
|
|
||||||
metadata,
|
|
||||||
isBlogPostPage = false,
|
|
||||||
} = props;
|
|
||||||
const {date, formattedDate, permalink, title, authors} = metadata;
|
|
||||||
|
|
||||||
const image = assets.image ?? frontMatter.image;
|
|
||||||
|
|
||||||
const TitleHeading = isBlogPostPage ? 'h1' : 'h2';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<BlogPostItemContainer className={styles.changelogItemContainer}>
|
||||||
className={!isBlogPostPage ? 'margin-bottom--md' : undefined}
|
<ChangelogItemHeader />
|
||||||
itemProp="blogPost"
|
<BlogPostItemContent>{children}</BlogPostItemContent>
|
||||||
itemScope
|
</BlogPostItemContainer>
|
||||||
itemType="http://schema.org/BlogPosting">
|
|
||||||
<header>
|
|
||||||
<TitleHeading
|
|
||||||
className={clsx(
|
|
||||||
isBlogPostPage ? styles.blogPostPageTitle : styles.blogPostTitle,
|
|
||||||
)}
|
|
||||||
itemProp="headline">
|
|
||||||
{isBlogPostPage ? (
|
|
||||||
title
|
|
||||||
) : (
|
|
||||||
<Link itemProp="url" to={permalink}>
|
|
||||||
{title}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</TitleHeading>
|
|
||||||
<div className={clsx(styles.blogPostData, 'margin-vert--md')}>
|
|
||||||
<time dateTime={date} itemProp="datePublished">
|
|
||||||
{formattedDate}
|
|
||||||
</time>
|
|
||||||
</div>
|
|
||||||
<ChangelogAuthors authors={authors} assets={assets} />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{image && (
|
|
||||||
<meta itemProp="image" content={withBaseUrl(image, {absolute: true})} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
// This ID is used for the feed generation to locate the main content
|
|
||||||
id={isBlogPostPage ? blogPostContainerID : undefined}
|
|
||||||
className="markdown"
|
|
||||||
itemProp="articleBody">
|
|
||||||
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,6 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.blogPostTitle {
|
.changelogItemContainer {
|
||||||
font-size: 2rem;
|
margin-bottom: 1rem;
|
||||||
}
|
|
||||||
|
|
||||||
.blogPostPageTitle {
|
|
||||||
font-size: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blogPostData {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blogPostDetailsFull {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/**
|
||||||
|
* 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 Translate from '@docusaurus/Translate';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
function TwitterLink() {
|
||||||
|
return (
|
||||||
|
<Link href="https://twitter.com/docusaurus" className={styles.twitter}>
|
||||||
|
<b>Twitter</b>
|
||||||
|
<svg
|
||||||
|
style={{
|
||||||
|
fill: '#1da1f2',
|
||||||
|
position: 'relative',
|
||||||
|
left: 4,
|
||||||
|
top: 1,
|
||||||
|
marginRight: 8,
|
||||||
|
}}
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 512 512">
|
||||||
|
<path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RssLink() {
|
||||||
|
return (
|
||||||
|
<Link href="pathname:///changelog/rss.xml" className={styles.rss}>
|
||||||
|
<b>
|
||||||
|
<Translate id="changelog.description.rssLink">RSS feeds</Translate>
|
||||||
|
</b>
|
||||||
|
<svg
|
||||||
|
style={{
|
||||||
|
fill: '#f26522',
|
||||||
|
position: 'relative',
|
||||||
|
left: 4,
|
||||||
|
top: 1,
|
||||||
|
marginRight: 8,
|
||||||
|
}}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248-1.796 0-3.252-1.454-3.252-3.248 0-1.794 1.456-3.248 3.252-3.248 1.795.001 3.251 1.454 3.251 3.248zm-6.503-12.572v4.811c6.05.062 10.96 4.966 11.022 11.009h4.817c-.062-8.71-7.118-15.758-15.839-15.82zm0-3.368c10.58.046 19.152 8.594 19.183 19.188h4.817c-.03-13.231-10.755-23.954-24-24v4.812z" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChangelogListHeader({
|
||||||
|
blogTitle,
|
||||||
|
}: {
|
||||||
|
blogTitle: string;
|
||||||
|
}): JSX.Element {
|
||||||
|
return (
|
||||||
|
<header className="margin-bottom--lg">
|
||||||
|
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
|
||||||
|
<p>
|
||||||
|
<Translate
|
||||||
|
id="changelog.description"
|
||||||
|
values={{
|
||||||
|
twitterLink: <TwitterLink />,
|
||||||
|
rssLink: <RssLink />,
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
'Subscribe through {rssLink} or follow us on {twitterLink} to stay up-to-date with new releases!'
|
||||||
|
}
|
||||||
|
</Translate>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,18 +5,6 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.blogPostTitle {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blogPostData {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blogPostDetailsFull {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rss,
|
.rss,
|
||||||
.rss:hover {
|
.rss:hover {
|
||||||
color: #f26522;
|
color: #f26522;
|
|
@ -7,8 +7,6 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Translate from '@docusaurus/Translate';
|
|
||||||
import Link from '@docusaurus/Link';
|
|
||||||
import {
|
import {
|
||||||
PageMetadata,
|
PageMetadata,
|
||||||
HtmlClassNameProvider,
|
HtmlClassNameProvider,
|
||||||
|
@ -16,12 +14,12 @@ import {
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
|
import BlogPostItems from '@theme/BlogPostItems';
|
||||||
import SearchMetadata from '@theme/SearchMetadata';
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
import ChangelogItem from '@theme/ChangelogItem';
|
import ChangelogItem from '@theme/ChangelogItem';
|
||||||
|
import ChangelogListHeader from '@theme/ChangelogList/Header';
|
||||||
import type {Props} from '@theme/BlogListPage';
|
import type {Props} from '@theme/BlogListPage';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
|
||||||
|
|
||||||
function ChangelogListMetadata(props: Props): JSX.Element {
|
function ChangelogListMetadata(props: Props): JSX.Element {
|
||||||
const {metadata} = props;
|
const {metadata} = props;
|
||||||
const {blogTitle, blogDescription} = metadata;
|
const {blogTitle, blogDescription} = metadata;
|
||||||
|
@ -36,78 +34,10 @@ function ChangelogListMetadata(props: Props): JSX.Element {
|
||||||
function ChangelogListContent(props: Props): JSX.Element {
|
function ChangelogListContent(props: Props): JSX.Element {
|
||||||
const {metadata, items, sidebar} = props;
|
const {metadata, items, sidebar} = props;
|
||||||
const {blogTitle} = metadata;
|
const {blogTitle} = metadata;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlogLayout sidebar={sidebar}>
|
<BlogLayout sidebar={sidebar}>
|
||||||
<header className="margin-bottom--lg">
|
<ChangelogListHeader blogTitle={blogTitle} />
|
||||||
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
|
<BlogPostItems items={items} component={ChangelogItem} />
|
||||||
<p>
|
|
||||||
<Translate
|
|
||||||
id="changelog.description"
|
|
||||||
values={{
|
|
||||||
twitterLink: (
|
|
||||||
<Link
|
|
||||||
href="https://twitter.com/docusaurus"
|
|
||||||
className={styles.twitter}>
|
|
||||||
<b>Twitter</b>
|
|
||||||
<svg
|
|
||||||
style={{
|
|
||||||
fill: '#1da1f2',
|
|
||||||
position: 'relative',
|
|
||||||
left: 4,
|
|
||||||
top: 1,
|
|
||||||
marginRight: 8,
|
|
||||||
}}
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 512 512">
|
|
||||||
<path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
rssLink: (
|
|
||||||
<Link
|
|
||||||
href="pathname:///changelog/rss.xml"
|
|
||||||
className={styles.rss}>
|
|
||||||
<b>
|
|
||||||
<Translate id="changelog.description.rssLink">
|
|
||||||
RSS feeds
|
|
||||||
</Translate>
|
|
||||||
</b>
|
|
||||||
<svg
|
|
||||||
style={{
|
|
||||||
fill: '#f26522',
|
|
||||||
position: 'relative',
|
|
||||||
left: 4,
|
|
||||||
top: 1,
|
|
||||||
marginRight: 8,
|
|
||||||
}}
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248-1.796 0-3.252-1.454-3.252-3.248 0-1.794 1.456-3.248 3.252-3.248 1.795.001 3.251 1.454 3.251 3.248zm-6.503-12.572v4.811c6.05.062 10.96 4.966 11.022 11.009h4.817c-.062-8.71-7.118-15.758-15.839-15.82zm0-3.368c10.58.046 19.152 8.594 19.183 19.188h4.817c-.03-13.231-10.755-23.954-24-24v4.812z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
}}>
|
|
||||||
{
|
|
||||||
'Subscribe through {rssLink} or follow us on {twitterLink} to stay up-to-date with new releases!'
|
|
||||||
}
|
|
||||||
</Translate>
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
{items.map(({content: BlogPostContent}) => (
|
|
||||||
<ChangelogItem
|
|
||||||
key={BlogPostContent.metadata.permalink}
|
|
||||||
frontMatter={BlogPostContent.frontMatter}
|
|
||||||
assets={BlogPostContent.assets}
|
|
||||||
metadata={BlogPostContent.metadata}
|
|
||||||
truncated={BlogPostContent.metadata.truncated}>
|
|
||||||
<BlogPostContent />
|
|
||||||
</ChangelogItem>
|
|
||||||
))}
|
|
||||||
<BlogListPaginator metadata={metadata} />
|
<BlogListPaginator metadata={metadata} />
|
||||||
</BlogLayout>
|
</BlogLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,103 +5,65 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {type ReactNode} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Translate from '@docusaurus/Translate';
|
import Translate from '@docusaurus/Translate';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import {
|
import {HtmlClassNameProvider, ThemeClassNames} from '@docusaurus/theme-common';
|
||||||
PageMetadata,
|
import {BlogPostProvider, useBlogPost} from '@docusaurus/theme-common/internal';
|
||||||
HtmlClassNameProvider,
|
import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
|
||||||
ThemeClassNames,
|
|
||||||
} from '@docusaurus/theme-common';
|
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import ChangelogItem from '@theme/ChangelogItem';
|
import ChangelogItem from '@theme/ChangelogItem';
|
||||||
import ChangelogPaginator from '@theme/ChangelogPaginator';
|
import ChangelogPaginator from '@theme/ChangelogPaginator';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
import type {Props} from '@theme/BlogPostPage';
|
import type {Props} from '@theme/BlogPostPage';
|
||||||
|
import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
function ChangelogPageMetadata(props: Props): JSX.Element {
|
function BackToIndexLink() {
|
||||||
const {content: BlogPostContents} = props;
|
const {metadata} = useBlogPost();
|
||||||
const {assets, metadata} = BlogPostContents;
|
// @ts-expect-error: we injected this
|
||||||
const {title, description, date, tags, authors, frontMatter} = metadata;
|
const {listPageLink} = metadata;
|
||||||
const {keywords} = frontMatter;
|
|
||||||
|
|
||||||
const image = assets.image ?? frontMatter.image;
|
|
||||||
return (
|
return (
|
||||||
<PageMetadata
|
<Link to={listPageLink}>
|
||||||
title={title}
|
<Translate id="changelog.backLink">← Back to index page</Translate>
|
||||||
description={description}
|
</Link>
|
||||||
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 {
|
function ChangelogPageContent({
|
||||||
const {content: BlogPostContents, sidebar} = props;
|
sidebar,
|
||||||
const {assets, metadata} = BlogPostContents;
|
children,
|
||||||
const {
|
}: {
|
||||||
nextItem,
|
sidebar: BlogSidebar;
|
||||||
prevItem,
|
children: ReactNode;
|
||||||
frontMatter,
|
}): JSX.Element {
|
||||||
// @ts-expect-error: we injected this
|
const {metadata, toc} = useBlogPost();
|
||||||
listPageLink,
|
const {nextItem, prevItem, frontMatter} = metadata;
|
||||||
} = metadata;
|
|
||||||
const {
|
const {
|
||||||
hide_table_of_contents: hideTableOfContents,
|
hide_table_of_contents: hideTableOfContents,
|
||||||
toc_min_heading_level: tocMinHeadingLevel,
|
toc_min_heading_level: tocMinHeadingLevel,
|
||||||
toc_max_heading_level: tocMaxHeadingLevel,
|
toc_max_heading_level: tocMaxHeadingLevel,
|
||||||
} = frontMatter;
|
} = frontMatter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BlogLayout
|
||||||
<PageMetadata />
|
sidebar={sidebar}
|
||||||
<BlogLayout
|
toc={
|
||||||
sidebar={sidebar}
|
!hideTableOfContents && toc.length > 0 ? (
|
||||||
toc={
|
<TOC
|
||||||
!hideTableOfContents && BlogPostContents.toc.length > 0 ? (
|
toc={toc}
|
||||||
<TOC
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
toc={BlogPostContents.toc}
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
minHeadingLevel={tocMinHeadingLevel}
|
/>
|
||||||
maxHeadingLevel={tocMaxHeadingLevel}
|
) : undefined
|
||||||
/>
|
}>
|
||||||
) : undefined
|
<BackToIndexLink />
|
||||||
}>
|
|
||||||
<Link to={listPageLink}>
|
|
||||||
<Translate id="changelog.backLink">← Back to index page</Translate>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<ChangelogItem
|
<ChangelogItem>{children}</ChangelogItem>
|
||||||
frontMatter={frontMatter}
|
|
||||||
assets={assets}
|
|
||||||
metadata={metadata}
|
|
||||||
isBlogPostPage>
|
|
||||||
<BlogPostContents />
|
|
||||||
</ChangelogItem>
|
|
||||||
|
|
||||||
{(nextItem || prevItem) && (
|
{(nextItem || prevItem) && (
|
||||||
<ChangelogPaginator nextItem={nextItem} prevItem={prevItem} />
|
<ChangelogPaginator nextItem={nextItem} prevItem={prevItem} />
|
||||||
)}
|
)}
|
||||||
</BlogLayout>
|
</BlogLayout>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,14 +71,19 @@ function ChangelogPageContent(props: Props): JSX.Element {
|
||||||
// own ChangelogItem. We don't want to apply the swizzled item to the actual
|
// own ChangelogItem. We don't want to apply the swizzled item to the actual
|
||||||
// blog.
|
// blog.
|
||||||
export default function ChangelogPage(props: Props): JSX.Element {
|
export default function ChangelogPage(props: Props): JSX.Element {
|
||||||
|
const ChangelogContent = props.content;
|
||||||
return (
|
return (
|
||||||
<HtmlClassNameProvider
|
<BlogPostProvider content={props.content} isBlogPostPage>
|
||||||
className={clsx(
|
<HtmlClassNameProvider
|
||||||
ThemeClassNames.wrapper.blogPages,
|
className={clsx(
|
||||||
ThemeClassNames.page.blogPostPage,
|
ThemeClassNames.wrapper.blogPages,
|
||||||
)}>
|
ThemeClassNames.page.blogPostPage,
|
||||||
<ChangelogPageMetadata {...props} />
|
)}>
|
||||||
<ChangelogPageContent {...props} />
|
<BlogPostPageMetadata />
|
||||||
</HtmlClassNameProvider>
|
<ChangelogPageContent sidebar={props.sidebar}>
|
||||||
|
<ChangelogContent />
|
||||||
|
</ChangelogPageContent>
|
||||||
|
</HtmlClassNameProvider>
|
||||||
|
</BlogPostProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
12
website/src/plugins/changelog/theme/types.d.ts
vendored
12
website/src/plugins/changelog/theme/types.d.ts
vendored
|
@ -5,10 +5,16 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare module '@theme/ChangelogItem';
|
|
||||||
declare module '@theme/ChangelogAuthors';
|
|
||||||
declare module '@theme/ChangelogAuthor';
|
|
||||||
declare module '@theme/ChangelogPaginator';
|
declare module '@theme/ChangelogPaginator';
|
||||||
|
|
||||||
|
declare module '@theme/ChangelogItem';
|
||||||
|
declare module '@theme/ChangelogItem/Header';
|
||||||
|
declare module '@theme/ChangelogItem/Header/Author';
|
||||||
|
declare module '@theme/ChangelogItem/Header/Authors';
|
||||||
|
|
||||||
|
declare module '@theme/ChangelogList';
|
||||||
|
declare module '@theme/ChangelogList/Header';
|
||||||
|
|
||||||
declare module '@theme/IconExpand' {
|
declare module '@theme/IconExpand' {
|
||||||
import type {ComponentProps} from 'react';
|
import type {ComponentProps} from 'react';
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue