refactor(content-{blog,docs}): unify handling of tags (#7117)

This commit is contained in:
Joshua Chen 2022-04-07 21:58:21 +08:00 committed by GitHub
parent ca718ccac0
commit 1156be3f20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 170 additions and 178 deletions

View file

@ -8,7 +8,7 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"/another/tags", "/another/tags",
"/another/tags2", "/another/tags2",
], ],
"name": "tag1", "label": "tag1",
"pages": [ "pages": [
{ {
"items": [ "items": [
@ -36,7 +36,7 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
"/another/tags", "/another/tags",
"/another/tags2", "/another/tags2",
], ],
"name": "tag2", "label": "tag2",
"pages": [ "pages": [
{ {
"items": [ "items": [
@ -69,7 +69,7 @@ exports[`blog plugin works with blog tags 1`] = `
"/another/tags", "/another/tags",
"/another/tags2", "/another/tags2",
], ],
"name": "tag1", "label": "tag1",
"pages": [ "pages": [
{ {
"items": [ "items": [
@ -112,7 +112,7 @@ exports[`blog plugin works with blog tags 1`] = `
"/another/tags", "/another/tags",
"/another/tags2", "/another/tags2",
], ],
"name": "tag2", "label": "tag2",
"pages": [ "pages": [
{ {
"items": [ "items": [

View file

@ -114,7 +114,7 @@ export function getBlogTags({
); );
return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({ return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({
name: tag.label, label: tag.label,
items: tagBlogPosts.map((item) => item.id), items: tagBlogPosts.map((item) => item.id),
permalink: tag.permalink, permalink: tag.permalink,
pages: paginateBlogPosts({ pages: paginateBlogPosts({

View file

@ -30,7 +30,13 @@ import type {
BlogContentPaths, BlogContentPaths,
BlogMarkdownLoaderOptions, BlogMarkdownLoaderOptions,
} from './types'; } from './types';
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types'; import type {
LoadContext,
Plugin,
HtmlTags,
TagsListItem,
TagModule,
} from '@docusaurus/types';
import { import {
generateBlogPosts, generateBlogPosts,
getSourceToPermalink, getSourceToPermalink,
@ -43,7 +49,6 @@ import type {
BlogPostFrontMatter, BlogPostFrontMatter,
BlogPostMetadata, BlogPostMetadata,
Assets, Assets,
TagModule,
} from '@docusaurus/plugin-content-blog'; } from '@docusaurus/plugin-content-blog';
export default async function pluginContentBlog( export default async function pluginContentBlog(
@ -117,6 +122,8 @@ export default async function pluginContentBlog(
blogSidebarTitle, blogSidebarTitle,
} = options; } = options;
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
const blogPosts = await generateBlogPosts(contentPaths, context, options); const blogPosts = await generateBlogPosts(contentPaths, context, options);
if (!blogPosts.length) { if (!blogPosts.length) {
@ -125,7 +132,7 @@ export default async function pluginContentBlog(
blogPosts: [], blogPosts: [],
blogListPaginated: [], blogListPaginated: [],
blogTags: {}, blogTags: {},
blogTagsListPath: null, blogTagsListPath,
blogTagsPaginated: [], blogTagsPaginated: [],
}; };
} }
@ -150,8 +157,6 @@ export default async function pluginContentBlog(
} }
}); });
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
const blogListPaginated: BlogPaginated[] = paginateBlogPosts({ const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
blogPosts, blogPosts,
blogTitle, blogTitle,
@ -167,11 +172,6 @@ export default async function pluginContentBlog(
blogTitle, blogTitle,
}); });
const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
const blogTagsListPath =
Object.keys(blogTags).length > 0 ? tagsPath : null;
return { return {
blogSidebarTitle, blogSidebarTitle,
blogPosts, blogPosts,
@ -307,30 +307,47 @@ export default async function pluginContentBlog(
}), }),
); );
// Tags. // Tags. This is the last part so we early-return if there are no tags.
if (blogTagsListPath === null) { if (Object.keys(blogTags).length === 0) {
return; return;
} }
const tagsModule: {[tagName: string]: TagModule} = Object.fromEntries( async function createTagsListPage() {
Object.entries(blogTags).map(([, tag]) => { const tagsProp: TagsListItem[] = Object.values(blogTags).map((tag) => ({
const tagModule: TagModule = { label: tag.label,
allTagsPath: blogTagsListPath,
name: tag.name,
count: tag.items.length,
permalink: tag.permalink, permalink: tag.permalink,
}; count: tag.items.length,
return [tag.name, tagModule]; }));
}),
const tagsPropPath = await createData(
`${docuHash(`${blogTagsListPath}-tags`)}.json`,
JSON.stringify(tagsProp, null, 2),
); );
async function createTagRoutes(tag: BlogTag): Promise<void> { addRoute({
path: blogTagsListPath,
component: blogTagsListComponent,
exact: true,
modules: {
sidebar: aliasedSource(sidebarProp),
tags: aliasedSource(tagsPropPath),
},
});
}
async function createTagPostsListPage(tag: BlogTag): Promise<void> {
await Promise.all( await Promise.all(
tag.pages.map(async (blogPaginated) => { tag.pages.map(async (blogPaginated) => {
const {metadata, items} = blogPaginated; const {metadata, items} = blogPaginated;
const tagsMetadataPath = await createData( const tagProp: TagModule = {
label: tag.label,
permalink: tag.permalink,
allTagsPath: blogTagsListPath,
count: tag.items.length,
};
const tagPropPath = await createData(
`${docuHash(metadata.permalink)}.json`, `${docuHash(metadata.permalink)}.json`,
JSON.stringify(tagsModule[tag.name], null, 2), JSON.stringify(tagProp, null, 2),
); );
const listMetadataPath = await createData( const listMetadataPath = await createData(
@ -356,7 +373,7 @@ export default async function pluginContentBlog(
}, },
}; };
}), }),
metadata: aliasedSource(tagsMetadataPath), tag: aliasedSource(tagPropPath),
listMetadata: aliasedSource(listMetadataPath), listMetadata: aliasedSource(listMetadataPath),
}, },
}); });
@ -364,25 +381,8 @@ export default async function pluginContentBlog(
); );
} }
await Promise.all(Object.values(blogTags).map(createTagRoutes)); await createTagsListPage();
await Promise.all(Object.values(blogTags).map(createTagPostsListPage));
// Only create /tags page if there are tags.
if (Object.keys(blogTags).length > 0) {
const tagsListPath = await createData(
`${docuHash(`${blogTagsListPath}-tags`)}.json`,
JSON.stringify(tagsModule, null, 2),
);
addRoute({
path: blogTagsListPath,
component: blogTagsListComponent,
exact: true,
modules: {
sidebar: aliasedSource(sidebarProp),
tags: aliasedSource(tagsListPath),
},
});
}
}, },
translateContent({content, translationFiles}) { translateContent({content, translationFiles}) {

View file

@ -7,8 +7,9 @@
declare module '@docusaurus/plugin-content-blog' { declare module '@docusaurus/plugin-content-blog' {
import type {MDXOptions} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag, Tag} from '@docusaurus/utils'; import type {FrontMatterTag} from '@docusaurus/utils';
import type {Overwrite} from 'utility-types'; import type {Overwrite} from 'utility-types';
import type {Tag} from '@docusaurus/types';
export type Assets = { export type Assets = {
/** /**
@ -406,17 +407,6 @@ declare module '@docusaurus/plugin-content-blog' {
} }
>; >;
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 = { export type BlogSidebar = {
title: string; title: string;
items: {title: string; permalink: string}[]; items: {title: string; permalink: string}[];
@ -511,28 +501,30 @@ declare module '@theme/BlogListPage' {
} }
declare module '@theme/BlogTagsListPage' { declare module '@theme/BlogTagsListPage' {
import type {BlogSidebar, TagModule} from '@docusaurus/plugin-content-blog'; import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
import type {TagsListItem} from '@docusaurus/types';
export interface Props { export interface Props {
/** Blog sidebar. */ /** Blog sidebar. */
readonly sidebar: BlogSidebar; readonly sidebar: BlogSidebar;
/** A map from tag names to the full tag module. */ /** All tags declared in this blog. */
readonly tags: Readonly<{[tagName: string]: TagModule}>; readonly tags: TagsListItem[];
} }
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, TagModule} from '@docusaurus/plugin-content-blog'; import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
import type {Content} from '@theme/BlogPostPage'; import type {Content} from '@theme/BlogPostPage';
import type {Metadata} from '@theme/BlogListPage'; import type {Metadata} from '@theme/BlogListPage';
import type {TagModule} from '@docusaurus/types';
export interface Props { export interface Props {
/** Blog sidebar. */ /** Blog sidebar. */
readonly sidebar: BlogSidebar; readonly sidebar: BlogSidebar;
/** Metadata of this tag. */ /** Metadata of this tag. */
readonly metadata: TagModule; readonly tag: TagModule;
/** Looks exactly the same as the posts list page */ /** Looks exactly the same as the posts list page */
readonly listMetadata: Metadata; readonly listMetadata: Metadata;
/** /**

View file

@ -6,6 +6,7 @@
*/ */
import type {BrokenMarkdownLink, ContentPaths} from '@docusaurus/utils'; import type {BrokenMarkdownLink, ContentPaths} from '@docusaurus/utils';
import type {Tag} from '@docusaurus/types';
import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog'; import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog';
import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage'; import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage';
@ -16,22 +17,16 @@ export type BlogContent = {
blogPosts: BlogPost[]; blogPosts: BlogPost[];
blogListPaginated: BlogPaginated[]; blogListPaginated: BlogPaginated[];
blogTags: BlogTags; blogTags: BlogTags;
blogTagsListPath: string | null; blogTagsListPath: string;
}; };
export type BlogTags = { export type BlogTags = {
// TODO, the key is the tag slug/permalink [permalink: string]: BlogTag;
// This is due to legacy frontmatter: tags:
// [{label: "xyz", permalink: "/1"}, {label: "xyz", permalink: "/2"}]
// Soon we should forbid declaring permalink through frontmatter
[tagKey: string]: BlogTag;
}; };
export type BlogTag = { export type BlogTag = Tag & {
name: string;
/** Blog post permalinks. */ /** Blog post permalinks. */
items: string[]; items: string[];
permalink: string;
pages: BlogPaginated[]; pages: BlogPaginated[];
}; };

View file

@ -714,9 +714,11 @@ exports[`simple website content: data 1`] = `
} }
}", }",
"tag-docs-tags-tag-1-b3f.json": "{ "tag-docs-tags-tag-1-b3f.json": "{
\\"name\\": \\"tag 1\\", \\"label\\": \\"tag 1\\",
\\"permalink\\": \\"/docs/tags/tag-1\\", \\"permalink\\": \\"/docs/tags/tag-1\\",
\\"docs\\": [ \\"allTagsPath\\": \\"/docs/tags\\",
\\"count\\": 2,
\\"items\\": [
{ {
\\"id\\": \\"foo/baz\\", \\"id\\": \\"foo/baz\\",
\\"title\\": \\"baz\\", \\"title\\": \\"baz\\",
@ -729,48 +731,49 @@ exports[`simple website content: data 1`] = `
\\"description\\": \\"Hi, Endilie here :)\\", \\"description\\": \\"Hi, Endilie here :)\\",
\\"permalink\\": \\"/docs/\\" \\"permalink\\": \\"/docs/\\"
} }
], ]
\\"allTagsPath\\": \\"/docs/tags\\"
}", }",
"tag-docs-tags-tag-2-custom-permalink-825.json": "{ "tag-docs-tags-tag-2-custom-permalink-825.json": "{
\\"name\\": \\"tag 2\\", \\"label\\": \\"tag 2\\",
\\"permalink\\": \\"/docs/tags/tag2-custom-permalink\\", \\"permalink\\": \\"/docs/tags/tag2-custom-permalink\\",
\\"docs\\": [ \\"allTagsPath\\": \\"/docs/tags\\",
\\"count\\": 1,
\\"items\\": [
{ {
\\"id\\": \\"foo/baz\\", \\"id\\": \\"foo/baz\\",
\\"title\\": \\"baz\\", \\"title\\": \\"baz\\",
\\"description\\": \\"Images\\", \\"description\\": \\"Images\\",
\\"permalink\\": \\"/docs/foo/bazSlug.html\\" \\"permalink\\": \\"/docs/foo/bazSlug.html\\"
} }
], ]
\\"allTagsPath\\": \\"/docs/tags\\"
}", }",
"tag-docs-tags-tag-3-ab5.json": "{ "tag-docs-tags-tag-3-ab5.json": "{
\\"name\\": \\"tag 3\\", \\"label\\": \\"tag 3\\",
\\"permalink\\": \\"/docs/tags/tag-3\\", \\"permalink\\": \\"/docs/tags/tag-3\\",
\\"docs\\": [ \\"allTagsPath\\": \\"/docs/tags\\",
\\"count\\": 1,
\\"items\\": [
{ {
\\"id\\": \\"hello\\", \\"id\\": \\"hello\\",
\\"title\\": \\"Hello, World !\\", \\"title\\": \\"Hello, World !\\",
\\"description\\": \\"Hi, Endilie here :)\\", \\"description\\": \\"Hi, Endilie here :)\\",
\\"permalink\\": \\"/docs/\\" \\"permalink\\": \\"/docs/\\"
} }
], ]
\\"allTagsPath\\": \\"/docs/tags\\"
}", }",
"tags-list-current-prop-15a.json": "[ "tags-list-current-prop-15a.json": "[
{ {
\\"name\\": \\"tag 1\\", \\"label\\": \\"tag 1\\",
\\"permalink\\": \\"/docs/tags/tag-1\\", \\"permalink\\": \\"/docs/tags/tag-1\\",
\\"count\\": 2 \\"count\\": 2
}, },
{ {
\\"name\\": \\"tag 2\\", \\"label\\": \\"tag 2\\",
\\"permalink\\": \\"/docs/tags/tag2-custom-permalink\\", \\"permalink\\": \\"/docs/tags/tag2-custom-permalink\\",
\\"count\\": 1 \\"count\\": 1
}, },
{ {
\\"name\\": \\"tag 3\\", \\"label\\": \\"tag 3\\",
\\"permalink\\": \\"/docs/tags/tag-3\\", \\"permalink\\": \\"/docs/tags/tag-3\\",
\\"count\\": 1 \\"count\\": 1
} }
@ -3172,57 +3175,60 @@ exports[`versioned website content: data 1`] = `
} }
}", }",
"tag-docs-next-tags-bar-tag-1-a8f.json": "{ "tag-docs-next-tags-bar-tag-1-a8f.json": "{
\\"name\\": \\"barTag 1\\", \\"label\\": \\"barTag 1\\",
\\"permalink\\": \\"/docs/next/tags/bar-tag-1\\", \\"permalink\\": \\"/docs/next/tags/bar-tag-1\\",
\\"docs\\": [ \\"allTagsPath\\": \\"/docs/next/tags\\",
\\"count\\": 1,
\\"items\\": [
{ {
\\"id\\": \\"foo/bar\\", \\"id\\": \\"foo/bar\\",
\\"title\\": \\"bar\\", \\"title\\": \\"bar\\",
\\"description\\": \\"This is next version of bar.\\", \\"description\\": \\"This is next version of bar.\\",
\\"permalink\\": \\"/docs/next/foo/barSlug\\" \\"permalink\\": \\"/docs/next/foo/barSlug\\"
} }
], ]
\\"allTagsPath\\": \\"/docs/next/tags\\"
}", }",
"tag-docs-next-tags-bar-tag-2-216.json": "{ "tag-docs-next-tags-bar-tag-2-216.json": "{
\\"name\\": \\"barTag-2\\", \\"label\\": \\"barTag-2\\",
\\"permalink\\": \\"/docs/next/tags/bar-tag-2\\", \\"permalink\\": \\"/docs/next/tags/bar-tag-2\\",
\\"docs\\": [ \\"allTagsPath\\": \\"/docs/next/tags\\",
\\"count\\": 1,
\\"items\\": [
{ {
\\"id\\": \\"foo/bar\\", \\"id\\": \\"foo/bar\\",
\\"title\\": \\"bar\\", \\"title\\": \\"bar\\",
\\"description\\": \\"This is next version of bar.\\", \\"description\\": \\"This is next version of bar.\\",
\\"permalink\\": \\"/docs/next/foo/barSlug\\" \\"permalink\\": \\"/docs/next/foo/barSlug\\"
} }
], ]
\\"allTagsPath\\": \\"/docs/next/tags\\"
}", }",
"tag-docs-next-tags-bar-tag-3-permalink-94a.json": "{ "tag-docs-next-tags-bar-tag-3-permalink-94a.json": "{
\\"name\\": \\"barTag 3\\", \\"label\\": \\"barTag 3\\",
\\"permalink\\": \\"/docs/next/tags/barTag-3-permalink\\", \\"permalink\\": \\"/docs/next/tags/barTag-3-permalink\\",
\\"docs\\": [ \\"allTagsPath\\": \\"/docs/next/tags\\",
\\"count\\": 1,
\\"items\\": [
{ {
\\"id\\": \\"foo/bar\\", \\"id\\": \\"foo/bar\\",
\\"title\\": \\"bar\\", \\"title\\": \\"bar\\",
\\"description\\": \\"This is next version of bar.\\", \\"description\\": \\"This is next version of bar.\\",
\\"permalink\\": \\"/docs/next/foo/barSlug\\" \\"permalink\\": \\"/docs/next/foo/barSlug\\"
} }
], ]
\\"allTagsPath\\": \\"/docs/next/tags\\"
}", }",
"tags-list-current-prop-15a.json": "[ "tags-list-current-prop-15a.json": "[
{ {
\\"name\\": \\"barTag 1\\", \\"label\\": \\"barTag 1\\",
\\"permalink\\": \\"/docs/next/tags/bar-tag-1\\", \\"permalink\\": \\"/docs/next/tags/bar-tag-1\\",
\\"count\\": 1 \\"count\\": 1
}, },
{ {
\\"name\\": \\"barTag-2\\", \\"label\\": \\"barTag-2\\",
\\"permalink\\": \\"/docs/next/tags/bar-tag-2\\", \\"permalink\\": \\"/docs/next/tags/bar-tag-2\\",
\\"count\\": 1 \\"count\\": 1
}, },
{ {
\\"name\\": \\"barTag 3\\", \\"label\\": \\"barTag 3\\",
\\"permalink\\": \\"/docs/next/tags/barTag-3-permalink\\", \\"permalink\\": \\"/docs/next/tags/barTag-3-permalink\\",
\\"count\\": 1 \\"count\\": 1
} }

View file

@ -54,9 +54,10 @@ describe('toTagDocListProp', () => {
expect(result).toEqual({ expect(result).toEqual({
allTagsPath, allTagsPath,
name: tag.label, count: 2,
label: tag.label,
permalink: tag.permalink, permalink: tag.permalink,
docs: [doc3, doc1], // docs sorted by title, ignore "id5" absence items: [doc3, doc1], // docs sorted by title, ignore "id5" absence
}); });
}); });
}); });

View file

@ -228,13 +228,13 @@ export default async function pluginContentDocs(
const tagsProp: PropTagsListPage['tags'] = Object.values( const tagsProp: PropTagsListPage['tags'] = Object.values(
versionTags, versionTags,
).map((tagValue) => ({ ).map((tagValue) => ({
name: tagValue.label, label: tagValue.label,
permalink: tagValue.permalink, permalink: tagValue.permalink,
count: tagValue.docIds.length, count: tagValue.docIds.length,
})); }));
// Only create /tags page if there are tags. // Only create /tags page if there are tags.
if (Object.keys(tagsProp).length > 0) { if (tagsProp.length > 0) {
const tagsPropPath = await createData( const tagsPropPath = await createData(
`${docuHash(`tags-list-${version.versionName}-prop`)}.json`, `${docuHash(`tags-list-${version.versionName}-prop`)}.json`,
JSON.stringify(tagsProp, null, 2), JSON.stringify(tagsProp, null, 2),

View file

@ -7,7 +7,8 @@
declare module '@docusaurus/plugin-content-docs' { declare module '@docusaurus/plugin-content-docs' {
import type {MDXOptions} from '@docusaurus/mdx-loader'; import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {ContentPaths, Tag, FrontMatterTag} from '@docusaurus/utils'; import type {ContentPaths, FrontMatterTag} from '@docusaurus/utils';
import type {TagsListItem, TagModule, Tag} from '@docusaurus/types';
import type {Required} from 'utility-types'; import type {Required} from 'utility-types';
export type Assets = { export type Assets = {
@ -483,25 +484,14 @@ declare module '@docusaurus/plugin-content-docs' {
export type PropSidebar = import('./sidebars/types').PropSidebar; export type PropSidebar = import('./sidebars/types').PropSidebar;
export type PropSidebars = import('./sidebars/types').PropSidebars; export type PropSidebars = import('./sidebars/types').PropSidebars;
export type PropTagDocListDoc = { export type PropTagDocListDoc = Pick<
id: string; DocMetadata,
title: string; 'id' | 'title' | 'description' | 'permalink'
description: string; >;
permalink: string; export type PropTagDocList = TagModule & {items: PropTagDocListDoc[]};
};
export type PropTagDocList = {
allTagsPath: string;
name: string; // normalized name/label of the tag
permalink: string; // pathname of the tag
docs: PropTagDocListDoc[];
};
export type PropTagsListPage = { export type PropTagsListPage = {
tags: { tags: TagsListItem[];
name: string;
permalink: string;
count: number;
}[];
}; };
} }

View file

@ -137,7 +137,7 @@ export function toTagDocListProp({
}: { }: {
allTagsPath: string; allTagsPath: string;
tag: VersionTag; tag: VersionTag;
docs: Pick<DocMetadata, 'id' | 'title' | 'description' | 'permalink'>[]; docs: DocMetadata[];
}): PropTagDocList { }): PropTagDocList {
function toDocListProp(): PropTagDocListDoc[] { function toDocListProp(): PropTagDocListDoc[] {
const list = _.compact( const list = _.compact(
@ -154,9 +154,10 @@ export function toTagDocListProp({
} }
return { return {
name: tag.label, label: tag.label,
permalink: tag.permalink, permalink: tag.permalink,
docs: toDocListProp(),
allTagsPath, allTagsPath,
count: tag.docIds.length,
items: toDocListProp(),
}; };
} }

View file

@ -8,13 +8,14 @@
/// <reference types="@docusaurus/module-type-aliases" /> /// <reference types="@docusaurus/module-type-aliases" />
import type {Sidebars} from './sidebars/types'; import type {Sidebars} from './sidebars/types';
import type {BrokenMarkdownLink, Tag} from '@docusaurus/utils'; import type {BrokenMarkdownLink} from '@docusaurus/utils';
import type { import type {
VersionMetadata, VersionMetadata,
LastUpdateData, LastUpdateData,
DocMetadata, DocMetadata,
CategoryGeneratedIndexMetadata, CategoryGeneratedIndexMetadata,
} from '@docusaurus/plugin-content-docs'; } from '@docusaurus/plugin-content-docs';
import type {Tag} from '@docusaurus/types';
export type DocFile = { export type DocFile = {
contentPath: string; // /!\ may be localized contentPath: string; // /!\ may be localized
@ -33,7 +34,7 @@ export type VersionTag = Tag & {
docIds: string[]; docIds: string[];
}; };
export type VersionTags = { export type VersionTags = {
[key: string]: VersionTag; [permalink: string]: VersionTag;
}; };
export type LoadedVersion = VersionMetadata & { export type LoadedVersion = VersionMetadata & {

View file

@ -1031,7 +1031,7 @@ declare module '@theme/IconExternalLink' {
} }
declare module '@theme/TagsListByLetter' { declare module '@theme/TagsListByLetter' {
import type {TagsListItem} from '@docusaurus/theme-common'; import type {TagsListItem} from '@docusaurus/types';
export interface Props { export interface Props {
readonly tags: readonly TagsListItem[]; readonly tags: readonly TagsListItem[];
@ -1040,7 +1040,7 @@ declare module '@theme/TagsListByLetter' {
} }
declare module '@theme/TagsListInline' { declare module '@theme/TagsListInline' {
import type {Tag} from '@docusaurus/utils'; import type {Tag} from '@docusaurus/types';
export interface Props { export interface Props {
readonly tags: readonly Tag[]; readonly tags: readonly Tag[];
@ -1049,7 +1049,7 @@ declare module '@theme/TagsListInline' {
} }
declare module '@theme/Tag' { declare module '@theme/Tag' {
import type {TagsListItem} from '@docusaurus/theme-common'; import type {TagsListItem} from '@docusaurus/types';
import type {Optional} from 'utility-types'; import type {Optional} from 'utility-types';
export interface Props extends Optional<TagsListItem, 'count'> {} export interface Props extends Optional<TagsListItem, 'count'> {}

View file

@ -19,8 +19,7 @@ import {
import SearchMetadata from '../SearchMetadata'; import SearchMetadata from '../SearchMetadata';
import clsx from 'clsx'; import clsx from 'clsx';
export default function BlogTagsListPage(props: Props): JSX.Element { export default function BlogTagsListPage({tags, sidebar}: Props): JSX.Element {
const {tags, sidebar} = props;
const title = translateTagsPageTitle(); const title = translateTagsPageTitle();
return ( return (
<HtmlClassNameProvider <HtmlClassNameProvider
@ -32,7 +31,7 @@ export default function BlogTagsListPage(props: Props): JSX.Element {
<SearchMetadata tag="blog_tags_list" /> <SearchMetadata tag="blog_tags_list" />
<BlogLayout sidebar={sidebar}> <BlogLayout sidebar={sidebar}>
<h1>{title}</h1> <h1>{title}</h1>
<TagsListByLetter tags={Object.values(tags)} /> <TagsListByLetter tags={tags} />
</BlogLayout> </BlogLayout>
</HtmlClassNameProvider> </HtmlClassNameProvider>
); );

View file

@ -40,9 +40,12 @@ function useBlogPostsPlural() {
); );
} }
export default function BlogTagsPostsPage(props: Props): JSX.Element { export default function BlogTagsPostsPage({
const {metadata, items, sidebar, listMetadata} = props; tag,
const {allTagsPath, name: tagName, count} = metadata; items,
sidebar,
listMetadata,
}: Props): JSX.Element {
const blogPostsPlural = useBlogPostsPlural(); const blogPostsPlural = useBlogPostsPlural();
const title = translate( const title = translate(
{ {
@ -50,7 +53,7 @@ export default function BlogTagsPostsPage(props: Props): JSX.Element {
description: 'The title of the page for a blog tag', description: 'The title of the page for a blog tag',
message: '{nPosts} tagged with "{tagName}"', message: '{nPosts} tagged with "{tagName}"',
}, },
{nPosts: blogPostsPlural(count), tagName}, {nPosts: blogPostsPlural(tag.count), tagName: tag.label},
); );
return ( return (
@ -65,7 +68,7 @@ export default function BlogTagsPostsPage(props: Props): JSX.Element {
<header className="margin-bottom--xl"> <header className="margin-bottom--xl">
<h1>{title}</h1> <h1>{title}</h1>
<Link href={allTagsPath}> <Link href={tag.allTagsPath}>
<Translate <Translate
id="theme.tags.tagsPageLink" id="theme.tags.tagsPageLink"
description="The label of the link targeting the tag list page"> description="The label of the link targeting the tag list page">

View file

@ -15,7 +15,6 @@ import {
ThemeClassNames, ThemeClassNames,
usePluralForm, usePluralForm,
} from '@docusaurus/theme-common'; } from '@docusaurus/theme-common';
import type {PropTagDocListDoc} from '@docusaurus/plugin-content-docs';
import Translate, {translate} from '@docusaurus/Translate'; import Translate, {translate} from '@docusaurus/Translate';
import type {Props} from '@theme/DocTagDocListPage'; import type {Props} from '@theme/DocTagDocListPage';
import SearchMetadata from '@theme/SearchMetadata'; import SearchMetadata from '@theme/SearchMetadata';
@ -39,7 +38,7 @@ function useNDocsTaggedPlural() {
); );
} }
function DocItem({doc}: {doc: PropTagDocListDoc}): JSX.Element { function DocItem({doc}: {doc: Props['tag']['items'][number]}): JSX.Element {
return ( return (
<article className="margin-vert--lg"> <article className="margin-vert--lg">
<Link to={doc.permalink}> <Link to={doc.permalink}>
@ -58,7 +57,7 @@ export default function DocTagDocListPage({tag}: Props): JSX.Element {
description: 'The title of the page for a docs tag', description: 'The title of the page for a docs tag',
message: '{nDocsTagged} with "{tagName}"', message: '{nDocsTagged} with "{tagName}"',
}, },
{nDocsTagged: nDocsTaggedPlural(tag.docs.length), tagName: tag.name}, {nDocsTagged: nDocsTaggedPlural(tag.count), tagName: tag.label},
); );
return ( return (
@ -84,7 +83,7 @@ export default function DocTagDocListPage({tag}: Props): JSX.Element {
</Link> </Link>
</header> </header>
<section className="margin-vert--lg"> <section className="margin-vert--lg">
{tag.docs.map((doc) => ( {tag.items.map((doc) => (
<DocItem key={doc.id} doc={doc} /> <DocItem key={doc.id} doc={doc} />
))} ))}
</section> </section>

View file

@ -12,9 +12,7 @@ import type {Props} from '@theme/Tag';
import styles from './styles.module.css'; import styles from './styles.module.css';
export default function Tag(props: Props): JSX.Element { export default function Tag({permalink, label, count}: Props): JSX.Element {
const {permalink, name, count} = props;
return ( return (
<Link <Link
href={permalink} href={permalink}
@ -22,7 +20,7 @@ export default function Tag(props: Props): JSX.Element {
styles.tag, styles.tag,
count ? styles.tagWithCount : styles.tagRegular, count ? styles.tagWithCount : styles.tagRegular,
)}> )}>
{name} {label}
{count && <span>{count}</span>} {count && <span>{count}</span>}
</Link> </Link>
); );

View file

@ -26,7 +26,7 @@ export default function TagsListInline({tags}: Props): JSX.Element {
<ul className={clsx(styles.tags, 'padding--none', 'margin-left--sm')}> <ul className={clsx(styles.tags, 'padding--none', 'margin-left--sm')}>
{tags.map(({label, permalink: tagPermalink}) => ( {tags.map(({label, permalink: tagPermalink}) => (
<li key={tagPermalink} className={styles.tag}> <li key={tagPermalink} className={styles.tag}>
<Tag name={label} permalink={tagPermalink} /> <Tag label={label} permalink={tagPermalink} />
</li> </li>
))} ))}
</ul> </ul>

View file

@ -86,7 +86,6 @@ export {
translateTagsPageTitle, translateTagsPageTitle,
listTagsByLetters, listTagsByLetters,
type TagLetterEntry, type TagLetterEntry,
type TagsListItem,
} from './utils/tagsUtils'; } from './utils/tagsUtils';
export {useHistoryPopHandler} from './utils/historyUtils'; export {useHistoryPopHandler} from './utils/historyUtils';

View file

@ -15,32 +15,32 @@ describe('listTagsByLetters', () => {
it('creates letters list', () => { it('creates letters list', () => {
const tag1: Tag = { const tag1: Tag = {
name: 'tag1', label: 'tag1',
permalink: '/tag1', permalink: '/tag1',
count: 1, count: 1,
}; };
const tag2: Tag = { const tag2: Tag = {
name: 'Tag2', label: 'Tag2',
permalink: '/tag2', permalink: '/tag2',
count: 11, count: 11,
}; };
const tagZxy: Tag = { const tagZxy: Tag = {
name: 'zxy', label: 'zxy',
permalink: '/zxy', permalink: '/zxy',
count: 987, count: 987,
}; };
const tagAbc: Tag = { const tagAbc: Tag = {
name: 'Abc', label: 'Abc',
permalink: '/abc', permalink: '/abc',
count: 123, count: 123,
}; };
const tagDef: Tag = { const tagDef: Tag = {
name: 'def', label: 'def',
permalink: '/def', permalink: '/def',
count: 1, count: 1,
}; };
const tagAaa: Tag = { const tagAaa: Tag = {
name: 'aaa', label: 'aaa',
permalink: '/aaa', permalink: '/aaa',
count: 10, count: 10,
}; };

View file

@ -6,6 +6,7 @@
*/ */
import {translate} from '@docusaurus/Translate'; import {translate} from '@docusaurus/Translate';
import type {TagsListItem} from '@docusaurus/types';
export const translateTagsPageTitle = (): string => export const translateTagsPageTitle = (): string =>
translate({ translate({
@ -14,13 +15,7 @@ export const translateTagsPageTitle = (): string =>
description: 'The title of the tag list page', description: 'The title of the tag list page',
}); });
export type TagsListItem = Readonly<{ export type TagLetterEntry = {letter: string; tags: TagsListItem[]};
name: string;
permalink: string;
count: number;
}>;
export type TagLetterEntry = Readonly<{letter: string; tags: TagsListItem[]}>;
function getTagLetter(tag: string): string { function getTagLetter(tag: string): string {
return tag[0]!.toUpperCase(); return tag[0]!.toUpperCase();
@ -35,7 +30,7 @@ export function listTagsByLetters(
): TagLetterEntry[] { ): TagLetterEntry[] {
const groups: {[initial: string]: TagsListItem[]} = {}; const groups: {[initial: string]: TagsListItem[]} = {};
Object.values(tags).forEach((tag) => { Object.values(tags).forEach((tag) => {
const initial = getTagLetter(tag.name); const initial = getTagLetter(tag.label);
groups[initial] ??= []; groups[initial] ??= [];
groups[initial]!.push(tag); groups[initial]!.push(tag);
}); });
@ -47,7 +42,7 @@ export function listTagsByLetters(
.map(([letter, letterTags]) => { .map(([letter, letterTags]) => {
// Sort tags inside a letter // Sort tags inside a letter
const sortedTags = letterTags.sort((tag1, tag2) => const sortedTags = letterTags.sort((tag1, tag2) =>
tag1.name.localeCompare(tag2.name), tag1.label.localeCompare(tag2.label),
); );
return {letter, tags: sortedTags}; return {letter, tags: sortedTags};
}) })

View file

@ -594,3 +594,22 @@ export type ClientModule = {
}) => void; }) => void;
onRouteUpdateDelayed?: (args: {location: Location}) => void; onRouteUpdateDelayed?: (args: {location: Location}) => void;
}; };
/** What the user configures. */
export type Tag = {
label: string;
/** Permalink to this tag's page, without the `/tags/` base path. */
permalink: string;
};
/** What the tags list page should know about each tag. */
export type TagsListItem = Tag & {
/** Number of posts/docs with this tag. */
count: number;
};
/** What the tag's own page should know about the tag. */
export type TagModule = TagsListItem & {
/** The tags list page's permalink. */
allTagsPath: string;
};

View file

@ -7,7 +7,7 @@
import Joi from './Joi'; import Joi from './Joi';
import {isValidPathname, DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import {isValidPathname, DEFAULT_PLUGIN_ID} from '@docusaurus/utils';
import type {Tag} from '@docusaurus/utils'; import type {Tag} from '@docusaurus/types';
import {JoiFrontMatter} from './JoiFrontMatter'; import {JoiFrontMatter} from './JoiFrontMatter';
export const PluginIdSchema = Joi.string() export const PluginIdSchema = Joi.string()

View file

@ -56,7 +56,6 @@ export {
buildSshUrl, buildSshUrl,
} from './urlUtils'; } from './urlUtils';
export { export {
type Tag,
type FrontMatterTag, type FrontMatterTag,
normalizeFrontMatterTags, normalizeFrontMatterTags,
groupTaggedItems, groupTaggedItems,

View file

@ -7,12 +7,7 @@
import _ from 'lodash'; import _ from 'lodash';
import {normalizeUrl} from './urlUtils'; import {normalizeUrl} from './urlUtils';
import type {Tag} from '@docusaurus/types';
export type Tag = {
label: string;
/** Permalink to this tag's page, without the `/tags/` base path. */
permalink: string;
};
export type FrontMatterTag = string | Tag; export type FrontMatterTag = string | Tag;