feat(v2): list blog tags on posts (#1456)

* feat(v2): list blog tags on posts

* fix date handling on blog header

* fix console log error due to non unique key
This commit is contained in:
Yangshun Tay 2019-05-15 02:56:08 -07:00 committed by Endi
parent 55d7920825
commit f47059b5bd
5 changed files with 64 additions and 29 deletions

View file

@ -135,29 +135,44 @@ class DocusaurusPluginContentBlog {
} }
const blogTags = {}; const blogTags = {};
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
blogPosts.forEach(blogPost => { blogPosts.forEach(blogPost => {
const {tags} = blogPost.metadata; const {tags} = blogPost.metadata;
if (!tags || tags.length === 0) { if (!tags || tags.length === 0) {
// TODO: Extract tags out into a separate plugin.
// eslint-disable-next-line no-param-reassign
blogPost.metadata.tags = [];
return; return;
} }
tags.forEach(tag => { // eslint-disable-next-line no-param-reassign
blogPost.metadata.tags = tags.map(tag => {
const normalizedTag = _.kebabCase(tag); const normalizedTag = _.kebabCase(tag);
const permalink = normalizeUrl([tagsPath, normalizedTag]);
if (!blogTags[normalizedTag]) { if (!blogTags[normalizedTag]) {
blogTags[normalizedTag] = { blogTags[normalizedTag] = {
name: tag.toLowerCase(), // Will only use the name of the first occurrence of the tag. name: tag.toLowerCase(), // Will only use the name of the first occurrence of the tag.
items: [], items: [],
permalink,
}; };
} }
blogTags[normalizedTag].items.push(blogPost.id); blogTags[normalizedTag].items.push(blogPost.id);
return {
label: tag,
permalink,
};
}); });
}); });
const blogTagsListPath = Object.keys(blogTags).length > 0 ? tagsPath : null;
return { return {
blogPosts, blogPosts,
blogListPaginated, blogListPaginated,
blogTags, blogTags,
blogTagsListPath,
}; };
} }
@ -174,7 +189,12 @@ class DocusaurusPluginContentBlog {
} = this.options; } = this.options;
const {addRoute, createData} = actions; const {addRoute, createData} = actions;
const {blogPosts, blogListPaginated, blogTags} = blogContents; const {
blogPosts,
blogListPaginated,
blogTags,
blogTagsListPath,
} = blogContents;
const blogItemsToModules = {}; const blogItemsToModules = {};
// Create routes for blog entries. // Create routes for blog entries.
@ -254,21 +274,14 @@ class DocusaurusPluginContentBlog {
); );
// Tags. // Tags.
const {routeBasePath} = this.options;
const {
siteConfig: {baseUrl},
} = this.context;
const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
const tagsModule = {}; const tagsModule = {};
await Promise.all( await Promise.all(
Object.keys(blogTags).map(async tag => { Object.keys(blogTags).map(async tag => {
const permalink = normalizeUrl([tagsPath, tag]); const {name, items, permalink} = blogTags[tag];
const {name, items} = blogTags[tag];
tagsModule[tag] = { tagsModule[tag] = {
allTagsPath: blogTagsListPath,
slug: tag, slug: tag,
name, name,
count: items.length, count: items.length,
@ -309,12 +322,12 @@ class DocusaurusPluginContentBlog {
// Only create /tags page if there are tags. // Only create /tags page if there are tags.
if (Object.keys(blogTags).length > 0) { if (Object.keys(blogTags).length > 0) {
const tagsListPath = await createData( const tagsListPath = await createData(
`${docuHash(`${tagsPath}-tags`)}.json`, `${docuHash(`${blogTagsListPath}-tags`)}.json`,
JSON.stringify(tagsModule, null, 2), JSON.stringify(tagsModule, null, 2),
); );
addRoute({ addRoute({
path: tagsPath, path: blogTagsListPath,
component: blogTagsListComponent, component: blogTagsListComponent,
exact: true, exact: true,
modules: { modules: {

View file

@ -10,12 +10,12 @@ import Link from '@docusaurus/Link';
function BlogPostItem(props) { function BlogPostItem(props) {
const {children, frontMatter, metadata, truncated} = props; const {children, frontMatter, metadata, truncated} = props;
const {date, permalink, tags} = metadata;
const {author, authorURL, authorTitle, authorFBID, title} = frontMatter;
const renderPostHeader = () => { const renderPostHeader = () => {
const {author, authorURL, authorTitle, authorFBID, title} = frontMatter; const match = date.substring(0, 10).split('-');
const {date, permalink} = metadata; const year = match[0];
const blogPostDate = new Date(date);
const month = [ const month = [
'January', 'January',
'February', 'February',
@ -29,7 +29,8 @@ function BlogPostItem(props) {
'October', 'October',
'November', 'November',
'December', 'December',
]; ][parseInt(match[1], 10) - 1];
const day = parseInt(match[2], 10);
const authorImageURL = authorFBID const authorImageURL = authorFBID
? `https://graph.facebook.com/${authorFBID}/picture/?height=200&width=200` ? `https://graph.facebook.com/${authorFBID}/picture/?height=200&width=200`
@ -37,12 +38,13 @@ function BlogPostItem(props) {
return ( return (
<header> <header>
<h1> <h1 className="margin-bottom--xs">
<Link to={permalink}>{title}</Link> <Link to={permalink}>{title}</Link>
</h1> </h1>
<div className="margin-bottom--sm"> <div className="margin-bottom--sm">
{month[blogPostDate.getMonth()]} {blogPostDate.getDay()},{' '} <small>
{blogPostDate.getFullYear()} {month} {day}, {year}
</small>
</div> </div>
<div className="avatar margin-bottom--md"> <div className="avatar margin-bottom--md">
{authorImageURL && ( {authorImageURL && (
@ -79,11 +81,30 @@ function BlogPostItem(props) {
<div> <div>
{renderPostHeader()} {renderPostHeader()}
<article className="markdown">{children}</article> <article className="markdown">{children}</article>
{truncated && ( <div className="row margin-vert--lg">
<div className="text--right margin-vert--md"> <div className="col col-6">
<Link to={metadata.permalink}>Read More</Link> {tags.length > 0 && (
<>
<strong>Tags:</strong>
{tags.map(({label, permalink: tagPermalink}) => (
<Link
key={tagPermalink}
className="margin-horiz--sm"
to={tagPermalink}>
{label}
</Link>
))}
</>
)}
</div> </div>
)} <div className="col col-6 text--right">
{truncated && (
<Link to={metadata.permalink}>
<strong>Read More</strong>
</Link>
)}
</div>
</div>
</div> </div>
); );
} }

View file

@ -23,7 +23,7 @@ function BlogPostPage(props) {
<BlogPostItem frontMatter={frontMatter} metadata={metadata}> <BlogPostItem frontMatter={frontMatter} metadata={metadata}>
<BlogPostContents /> <BlogPostContents />
</BlogPostItem> </BlogPostItem>
<div className="margin-vert--lg"> <div className="margin-vert--xl">
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} /> <BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
</div> </div>
</div> </div>

View file

@ -9,10 +9,11 @@ import React from 'react';
import Layout from '@theme/Layout'; // eslint-disable-line import Layout from '@theme/Layout'; // eslint-disable-line
import BlogPostItem from '@theme/BlogPostItem'; import BlogPostItem from '@theme/BlogPostItem';
import Link from '@docusaurus/Link';
function BlogTagsPostPage(props) { function BlogTagsPostPage(props) {
const {metadata, items} = props; const {metadata, items} = props;
const {name: tagName, count} = metadata; const {allTagsPath, name: tagName, count} = metadata;
return ( return (
<Layout <Layout
@ -24,7 +25,8 @@ function BlogTagsPostPage(props) {
<h1> <h1>
{count} post(s) tagged with &quot;{tagName}&quot; {count} post(s) tagged with &quot;{tagName}&quot;
</h1> </h1>
<div className="margin-vert--lg"> <Link href={allTagsPath}>View All Tags</Link>
<div className="margin-vert--xl">
{items.map( {items.map(
({content: BlogPostContent, metadata: blogPostMetadata}) => ( ({content: BlogPostContent, metadata: blogPostMetadata}) => (
<div key={blogPostMetadata.permalink}> <div key={blogPostMetadata.permalink}>

View file

@ -4,7 +4,6 @@ author: Joel Marcey
authorURL: http://twitter.com/JoelMarcey authorURL: http://twitter.com/JoelMarcey
authorFBID: 611217057 authorFBID: 611217057
authorTwitter: JoelMarcey authorTwitter: JoelMarcey
tags: [birth]
--- ---
![Introducing Slash](/img/slash-introducing.svg) ![Introducing Slash](/img/slash-introducing.svg)