refactor(content-blog): clean up type definitions; in-code documentation (#6922)

* refactor(content-blog): clean up type definitions; in-code documentation

* add doc
This commit is contained in:
Joshua Chen 2022-03-16 19:36:57 +08:00 committed by GitHub
parent 46b1027c4a
commit 8d1c1954c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 509 additions and 240 deletions

View file

@ -37,7 +37,7 @@ exports[`paginateBlogPosts generates right pages 1`] = `
"page": 1, "page": 1,
"permalink": "/blog", "permalink": "/blog",
"postsPerPage": 2, "postsPerPage": 2,
"previousPage": null, "previousPage": undefined,
"totalCount": 5, "totalCount": 5,
"totalPages": 3, "totalPages": 3,
}, },
@ -66,7 +66,7 @@ exports[`paginateBlogPosts generates right pages 1`] = `
"metadata": { "metadata": {
"blogDescription": "Blog Description", "blogDescription": "Blog Description",
"blogTitle": "Blog Title", "blogTitle": "Blog Title",
"nextPage": null, "nextPage": undefined,
"page": 3, "page": 3,
"permalink": "/blog/page/3", "permalink": "/blog/page/3",
"postsPerPage": 2, "postsPerPage": 2,
@ -92,7 +92,7 @@ exports[`paginateBlogPosts generates right pages 2`] = `
"page": 1, "page": 1,
"permalink": "/", "permalink": "/",
"postsPerPage": 2, "postsPerPage": 2,
"previousPage": null, "previousPage": undefined,
"totalCount": 5, "totalCount": 5,
"totalPages": 3, "totalPages": 3,
}, },
@ -121,7 +121,7 @@ exports[`paginateBlogPosts generates right pages 2`] = `
"metadata": { "metadata": {
"blogDescription": "Blog Description", "blogDescription": "Blog Description",
"blogTitle": "Blog Title", "blogTitle": "Blog Title",
"nextPage": null, "nextPage": undefined,
"page": 3, "page": 3,
"permalink": "/page/3", "permalink": "/page/3",
"postsPerPage": 2, "postsPerPage": 2,
@ -146,11 +146,11 @@ exports[`paginateBlogPosts generates right pages 3`] = `
"metadata": { "metadata": {
"blogDescription": "Blog Description", "blogDescription": "Blog Description",
"blogTitle": "Blog Title", "blogTitle": "Blog Title",
"nextPage": null, "nextPage": undefined,
"page": 1, "page": 1,
"permalink": "/", "permalink": "/",
"postsPerPage": 10, "postsPerPage": 10,
"previousPage": null, "previousPage": undefined,
"totalCount": 5, "totalCount": 5,
"totalPages": 1, "totalPages": 1,
}, },

View file

@ -182,6 +182,7 @@ exports[`rss has feed item for each post 1`] = `
<lastBuildDate>Sat, 06 Mar 2021 00:00:00 GMT</lastBuildDate> <lastBuildDate>Sat, 06 Mar 2021 00:00:00 GMT</lastBuildDate>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs> <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<generator>https://github.com/jpmonette/feed</generator> <generator>https://github.com/jpmonette/feed</generator>
<language>en</language>
<copyright>Copyright</copyright> <copyright>Copyright</copyright>
<item> <item>
<title><![CDATA[MDX Blog Sample with require calls]]></title> <title><![CDATA[MDX Blog Sample with require calls]]></title>

View file

@ -19,11 +19,11 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"metadata": { "metadata": {
"blogDescription": "Blog", "blogDescription": "Blog",
"blogTitle": "Blog", "blogTitle": "Blog",
"nextPage": null, "nextPage": undefined,
"page": 1, "page": 1,
"permalink": "/blog/tags/tag-1", "permalink": "/blog/tags/tag-1",
"postsPerPage": 3, "postsPerPage": 3,
"previousPage": null, "previousPage": undefined,
"totalCount": 3, "totalCount": 3,
"totalPages": 1, "totalPages": 1,
}, },
@ -46,11 +46,11 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"metadata": { "metadata": {
"blogDescription": "Blog", "blogDescription": "Blog",
"blogTitle": "Blog", "blogTitle": "Blog",
"nextPage": null, "nextPage": undefined,
"page": 1, "page": 1,
"permalink": "/blog/tags/tag-2", "permalink": "/blog/tags/tag-2",
"postsPerPage": 2, "postsPerPage": 2,
"previousPage": null, "previousPage": undefined,
"totalCount": 2, "totalCount": 2,
"totalPages": 1, "totalPages": 1,
}, },
@ -83,7 +83,7 @@ exports[`blog plugin works with blog tags 1`] = `
"page": 1, "page": 1,
"permalink": "/blog/tags/tag-1", "permalink": "/blog/tags/tag-1",
"postsPerPage": 2, "postsPerPage": 2,
"previousPage": null, "previousPage": undefined,
"totalCount": 3, "totalCount": 3,
"totalPages": 2, "totalPages": 2,
}, },
@ -95,7 +95,7 @@ exports[`blog plugin works with blog tags 1`] = `
"metadata": { "metadata": {
"blogDescription": "Blog", "blogDescription": "Blog",
"blogTitle": "Blog", "blogTitle": "Blog",
"nextPage": null, "nextPage": undefined,
"page": 2, "page": 2,
"permalink": "/blog/tags/tag-1/page/2", "permalink": "/blog/tags/tag-1/page/2",
"postsPerPage": 2, "postsPerPage": 2,
@ -122,11 +122,11 @@ exports[`blog plugin works with blog tags 1`] = `
"metadata": { "metadata": {
"blogDescription": "Blog", "blogDescription": "Blog",
"blogTitle": "Blog", "blogTitle": "Blog",
"nextPage": null, "nextPage": undefined,
"page": 1, "page": 1,
"permalink": "/blog/tags/tag-2", "permalink": "/blog/tags/tag-2",
"postsPerPage": 2, "postsPerPage": 2,
"previousPage": null, "previousPage": undefined,
"totalCount": 2, "totalCount": 2,
"totalPages": 1, "totalPages": 1,
}, },

View file

@ -32,11 +32,11 @@ exports[`translateContent falls back when translation is incomplete 1`] = `
"metadata": { "metadata": {
"blogDescription": "Someone's random blog", "blogDescription": "Someone's random blog",
"blogTitle": "My blog", "blogTitle": "My blog",
"nextPage": null, "nextPage": undefined,
"page": 1, "page": 1,
"permalink": "/", "permalink": "/",
"postsPerPage": 10, "postsPerPage": 10,
"previousPage": null, "previousPage": undefined,
"totalCount": 1, "totalCount": 1,
"totalPages": 1, "totalPages": 1,
}, },
@ -73,11 +73,11 @@ exports[`translateContent returns translated loaded 1`] = `
"metadata": { "metadata": {
"blogDescription": "Someone's random blog (translated)", "blogDescription": "Someone's random blog (translated)",
"blogTitle": "My blog (translated)", "blogTitle": "My blog (translated)",
"nextPage": null, "nextPage": undefined,
"page": 1, "page": 1,
"permalink": "/", "permalink": "/",
"postsPerPage": 10, "postsPerPage": 10,
"previousPage": null, "previousPage": undefined,
"totalCount": 1, "totalCount": 1,
"totalPages": 1, "totalPages": 1,
}, },

View file

@ -49,6 +49,7 @@ async function testGenerateFeeds(
options, options,
siteConfig: context.siteConfig, siteConfig: context.siteConfig,
outDir: context.outDir, outDir: context.outDir,
locale: 'en',
}); });
} }

View file

@ -45,8 +45,8 @@ const sampleBlogContent: BlogContent = {
postsPerPage: 10, postsPerPage: 10,
totalPages: 1, totalPages: 1,
totalCount: 1, totalCount: 1,
previousPage: null, previousPage: undefined,
nextPage: null, nextPage: undefined,
blogTitle: sampleBlogOptions.blogTitle, blogTitle: sampleBlogOptions.blogTitle,
blogDescription: sampleBlogOptions.blogDescription, blogDescription: sampleBlogOptions.blogDescription,
}, },

View file

@ -70,7 +70,7 @@ type AuthorsParam = {
// We may want to deprecate those in favor of using only frontMatter.authors // We may want to deprecate those in favor of using only frontMatter.authors
function getFrontMatterAuthorLegacy( function getFrontMatterAuthorLegacy(
frontMatter: BlogPostFrontMatter, frontMatter: BlogPostFrontMatter,
): BlogPostFrontMatterAuthor | undefined { ): Author | undefined {
const name = frontMatter.author; const name = frontMatter.author;
const title = frontMatter.author_title ?? frontMatter.authorTitle; const title = frontMatter.author_title ?? frontMatter.authorTitle;
const url = frontMatter.author_url ?? frontMatter.authorURL; const url = frontMatter.author_url ?? frontMatter.authorURL;
@ -92,7 +92,7 @@ function normalizeFrontMatterAuthors(
frontMatterAuthors: BlogPostFrontMatterAuthors = [], frontMatterAuthors: BlogPostFrontMatterAuthors = [],
): BlogPostFrontMatterAuthor[] { ): BlogPostFrontMatterAuthor[] {
function normalizeAuthor( function normalizeAuthor(
authorInput: string | BlogPostFrontMatterAuthor, authorInput: string | Author,
): BlogPostFrontMatterAuthor { ): BlogPostFrontMatterAuthor {
if (typeof authorInput === 'string') { if (typeof authorInput === 'string') {
// Technically, we could allow users to provide an author's name here, but // Technically, we could allow users to provide an author's name here, but

View file

@ -88,8 +88,8 @@ export function paginateBlogPosts({
postsPerPage, postsPerPage,
totalPages: numberOfPages, totalPages: numberOfPages,
totalCount, totalCount,
previousPage: page !== 0 ? permalink(page - 1) : null, previousPage: page !== 0 ? permalink(page - 1) : undefined,
nextPage: page < numberOfPages - 1 ? permalink(page + 1) : null, nextPage: page < numberOfPages - 1 ? permalink(page + 1) : undefined,
blogDescription, blogDescription,
blogTitle, blogTitle,
}, },

View file

@ -29,11 +29,13 @@ async function generateBlogFeed({
options, options,
siteConfig, siteConfig,
outDir, outDir,
locale,
}: { }: {
blogPosts: BlogPost[]; blogPosts: BlogPost[];
options: PluginOptions; options: PluginOptions;
siteConfig: DocusaurusConfig; siteConfig: DocusaurusConfig;
outDir: string; outDir: string;
locale: string;
}): Promise<Feed | null> { }): Promise<Feed | null> {
if (!blogPosts.length) { if (!blogPosts.length) {
return null; return null;
@ -47,11 +49,11 @@ async function generateBlogFeed({
const feed = new Feed({ const feed = new Feed({
id: blogBaseUrl, id: blogBaseUrl,
title: feedOptions.title || `${title} Blog`, title: feedOptions.title ?? `${title} Blog`,
updated, updated,
language: feedOptions.language, language: feedOptions.language ?? locale,
link: blogBaseUrl, link: blogBaseUrl,
description: feedOptions.description || `${siteConfig.title} Blog`, description: feedOptions.description ?? `${siteConfig.title} Blog`,
favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined, favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
copyright: feedOptions.copyright, copyright: feedOptions.copyright,
}); });
@ -140,17 +142,20 @@ export async function createBlogFeedFiles({
options, options,
siteConfig, siteConfig,
outDir, outDir,
locale,
}: { }: {
blogPosts: BlogPost[]; blogPosts: BlogPost[];
options: PluginOptions; options: PluginOptions;
siteConfig: DocusaurusConfig; siteConfig: DocusaurusConfig;
outDir: string; outDir: string;
locale: string;
}): Promise<void> { }): Promise<void> {
const feed = await generateBlogFeed({ const feed = await generateBlogFeed({
blogPosts, blogPosts,
options, options,
siteConfig, siteConfig,
outDir, outDir,
locale,
}); });
const feedTypes = options.feedOptions.type; const feedTypes = options.feedOptions.type;

View file

@ -26,13 +26,9 @@ import type {
BlogTag, BlogTag,
BlogTags, BlogTags,
BlogContent, BlogContent,
BlogItemsToMetadata,
TagsModule,
BlogPaginated, BlogPaginated,
BlogContentPaths, BlogContentPaths,
BlogMarkdownLoaderOptions, BlogMarkdownLoaderOptions,
MetaData,
TagModule,
} from './types'; } from './types';
import {PluginOptionSchema} from './pluginOptionSchema'; import {PluginOptionSchema} from './pluginOptionSchema';
import type { import type {
@ -52,7 +48,9 @@ import {createBlogFeedFiles} from './feed';
import type { import type {
PluginOptions, PluginOptions,
BlogPostFrontMatter, BlogPostFrontMatter,
BlogPostMetadata,
Assets, Assets,
TagModule,
} from '@docusaurus/plugin-content-blog'; } from '@docusaurus/plugin-content-blog';
export default async function pluginContentBlog( export default async function pluginContentBlog(
@ -214,7 +212,7 @@ export default async function pluginContentBlog(
blogTagsListPath, blogTagsListPath,
} = blogContents; } = blogContents;
const blogItemsToMetadata: BlogItemsToMetadata = {}; const blogItemsToMetadata: Record<string, BlogPostMetadata> = {};
const sidebarBlogPosts = const sidebarBlogPosts =
options.blogSidebarCount === 'ALL' options.blogSidebarCount === 'ALL'
@ -325,11 +323,10 @@ export default async function pluginContentBlog(
return; return;
} }
const tagsModule: TagsModule = Object.fromEntries( const tagsModule: Record<string, TagModule> = Object.fromEntries(
Object.entries(blogTags).map(([tagKey, tag]) => { Object.entries(blogTags).map(([, tag]) => {
const tagModule: TagModule = { const tagModule: TagModule = {
allTagsPath: blogTagsListPath, allTagsPath: blogTagsListPath,
slug: tagKey,
name: tag.name, name: tag.name,
count: tag.items.length, count: tag.items.length,
permalink: tag.permalink, permalink: tag.permalink,
@ -479,7 +476,7 @@ export default async function pluginContentBlog(
metadata, metadata,
}: { }: {
frontMatter: BlogPostFrontMatter; frontMatter: BlogPostFrontMatter;
metadata: MetaData; metadata: BlogPostMetadata;
}): Assets => ({ }): Assets => ({
image: frontMatter.image, image: frontMatter.image,
authorsImageUrls: metadata.authors.map( authorsImageUrls: metadata.authors.map(
@ -512,6 +509,7 @@ export default async function pluginContentBlog(
options, options,
outDir, outDir,
siteConfig, siteConfig,
locale: currentLocale,
}); });
}, },

View file

@ -11,184 +11,487 @@ declare module '@docusaurus/plugin-content-blog' {
import type {Overwrite} from 'utility-types'; import type {Overwrite} from 'utility-types';
export interface Assets { 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; 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<string, unknown> { export interface Author extends Record<string, unknown> {
/**
* If `name` doesn't exist, an `imageURL` is expected.
*/
name?: string; 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; imageURL?: string;
/**
* Used to generate the author's link.
*/
url?: string; url?: string;
/**
* Used as a subtitle for the author, e.g. "maintainer of Docusaurus"
*/
title?: string; title?: string;
/**
* Mainly used for RSS feeds; if `url` doesn't exist, `email` can be used
* to generate a fallback `mailto:` URL.
*/
email?: string; 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 = { export type BlogPostFrontMatter = {
/**
* @deprecated Use `slug` instead.
*/
id?: string; id?: string;
/**
* Will override the default title collected from h1 heading.
* @see {@link BlogPostMetadata.title}
*/
title?: string; title?: string;
/**
* Will override the default excerpt.
* @see {@link BlogPostMetadata.description}
*/
description?: string; description?: string;
/**
* Front matter tags, unnormalized.
* @see {@link BlogPostMetadata.tags}
*/
tags?: FrontMatterTag[]; tags?: FrontMatterTag[];
/**
* Custom slug appended after /<baseUrl>/<routeBasePath>/
* @see {@link BlogPostMetadata.slug}
*/
slug?: string; slug?: string;
/**
* Marks the post as draft and excludes it from the production build.
*/
draft?: boolean; 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; authors?: BlogPostFrontMatterAuthors;
/**
// We may want to deprecate those older author front matter fields later: * To be deprecated
* @see {@link BlogPostFrontMatterAuthor.name}
*/
author?: string; author?: string;
/**
* To be deprecated
* @see {@link BlogPostFrontMatterAuthor.title}
*/
author_title?: string; author_title?: string;
/**
* To be deprecated
* @see {@link BlogPostFrontMatterAuthor.url}
*/
author_url?: string; author_url?: string;
/**
* To be deprecated
* @see {@link BlogPostFrontMatterAuthor.imageURL}
*/
author_image_url?: string; author_image_url?: string;
/** @deprecated */ /** @deprecated v1 legacy */
authorTitle?: string; authorTitle?: string;
/** @deprecated */ /** @deprecated v1 legacy */
authorURL?: string; authorURL?: string;
/** @deprecated */ /** @deprecated v1 legacy */
authorImageURL?: string; authorImageURL?: string;
/**
* @see {@link BlogPostMetadata.image}
*/
image?: string; image?: string;
/**
* Used in the head meta
*/
keywords?: string[]; keywords?: string[];
/**
* Hide the right TOC
*/
hide_table_of_contents?: boolean; hide_table_of_contents?: boolean;
/**
* Minimum TOC heading level
*/
toc_min_heading_level?: number; toc_min_heading_level?: number;
/**
* Maximum TOC heading level
*/
toc_max_heading_level?: number; toc_max_heading_level?: number;
}; };
export type BlogPostFrontMatterAuthor = Record<string, unknown> & { export type BlogPostFrontMatterAuthor = Author & {
/**
* Will be normalized into the `imageURL` prop.
*/
image_url?: string;
/**
* References an existing author in the authors map.
*/
key?: string; 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 = export type BlogPostFrontMatterAuthors =
| string | string
| BlogPostFrontMatterAuthor | BlogPostFrontMatterAuthor
| (string | BlogPostFrontMatterAuthor)[]; | (string | BlogPostFrontMatterAuthor)[];
export type EditUrlFunction = (editUrlParams: { export type BlogPostMetadata = {
blogDirPath: string; /**
blogPath: string; * Path to the Markdown source, with `@site` alias.
permalink: string; */
locale: string; readonly source: string;
}) => string | undefined; /**
* Used to generate the page h1 heading, tab title, and pagination title.
export type FeedType = 'rss' | 'atom' | 'json'; */
export type FeedOptions = {
type?: FeedType[] | null;
title?: string;
description?: string;
copyright: string;
language?: string;
};
// Feed options, as provided by user config
export type UserFeedOptions = Overwrite<
Partial<FeedOptions>,
{type?: FeedOptions['type'] | 'all'} // Handle the type: "all" shortcut
>;
// Duplicate from ngryman/reading-time to keep stability of API
type ReadingTimeOptions = {
wordsPerMinute?: number;
wordBound?: (char: string) => boolean;
};
export type ReadingTimeFunction = (params: {
content: string;
frontMatter?: BlogPostFrontMatter & Record<string, unknown>;
options?: ReadingTimeOptions;
}) => number;
export type ReadingTimeFunctionOption = (
params: Required<Omit<Parameters<ReadingTimeFunction>[0], 'options'>> & {
defaultReadingTime: ReadingTimeFunction;
},
) => number | undefined;
export type PluginOptions = RemarkAndRehypePluginOptions & {
id?: string;
path: string;
routeBasePath: string;
tagsBasePath: string;
archiveBasePath: string | null;
include: string[];
exclude: string[];
postsPerPage: number | 'ALL';
blogListComponent: string;
blogPostComponent: string;
blogTagsListComponent: string;
blogTagsPostsComponent: string;
blogArchiveComponent: string;
blogTitle: string;
blogDescription: string;
blogSidebarCount: number | 'ALL';
blogSidebarTitle: string;
truncateMarker: RegExp;
showReadingTime: boolean;
feedOptions: {
type?: FeedType[] | null;
title?: string;
description?: string;
copyright: string;
language?: string;
};
editUrl?: string | EditUrlFunction;
editLocalizedFiles?: boolean;
admonitions: Record<string, unknown>;
authorsMapPath: string;
readingTime: ReadingTimeFunctionOption;
sortPosts: 'ascending' | 'descending';
};
// Options, as provided in the user config (before normalization)
export type Options = Overwrite<
Partial<PluginOptions>,
{feedOptions?: UserFeedOptions}
>;
}
declare module '@theme/BlogPostPage' {
import type {BlogSidebar} from '@theme/BlogSidebar';
import type {TOCItem} from '@docusaurus/types';
import type {
BlogPostFrontMatter,
Author,
Assets,
} from '@docusaurus/plugin-content-blog';
export type FrontMatter = BlogPostFrontMatter;
export type Metadata = {
readonly title: string; readonly title: string;
readonly date: 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; readonly formattedDate: string;
/**
* Full link including base URL.
*/
readonly permalink: string; readonly permalink: string;
readonly description?: 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; readonly editUrl?: string;
/**
* Reading time in minutes calculated based on word count.
*/
readonly readingTime?: number; readonly readingTime?: number;
readonly truncated?: string; /**
readonly nextItem?: {readonly title: string; readonly permalink: string}; * Whether the truncate marker exists in the post's content.
readonly prevItem?: {readonly title: string; readonly permalink: string}; */
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[]; readonly authors: Author[];
readonly frontMatter: FrontMatter & Record<string, unknown>; /**
* Front matter, as-is.
*/
readonly frontMatter: BlogPostFrontMatter & Record<string, unknown>;
/**
* Tags, normalized.
*/
readonly tags: readonly { readonly tags: readonly {
readonly label: string; readonly label: string;
readonly permalink: 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;
};
/**
* 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<string, unknown>;
/** 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<Omit<Parameters<ReadingTimeFunction>[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;
/** 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<string, unknown>;
/** 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';
};
/**
* Feed options, as provided by user config. `type` accepts `all` as shortcut
*/
export type UserFeedOptions = Overwrite<
Partial<FeedOptions>,
{
/** 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<PluginOptions>,
{
/** 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 {TOCItem} from '@docusaurus/types';
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,
{
/** The publish date of the post. Serialized from the `Date` object. */
date: string;
}
>;
export type Content = { 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; 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; readonly assets: Assets;
/** Metadata of the post. */
readonly metadata: Metadata; readonly metadata: Metadata;
/** A list of TOC items (headings). */
readonly toc: readonly TOCItem[]; readonly toc: readonly TOCItem[];
/** Renders the actual MDX content. */
(): JSX.Element; (): JSX.Element;
}; };
export interface Props { export interface Props {
/** Blog sidebar. */
readonly sidebar: BlogSidebar; readonly sidebar: BlogSidebar;
/** Content of this post as an MDX component, with useful metadata. */
readonly content: Content; readonly content: Content;
} }
@ -197,23 +500,38 @@ declare module '@theme/BlogPostPage' {
declare module '@theme/BlogListPage' { declare module '@theme/BlogListPage' {
import type {Content} from '@theme/BlogPostPage'; import type {Content} from '@theme/BlogPostPage';
import type {BlogSidebar} from '@theme/BlogSidebar'; import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
export type Metadata = { export type Metadata = {
/** Title of the entire blog. */
readonly blogTitle: string; readonly blogTitle: string;
/** Blog description. */
readonly blogDescription: string; readonly blogDescription: string;
/** Permalink to the next list page. */
readonly nextPage?: string; readonly nextPage?: string;
readonly page: number; /** Permalink of the current page. */
readonly permalink: string; readonly permalink: string;
readonly postsPerPage: number; /** Permalink to the previous list page. */
readonly previousPage?: string; 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; readonly totalCount: number;
/** Total number of list pages. */
readonly totalPages: number; readonly totalPages: number;
}; };
export interface Props { export interface Props {
/** Blog sidebar. */
readonly sidebar: BlogSidebar; readonly sidebar: BlogSidebar;
/** Metadata of the current listing page. */
readonly metadata: Metadata; readonly metadata: Metadata;
/**
* Array of blog posts included on this page. Every post's metadata is also
* available.
*/
readonly items: readonly {readonly content: Content}[]; readonly items: readonly {readonly content: Content}[];
} }
@ -221,34 +539,34 @@ declare module '@theme/BlogListPage' {
} }
declare module '@theme/BlogTagsListPage' { declare module '@theme/BlogTagsListPage' {
import type {BlogSidebar} from '@theme/BlogSidebar'; import type {BlogSidebar, TagModule} from '@docusaurus/plugin-content-blog';
export type Tag = {
permalink: string;
name: string;
count: number;
allTagsPath: string;
slug: string;
};
export interface Props { export interface Props {
/** Blog sidebar. */
readonly sidebar: BlogSidebar; readonly sidebar: BlogSidebar;
readonly tags: Readonly<Record<string, Tag>>; /** A map from tag names to the full tag module. */
readonly tags: Readonly<Record<string, TagModule>>;
} }
export default function BlogTagsListPage(props: Props): JSX.Element; export default function BlogTagsListPage(props: Props): JSX.Element;
} }
declare module '@theme/BlogTagsPostsPage' { declare module '@theme/BlogTagsPostsPage' {
import type {BlogSidebar} from '@theme/BlogSidebar'; import type {BlogSidebar, TagModule} from '@docusaurus/plugin-content-blog';
import type {Tag} from '@theme/BlogTagsListPage';
import type {Content} from '@theme/BlogPostPage'; import type {Content} from '@theme/BlogPostPage';
import type {Metadata} from '@theme/BlogListPage'; import type {Metadata} from '@theme/BlogListPage';
export interface Props { export interface Props {
/** Blog sidebar. */
readonly sidebar: BlogSidebar; 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; readonly listMetadata: Metadata;
/**
* Array of blog posts included on this page. Every post's metadata is also
* available.
*/
readonly items: readonly {readonly content: Content}[]; readonly items: readonly {readonly content: Content}[];
} }
@ -258,10 +576,13 @@ declare module '@theme/BlogTagsPostsPage' {
declare module '@theme/BlogArchivePage' { declare module '@theme/BlogArchivePage' {
import type {Content} from '@theme/BlogPostPage'; import type {Content} from '@theme/BlogPostPage';
/** We may add extra metadata or prune some metadata from here */
export type ArchiveBlogPost = Content; export type ArchiveBlogPost = Content;
export interface Props { export interface Props {
/** The entirety of the blog's data. */
readonly archive: { readonly archive: {
/** All posts. Can select any useful data/metadata to render. */
readonly blogPosts: readonly ArchiveBlogPost[]; readonly blogPosts: readonly ArchiveBlogPost[];
}; };
} }

View file

@ -5,15 +5,12 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import type {Tag} from '@docusaurus/utils';
import type { import type {
BrokenMarkdownLink, BrokenMarkdownLink,
ContentPaths, ContentPaths,
} from '@docusaurus/utils/lib/markdownLinks'; } from '@docusaurus/utils/lib/markdownLinks';
import type { import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog';
BlogPostFrontMatter, import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage';
Author,
} from '@docusaurus/plugin-content-blog';
export type BlogContentPaths = ContentPaths; export type BlogContentPaths = ContentPaths;
@ -42,65 +39,15 @@ export interface BlogTag {
export interface BlogPost { export interface BlogPost {
id: string; id: string;
metadata: MetaData; metadata: BlogPostMetadata;
content: string; 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 { export interface BlogPaginated {
metadata: BlogPaginatedMetadata; metadata: BlogPaginatedMetadata;
items: string[]; // blog post permalinks 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<string, unknown>;
}
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<BlogContentPaths>; export type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths>;
export type BlogMarkdownLoaderOptions = { export type BlogMarkdownLoaderOptions = {
siteDir: string; siteDir: string;

View file

@ -41,11 +41,7 @@ declare module '@theme/BlogListPaginator' {
} }
declare module '@theme/BlogSidebar' { declare module '@theme/BlogSidebar' {
export type BlogSidebarItem = {title: string; permalink: string}; import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
export type BlogSidebar = {
title: string;
items: BlogSidebarItem[];
};
export interface Props { export interface Props {
readonly sidebar: BlogSidebar; readonly sidebar: BlogSidebar;
@ -106,7 +102,7 @@ declare module '@theme/BlogPostPaginator' {
declare module '@theme/BlogLayout' { declare module '@theme/BlogLayout' {
import type {ReactNode} from 'react'; import type {ReactNode} from 'react';
import type {Props as LayoutProps} from '@theme/Layout'; 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 { export interface Props extends LayoutProps {
readonly sidebar?: BlogSidebar; readonly sidebar?: BlogSidebar;

View file

@ -22,7 +22,7 @@ export default function BlogLayout(props: Props): JSX.Element {
<div className="row"> <div className="row">
{hasSidebar && ( {hasSidebar && (
<aside className="col col--3"> <aside className="col col--3">
<BlogSidebar sidebar={sidebar!} /> <BlogSidebar sidebar={sidebar} />
</aside> </aside>
)} )}
<main <main

View file

@ -70,8 +70,8 @@ export type TaggedItemGroup<Item> = {
* override the other * override the other
*/ */
export function groupTaggedItems<Item>( export function groupTaggedItems<Item>(
items: Item[], items: readonly Item[],
getItemTags: (item: Item) => Tag[], getItemTags: (item: Item) => readonly Tag[],
): Record<string, TaggedItemGroup<Item>> { ): Record<string, TaggedItemGroup<Item>> {
const result: Record<string, TaggedItemGroup<Item>> = {}; const result: Record<string, TaggedItemGroup<Item>> = {};

View file

@ -37,32 +37,32 @@ Accepted fields:
| Name | Type | Default | Description | | Name | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `path` | `string` | `'blog'` | Path to the blog content directory on the filesystem, relative to site dir. | | `path` | `string` | `'blog'` | Path to the blog content directory on the file system, relative to site dir. |
| `editUrl` | <code>string \| EditUrlFunction</code> | `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. | | `editUrl` | <code>string \| EditUrlFunction</code> | `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. | | `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. | | `blogTitle` | `string` | `'Blog'` | Blog page title for better SEO. |
| `blogDescription` | `string` | `'Blog'` | Blog page meta description for better SEO. | | `blogDescription` | `string` | `'Blog'` | Blog page meta description for better SEO. |
| `blogSidebarCount` | <code>number \| 'ALL'</code> | `5` | Number of blog post elements to show in the blog sidebar. `'ALL'` to show all blog posts; `0` to disable | | `blogSidebarCount` | <code>number \| 'ALL'</code> | `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. | | `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. | | `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`. | | `tagsBasePath` | `string` | `'tags'` | URL route for the tags section of your blog. Will be appended to `routeBasePath`. **DO NOT** include a trailing slash. |
| `archiveBasePath` | <code>string \| null</code> | `'/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. | | `archiveBasePath` | <code>string \| null</code> | `'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}']` | Matching files will be included and processed. | | `include` | `string[]` | `['**/*.{md,mdx}']` | Array of glob patterns matching Markdown files to be built, relative to the content path. |
| `exclude` | `string[]` | _See example configuration_ | No route will be created for matching files. | | `exclude` | `string[]` | _See example configuration_ | Array of glob patterns matching Markdown files to be excluded. Serves as refinement based on the `include` option. |
| `postsPerPage` | <code>number \| 'ALL'</code> | `10` | Number of posts to show per page in the listing page. Use `'ALL'` to display all posts on one listing page. | | `postsPerPage` | <code>number \| 'ALL'</code> | `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. | | `blogListComponent` | `string` | `'@theme/BlogListPage'` | Root component of the blog listing page. |
| `blogPostComponent` | `string` | `'@theme/BlogPostPage'` | Root component of each blog post 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. | | `blogTagsPostsComponent` | `string` | `'@theme/BlogTagsPostsPage'` | Root component of the "posts containing tag" page. |
| `blogArchiveComponent` | `string` | `'@theme/BlogArchivePage'` | Root component of the blog archive page. | | `blogArchiveComponent` | `string` | `'@theme/BlogArchivePage'` | Root component of the blog archive page. |
| `remarkPlugins` | `any[]` | `[]` | Remark plugins passed to MDX. | | `remarkPlugins` | `any[]` | `[]` | Remark plugins passed to MDX. |
| `rehypePlugins` | `any[]` | `[]` | Rehype 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. | | `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. | | `beforeDefaultRehypePlugins` | `any[]` | `[]` | Custom Rehype plugins passed to MDX before the default Docusaurus Rehype plugins. |
| `truncateMarker` | `string` | `/<!--\s*(truncate)\s*-->/` | Truncate marker, can be a regex or string. | | `truncateMarker` | `RegExp` | `/<!--\s*(truncate)\s*-->/` | Truncate marker marking where the summary ends. |
| `showReadingTime` | `boolean` | `true` | Show estimated reading time for the blog post. | | `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. | | `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` | _See below_ | `{type: ['rss', 'atom']}` | Blog feed. |
| `feedOptions.type` | <code>FeedType \| FeedType[] \| 'all' \| null</code> | **Required** | Type of feed to be generated. Use `null` to disable generation. | | `feedOptions.type` | <code>FeedType \| FeedType[] \| 'all' \| null</code> | **Required** | Type of feed to be generated. Use `null` to disable generation. |
| `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. | | `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. |