mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
feat(v2): create docusaurus-content-blog plugin draft
This commit is contained in:
parent
211e04f409
commit
e5b7daef33
8 changed files with 95 additions and 41 deletions
|
@ -29,4 +29,12 @@ module.exports = {
|
||||||
indexName: 'docusaurus',
|
indexName: 'docusaurus',
|
||||||
algoliaOptions: {},
|
algoliaOptions: {},
|
||||||
},
|
},
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'docusaurus-content-blog',
|
||||||
|
options: {
|
||||||
|
include: ['*.md', '*.mdx'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,8 +18,8 @@ const REQUIRED_FIELDS = [
|
||||||
'headerIcon',
|
'headerIcon',
|
||||||
'organizationName',
|
'organizationName',
|
||||||
'projectName',
|
'projectName',
|
||||||
'title',
|
|
||||||
'tagline',
|
'tagline',
|
||||||
|
'title',
|
||||||
'url',
|
'url',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ const OPTIONAL_FIELDS = [
|
||||||
'githubHost',
|
'githubHost',
|
||||||
'highlight',
|
'highlight',
|
||||||
'markdownPlugins',
|
'markdownPlugins',
|
||||||
|
'plugins',
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
|
@ -94,12 +95,12 @@ function loadConfig(siteDir, deleteCache = true) {
|
||||||
}
|
}
|
||||||
config.headerLinks = headerLinks;
|
config.headerLinks = headerLinks;
|
||||||
|
|
||||||
/*
|
// User's own array of custom fields/
|
||||||
User's own array of custom fields,
|
// e.g: if they want to include some.field so they can access it later from `props.siteConfig`.
|
||||||
e.g: if they want to include some field so they can access it later from `props.siteConfig`
|
|
||||||
*/
|
|
||||||
const {customFields = []} = config;
|
const {customFields = []} = config;
|
||||||
|
|
||||||
|
// TODO: Check that plugins mentioned exist.
|
||||||
|
|
||||||
// Don't allow unrecognized fields.
|
// Don't allow unrecognized fields.
|
||||||
const allowedFields = [
|
const allowedFields = [
|
||||||
...REQUIRED_FIELDS,
|
...REQUIRED_FIELDS,
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const loadBlog = require('./blog');
|
|
||||||
const loadConfig = require('./config');
|
const loadConfig = require('./config');
|
||||||
const loadDocs = require('./docs');
|
const loadDocs = require('./docs');
|
||||||
const loadEnv = require('./env');
|
const loadEnv = require('./env');
|
||||||
|
@ -80,14 +79,27 @@ module.exports = async function load(siteDir) {
|
||||||
`export default ${JSON.stringify(pagesMetadatas, null, 2)};`,
|
`export default ${JSON.stringify(pagesMetadatas, null, 2)};`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Blog.
|
const contentsStore = {};
|
||||||
const blogDir = path.resolve(siteDir, 'blog');
|
|
||||||
const blogMetadatas = await loadBlog({blogDir, env, siteConfig});
|
// Process plugins.
|
||||||
await generate(
|
if (siteConfig.plugins) {
|
||||||
generatedFilesDir,
|
const context = {env, siteDir, siteConfig};
|
||||||
'blogMetadatas.js',
|
await Promise.all(
|
||||||
`export default ${JSON.stringify(blogMetadatas, null, 2)};`,
|
siteConfig.plugins.map(async ({name, options: opts}) => {
|
||||||
);
|
// TODO: Resolve using node_modules as well.
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const plugin = require(path.resolve(__dirname, '../../plugins', name));
|
||||||
|
const pluginContent = await plugin.onLoadContent(opts, context);
|
||||||
|
const {options, contents} = pluginContent;
|
||||||
|
contentsStore[options.contentKey] = pluginContent;
|
||||||
|
await generate(
|
||||||
|
generatedFilesDir,
|
||||||
|
options.cachePath,
|
||||||
|
`export default ${JSON.stringify(contents, null, 2)};`,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve outDir.
|
// Resolve outDir.
|
||||||
const outDir = path.resolve(siteDir, 'build');
|
const outDir = path.resolve(siteDir, 'build');
|
||||||
|
@ -102,8 +114,6 @@ module.exports = async function load(siteDir) {
|
||||||
const props = {
|
const props = {
|
||||||
siteConfig,
|
siteConfig,
|
||||||
siteDir,
|
siteDir,
|
||||||
blogDir,
|
|
||||||
blogMetadatas,
|
|
||||||
docsDir,
|
docsDir,
|
||||||
docsMetadatas,
|
docsMetadatas,
|
||||||
docsSidebars,
|
docsSidebars,
|
||||||
|
@ -117,6 +127,7 @@ module.exports = async function load(siteDir) {
|
||||||
versionedDir,
|
versionedDir,
|
||||||
translatedDir,
|
translatedDir,
|
||||||
generatedFilesDir,
|
generatedFilesDir,
|
||||||
|
contentsStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate React Router Config.
|
// Generate React Router Config.
|
||||||
|
|
|
@ -11,7 +11,7 @@ async function genRoutesConfig({
|
||||||
siteConfig = {},
|
siteConfig = {},
|
||||||
docsMetadatas = {},
|
docsMetadatas = {},
|
||||||
pagesMetadatas = [],
|
pagesMetadatas = [],
|
||||||
blogMetadatas = [],
|
contentsStore = {},
|
||||||
}) {
|
}) {
|
||||||
const {docsUrl, baseUrl} = siteConfig;
|
const {docsUrl, baseUrl} = siteConfig;
|
||||||
function genDocsRoute(metadata) {
|
function genDocsRoute(metadata) {
|
||||||
|
@ -138,7 +138,11 @@ async function genRoutesConfig({
|
||||||
`const routes = [
|
`const routes = [
|
||||||
${pagesMetadatas.map(genPagesRoute).join(',')},
|
${pagesMetadatas.map(genPagesRoute).join(',')},
|
||||||
${docsRoutes},
|
${docsRoutes},
|
||||||
${blogMetadatas.map(genBlogRoute).join(',')},
|
${
|
||||||
|
contentsStore.blog
|
||||||
|
? contentsStore.blog.contents.map(genBlogRoute).join(',')
|
||||||
|
: ''
|
||||||
|
},
|
||||||
${notFoundRoute}\n];\n` +
|
${notFoundRoute}\n];\n` +
|
||||||
`export default routes;\n`
|
`export default routes;\n`
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import DocusaurusContext from '@docusaurus/context';
|
||||||
|
|
||||||
function BlogPage(props) {
|
function BlogPage(props) {
|
||||||
const context = useContext(DocusaurusContext);
|
const context = useContext(DocusaurusContext);
|
||||||
|
console.log(context);
|
||||||
const {blogMetadatas, language, siteConfig = {}} = context;
|
const {blogMetadatas, language, siteConfig = {}} = context;
|
||||||
const {baseUrl, favicon} = siteConfig;
|
const {baseUrl, favicon} = siteConfig;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const _ = require('lodash');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const staticSiteGenerator = require('static-site-generator-webpack-plugin');
|
const staticSiteGenerator = require('static-site-generator-webpack-plugin');
|
||||||
const webpackNiceLog = require('webpack-nicelog');
|
const webpackNiceLog = require('webpack-nicelog');
|
||||||
|
@ -21,13 +22,18 @@ module.exports = function createServerConfig(props) {
|
||||||
// Workaround for Webpack 4 Bug (https://github.com/webpack/webpack/issues/6522)
|
// Workaround for Webpack 4 Bug (https://github.com/webpack/webpack/issues/6522)
|
||||||
config.output.globalObject('this');
|
config.output.globalObject('this');
|
||||||
|
|
||||||
const {siteConfig, blogMetadatas, docsMetadatas, pagesMetadatas} = props;
|
const {siteConfig, docsMetadatas, pagesMetadatas, contentsStore} = props;
|
||||||
|
|
||||||
// Static site generator webpack plugin.
|
// Static site generator webpack plugin.
|
||||||
const docsFlatMetadatas = Object.values(docsMetadatas);
|
const docsFlatMetadatas = Object.values(docsMetadatas);
|
||||||
const paths = [...blogMetadatas, ...docsFlatMetadatas, ...pagesMetadatas].map(
|
|
||||||
data => data.permalink,
|
// TODO: Generalize this into blog plugin.
|
||||||
);
|
const blogPermalinks = _.get(contentsStore, ['blog', 'contents'], []);
|
||||||
|
const paths = [
|
||||||
|
...blogPermalinks,
|
||||||
|
...docsFlatMetadatas,
|
||||||
|
...pagesMetadatas,
|
||||||
|
].map(data => data.permalink);
|
||||||
config.plugin('siteGenerator').use(staticSiteGenerator, [
|
config.plugin('siteGenerator').use(staticSiteGenerator, [
|
||||||
{
|
{
|
||||||
entry: 'main',
|
entry: 'main',
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
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('./utils');
|
// TODO: Do not make it relative because plugins can be from node_modules.
|
||||||
|
const {parse, idx, normalizeUrl} = require('../lib/load/utils');
|
||||||
|
|
||||||
function fileToUrl(fileName) {
|
function fileToUrl(fileName) {
|
||||||
return fileName
|
return fileName
|
||||||
|
@ -18,15 +19,29 @@ function fileToUrl(fileName) {
|
||||||
.replace(/\.md$/, '');
|
.replace(/\.md$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadBlog({blogDir, env, siteConfig}) {
|
const DEFAULT_OPTIONS = {
|
||||||
const blogFiles = await globby(['*.md'], {
|
contentKey: 'blog',
|
||||||
|
path: 'blog', // Path to data on filesystem.
|
||||||
|
routeBasePath: 'blog', // URL Route.
|
||||||
|
include: ['*.md'], // Extensions to include.
|
||||||
|
pageCount: 10, // How many entries per page.
|
||||||
|
cachePath: 'blogMetadata.js',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function onLoadContent(opts, context) {
|
||||||
|
const options = {...DEFAULT_OPTIONS, ...opts};
|
||||||
|
|
||||||
|
const {env, siteConfig, siteDir} = context;
|
||||||
|
const {pageCount, path: filePath, routeBasePath} = options;
|
||||||
|
const blogDir = path.resolve(siteDir, filePath);
|
||||||
|
const {baseUrl} = siteConfig;
|
||||||
|
|
||||||
|
const blogFiles = await globby(options.include, {
|
||||||
cwd: blogDir,
|
cwd: blogDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {baseUrl} = siteConfig;
|
|
||||||
|
|
||||||
// Prepare metadata container.
|
// Prepare metadata container.
|
||||||
const blogMetadatas = [];
|
const blogMetadata = [];
|
||||||
|
|
||||||
// Language for each blog page.
|
// Language for each blog page.
|
||||||
const defaultLangTag = idx(env, ['translation', 'defaultLanguage', 'tag']);
|
const defaultLangTag = idx(env, ['translation', 'defaultLanguage', 'tag']);
|
||||||
|
@ -36,7 +51,7 @@ async function loadBlog({blogDir, env, siteConfig}) {
|
||||||
const source = path.join(blogDir, relativeSource);
|
const source = path.join(blogDir, relativeSource);
|
||||||
|
|
||||||
const blogFileName = path.basename(relativeSource);
|
const blogFileName = path.basename(relativeSource);
|
||||||
// Extract, YYYY, MM, DD from the file name
|
// Extract, YYYY, MM, DD from the file name.
|
||||||
const filePathDateArr = blogFileName.split('-');
|
const filePathDateArr = blogFileName.split('-');
|
||||||
const date = new Date(
|
const date = new Date(
|
||||||
`${filePathDateArr[0]}-${filePathDateArr[1]}-${
|
`${filePathDateArr[0]}-${filePathDateArr[1]}-${
|
||||||
|
@ -47,37 +62,45 @@ async function loadBlog({blogDir, env, siteConfig}) {
|
||||||
const fileString = await fs.readFile(source, 'utf-8');
|
const fileString = await fs.readFile(source, 'utf-8');
|
||||||
const {metadata: rawMetadata} = parse(fileString);
|
const {metadata: rawMetadata} = parse(fileString);
|
||||||
const metadata = {
|
const metadata = {
|
||||||
permalink: normalizeUrl([baseUrl, `blog`, fileToUrl(blogFileName)]),
|
permalink: normalizeUrl([
|
||||||
|
baseUrl,
|
||||||
|
routeBasePath,
|
||||||
|
fileToUrl(blogFileName),
|
||||||
|
]),
|
||||||
source,
|
source,
|
||||||
...rawMetadata,
|
...rawMetadata,
|
||||||
date,
|
date,
|
||||||
language: defaultLangTag,
|
language: defaultLangTag,
|
||||||
};
|
};
|
||||||
blogMetadatas.push(metadata);
|
blogMetadata.push(metadata);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
blogMetadatas.sort((a, b) => a.date - b.date);
|
blogMetadata.sort((a, b) => a.date - b.date);
|
||||||
|
|
||||||
// Blog page handling. Example: `/blog`, `/blog/page1`, `/blog/page2`
|
// Blog page handling. Example: `/blog`, `/blog/page1`, `/blog/page2`
|
||||||
const perPage = 10;
|
const numOfBlog = blogMetadata.length;
|
||||||
const numOfBlog = blogMetadatas.length;
|
const numberOfPage = Math.ceil(numOfBlog / pageCount);
|
||||||
const numberOfPage = Math.ceil(numOfBlog / perPage);
|
const basePageUrl = path.join(baseUrl, routeBasePath);
|
||||||
const basePageUrl = path.join(baseUrl, 'blog');
|
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
for (let page = 0; page < numberOfPage; page++) {
|
for (let page = 0; page < numberOfPage; page++) {
|
||||||
blogMetadatas.push({
|
blogMetadata.push({
|
||||||
permalink: normalizeUrl([
|
permalink: normalizeUrl([
|
||||||
basePageUrl,
|
basePageUrl,
|
||||||
`${page > 0 ? `page${page + 1}` : ''}`,
|
`${page > 0 ? `page${page + 1}` : ''}`,
|
||||||
]),
|
]),
|
||||||
language: defaultLangTag,
|
language: defaultLangTag,
|
||||||
isBlogPage: true,
|
isBlogPage: true,
|
||||||
posts: blogMetadatas.slice(page * perPage, (page + 1) * perPage),
|
posts: blogMetadata.slice(page * pageCount, (page + 1) * pageCount),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return blogMetadatas;
|
return {
|
||||||
|
contents: blogMetadata,
|
||||||
|
options,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = loadBlog;
|
module.exports = {
|
||||||
|
onLoadContent,
|
||||||
|
};
|
|
@ -66,7 +66,7 @@ Object {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
loadConfig(siteDir);
|
loadConfig(siteDir);
|
||||||
}).toThrowErrorMatchingInlineSnapshot(
|
}).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"The required field(s) 'baseUrl', 'favicon', 'headerLinks', 'headerIcon', 'organizationName', 'projectName', 'title', 'tagline', 'url' are missing from docusaurus.config.js"`,
|
`"The required field(s) 'baseUrl', 'favicon', 'headerLinks', 'headerIcon', 'organizationName', 'projectName', 'tagline', 'title', 'url' are missing from docusaurus.config.js"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue