Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(v2): blog data revamp #1450

Merged
merged 4 commits into from
May 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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