mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-18 03:26:57 +02:00
feat(v2): blog tags (#1453)
* feat(v2): blog tags * feat(v2): blog tags
This commit is contained in:
parent
ffdd3e60fc
commit
fd270bdceb
7 changed files with 218 additions and 3 deletions
|
@ -26,6 +26,8 @@ const DEFAULT_OPTIONS = {
|
|||
postsPerPage: 10, // How many posts per page.
|
||||
blogListComponent: '@theme/BlogListPage',
|
||||
blogPostComponent: '@theme/BlogPostPage',
|
||||
blogTagsListComponent: '@theme/BlogTagsListPage',
|
||||
blogTagsPostsComponent: '@theme/BlogTagsPostsPage',
|
||||
};
|
||||
|
||||
class DocusaurusPluginContentBlog {
|
||||
|
@ -87,6 +89,7 @@ class DocusaurusPluginContentBlog {
|
|||
source,
|
||||
description: frontMatter.description || excerpt,
|
||||
date,
|
||||
tags: frontMatter.tags,
|
||||
title: frontMatter.title || blogFileName,
|
||||
},
|
||||
});
|
||||
|
@ -126,16 +129,39 @@ class DocusaurusPluginContentBlog {
|
|||
});
|
||||
}
|
||||
|
||||
const blogTags = {};
|
||||
blogPosts.forEach(blogPost => {
|
||||
const {tags} = blogPost.metadata;
|
||||
if (!tags || tags.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
tags.forEach(tag => {
|
||||
const normalizedTag = tag.toLowerCase();
|
||||
if (!blogTags[normalizedTag]) {
|
||||
blogTags[normalizedTag] = [];
|
||||
}
|
||||
blogTags[normalizedTag].push(blogPost.id);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
blogPosts,
|
||||
blogListPaginated,
|
||||
blogTags,
|
||||
};
|
||||
}
|
||||
|
||||
async contentLoaded({content: blogContents, actions}) {
|
||||
const {blogListComponent, blogPostComponent} = this.options;
|
||||
const {
|
||||
blogListComponent,
|
||||
blogPostComponent,
|
||||
blogTagsListComponent,
|
||||
blogTagsPostsComponent,
|
||||
} = this.options;
|
||||
|
||||
const {addRoute, createData} = actions;
|
||||
const {blogPosts, blogListPaginated} = blogContents;
|
||||
const {blogPosts, blogListPaginated, blogTags} = blogContents;
|
||||
|
||||
const blogItemsToModules = {};
|
||||
// Create routes for blog entries.
|
||||
|
@ -213,6 +239,76 @@ class DocusaurusPluginContentBlog {
|
|||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Tags.
|
||||
const {routeBasePath} = this.options;
|
||||
const {
|
||||
siteConfig: {baseUrl},
|
||||
} = this.context;
|
||||
|
||||
const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
|
||||
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
|
||||
const tagsModule = {};
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(blogTags).map(async tag => {
|
||||
const permalink = normalizeUrl([tagsPath, tag]);
|
||||
const postIDs = blogTags[tag];
|
||||
tagsModule[tag] = {
|
||||
count: postIDs.length,
|
||||
permalink,
|
||||
};
|
||||
|
||||
const tagsMetadataPath = await createData(
|
||||
`${docuHash(permalink)}.json`,
|
||||
JSON.stringify(
|
||||
{
|
||||
tag,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
addRoute({
|
||||
path: permalink,
|
||||
component: blogTagsPostsComponent,
|
||||
exact: true,
|
||||
modules: {
|
||||
items: postIDs.map(postID => {
|
||||
const {metadata: postMetadata, metadataPath} = blogItemsToModules[
|
||||
postID
|
||||
];
|
||||
return {
|
||||
content: {
|
||||
__import: true,
|
||||
path: postMetadata.source,
|
||||
query: {
|
||||
truncated: true,
|
||||
},
|
||||
},
|
||||
metadata: metadataPath,
|
||||
};
|
||||
}),
|
||||
metadata: tagsMetadataPath,
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const tagsListPath = await createData(
|
||||
`${docuHash(`${tagsPath}-tags`)}.json`,
|
||||
JSON.stringify(tagsModule, null, 2),
|
||||
);
|
||||
|
||||
addRoute({
|
||||
path: tagsPath,
|
||||
component: blogTagsListComponent,
|
||||
exact: true,
|
||||
modules: {
|
||||
tags: tagsListPath,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getThemePath() {
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Layout from '@theme/Layout'; // eslint-disable-line
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
const CHARS_IN_ALPHABET = 26;
|
||||
const ASCII_LOWERCASE_A = 97;
|
||||
|
||||
function BlogTagsListPage(props) {
|
||||
const {tags} = props;
|
||||
|
||||
const tagsList = Array(CHARS_IN_ALPHABET)
|
||||
.fill(null)
|
||||
.map(() => []);
|
||||
const allTags = Object.keys(tags).sort();
|
||||
|
||||
allTags.forEach(tag => {
|
||||
const firstLetter = tag.charCodeAt(0);
|
||||
tagsList[firstLetter - ASCII_LOWERCASE_A].push(tag);
|
||||
});
|
||||
|
||||
const tagsSection = tagsList
|
||||
.map((tagsForLetter, index) => {
|
||||
if (tagsForLetter.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const letter = String.fromCharCode(
|
||||
ASCII_LOWERCASE_A + index,
|
||||
).toUpperCase();
|
||||
|
||||
return (
|
||||
<div key={letter}>
|
||||
<h3>{letter}</h3>
|
||||
{tagsForLetter.map(tag => (
|
||||
<Link
|
||||
className="padding-right--md"
|
||||
href={tags[tag].permalink}
|
||||
key="tag">
|
||||
{tag} ({tags[tag].count})
|
||||
</Link>
|
||||
))}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
.filter(item => item != null);
|
||||
|
||||
return (
|
||||
<Layout title="Blog Tags" description="Blog Tags">
|
||||
<div className="container margin-vert--xl">
|
||||
<div className="row">
|
||||
<div className="col col--8 col--offset-2">
|
||||
<h1>Tags</h1>
|
||||
<div className="margin-vert--lg">{tagsSection}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default BlogTagsListPage;
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Layout from '@theme/Layout'; // eslint-disable-line
|
||||
import BlogPostItem from '@theme/BlogPostItem';
|
||||
|
||||
function BlogTagsPostPage(props) {
|
||||
const {metadata, items} = props;
|
||||
const {tag} = metadata;
|
||||
|
||||
return (
|
||||
<Layout title={`Blog | Tagged ${tag}`} description={`Blog | Tagged ${tag}`}>
|
||||
<div className="container margin-vert--xl">
|
||||
<div className="row">
|
||||
<div className="col col--8 col--offset-2">
|
||||
<h1>
|
||||
{items.length} post(s) tagged with "{tag}"
|
||||
</h1>
|
||||
<div className="margin-vert--lg">
|
||||
{items.map(
|
||||
({content: BlogPostContent, metadata: blogPostMetadata}) => (
|
||||
<div key={blogPostMetadata.permalink}>
|
||||
<BlogPostItem
|
||||
frontMatter={BlogPostContent.frontMatter}
|
||||
metadata={blogPostMetadata}
|
||||
truncated>
|
||||
<BlogPostContent />
|
||||
</BlogPostItem>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default BlogTagsPostPage;
|
Loading…
Add table
Add a link
Reference in a new issue