feat(v2): enhance @docusaurus/plugin-content-blog (#1311)

* feat(v2): @docusaurus/plugin-content-blog

* address code review
This commit is contained in:
Endilie Yacop Sucipto 2019-03-26 01:39:58 +07:00 committed by GitHub
parent bd0cdbc701
commit 4bd5d3937e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 156 additions and 55 deletions

View file

@ -6,11 +6,10 @@
*/
import React, {useContext} from 'react';
import Link from '@docusaurus/Link';
import Head from '@docusaurus/Head';
import Layout from '@theme/Layout'; // eslint-disable-line
import DocusaurusContext from '@docusaurus/context';
import Post from '../Post';
function BlogPage(props) {
const context = useContext(DocusaurusContext);
@ -30,15 +29,13 @@ function BlogPage(props) {
{language && <meta name="docsearch:language" content={language} />}
</Head>
<div>
<ul>
{posts.map(metadata => (
<li key={metadata.permalink}>
<Link to={metadata.permalink}>{metadata.permalink}</Link>
</li>
))}
</ul>
{BlogPosts.map((BlogPost, index) => (
<BlogPost key={index} />
{BlogPosts.map((PostContent, index) => (
<Post
key={index}
truncated={posts[index].truncatedSource}
metadata={posts[index]}>
<PostContent />
</Post>
))}
</div>
</Layout>

View file

@ -0,0 +1,43 @@
/**
* 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, {useContext} from 'react';
import Head from '@docusaurus/Head';
import Layout from '@theme/Layout'; // eslint-disable-line
import DocusaurusContext from '@docusaurus/context';
import Post from '../Post';
import styles from './styles.module.css';
function BlogPost(props) {
const {metadata: contextMetadata = {}, siteConfig = {}} = useContext(
DocusaurusContext,
);
const {baseUrl, favicon} = siteConfig;
const {language, title} = contextMetadata;
const {modules, metadata} = props;
const BlogPostContents = modules[0];
return (
<Layout>
<Head defaultTitle={siteConfig.title}>
{title && <title>{title}</title>}
{favicon && <link rel="shortcut icon" href={baseUrl + favicon} />}
{language && <html lang={language} />}
</Head>
{BlogPostContents && (
<div className={styles.blogPostContainer}>
<Post metadata={metadata}>
<BlogPostContents />
</Post>
</div>
)}
</Layout>
);
}
export default BlogPost;

View file

@ -0,0 +1,15 @@
/**
* 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.
*/
.blogPostContainer {
border-bottom: 1px solid #e0e0e0;
border-radius: 3px;
padding-bottom: 20px;
margin: 50px auto;
text-align: left;
max-width: 1100px;
}

View file

@ -7,21 +7,16 @@
import React from 'react';
import Link from '@docusaurus/Link';
import Head from '@docusaurus/Head';
import classnames from 'classnames';
import Layout from '@theme/Layout'; // eslint-disable-line
import DocusaurusContext from '@docusaurus/context';
import classnames from 'classnames'; // eslint-disable-line
import styles from './styles.module.css';
class BlogPost extends React.Component {
renderPostHeader() {
const {metadata} = this.props;
function Post(props) {
const {metadata, children, truncated} = props;
const renderPostHeader = () => {
if (!metadata) {
return null;
}
const {
date,
author,
@ -82,29 +77,21 @@ class BlogPost extends React.Component {
</div>
</header>
);
}
};
render() {
const {metadata = {}, siteConfig = {}} = this.context;
const {baseUrl, favicon} = siteConfig;
const {language, title} = metadata;
const {modules} = this.props;
const BlogPostContents = modules[0];
return (
<Layout>
<Head defaultTitle={siteConfig.title}>
{title && <title>{title}</title>}
{favicon && <link rel="shortcut icon" href={baseUrl + favicon} />}
{language && <html lang={language} />}
</Head>
{this.renderPostHeader()}
<BlogPostContents />
</Layout>
);
}
return (
<div className={styles.postContainer}>
{renderPostHeader()}
{children}
{truncated && (
<div className={styles.readMoreContainer}>
<Link className={styles.readMoreLink} to={metadata.permalink}>
Read More
</Link>
</div>
)}
</div>
);
}
BlogPost.contextType = DocusaurusContext;
export default BlogPost;
export default Post;

View file

@ -45,3 +45,38 @@
height: 50px;
width: 50px;
}
.readMoreContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.readMoreLink {
border: 1px solid #2e8555;
border-radius: 3px;
color: #2e8555;
display: inline-block;
font-size: 14px;
font-weight: 400;
line-height: 1.2em;
padding: 10px;
text-decoration: none!important;
text-transform: uppercase;
transition: background .3s,color .3s;
}
.readMoreLink:hover {
background-color: #00a388;
color: #fff;
}
.postContainer {
border-bottom: 1px solid #e0e0e0;
border-radius: 3px;
padding-bottom: 20px;
margin: 50px auto;
text-align: left;
width: 600px;
max-width: 1100px;
}

View file

@ -8,7 +8,7 @@
const globby = require('globby');
const path = require('path');
const fs = require('fs-extra');
const {parse, idx, normalizeUrl} = require('@docusaurus/utils');
const {parse, idx, normalizeUrl, generate} = require('@docusaurus/utils');
function fileToUrl(fileName) {
return fileName
@ -25,10 +25,12 @@ const DEFAULT_OPTIONS = {
routeBasePath: 'blog', // URL Route.
include: ['*.md, *.mdx'], // Extensions to include.
pageCount: 10, // How many entries per page.
blogPageComponent: '@theme/BlogPage',
blogPostComponent: '@theme/BlogPost',
blogPageComponent: path.resolve(__dirname, './components/BlogPage'),
blogPostComponent: path.resolve(__dirname, './components/BlogPost'),
};
const TRUNCATE_MARKER = /<!--\s*truncate\s*-->/;
class DocusaurusPluginContentBlog {
constructor(opts, context) {
this.options = {...DEFAULT_OPTIONS, ...opts};
@ -43,7 +45,7 @@ class DocusaurusPluginContentBlog {
// Fetches blog contents and returns metadata for the contents.
async loadContent() {
const {pageCount, include, routeBasePath} = this.options;
const {env, siteConfig} = this.context;
const {env, generatedFilesDir, siteConfig} = this.context;
const blogDir = this.contentPath;
const {baseUrl} = siteConfig;
@ -71,7 +73,20 @@ class DocusaurusPluginContentBlog {
);
const fileString = await fs.readFile(source, 'utf-8');
const {metadata: rawMetadata} = parse(fileString);
const {metadata: rawMetadata, content} = parse(fileString);
let truncatedSource;
const isTruncated = TRUNCATE_MARKER.test(content);
if (isTruncated) {
const pluginContentDir = path.join(generatedFilesDir, this.getName());
await generate(
pluginContentDir,
blogFileName,
content.split(TRUNCATE_MARKER)[0],
);
truncatedSource = path.join(pluginContentDir, blogFileName);
}
const metadata = {
permalink: normalizeUrl([
baseUrl,
@ -79,6 +94,7 @@ class DocusaurusPluginContentBlog {
fileToUrl(blogFileName),
]),
source,
truncatedSource,
...rawMetadata,
date,
language: defaultLangTag,
@ -119,7 +135,9 @@ class DocusaurusPluginContentBlog {
path: permalink,
component: blogPageComponent,
metadata: metadataItem,
modules: metadataItem.posts.map(post => post.source),
modules: metadataItem.posts.map(
post => post.truncatedSource || post.source,
),
});
return;
}

View file

@ -3,10 +3,15 @@
"version": "1.0.0",
"description": "Blog content plugin for Docusaurus",
"main": "index.js",
"files": [
"components"
],
"license": "MIT",
"dependencies": {
"@docusaurus/utils": "^1.0.0",
"classnames": "^2.2.6",
"fs-extra": "^7.0.1",
"globby": "^9.1.0"
"globby": "^9.1.0",
"react": "^16.8.5"
}
}

View file

@ -14,6 +14,7 @@ const genCache = new Map();
async function generate(generatedFilesDir, file, content) {
const cached = genCache.get(file);
if (cached !== content) {
await fs.ensureDir(generatedFilesDir);
await fs.writeFile(path.join(generatedFilesDir, file), content);
genCache.set(file, content);
}

View file

@ -73,7 +73,7 @@ module.exports = async function load(siteDir) {
// Process plugins.
const pluginConfigs = siteConfig.plugins || [];
const context = {env, siteDir, siteConfig};
const context = {env, siteDir, generatedFilesDir, siteConfig};
const {plugins, pluginRouteConfigs} = await loadPlugins({
pluginConfigs,
context,

View file

@ -16,8 +16,6 @@ module.exports = function loadConfig(siteDir) {
const themeComponents = [
'Doc',
'BlogPost',
'BlogPage',
'Pages',
'Loading',
'NotFound',
@ -26,7 +24,9 @@ module.exports = function loadConfig(siteDir) {
];
themeComponents.forEach(component => {
if (!require.resolve(path.join(themePath, component))) {
try {
require.resolve(path.join(themePath, component));
} catch (e) {
throw new Error(
`Failed to load ${themePath}/${component}. It does not exist.`,
);

View file

@ -10736,7 +10736,7 @@ react-youtube@^7.9.0:
prop-types "^15.5.3"
youtube-player "^5.5.1"
react@^16.5.0, react@^16.8.4:
react@^16.5.0, react@^16.8.4, react@^16.8.5:
version "16.8.5"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.5.tgz#49be3b655489d74504ad994016407e8a0445de66"
integrity sha512-daCb9TD6FZGvJ3sg8da1tRAtIuw29PbKZW++NN4wqkbEvxL+bZpaaYb4xuftW/SpXmgacf1skXl/ddX6CdOlDw==