mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-25 15:07:17 +02:00
feat(v2): enhance @docusaurus/plugin-content-blog (#1311)
* feat(v2): @docusaurus/plugin-content-blog * address code review
This commit is contained in:
parent
bd0cdbc701
commit
4bd5d3937e
11 changed files with 156 additions and 55 deletions
|
@ -6,11 +6,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useContext} from 'react';
|
import React, {useContext} from 'react';
|
||||||
import Link from '@docusaurus/Link';
|
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
import Layout from '@theme/Layout'; // eslint-disable-line
|
import Layout from '@theme/Layout'; // eslint-disable-line
|
||||||
|
|
||||||
import DocusaurusContext from '@docusaurus/context';
|
import DocusaurusContext from '@docusaurus/context';
|
||||||
|
import Post from '../Post';
|
||||||
|
|
||||||
function BlogPage(props) {
|
function BlogPage(props) {
|
||||||
const context = useContext(DocusaurusContext);
|
const context = useContext(DocusaurusContext);
|
||||||
|
@ -30,15 +29,13 @@ function BlogPage(props) {
|
||||||
{language && <meta name="docsearch:language" content={language} />}
|
{language && <meta name="docsearch:language" content={language} />}
|
||||||
</Head>
|
</Head>
|
||||||
<div>
|
<div>
|
||||||
<ul>
|
{BlogPosts.map((PostContent, index) => (
|
||||||
{posts.map(metadata => (
|
<Post
|
||||||
<li key={metadata.permalink}>
|
key={index}
|
||||||
<Link to={metadata.permalink}>{metadata.permalink}</Link>
|
truncated={posts[index].truncatedSource}
|
||||||
</li>
|
metadata={posts[index]}>
|
||||||
))}
|
<PostContent />
|
||||||
</ul>
|
</Post>
|
||||||
{BlogPosts.map((BlogPost, index) => (
|
|
||||||
<BlogPost key={index} />
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
|
@ -7,21 +7,16 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import Head from '@docusaurus/Head';
|
import classnames from 'classnames'; // eslint-disable-line
|
||||||
import classnames from 'classnames';
|
|
||||||
import Layout from '@theme/Layout'; // eslint-disable-line
|
|
||||||
|
|
||||||
import DocusaurusContext from '@docusaurus/context';
|
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
class BlogPost extends React.Component {
|
function Post(props) {
|
||||||
renderPostHeader() {
|
const {metadata, children, truncated} = props;
|
||||||
const {metadata} = this.props;
|
const renderPostHeader = () => {
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
date,
|
date,
|
||||||
author,
|
author,
|
||||||
|
@ -82,29 +77,21 @@ class BlogPost extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const {metadata = {}, siteConfig = {}} = this.context;
|
|
||||||
const {baseUrl, favicon} = siteConfig;
|
|
||||||
const {language, title} = metadata;
|
|
||||||
const {modules} = this.props;
|
|
||||||
const BlogPostContents = modules[0];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<div className={styles.postContainer}>
|
||||||
<Head defaultTitle={siteConfig.title}>
|
{renderPostHeader()}
|
||||||
{title && <title>{title}</title>}
|
{children}
|
||||||
{favicon && <link rel="shortcut icon" href={baseUrl + favicon} />}
|
{truncated && (
|
||||||
{language && <html lang={language} />}
|
<div className={styles.readMoreContainer}>
|
||||||
</Head>
|
<Link className={styles.readMoreLink} to={metadata.permalink}>
|
||||||
{this.renderPostHeader()}
|
Read More
|
||||||
<BlogPostContents />
|
</Link>
|
||||||
</Layout>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
BlogPost.contextType = DocusaurusContext;
|
export default Post;
|
||||||
|
|
||||||
export default BlogPost;
|
|
|
@ -45,3 +45,38 @@
|
||||||
height: 50px;
|
height: 50px;
|
||||||
width: 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;
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
const globby = require('globby');
|
const globby = require('globby');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const {parse, idx, normalizeUrl} = require('@docusaurus/utils');
|
const {parse, idx, normalizeUrl, generate} = require('@docusaurus/utils');
|
||||||
|
|
||||||
function fileToUrl(fileName) {
|
function fileToUrl(fileName) {
|
||||||
return fileName
|
return fileName
|
||||||
|
@ -25,10 +25,12 @@ const DEFAULT_OPTIONS = {
|
||||||
routeBasePath: 'blog', // URL Route.
|
routeBasePath: 'blog', // URL Route.
|
||||||
include: ['*.md, *.mdx'], // Extensions to include.
|
include: ['*.md, *.mdx'], // Extensions to include.
|
||||||
pageCount: 10, // How many entries per page.
|
pageCount: 10, // How many entries per page.
|
||||||
blogPageComponent: '@theme/BlogPage',
|
blogPageComponent: path.resolve(__dirname, './components/BlogPage'),
|
||||||
blogPostComponent: '@theme/BlogPost',
|
blogPostComponent: path.resolve(__dirname, './components/BlogPost'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TRUNCATE_MARKER = /<!--\s*truncate\s*-->/;
|
||||||
|
|
||||||
class DocusaurusPluginContentBlog {
|
class DocusaurusPluginContentBlog {
|
||||||
constructor(opts, context) {
|
constructor(opts, context) {
|
||||||
this.options = {...DEFAULT_OPTIONS, ...opts};
|
this.options = {...DEFAULT_OPTIONS, ...opts};
|
||||||
|
@ -43,7 +45,7 @@ class DocusaurusPluginContentBlog {
|
||||||
// Fetches blog contents and returns metadata for the contents.
|
// Fetches blog contents and returns metadata for the contents.
|
||||||
async loadContent() {
|
async loadContent() {
|
||||||
const {pageCount, include, routeBasePath} = this.options;
|
const {pageCount, include, routeBasePath} = this.options;
|
||||||
const {env, siteConfig} = this.context;
|
const {env, generatedFilesDir, siteConfig} = this.context;
|
||||||
const blogDir = this.contentPath;
|
const blogDir = this.contentPath;
|
||||||
|
|
||||||
const {baseUrl} = siteConfig;
|
const {baseUrl} = siteConfig;
|
||||||
|
@ -71,7 +73,20 @@ class DocusaurusPluginContentBlog {
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileString = await fs.readFile(source, 'utf-8');
|
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 = {
|
const metadata = {
|
||||||
permalink: normalizeUrl([
|
permalink: normalizeUrl([
|
||||||
baseUrl,
|
baseUrl,
|
||||||
|
@ -79,6 +94,7 @@ class DocusaurusPluginContentBlog {
|
||||||
fileToUrl(blogFileName),
|
fileToUrl(blogFileName),
|
||||||
]),
|
]),
|
||||||
source,
|
source,
|
||||||
|
truncatedSource,
|
||||||
...rawMetadata,
|
...rawMetadata,
|
||||||
date,
|
date,
|
||||||
language: defaultLangTag,
|
language: defaultLangTag,
|
||||||
|
@ -119,7 +135,9 @@ class DocusaurusPluginContentBlog {
|
||||||
path: permalink,
|
path: permalink,
|
||||||
component: blogPageComponent,
|
component: blogPageComponent,
|
||||||
metadata: metadataItem,
|
metadata: metadataItem,
|
||||||
modules: metadataItem.posts.map(post => post.source),
|
modules: metadataItem.posts.map(
|
||||||
|
post => post.truncatedSource || post.source,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,15 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Blog content plugin for Docusaurus",
|
"description": "Blog content plugin for Docusaurus",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
"files": [
|
||||||
|
"components"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/utils": "^1.0.0",
|
"@docusaurus/utils": "^1.0.0",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
"globby": "^9.1.0"
|
"globby": "^9.1.0",
|
||||||
|
"react": "^16.8.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ const genCache = new Map();
|
||||||
async function generate(generatedFilesDir, file, content) {
|
async function generate(generatedFilesDir, file, content) {
|
||||||
const cached = genCache.get(file);
|
const cached = genCache.get(file);
|
||||||
if (cached !== content) {
|
if (cached !== content) {
|
||||||
|
await fs.ensureDir(generatedFilesDir);
|
||||||
await fs.writeFile(path.join(generatedFilesDir, file), content);
|
await fs.writeFile(path.join(generatedFilesDir, file), content);
|
||||||
genCache.set(file, content);
|
genCache.set(file, content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ module.exports = async function load(siteDir) {
|
||||||
|
|
||||||
// Process plugins.
|
// Process plugins.
|
||||||
const pluginConfigs = siteConfig.plugins || [];
|
const pluginConfigs = siteConfig.plugins || [];
|
||||||
const context = {env, siteDir, siteConfig};
|
const context = {env, siteDir, generatedFilesDir, siteConfig};
|
||||||
const {plugins, pluginRouteConfigs} = await loadPlugins({
|
const {plugins, pluginRouteConfigs} = await loadPlugins({
|
||||||
pluginConfigs,
|
pluginConfigs,
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -16,8 +16,6 @@ module.exports = function loadConfig(siteDir) {
|
||||||
|
|
||||||
const themeComponents = [
|
const themeComponents = [
|
||||||
'Doc',
|
'Doc',
|
||||||
'BlogPost',
|
|
||||||
'BlogPage',
|
|
||||||
'Pages',
|
'Pages',
|
||||||
'Loading',
|
'Loading',
|
||||||
'NotFound',
|
'NotFound',
|
||||||
|
@ -26,7 +24,9 @@ module.exports = function loadConfig(siteDir) {
|
||||||
];
|
];
|
||||||
|
|
||||||
themeComponents.forEach(component => {
|
themeComponents.forEach(component => {
|
||||||
if (!require.resolve(path.join(themePath, component))) {
|
try {
|
||||||
|
require.resolve(path.join(themePath, component));
|
||||||
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to load ${themePath}/${component}. It does not exist.`,
|
`Failed to load ${themePath}/${component}. It does not exist.`,
|
||||||
);
|
);
|
||||||
|
|
|
@ -10736,7 +10736,7 @@ react-youtube@^7.9.0:
|
||||||
prop-types "^15.5.3"
|
prop-types "^15.5.3"
|
||||||
youtube-player "^5.5.1"
|
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"
|
version "16.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.8.5.tgz#49be3b655489d74504ad994016407e8a0445de66"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.8.5.tgz#49be3b655489d74504ad994016407e8a0445de66"
|
||||||
integrity sha512-daCb9TD6FZGvJ3sg8da1tRAtIuw29PbKZW++NN4wqkbEvxL+bZpaaYb4xuftW/SpXmgacf1skXl/ddX6CdOlDw==
|
integrity sha512-daCb9TD6FZGvJ3sg8da1tRAtIuw29PbKZW++NN4wqkbEvxL+bZpaaYb4xuftW/SpXmgacf1skXl/ddX6CdOlDw==
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue