Skip to content

Commit

Permalink
refactor(v2): blog data revamp (facebook#1450)
Browse files Browse the repository at this point in the history
* refactor(v2): blog data revamp

* fix(v2): fix incorrect blog total count

* misc: remove console.log

* feat(v2): export frontMatter as an object within MDX file (facebook#1451)

* refactor. Don't confuse metadata & frontmatter

* export frontMatter in content itself

* nits

* nits name

* dont truncate first four lines in blog
  • Loading branch information
yangshun authored and shakcho committed May 17, 2019
1 parent 9060e0b commit fb6c1fc
Show file tree
Hide file tree
Showing 18 changed files with 259 additions and 120 deletions.
1 change: 1 addition & 0 deletions packages/docusaurus-mdx-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@mdx-js/mdx": "^1.0.18",
"@mdx-js/react": "^1.0.16",
"github-slugger": "^1.2.1",
"gray-matter": "^4.0.2",
"loader-utils": "^1.2.3",
"mdast-util-to-string": "^1.0.5",
"prism-themes": "^1.1.0",
Expand Down
6 changes: 5 additions & 1 deletion packages/docusaurus-mdx-loader/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const mdx = require('@mdx-js/mdx');
const rehypePrism = require('@mapbox/rehype-prism');
const emoji = require('remark-emoji');
const slug = require('rehype-slug');
const matter = require('gray-matter');
const stringifyObject = require('stringify-object');
const linkHeadings = require('./linkHeadings');
const rightToc = require('./rightToc');

Expand All @@ -19,9 +21,10 @@ const DEFAULT_OPTIONS = {
prismTheme: 'prism-themes/themes/prism-atom-dark.css',
};

module.exports = async function(content) {
module.exports = async function(fileString) {
const callback = this.async();

const {data, content} = matter(fileString);
const options = Object.assign(DEFAULT_OPTIONS, getOptions(this), {
filepath: this.resourcePath,
});
Expand All @@ -43,6 +46,7 @@ module.exports = async function(content) {
import React from 'react';
import { mdx } from '@mdx-js/react';
${importStr}
export const frontMatter = ${stringifyObject(data)};
${result}
`;

Expand Down
1 change: 0 additions & 1 deletion packages/docusaurus-plugin-content-blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"@docusaurus/utils": "^2.0.0-alpha.13",
"fs-extra": "^7.0.1",
"globby": "^9.1.0",
"gray-matter": "^4.0.2",
"loader-utils": "^1.2.3"
},
"peerDependencies": {
Expand Down
178 changes: 118 additions & 60 deletions packages/docusaurus-plugin-content-blog/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const DEFAULT_OPTIONS = {
path: 'blog', // Path to data on filesystem, relative to site dir.
routeBasePath: 'blog', // URL Route.
include: ['*.md', '*.mdx'], // Extensions to include.
pageCount: 10, // How many entries per page.
postsPerPage: 10, // How many posts per page.
blogListComponent: '@theme/BlogListPage',
blogPostComponent: '@theme/BlogPostPage',
};
Expand All @@ -47,9 +47,9 @@ class DocusaurusPluginContentBlog {
return [...globPattern];
}

// Fetches blog contents and returns metadata for the contents.
// Fetches blog contents and returns metadata for the necessary routes.
async loadContent() {
const {pageCount, include, routeBasePath} = this.options;
const {postsPerPage, include, routeBasePath} = this.options;
const {siteConfig} = this.context;
const blogDir = this.contentPath;

Expand All @@ -58,8 +58,7 @@ class DocusaurusPluginContentBlog {
cwd: blogDir,
});

// Prepare metadata container.
const blogMetadata = [];
const blogPosts = [];

await Promise.all(
blogFiles.map(async relativeSource => {
Expand All @@ -75,82 +74,141 @@ class DocusaurusPluginContentBlog {
);

const fileString = await fs.readFile(source, 'utf-8');
const {metadata: rawMetadata, excerpt: description} = parse(fileString);

const metadata = {
permalink: normalizeUrl([
baseUrl,
routeBasePath,
fileToUrl(blogFileName),
]),
source,
description,
...rawMetadata,
date,
};
blogMetadata.push(metadata);
const {frontMatter, excerpt} = parse(fileString);

blogPosts.push({
id: blogFileName,
metadata: {
permalink: normalizeUrl([
baseUrl,
routeBasePath,
fileToUrl(blogFileName),
]),
source,
description: frontMatter.description || excerpt,
date,
title: frontMatter.title || blogFileName,
},
});
}),
);
blogMetadata.sort((a, b) => b.date - a.date);
blogPosts.sort((a, b) => b.metadata.date - a.metadata.date);

// Blog page handling. Example: `/blog`, `/blog/page1`, `/blog/page2`
const numOfBlog = blogMetadata.length;
const numberOfPage = Math.ceil(numOfBlog / pageCount);
// Blog pagination routes.
// Example: `/blog`, `/blog/page/1`, `/blog/page/2`
const totalCount = blogPosts.length;
const numberOfPages = Math.ceil(totalCount / postsPerPage);
const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);

// eslint-disable-next-line
for (let page = 0; page < numberOfPage; page++) {
blogMetadata.push({
permalink:
page > 0
? normalizeUrl([basePageUrl, `page/${page + 1}`])
: basePageUrl,
isBlogPage: true,
posts: blogMetadata.slice(page * pageCount, (page + 1) * pageCount),
const blogListPaginated = [];

function blogPaginationPermalink(page) {
return page > 0
? normalizeUrl([basePageUrl, `page/${page + 1}`])
: basePageUrl;
}

for (let page = 0; page < numberOfPages; page += 1) {
blogListPaginated.push({
metadata: {
permalink: blogPaginationPermalink(page),
page: page + 1,
postsPerPage,
totalPages: numberOfPages,
totalCount,
previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
nextPage:
page < numberOfPages - 1 ? blogPaginationPermalink(page + 1) : null,
},
items: blogPosts
.slice(page * postsPerPage, (page + 1) * postsPerPage)
.map(item => item.id),
});
}

return blogMetadata;
return {
blogPosts,
blogListPaginated,
};
}

async contentLoaded({content, actions}) {
async contentLoaded({content: blogContents, actions}) {
const {blogListComponent, blogPostComponent} = this.options;
const {addRoute, createData} = actions;
await Promise.all(
content.map(async metadataItem => {
const {isBlogPage, permalink} = metadataItem;
const {blogPosts, blogListPaginated} = blogContents;

const blogItemsToModules = {};
// Create routes for blog entries.
const blogItems = await Promise.all(
blogPosts.map(async blogPost => {
const {id, metadata} = blogPost;
const {permalink} = metadata;
const metadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(metadataItem, null, 2),
JSON.stringify(metadata, null, 2),
);
if (isBlogPage) {
addRoute({
path: permalink,
component: blogListComponent,
exact: true,
modules: {
entries: metadataItem.posts.map(post => ({
// To tell routes.js this is an import and not a nested object to recurse.
__import: true,
path: post.source,
query: {
truncated: true,
},
})),
metadata: metadataPath,
},
});
const temp = {
metadata,
metadataPath,
};

return;
}
blogItemsToModules[id] = temp;
return temp;
}),
);

blogItems.forEach((blogItem, index) => {
const prevItem = index > 0 ? blogItems[index - 1] : null;
const nextItem =
index < blogItems.length - 1 ? blogItems[index + 1] : null;
const {metadata, metadataPath} = blogItem;
const {source, permalink} = metadata;

addRoute({
path: permalink,
component: blogPostComponent,
exact: true,
modules: {
content: source,
metadata: metadataPath,
prevItem: prevItem && prevItem.metadataPath,
nextItem: nextItem && nextItem.metadataPath,
},
});
});

// Create routes for blog's paginated list entries.
await Promise.all(
blogListPaginated.map(async listPage => {
const {metadata, items} = listPage;
const {permalink} = metadata;
const pageMetadataPath = await createData(
`${docuHash(permalink)}.json`,
JSON.stringify(metadata, null, 2),
);

addRoute({
path: permalink,
component: blogPostComponent,
component: blogListComponent,
exact: true,
modules: {
content: metadataItem.source,
metadata: metadataPath,
items: items.map(postID => {
const {metadata: postMetadata, metadataPath} = blogItemsToModules[
postID
];
// To tell routes.js this is an import and not a nested object to recurse.
return {
content: {
__import: true,
path: postMetadata.source,
query: {
truncated: true,
},
},
metadata: metadataPath,
};
}),
metadata: pageMetadataPath,
},
});
}),
Expand Down
20 changes: 5 additions & 15 deletions packages/docusaurus-plugin-content-blog/src/markdownLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,20 @@
* LICENSE file in the root directory of this source tree.
*/

const matter = require('gray-matter');
const {parseQuery} = require('loader-utils');

const TRUNCATE_MARKER = /<!--\s*truncate\s*-->/;

module.exports = async function(fileString) {
const callback = this.async();

// Extract content of markdown (without frontmatter).
let {content} = matter(fileString);
let finalContent = fileString;

// Truncate content if requested (e.g: file.md?truncated=true)
const {truncated} = this.resourceQuery && parseQuery(this.resourceQuery);
if (truncated) {
if (TRUNCATE_MARKER.test(content)) {
// eslint-disable-next-line
content = content.split(TRUNCATE_MARKER)[0];
} else {
// Return first 4 lines of the content as summary
content = content
.split('\n')
.slice(0, 4)
.join('\n');
}
if (truncated && TRUNCATE_MARKER.test(fileString)) {
// eslint-disable-next-line
finalContent = fileString.split(TRUNCATE_MARKER)[0];
}
return callback(null, content);
return callback(null, finalContent);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,31 @@ import React from 'react';

import Layout from '@theme/Layout'; // eslint-disable-line
import BlogPostItem from '@theme/BlogPostItem';
import BlogListPaginator from '@theme/BlogListPaginator';

function BlogListPage(props) {
const {
metadata: {posts = []},
entries: BlogPosts,
} = props;
const {metadata, items} = props;

return (
<Layout title="Blog" description="Blog">
<div className="container margin-vert--xl">
<div className="row">
<div className="col col--6 col--offset-3">
{BlogPosts.map((PostContent, index) => (
<div className="margin-bottom--xl" key={index}>
<BlogPostItem truncated metadata={posts[index]}>
<PostContent />
</BlogPostItem>
</div>
))}
<div className="col col--8 col--offset-2">
{items.map(
({content: BlogPostContent, metadata: blogPostMetadata}) => (
<div
className="margin-bottom--xl"
key={blogPostMetadata.permalink}>
<BlogPostItem
frontMatter={BlogPostContent.frontMatter}
metadata={blogPostMetadata}
truncated>
<BlogPostContent />
</BlogPostItem>
</div>
),
)}
<BlogListPaginator metadata={metadata} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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 Link from '@docusaurus/Link';

function BlogListPaginator(props) {
const {metadata} = props;
const {previousPage, nextPage} = metadata;

return (
<div className="row">
<div className="col col--6">
{previousPage && (
<Link className="button button--secondary" to={previousPage}>
Newer entries
</Link>
)}
</div>
<div className="col col--6 text--right">
{nextPage && (
<Link className="button button--secondary" to={nextPage}>
Older entries
</Link>
)}
</div>
</div>
);
}

export default BlogListPaginator;
Loading

0 comments on commit fb6c1fc

Please sign in to comment.