diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap
index 11e9d9ff1cbf..6137538dad52 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap
@@ -37,7 +37,7 @@ exports[`paginateBlogPosts generates right pages 1`] = `
"page": 1,
"permalink": "/blog",
"postsPerPage": 2,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 5,
"totalPages": 3,
},
@@ -66,7 +66,7 @@ exports[`paginateBlogPosts generates right pages 1`] = `
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
- "nextPage": null,
+ "nextPage": undefined,
"page": 3,
"permalink": "/blog/page/3",
"postsPerPage": 2,
@@ -92,7 +92,7 @@ exports[`paginateBlogPosts generates right pages 2`] = `
"page": 1,
"permalink": "/",
"postsPerPage": 2,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 5,
"totalPages": 3,
},
@@ -121,7 +121,7 @@ exports[`paginateBlogPosts generates right pages 2`] = `
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
- "nextPage": null,
+ "nextPage": undefined,
"page": 3,
"permalink": "/page/3",
"postsPerPage": 2,
@@ -146,11 +146,11 @@ exports[`paginateBlogPosts generates right pages 3`] = `
"metadata": {
"blogDescription": "Blog Description",
"blogTitle": "Blog Title",
- "nextPage": null,
+ "nextPage": undefined,
"page": 1,
"permalink": "/",
"postsPerPage": 10,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 5,
"totalPages": 1,
},
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
index ec932459f47a..5fa35968ed25 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
@@ -182,6 +182,7 @@ exports[`rss has feed item for each post 1`] = `
Sat, 06 Mar 2021 00:00:00 GMT
https://validator.w3.org/feed/docs/rss2.html
https://github.com/jpmonette/feed
+ en
Copyright
-
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap
index 473ed4d5bdc3..1eab791d4450 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap
@@ -19,11 +19,11 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
- "nextPage": null,
+ "nextPage": undefined,
"page": 1,
"permalink": "/blog/tags/tag-1",
"postsPerPage": 3,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 3,
"totalPages": 1,
},
@@ -46,11 +46,11 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
- "nextPage": null,
+ "nextPage": undefined,
"page": 1,
"permalink": "/blog/tags/tag-2",
"postsPerPage": 2,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 2,
"totalPages": 1,
},
@@ -83,7 +83,7 @@ exports[`blog plugin works with blog tags 1`] = `
"page": 1,
"permalink": "/blog/tags/tag-1",
"postsPerPage": 2,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 3,
"totalPages": 2,
},
@@ -95,7 +95,7 @@ exports[`blog plugin works with blog tags 1`] = `
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
- "nextPage": null,
+ "nextPage": undefined,
"page": 2,
"permalink": "/blog/tags/tag-1/page/2",
"postsPerPage": 2,
@@ -122,11 +122,11 @@ exports[`blog plugin works with blog tags 1`] = `
"metadata": {
"blogDescription": "Blog",
"blogTitle": "Blog",
- "nextPage": null,
+ "nextPage": undefined,
"page": 1,
"permalink": "/blog/tags/tag-2",
"postsPerPage": 2,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 2,
"totalPages": 1,
},
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap
index e07e327f88f7..beb31a882566 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap
@@ -32,11 +32,11 @@ exports[`translateContent falls back when translation is incomplete 1`] = `
"metadata": {
"blogDescription": "Someone's random blog",
"blogTitle": "My blog",
- "nextPage": null,
+ "nextPage": undefined,
"page": 1,
"permalink": "/",
"postsPerPage": 10,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 1,
"totalPages": 1,
},
@@ -73,11 +73,11 @@ exports[`translateContent returns translated loaded 1`] = `
"metadata": {
"blogDescription": "Someone's random blog (translated)",
"blogTitle": "My blog (translated)",
- "nextPage": null,
+ "nextPage": undefined,
"page": 1,
"permalink": "/",
"postsPerPage": 10,
- "previousPage": null,
+ "previousPage": undefined,
"totalCount": 1,
"totalPages": 1,
},
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
index 99ac95ca49ed..03b4abb14d78 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
@@ -49,6 +49,7 @@ async function testGenerateFeeds(
options,
siteConfig: context.siteConfig,
outDir: context.outDir,
+ locale: 'en',
});
}
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts
index c6cfd5b5ae04..b6f950a10e53 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts
@@ -45,8 +45,8 @@ const sampleBlogContent: BlogContent = {
postsPerPage: 10,
totalPages: 1,
totalCount: 1,
- previousPage: null,
- nextPage: null,
+ previousPage: undefined,
+ nextPage: undefined,
blogTitle: sampleBlogOptions.blogTitle,
blogDescription: sampleBlogOptions.blogDescription,
},
diff --git a/packages/docusaurus-plugin-content-blog/src/authors.ts b/packages/docusaurus-plugin-content-blog/src/authors.ts
index 9e234d86d9a7..1de62ec0c8c6 100644
--- a/packages/docusaurus-plugin-content-blog/src/authors.ts
+++ b/packages/docusaurus-plugin-content-blog/src/authors.ts
@@ -70,7 +70,7 @@ type AuthorsParam = {
// We may want to deprecate those in favor of using only frontMatter.authors
function getFrontMatterAuthorLegacy(
frontMatter: BlogPostFrontMatter,
-): BlogPostFrontMatterAuthor | undefined {
+): Author | undefined {
const name = frontMatter.author;
const title = frontMatter.author_title ?? frontMatter.authorTitle;
const url = frontMatter.author_url ?? frontMatter.authorURL;
@@ -92,7 +92,7 @@ function normalizeFrontMatterAuthors(
frontMatterAuthors: BlogPostFrontMatterAuthors = [],
): BlogPostFrontMatterAuthor[] {
function normalizeAuthor(
- authorInput: string | BlogPostFrontMatterAuthor,
+ authorInput: string | Author,
): BlogPostFrontMatterAuthor {
if (typeof authorInput === 'string') {
// Technically, we could allow users to provide an author's name here, but
diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
index eefbf31cba85..ab24aa34ab92 100644
--- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
+++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts
@@ -88,8 +88,8 @@ export function paginateBlogPosts({
postsPerPage,
totalPages: numberOfPages,
totalCount,
- previousPage: page !== 0 ? permalink(page - 1) : null,
- nextPage: page < numberOfPages - 1 ? permalink(page + 1) : null,
+ previousPage: page !== 0 ? permalink(page - 1) : undefined,
+ nextPage: page < numberOfPages - 1 ? permalink(page + 1) : undefined,
blogDescription,
blogTitle,
},
diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts
index 58a59b4302e4..426f19df8db5 100644
--- a/packages/docusaurus-plugin-content-blog/src/feed.ts
+++ b/packages/docusaurus-plugin-content-blog/src/feed.ts
@@ -29,11 +29,13 @@ async function generateBlogFeed({
options,
siteConfig,
outDir,
+ locale,
}: {
blogPosts: BlogPost[];
options: PluginOptions;
siteConfig: DocusaurusConfig;
outDir: string;
+ locale: string;
}): Promise {
if (!blogPosts.length) {
return null;
@@ -47,11 +49,11 @@ async function generateBlogFeed({
const feed = new Feed({
id: blogBaseUrl,
- title: feedOptions.title || `${title} Blog`,
+ title: feedOptions.title ?? `${title} Blog`,
updated,
- language: feedOptions.language,
+ language: feedOptions.language ?? locale,
link: blogBaseUrl,
- description: feedOptions.description || `${siteConfig.title} Blog`,
+ description: feedOptions.description ?? `${siteConfig.title} Blog`,
favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
copyright: feedOptions.copyright,
});
@@ -140,17 +142,20 @@ export async function createBlogFeedFiles({
options,
siteConfig,
outDir,
+ locale,
}: {
blogPosts: BlogPost[];
options: PluginOptions;
siteConfig: DocusaurusConfig;
outDir: string;
+ locale: string;
}): Promise {
const feed = await generateBlogFeed({
blogPosts,
options,
siteConfig,
outDir,
+ locale,
});
const feedTypes = options.feedOptions.type;
diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts
index 2027f068ffb4..fca3a7d81b30 100644
--- a/packages/docusaurus-plugin-content-blog/src/index.ts
+++ b/packages/docusaurus-plugin-content-blog/src/index.ts
@@ -26,13 +26,9 @@ import type {
BlogTag,
BlogTags,
BlogContent,
- BlogItemsToMetadata,
- TagsModule,
BlogPaginated,
BlogContentPaths,
BlogMarkdownLoaderOptions,
- MetaData,
- TagModule,
} from './types';
import {PluginOptionSchema} from './pluginOptionSchema';
import type {
@@ -52,7 +48,9 @@ import {createBlogFeedFiles} from './feed';
import type {
PluginOptions,
BlogPostFrontMatter,
+ BlogPostMetadata,
Assets,
+ TagModule,
} from '@docusaurus/plugin-content-blog';
export default async function pluginContentBlog(
@@ -214,7 +212,7 @@ export default async function pluginContentBlog(
blogTagsListPath,
} = blogContents;
- const blogItemsToMetadata: BlogItemsToMetadata = {};
+ const blogItemsToMetadata: Record = {};
const sidebarBlogPosts =
options.blogSidebarCount === 'ALL'
@@ -325,11 +323,10 @@ export default async function pluginContentBlog(
return;
}
- const tagsModule: TagsModule = Object.fromEntries(
- Object.entries(blogTags).map(([tagKey, tag]) => {
+ const tagsModule: Record = Object.fromEntries(
+ Object.entries(blogTags).map(([, tag]) => {
const tagModule: TagModule = {
allTagsPath: blogTagsListPath,
- slug: tagKey,
name: tag.name,
count: tag.items.length,
permalink: tag.permalink,
@@ -479,7 +476,7 @@ export default async function pluginContentBlog(
metadata,
}: {
frontMatter: BlogPostFrontMatter;
- metadata: MetaData;
+ metadata: BlogPostMetadata;
}): Assets => ({
image: frontMatter.image,
authorsImageUrls: metadata.authors.map(
@@ -512,6 +509,7 @@ export default async function pluginContentBlog(
options,
outDir,
siteConfig,
+ locale: currentLocale,
});
},
diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
index 411f95ced3e3..b369cd978644 100644
--- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
+++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts
@@ -11,184 +11,487 @@ declare module '@docusaurus/plugin-content-blog' {
import type {Overwrite} from 'utility-types';
export interface Assets {
+ /**
+ * If `metadata.image` is a collocated image path, this entry will be the
+ * bundler-generated image path. Otherwise, it's empty, and the image URL
+ * should be accessed through `frontMatter.image`.
+ */
image?: string;
- authorsImageUrls: (string | undefined)[]; // Array of same size as the original MetaData.authors array
+ /**
+ * Array where each item is 1-1 correlated with the `metadata.authors` array
+ * so that client can render the correct author images. If the author's
+ * image is a local file path, the slot will be filled with the bundler-
+ * generated image path; otherwise, it's empty, and the author's image URL
+ * should be accessed through `authors.imageURL`.
+ */
+ authorsImageUrls: (string | undefined)[];
}
- // We allow passing custom fields to authors, e.g., twitter
+ /**
+ * Unknown keys are allowed, so that we can pass custom fields to authors,
+ * e.g., `twitter`.
+ */
export interface Author extends Record {
+ /**
+ * If `name` doesn't exist, an `imageURL` is expected.
+ */
name?: string;
+ /**
+ * The image path could be collocated, in which case
+ * `metadata.assets.authorsImageUrls` should be used instead. If `imageURL`
+ * doesn't exist, a `name` is expected.
+ */
imageURL?: string;
+ /**
+ * Used to generate the author's link.
+ */
url?: string;
+ /**
+ * Used as a subtitle for the author, e.g. "maintainer of Docusaurus"
+ */
title?: string;
+ /**
+ * Mainly used for RSS feeds; if `url` doesn't exist, `email` can be used
+ * to generate a fallback `mailto:` URL.
+ */
email?: string;
}
+ /**
+ * Everything is partial/unnormalized, because front matter is always
+ * preserved as-is. Default values will be applied when generating metadata
+ */
export type BlogPostFrontMatter = {
+ /**
+ * @deprecated Use `slug` instead.
+ */
id?: string;
+ /**
+ * Will override the default title collected from h1 heading.
+ * @see {@link BlogPostMetadata.title}
+ */
title?: string;
+ /**
+ * Will override the default excerpt.
+ * @see {@link BlogPostMetadata.description}
+ */
description?: string;
+ /**
+ * Front matter tags, unnormalized.
+ * @see {@link BlogPostMetadata.tags}
+ */
tags?: FrontMatterTag[];
+ /**
+ * Custom slug appended after ///
+ * @see {@link BlogPostMetadata.slug}
+ */
slug?: string;
+ /**
+ * Marks the post as draft and excludes it from the production build.
+ */
draft?: boolean;
- date?: Date | string; // Yaml automatically convert some string patterns as Date, but not all
-
+ /**
+ * Will override the default publish date inferred from git/filename. Yaml
+ * only converts standard yyyy-MM-dd format to dates, so this may stay as a
+ * plain string.
+ * @see {@link BlogPostMetadata.date}
+ */
+ date?: Date | string;
+ /**
+ * Authors, unnormalized.
+ * @see {@link BlogPostMetadata.authors}
+ */
authors?: BlogPostFrontMatterAuthors;
-
- // We may want to deprecate those older author front matter fields later:
+ /**
+ * To be deprecated
+ * @see {@link BlogPostFrontMatterAuthor.name}
+ */
author?: string;
+ /**
+ * To be deprecated
+ * @see {@link BlogPostFrontMatterAuthor.title}
+ */
author_title?: string;
+ /**
+ * To be deprecated
+ * @see {@link BlogPostFrontMatterAuthor.url}
+ */
author_url?: string;
+ /**
+ * To be deprecated
+ * @see {@link BlogPostFrontMatterAuthor.imageURL}
+ */
author_image_url?: string;
- /** @deprecated */
+ /** @deprecated v1 legacy */
authorTitle?: string;
- /** @deprecated */
+ /** @deprecated v1 legacy */
authorURL?: string;
- /** @deprecated */
+ /** @deprecated v1 legacy */
authorImageURL?: string;
+ /**
+ * @see {@link BlogPostMetadata.image}
+ */
image?: string;
+ /**
+ * Used in the head meta
+ */
keywords?: string[];
+ /**
+ * Hide the right TOC
+ */
hide_table_of_contents?: boolean;
+ /**
+ * Minimum TOC heading level
+ */
toc_min_heading_level?: number;
+ /**
+ * Maximum TOC heading level
+ */
toc_max_heading_level?: number;
};
- export type BlogPostFrontMatterAuthor = Record & {
+ export type BlogPostFrontMatterAuthor = Author & {
+ /**
+ * Will be normalized into the `imageURL` prop.
+ */
+ image_url?: string;
+ /**
+ * References an existing author in the authors map.
+ */
key?: string;
- name?: string;
- imageURL?: string;
- url?: string;
- title?: string;
};
- // All the possible variants that the user can use for convenience
+ /**
+ * Blog post authors can be declared in front matter as a string key
+ * (referencing an author in authors map), an object (partially overriding the
+ * data in authors map, or a completely new author), or an array of a mix of
+ * both.
+ */
export type BlogPostFrontMatterAuthors =
| string
| BlogPostFrontMatterAuthor
| (string | BlogPostFrontMatterAuthor)[];
+ export type BlogPostMetadata = {
+ /**
+ * Path to the Markdown source, with `@site` alias.
+ */
+ readonly source: string;
+ /**
+ * Used to generate the page h1 heading, tab title, and pagination title.
+ */
+ readonly title: string;
+ /**
+ * The publish date of the post. On client side, this will be serialized
+ * into a string.
+ */
+ readonly date: Date;
+ /**
+ * Publish date formatted according to the locale, so that the client can
+ * render the date regardless of the existence of `Intl.DateTimeFormat`.
+ */
+ readonly formattedDate: string;
+ /**
+ * Full link including base URL.
+ */
+ readonly permalink: string;
+ /**
+ * Description used in the meta. Could be an empty string (empty content)
+ */
+ readonly description: string;
+ /**
+ * Absolute URL to the editing page of the post. Undefined if the post
+ * shouldn't be edited.
+ */
+ readonly editUrl?: string;
+ /**
+ * Reading time in minutes calculated based on word count.
+ */
+ readonly readingTime?: number;
+ /**
+ * Whether the truncate marker exists in the post's content.
+ */
+ readonly truncated?: boolean;
+ /**
+ * Used in pagination. Generated after the other metadata, so not readonly.
+ * Content is just a subset of another post's metadata.
+ */
+ nextItem?: {readonly title: string; readonly permalink: string};
+ /**
+ * Used in pagination. Generated after the other metadata, so not readonly.
+ * Content is just a subset of another post's metadata.
+ */
+ prevItem?: {readonly title: string; readonly permalink: string};
+ /**
+ * Author metadata, normalized. Should be used in joint with
+ * `assets.authorsImageUrls` on client side.
+ */
+ readonly authors: Author[];
+ /**
+ * Front matter, as-is.
+ */
+ readonly frontMatter: BlogPostFrontMatter & Record;
+ /**
+ * Tags, normalized.
+ */
+ readonly tags: readonly {
+ readonly label: string;
+ readonly permalink: string;
+ }[];
+ };
+ /**
+ * @returns The edit URL that's directly plugged into metadata.
+ */
export type EditUrlFunction = (editUrlParams: {
+ /**
+ * The root content directory containing this post file, relative to the
+ * site path. Usually the same as `options.path` but can be localized
+ */
blogDirPath: string;
+ /**
+ * Path to this post file, relative to `blogDirPath`
+ */
blogPath: string;
+ /**
+ * @see {@link BlogPostMetadata.permalink}
+ */
permalink: string;
+ /**
+ * Locale name.
+ */
locale: string;
}) => string | undefined;
export type FeedType = 'rss' | 'atom' | 'json';
+ /**
+ * Normalized feed options used within code.
+ */
export type FeedOptions = {
+ /** If `null`, no feed is generated. */
type?: FeedType[] | null;
+ /** Title of generated feed. */
title?: string;
+ /** Description of generated feed. */
description?: string;
+ /** Copyright notice. Required because the feed library marked it that. */
copyright: string;
+ /** Language of the feed. */
language?: string;
};
- // Feed options, as provided by user config
- export type UserFeedOptions = Overwrite<
- Partial,
- {type?: FeedOptions['type'] | 'all'} // Handle the type: "all" shortcut
- >;
- // Duplicate from ngryman/reading-time to keep stability of API
+ /**
+ * Duplicate from ngryman/reading-time to keep stability of API.
+ */
type ReadingTimeOptions = {
wordsPerMinute?: number;
+ /**
+ * @param char The character to be matched.
+ * @returns `true` if this character is a word bound.
+ */
wordBound?: (char: string) => boolean;
};
+ /**
+ * Represents the default reading time implementation.
+ * @returns The reading time directly plugged into metadata.
+ */
export type ReadingTimeFunction = (params: {
+ /** Markdown content. */
content: string;
+ /** Front matter. */
frontMatter?: BlogPostFrontMatter & Record;
+ /** Options accepted by ngryman/reading-time. */
options?: ReadingTimeOptions;
}) => number;
+ /**
+ * @returns The reading time directly plugged into metadata. `undefined` to
+ * hide reading time for a specific post.
+ */
export type ReadingTimeFunctionOption = (
+ /**
+ * The `options` is not provided by the caller; the user can inject their
+ * own option values into `defaultReadingTime`
+ */
params: Required[0], 'options'>> & {
+ /**
+ * The default reading time implementation from ngryman/reading-time.
+ */
defaultReadingTime: ReadingTimeFunction;
},
) => number | undefined;
-
+ /**
+ * Plugin options after normalization.
+ */
export type PluginOptions = RemarkAndRehypePluginOptions & {
+ /** Plugin ID. */
id?: string;
+ /**
+ * Path to the blog content directory on the file system, relative to site
+ * directory.
+ */
path: string;
+ /**
+ * URL route for the blog section of your site. **DO NOT** include a
+ * trailing slash. Use `/` to put the blog at root path.
+ */
routeBasePath: string;
+ /**
+ * URL route for the tags section of your blog. Will be appended to
+ * `routeBasePath`. **DO NOT** include a trailing slash.
+ */
tagsBasePath: string;
+ /**
+ * URL route for the archive section of your blog. Will be appended to
+ * `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to
+ * disable generation of archive.
+ */
archiveBasePath: string | null;
+ /**
+ * Array of glob patterns matching Markdown files to be built, relative to
+ * the content path.
+ */
include: string[];
+ /**
+ * Array of glob patterns matching Markdown files to be excluded. Serves as
+ * refinement based on the `include` option.
+ */
exclude: string[];
+ /**
+ * Number of posts to show per page in the listing page. Use `'ALL'` to
+ * display all posts on one listing page.
+ */
postsPerPage: number | 'ALL';
+ /** Root component of the blog listing page. */
blogListComponent: string;
+ /** Root component of each blog post page. */
blogPostComponent: string;
+ /** Root component of the tags list page. */
blogTagsListComponent: string;
+ /** Root component of the "posts containing tag" page. */
blogTagsPostsComponent: string;
+ /** Root component of the blog archive page. */
blogArchiveComponent: string;
+ /** Blog page title for better SEO. */
blogTitle: string;
+ /** Blog page meta description for better SEO. */
blogDescription: string;
+ /**
+ * Number of blog post elements to show in the blog sidebar. `'ALL'` to show
+ * all blog posts; `0` to disable.
+ */
blogSidebarCount: number | 'ALL';
+ /** Title of the blog sidebar. */
blogSidebarTitle: string;
+ /** Truncate marker marking where the summary ends. */
truncateMarker: RegExp;
+ /** Show estimated reading time for the blog post. */
showReadingTime: boolean;
- feedOptions: {
- type?: FeedType[] | null;
- title?: string;
- description?: string;
- copyright: string;
- language?: string;
- };
+ /** Blog feed. */
+ feedOptions: FeedOptions;
+ /**
+ * Base URL to edit your site. The final URL is computed by `editUrl +
+ * relativePostPath`. Using a function allows more nuanced control for each
+ * file. Omitting this variable entirely will disable edit links.
+ */
editUrl?: string | EditUrlFunction;
+ /**
+ * The edit URL will target the localized file, instead of the original
+ * unlocalized file. Ignored when `editUrl` is a function.
+ */
editLocalizedFiles?: boolean;
admonitions: Record;
+ /** Path to the authors map file, relative to the blog content directory. */
authorsMapPath: string;
+ /** A callback to customize the reading time number displayed. */
readingTime: ReadingTimeFunctionOption;
+ /** Governs the direction of blog post sorting. */
sortPosts: 'ascending' | 'descending';
};
- // Options, as provided in the user config (before normalization)
+
+ /**
+ * Feed options, as provided by user config. `type` accepts `all` as shortcut
+ */
+ export type UserFeedOptions = Overwrite<
+ Partial,
+ {
+ /** Type of feed to be generated. Use `null` to disable generation. */
+ type?: FeedOptions['type'] | 'all' | FeedType;
+ }
+ >;
+ /**
+ * Options as provided in the user config (before normalization)
+ */
export type Options = Overwrite<
Partial,
- {feedOptions?: UserFeedOptions}
+ {
+ /** Blog feed. */
+ feedOptions?: UserFeedOptions;
+ }
>;
+
+ export type TagModule = {
+ /** Permalink of the tag's own page. */
+ permalink: string;
+ /** Name of the tag. */
+ name: string;
+ /** Number of posts with this tag. */
+ count: number;
+ /** The tags list page. */
+ allTagsPath: string;
+ };
+
+ export type BlogSidebar = {
+ title: string;
+ items: {title: string; permalink: string}[];
+ };
}
declare module '@theme/BlogPostPage' {
- import type {BlogSidebar} from '@theme/BlogSidebar';
import type {TOCItem} from '@docusaurus/types';
import type {
BlogPostFrontMatter,
- Author,
+ BlogPostMetadata,
Assets,
+ BlogSidebar,
} from '@docusaurus/plugin-content-blog';
+ import type {Overwrite} from 'utility-types';
export type FrontMatter = BlogPostFrontMatter;
- export type Metadata = {
- readonly title: string;
- readonly date: string;
- readonly formattedDate: string;
- readonly permalink: string;
- readonly description?: string;
- readonly editUrl?: string;
- readonly readingTime?: number;
- readonly truncated?: string;
- readonly nextItem?: {readonly title: string; readonly permalink: string};
- readonly prevItem?: {readonly title: string; readonly permalink: string};
- readonly authors: Author[];
- readonly frontMatter: FrontMatter & Record;
- readonly tags: readonly {
- readonly label: string;
- readonly permalink: string;
- }[];
- };
+ export type Metadata = Overwrite<
+ BlogPostMetadata,
+ {
+ /** The publish date of the post. Serialized from the `Date` object. */
+ date: string;
+ }
+ >;
export type Content = {
+ // TODO remove this. `metadata.frontMatter` is preferred because it can be
+ // accessed in enhanced plugins
+ /** Same as `metadata.frontMatter` */
readonly frontMatter: FrontMatter;
+ /**
+ * Usually image assets that may be collocated like `./img/thumbnail.png`.
+ * The loader would also bundle these assets and the client should use these
+ * in priority.
+ */
readonly assets: Assets;
+ /** Metadata of the post. */
readonly metadata: Metadata;
+ /** A list of TOC items (headings). */
readonly toc: readonly TOCItem[];
+ /** Renders the actual MDX content. */
(): JSX.Element;
};
export interface Props {
+ /** Blog sidebar. */
readonly sidebar: BlogSidebar;
+ /** Content of this post as an MDX component, with useful metadata. */
readonly content: Content;
}
@@ -197,23 +500,38 @@ declare module '@theme/BlogPostPage' {
declare module '@theme/BlogListPage' {
import type {Content} from '@theme/BlogPostPage';
- import type {BlogSidebar} from '@theme/BlogSidebar';
+ import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
export type Metadata = {
+ /** Title of the entire blog. */
readonly blogTitle: string;
+ /** Blog description. */
readonly blogDescription: string;
+ /** Permalink to the next list page. */
readonly nextPage?: string;
- readonly page: number;
+ /** Permalink of the current page. */
readonly permalink: string;
- readonly postsPerPage: number;
+ /** Permalink to the previous list page. */
readonly previousPage?: string;
+ /** Index of the current page, 1-based. */
+ readonly page: number;
+ /** Posts displayed on each list page. */
+ readonly postsPerPage: number;
+ /** Total number of posts in the entire blog. */
readonly totalCount: number;
+ /** Total number of list pages. */
readonly totalPages: number;
};
export interface Props {
+ /** Blog sidebar. */
readonly sidebar: BlogSidebar;
+ /** Metadata of the current listing page. */
readonly metadata: Metadata;
+ /**
+ * Array of blog posts included on this page. Every post's metadata is also
+ * available.
+ */
readonly items: readonly {readonly content: Content}[];
}
@@ -221,34 +539,34 @@ declare module '@theme/BlogListPage' {
}
declare module '@theme/BlogTagsListPage' {
- import type {BlogSidebar} from '@theme/BlogSidebar';
-
- export type Tag = {
- permalink: string;
- name: string;
- count: number;
- allTagsPath: string;
- slug: string;
- };
+ import type {BlogSidebar, TagModule} from '@docusaurus/plugin-content-blog';
export interface Props {
+ /** Blog sidebar. */
readonly sidebar: BlogSidebar;
- readonly tags: Readonly>;
+ /** A map from tag names to the full tag module. */
+ readonly tags: Readonly>;
}
export default function BlogTagsListPage(props: Props): JSX.Element;
}
declare module '@theme/BlogTagsPostsPage' {
- import type {BlogSidebar} from '@theme/BlogSidebar';
- import type {Tag} from '@theme/BlogTagsListPage';
+ import type {BlogSidebar, TagModule} from '@docusaurus/plugin-content-blog';
import type {Content} from '@theme/BlogPostPage';
import type {Metadata} from '@theme/BlogListPage';
export interface Props {
+ /** Blog sidebar. */
readonly sidebar: BlogSidebar;
- readonly metadata: Tag;
+ /** Metadata of this tag. */
+ readonly metadata: TagModule;
+ /** Looks exactly the same as the posts list page */
readonly listMetadata: Metadata;
+ /**
+ * Array of blog posts included on this page. Every post's metadata is also
+ * available.
+ */
readonly items: readonly {readonly content: Content}[];
}
@@ -258,10 +576,13 @@ declare module '@theme/BlogTagsPostsPage' {
declare module '@theme/BlogArchivePage' {
import type {Content} from '@theme/BlogPostPage';
+ /** We may add extra metadata or prune some metadata from here */
export type ArchiveBlogPost = Content;
export interface Props {
+ /** The entirety of the blog's data. */
readonly archive: {
+ /** All posts. Can select any useful data/metadata to render. */
readonly blogPosts: readonly ArchiveBlogPost[];
};
}
diff --git a/packages/docusaurus-plugin-content-blog/src/types.ts b/packages/docusaurus-plugin-content-blog/src/types.ts
index 54dc73c501b0..451f571d9342 100644
--- a/packages/docusaurus-plugin-content-blog/src/types.ts
+++ b/packages/docusaurus-plugin-content-blog/src/types.ts
@@ -5,15 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
-import type {Tag} from '@docusaurus/utils';
import type {
BrokenMarkdownLink,
ContentPaths,
} from '@docusaurus/utils/lib/markdownLinks';
-import type {
- BlogPostFrontMatter,
- Author,
-} from '@docusaurus/plugin-content-blog';
+import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog';
+import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage';
export type BlogContentPaths = ContentPaths;
@@ -42,65 +39,15 @@ export interface BlogTag {
export interface BlogPost {
id: string;
- metadata: MetaData;
+ metadata: BlogPostMetadata;
content: string;
}
-export interface BlogPaginatedMetadata {
- permalink: string;
- page: number;
- postsPerPage: number;
- totalPages: number;
- totalCount: number;
- previousPage: string | null;
- nextPage: string | null;
- blogTitle: string;
- blogDescription: string;
-}
-
export interface BlogPaginated {
metadata: BlogPaginatedMetadata;
items: string[]; // blog post permalinks
}
-export interface MetaData {
- permalink: string;
- source: string;
- description: string;
- date: Date;
- formattedDate: string;
- tags: Tag[];
- title: string;
- readingTime?: number;
- prevItem?: Paginator;
- nextItem?: Paginator;
- truncated: boolean;
- editUrl?: string;
- authors: Author[];
- frontMatter: BlogPostFrontMatter & Record;
-}
-
-export interface Paginator {
- title: string;
- permalink: string;
-}
-
-export interface BlogItemsToMetadata {
- [key: string]: MetaData;
-}
-
-export interface TagsModule {
- [key: string]: TagModule;
-}
-
-export interface TagModule {
- allTagsPath: string;
- slug: string;
- name: string;
- count: number;
- permalink: string;
-}
-
export type BlogBrokenMarkdownLink = BrokenMarkdownLink;
export type BlogMarkdownLoaderOptions = {
siteDir: string;
diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
index bd926b51379d..f6973773c9b3 100644
--- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts
+++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
@@ -41,11 +41,7 @@ declare module '@theme/BlogListPaginator' {
}
declare module '@theme/BlogSidebar' {
- export type BlogSidebarItem = {title: string; permalink: string};
- export type BlogSidebar = {
- title: string;
- items: BlogSidebarItem[];
- };
+ import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
export interface Props {
readonly sidebar: BlogSidebar;
@@ -106,7 +102,7 @@ declare module '@theme/BlogPostPaginator' {
declare module '@theme/BlogLayout' {
import type {ReactNode} from 'react';
import type {Props as LayoutProps} from '@theme/Layout';
- import type {BlogSidebar} from '@theme/BlogSidebar';
+ import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
export interface Props extends LayoutProps {
readonly sidebar?: BlogSidebar;
diff --git a/packages/docusaurus-theme-classic/src/theme/BlogLayout/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogLayout/index.tsx
index 08c183746090..009eef93276d 100644
--- a/packages/docusaurus-theme-classic/src/theme/BlogLayout/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/BlogLayout/index.tsx
@@ -22,7 +22,7 @@ export default function BlogLayout(props: Props): JSX.Element {
{hasSidebar && (
)}
= {
* override the other
*/
export function groupTaggedItems- (
- items: Item[],
- getItemTags: (item: Item) => Tag[],
+ items: readonly Item[],
+ getItemTags: (item: Item) => readonly Tag[],
): Record> {
const result: Record> = {};
diff --git a/website/docs/api/plugins/plugin-content-blog.md b/website/docs/api/plugins/plugin-content-blog.md
index 7eda77338a4c..dbd658b6812e 100644
--- a/website/docs/api/plugins/plugin-content-blog.md
+++ b/website/docs/api/plugins/plugin-content-blog.md
@@ -37,32 +37,32 @@ Accepted fields:
| Name | Type | Default | Description |
| --- | --- | --- | --- |
-| `path` | `string` | `'blog'` | Path to the blog content directory on the filesystem, relative to site dir. |
-| `editUrl` |
string \| EditUrlFunction
| `undefined` | Base URL to edit your site. The final URL is computed by `editUrl + relativeDocPath`. Using a function allows more nuanced control for each file. Omitting this variable entirely will disable edit links. |
+| `path` | `string` | `'blog'` | Path to the blog content directory on the file system, relative to site dir. |
+| `editUrl` | string \| EditUrlFunction
| `undefined` | Base URL to edit your site. The final URL is computed by `editUrl + relativePostPath`. Using a function allows more nuanced control for each file. Omitting this variable entirely will disable edit links. |
| `editLocalizedFiles` | `boolean` | `false` | The edit URL will target the localized file, instead of the original unlocalized file. Ignored when `editUrl` is a function. |
| `blogTitle` | `string` | `'Blog'` | Blog page title for better SEO. |
| `blogDescription` | `string` | `'Blog'` | Blog page meta description for better SEO. |
-| `blogSidebarCount` | number \| 'ALL'
| `5` | Number of blog post elements to show in the blog sidebar. `'ALL'` to show all blog posts; `0` to disable |
+| `blogSidebarCount` | number \| 'ALL'
| `5` | Number of blog post elements to show in the blog sidebar. `'ALL'` to show all blog posts; `0` to disable. |
| `blogSidebarTitle` | `string` | `'Recent posts'` | Title of the blog sidebar. |
| `routeBasePath` | `string` | `'blog'` | URL route for the blog section of your site. **DO NOT** include a trailing slash. Use `/` to put the blog at root path. |
-| `tagsBasePath` | `string` | `'tags'` | URL route for the tags list page of your site. It is prepended to the `routeBasePath`. |
-| `archiveBasePath` | string \| null
| `'/archive'` | URL route for the archive blog section of your site. It is prepended to the `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to disable generation of archive. |
-| `include` | `string[]` | `['**/*.{md,mdx}']` | Matching files will be included and processed. |
-| `exclude` | `string[]` | _See example configuration_ | No route will be created for matching files. |
+| `tagsBasePath` | `string` | `'tags'` | URL route for the tags section of your blog. Will be appended to `routeBasePath`. **DO NOT** include a trailing slash. |
+| `archiveBasePath` | string \| null
| `'archive'` | URL route for the archive section of your blog. Will be appended to `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to disable generation of archive. |
+| `include` | `string[]` | `['**/*.{md,mdx}']` | Array of glob patterns matching Markdown files to be built, relative to the content path. |
+| `exclude` | `string[]` | _See example configuration_ | Array of glob patterns matching Markdown files to be excluded. Serves as refinement based on the `include` option. |
| `postsPerPage` | number \| 'ALL'
| `10` | Number of posts to show per page in the listing page. Use `'ALL'` to display all posts on one listing page. |
| `blogListComponent` | `string` | `'@theme/BlogListPage'` | Root component of the blog listing page. |
| `blogPostComponent` | `string` | `'@theme/BlogPostPage'` | Root component of each blog post page. |
-| `blogTagsListComponent` | `string` | `'@theme/BlogTagsListPage'` | Root component of the tags list page |
+| `blogTagsListComponent` | `string` | `'@theme/BlogTagsListPage'` | Root component of the tags list page. |
| `blogTagsPostsComponent` | `string` | `'@theme/BlogTagsPostsPage'` | Root component of the "posts containing tag" page. |
| `blogArchiveComponent` | `string` | `'@theme/BlogArchivePage'` | Root component of the blog archive page. |
| `remarkPlugins` | `any[]` | `[]` | Remark plugins passed to MDX. |
| `rehypePlugins` | `any[]` | `[]` | Rehype plugins passed to MDX. |
| `beforeDefaultRemarkPlugins` | `any[]` | `[]` | Custom Remark plugins passed to MDX before the default Docusaurus Remark plugins. |
| `beforeDefaultRehypePlugins` | `any[]` | `[]` | Custom Rehype plugins passed to MDX before the default Docusaurus Rehype plugins. |
-| `truncateMarker` | `string` | `//` | Truncate marker, can be a regex or string. |
+| `truncateMarker` | `RegExp` | `//` | Truncate marker marking where the summary ends. |
| `showReadingTime` | `boolean` | `true` | Show estimated reading time for the blog post. |
| `readingTime` | `ReadingTimeFunctionOption` | The default reading time | A callback to customize the reading time number displayed. |
-| `authorsMapPath` | `string` | `'authors.yml'` | Path to the authors map file, relative to the blog content directory specified with `path`. Can also be a `json` file. |
+| `authorsMapPath` | `string` | `'authors.yml'` | Path to the authors map file, relative to the blog content directory. |
| `feedOptions` | _See below_ | `{type: ['rss', 'atom']}` | Blog feed. |
| `feedOptions.type` | FeedType \| FeedType[] \| 'all' \| null
| **Required** | Type of feed to be generated. Use `null` to disable generation. |
| `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. |