feat(v2): implement blog (#1062)

* feat(v2): implement blog

* expect flat blog structure

* \n

* blogpage can import many posts
This commit is contained in:
Endilie Yacop Sucipto 2018-10-25 14:23:29 +08:00 committed by GitHub
parent a2d3f26722
commit 12fd204840
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 736 additions and 16 deletions

73
v2/lib/load/blog.js Normal file
View file

@ -0,0 +1,73 @@
const globby = require('globby');
const path = require('path');
const fs = require('fs-extra');
const {parse, idx} = require('./utils');
function fileToUrl(fileName) {
return fileName
.replace('-', '/')
.replace('-', '/')
.replace('-', '/')
.replace(/\.md$/, '');
}
async function loadBlog({blogDir, env, siteConfig}) {
const blogFiles = await globby(['*.md'], {
cwd: blogDir,
});
const {baseUrl} = siteConfig;
/* Prepare metadata container */
const blogMetadatas = [];
/* the language for each blog page */
const defaultLangTag = idx(env, ['translation', 'defaultLanguage', 'tag']);
await Promise.all(
blogFiles.map(async relativeSource => {
const source = path.join(blogDir, relativeSource);
const blogFileName = path.basename(relativeSource);
// Extract, YYYY, MM, DD from the file name
const filePathDateArr = blogFileName.split('-');
const date = new Date(
`${filePathDateArr[0]}-${filePathDateArr[1]}-${
filePathDateArr[2]
}T06:00:00.000Z`,
);
const fileString = await fs.readFile(source, 'utf-8');
const {metadata: rawMetadata} = parse(fileString);
const metadata = {
permalink: path.join(baseUrl, `blog`, fileToUrl(blogFileName)),
source,
...rawMetadata,
date,
language: defaultLangTag,
};
blogMetadatas.push(metadata);
}),
);
blogMetadatas.sort((a, b) => a.date - b.date);
// blogpage handling. Example: `/blog`, `/blog/page1`, `/blog/page2`
const perPage = 10;
const numOfBlog = blogMetadatas.length;
const numberOfPage = Math.ceil(numOfBlog / perPage);
const basePageUrl = path.join(baseUrl, 'blog');
/* eslint-disable */
for (let page = 0; page < numberOfPage; page++) {
blogMetadatas.push({
permalink: path.join(basePageUrl, `${page > 0 ? `page${page + 1}` : ''}`),
language: defaultLangTag,
isBlogPage: true,
posts: blogMetadatas.slice(page * perPage, (page + 1) * perPage),
});
}
return blogMetadatas;
}
module.exports = loadBlog;

View file

@ -1,4 +1,5 @@
const path = require('path');
const loadBlog = require('./blog');
const loadConfig = require('./config');
const loadDocs = require('./docs');
const loadEnv = require('./env');
@ -55,6 +56,14 @@ module.exports = async function load(siteDir) {
`export default ${JSON.stringify(pagesMetadatas, null, 2)};`,
);
// blog
const blogDir = path.resolve(siteDir, 'blog');
const blogMetadatas = await loadBlog({blogDir, env, siteConfig});
await generate(
'blogMetadatas.js',
`export default ${JSON.stringify(blogMetadatas, null, 2)};`,
);
// resolve outDir
const outDir = path.resolve(siteDir, 'build');
@ -68,6 +77,8 @@ module.exports = async function load(siteDir) {
const props = {
siteConfig,
siteDir,
blogDir,
blogMetadatas,
docsDir,
docsMetadatas,
docsSidebars,

View file

@ -1,4 +1,8 @@
async function genRoutesConfig({docsMetadatas = {}, pagesMetadatas = []}) {
async function genRoutesConfig({
docsMetadatas = {},
pagesMetadatas = [],
blogMetadatas = [],
}) {
function genDocsRoute(metadata) {
const {permalink, source} = metadata;
return `
@ -41,6 +45,54 @@ async function genRoutesConfig({docsMetadatas = {}, pagesMetadatas = []}) {
}`;
}
function genBlogRoute(metadata) {
const {permalink, source} = metadata;
if (metadata.isBlogPage) {
const {posts} = metadata;
return `
{
path: ${JSON.stringify(permalink)},
exact: true,
component: Loadable.Map({
loader: {
${posts
.map((p, i) => `post${i}: () => import(${JSON.stringify(p.source)})`)
.join(',\n\t\t\t\t')}
},
loading: Loading,
render(loaded, props) {
${posts
.map((p, i) => `const Post${i} = loaded.post${i}.default;`)
.join('\n\t\t\t\t')}
return (
<BlogPage {...props} metadata={${JSON.stringify(metadata)}} >
${posts.map((p, i) => `<Post${i} />`).join(' ')}
</BlogPage>
)
}
})
}`;
}
return `
{
path: ${JSON.stringify(permalink)},
exact: true,
component: Loadable({
loader: () => import(${JSON.stringify(source)}),
loading: Loading,
render(loaded, props) {
let MarkdownContent = loaded.default;
return (
<BlogPost {...props} metadata={${JSON.stringify(metadata)}}>
<MarkdownContent />
</BlogPost>
);
}
})
}`;
}
const notFoundRoute = `,
{
path: '*',
@ -56,10 +108,14 @@ async function genRoutesConfig({docsMetadatas = {}, pagesMetadatas = []}) {
`import Loadable from 'react-loadable';\n` +
`import Loading from '@theme/Loading';\n` +
`import Doc from '@theme/Doc';\n` +
`import BlogPost from '@theme/BlogPost';\n` +
`import BlogPage from '@theme/BlogPage';\n` +
`import Pages from '@theme/Pages';\n` +
`import NotFound from '@theme/NotFound';\n` +
`const routes = [${docsRoutes},${pagesMetadatas
.map(genPagesRoute)
.join(',')},${blogMetadatas
.map(genBlogRoute)
.join(',')}${notFoundRoute}\n];\n` +
`export default routes;\n`
);

View file

@ -7,7 +7,15 @@ module.exports = function loadConfig(siteDir) {
? customThemePath
: path.resolve(__dirname, '../theme');
const themeComponents = ['Doc', 'Pages', 'Loading', 'NotFound', 'Markdown'];
const themeComponents = [
'Doc',
'BlogPost',
'BlogPage',
'Pages',
'Loading',
'NotFound',
'Markdown',
];
themeComponents.forEach(component => {
if (!require.resolve(path.join(themePath, component))) {
throw new Error(