+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx
new file mode 100644
index 000000000000..75f725cc75cc
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx
@@ -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 (
+
+
+ Read More
+
+
+ );
+}
+
+export default function BlogPostItemFooterReadMoreLink(
+ props: Props,
+): JSX.Element {
+ const {blogPostTitle, ...linkProps} = props;
+ return (
+
+
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx
new file mode 100644
index 000000000000..314faacf2d98
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/index.tsx
@@ -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 (
+
+ );
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/styles.module.css b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/styles.module.css
new file mode 100644
index 000000000000..f9272fb53b69
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Footer/styles.module.css
@@ -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;
+}
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostAuthor/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/index.tsx
similarity index 83%
rename from packages/docusaurus-theme-classic/src/theme/BlogPostAuthor/index.tsx
rename to packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/index.tsx
index 8dce39bd3e06..92ace43f3220 100644
--- a/packages/docusaurus-theme-classic/src/theme/BlogPostAuthor/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Author/index.tsx
@@ -6,8 +6,10 @@
*/
import React from 'react';
+import clsx from 'clsx';
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 {
if (props.href) {
@@ -16,11 +18,14 @@ function MaybeLink(props: LinkProps): JSX.Element {
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 link = url || (email && `mailto:${email}`) || undefined;
return (
-
+
{imageURL && (
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostAuthors/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Authors/index.tsx
similarity index 74%
rename from packages/docusaurus-theme-classic/src/theme/BlogPostAuthors/index.tsx
rename to packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Authors/index.tsx
index 99386d4ae900..d1ed5bb11cdd 100644
--- a/packages/docusaurus-theme-classic/src/theme/BlogPostAuthors/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/BlogPostItem/Header/Authors/index.tsx
@@ -7,16 +7,19 @@
import React from 'react';
import clsx from 'clsx';
-import BlogPostAuthor from '@theme/BlogPostAuthor';
-import type {Props} from '@theme/BlogPostAuthors';
-
+import {useBlogPost} from '@docusaurus/theme-common/internal';
+import BlogPostItemHeaderAuthor from '@theme/BlogPostItem/Header/Author';
+import type {Props} from '@theme/BlogPostItem/Header/Authors';
import styles from './styles.module.css';
// Component responsible for the authors layout
-export default function BlogPostAuthors({
- authors,
- assets,
+export default function BlogPostItemHeaderAuthors({
+ className,
}: Props): JSX.Element | null {
+ const {
+ metadata: {authors},
+ assets,
+ } = useBlogPost();
const authorsCount = authors.length;
if (authorsCount === 0) {
return null;
@@ -27,6 +30,7 @@ export default function BlogPostAuthors({
className={clsx(
'margin-top--md margin-bottom--sm',
imageOnly ? styles.imageOnlyAuthorRow : 'row',
+ className,
)}>
{authors.map((author, idx) => (
- {
+ 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 (
+
+ );
+}
+
+function Spacer() {
+ return <>{' · '}>;
+}
+
+export default function BlogPostItemHeaderInfo({
+ className,
+}: Props): JSX.Element {
+ const {metadata} = useBlogPost();
+ const {date, formattedDate, readingTime} = metadata;
+
+ return (
+