mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-09 12:58:01 +02:00
feat(docs,blog,pages): add support for "unlisted" front matter - hide md content in production (#8004)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
7a023a2c41
commit
683ba3d2a0
131 changed files with 2449 additions and 303 deletions
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
slug: /another/blog-with-tags-unlisted
|
||||
title: Another Blog With Tag - unlisted
|
||||
date: 2020-08-19
|
||||
tags: [unlisted]
|
||||
unlisted: true
|
||||
---
|
||||
|
||||
with tag
|
6
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/unlisted.md
generated
Normal file
6
packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/unlisted.md
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
date: 2020-02-27
|
||||
unlisted: true
|
||||
---
|
||||
|
||||
this post is unlisted
|
File diff suppressed because one or more lines are too long
|
@ -30,6 +30,7 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
|
|||
},
|
||||
],
|
||||
"permalink": "/blog/tags/tag-1",
|
||||
"unlisted": false,
|
||||
},
|
||||
"/blog/tags/tag-2": {
|
||||
"items": [
|
||||
|
@ -57,6 +58,33 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
|
|||
},
|
||||
],
|
||||
"permalink": "/blog/tags/tag-2",
|
||||
"unlisted": false,
|
||||
},
|
||||
"/blog/tags/unlisted": {
|
||||
"items": [
|
||||
"/another/blog-with-tags-unlisted",
|
||||
],
|
||||
"label": "unlisted",
|
||||
"pages": [
|
||||
{
|
||||
"items": [
|
||||
"/another/blog-with-tags-unlisted",
|
||||
],
|
||||
"metadata": {
|
||||
"blogDescription": "Blog",
|
||||
"blogTitle": "Blog",
|
||||
"nextPage": undefined,
|
||||
"page": 1,
|
||||
"permalink": "/blog/tags/unlisted",
|
||||
"postsPerPage": 1,
|
||||
"previousPage": undefined,
|
||||
"totalCount": 1,
|
||||
"totalPages": 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
"permalink": "/blog/tags/unlisted",
|
||||
"unlisted": false,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -106,6 +134,7 @@ exports[`blog plugin works with blog tags 1`] = `
|
|||
},
|
||||
],
|
||||
"permalink": "/blog/tags/tag-1",
|
||||
"unlisted": false,
|
||||
},
|
||||
"/blog/tags/tag-2": {
|
||||
"items": [
|
||||
|
@ -133,6 +162,33 @@ exports[`blog plugin works with blog tags 1`] = `
|
|||
},
|
||||
],
|
||||
"permalink": "/blog/tags/tag-2",
|
||||
"unlisted": false,
|
||||
},
|
||||
"/blog/tags/unlisted": {
|
||||
"items": [
|
||||
"/another/blog-with-tags-unlisted",
|
||||
],
|
||||
"label": "unlisted",
|
||||
"pages": [
|
||||
{
|
||||
"items": [
|
||||
"/another/blog-with-tags-unlisted",
|
||||
],
|
||||
"metadata": {
|
||||
"blogDescription": "Blog",
|
||||
"blogTitle": "Blog",
|
||||
"nextPage": undefined,
|
||||
"page": 1,
|
||||
"permalink": "/blog/tags/unlisted",
|
||||
"postsPerPage": 2,
|
||||
"previousPage": undefined,
|
||||
"totalCount": 1,
|
||||
"totalPages": 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
"permalink": "/blog/tags/unlisted",
|
||||
"unlisted": false,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -54,7 +54,7 @@ async function testGenerateFeeds(
|
|||
);
|
||||
|
||||
await createBlogFeedFiles({
|
||||
blogPosts: blogPosts.filter((post) => !post.metadata.frontMatter.draft),
|
||||
blogPosts,
|
||||
options,
|
||||
siteConfig: context.siteConfig,
|
||||
outDir: context.outDir,
|
||||
|
|
|
@ -12,7 +12,7 @@ import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
|||
// TODO this abstraction reduce verbosity but it makes it harder to debug
|
||||
// It would be preferable to just expose helper methods
|
||||
function testField(params: {
|
||||
fieldName: keyof BlogPostFrontMatter;
|
||||
prefix: string;
|
||||
validFrontMatters: BlogPostFrontMatter[];
|
||||
convertibleFrontMatter?: [
|
||||
ConvertibleFrontMatter: {[key: string]: unknown},
|
||||
|
@ -23,7 +23,7 @@ function testField(params: {
|
|||
ErrorMessage: string,
|
||||
][];
|
||||
}) {
|
||||
describe(`"${params.fieldName}" field`, () => {
|
||||
describe(`"${params.prefix}" field`, () => {
|
||||
it('accept valid values', () => {
|
||||
params.validFrontMatters.forEach((frontMatter) => {
|
||||
expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter);
|
||||
|
@ -44,15 +44,12 @@ function testField(params: {
|
|||
params.invalidFrontMatters?.forEach(([frontMatter, message]) => {
|
||||
try {
|
||||
validateBlogPostFrontMatter(frontMatter);
|
||||
// eslint-disable-next-line jest/no-jasmine-globals
|
||||
fail(
|
||||
new Error(
|
||||
`Blog front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify(
|
||||
frontMatter,
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
),
|
||||
throw new Error(
|
||||
`Blog front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify(
|
||||
frontMatter,
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
|
@ -79,7 +76,7 @@ describe('validateBlogPostFrontMatter', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter description', () => {
|
||||
testField({
|
||||
fieldName: 'description',
|
||||
prefix: 'description',
|
||||
validFrontMatters: [
|
||||
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
||||
{description: ''},
|
||||
|
@ -90,7 +87,7 @@ describe('validateBlogPostFrontMatter description', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter title', () => {
|
||||
testField({
|
||||
fieldName: 'title',
|
||||
prefix: 'title',
|
||||
validFrontMatters: [
|
||||
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
||||
{title: ''},
|
||||
|
@ -101,7 +98,7 @@ describe('validateBlogPostFrontMatter title', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter id', () => {
|
||||
testField({
|
||||
fieldName: 'id',
|
||||
prefix: 'id',
|
||||
validFrontMatters: [{id: '123'}, {id: 'id'}],
|
||||
invalidFrontMatters: [[{id: ''}, 'not allowed to be empty']],
|
||||
});
|
||||
|
@ -132,7 +129,7 @@ describe('validateBlogPostFrontMatter handles legacy/new author front matter', (
|
|||
|
||||
describe('validateBlogPostFrontMatter author', () => {
|
||||
testField({
|
||||
fieldName: 'author',
|
||||
prefix: 'author',
|
||||
validFrontMatters: [{author: '123'}, {author: 'author'}],
|
||||
invalidFrontMatters: [[{author: ''}, 'not allowed to be empty']],
|
||||
});
|
||||
|
@ -140,7 +137,7 @@ describe('validateBlogPostFrontMatter author', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter author_title', () => {
|
||||
testField({
|
||||
fieldName: 'author_title',
|
||||
prefix: 'author_title',
|
||||
validFrontMatters: [
|
||||
{author: '123', author_title: '123'},
|
||||
{author: '123', author_title: 'author_title'},
|
||||
|
@ -149,7 +146,7 @@ describe('validateBlogPostFrontMatter author_title', () => {
|
|||
});
|
||||
|
||||
testField({
|
||||
fieldName: 'authorTitle',
|
||||
prefix: 'authorTitle',
|
||||
validFrontMatters: [{authorTitle: '123'}, {authorTitle: 'authorTitle'}],
|
||||
invalidFrontMatters: [[{authorTitle: ''}, 'not allowed to be empty']],
|
||||
});
|
||||
|
@ -157,7 +154,7 @@ describe('validateBlogPostFrontMatter author_title', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter author_url', () => {
|
||||
testField({
|
||||
fieldName: 'author_url',
|
||||
prefix: 'author_url',
|
||||
validFrontMatters: [
|
||||
{author_url: 'https://docusaurus.io'},
|
||||
{author_url: '../../relative'},
|
||||
|
@ -172,7 +169,7 @@ describe('validateBlogPostFrontMatter author_url', () => {
|
|||
});
|
||||
|
||||
testField({
|
||||
fieldName: 'authorURL',
|
||||
prefix: 'authorURL',
|
||||
validFrontMatters: [
|
||||
{authorURL: 'https://docusaurus.io'},
|
||||
{authorURL: '../../relative'},
|
||||
|
@ -190,7 +187,7 @@ describe('validateBlogPostFrontMatter author_url', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter author_image_url', () => {
|
||||
testField({
|
||||
fieldName: 'author_image_url',
|
||||
prefix: 'author_image_url',
|
||||
validFrontMatters: [
|
||||
{author_image_url: 'https://docusaurus.io/asset/image.png'},
|
||||
{author_image_url: '../../relative'},
|
||||
|
@ -205,7 +202,7 @@ describe('validateBlogPostFrontMatter author_image_url', () => {
|
|||
});
|
||||
|
||||
testField({
|
||||
fieldName: 'authorImageURL',
|
||||
prefix: 'authorImageURL',
|
||||
validFrontMatters: [
|
||||
{authorImageURL: 'https://docusaurus.io/asset/image.png'},
|
||||
{authorImageURL: '../../relative'},
|
||||
|
@ -222,7 +219,7 @@ describe('validateBlogPostFrontMatter author_image_url', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter authors', () => {
|
||||
testField({
|
||||
fieldName: 'author',
|
||||
prefix: 'author',
|
||||
validFrontMatters: [
|
||||
{authors: []},
|
||||
{authors: 'authorKey'},
|
||||
|
@ -270,7 +267,7 @@ describe('validateBlogPostFrontMatter authors', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter slug', () => {
|
||||
testField({
|
||||
fieldName: 'slug',
|
||||
prefix: 'slug',
|
||||
validFrontMatters: [
|
||||
{slug: 'blog/'},
|
||||
{slug: '/blog'},
|
||||
|
@ -287,7 +284,7 @@ describe('validateBlogPostFrontMatter slug', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter image', () => {
|
||||
testField({
|
||||
fieldName: 'image',
|
||||
prefix: 'image',
|
||||
validFrontMatters: [
|
||||
{image: 'https://docusaurus.io/image.png'},
|
||||
{image: 'blog/'},
|
||||
|
@ -307,7 +304,7 @@ describe('validateBlogPostFrontMatter image', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter tags', () => {
|
||||
testField({
|
||||
fieldName: 'tags',
|
||||
prefix: 'tags',
|
||||
validFrontMatters: [
|
||||
{tags: []},
|
||||
{tags: ['hello']},
|
||||
|
@ -335,7 +332,7 @@ describe('validateBlogPostFrontMatter tags', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter keywords', () => {
|
||||
testField({
|
||||
fieldName: 'keywords',
|
||||
prefix: 'keywords',
|
||||
validFrontMatters: [
|
||||
{keywords: ['hello']},
|
||||
{keywords: ['hello', 'world']},
|
||||
|
@ -352,7 +349,7 @@ describe('validateBlogPostFrontMatter keywords', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter draft', () => {
|
||||
testField({
|
||||
fieldName: 'draft',
|
||||
prefix: 'draft',
|
||||
validFrontMatters: [{draft: true}, {draft: false}],
|
||||
convertibleFrontMatter: [
|
||||
[{draft: 'true'}, {draft: true}],
|
||||
|
@ -365,9 +362,43 @@ describe('validateBlogPostFrontMatter draft', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('validateBlogPostFrontMatter unlisted', () => {
|
||||
testField({
|
||||
prefix: 'unlisted',
|
||||
validFrontMatters: [{unlisted: true}, {unlisted: false}],
|
||||
convertibleFrontMatter: [
|
||||
[{unlisted: 'true'}, {unlisted: true}],
|
||||
[{unlisted: 'false'}, {unlisted: false}],
|
||||
],
|
||||
invalidFrontMatters: [
|
||||
[{unlisted: 'yes'}, 'must be a boolean'],
|
||||
[{unlisted: 'no'}, 'must be a boolean'],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateDocFrontMatter draft XOR unlisted', () => {
|
||||
testField({
|
||||
prefix: 'draft XOR unlisted',
|
||||
validFrontMatters: [
|
||||
{draft: false},
|
||||
{unlisted: false},
|
||||
{draft: false, unlisted: false},
|
||||
{draft: true, unlisted: false},
|
||||
{draft: false, unlisted: true},
|
||||
],
|
||||
invalidFrontMatters: [
|
||||
[
|
||||
{draft: true, unlisted: true},
|
||||
"Can't be draft and unlisted at the same time.",
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateBlogPostFrontMatter hide_table_of_contents', () => {
|
||||
testField({
|
||||
fieldName: 'hide_table_of_contents',
|
||||
prefix: 'hide_table_of_contents',
|
||||
validFrontMatters: [
|
||||
{hide_table_of_contents: true},
|
||||
{hide_table_of_contents: false},
|
||||
|
@ -385,7 +416,7 @@ describe('validateBlogPostFrontMatter hide_table_of_contents', () => {
|
|||
|
||||
describe('validateBlogPostFrontMatter date', () => {
|
||||
testField({
|
||||
fieldName: 'date',
|
||||
prefix: 'date',
|
||||
validFrontMatters: [
|
||||
{date: new Date('2020-01-01')},
|
||||
{date: '2020-01-01'},
|
||||
|
|
|
@ -172,6 +172,7 @@ describe('blog plugin', () => {
|
|||
title: 'Happy 1st Birthday Slash! (translated)',
|
||||
},
|
||||
hasTruncateMarker: false,
|
||||
unlisted: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -215,6 +216,7 @@ describe('blog plugin', () => {
|
|||
title: 'date-matter',
|
||||
},
|
||||
hasTruncateMarker: false,
|
||||
unlisted: false,
|
||||
});
|
||||
|
||||
expect({
|
||||
|
@ -252,6 +254,7 @@ describe('blog plugin', () => {
|
|||
},
|
||||
],
|
||||
hasTruncateMarker: false,
|
||||
unlisted: false,
|
||||
});
|
||||
|
||||
expect({
|
||||
|
@ -289,6 +292,7 @@ describe('blog plugin', () => {
|
|||
},
|
||||
tags: [],
|
||||
hasTruncateMarker: false,
|
||||
unlisted: false,
|
||||
});
|
||||
|
||||
expect({
|
||||
|
@ -314,13 +318,14 @@ describe('blog plugin', () => {
|
|||
title: 'date-matter',
|
||||
},
|
||||
hasTruncateMarker: false,
|
||||
unlisted: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('builds simple website blog with localized dates', async () => {
|
||||
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||
const blogPostsFrench = await getBlogPosts(siteDir, {}, getI18n('fr'));
|
||||
expect(blogPostsFrench).toHaveLength(8);
|
||||
expect(blogPostsFrench).toHaveLength(9);
|
||||
expect(blogPostsFrench[0]!.metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"6 mars 2021"`,
|
||||
);
|
||||
|
@ -337,13 +342,13 @@ describe('blog plugin', () => {
|
|||
`"27 février 2020"`,
|
||||
);
|
||||
expect(blogPostsFrench[5]!.metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"2 janvier 2019"`,
|
||||
`"27 février 2020"`,
|
||||
);
|
||||
expect(blogPostsFrench[6]!.metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"1 janvier 2019"`,
|
||||
`"2 janvier 2019"`,
|
||||
);
|
||||
expect(blogPostsFrench[7]!.metadata.formattedDate).toMatchInlineSnapshot(
|
||||
`"14 décembre 2018"`,
|
||||
`"1 janvier 2019"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -372,7 +377,7 @@ describe('blog plugin', () => {
|
|||
expect(blogPost.metadata.editUrl).toEqual(hardcodedEditUrl);
|
||||
});
|
||||
|
||||
expect(editUrlFunction).toHaveBeenCalledTimes(8);
|
||||
expect(editUrlFunction).toHaveBeenCalledTimes(9);
|
||||
|
||||
expect(editUrlFunction).toHaveBeenCalledWith({
|
||||
blogDirPath: 'blog',
|
||||
|
@ -471,6 +476,7 @@ describe('blog plugin', () => {
|
|||
prevItem: undefined,
|
||||
nextItem: undefined,
|
||||
hasTruncateMarker: false,
|
||||
unlisted: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -495,7 +501,7 @@ describe('blog plugin', () => {
|
|||
postsPerPage: 2,
|
||||
});
|
||||
|
||||
expect(Object.keys(blogTags)).toHaveLength(2);
|
||||
expect(Object.keys(blogTags)).toHaveLength(3);
|
||||
expect(blogTags).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {toTagsProp} from '../props';
|
||||
|
||||
describe('toTagsProp', () => {
|
||||
type Tags = Parameters<typeof toTagsProp>[0]['blogTags'];
|
||||
type Tag = Tags[number];
|
||||
|
||||
const tag1: Tag = {
|
||||
label: 'Tag 1',
|
||||
permalink: '/tag1',
|
||||
items: ['item1', 'item2'],
|
||||
pages: [],
|
||||
unlisted: false,
|
||||
};
|
||||
|
||||
const tag2: Tag = {
|
||||
label: 'Tag 2',
|
||||
permalink: '/tag2',
|
||||
items: ['item3'],
|
||||
pages: [],
|
||||
unlisted: false,
|
||||
};
|
||||
|
||||
function testTags(...tags: Tag[]) {
|
||||
const blogTags: Tags = {};
|
||||
tags.forEach((tag) => {
|
||||
blogTags[tag.permalink] = tag;
|
||||
});
|
||||
return toTagsProp({blogTags});
|
||||
}
|
||||
|
||||
it('works', () => {
|
||||
expect(testTags(tag1, tag2)).toEqual([
|
||||
{
|
||||
count: tag1.items.length,
|
||||
label: tag1.label,
|
||||
permalink: tag1.permalink,
|
||||
},
|
||||
{
|
||||
count: tag2.items.length,
|
||||
label: tag2.label,
|
||||
permalink: tag2.permalink,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters unlisted tags', () => {
|
||||
expect(testTags(tag1, {...tag2, unlisted: true})).toEqual([
|
||||
{
|
||||
count: tag1.items.length,
|
||||
label: tag1.label,
|
||||
permalink: tag1.permalink,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(testTags({...tag1, unlisted: true}, tag2)).toEqual([
|
||||
{
|
||||
count: tag2.items.length,
|
||||
label: tag2.label,
|
||||
permalink: tag2.permalink,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -21,8 +21,11 @@ import {
|
|||
Globby,
|
||||
normalizeFrontMatterTags,
|
||||
groupTaggedItems,
|
||||
getTagVisibility,
|
||||
getFileCommitDate,
|
||||
getContentPathList,
|
||||
isUnlisted,
|
||||
isDraft,
|
||||
} from '@docusaurus/utils';
|
||||
import {validateBlogPostFrontMatter} from './frontMatter';
|
||||
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
||||
|
@ -96,6 +99,10 @@ export function paginateBlogPosts({
|
|||
return pages;
|
||||
}
|
||||
|
||||
export function shouldBeListed(blogPost: BlogPost): boolean {
|
||||
return !blogPost.metadata.unlisted;
|
||||
}
|
||||
|
||||
export function getBlogTags({
|
||||
blogPosts,
|
||||
...params
|
||||
|
@ -109,17 +116,23 @@ export function getBlogTags({
|
|||
blogPosts,
|
||||
(blogPost) => blogPost.metadata.tags,
|
||||
);
|
||||
|
||||
return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({
|
||||
label: tag.label,
|
||||
items: tagBlogPosts.map((item) => item.id),
|
||||
permalink: tag.permalink,
|
||||
pages: paginateBlogPosts({
|
||||
blogPosts: tagBlogPosts,
|
||||
basePageUrl: tag.permalink,
|
||||
...params,
|
||||
}),
|
||||
}));
|
||||
return _.mapValues(groups, ({tag, items: tagBlogPosts}) => {
|
||||
const tagVisibility = getTagVisibility({
|
||||
items: tagBlogPosts,
|
||||
isUnlisted: (item) => item.metadata.unlisted,
|
||||
});
|
||||
return {
|
||||
label: tag.label,
|
||||
items: tagVisibility.listedItems.map((item) => item.id),
|
||||
permalink: tag.permalink,
|
||||
pages: paginateBlogPosts({
|
||||
blogPosts: tagVisibility.listedItems,
|
||||
basePageUrl: tag.permalink,
|
||||
...params,
|
||||
}),
|
||||
unlisted: tagVisibility.unlisted,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const DATE_FILENAME_REGEX =
|
||||
|
@ -219,7 +232,10 @@ async function processBlogSourceFile(
|
|||
|
||||
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
|
||||
|
||||
if (frontMatter.draft && process.env.NODE_ENV === 'production') {
|
||||
const draft = isDraft({frontMatter});
|
||||
const unlisted = isUnlisted({frontMatter});
|
||||
|
||||
if (draft) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -326,6 +342,7 @@ async function processBlogSourceFile(
|
|||
hasTruncateMarker: truncateMarker.test(content),
|
||||
authors,
|
||||
frontMatter,
|
||||
unlisted,
|
||||
},
|
||||
content,
|
||||
};
|
||||
|
@ -352,23 +369,25 @@ export async function generateBlogPosts(
|
|||
authorsMapPath: options.authorsMapPath,
|
||||
});
|
||||
|
||||
async function doProcessBlogSourceFile(blogSourceFile: string) {
|
||||
try {
|
||||
return await processBlogSourceFile(
|
||||
blogSourceFile,
|
||||
contentPaths,
|
||||
context,
|
||||
options,
|
||||
authorsMap,
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Processing of blog source file path=${blogSourceFile} failed.`,
|
||||
{cause: err as Error},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const blogPosts = (
|
||||
await Promise.all(
|
||||
blogSourceFiles.map(async (blogSourceFile: string) => {
|
||||
try {
|
||||
return await processBlogSourceFile(
|
||||
blogSourceFile,
|
||||
contentPaths,
|
||||
context,
|
||||
options,
|
||||
authorsMap,
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error`Processing of blog source file path=${blogSourceFile} failed.`;
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
)
|
||||
await Promise.all(blogSourceFiles.map(doProcessBlogSourceFile))
|
||||
).filter(Boolean) as BlogPost[];
|
||||
|
||||
blogPosts.sort(
|
||||
|
|
|
@ -133,8 +133,15 @@ async function createBlogFeedFile({
|
|||
}
|
||||
}
|
||||
|
||||
function shouldBeInFeed(blogPost: BlogPost): boolean {
|
||||
const excluded =
|
||||
blogPost.metadata.frontMatter.draft ||
|
||||
blogPost.metadata.frontMatter.unlisted;
|
||||
return !excluded;
|
||||
}
|
||||
|
||||
export async function createBlogFeedFiles({
|
||||
blogPosts,
|
||||
blogPosts: allBlogPosts,
|
||||
options,
|
||||
siteConfig,
|
||||
outDir,
|
||||
|
@ -146,6 +153,8 @@ export async function createBlogFeedFiles({
|
|||
outDir: string;
|
||||
locale: string;
|
||||
}): Promise<void> {
|
||||
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
|
||||
|
||||
const feed = await generateBlogFeed({
|
||||
blogPosts,
|
||||
options,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
validateFrontMatter,
|
||||
FrontMatterTagsSchema,
|
||||
FrontMatterTOCHeadingLevels,
|
||||
ContentVisibilitySchema,
|
||||
} from '@docusaurus/utils-validation';
|
||||
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
|
@ -32,7 +33,6 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
|||
title: Joi.string().allow(''),
|
||||
description: Joi.string().allow(''),
|
||||
tags: FrontMatterTagsSchema,
|
||||
draft: Joi.boolean(),
|
||||
date: Joi.date().raw(),
|
||||
|
||||
// New multi-authors front matter:
|
||||
|
@ -69,10 +69,12 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
|||
hide_table_of_contents: Joi.boolean(),
|
||||
|
||||
...FrontMatterTOCHeadingLevels,
|
||||
}).messages({
|
||||
'deprecate.error':
|
||||
'{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
||||
});
|
||||
})
|
||||
.messages({
|
||||
'deprecate.error':
|
||||
'{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
||||
})
|
||||
.concat(ContentVisibilitySchema);
|
||||
|
||||
export function validateBlogPostFrontMatter(frontMatter: {
|
||||
[key: string]: unknown;
|
||||
|
|
|
@ -18,19 +18,19 @@ import {
|
|||
getContentPathList,
|
||||
getDataFilePath,
|
||||
DEFAULT_PLUGIN_ID,
|
||||
type TagsListItem,
|
||||
type TagModule,
|
||||
} from '@docusaurus/utils';
|
||||
import {
|
||||
generateBlogPosts,
|
||||
getSourceToPermalink,
|
||||
getBlogTags,
|
||||
paginateBlogPosts,
|
||||
shouldBeListed,
|
||||
} from './blogUtils';
|
||||
import footnoteIDFixer from './remark/footnoteIDFixer';
|
||||
import {translateContent, getTranslationFiles} from './translations';
|
||||
import {createBlogFeedFiles} from './feed';
|
||||
|
||||
import {toTagProp, toTagsProp} from './props';
|
||||
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
||||
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
|
||||
import type {
|
||||
|
@ -112,6 +112,7 @@ export default async function pluginContentBlog(
|
|||
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
||||
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
||||
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
||||
const listedBlogPosts = blogPosts.filter(shouldBeListed);
|
||||
|
||||
if (!blogPosts.length) {
|
||||
return {
|
||||
|
@ -125,8 +126,8 @@ export default async function pluginContentBlog(
|
|||
}
|
||||
|
||||
// Colocate next and prev metadata.
|
||||
blogPosts.forEach((blogPost, index) => {
|
||||
const prevItem = index > 0 ? blogPosts[index - 1] : null;
|
||||
listedBlogPosts.forEach((blogPost, index) => {
|
||||
const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
|
||||
if (prevItem) {
|
||||
blogPost.metadata.prevItem = {
|
||||
title: prevItem.metadata.title,
|
||||
|
@ -135,7 +136,9 @@ export default async function pluginContentBlog(
|
|||
}
|
||||
|
||||
const nextItem =
|
||||
index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
|
||||
index < listedBlogPosts.length - 1
|
||||
? listedBlogPosts[index + 1]
|
||||
: null;
|
||||
if (nextItem) {
|
||||
blogPost.metadata.nextItem = {
|
||||
title: nextItem.metadata.title,
|
||||
|
@ -145,7 +148,7 @@ export default async function pluginContentBlog(
|
|||
});
|
||||
|
||||
const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
|
||||
blogPosts,
|
||||
blogPosts: listedBlogPosts,
|
||||
blogTitle,
|
||||
blogDescription,
|
||||
postsPerPageOption,
|
||||
|
@ -242,6 +245,7 @@ export default async function pluginContentBlog(
|
|||
items: sidebarBlogPosts.map((blogPost) => ({
|
||||
title: blogPost.metadata.title,
|
||||
permalink: blogPost.metadata.permalink,
|
||||
unlisted: blogPost.metadata.unlisted,
|
||||
})),
|
||||
},
|
||||
null,
|
||||
|
@ -303,17 +307,10 @@ export default async function pluginContentBlog(
|
|||
}
|
||||
|
||||
async function createTagsListPage() {
|
||||
const tagsProp: TagsListItem[] = Object.values(blogTags).map((tag) => ({
|
||||
label: tag.label,
|
||||
permalink: tag.permalink,
|
||||
count: tag.items.length,
|
||||
}));
|
||||
|
||||
const tagsPropPath = await createData(
|
||||
`${docuHash(`${blogTagsListPath}-tags`)}.json`,
|
||||
JSON.stringify(tagsProp, null, 2),
|
||||
JSON.stringify(toTagsProp({blogTags}), null, 2),
|
||||
);
|
||||
|
||||
addRoute({
|
||||
path: blogTagsListPath,
|
||||
component: blogTagsListComponent,
|
||||
|
@ -329,15 +326,9 @@ export default async function pluginContentBlog(
|
|||
await Promise.all(
|
||||
tag.pages.map(async (blogPaginated) => {
|
||||
const {metadata, items} = blogPaginated;
|
||||
const tagProp: TagModule = {
|
||||
label: tag.label,
|
||||
permalink: tag.permalink,
|
||||
allTagsPath: blogTagsListPath,
|
||||
count: tag.items.length,
|
||||
};
|
||||
const tagPropPath = await createData(
|
||||
`${docuHash(metadata.permalink)}.json`,
|
||||
JSON.stringify(tagProp, null, 2),
|
||||
JSON.stringify(toTagProp({tag, blogTagsListPath}), null, 2),
|
||||
);
|
||||
|
||||
const listMetadataPath = await createData(
|
||||
|
|
|
@ -90,6 +90,10 @@ declare module '@docusaurus/plugin-content-blog' {
|
|||
* Marks the post as draft and excludes it from the production build.
|
||||
*/
|
||||
draft?: boolean;
|
||||
/**
|
||||
* Marks the post as unlisted and visibly hides it unless directly accessed.
|
||||
*/
|
||||
unlisted?: boolean;
|
||||
/**
|
||||
* Will override the default publish date inferred from git/filename. Yaml
|
||||
* only converts standard yyyy-MM-dd format to dates, so this may stay as a
|
||||
|
@ -222,6 +226,10 @@ declare module '@docusaurus/plugin-content-blog' {
|
|||
readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown};
|
||||
/** Tags, normalized. */
|
||||
readonly tags: Tag[];
|
||||
/**
|
||||
* Marks the post as unlisted and visibly hides it unless directly accessed.
|
||||
*/
|
||||
readonly unlisted: boolean;
|
||||
};
|
||||
/**
|
||||
* @returns The edit URL that's directly plugged into metadata.
|
||||
|
@ -407,9 +415,15 @@ declare module '@docusaurus/plugin-content-blog' {
|
|||
}
|
||||
>;
|
||||
|
||||
export type BlogSidebarItem = {
|
||||
title: string;
|
||||
permalink: string;
|
||||
unlisted: boolean;
|
||||
};
|
||||
|
||||
export type BlogSidebar = {
|
||||
title: string;
|
||||
items: {title: string; permalink: string}[];
|
||||
items: BlogSidebarItem[];
|
||||
};
|
||||
|
||||
export type BlogContent = {
|
||||
|
@ -428,6 +442,7 @@ declare module '@docusaurus/plugin-content-blog' {
|
|||
/** Blog post permalinks. */
|
||||
items: string[];
|
||||
pages: BlogPaginated[];
|
||||
unlisted: boolean;
|
||||
};
|
||||
|
||||
export type BlogPost = {
|
||||
|
|
34
packages/docusaurus-plugin-content-blog/src/props.ts
Normal file
34
packages/docusaurus-plugin-content-blog/src/props.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import type {TagsListItem, TagModule} from '@docusaurus/utils';
|
||||
import type {BlogTag, BlogTags} from '@docusaurus/plugin-content-blog';
|
||||
|
||||
export function toTagsProp({blogTags}: {blogTags: BlogTags}): TagsListItem[] {
|
||||
return Object.values(blogTags)
|
||||
.filter((tag) => !tag.unlisted)
|
||||
.map((tag) => ({
|
||||
label: tag.label,
|
||||
permalink: tag.permalink,
|
||||
count: tag.items.length,
|
||||
}));
|
||||
}
|
||||
|
||||
export function toTagProp({
|
||||
blogTagsListPath,
|
||||
tag,
|
||||
}: {
|
||||
blogTagsListPath: string;
|
||||
tag: BlogTag;
|
||||
}): TagModule {
|
||||
return {
|
||||
label: tag.label,
|
||||
permalink: tag.permalink,
|
||||
allTagsPath: blogTagsListPath,
|
||||
count: tag.items.length,
|
||||
unlisted: tag.unlisted,
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue