mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-22 05:27:00 +02:00
feat: doc tags (same as blog tags) (#3646)
* [v2] tags to doc, same as tags to blog - [IN PROGRESS] - Addition of plugin-content-docs - Addition of DocTagsListPage in `docusaurus-theme-classic` ! Error exists for this commit towards the theme aspect and help required. Commit towards #3434 * docs: make tags list page work * temp: disable onBrokenLinks * theme bootstrap: create DocTagsListPage * DocTagsPage added and functionality too - individual doc tag page added to show docs for that specific tag * Added all Docs Tags Link * add some shared tag utils * move tag tests to _dogfooding * fix type * fix some tests * fix blog test * refactor blog post tags handling * better yaml tag examples * better dogfood md files * refactor and factorize theme tag components * finish DocTagDocListPage * Extract DocItemFooter + add inline tag list * minor fix * better typings * fix versions.test.ts tests * add tests for doc tags * fix tests * test toTagDocListProp * move shared theme code to tagUtils * Add new theme translation keys * move common theme code to tagUtils + add tests * update-code-translations should handle theme-common * update french translation * revert add translation * fix pluralization problem in theme.docs.tagDocListPageTitle * add theme component configuration options * add more tags tests * add documentation for docs tagging Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
f666de7e59
commit
f9c79cbd58
81 changed files with 1874 additions and 381 deletions
|
@ -9,15 +9,16 @@ import {
|
|||
JoiFrontMatter as Joi, // Custom instance for frontmatter
|
||||
URISchema,
|
||||
validateFrontMatter,
|
||||
FrontMatterTagsSchema,
|
||||
} from '@docusaurus/utils-validation';
|
||||
import {Tag} from './types';
|
||||
import type {FrontMatterTag} from '@docusaurus/utils';
|
||||
|
||||
export type BlogPostFrontMatter = {
|
||||
/* eslint-disable camelcase */
|
||||
id?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
tags?: (string | Tag)[];
|
||||
tags?: FrontMatterTag[];
|
||||
slug?: string;
|
||||
draft?: boolean;
|
||||
date?: Date | string; // Yaml automagically convert some string patterns as Date, but not all
|
||||
|
@ -38,23 +39,11 @@ export type BlogPostFrontMatter = {
|
|||
/* eslint-enable camelcase */
|
||||
};
|
||||
|
||||
// NOTE: we don't add any default value on purpose here
|
||||
// We don't want default values to magically appear in doc metadatas and props
|
||||
// While the user did not provide those values explicitly
|
||||
// We use default values in code instead
|
||||
const BlogTagSchema = Joi.alternatives().try(
|
||||
Joi.string().required(),
|
||||
Joi.object<Tag>({
|
||||
label: Joi.string().required(),
|
||||
permalink: Joi.string().required(),
|
||||
}),
|
||||
);
|
||||
|
||||
const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
||||
id: Joi.string(),
|
||||
title: Joi.string().allow(''),
|
||||
description: Joi.string().allow(''),
|
||||
tags: Joi.array().items(BlogTagSchema),
|
||||
tags: FrontMatterTagsSchema,
|
||||
draft: Joi.boolean(),
|
||||
date: Joi.date().raw(),
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
BlogPost,
|
||||
BlogContentPaths,
|
||||
BlogMarkdownLoaderOptions,
|
||||
BlogTags,
|
||||
} from './types';
|
||||
import {
|
||||
parseMarkdownFile,
|
||||
|
@ -26,6 +27,8 @@ import {
|
|||
posixPath,
|
||||
replaceMarkdownLinks,
|
||||
Globby,
|
||||
normalizeFrontMatterTags,
|
||||
groupTaggedItems,
|
||||
} from '@docusaurus/utils';
|
||||
import {LoadContext} from '@docusaurus/types';
|
||||
import {validateBlogPostFrontMatter} from './blogFrontMatter';
|
||||
|
@ -43,6 +46,20 @@ export function getSourceToPermalink(
|
|||
);
|
||||
}
|
||||
|
||||
export function getBlogTags(blogPosts: BlogPost[]): BlogTags {
|
||||
const groups = groupTaggedItems(
|
||||
blogPosts,
|
||||
(blogPost) => blogPost.metadata.tags,
|
||||
);
|
||||
return mapValues(groups, (group) => {
|
||||
return {
|
||||
name: group.tag.label,
|
||||
items: group.items.map((item) => item.id),
|
||||
permalink: group.tag.permalink,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const DATE_FILENAME_REGEX = /^(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
|
||||
|
||||
type ParsedBlogFileName = {
|
||||
|
@ -240,6 +257,8 @@ async function processBlogSourceFile(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const tagsBasePath = normalizeUrl([baseUrl, options.routeBasePath, 'tags']); // make this configurable?
|
||||
|
||||
return {
|
||||
id: frontMatter.slug ?? title,
|
||||
metadata: {
|
||||
|
@ -250,7 +269,7 @@ async function processBlogSourceFile(
|
|||
description,
|
||||
date,
|
||||
formattedDate,
|
||||
tags: frontMatter.tags ?? [],
|
||||
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
|
||||
readingTime: showReadingTime ? readingTime(content).minutes : undefined,
|
||||
truncated: truncateMarker?.test(content) || false,
|
||||
},
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
STATIC_DIR_NAME,
|
||||
DEFAULT_PLUGIN_ID,
|
||||
} from '@docusaurus/core/lib/constants';
|
||||
import {flatten, take, kebabCase} from 'lodash';
|
||||
import {flatten, take} from 'lodash';
|
||||
|
||||
import {
|
||||
PluginOptions,
|
||||
|
@ -51,6 +51,7 @@ import {
|
|||
generateBlogPosts,
|
||||
getContentPathList,
|
||||
getSourceToPermalink,
|
||||
getBlogTags,
|
||||
} from './blogUtils';
|
||||
|
||||
export default function pluginContentBlog(
|
||||
|
@ -65,7 +66,7 @@ export default function pluginContentBlog(
|
|||
|
||||
const {
|
||||
siteDir,
|
||||
siteConfig: {onBrokenMarkdownLinks},
|
||||
siteConfig: {onBrokenMarkdownLinks, baseUrl},
|
||||
generatedFilesDir,
|
||||
i18n: {currentLocale},
|
||||
} = context;
|
||||
|
@ -151,17 +152,14 @@ export default function pluginContentBlog(
|
|||
const postsPerPage =
|
||||
postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
|
||||
const numberOfPages = Math.ceil(totalCount / postsPerPage);
|
||||
const {
|
||||
siteConfig: {baseUrl = ''},
|
||||
} = context;
|
||||
const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
|
||||
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
||||
|
||||
const blogListPaginated: BlogPaginated[] = [];
|
||||
|
||||
function blogPaginationPermalink(page: number) {
|
||||
return page > 0
|
||||
? normalizeUrl([basePageUrl, `page/${page + 1}`])
|
||||
: basePageUrl;
|
||||
? normalizeUrl([baseBlogUrl, `page/${page + 1}`])
|
||||
: baseBlogUrl;
|
||||
}
|
||||
|
||||
for (let page = 0; page < numberOfPages; page += 1) {
|
||||
|
@ -186,41 +184,9 @@ export default function pluginContentBlog(
|
|||
});
|
||||
}
|
||||
|
||||
const blogTags: BlogTags = {};
|
||||
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
|
||||
blogPosts.forEach((blogPost) => {
|
||||
const {tags} = blogPost.metadata;
|
||||
if (!tags || tags.length === 0) {
|
||||
// TODO: Extract tags out into a separate plugin.
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
blogPost.metadata.tags = [];
|
||||
return;
|
||||
}
|
||||
const blogTags: BlogTags = getBlogTags(blogPosts);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
blogPost.metadata.tags = tags.map((tag) => {
|
||||
if (typeof tag === 'string') {
|
||||
const normalizedTag = kebabCase(tag);
|
||||
const permalink = normalizeUrl([tagsPath, normalizedTag]);
|
||||
if (!blogTags[normalizedTag]) {
|
||||
blogTags[normalizedTag] = {
|
||||
// Will only use the name of the first occurrence of the tag.
|
||||
name: tag.toLowerCase(),
|
||||
items: [],
|
||||
permalink,
|
||||
};
|
||||
}
|
||||
|
||||
blogTags[normalizedTag].items.push(blogPost.id);
|
||||
|
||||
return {
|
||||
label: tag,
|
||||
permalink,
|
||||
};
|
||||
}
|
||||
return tag;
|
||||
});
|
||||
});
|
||||
const tagsPath = normalizeUrl([baseBlogUrl, 'tags']);
|
||||
|
||||
const blogTagsListPath =
|
||||
Object.keys(blogTags).length > 0 ? tagsPath : null;
|
||||
|
@ -348,6 +314,7 @@ export default function pluginContentBlog(
|
|||
Object.keys(blogTags).map(async (tag) => {
|
||||
const {name, items, permalink} = blogTags[tag];
|
||||
|
||||
// Refactor all this, see docs implementation
|
||||
tagsModule[tag] = {
|
||||
allTagsPath: blogTagsListPath,
|
||||
slug: tag,
|
||||
|
@ -535,7 +502,6 @@ export default function pluginContentBlog(
|
|||
const feedTypes = options.feedOptions.type;
|
||||
const {
|
||||
siteConfig: {title},
|
||||
baseUrl,
|
||||
} = context;
|
||||
const feedsConfig = {
|
||||
rss: {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader';
|
||||
import {
|
||||
import type {Tag} from '@docusaurus/utils';
|
||||
import type {
|
||||
BrokenMarkdownLink,
|
||||
ContentPaths,
|
||||
} from '@docusaurus/utils/lib/markdownLinks';
|
||||
|
@ -96,7 +97,7 @@ export interface MetaData {
|
|||
description: string;
|
||||
date: Date;
|
||||
formattedDate: string;
|
||||
tags: (Tag | string)[];
|
||||
tags: Tag[];
|
||||
title: string;
|
||||
readingTime?: number;
|
||||
prevItem?: Paginator;
|
||||
|
@ -110,11 +111,6 @@ export interface Paginator {
|
|||
permalink: string;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
label: string;
|
||||
permalink: string;
|
||||
}
|
||||
|
||||
export interface BlogItemsToMetadata {
|
||||
[key: string]: MetaData;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue