mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 16:47:26 +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",
|
"permalink": "/blog/tags/tag-1",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
"/blog/tags/tag-2": {
|
"/blog/tags/tag-2": {
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -57,6 +58,33 @@ exports[`blog plugin works on blog tags without pagination 1`] = `
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"permalink": "/blog/tags/tag-2",
|
"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",
|
"permalink": "/blog/tags/tag-1",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
"/blog/tags/tag-2": {
|
"/blog/tags/tag-2": {
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -133,6 +162,33 @@ exports[`blog plugin works with blog tags 1`] = `
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"permalink": "/blog/tags/tag-2",
|
"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({
|
await createBlogFeedFiles({
|
||||||
blogPosts: blogPosts.filter((post) => !post.metadata.frontMatter.draft),
|
blogPosts,
|
||||||
options,
|
options,
|
||||||
siteConfig: context.siteConfig,
|
siteConfig: context.siteConfig,
|
||||||
outDir: context.outDir,
|
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
|
// TODO this abstraction reduce verbosity but it makes it harder to debug
|
||||||
// It would be preferable to just expose helper methods
|
// It would be preferable to just expose helper methods
|
||||||
function testField(params: {
|
function testField(params: {
|
||||||
fieldName: keyof BlogPostFrontMatter;
|
prefix: string;
|
||||||
validFrontMatters: BlogPostFrontMatter[];
|
validFrontMatters: BlogPostFrontMatter[];
|
||||||
convertibleFrontMatter?: [
|
convertibleFrontMatter?: [
|
||||||
ConvertibleFrontMatter: {[key: string]: unknown},
|
ConvertibleFrontMatter: {[key: string]: unknown},
|
||||||
|
@ -23,7 +23,7 @@ function testField(params: {
|
||||||
ErrorMessage: string,
|
ErrorMessage: string,
|
||||||
][];
|
][];
|
||||||
}) {
|
}) {
|
||||||
describe(`"${params.fieldName}" field`, () => {
|
describe(`"${params.prefix}" field`, () => {
|
||||||
it('accept valid values', () => {
|
it('accept valid values', () => {
|
||||||
params.validFrontMatters.forEach((frontMatter) => {
|
params.validFrontMatters.forEach((frontMatter) => {
|
||||||
expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter);
|
expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter);
|
||||||
|
@ -44,15 +44,12 @@ function testField(params: {
|
||||||
params.invalidFrontMatters?.forEach(([frontMatter, message]) => {
|
params.invalidFrontMatters?.forEach(([frontMatter, message]) => {
|
||||||
try {
|
try {
|
||||||
validateBlogPostFrontMatter(frontMatter);
|
validateBlogPostFrontMatter(frontMatter);
|
||||||
// eslint-disable-next-line jest/no-jasmine-globals
|
throw new Error(
|
||||||
fail(
|
`Blog front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify(
|
||||||
new Error(
|
frontMatter,
|
||||||
`Blog front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify(
|
null,
|
||||||
frontMatter,
|
2,
|
||||||
null,
|
)}`,
|
||||||
2,
|
|
||||||
)}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line jest/no-conditional-expect
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
@ -79,7 +76,7 @@ describe('validateBlogPostFrontMatter', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter description', () => {
|
describe('validateBlogPostFrontMatter description', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'description',
|
prefix: 'description',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
||||||
{description: ''},
|
{description: ''},
|
||||||
|
@ -90,7 +87,7 @@ describe('validateBlogPostFrontMatter description', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter title', () => {
|
describe('validateBlogPostFrontMatter title', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'title',
|
prefix: 'title',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
|
||||||
{title: ''},
|
{title: ''},
|
||||||
|
@ -101,7 +98,7 @@ describe('validateBlogPostFrontMatter title', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter id', () => {
|
describe('validateBlogPostFrontMatter id', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'id',
|
prefix: 'id',
|
||||||
validFrontMatters: [{id: '123'}, {id: 'id'}],
|
validFrontMatters: [{id: '123'}, {id: 'id'}],
|
||||||
invalidFrontMatters: [[{id: ''}, 'not allowed to be empty']],
|
invalidFrontMatters: [[{id: ''}, 'not allowed to be empty']],
|
||||||
});
|
});
|
||||||
|
@ -132,7 +129,7 @@ describe('validateBlogPostFrontMatter handles legacy/new author front matter', (
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter author', () => {
|
describe('validateBlogPostFrontMatter author', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'author',
|
prefix: 'author',
|
||||||
validFrontMatters: [{author: '123'}, {author: 'author'}],
|
validFrontMatters: [{author: '123'}, {author: 'author'}],
|
||||||
invalidFrontMatters: [[{author: ''}, 'not allowed to be empty']],
|
invalidFrontMatters: [[{author: ''}, 'not allowed to be empty']],
|
||||||
});
|
});
|
||||||
|
@ -140,7 +137,7 @@ describe('validateBlogPostFrontMatter author', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter author_title', () => {
|
describe('validateBlogPostFrontMatter author_title', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'author_title',
|
prefix: 'author_title',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{author: '123', author_title: '123'},
|
{author: '123', author_title: '123'},
|
||||||
{author: '123', author_title: 'author_title'},
|
{author: '123', author_title: 'author_title'},
|
||||||
|
@ -149,7 +146,7 @@ describe('validateBlogPostFrontMatter author_title', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'authorTitle',
|
prefix: 'authorTitle',
|
||||||
validFrontMatters: [{authorTitle: '123'}, {authorTitle: 'authorTitle'}],
|
validFrontMatters: [{authorTitle: '123'}, {authorTitle: 'authorTitle'}],
|
||||||
invalidFrontMatters: [[{authorTitle: ''}, 'not allowed to be empty']],
|
invalidFrontMatters: [[{authorTitle: ''}, 'not allowed to be empty']],
|
||||||
});
|
});
|
||||||
|
@ -157,7 +154,7 @@ describe('validateBlogPostFrontMatter author_title', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter author_url', () => {
|
describe('validateBlogPostFrontMatter author_url', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'author_url',
|
prefix: 'author_url',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{author_url: 'https://docusaurus.io'},
|
{author_url: 'https://docusaurus.io'},
|
||||||
{author_url: '../../relative'},
|
{author_url: '../../relative'},
|
||||||
|
@ -172,7 +169,7 @@ describe('validateBlogPostFrontMatter author_url', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'authorURL',
|
prefix: 'authorURL',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{authorURL: 'https://docusaurus.io'},
|
{authorURL: 'https://docusaurus.io'},
|
||||||
{authorURL: '../../relative'},
|
{authorURL: '../../relative'},
|
||||||
|
@ -190,7 +187,7 @@ describe('validateBlogPostFrontMatter author_url', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter author_image_url', () => {
|
describe('validateBlogPostFrontMatter author_image_url', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'author_image_url',
|
prefix: 'author_image_url',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{author_image_url: 'https://docusaurus.io/asset/image.png'},
|
{author_image_url: 'https://docusaurus.io/asset/image.png'},
|
||||||
{author_image_url: '../../relative'},
|
{author_image_url: '../../relative'},
|
||||||
|
@ -205,7 +202,7 @@ describe('validateBlogPostFrontMatter author_image_url', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'authorImageURL',
|
prefix: 'authorImageURL',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{authorImageURL: 'https://docusaurus.io/asset/image.png'},
|
{authorImageURL: 'https://docusaurus.io/asset/image.png'},
|
||||||
{authorImageURL: '../../relative'},
|
{authorImageURL: '../../relative'},
|
||||||
|
@ -222,7 +219,7 @@ describe('validateBlogPostFrontMatter author_image_url', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter authors', () => {
|
describe('validateBlogPostFrontMatter authors', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'author',
|
prefix: 'author',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{authors: []},
|
{authors: []},
|
||||||
{authors: 'authorKey'},
|
{authors: 'authorKey'},
|
||||||
|
@ -270,7 +267,7 @@ describe('validateBlogPostFrontMatter authors', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter slug', () => {
|
describe('validateBlogPostFrontMatter slug', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'slug',
|
prefix: 'slug',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{slug: 'blog/'},
|
{slug: 'blog/'},
|
||||||
{slug: '/blog'},
|
{slug: '/blog'},
|
||||||
|
@ -287,7 +284,7 @@ describe('validateBlogPostFrontMatter slug', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter image', () => {
|
describe('validateBlogPostFrontMatter image', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'image',
|
prefix: 'image',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{image: 'https://docusaurus.io/image.png'},
|
{image: 'https://docusaurus.io/image.png'},
|
||||||
{image: 'blog/'},
|
{image: 'blog/'},
|
||||||
|
@ -307,7 +304,7 @@ describe('validateBlogPostFrontMatter image', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter tags', () => {
|
describe('validateBlogPostFrontMatter tags', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'tags',
|
prefix: 'tags',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{tags: []},
|
{tags: []},
|
||||||
{tags: ['hello']},
|
{tags: ['hello']},
|
||||||
|
@ -335,7 +332,7 @@ describe('validateBlogPostFrontMatter tags', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter keywords', () => {
|
describe('validateBlogPostFrontMatter keywords', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'keywords',
|
prefix: 'keywords',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{keywords: ['hello']},
|
{keywords: ['hello']},
|
||||||
{keywords: ['hello', 'world']},
|
{keywords: ['hello', 'world']},
|
||||||
|
@ -352,7 +349,7 @@ describe('validateBlogPostFrontMatter keywords', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter draft', () => {
|
describe('validateBlogPostFrontMatter draft', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'draft',
|
prefix: 'draft',
|
||||||
validFrontMatters: [{draft: true}, {draft: false}],
|
validFrontMatters: [{draft: true}, {draft: false}],
|
||||||
convertibleFrontMatter: [
|
convertibleFrontMatter: [
|
||||||
[{draft: 'true'}, {draft: true}],
|
[{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', () => {
|
describe('validateBlogPostFrontMatter hide_table_of_contents', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'hide_table_of_contents',
|
prefix: 'hide_table_of_contents',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{hide_table_of_contents: true},
|
{hide_table_of_contents: true},
|
||||||
{hide_table_of_contents: false},
|
{hide_table_of_contents: false},
|
||||||
|
@ -385,7 +416,7 @@ describe('validateBlogPostFrontMatter hide_table_of_contents', () => {
|
||||||
|
|
||||||
describe('validateBlogPostFrontMatter date', () => {
|
describe('validateBlogPostFrontMatter date', () => {
|
||||||
testField({
|
testField({
|
||||||
fieldName: 'date',
|
prefix: 'date',
|
||||||
validFrontMatters: [
|
validFrontMatters: [
|
||||||
{date: new Date('2020-01-01')},
|
{date: new Date('2020-01-01')},
|
||||||
{date: '2020-01-01'},
|
{date: '2020-01-01'},
|
||||||
|
|
|
@ -172,6 +172,7 @@ describe('blog plugin', () => {
|
||||||
title: 'Happy 1st Birthday Slash! (translated)',
|
title: 'Happy 1st Birthday Slash! (translated)',
|
||||||
},
|
},
|
||||||
hasTruncateMarker: false,
|
hasTruncateMarker: false,
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -215,6 +216,7 @@ describe('blog plugin', () => {
|
||||||
title: 'date-matter',
|
title: 'date-matter',
|
||||||
},
|
},
|
||||||
hasTruncateMarker: false,
|
hasTruncateMarker: false,
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
|
@ -252,6 +254,7 @@ describe('blog plugin', () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
hasTruncateMarker: false,
|
hasTruncateMarker: false,
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
|
@ -289,6 +292,7 @@ describe('blog plugin', () => {
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
hasTruncateMarker: false,
|
hasTruncateMarker: false,
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
|
@ -314,13 +318,14 @@ describe('blog plugin', () => {
|
||||||
title: 'date-matter',
|
title: 'date-matter',
|
||||||
},
|
},
|
||||||
hasTruncateMarker: false,
|
hasTruncateMarker: false,
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds simple website blog with localized dates', async () => {
|
it('builds simple website blog with localized dates', async () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||||
const blogPostsFrench = await getBlogPosts(siteDir, {}, getI18n('fr'));
|
const blogPostsFrench = await getBlogPosts(siteDir, {}, getI18n('fr'));
|
||||||
expect(blogPostsFrench).toHaveLength(8);
|
expect(blogPostsFrench).toHaveLength(9);
|
||||||
expect(blogPostsFrench[0]!.metadata.formattedDate).toMatchInlineSnapshot(
|
expect(blogPostsFrench[0]!.metadata.formattedDate).toMatchInlineSnapshot(
|
||||||
`"6 mars 2021"`,
|
`"6 mars 2021"`,
|
||||||
);
|
);
|
||||||
|
@ -337,13 +342,13 @@ describe('blog plugin', () => {
|
||||||
`"27 février 2020"`,
|
`"27 février 2020"`,
|
||||||
);
|
);
|
||||||
expect(blogPostsFrench[5]!.metadata.formattedDate).toMatchInlineSnapshot(
|
expect(blogPostsFrench[5]!.metadata.formattedDate).toMatchInlineSnapshot(
|
||||||
`"2 janvier 2019"`,
|
`"27 février 2020"`,
|
||||||
);
|
);
|
||||||
expect(blogPostsFrench[6]!.metadata.formattedDate).toMatchInlineSnapshot(
|
expect(blogPostsFrench[6]!.metadata.formattedDate).toMatchInlineSnapshot(
|
||||||
`"1 janvier 2019"`,
|
`"2 janvier 2019"`,
|
||||||
);
|
);
|
||||||
expect(blogPostsFrench[7]!.metadata.formattedDate).toMatchInlineSnapshot(
|
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(blogPost.metadata.editUrl).toEqual(hardcodedEditUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(editUrlFunction).toHaveBeenCalledTimes(8);
|
expect(editUrlFunction).toHaveBeenCalledTimes(9);
|
||||||
|
|
||||||
expect(editUrlFunction).toHaveBeenCalledWith({
|
expect(editUrlFunction).toHaveBeenCalledWith({
|
||||||
blogDirPath: 'blog',
|
blogDirPath: 'blog',
|
||||||
|
@ -471,6 +476,7 @@ describe('blog plugin', () => {
|
||||||
prevItem: undefined,
|
prevItem: undefined,
|
||||||
nextItem: undefined,
|
nextItem: undefined,
|
||||||
hasTruncateMarker: false,
|
hasTruncateMarker: false,
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -495,7 +501,7 @@ describe('blog plugin', () => {
|
||||||
postsPerPage: 2,
|
postsPerPage: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(Object.keys(blogTags)).toHaveLength(2);
|
expect(Object.keys(blogTags)).toHaveLength(3);
|
||||||
expect(blogTags).toMatchSnapshot();
|
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,
|
Globby,
|
||||||
normalizeFrontMatterTags,
|
normalizeFrontMatterTags,
|
||||||
groupTaggedItems,
|
groupTaggedItems,
|
||||||
|
getTagVisibility,
|
||||||
getFileCommitDate,
|
getFileCommitDate,
|
||||||
getContentPathList,
|
getContentPathList,
|
||||||
|
isUnlisted,
|
||||||
|
isDraft,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {validateBlogPostFrontMatter} from './frontMatter';
|
import {validateBlogPostFrontMatter} from './frontMatter';
|
||||||
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
||||||
|
@ -96,6 +99,10 @@ export function paginateBlogPosts({
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldBeListed(blogPost: BlogPost): boolean {
|
||||||
|
return !blogPost.metadata.unlisted;
|
||||||
|
}
|
||||||
|
|
||||||
export function getBlogTags({
|
export function getBlogTags({
|
||||||
blogPosts,
|
blogPosts,
|
||||||
...params
|
...params
|
||||||
|
@ -109,17 +116,23 @@ export function getBlogTags({
|
||||||
blogPosts,
|
blogPosts,
|
||||||
(blogPost) => blogPost.metadata.tags,
|
(blogPost) => blogPost.metadata.tags,
|
||||||
);
|
);
|
||||||
|
return _.mapValues(groups, ({tag, items: tagBlogPosts}) => {
|
||||||
return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({
|
const tagVisibility = getTagVisibility({
|
||||||
label: tag.label,
|
items: tagBlogPosts,
|
||||||
items: tagBlogPosts.map((item) => item.id),
|
isUnlisted: (item) => item.metadata.unlisted,
|
||||||
permalink: tag.permalink,
|
});
|
||||||
pages: paginateBlogPosts({
|
return {
|
||||||
blogPosts: tagBlogPosts,
|
label: tag.label,
|
||||||
basePageUrl: tag.permalink,
|
items: tagVisibility.listedItems.map((item) => item.id),
|
||||||
...params,
|
permalink: tag.permalink,
|
||||||
}),
|
pages: paginateBlogPosts({
|
||||||
}));
|
blogPosts: tagVisibility.listedItems,
|
||||||
|
basePageUrl: tag.permalink,
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
unlisted: tagVisibility.unlisted,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const DATE_FILENAME_REGEX =
|
const DATE_FILENAME_REGEX =
|
||||||
|
@ -219,7 +232,10 @@ async function processBlogSourceFile(
|
||||||
|
|
||||||
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
|
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;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +342,7 @@ async function processBlogSourceFile(
|
||||||
hasTruncateMarker: truncateMarker.test(content),
|
hasTruncateMarker: truncateMarker.test(content),
|
||||||
authors,
|
authors,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
|
unlisted,
|
||||||
},
|
},
|
||||||
content,
|
content,
|
||||||
};
|
};
|
||||||
|
@ -352,23 +369,25 @@ export async function generateBlogPosts(
|
||||||
authorsMapPath: options.authorsMapPath,
|
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 = (
|
const blogPosts = (
|
||||||
await Promise.all(
|
await Promise.all(blogSourceFiles.map(doProcessBlogSourceFile))
|
||||||
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;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
).filter(Boolean) as BlogPost[];
|
).filter(Boolean) as BlogPost[];
|
||||||
|
|
||||||
blogPosts.sort(
|
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({
|
export async function createBlogFeedFiles({
|
||||||
blogPosts,
|
blogPosts: allBlogPosts,
|
||||||
options,
|
options,
|
||||||
siteConfig,
|
siteConfig,
|
||||||
outDir,
|
outDir,
|
||||||
|
@ -146,6 +153,8 @@ export async function createBlogFeedFiles({
|
||||||
outDir: string;
|
outDir: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
const blogPosts = allBlogPosts.filter(shouldBeInFeed);
|
||||||
|
|
||||||
const feed = await generateBlogFeed({
|
const feed = await generateBlogFeed({
|
||||||
blogPosts,
|
blogPosts,
|
||||||
options,
|
options,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
validateFrontMatter,
|
validateFrontMatter,
|
||||||
FrontMatterTagsSchema,
|
FrontMatterTagsSchema,
|
||||||
FrontMatterTOCHeadingLevels,
|
FrontMatterTOCHeadingLevels,
|
||||||
|
ContentVisibilitySchema,
|
||||||
} from '@docusaurus/utils-validation';
|
} from '@docusaurus/utils-validation';
|
||||||
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
@ -32,7 +33,6 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
||||||
title: Joi.string().allow(''),
|
title: Joi.string().allow(''),
|
||||||
description: Joi.string().allow(''),
|
description: Joi.string().allow(''),
|
||||||
tags: FrontMatterTagsSchema,
|
tags: FrontMatterTagsSchema,
|
||||||
draft: Joi.boolean(),
|
|
||||||
date: Joi.date().raw(),
|
date: Joi.date().raw(),
|
||||||
|
|
||||||
// New multi-authors front matter:
|
// New multi-authors front matter:
|
||||||
|
@ -69,10 +69,12 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
||||||
hide_table_of_contents: Joi.boolean(),
|
hide_table_of_contents: Joi.boolean(),
|
||||||
|
|
||||||
...FrontMatterTOCHeadingLevels,
|
...FrontMatterTOCHeadingLevels,
|
||||||
}).messages({
|
})
|
||||||
'deprecate.error':
|
.messages({
|
||||||
'{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
'deprecate.error':
|
||||||
});
|
'{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
||||||
|
})
|
||||||
|
.concat(ContentVisibilitySchema);
|
||||||
|
|
||||||
export function validateBlogPostFrontMatter(frontMatter: {
|
export function validateBlogPostFrontMatter(frontMatter: {
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
|
|
@ -18,19 +18,19 @@ import {
|
||||||
getContentPathList,
|
getContentPathList,
|
||||||
getDataFilePath,
|
getDataFilePath,
|
||||||
DEFAULT_PLUGIN_ID,
|
DEFAULT_PLUGIN_ID,
|
||||||
type TagsListItem,
|
|
||||||
type TagModule,
|
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {
|
import {
|
||||||
generateBlogPosts,
|
generateBlogPosts,
|
||||||
getSourceToPermalink,
|
getSourceToPermalink,
|
||||||
getBlogTags,
|
getBlogTags,
|
||||||
paginateBlogPosts,
|
paginateBlogPosts,
|
||||||
|
shouldBeListed,
|
||||||
} from './blogUtils';
|
} from './blogUtils';
|
||||||
import footnoteIDFixer from './remark/footnoteIDFixer';
|
import footnoteIDFixer from './remark/footnoteIDFixer';
|
||||||
import {translateContent, getTranslationFiles} from './translations';
|
import {translateContent, getTranslationFiles} from './translations';
|
||||||
import {createBlogFeedFiles} from './feed';
|
import {createBlogFeedFiles} from './feed';
|
||||||
|
|
||||||
|
import {toTagProp, toTagsProp} from './props';
|
||||||
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
||||||
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
|
import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
|
||||||
import type {
|
import type {
|
||||||
|
@ -112,6 +112,7 @@ export default async function pluginContentBlog(
|
||||||
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
||||||
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
||||||
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
||||||
|
const listedBlogPosts = blogPosts.filter(shouldBeListed);
|
||||||
|
|
||||||
if (!blogPosts.length) {
|
if (!blogPosts.length) {
|
||||||
return {
|
return {
|
||||||
|
@ -125,8 +126,8 @@ export default async function pluginContentBlog(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colocate next and prev metadata.
|
// Colocate next and prev metadata.
|
||||||
blogPosts.forEach((blogPost, index) => {
|
listedBlogPosts.forEach((blogPost, index) => {
|
||||||
const prevItem = index > 0 ? blogPosts[index - 1] : null;
|
const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
|
||||||
if (prevItem) {
|
if (prevItem) {
|
||||||
blogPost.metadata.prevItem = {
|
blogPost.metadata.prevItem = {
|
||||||
title: prevItem.metadata.title,
|
title: prevItem.metadata.title,
|
||||||
|
@ -135,7 +136,9 @@ export default async function pluginContentBlog(
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextItem =
|
const nextItem =
|
||||||
index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
|
index < listedBlogPosts.length - 1
|
||||||
|
? listedBlogPosts[index + 1]
|
||||||
|
: null;
|
||||||
if (nextItem) {
|
if (nextItem) {
|
||||||
blogPost.metadata.nextItem = {
|
blogPost.metadata.nextItem = {
|
||||||
title: nextItem.metadata.title,
|
title: nextItem.metadata.title,
|
||||||
|
@ -145,7 +148,7 @@ export default async function pluginContentBlog(
|
||||||
});
|
});
|
||||||
|
|
||||||
const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
|
const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
|
||||||
blogPosts,
|
blogPosts: listedBlogPosts,
|
||||||
blogTitle,
|
blogTitle,
|
||||||
blogDescription,
|
blogDescription,
|
||||||
postsPerPageOption,
|
postsPerPageOption,
|
||||||
|
@ -242,6 +245,7 @@ export default async function pluginContentBlog(
|
||||||
items: sidebarBlogPosts.map((blogPost) => ({
|
items: sidebarBlogPosts.map((blogPost) => ({
|
||||||
title: blogPost.metadata.title,
|
title: blogPost.metadata.title,
|
||||||
permalink: blogPost.metadata.permalink,
|
permalink: blogPost.metadata.permalink,
|
||||||
|
unlisted: blogPost.metadata.unlisted,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
|
@ -303,17 +307,10 @@ export default async function pluginContentBlog(
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTagsListPage() {
|
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(
|
const tagsPropPath = await createData(
|
||||||
`${docuHash(`${blogTagsListPath}-tags`)}.json`,
|
`${docuHash(`${blogTagsListPath}-tags`)}.json`,
|
||||||
JSON.stringify(tagsProp, null, 2),
|
JSON.stringify(toTagsProp({blogTags}), null, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
addRoute({
|
addRoute({
|
||||||
path: blogTagsListPath,
|
path: blogTagsListPath,
|
||||||
component: blogTagsListComponent,
|
component: blogTagsListComponent,
|
||||||
|
@ -329,15 +326,9 @@ export default async function pluginContentBlog(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
tag.pages.map(async (blogPaginated) => {
|
tag.pages.map(async (blogPaginated) => {
|
||||||
const {metadata, items} = blogPaginated;
|
const {metadata, items} = blogPaginated;
|
||||||
const tagProp: TagModule = {
|
|
||||||
label: tag.label,
|
|
||||||
permalink: tag.permalink,
|
|
||||||
allTagsPath: blogTagsListPath,
|
|
||||||
count: tag.items.length,
|
|
||||||
};
|
|
||||||
const tagPropPath = await createData(
|
const tagPropPath = await createData(
|
||||||
`${docuHash(metadata.permalink)}.json`,
|
`${docuHash(metadata.permalink)}.json`,
|
||||||
JSON.stringify(tagProp, null, 2),
|
JSON.stringify(toTagProp({tag, blogTagsListPath}), null, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
const listMetadataPath = await createData(
|
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.
|
* Marks the post as draft and excludes it from the production build.
|
||||||
*/
|
*/
|
||||||
draft?: boolean;
|
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
|
* 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
|
* 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};
|
readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown};
|
||||||
/** Tags, normalized. */
|
/** Tags, normalized. */
|
||||||
readonly tags: Tag[];
|
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.
|
* @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 = {
|
export type BlogSidebar = {
|
||||||
title: string;
|
title: string;
|
||||||
items: {title: string; permalink: string}[];
|
items: BlogSidebarItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BlogContent = {
|
export type BlogContent = {
|
||||||
|
@ -428,6 +442,7 @@ declare module '@docusaurus/plugin-content-blog' {
|
||||||
/** Blog post permalinks. */
|
/** Blog post permalinks. */
|
||||||
items: string[];
|
items: string[];
|
||||||
pages: BlogPaginated[];
|
pages: BlogPaginated[];
|
||||||
|
unlisted: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BlogPost = {
|
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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
unlisted: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This is an unlisted document
|
|
@ -1,5 +1,6 @@
|
||||||
---
|
---
|
||||||
custom_edit_url: null
|
custom_edit_url: null
|
||||||
|
pagination_next: doc-unlisted
|
||||||
---
|
---
|
||||||
|
|
||||||
Lorem ipsum.
|
Lorem ipsum.
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
id: unlisted-category-index
|
||||||
|
unlisted: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This is an unlisted category index
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
id: unlisted-category-doc
|
||||||
|
unlisted: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This is an unlisted category doc
|
|
@ -4,7 +4,16 @@
|
||||||
{
|
{
|
||||||
"type": "category",
|
"type": "category",
|
||||||
"label": "foo",
|
"label": "foo",
|
||||||
"items": ["foo/bar", "foo/baz"]
|
"items": ["foo/bar", "doc-unlisted", "foo/baz"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "category",
|
||||||
|
"label": "Unlisted category",
|
||||||
|
"link": {
|
||||||
|
"type": "doc",
|
||||||
|
"id": "unlisted-category/unlisted-category-index"
|
||||||
|
},
|
||||||
|
"items": ["unlisted-category/unlisted-category-doc"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "category",
|
"type": "category",
|
||||||
|
|
|
@ -10,11 +10,23 @@ exports[`docsVersion first time versioning 1`] = `
|
||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
"foo/bar",
|
"foo/bar",
|
||||||
|
"doc-unlisted",
|
||||||
"foo/baz",
|
"foo/baz",
|
||||||
],
|
],
|
||||||
"label": "foo",
|
"label": "foo",
|
||||||
"type": "category",
|
"type": "category",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
"unlisted-category/unlisted-category-doc",
|
||||||
|
],
|
||||||
|
"label": "Unlisted category",
|
||||||
|
"link": {
|
||||||
|
"id": "unlisted-category/unlisted-category-index",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
"type": "category",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
"rootAbsoluteSlug",
|
"rootAbsoluteSlug",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`simple site custom pagination 1`] = `
|
exports[`simple site custom pagination - development 1`] = `
|
||||||
{
|
{
|
||||||
"pagination": [
|
"pagination": [
|
||||||
{
|
{
|
||||||
|
@ -25,14 +25,25 @@ exports[`simple site custom pagination 1`] = `
|
||||||
{
|
{
|
||||||
"id": "doc-draft",
|
"id": "doc-draft",
|
||||||
"next": {
|
"next": {
|
||||||
"permalink": "/docs/foo/bar",
|
"permalink": "/docs/doc-unlisted",
|
||||||
"title": "Bar",
|
"title": "doc-unlisted",
|
||||||
},
|
},
|
||||||
"prev": {
|
"prev": {
|
||||||
"permalink": "/docs/doc with space",
|
"permalink": "/docs/doc with space",
|
||||||
"title": "Hoo hoo, if this path tricks you...",
|
"title": "Hoo hoo, if this path tricks you...",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "doc-unlisted",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/foo/bar",
|
||||||
|
"title": "Bar",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/doc-draft",
|
||||||
|
"title": "doc-draft",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "foo/bar",
|
"id": "foo/bar",
|
||||||
"next": undefined,
|
"next": undefined,
|
||||||
|
@ -74,9 +85,356 @@ exports[`simple site custom pagination 1`] = `
|
||||||
{
|
{
|
||||||
"id": "ipsum",
|
"id": "ipsum",
|
||||||
"next": {
|
"next": {
|
||||||
|
"permalink": "/docs/doc-unlisted",
|
||||||
|
"title": "doc-unlisted",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/",
|
||||||
|
"title": "Hello sidebar_label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lastUpdateAuthorOnly",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/lastUpdateDateOnly",
|
||||||
|
"title": "Last Update Date Only",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/ipsum",
|
||||||
|
"title": "ipsum",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lastUpdateDateOnly",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/lorem",
|
||||||
|
"title": "lorem",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
"permalink": "/docs/lastUpdateAuthorOnly",
|
"permalink": "/docs/lastUpdateAuthorOnly",
|
||||||
"title": "Last Update Author Only",
|
"title": "Last Update Author Only",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lorem",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/rootAbsoluteSlug",
|
||||||
|
"title": "rootAbsoluteSlug",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/lastUpdateDateOnly",
|
||||||
|
"title": "Last Update Date Only",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootAbsoluteSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/headingAsTitle",
|
||||||
|
"title": "My heading as title",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/foo/bazSlug.html",
|
||||||
|
"title": "baz pagination_label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootRelativeSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/headingAsTitle",
|
||||||
|
"title": "My heading as title",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/foo/bazSlug.html",
|
||||||
|
"title": "baz pagination_label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootResolvedSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/headingAsTitle",
|
||||||
|
"title": "My heading as title",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/foo/bazSlug.html",
|
||||||
|
"title": "baz pagination_label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootTryToEscapeSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/headingAsTitle",
|
||||||
|
"title": "My heading as title",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/foo/bazSlug.html",
|
||||||
|
"title": "baz pagination_label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slugs/absoluteSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/slugs/relativeSlug",
|
||||||
|
"title": "relativeSlug",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/rootTryToEscapeSlug",
|
||||||
|
"title": "rootTryToEscapeSlug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slugs/relativeSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/slugs/hey/resolvedSlug",
|
||||||
|
"title": "resolvedSlug",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/absoluteSlug",
|
||||||
|
"title": "absoluteSlug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slugs/resolvedSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/tryToEscapeSlug",
|
||||||
|
"title": "tryToEscapeSlug",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/slugs/relativeSlug",
|
||||||
|
"title": "relativeSlug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slugs/tryToEscapeSlug",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/unlisted-category/",
|
||||||
|
"title": "unlisted-category-index",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/slugs/hey/resolvedSlug",
|
||||||
|
"title": "resolvedSlug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unlisted-category/unlisted-category-doc",
|
||||||
|
"next": undefined,
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/unlisted-category/",
|
||||||
|
"title": "unlisted-category-index",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unlisted-category/unlisted-category-index",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/unlisted-category/unlisted-category-doc",
|
||||||
|
"title": "unlisted-category-doc",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/tryToEscapeSlug",
|
||||||
|
"title": "tryToEscapeSlug",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebars": {
|
||||||
|
"defaultSidebar": [
|
||||||
|
{
|
||||||
|
"id": "customLastUpdate",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc with space",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc-draft",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc-unlisted",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"collapsible": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "foo/bar",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "foo/baz",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"label": "foo",
|
||||||
|
"link": undefined,
|
||||||
|
"type": "category",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "headingAsTitle",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hello",
|
||||||
|
"label": "Hello sidebar_label",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ipsum",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lastUpdateAuthorOnly",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lastUpdateDateOnly",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lorem",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootAbsoluteSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootRelativeSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootResolvedSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rootTryToEscapeSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"collapsible": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "slugs/absoluteSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slugs/relativeSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slugs/resolvedSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "slugs/tryToEscapeSlug",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"label": "slugs",
|
||||||
|
"link": undefined,
|
||||||
|
"type": "category",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"collapsible": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "unlisted-category/unlisted-category-doc",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"label": "unlisted-category-index",
|
||||||
|
"link": {
|
||||||
|
"id": "unlisted-category/unlisted-category-index",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
"type": "category",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`simple site custom pagination - production 1`] = `
|
||||||
|
{
|
||||||
|
"pagination": [
|
||||||
|
{
|
||||||
|
"id": "customLastUpdate",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/doc with space",
|
||||||
|
"title": "Hoo hoo, if this path tricks you...",
|
||||||
|
},
|
||||||
|
"prev": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc with space",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/doc-draft",
|
||||||
|
"title": "doc-draft",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/customLastUpdate",
|
||||||
|
"title": "Custom Last Update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc-draft",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/foo/bar",
|
||||||
|
"title": "Bar",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/doc with space",
|
||||||
|
"title": "Hoo hoo, if this path tricks you...",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "doc-unlisted",
|
||||||
|
"next": undefined,
|
||||||
|
"prev": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "foo/bar",
|
||||||
|
"next": undefined,
|
||||||
|
"prev": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "foo/baz",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/headingAsTitle",
|
||||||
|
"title": "My heading as title",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/foo/bar",
|
||||||
|
"title": "Bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "headingAsTitle",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/",
|
||||||
|
"title": "Hello sidebar_label",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/foo/bazSlug.html",
|
||||||
|
"title": "baz pagination_label",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hello",
|
||||||
|
"next": {
|
||||||
|
"permalink": "/docs/ipsum",
|
||||||
|
"title": "ipsum",
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"permalink": "/docs/headingAsTitle",
|
||||||
|
"title": "My heading as title",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ipsum",
|
||||||
|
"next": undefined,
|
||||||
"prev": {
|
"prev": {
|
||||||
"permalink": "/docs/",
|
"permalink": "/docs/",
|
||||||
"title": "Hello sidebar_label",
|
"title": "Hello sidebar_label",
|
||||||
|
@ -200,6 +558,16 @@ exports[`simple site custom pagination 1`] = `
|
||||||
"title": "resolvedSlug",
|
"title": "resolvedSlug",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "unlisted-category/unlisted-category-doc",
|
||||||
|
"next": undefined,
|
||||||
|
"prev": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unlisted-category/unlisted-category-index",
|
||||||
|
"next": undefined,
|
||||||
|
"prev": undefined,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"sidebars": {
|
"sidebars": {
|
||||||
"defaultSidebar": [
|
"defaultSidebar": [
|
||||||
|
@ -215,6 +583,10 @@ exports[`simple site custom pagination 1`] = `
|
||||||
"id": "doc-draft",
|
"id": "doc-draft",
|
||||||
"type": "doc",
|
"type": "doc",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "doc-unlisted",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"collapsed": false,
|
"collapsed": false,
|
||||||
"collapsible": true,
|
"collapsible": true,
|
||||||
|
@ -298,6 +670,22 @@ exports[`simple site custom pagination 1`] = `
|
||||||
"link": undefined,
|
"link": undefined,
|
||||||
"type": "category",
|
"type": "category",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"collapsed": false,
|
||||||
|
"collapsible": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "unlisted-category/unlisted-category-doc",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"label": "unlisted-category-index",
|
||||||
|
"link": {
|
||||||
|
"id": "unlisted-category/unlisted-category-index",
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
"type": "category",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,12 @@ exports[`toGlobalDataVersion generates the right docs, sidebars, and metadata 1`
|
||||||
"path": "/current/doc",
|
"path": "/current/doc",
|
||||||
"sidebar": "tutorial",
|
"sidebar": "tutorial",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "docNoSidebarUnlisted",
|
||||||
|
"path": "/current/docNoSidebarUnlisted",
|
||||||
|
"sidebar": undefined,
|
||||||
|
"unlisted": true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "/current/generated",
|
"id": "/current/generated",
|
||||||
"path": "/current/generated",
|
"path": "/current/generated",
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -153,7 +153,7 @@ function createTestUtils({
|
||||||
versionMetadata,
|
versionMetadata,
|
||||||
context,
|
context,
|
||||||
options,
|
options,
|
||||||
env: 'production',
|
env,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -173,11 +173,11 @@ function createTestUtils({
|
||||||
const sidebarsUtils = createSidebarsUtils(sidebars);
|
const sidebarsUtils = createSidebarsUtils(sidebars);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pagination: addDocNavigation(
|
pagination: addDocNavigation({
|
||||||
rawDocs,
|
docs: rawDocs,
|
||||||
sidebarsUtils,
|
sidebarsUtils,
|
||||||
versionMetadata.sidebarFilePath as string,
|
sidebarFilePath: versionMetadata.sidebarFilePath as string,
|
||||||
).map((doc) => ({prev: doc.previous, next: doc.next, id: doc.id})),
|
}).map((doc) => ({prev: doc.previous, next: doc.next, id: doc.id})),
|
||||||
sidebars,
|
sidebars,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -247,6 +247,7 @@ describe('simple site', () => {
|
||||||
'headingAsTitle.md',
|
'headingAsTitle.md',
|
||||||
'doc with space.md',
|
'doc with space.md',
|
||||||
'doc-draft.md',
|
'doc-draft.md',
|
||||||
|
'doc-unlisted.md',
|
||||||
'customLastUpdate.md',
|
'customLastUpdate.md',
|
||||||
'lastUpdateAuthorOnly.md',
|
'lastUpdateAuthorOnly.md',
|
||||||
'lastUpdateDateOnly.md',
|
'lastUpdateDateOnly.md',
|
||||||
|
@ -256,6 +257,8 @@ describe('simple site', () => {
|
||||||
'slugs/relativeSlug.md',
|
'slugs/relativeSlug.md',
|
||||||
'slugs/resolvedSlug.md',
|
'slugs/resolvedSlug.md',
|
||||||
'slugs/tryToEscapeSlug.md',
|
'slugs/tryToEscapeSlug.md',
|
||||||
|
'unlisted-category/index.md',
|
||||||
|
'unlisted-category/unlisted-category-doc.md',
|
||||||
].sort(),
|
].sort(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -279,6 +282,7 @@ describe('simple site', () => {
|
||||||
pagination_prev: null,
|
pagination_prev: null,
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
await defaultTestUtils.testMeta(path.join('hello.md'), {
|
await defaultTestUtils.testMeta(path.join('hello.md'), {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
|
@ -306,6 +310,7 @@ describe('simple site', () => {
|
||||||
permalink: '/docs/tags/tag-3',
|
permalink: '/docs/tags/tag-3',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -356,6 +361,7 @@ describe('simple site', () => {
|
||||||
permalink: '/docs/tags/tag2-custom-permalink',
|
permalink: '/docs/tags/tag2-custom-permalink',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -377,6 +383,7 @@ describe('simple site', () => {
|
||||||
unrelated_front_matter: "won't be part of metadata",
|
unrelated_front_matter: "won't be part of metadata",
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -430,6 +437,7 @@ describe('simple site', () => {
|
||||||
permalink: '/docs/tags/tag2-custom-permalink',
|
permalink: '/docs/tags/tag2-custom-permalink',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(editUrlFunction).toHaveBeenCalledTimes(1);
|
expect(editUrlFunction).toHaveBeenCalledTimes(1);
|
||||||
|
@ -476,6 +484,7 @@ describe('simple site', () => {
|
||||||
formattedLastUpdatedAt: 'Oct 14, 2018',
|
formattedLastUpdatedAt: 'Oct 14, 2018',
|
||||||
lastUpdatedBy: 'Author',
|
lastUpdatedBy: 'Author',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -501,6 +510,44 @@ describe('simple site', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('docs with unlisted frontmatter', async () => {
|
||||||
|
const {createTestUtilsPartial} = await loadSite();
|
||||||
|
|
||||||
|
const baseMeta = {
|
||||||
|
version: 'current',
|
||||||
|
id: 'doc-unlisted',
|
||||||
|
unversionedId: 'doc-unlisted',
|
||||||
|
sourceDirName: '.',
|
||||||
|
permalink: '/docs/doc-unlisted',
|
||||||
|
slug: '/doc-unlisted',
|
||||||
|
title: 'doc-unlisted',
|
||||||
|
description: 'This is an unlisted document',
|
||||||
|
frontMatter: {
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const testUtilsProd = createTestUtilsPartial({
|
||||||
|
env: 'production',
|
||||||
|
});
|
||||||
|
|
||||||
|
await testUtilsProd.testMeta('doc-unlisted.md', {
|
||||||
|
...baseMeta,
|
||||||
|
unlisted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const testUtilsDev = createTestUtilsPartial({
|
||||||
|
env: 'development',
|
||||||
|
});
|
||||||
|
|
||||||
|
await testUtilsDev.testMeta('doc-unlisted.md', {
|
||||||
|
...baseMeta,
|
||||||
|
unlisted: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('docs with last_update front matter', async () => {
|
it('docs with last_update front matter', async () => {
|
||||||
const {siteDir, context, options, currentVersion, createTestUtilsPartial} =
|
const {siteDir, context, options, currentVersion, createTestUtilsPartial} =
|
||||||
await loadSite({
|
await loadSite({
|
||||||
|
@ -538,6 +585,7 @@ describe('simple site', () => {
|
||||||
lastUpdatedBy: 'Custom Author',
|
lastUpdatedBy: 'Custom Author',
|
||||||
sidebarPosition: undefined,
|
sidebarPosition: undefined,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -577,6 +625,7 @@ describe('simple site', () => {
|
||||||
lastUpdatedBy: 'Custom Author',
|
lastUpdatedBy: 'Custom Author',
|
||||||
sidebarPosition: undefined,
|
sidebarPosition: undefined,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -616,6 +665,7 @@ describe('simple site', () => {
|
||||||
lastUpdatedBy: 'Author',
|
lastUpdatedBy: 'Author',
|
||||||
sidebarPosition: undefined,
|
sidebarPosition: undefined,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -656,6 +706,7 @@ describe('simple site', () => {
|
||||||
lastUpdatedBy: undefined,
|
lastUpdatedBy: undefined,
|
||||||
sidebarPosition: undefined,
|
sidebarPosition: undefined,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -718,12 +769,20 @@ describe('simple site', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('custom pagination', async () => {
|
it('custom pagination - production', async () => {
|
||||||
const {defaultTestUtils, options, versionsMetadata} = await loadSite();
|
const {createTestUtilsPartial, options, versionsMetadata} =
|
||||||
|
await loadSite();
|
||||||
|
const testUtils = createTestUtilsPartial({env: 'production'});
|
||||||
const docs = await readVersionDocs(versionsMetadata[0]!, options);
|
const docs = await readVersionDocs(versionsMetadata[0]!, options);
|
||||||
await expect(
|
await expect(testUtils.generateNavigation(docs)).resolves.toMatchSnapshot();
|
||||||
defaultTestUtils.generateNavigation(docs),
|
});
|
||||||
).resolves.toMatchSnapshot();
|
|
||||||
|
it('custom pagination - development', async () => {
|
||||||
|
const {createTestUtilsPartial, options, versionsMetadata} =
|
||||||
|
await loadSite();
|
||||||
|
const testUtils = createTestUtilsPartial({env: 'development'});
|
||||||
|
const docs = await readVersionDocs(versionsMetadata[0]!, options);
|
||||||
|
await expect(testUtils.generateNavigation(docs)).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bad pagination', async () => {
|
it('bad pagination', async () => {
|
||||||
|
@ -847,6 +906,7 @@ describe('versioned site', () => {
|
||||||
permalink: '/docs/next/tags/barTag-3-permalink',
|
permalink: '/docs/next/tags/barTag-3-permalink',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
await currentVersionTestUtils.testMeta(path.join('hello.md'), {
|
await currentVersionTestUtils.testMeta(path.join('hello.md'), {
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
|
@ -861,6 +921,7 @@ describe('versioned site', () => {
|
||||||
slug: '/',
|
slug: '/',
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -878,6 +939,7 @@ describe('versioned site', () => {
|
||||||
frontMatter: {slug: 'barSlug'},
|
frontMatter: {slug: 'barSlug'},
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
await version100TestUtils.testMeta(path.join('hello.md'), {
|
await version100TestUtils.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
|
@ -894,6 +956,7 @@ describe('versioned site', () => {
|
||||||
source:
|
source:
|
||||||
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
await version101TestUtils.testMeta(path.join('foo', 'bar.md'), {
|
await version101TestUtils.testMeta(path.join('foo', 'bar.md'), {
|
||||||
id: 'version-1.0.1/foo/bar',
|
id: 'version-1.0.1/foo/bar',
|
||||||
|
@ -906,6 +969,7 @@ describe('versioned site', () => {
|
||||||
version: '1.0.1',
|
version: '1.0.1',
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
await version101TestUtils.testMeta(path.join('hello.md'), {
|
await version101TestUtils.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.1/hello',
|
id: 'version-1.0.1/hello',
|
||||||
|
@ -920,6 +984,7 @@ describe('versioned site', () => {
|
||||||
slug: '/',
|
slug: '/',
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1016,6 +1081,7 @@ describe('versioned site', () => {
|
||||||
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||||
editUrl: hardcodedEditUrl,
|
editUrl: hardcodedEditUrl,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(editUrlFunction).toHaveBeenCalledTimes(1);
|
expect(editUrlFunction).toHaveBeenCalledTimes(1);
|
||||||
|
@ -1059,6 +1125,7 @@ describe('versioned site', () => {
|
||||||
editUrl:
|
editUrl:
|
||||||
'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0/hello.md',
|
'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0/hello.md',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1094,6 +1161,7 @@ describe('versioned site', () => {
|
||||||
editUrl:
|
editUrl:
|
||||||
'https://github.com/facebook/docusaurus/edit/main/website/docs/hello.md',
|
'https://github.com/facebook/docusaurus/edit/main/website/docs/hello.md',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1130,6 +1198,7 @@ describe('versioned site', () => {
|
||||||
editUrl:
|
editUrl:
|
||||||
'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1167,6 +1236,7 @@ describe('versioned site', () => {
|
||||||
editUrl:
|
editUrl:
|
||||||
'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/current/hello.md',
|
'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/current/hello.md',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
unlisted: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,15 +44,12 @@ function testField(params: {
|
||||||
params.invalidFrontMatters?.forEach(([frontMatter, message]) => {
|
params.invalidFrontMatters?.forEach(([frontMatter, message]) => {
|
||||||
try {
|
try {
|
||||||
validateDocFrontMatter(frontMatter);
|
validateDocFrontMatter(frontMatter);
|
||||||
// eslint-disable-next-line jest/no-jasmine-globals
|
throw new Error(
|
||||||
fail(
|
`Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify(
|
||||||
new Error(
|
frontMatter,
|
||||||
`Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify(
|
null,
|
||||||
frontMatter,
|
2,
|
||||||
null,
|
)}`,
|
||||||
2,
|
|
||||||
)}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line jest/no-conditional-expect
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
@ -397,6 +394,41 @@ describe('validateDocFrontMatter draft', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('validateDocFrontMatter 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'],
|
||||||
|
[{unlisted: ''}, '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('validateDocFrontMatter last_update', () => {
|
describe('validateDocFrontMatter last_update', () => {
|
||||||
testField({
|
testField({
|
||||||
prefix: 'last_update',
|
prefix: 'last_update',
|
||||||
|
|
|
@ -19,12 +19,21 @@ describe('toGlobalDataVersion', () => {
|
||||||
permalink: '/current/main',
|
permalink: '/current/main',
|
||||||
sidebar: 'tutorial',
|
sidebar: 'tutorial',
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
|
unlisted: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
unversionedId: 'doc',
|
unversionedId: 'doc',
|
||||||
permalink: '/current/doc',
|
permalink: '/current/doc',
|
||||||
sidebar: 'tutorial',
|
sidebar: 'tutorial',
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
|
unlisted: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unversionedId: 'docNoSidebarUnlisted',
|
||||||
|
permalink: '/current/docNoSidebarUnlisted',
|
||||||
|
sidebar: undefined,
|
||||||
|
frontMatter: {},
|
||||||
|
unlisted: true,
|
||||||
},
|
},
|
||||||
] as DocMetadata[];
|
] as DocMetadata[];
|
||||||
const sidebars: Sidebars = {
|
const sidebars: Sidebars = {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {toTagDocListProp} from '../props';
|
import {toSidebarDocItemLinkProp, toTagDocListProp} from '../props';
|
||||||
|
|
||||||
describe('toTagDocListProp', () => {
|
describe('toTagDocListProp', () => {
|
||||||
type Params = Parameters<typeof toTagDocListProp>[0];
|
type Params = Parameters<typeof toTagDocListProp>[0];
|
||||||
|
@ -61,3 +61,74 @@ describe('toTagDocListProp', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toSidebarDocItemLinkProp', () => {
|
||||||
|
type Params = Parameters<typeof toSidebarDocItemLinkProp>[0];
|
||||||
|
type Result = ReturnType<typeof toSidebarDocItemLinkProp>;
|
||||||
|
type DocSidebarItem = Params['item'];
|
||||||
|
type Doc = Params['doc'];
|
||||||
|
|
||||||
|
const id = 'some-doc-id';
|
||||||
|
const unversionedId = 'some-unversioned-doc-id';
|
||||||
|
|
||||||
|
const item: DocSidebarItem = {
|
||||||
|
type: 'doc',
|
||||||
|
id,
|
||||||
|
label: 'doc sidebar item label',
|
||||||
|
};
|
||||||
|
|
||||||
|
const doc: Doc = {
|
||||||
|
id,
|
||||||
|
unversionedId,
|
||||||
|
title: 'doc title',
|
||||||
|
permalink: '/docPermalink',
|
||||||
|
frontMatter: {},
|
||||||
|
unlisted: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('works', () => {
|
||||||
|
const result = toSidebarDocItemLinkProp({
|
||||||
|
item,
|
||||||
|
doc,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'link',
|
||||||
|
docId: unversionedId,
|
||||||
|
unlisted: false,
|
||||||
|
label: item.label,
|
||||||
|
autoAddBaseUrl: undefined,
|
||||||
|
className: undefined,
|
||||||
|
href: doc.permalink,
|
||||||
|
customProps: undefined,
|
||||||
|
} as Result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses unlisted from metadata and ignores frontMatter', () => {
|
||||||
|
expect(
|
||||||
|
toSidebarDocItemLinkProp({
|
||||||
|
item,
|
||||||
|
doc: {
|
||||||
|
...doc,
|
||||||
|
unlisted: true,
|
||||||
|
frontMatter: {
|
||||||
|
unlisted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).unlisted,
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
toSidebarDocItemLinkProp({
|
||||||
|
item,
|
||||||
|
doc: {
|
||||||
|
...doc,
|
||||||
|
unlisted: false,
|
||||||
|
frontMatter: {
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).unlisted,
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -37,7 +37,8 @@ export type GlobalDoc = {
|
||||||
*/
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
path: string;
|
path: string;
|
||||||
sidebar: string | undefined;
|
sidebar?: string;
|
||||||
|
unlisted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalVersion = {
|
export type GlobalVersion = {
|
||||||
|
|
|
@ -18,6 +18,8 @@ import {
|
||||||
posixPath,
|
posixPath,
|
||||||
Globby,
|
Globby,
|
||||||
normalizeFrontMatterTags,
|
normalizeFrontMatterTags,
|
||||||
|
isUnlisted,
|
||||||
|
isDraft,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
|
|
||||||
import {getFileLastUpdate} from './lastUpdate';
|
import {getFileLastUpdate} from './lastUpdate';
|
||||||
|
@ -35,7 +37,6 @@ import type {
|
||||||
PropNavigationLink,
|
PropNavigationLink,
|
||||||
LastUpdateData,
|
LastUpdateData,
|
||||||
VersionMetadata,
|
VersionMetadata,
|
||||||
DocFrontMatter,
|
|
||||||
LoadedVersion,
|
LoadedVersion,
|
||||||
FileChange,
|
FileChange,
|
||||||
} from '@docusaurus/plugin-content-docs';
|
} from '@docusaurus/plugin-content-docs';
|
||||||
|
@ -125,17 +126,6 @@ export async function readVersionDocs(
|
||||||
|
|
||||||
export type DocEnv = 'production' | 'development';
|
export type DocEnv = 'production' | 'development';
|
||||||
|
|
||||||
/** Docs with draft front matter are only considered draft in production. */
|
|
||||||
function isDraftForEnvironment({
|
|
||||||
env,
|
|
||||||
frontMatter,
|
|
||||||
}: {
|
|
||||||
frontMatter: DocFrontMatter;
|
|
||||||
env: DocEnv;
|
|
||||||
}): boolean {
|
|
||||||
return (env === 'production' && frontMatter.draft) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doProcessDocMetadata({
|
async function doProcessDocMetadata({
|
||||||
docFile,
|
docFile,
|
||||||
versionMetadata,
|
versionMetadata,
|
||||||
|
@ -268,7 +258,8 @@ async function doProcessDocMetadata({
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const draft = isDraftForEnvironment({env, frontMatter});
|
const draft = isDraft({env, frontMatter});
|
||||||
|
const unlisted = isUnlisted({env, frontMatter});
|
||||||
|
|
||||||
const formatDate = (locale: string, date: Date, calendar: string): string => {
|
const formatDate = (locale: string, date: Date, calendar: string): string => {
|
||||||
try {
|
try {
|
||||||
|
@ -299,6 +290,7 @@ async function doProcessDocMetadata({
|
||||||
slug: docSlug,
|
slug: docSlug,
|
||||||
permalink,
|
permalink,
|
||||||
draft,
|
draft,
|
||||||
|
unlisted,
|
||||||
editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
|
editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
|
||||||
tags: normalizeFrontMatterTags(versionMetadata.tagsPath, frontMatter.tags),
|
tags: normalizeFrontMatterTags(versionMetadata.tagsPath, frontMatter.tags),
|
||||||
version: versionMetadata.versionName,
|
version: versionMetadata.versionName,
|
||||||
|
@ -333,25 +325,32 @@ export async function processDocMetadata(args: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addDocNavigation(
|
function getUnlistedIds(docs: DocMetadataBase[]): Set<string> {
|
||||||
docsBase: DocMetadataBase[],
|
return new Set(docs.filter((doc) => doc.unlisted).map((doc) => doc.id));
|
||||||
sidebarsUtils: SidebarsUtils,
|
}
|
||||||
sidebarFilePath: string,
|
|
||||||
): LoadedVersion['docs'] {
|
|
||||||
const docsById = createDocsByIdIndex(docsBase);
|
|
||||||
|
|
||||||
sidebarsUtils.checkSidebarsDocIds(
|
export function addDocNavigation({
|
||||||
docsBase.flatMap(getDocIds),
|
docs,
|
||||||
sidebarFilePath,
|
sidebarsUtils,
|
||||||
);
|
sidebarFilePath,
|
||||||
|
}: {
|
||||||
|
docs: DocMetadataBase[];
|
||||||
|
sidebarsUtils: SidebarsUtils;
|
||||||
|
sidebarFilePath: string;
|
||||||
|
}): LoadedVersion['docs'] {
|
||||||
|
const docsById = createDocsByIdIndex(docs);
|
||||||
|
const unlistedIds = getUnlistedIds(docs);
|
||||||
|
|
||||||
|
sidebarsUtils.checkSidebarsDocIds(docs.flatMap(getDocIds), sidebarFilePath);
|
||||||
|
|
||||||
// Add sidebar/next/previous to the docs
|
// Add sidebar/next/previous to the docs
|
||||||
function addNavData(doc: DocMetadataBase): DocMetadata {
|
function addNavData(doc: DocMetadataBase): DocMetadata {
|
||||||
const navigation = sidebarsUtils.getDocNavigation(
|
const navigation = sidebarsUtils.getDocNavigation({
|
||||||
doc.unversionedId,
|
unversionedId: doc.unversionedId,
|
||||||
doc.id,
|
versionedId: doc.id,
|
||||||
doc.frontMatter.displayed_sidebar,
|
displayedSidebar: doc.frontMatter.displayed_sidebar,
|
||||||
);
|
unlistedIds,
|
||||||
|
});
|
||||||
|
|
||||||
const toNavigationLinkByDocId = (
|
const toNavigationLinkByDocId = (
|
||||||
docId: string | null | undefined,
|
docId: string | null | undefined,
|
||||||
|
@ -367,6 +366,10 @@ export function addDocNavigation(
|
||||||
`Error when loading ${doc.id} in ${doc.sourceDirName}: the pagination_${type} front matter points to a non-existent ID ${docId}.`,
|
`Error when loading ${doc.id} in ${doc.sourceDirName}: the pagination_${type} front matter points to a non-existent ID ${docId}.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Gracefully handle explicitly providing an unlisted doc ID in production
|
||||||
|
if (navDoc.unlisted) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
return toDocNavigationLink(navDoc);
|
return toDocNavigationLink(navDoc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -382,7 +385,7 @@ export function addDocNavigation(
|
||||||
return {...doc, sidebar: navigation.sidebarName, previous, next};
|
return {...doc, sidebar: navigation.sidebarName, previous, next};
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsWithNavigation = docsBase.map(addNavData);
|
const docsWithNavigation = docs.map(addNavData);
|
||||||
// Sort to ensure consistent output for tests
|
// Sort to ensure consistent output for tests
|
||||||
docsWithNavigation.sort((a, b) => a.id.localeCompare(b.id));
|
docsWithNavigation.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
return docsWithNavigation;
|
return docsWithNavigation;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
FrontMatterTagsSchema,
|
FrontMatterTagsSchema,
|
||||||
FrontMatterTOCHeadingLevels,
|
FrontMatterTOCHeadingLevels,
|
||||||
validateFrontMatter,
|
validateFrontMatter,
|
||||||
|
ContentVisibilitySchema,
|
||||||
} from '@docusaurus/utils-validation';
|
} from '@docusaurus/utils-validation';
|
||||||
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
|
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
|
@ -43,7 +44,6 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
||||||
parse_number_prefixes: Joi.boolean(),
|
parse_number_prefixes: Joi.boolean(),
|
||||||
pagination_next: Joi.string().allow(null),
|
pagination_next: Joi.string().allow(null),
|
||||||
pagination_prev: Joi.string().allow(null),
|
pagination_prev: Joi.string().allow(null),
|
||||||
draft: Joi.boolean(),
|
|
||||||
...FrontMatterTOCHeadingLevels,
|
...FrontMatterTOCHeadingLevels,
|
||||||
last_update: Joi.object({
|
last_update: Joi.object({
|
||||||
author: Joi.string(),
|
author: Joi.string(),
|
||||||
|
@ -54,7 +54,9 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
||||||
'object.missing': FrontMatterLastUpdateErrorMessage,
|
'object.missing': FrontMatterLastUpdateErrorMessage,
|
||||||
'object.base': FrontMatterLastUpdateErrorMessage,
|
'object.base': FrontMatterLastUpdateErrorMessage,
|
||||||
}),
|
}),
|
||||||
}).unknown();
|
})
|
||||||
|
.unknown()
|
||||||
|
.concat(ContentVisibilitySchema);
|
||||||
|
|
||||||
export function validateDocFrontMatter(frontMatter: {
|
export function validateDocFrontMatter(frontMatter: {
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
|
|
@ -23,6 +23,11 @@ function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
|
||||||
return {
|
return {
|
||||||
id: doc.unversionedId,
|
id: doc.unversionedId,
|
||||||
path: doc.permalink,
|
path: doc.permalink,
|
||||||
|
|
||||||
|
// optimize global data size: do not add unlisted: false/undefined
|
||||||
|
...(doc.unlisted && {unlisted: doc.unlisted}),
|
||||||
|
|
||||||
|
// TODO optimize size? remove attribute when no sidebar (breaking change?)
|
||||||
sidebar: doc.sidebar,
|
sidebar: doc.sidebar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,9 @@ export default async function pluginContentDocs(
|
||||||
const aliasedSource = (source: string) =>
|
const aliasedSource = (source: string) =>
|
||||||
`~docs/${posixPath(path.relative(pluginDataDirRoot, source))}`;
|
`~docs/${posixPath(path.relative(pluginDataDirRoot, source))}`;
|
||||||
|
|
||||||
|
// TODO env should be injected into all plugins
|
||||||
|
const env = process.env.NODE_ENV as DocEnv;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'docusaurus-plugin-content-docs',
|
name: 'docusaurus-plugin-content-docs',
|
||||||
|
|
||||||
|
@ -143,7 +146,7 @@ export default async function pluginContentDocs(
|
||||||
versionMetadata,
|
versionMetadata,
|
||||||
context,
|
context,
|
||||||
options,
|
options,
|
||||||
env: process.env.NODE_ENV as DocEnv,
|
env,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.all(docFiles.map(processVersionDoc));
|
return Promise.all(docFiles.map(processVersionDoc));
|
||||||
|
@ -156,6 +159,9 @@ export default async function pluginContentDocs(
|
||||||
versionMetadata,
|
versionMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO we only ever need draftIds in further code, not full draft items
|
||||||
|
// To simplify and prevent mistakes, avoid exposing draft
|
||||||
|
// replace draft=>draftIds in content loaded
|
||||||
const [drafts, docs] = _.partition(docsBase, (doc) => doc.draft);
|
const [drafts, docs] = _.partition(docsBase, (doc) => doc.draft);
|
||||||
|
|
||||||
const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
|
const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
|
||||||
|
@ -175,11 +181,11 @@ export default async function pluginContentDocs(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...versionMetadata,
|
...versionMetadata,
|
||||||
docs: addDocNavigation(
|
docs: addDocNavigation({
|
||||||
docs,
|
docs,
|
||||||
sidebarsUtils,
|
sidebarsUtils,
|
||||||
versionMetadata.sidebarFilePath as string,
|
sidebarFilePath: versionMetadata.sidebarFilePath as string,
|
||||||
),
|
}),
|
||||||
drafts,
|
drafts,
|
||||||
sidebars,
|
sidebars,
|
||||||
};
|
};
|
||||||
|
|
|
@ -398,6 +398,8 @@ declare module '@docusaurus/plugin-content-docs' {
|
||||||
pagination_prev?: string | null;
|
pagination_prev?: string | null;
|
||||||
/** Should this doc be excluded from production builds? */
|
/** Should this doc be excluded from production builds? */
|
||||||
draft?: boolean;
|
draft?: boolean;
|
||||||
|
/** Should this doc be accessible but hidden in production builds? */
|
||||||
|
unlisted?: boolean;
|
||||||
/** Allows overriding the last updated author and/or date. */
|
/** Allows overriding the last updated author and/or date. */
|
||||||
last_update?: FileChange;
|
last_update?: FileChange;
|
||||||
};
|
};
|
||||||
|
@ -448,6 +450,11 @@ declare module '@docusaurus/plugin-content-docs' {
|
||||||
* Draft docs will be excluded for production environment.
|
* Draft docs will be excluded for production environment.
|
||||||
*/
|
*/
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
|
/**
|
||||||
|
* Unlisted docs are accessible when directly visible, but will be hidden
|
||||||
|
* from the sidebar and pagination in production.
|
||||||
|
*/
|
||||||
|
unlisted: boolean;
|
||||||
/**
|
/**
|
||||||
* Position in an autogenerated sidebar slice, acquired through front matter
|
* Position in an autogenerated sidebar slice, acquired through front matter
|
||||||
* or number prefix.
|
* or number prefix.
|
||||||
|
|
|
@ -28,6 +28,37 @@ import type {
|
||||||
LoadedVersion,
|
LoadedVersion,
|
||||||
} from '@docusaurus/plugin-content-docs';
|
} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
|
export function toSidebarDocItemLinkProp({
|
||||||
|
item,
|
||||||
|
doc,
|
||||||
|
}: {
|
||||||
|
item: SidebarItemDoc;
|
||||||
|
doc: Pick<
|
||||||
|
DocMetadata,
|
||||||
|
'id' | 'title' | 'permalink' | 'unlisted' | 'frontMatter' | 'unversionedId'
|
||||||
|
>;
|
||||||
|
}): PropSidebarItemLink {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
permalink,
|
||||||
|
frontMatter: {
|
||||||
|
sidebar_label: sidebarLabel,
|
||||||
|
sidebar_custom_props: customProps,
|
||||||
|
},
|
||||||
|
unlisted,
|
||||||
|
unversionedId,
|
||||||
|
} = doc;
|
||||||
|
return {
|
||||||
|
type: 'link',
|
||||||
|
label: sidebarLabel ?? item.label ?? title,
|
||||||
|
href: permalink,
|
||||||
|
className: item.className,
|
||||||
|
customProps: item.customProps ?? customProps,
|
||||||
|
docId: unversionedId,
|
||||||
|
unlisted,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars {
|
export function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars {
|
||||||
const docsById = createDocsByIdIndex(loadedVersion.docs);
|
const docsById = createDocsByIdIndex(loadedVersion.docs);
|
||||||
|
|
||||||
|
@ -44,21 +75,8 @@ Available document ids are:
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertDocLink = (item: SidebarItemDoc): PropSidebarItemLink => {
|
const convertDocLink = (item: SidebarItemDoc): PropSidebarItemLink => {
|
||||||
const docMetadata = getDocById(item.id);
|
const doc = getDocById(item.id);
|
||||||
const {
|
return toSidebarDocItemLinkProp({item, doc});
|
||||||
title,
|
|
||||||
permalink,
|
|
||||||
frontMatter: {sidebar_label: sidebarLabel},
|
|
||||||
} = docMetadata;
|
|
||||||
return {
|
|
||||||
type: 'link',
|
|
||||||
label: sidebarLabel ?? item.label ?? title,
|
|
||||||
href: permalink,
|
|
||||||
className: item.className,
|
|
||||||
customProps:
|
|
||||||
item.customProps ?? docMetadata.frontMatter.sidebar_custom_props,
|
|
||||||
docId: docMetadata.unversionedId,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCategoryLinkHref(
|
function getCategoryLinkHref(
|
||||||
|
@ -74,6 +92,15 @@ Available document ids are:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCategoryLinkUnlisted(
|
||||||
|
link: SidebarItemCategoryLink | undefined,
|
||||||
|
): boolean {
|
||||||
|
if (link?.type === 'doc') {
|
||||||
|
return getDocById(link.id).unlisted;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function getCategoryLinkCustomProps(
|
function getCategoryLinkCustomProps(
|
||||||
link: SidebarItemCategoryLink | undefined,
|
link: SidebarItemCategoryLink | undefined,
|
||||||
) {
|
) {
|
||||||
|
@ -88,12 +115,14 @@ Available document ids are:
|
||||||
function convertCategory(item: SidebarItemCategory): PropSidebarItemCategory {
|
function convertCategory(item: SidebarItemCategory): PropSidebarItemCategory {
|
||||||
const {link, ...rest} = item;
|
const {link, ...rest} = item;
|
||||||
const href = getCategoryLinkHref(link);
|
const href = getCategoryLinkHref(link);
|
||||||
|
const linkUnlisted = getCategoryLinkUnlisted(link);
|
||||||
const customProps = item.customProps ?? getCategoryLinkCustomProps(link);
|
const customProps = item.customProps ?? getCategoryLinkCustomProps(link);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
items: item.items.map(normalizeItem),
|
items: item.items.map(normalizeItem),
|
||||||
...(href && {href}),
|
...(href && {href}),
|
||||||
|
...(linkUnlisted && {linkUnlisted}),
|
||||||
...(customProps && {customProps}),
|
...(customProps && {customProps}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -180,15 +209,18 @@ export function toTagDocListProp({
|
||||||
allTagsPath,
|
allTagsPath,
|
||||||
count: tag.docIds.length,
|
count: tag.docIds.length,
|
||||||
items: toDocListProp(),
|
items: toDocListProp(),
|
||||||
|
unlisted: tag.unlisted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toTagsListTagsProp(
|
export function toTagsListTagsProp(
|
||||||
versionTags: VersionTags,
|
versionTags: VersionTags,
|
||||||
): PropTagsListPage['tags'] {
|
): PropTagsListPage['tags'] {
|
||||||
return Object.values(versionTags).map((tagValue) => ({
|
return Object.values(versionTags)
|
||||||
label: tagValue.label,
|
.filter((tagValue) => !tagValue.unlisted)
|
||||||
permalink: tagValue.permalink,
|
.map((tagValue) => ({
|
||||||
count: tagValue.docIds.length,
|
label: tagValue.label,
|
||||||
}));
|
permalink: tagValue.permalink,
|
||||||
|
count: tagValue.docIds.length,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,14 @@ describe('createSidebarsUtils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getDocNavigation', () => {
|
it('getDocNavigation', () => {
|
||||||
expect(getDocNavigation('doc1', 'doc1', undefined)).toEqual({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc1',
|
||||||
|
versionedId: 'doc1',
|
||||||
|
displayedSidebar: undefined,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
sidebarName: 'sidebar1',
|
sidebarName: 'sidebar1',
|
||||||
previous: undefined,
|
previous: undefined,
|
||||||
next: {
|
next: {
|
||||||
|
@ -161,7 +168,14 @@ describe('createSidebarsUtils', () => {
|
||||||
id: 'doc2',
|
id: 'doc2',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(getDocNavigation('doc2', 'doc2', undefined)).toEqual({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc2',
|
||||||
|
versionedId: 'doc2',
|
||||||
|
displayedSidebar: undefined,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
sidebarName: 'sidebar1',
|
sidebarName: 'sidebar1',
|
||||||
previous: {
|
previous: {
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
|
@ -170,7 +184,14 @@ describe('createSidebarsUtils', () => {
|
||||||
next: undefined,
|
next: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getDocNavigation('doc3', 'doc3', undefined)).toEqual({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc3',
|
||||||
|
versionedId: 'doc3',
|
||||||
|
displayedSidebar: undefined,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
sidebarName: 'sidebar2',
|
sidebarName: 'sidebar2',
|
||||||
previous: undefined,
|
previous: undefined,
|
||||||
next: {
|
next: {
|
||||||
|
@ -178,7 +199,14 @@ describe('createSidebarsUtils', () => {
|
||||||
id: 'doc4',
|
id: 'doc4',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(getDocNavigation('doc4', 'doc4', undefined)).toEqual({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc4',
|
||||||
|
versionedId: 'doc4',
|
||||||
|
displayedSidebar: undefined,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
sidebarName: 'sidebar2',
|
sidebarName: 'sidebar2',
|
||||||
previous: {
|
previous: {
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
|
@ -188,7 +216,14 @@ describe('createSidebarsUtils', () => {
|
||||||
next: undefined,
|
next: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getDocNavigation('doc5', 'doc5', undefined)).toMatchObject({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc5',
|
||||||
|
versionedId: 'doc5',
|
||||||
|
displayedSidebar: undefined,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
sidebarName: 'sidebar3',
|
sidebarName: 'sidebar3',
|
||||||
previous: undefined,
|
previous: undefined,
|
||||||
next: {
|
next: {
|
||||||
|
@ -196,7 +231,14 @@ describe('createSidebarsUtils', () => {
|
||||||
label: 'S3 SubCategory',
|
label: 'S3 SubCategory',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(getDocNavigation('doc6', 'doc6', undefined)).toMatchObject({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc6',
|
||||||
|
versionedId: 'doc6',
|
||||||
|
displayedSidebar: undefined,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
sidebarName: 'sidebar3',
|
sidebarName: 'sidebar3',
|
||||||
previous: {
|
previous: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
@ -207,7 +249,14 @@ describe('createSidebarsUtils', () => {
|
||||||
id: 'doc7',
|
id: 'doc7',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(getDocNavigation('doc7', 'doc7', undefined)).toEqual({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc7',
|
||||||
|
versionedId: 'doc7',
|
||||||
|
displayedSidebar: undefined,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
sidebarName: 'sidebar3',
|
sidebarName: 'sidebar3',
|
||||||
previous: {
|
previous: {
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
|
@ -215,17 +264,36 @@ describe('createSidebarsUtils', () => {
|
||||||
},
|
},
|
||||||
next: undefined,
|
next: undefined,
|
||||||
});
|
});
|
||||||
expect(getDocNavigation('doc3', 'doc3', null)).toEqual({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc3',
|
||||||
|
versionedId: 'doc3',
|
||||||
|
displayedSidebar: null,
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
sidebarName: undefined,
|
sidebarName: undefined,
|
||||||
previous: undefined,
|
previous: undefined,
|
||||||
next: undefined,
|
next: undefined,
|
||||||
});
|
});
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getDocNavigation('doc3', 'doc3', 'foo'),
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc3',
|
||||||
|
versionedId: 'doc3',
|
||||||
|
displayedSidebar: 'foo',
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Doc with ID doc3 wants to display sidebar foo but a sidebar with this name doesn't exist"`,
|
`"Doc with ID doc3 wants to display sidebar foo but a sidebar with this name doesn't exist"`,
|
||||||
);
|
);
|
||||||
expect(getDocNavigation('doc3', 'doc3', 'sidebar1')).toEqual({
|
expect(
|
||||||
|
getDocNavigation({
|
||||||
|
unversionedId: 'doc3',
|
||||||
|
versionedId: 'doc3',
|
||||||
|
displayedSidebar: 'sidebar1',
|
||||||
|
unlistedIds: new Set(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
sidebarName: 'sidebar1',
|
sidebarName: 'sidebar1',
|
||||||
previous: undefined,
|
previous: undefined,
|
||||||
next: undefined,
|
next: undefined,
|
||||||
|
|
|
@ -183,11 +183,18 @@ export type PropSidebarItemCategory = Expand<
|
||||||
SidebarItemCategoryBase & {
|
SidebarItemCategoryBase & {
|
||||||
items: PropSidebarItem[];
|
items: PropSidebarItem[];
|
||||||
href?: string;
|
href?: string;
|
||||||
|
|
||||||
|
// Weird name => it would have been more convenient to have link.unlisted
|
||||||
|
// Note it is the category link that is unlisted, not the category itself
|
||||||
|
// We want to prevent users from clicking on an unlisted category link
|
||||||
|
// We can't use "href: undefined" otherwise sidebar item is not highlighted
|
||||||
|
linkUnlisted?: boolean;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type PropSidebarItemLink = SidebarItemLink & {
|
export type PropSidebarItemLink = SidebarItemLink & {
|
||||||
docId?: string;
|
docId?: string;
|
||||||
|
unlisted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropSidebarItemHtml = SidebarItemHtml;
|
export type PropSidebarItemHtml = SidebarItemHtml;
|
||||||
|
|
|
@ -135,11 +135,12 @@ export type SidebarsUtils = {
|
||||||
sidebars: Sidebars;
|
sidebars: Sidebars;
|
||||||
getFirstDocIdOfFirstSidebar: () => string | undefined;
|
getFirstDocIdOfFirstSidebar: () => string | undefined;
|
||||||
getSidebarNameByDocId: (docId: string) => string | undefined;
|
getSidebarNameByDocId: (docId: string) => string | undefined;
|
||||||
getDocNavigation: (
|
getDocNavigation: (params: {
|
||||||
unversionedId: string,
|
unversionedId: string;
|
||||||
versionedId: string,
|
versionedId: string;
|
||||||
displayedSidebar: string | null | undefined,
|
displayedSidebar: string | null | undefined;
|
||||||
) => SidebarNavigation;
|
unlistedIds: Set<string>;
|
||||||
|
}) => SidebarNavigation;
|
||||||
getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
|
getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
|
||||||
getCategoryGeneratedIndexNavigation: (
|
getCategoryGeneratedIndexNavigation: (
|
||||||
categoryGeneratedIndexPermalink: string,
|
categoryGeneratedIndexPermalink: string,
|
||||||
|
@ -192,11 +193,17 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDocNavigation(
|
function getDocNavigation({
|
||||||
unversionedId: string,
|
unversionedId,
|
||||||
versionedId: string,
|
versionedId,
|
||||||
displayedSidebar: string | null | undefined,
|
displayedSidebar,
|
||||||
): SidebarNavigation {
|
unlistedIds,
|
||||||
|
}: {
|
||||||
|
unversionedId: string;
|
||||||
|
versionedId: string;
|
||||||
|
displayedSidebar: string | null | undefined;
|
||||||
|
unlistedIds: Set<string>;
|
||||||
|
}): SidebarNavigation {
|
||||||
// TODO legacy id retro-compatibility!
|
// TODO legacy id retro-compatibility!
|
||||||
let docId = unversionedId;
|
let docId = unversionedId;
|
||||||
let sidebarName =
|
let sidebarName =
|
||||||
|
@ -211,12 +218,28 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils {
|
||||||
if (!sidebarName) {
|
if (!sidebarName) {
|
||||||
return emptySidebarNavigation();
|
return emptySidebarNavigation();
|
||||||
}
|
}
|
||||||
const navigationItems = sidebarNameToNavigationItems[sidebarName];
|
let navigationItems = sidebarNameToNavigationItems[sidebarName];
|
||||||
if (!navigationItems) {
|
if (!navigationItems) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`,
|
`Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter unlisted items from navigation
|
||||||
|
navigationItems = navigationItems.filter((item) => {
|
||||||
|
if (item.type === 'doc' && unlistedIds.has(item.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
item.type === 'category' &&
|
||||||
|
item.link.type === 'doc' &&
|
||||||
|
unlistedIds.has(item.link.id)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
const currentItemIndex = navigationItems.findIndex((item) => {
|
const currentItemIndex = navigationItems.findIndex((item) => {
|
||||||
if (item.type === 'doc') {
|
if (item.type === 'doc') {
|
||||||
return item.id === docId;
|
return item.id === docId;
|
||||||
|
|
|
@ -6,15 +6,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {groupTaggedItems} from '@docusaurus/utils';
|
import {getTagVisibility, groupTaggedItems} from '@docusaurus/utils';
|
||||||
import type {VersionTags} from './types';
|
import type {VersionTags} from './types';
|
||||||
import type {DocMetadata} from '@docusaurus/plugin-content-docs';
|
import type {DocMetadata} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
export function getVersionTags(docs: DocMetadata[]): VersionTags {
|
export function getVersionTags(docs: DocMetadata[]): VersionTags {
|
||||||
const groups = groupTaggedItems(docs, (doc) => doc.tags);
|
const groups = groupTaggedItems(docs, (doc) => doc.tags);
|
||||||
return _.mapValues(groups, (group) => ({
|
return _.mapValues(groups, ({tag, items: tagDocs}) => {
|
||||||
label: group.tag.label,
|
const tagVisibility = getTagVisibility({
|
||||||
docIds: group.items.map((item) => item.id),
|
items: tagDocs,
|
||||||
permalink: group.tag.permalink,
|
isUnlisted: (item) => item.unlisted,
|
||||||
}));
|
});
|
||||||
|
return {
|
||||||
|
label: tag.label,
|
||||||
|
docIds: tagVisibility.listedItems.map((item) => item.id),
|
||||||
|
permalink: tag.permalink,
|
||||||
|
unlisted: tagVisibility.unlisted,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ export type SourceToPermalink = {
|
||||||
export type VersionTag = Tag & {
|
export type VersionTag = Tag & {
|
||||||
/** All doc ids having this tag. */
|
/** All doc ids having this tag. */
|
||||||
docIds: string[];
|
docIds: string[];
|
||||||
|
unlisted: boolean;
|
||||||
};
|
};
|
||||||
export type VersionTags = {
|
export type VersionTags = {
|
||||||
[permalink: string]: VersionTag;
|
[permalink: string]: VersionTag;
|
||||||
|
|
|
@ -19,6 +19,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
|
||||||
"source": "@site/src/pages/hello/index.md",
|
"source": "@site/src/pages/hello/index.md",
|
||||||
"title": "Index",
|
"title": "Index",
|
||||||
"type": "mdx",
|
"type": "mdx",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "my MDX page",
|
"description": "my MDX page",
|
||||||
|
@ -30,6 +31,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
|
||||||
"source": "@site/src/pages/hello/mdxPage.mdx",
|
"source": "@site/src/pages/hello/mdxPage.mdx",
|
||||||
"title": "MDX page",
|
"title": "MDX page",
|
||||||
"type": "mdx",
|
"type": "mdx",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"permalink": "/hello/translatedJs",
|
"permalink": "/hello/translatedJs",
|
||||||
|
@ -43,6 +45,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
|
||||||
"source": "@site/src/pages/hello/translatedMd.md",
|
"source": "@site/src/pages/hello/translatedMd.md",
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
"type": "mdx",
|
"type": "mdx",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"permalink": "/hello/world",
|
"permalink": "/hello/world",
|
||||||
|
@ -71,6 +74,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
|
||||||
"source": "@site/src/pages/hello/index.md",
|
"source": "@site/src/pages/hello/index.md",
|
||||||
"title": "Index",
|
"title": "Index",
|
||||||
"type": "mdx",
|
"type": "mdx",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "my MDX page",
|
"description": "my MDX page",
|
||||||
|
@ -82,6 +86,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
|
||||||
"source": "@site/src/pages/hello/mdxPage.mdx",
|
"source": "@site/src/pages/hello/mdxPage.mdx",
|
||||||
"title": "MDX page",
|
"title": "MDX page",
|
||||||
"type": "mdx",
|
"type": "mdx",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"permalink": "/fr/hello/translatedJs",
|
"permalink": "/fr/hello/translatedJs",
|
||||||
|
@ -95,6 +100,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
|
||||||
"source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md",
|
"source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md",
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
"type": "mdx",
|
"type": "mdx",
|
||||||
|
"unlisted": false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"permalink": "/fr/hello/world",
|
"permalink": "/fr/hello/world",
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Joi,
|
Joi,
|
||||||
validateFrontMatter,
|
validateFrontMatter,
|
||||||
FrontMatterTOCHeadingLevels,
|
FrontMatterTOCHeadingLevels,
|
||||||
|
ContentVisibilitySchema,
|
||||||
} from '@docusaurus/utils-validation';
|
} from '@docusaurus/utils-validation';
|
||||||
import type {FrontMatter} from '@docusaurus/plugin-content-pages';
|
import type {FrontMatter} from '@docusaurus/plugin-content-pages';
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ const PageFrontMatterSchema = Joi.object<FrontMatter>({
|
||||||
wrapperClassName: Joi.string(),
|
wrapperClassName: Joi.string(),
|
||||||
hide_table_of_contents: Joi.boolean(),
|
hide_table_of_contents: Joi.boolean(),
|
||||||
...FrontMatterTOCHeadingLevels,
|
...FrontMatterTOCHeadingLevels,
|
||||||
});
|
}).concat(ContentVisibilitySchema);
|
||||||
|
|
||||||
export function validatePageFrontMatter(frontMatter: {
|
export function validatePageFrontMatter(frontMatter: {
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
|
|
@ -20,6 +20,8 @@ import {
|
||||||
normalizeUrl,
|
normalizeUrl,
|
||||||
DEFAULT_PLUGIN_ID,
|
DEFAULT_PLUGIN_ID,
|
||||||
parseMarkdownString,
|
parseMarkdownString,
|
||||||
|
isUnlisted,
|
||||||
|
isDraft,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {validatePageFrontMatter} from './frontMatter';
|
import {validatePageFrontMatter} from './frontMatter';
|
||||||
|
|
||||||
|
@ -82,7 +84,9 @@ export default function pluginContentPages(
|
||||||
ignore: options.exclude,
|
ignore: options.exclude,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function toMetadata(relativeSource: string): Promise<Metadata> {
|
async function processPageSourceFile(
|
||||||
|
relativeSource: string,
|
||||||
|
): Promise<Metadata | undefined> {
|
||||||
// Lookup in localized folder in priority
|
// Lookup in localized folder in priority
|
||||||
const contentPath = await getFolderContainingFile(
|
const contentPath = await getFolderContainingFile(
|
||||||
getContentPathList(contentPaths),
|
getContentPathList(contentPaths),
|
||||||
|
@ -110,6 +114,12 @@ export default function pluginContentPages(
|
||||||
excerpt,
|
excerpt,
|
||||||
} = parseMarkdownString(content);
|
} = parseMarkdownString(content);
|
||||||
const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
|
const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
|
||||||
|
|
||||||
|
if (isDraft({frontMatter})) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const unlisted = isUnlisted({frontMatter});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'mdx',
|
type: 'mdx',
|
||||||
permalink,
|
permalink,
|
||||||
|
@ -117,10 +127,24 @@ export default function pluginContentPages(
|
||||||
title: frontMatter.title ?? contentTitle,
|
title: frontMatter.title ?? contentTitle,
|
||||||
description: frontMatter.description ?? excerpt,
|
description: frontMatter.description ?? excerpt,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
|
unlisted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(pagesFiles.map(toMetadata));
|
async function doProcessPageSourceFile(relativeSource: string) {
|
||||||
|
try {
|
||||||
|
return await processPageSourceFile(relativeSource);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(
|
||||||
|
`Processing of page source file path=${relativeSource} failed.`,
|
||||||
|
{cause: err as Error},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
await Promise.all(pagesFiles.map(doProcessPageSourceFile))
|
||||||
|
).filter(Boolean) as Metadata[];
|
||||||
},
|
},
|
||||||
|
|
||||||
async contentLoaded({content, actions}) {
|
async contentLoaded({content, actions}) {
|
||||||
|
|
|
@ -27,6 +27,8 @@ declare module '@docusaurus/plugin-content-pages' {
|
||||||
readonly hide_table_of_contents?: string;
|
readonly hide_table_of_contents?: string;
|
||||||
readonly toc_min_heading_level?: number;
|
readonly toc_min_heading_level?: number;
|
||||||
readonly toc_max_heading_level?: number;
|
readonly toc_max_heading_level?: number;
|
||||||
|
readonly draft?: boolean;
|
||||||
|
readonly unlisted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JSXPageMetadata = {
|
export type JSXPageMetadata = {
|
||||||
|
@ -42,6 +44,7 @@ declare module '@docusaurus/plugin-content-pages' {
|
||||||
frontMatter: FrontMatter & {[key: string]: unknown};
|
frontMatter: FrontMatter & {[key: string]: unknown};
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
unlisted: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Metadata = JSXPageMetadata | MDXPageMetadata;
|
export type Metadata = JSXPageMetadata | MDXPageMetadata;
|
||||||
|
|
|
@ -1494,6 +1494,14 @@ declare module '@theme/Tag' {
|
||||||
export default function Tag(props: Props): JSX.Element;
|
export default function Tag(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@theme/Unlisted' {
|
||||||
|
export interface Props {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Unlisted(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@theme/prism-include-languages' {
|
declare module '@theme/prism-include-languages' {
|
||||||
import type * as PrismNamespace from 'prismjs';
|
import type * as PrismNamespace from 'prismjs';
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import BlogPostPaginator from '@theme/BlogPostPaginator';
|
||||||
import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
|
import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
import type {Props} from '@theme/BlogPostPage';
|
import type {Props} from '@theme/BlogPostPage';
|
||||||
|
import Unlisted from '@theme/Unlisted';
|
||||||
import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
|
import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
function BlogPostPageContent({
|
function BlogPostPageContent({
|
||||||
|
@ -25,7 +26,7 @@ function BlogPostPageContent({
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const {metadata, toc} = useBlogPost();
|
const {metadata, toc} = useBlogPost();
|
||||||
const {nextItem, prevItem, frontMatter} = metadata;
|
const {nextItem, prevItem, frontMatter, unlisted} = metadata;
|
||||||
const {
|
const {
|
||||||
hide_table_of_contents: hideTableOfContents,
|
hide_table_of_contents: hideTableOfContents,
|
||||||
toc_min_heading_level: tocMinHeadingLevel,
|
toc_min_heading_level: tocMinHeadingLevel,
|
||||||
|
@ -43,6 +44,7 @@ function BlogPostPageContent({
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}>
|
}>
|
||||||
|
{unlisted && <Unlisted />}
|
||||||
<BlogPostItem>{children}</BlogPostItem>
|
<BlogPostItem>{children}</BlogPostItem>
|
||||||
|
|
||||||
{(nextItem || prevItem) && (
|
{(nextItem || prevItem) && (
|
||||||
|
|
|
@ -9,11 +9,13 @@ import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import {translate} from '@docusaurus/Translate';
|
import {translate} from '@docusaurus/Translate';
|
||||||
|
import {useVisibleBlogSidebarItems} from '@docusaurus/theme-common/internal';
|
||||||
import type {Props} from '@theme/BlogSidebar/Desktop';
|
import type {Props} from '@theme/BlogSidebar/Desktop';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function BlogSidebarDesktop({sidebar}: Props): JSX.Element {
|
export default function BlogSidebarDesktop({sidebar}: Props): JSX.Element {
|
||||||
|
const items = useVisibleBlogSidebarItems(sidebar.items);
|
||||||
return (
|
return (
|
||||||
<aside className="col col--3">
|
<aside className="col col--3">
|
||||||
<nav
|
<nav
|
||||||
|
@ -27,7 +29,7 @@ export default function BlogSidebarDesktop({sidebar}: Props): JSX.Element {
|
||||||
{sidebar.title}
|
{sidebar.title}
|
||||||
</div>
|
</div>
|
||||||
<ul className={clsx(styles.sidebarItemList, 'clean-list')}>
|
<ul className={clsx(styles.sidebarItemList, 'clean-list')}>
|
||||||
{sidebar.items.map((item) => (
|
{items.map((item) => (
|
||||||
<li key={item.permalink} className={styles.sidebarItem}>
|
<li key={item.permalink} className={styles.sidebarItem}>
|
||||||
<Link
|
<Link
|
||||||
isNavLink
|
isNavLink
|
||||||
|
|
|
@ -7,13 +7,15 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
|
import {useVisibleBlogSidebarItems} from '@docusaurus/theme-common/internal';
|
||||||
import {NavbarSecondaryMenuFiller} from '@docusaurus/theme-common';
|
import {NavbarSecondaryMenuFiller} from '@docusaurus/theme-common';
|
||||||
import type {Props} from '@theme/BlogSidebar/Mobile';
|
import type {Props} from '@theme/BlogSidebar/Mobile';
|
||||||
|
|
||||||
function BlogSidebarMobileSecondaryMenu({sidebar}: Props): JSX.Element {
|
function BlogSidebarMobileSecondaryMenu({sidebar}: Props): JSX.Element {
|
||||||
|
const items = useVisibleBlogSidebarItems(sidebar.items);
|
||||||
return (
|
return (
|
||||||
<ul className="menu__list">
|
<ul className="menu__list">
|
||||||
{sidebar.items.map((item) => (
|
{items.map((item) => (
|
||||||
<li key={item.permalink} className="menu__list-item">
|
<li key={item.permalink} className="menu__list-item">
|
||||||
<Link
|
<Link
|
||||||
isNavLink
|
isNavLink
|
||||||
|
|
|
@ -20,6 +20,7 @@ import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
import SearchMetadata from '@theme/SearchMetadata';
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
import type {Props} from '@theme/BlogTagsPostsPage';
|
import type {Props} from '@theme/BlogTagsPostsPage';
|
||||||
import BlogPostItems from '@theme/BlogPostItems';
|
import BlogPostItems from '@theme/BlogPostItems';
|
||||||
|
import Unlisted from '@theme/Unlisted';
|
||||||
|
|
||||||
// Very simple pluralization: probably good enough for now
|
// Very simple pluralization: probably good enough for now
|
||||||
function useBlogPostsPlural() {
|
function useBlogPostsPlural() {
|
||||||
|
@ -70,9 +71,9 @@ function BlogTagsPostsPageContent({
|
||||||
const title = useBlogTagsPostsPageTitle(tag);
|
const title = useBlogTagsPostsPageTitle(tag);
|
||||||
return (
|
return (
|
||||||
<BlogLayout sidebar={sidebar}>
|
<BlogLayout sidebar={sidebar}>
|
||||||
|
{tag.unlisted && <Unlisted />}
|
||||||
<header className="margin-bottom--xl">
|
<header className="margin-bottom--xl">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
|
|
||||||
<Link href={tag.allTagsPath}>
|
<Link href={tag.allTagsPath}>
|
||||||
<Translate
|
<Translate
|
||||||
id="theme.tags.tagsPageLink"
|
id="theme.tags.tagsPageLink"
|
||||||
|
|
|
@ -123,13 +123,17 @@ export default function DocBreadcrumbs(): JSX.Element | null {
|
||||||
{homePageRoute && <HomeBreadcrumbItem />}
|
{homePageRoute && <HomeBreadcrumbItem />}
|
||||||
{breadcrumbs.map((item, idx) => {
|
{breadcrumbs.map((item, idx) => {
|
||||||
const isLast = idx === breadcrumbs.length - 1;
|
const isLast = idx === breadcrumbs.length - 1;
|
||||||
|
const href =
|
||||||
|
item.type === 'category' && item.linkUnlisted
|
||||||
|
? undefined
|
||||||
|
: item.href;
|
||||||
return (
|
return (
|
||||||
<BreadcrumbsItem
|
<BreadcrumbsItem
|
||||||
key={idx}
|
key={idx}
|
||||||
active={isLast}
|
active={isLast}
|
||||||
index={idx}
|
index={idx}
|
||||||
addMicrodata={!!item.href}>
|
addMicrodata={!!href}>
|
||||||
<BreadcrumbsItemLink href={item.href} isLast={isLast}>
|
<BreadcrumbsItemLink href={href} isLast={isLast}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</BreadcrumbsItemLink>
|
</BreadcrumbsItemLink>
|
||||||
</BreadcrumbsItem>
|
</BreadcrumbsItem>
|
||||||
|
|
|
@ -17,6 +17,7 @@ import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
|
||||||
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
|
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
|
||||||
import DocItemContent from '@theme/DocItem/Content';
|
import DocItemContent from '@theme/DocItem/Content';
|
||||||
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
|
||||||
|
import Unlisted from '@theme/Unlisted';
|
||||||
import type {Props} from '@theme/DocItem/Layout';
|
import type {Props} from '@theme/DocItem/Layout';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
@ -47,9 +48,13 @@ function useDocTOC() {
|
||||||
|
|
||||||
export default function DocItemLayout({children}: Props): JSX.Element {
|
export default function DocItemLayout({children}: Props): JSX.Element {
|
||||||
const docTOC = useDocTOC();
|
const docTOC = useDocTOC();
|
||||||
|
const {
|
||||||
|
metadata: {unlisted},
|
||||||
|
} = useDoc();
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
|
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
|
||||||
|
{unlisted && <Unlisted />}
|
||||||
<DocVersionBanner />
|
<DocVersionBanner />
|
||||||
<div className={styles.docItemContainer}>
|
<div className={styles.docItemContainer}>
|
||||||
<article>
|
<article>
|
||||||
|
|
|
@ -59,7 +59,7 @@ function useCategoryHrefWithSSRFallback(
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const isBrowser = useIsBrowser();
|
const isBrowser = useIsBrowser();
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (item.href) {
|
if (item.href && !item.linkUnlisted) {
|
||||||
return item.href;
|
return item.href;
|
||||||
}
|
}
|
||||||
// In these cases, it's not necessary to render a fallback
|
// In these cases, it's not necessary to render a fallback
|
||||||
|
|
|
@ -6,17 +6,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {memo} from 'react';
|
import React, {memo} from 'react';
|
||||||
import {DocSidebarItemsExpandedStateProvider} from '@docusaurus/theme-common/internal';
|
import {
|
||||||
|
DocSidebarItemsExpandedStateProvider,
|
||||||
|
useVisibleSidebarItems,
|
||||||
|
} from '@docusaurus/theme-common/internal';
|
||||||
import DocSidebarItem from '@theme/DocSidebarItem';
|
import DocSidebarItem from '@theme/DocSidebarItem';
|
||||||
|
|
||||||
import type {Props} from '@theme/DocSidebarItems';
|
import type {Props} from '@theme/DocSidebarItems';
|
||||||
|
|
||||||
// TODO this item should probably not receive the "activePath" props
|
|
||||||
// TODO this triggers whole sidebar re-renders on navigation
|
|
||||||
function DocSidebarItems({items, ...props}: Props): JSX.Element {
|
function DocSidebarItems({items, ...props}: Props): JSX.Element {
|
||||||
|
const visibleItems = useVisibleSidebarItems(items, props.activePath);
|
||||||
return (
|
return (
|
||||||
<DocSidebarItemsExpandedStateProvider>
|
<DocSidebarItemsExpandedStateProvider>
|
||||||
{items.map((item, index) => (
|
{visibleItems.map((item, index) => (
|
||||||
<DocSidebarItem key={index} item={item} index={index} {...props} />
|
<DocSidebarItem key={index} item={item} index={index} {...props} />
|
||||||
))}
|
))}
|
||||||
</DocSidebarItemsExpandedStateProvider>
|
</DocSidebarItemsExpandedStateProvider>
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
import Translate, {translate} from '@docusaurus/Translate';
|
import Translate, {translate} from '@docusaurus/Translate';
|
||||||
import SearchMetadata from '@theme/SearchMetadata';
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
import type {Props} from '@theme/DocTagDocListPage';
|
import type {Props} from '@theme/DocTagDocListPage';
|
||||||
|
import Unlisted from '@theme/Unlisted';
|
||||||
|
|
||||||
// Very simple pluralization: probably good enough for now
|
// Very simple pluralization: probably good enough for now
|
||||||
function useNDocsTaggedPlural() {
|
function useNDocsTaggedPlural() {
|
||||||
|
@ -80,6 +81,7 @@ function DocTagDocListPageContent({
|
||||||
<div className="container margin-vert--lg">
|
<div className="container margin-vert--lg">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<main className="col col--8 col--offset-2">
|
<main className="col col--8 col--offset-2">
|
||||||
|
{tag.unlisted && <Unlisted />}
|
||||||
<header className="margin-bottom--xl">
|
<header className="margin-bottom--xl">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
<Link href={tag.allTagsPath}>
|
<Link href={tag.allTagsPath}>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import MDXContent from '@theme/MDXContent';
|
import MDXContent from '@theme/MDXContent';
|
||||||
import TOC from '@theme/TOC';
|
import TOC from '@theme/TOC';
|
||||||
|
import Unlisted from '@theme/Unlisted';
|
||||||
import type {Props} from '@theme/MDXPage';
|
import type {Props} from '@theme/MDXPage';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
@ -22,7 +23,7 @@ import styles from './styles.module.css';
|
||||||
export default function MDXPage(props: Props): JSX.Element {
|
export default function MDXPage(props: Props): JSX.Element {
|
||||||
const {content: MDXPageContent} = props;
|
const {content: MDXPageContent} = props;
|
||||||
const {
|
const {
|
||||||
metadata: {title, description, frontMatter},
|
metadata: {title, description, frontMatter, unlisted},
|
||||||
} = MDXPageContent;
|
} = MDXPageContent;
|
||||||
const {wrapperClassName, hide_table_of_contents: hideTableOfContents} =
|
const {wrapperClassName, hide_table_of_contents: hideTableOfContents} =
|
||||||
frontMatter;
|
frontMatter;
|
||||||
|
@ -38,6 +39,7 @@ export default function MDXPage(props: Props): JSX.Element {
|
||||||
<main className="container container--fluid margin-vert--lg">
|
<main className="container container--fluid margin-vert--lg">
|
||||||
<div className={clsx('row', styles.mdxPageWrapper)}>
|
<div className={clsx('row', styles.mdxPageWrapper)}>
|
||||||
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
<div className={clsx('col', !hideTableOfContents && 'col--8')}>
|
||||||
|
{unlisted && <Unlisted />}
|
||||||
<article>
|
<article>
|
||||||
<MDXContent>
|
<MDXContent>
|
||||||
<MDXPageContent />
|
<MDXPageContent />
|
||||||
|
|
|
@ -19,9 +19,10 @@ export default function DocNavbarItem({
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
const {activeDoc} = useActiveDocContext(docsPluginId);
|
const {activeDoc} = useActiveDocContext(docsPluginId);
|
||||||
const doc = useLayoutDoc(docId, docsPluginId);
|
const doc = useLayoutDoc(docId, docsPluginId);
|
||||||
|
const pageActive = activeDoc?.path === doc?.path;
|
||||||
|
|
||||||
// Draft items are not displayed in the navbar.
|
// Draft and unlisted items are not displayed in the navbar.
|
||||||
if (doc === null) {
|
if (doc === null || (doc.unlisted && !pageActive)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ export default function DocNavbarItem({
|
||||||
exact
|
exact
|
||||||
{...props}
|
{...props}
|
||||||
isActive={() =>
|
isActive={() =>
|
||||||
activeDoc?.path === doc.path ||
|
pageActive ||
|
||||||
(!!activeDoc?.sidebar && activeDoc.sidebar === doc.sidebar)
|
(!!activeDoc?.sidebar && activeDoc.sidebar === doc.sidebar)
|
||||||
}
|
}
|
||||||
label={staticLabel ?? doc.id}
|
label={staticLabel ?? doc.id}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* 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 React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import {
|
||||||
|
ThemeClassNames,
|
||||||
|
UnlistedBannerTitle,
|
||||||
|
UnlistedBannerMessage,
|
||||||
|
UnlistedMetadata,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
|
import Admonition from '@theme/Admonition';
|
||||||
|
import type {Props} from '@theme/Unlisted';
|
||||||
|
|
||||||
|
function UnlistedBanner({className}: Props) {
|
||||||
|
return (
|
||||||
|
<Admonition
|
||||||
|
type="caution"
|
||||||
|
title={<UnlistedBannerTitle />}
|
||||||
|
className={clsx(className, ThemeClassNames.common.unlistedBanner)}>
|
||||||
|
<UnlistedBannerMessage />
|
||||||
|
</Admonition>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Unlisted(props: Props): JSX.Element | null {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/*
|
||||||
|
Unlisted metadata declared here for simplicity.
|
||||||
|
Ensures we never forget to add the correct noindex metadata.
|
||||||
|
Also gives a central place for user to swizzle override default metadata.
|
||||||
|
*/}
|
||||||
|
<UnlistedMetadata />
|
||||||
|
<UnlistedBanner {...props} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -86,4 +86,10 @@ export {
|
||||||
SkipToContentLink,
|
SkipToContentLink,
|
||||||
} from './utils/skipToContentUtils';
|
} from './utils/skipToContentUtils';
|
||||||
|
|
||||||
|
export {
|
||||||
|
UnlistedBannerTitle,
|
||||||
|
UnlistedBannerMessage,
|
||||||
|
UnlistedMetadata,
|
||||||
|
} from './utils/unlistedUtils';
|
||||||
|
|
||||||
export {ErrorBoundaryTryAgainButton} from './utils/errorBoundaryUtils';
|
export {ErrorBoundaryTryAgainButton} from './utils/errorBoundaryUtils';
|
||||||
|
|
|
@ -69,6 +69,8 @@ export {
|
||||||
findSidebarCategory,
|
findSidebarCategory,
|
||||||
findFirstCategoryLink,
|
findFirstCategoryLink,
|
||||||
isActiveSidebarItem,
|
isActiveSidebarItem,
|
||||||
|
isVisibleSidebarItem,
|
||||||
|
useVisibleSidebarItems,
|
||||||
useSidebarBreadcrumbs,
|
useSidebarBreadcrumbs,
|
||||||
useDocsVersionCandidates,
|
useDocsVersionCandidates,
|
||||||
useLayoutDoc,
|
useLayoutDoc,
|
||||||
|
@ -109,6 +111,8 @@ export {
|
||||||
type TOCHighlightConfig,
|
type TOCHighlightConfig,
|
||||||
} from './hooks/useTOCHighlight';
|
} from './hooks/useTOCHighlight';
|
||||||
|
|
||||||
|
export {useVisibleBlogSidebarItems} from './utils/blogUtils';
|
||||||
|
|
||||||
export {useHideableNavbar} from './hooks/useHideableNavbar';
|
export {useHideableNavbar} from './hooks/useHideableNavbar';
|
||||||
export {
|
export {
|
||||||
useKeyboardNavigation,
|
useKeyboardNavigation,
|
||||||
|
|
|
@ -40,6 +40,8 @@ export const ThemeClassNames = {
|
||||||
backToTopButton: 'theme-back-to-top-button',
|
backToTopButton: 'theme-back-to-top-button',
|
||||||
codeBlock: 'theme-code-block',
|
codeBlock: 'theme-code-block',
|
||||||
admonition: 'theme-admonition',
|
admonition: 'theme-admonition',
|
||||||
|
unlistedBanner: 'theme-unlisted-banner',
|
||||||
|
|
||||||
admonitionType: (type: string) => `theme-admonition-${type}`,
|
admonitionType: (type: string) => `theme-admonition-${type}`,
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
findSidebarCategory,
|
findSidebarCategory,
|
||||||
useCurrentSidebarCategory,
|
useCurrentSidebarCategory,
|
||||||
useSidebarBreadcrumbs,
|
useSidebarBreadcrumbs,
|
||||||
|
isVisibleSidebarItem,
|
||||||
} from '../docsUtils';
|
} from '../docsUtils';
|
||||||
import {DocsSidebarProvider} from '../../contexts/docsSidebar';
|
import {DocsSidebarProvider} from '../../contexts/docsSidebar';
|
||||||
import {DocsVersionProvider} from '../../contexts/docsVersion';
|
import {DocsVersionProvider} from '../../contexts/docsVersion';
|
||||||
|
@ -293,6 +294,98 @@ describe('isActiveSidebarItem', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isVisibleSidebarItem', () => {
|
||||||
|
it('works with item', () => {
|
||||||
|
const item: PropSidebarItem = {
|
||||||
|
type: 'link',
|
||||||
|
href: '/itemPath',
|
||||||
|
label: 'Label',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(isVisibleSidebarItem(item, item.href)).toBe(true);
|
||||||
|
expect(isVisibleSidebarItem(item, '/nonexistentPath/')).toBe(true);
|
||||||
|
|
||||||
|
expect(isVisibleSidebarItem({...item, unlisted: false}, item.href)).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
isVisibleSidebarItem({...item, unlisted: undefined}, item.href),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(isVisibleSidebarItem({...item, unlisted: true}, item.href)).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
isVisibleSidebarItem({...item, unlisted: true}, '/nonexistentPath/'),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with category', () => {
|
||||||
|
const subCategoryAllUnlisted = testCategory({
|
||||||
|
href: '/sub-category-path',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/sub-sub-link-path',
|
||||||
|
label: 'Label',
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/sub-sub-link-path',
|
||||||
|
label: 'Label',
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
testCategory({
|
||||||
|
href: '/sub-sub-category-path',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/sub-sub-sub-link-path',
|
||||||
|
label: 'Label',
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
isVisibleSidebarItem(subCategoryAllUnlisted, '/nonexistentPath'),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
isVisibleSidebarItem(
|
||||||
|
subCategoryAllUnlisted,
|
||||||
|
subCategoryAllUnlisted.href!,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
isVisibleSidebarItem(subCategoryAllUnlisted, '/sub-sub-link-path'),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
isVisibleSidebarItem(subCategoryAllUnlisted, '/sub-sub-sub-link-path'),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
const categorySomeUnlisted = testCategory({
|
||||||
|
href: '/category-path',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/sub-link-path',
|
||||||
|
label: 'Label',
|
||||||
|
},
|
||||||
|
subCategoryAllUnlisted,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(isVisibleSidebarItem(categorySomeUnlisted, '/nonexistentPath')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
isVisibleSidebarItem(categorySomeUnlisted, categorySomeUnlisted.href!),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('useSidebarBreadcrumbs', () => {
|
describe('useSidebarBreadcrumbs', () => {
|
||||||
const createUseSidebarBreadcrumbsMock =
|
const createUseSidebarBreadcrumbsMock =
|
||||||
(sidebar: PropSidebar | undefined, breadcrumbsOption?: boolean) =>
|
(sidebar: PropSidebar | undefined, breadcrumbsOption?: boolean) =>
|
||||||
|
|
32
packages/docusaurus-theme-common/src/utils/blogUtils.ts
Normal file
32
packages/docusaurus-theme-common/src/utils/blogUtils.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* 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 {useMemo} from 'react';
|
||||||
|
import {useLocation} from '@docusaurus/router';
|
||||||
|
import {isSamePath} from './routesUtils';
|
||||||
|
import type {BlogSidebarItem} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
function isVisible(item: BlogSidebarItem, pathname: string): boolean {
|
||||||
|
if (item.unlisted && !isSamePath(item.permalink, pathname)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the visible blog sidebar items to display.
|
||||||
|
* Unlisted items are filtered.
|
||||||
|
*/
|
||||||
|
export function useVisibleBlogSidebarItems(
|
||||||
|
items: BlogSidebarItem[],
|
||||||
|
): BlogSidebarItem[] {
|
||||||
|
const {pathname} = useLocation();
|
||||||
|
return useMemo(
|
||||||
|
() => items.filter((item) => isVisible(item, pathname)),
|
||||||
|
[items, pathname],
|
||||||
|
);
|
||||||
|
}
|
|
@ -152,6 +152,34 @@ export function isActiveSidebarItem(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isVisibleSidebarItem(
|
||||||
|
item: PropSidebarItem,
|
||||||
|
activePath: string,
|
||||||
|
): boolean {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'category':
|
||||||
|
return (
|
||||||
|
isActiveSidebarItem(item, activePath) ||
|
||||||
|
item.items.some((subItem) => isVisibleSidebarItem(subItem, activePath))
|
||||||
|
);
|
||||||
|
case 'link':
|
||||||
|
// An unlisted item remains visible if it is active
|
||||||
|
return !item.unlisted || isActiveSidebarItem(item, activePath);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useVisibleSidebarItems(
|
||||||
|
items: readonly PropSidebarItem[],
|
||||||
|
activePath: string,
|
||||||
|
): PropSidebarItem[] {
|
||||||
|
return useMemo(
|
||||||
|
() => items.filter((item) => isVisibleSidebarItem(item, activePath)),
|
||||||
|
[items, activePath],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getSidebarBreadcrumbs(param: {
|
function getSidebarBreadcrumbs(param: {
|
||||||
sidebarItems: PropSidebar;
|
sidebarItems: PropSidebar;
|
||||||
pathname: string;
|
pathname: string;
|
||||||
|
|
39
packages/docusaurus-theme-common/src/utils/unlistedUtils.tsx
Normal file
39
packages/docusaurus-theme-common/src/utils/unlistedUtils.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* 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 React from 'react';
|
||||||
|
import Translate from '@docusaurus/Translate';
|
||||||
|
import Head from '@docusaurus/Head';
|
||||||
|
|
||||||
|
export function UnlistedBannerTitle(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Translate
|
||||||
|
id="theme.unlistedContent.title"
|
||||||
|
description="The unlisted content banner title">
|
||||||
|
Unlisted page
|
||||||
|
</Translate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UnlistedBannerMessage(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Translate
|
||||||
|
id="theme.unlistedContent.message"
|
||||||
|
description="The unlisted content banner message">
|
||||||
|
This page is unlisted. Search engines will not index it, and only users
|
||||||
|
having a direct link can access it.
|
||||||
|
</Translate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UnlistedMetadata(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Head>
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
</Head>
|
||||||
|
);
|
||||||
|
}
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "إصدارات",
|
"theme.navbar.mobileVersionsDropdown.label": "إصدارات",
|
||||||
"theme.tags.tagsListLabel": "الوسوم:",
|
"theme.tags.tagsListLabel": "الوسوم:",
|
||||||
"theme.tags.tagsPageLink": "عرض كل الوسوم",
|
"theme.tags.tagsPageLink": "عرض كل الوسوم",
|
||||||
"theme.tags.tagsPageTitle": "الوسوم"
|
"theme.tags.tagsPageTitle": "الوسوم",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"theme.ErrorPageContent.title": "This page crashed.",
|
"theme.ErrorPageContent.title": "This page crashed.",
|
||||||
"theme.ErrorPageContent.title___DESCRIPTION": "The title of the fallback page when the page crashed",
|
"theme.ErrorPageContent.title___DESCRIPTION": "The title of the fallback page when the page crashed",
|
||||||
"theme.ErrorPageContent.tryAgain": "Try again",
|
"theme.ErrorPageContent.tryAgain": "Try again",
|
||||||
"theme.ErrorPageContent.tryAgain___DESCRIPTION": "The label of the button to try again when the page crashed",
|
"theme.ErrorPageContent.tryAgain___DESCRIPTION": "The label of the button to try again rendering when the React error boundary captures an error",
|
||||||
"theme.NotFound.p1": "We could not find what you were looking for.",
|
"theme.NotFound.p1": "We could not find what you were looking for.",
|
||||||
"theme.NotFound.p1___DESCRIPTION": "The first paragraph of the 404 page",
|
"theme.NotFound.p1___DESCRIPTION": "The first paragraph of the 404 page",
|
||||||
"theme.NotFound.p2": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",
|
"theme.NotFound.p2": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",
|
||||||
|
@ -129,5 +129,9 @@
|
||||||
"theme.tags.tagsPageLink": "View All Tags",
|
"theme.tags.tagsPageLink": "View All Tags",
|
||||||
"theme.tags.tagsPageLink___DESCRIPTION": "The label of the link targeting the tag list page",
|
"theme.tags.tagsPageLink___DESCRIPTION": "The label of the link targeting the tag list page",
|
||||||
"theme.tags.tagsPageTitle": "Tags",
|
"theme.tags.tagsPageTitle": "Tags",
|
||||||
"theme.tags.tagsPageTitle___DESCRIPTION": "The title of the tag list page"
|
"theme.tags.tagsPageTitle___DESCRIPTION": "The title of the tag list page",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.message___DESCRIPTION": "The unlisted content banner message",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page",
|
||||||
|
"theme.unlistedContent.title___DESCRIPTION": "The unlisted content banner title"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "ট্যাগ্স:",
|
"theme.tags.tagsListLabel": "ট্যাগ্স:",
|
||||||
"theme.tags.tagsPageLink": "সমস্ত ট্যাগ্স দেখুন",
|
"theme.tags.tagsPageLink": "সমস্ত ট্যাগ্স দেখুন",
|
||||||
"theme.tags.tagsPageTitle": "ট্যাগ্স"
|
"theme.tags.tagsPageTitle": "ট্যাগ্স",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "Tagy:",
|
"theme.tags.tagsListLabel": "Tagy:",
|
||||||
"theme.tags.tagsPageLink": "Zobrazit všechny tagy",
|
"theme.tags.tagsPageLink": "Zobrazit všechny tagy",
|
||||||
"theme.tags.tagsPageTitle": "Tagy"
|
"theme.tags.tagsPageTitle": "Tagy",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "Tags:",
|
"theme.tags.tagsListLabel": "Tags:",
|
||||||
"theme.tags.tagsPageLink": "Se alle Tags",
|
"theme.tags.tagsPageLink": "Se alle Tags",
|
||||||
"theme.tags.tagsPageTitle": "Tags"
|
"theme.tags.tagsPageTitle": "Tags",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versionen",
|
"theme.navbar.mobileVersionsDropdown.label": "Versionen",
|
||||||
"theme.tags.tagsListLabel": "Tags:",
|
"theme.tags.tagsListLabel": "Tags:",
|
||||||
"theme.tags.tagsPageLink": "Alle Tags anzeigen",
|
"theme.tags.tagsPageLink": "Alle Tags anzeigen",
|
||||||
"theme.tags.tagsPageTitle": "Tags"
|
"theme.tags.tagsPageTitle": "Tags",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versiones",
|
"theme.navbar.mobileVersionsDropdown.label": "Versiones",
|
||||||
"theme.tags.tagsListLabel": "Etiquetas:",
|
"theme.tags.tagsListLabel": "Etiquetas:",
|
||||||
"theme.tags.tagsPageLink": "Ver Todas las Etiquetas",
|
"theme.tags.tagsPageLink": "Ver Todas las Etiquetas",
|
||||||
"theme.tags.tagsPageTitle": "Etiquetas"
|
"theme.tags.tagsPageTitle": "Etiquetas",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "نسخهها",
|
"theme.navbar.mobileVersionsDropdown.label": "نسخهها",
|
||||||
"theme.tags.tagsListLabel": "برچسبها:",
|
"theme.tags.tagsListLabel": "برچسبها:",
|
||||||
"theme.tags.tagsPageLink": "مشاهده تمام برچسبها",
|
"theme.tags.tagsPageLink": "مشاهده تمام برچسبها",
|
||||||
"theme.tags.tagsPageTitle": "برچسبها"
|
"theme.tags.tagsPageTitle": "برچسبها",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "Mga Tag:",
|
"theme.tags.tagsListLabel": "Mga Tag:",
|
||||||
"theme.tags.tagsPageLink": "Tingnan Lahat ng mga Tag",
|
"theme.tags.tagsPageLink": "Tingnan Lahat ng mga Tag",
|
||||||
"theme.tags.tagsPageTitle": "Mga Tag"
|
"theme.tags.tagsPageTitle": "Mga Tag",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "Tags :",
|
"theme.tags.tagsListLabel": "Tags :",
|
||||||
"theme.tags.tagsPageLink": "Voir tous les tags",
|
"theme.tags.tagsPageLink": "Voir tous les tags",
|
||||||
"theme.tags.tagsPageTitle": "Tags"
|
"theme.tags.tagsPageTitle": "Tags",
|
||||||
|
"theme.unlistedContent.message": "Cette page n'est pas répertoriée. Les moteurs de recherche ne l'indexeront pas, et seuls les utilisateurs ayant un lien direct peuvent y accéder.",
|
||||||
|
"theme.unlistedContent.title": "Page non répertoriée"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "תגיות:",
|
"theme.tags.tagsListLabel": "תגיות:",
|
||||||
"theme.tags.tagsPageLink": "כל התגיות",
|
"theme.tags.tagsPageLink": "כל התגיות",
|
||||||
"theme.tags.tagsPageTitle": "תגיות"
|
"theme.tags.tagsPageTitle": "תגיות",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "टैग:",
|
"theme.tags.tagsListLabel": "टैग:",
|
||||||
"theme.tags.tagsPageLink": "सारे टैग देखें",
|
"theme.tags.tagsPageLink": "सारे टैग देखें",
|
||||||
"theme.tags.tagsPageTitle": "टैग"
|
"theme.tags.tagsPageTitle": "टैग",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versioni",
|
"theme.navbar.mobileVersionsDropdown.label": "Versioni",
|
||||||
"theme.tags.tagsListLabel": "Etichette:",
|
"theme.tags.tagsListLabel": "Etichette:",
|
||||||
"theme.tags.tagsPageLink": "Guarda tutte le etichette",
|
"theme.tags.tagsPageLink": "Guarda tutte le etichette",
|
||||||
"theme.tags.tagsPageTitle": "Etichette"
|
"theme.tags.tagsPageTitle": "Etichette",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "他のバージョン",
|
"theme.navbar.mobileVersionsDropdown.label": "他のバージョン",
|
||||||
"theme.tags.tagsListLabel": "タグ:",
|
"theme.tags.tagsListLabel": "タグ:",
|
||||||
"theme.tags.tagsPageLink": "全てのタグを見る",
|
"theme.tags.tagsPageLink": "全てのタグを見る",
|
||||||
"theme.tags.tagsPageTitle": "タグ"
|
"theme.tags.tagsPageTitle": "タグ",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "버전",
|
"theme.navbar.mobileVersionsDropdown.label": "버전",
|
||||||
"theme.tags.tagsListLabel": "태그:",
|
"theme.tags.tagsListLabel": "태그:",
|
||||||
"theme.tags.tagsPageLink": "모든 태그 보기",
|
"theme.tags.tagsPageLink": "모든 태그 보기",
|
||||||
"theme.tags.tagsPageTitle": "태그"
|
"theme.tags.tagsPageTitle": "태그",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versies",
|
"theme.navbar.mobileVersionsDropdown.label": "Versies",
|
||||||
"theme.tags.tagsListLabel": "Tags:",
|
"theme.tags.tagsListLabel": "Tags:",
|
||||||
"theme.tags.tagsPageLink": "Laat alle tags zien",
|
"theme.tags.tagsPageLink": "Laat alle tags zien",
|
||||||
"theme.tags.tagsPageTitle": "Tags"
|
"theme.tags.tagsPageTitle": "Tags",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Wersje",
|
"theme.navbar.mobileVersionsDropdown.label": "Wersje",
|
||||||
"theme.tags.tagsListLabel": "Tagi:",
|
"theme.tags.tagsListLabel": "Tagi:",
|
||||||
"theme.tags.tagsPageLink": "Wyświetl wszystkie tagi",
|
"theme.tags.tagsPageLink": "Wyświetl wszystkie tagi",
|
||||||
"theme.tags.tagsPageTitle": "Tagi"
|
"theme.tags.tagsPageTitle": "Tagi",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "Marcadores:",
|
"theme.tags.tagsListLabel": "Marcadores:",
|
||||||
"theme.tags.tagsPageLink": "Ver todas os Marcadores",
|
"theme.tags.tagsPageLink": "Ver todas os Marcadores",
|
||||||
"theme.tags.tagsPageTitle": "Marcadores"
|
"theme.tags.tagsPageTitle": "Marcadores",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
"theme.navbar.mobileVersionsDropdown.label": "Versions",
|
||||||
"theme.tags.tagsListLabel": "Tags:",
|
"theme.tags.tagsListLabel": "Tags:",
|
||||||
"theme.tags.tagsPageLink": "Ver todas as Tags",
|
"theme.tags.tagsPageLink": "Ver todas as Tags",
|
||||||
"theme.tags.tagsPageTitle": "Tags"
|
"theme.tags.tagsPageTitle": "Tags",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Версии",
|
"theme.navbar.mobileVersionsDropdown.label": "Версии",
|
||||||
"theme.tags.tagsListLabel": "Теги:",
|
"theme.tags.tagsListLabel": "Теги:",
|
||||||
"theme.tags.tagsPageLink": "Посмотреть все теги",
|
"theme.tags.tagsPageLink": "Посмотреть все теги",
|
||||||
"theme.tags.tagsPageTitle": "Теги"
|
"theme.tags.tagsPageTitle": "Теги",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Верзије",
|
"theme.navbar.mobileVersionsDropdown.label": "Верзије",
|
||||||
"theme.tags.tagsListLabel": "Ознаке:",
|
"theme.tags.tagsListLabel": "Ознаке:",
|
||||||
"theme.tags.tagsPageLink": "Погледај све ознаке",
|
"theme.tags.tagsPageLink": "Погледај све ознаке",
|
||||||
"theme.tags.tagsPageTitle": "Ознаке"
|
"theme.tags.tagsPageTitle": "Ознаке",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versioner",
|
"theme.navbar.mobileVersionsDropdown.label": "Versioner",
|
||||||
"theme.tags.tagsListLabel": "Taggar:",
|
"theme.tags.tagsListLabel": "Taggar:",
|
||||||
"theme.tags.tagsPageLink": "Visa Alla Taggar",
|
"theme.tags.tagsPageLink": "Visa Alla Taggar",
|
||||||
"theme.tags.tagsPageTitle": "Taggar"
|
"theme.tags.tagsPageTitle": "Taggar",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Versiyonlar",
|
"theme.navbar.mobileVersionsDropdown.label": "Versiyonlar",
|
||||||
"theme.tags.tagsListLabel": "Etiketler:",
|
"theme.tags.tagsListLabel": "Etiketler:",
|
||||||
"theme.tags.tagsPageLink": "Tüm Etiketleri Görüntüle",
|
"theme.tags.tagsPageLink": "Tüm Etiketleri Görüntüle",
|
||||||
"theme.tags.tagsPageTitle": "Etiketler"
|
"theme.tags.tagsPageTitle": "Etiketler",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Версії",
|
"theme.navbar.mobileVersionsDropdown.label": "Версії",
|
||||||
"theme.tags.tagsListLabel": "Теги:",
|
"theme.tags.tagsListLabel": "Теги:",
|
||||||
"theme.tags.tagsPageLink": "Переглянути всі теги",
|
"theme.tags.tagsPageLink": "Переглянути всі теги",
|
||||||
"theme.tags.tagsPageTitle": "Теги"
|
"theme.tags.tagsPageTitle": "Теги",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "Phiên bản",
|
"theme.navbar.mobileVersionsDropdown.label": "Phiên bản",
|
||||||
"theme.tags.tagsListLabel": "Thẻ:",
|
"theme.tags.tagsListLabel": "Thẻ:",
|
||||||
"theme.tags.tagsPageLink": "Xem tất cả Thẻ",
|
"theme.tags.tagsPageLink": "Xem tất cả Thẻ",
|
||||||
"theme.tags.tagsPageTitle": "Thẻ"
|
"theme.tags.tagsPageTitle": "Thẻ",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "选择版本",
|
"theme.navbar.mobileVersionsDropdown.label": "选择版本",
|
||||||
"theme.tags.tagsListLabel": "标签:",
|
"theme.tags.tagsListLabel": "标签:",
|
||||||
"theme.tags.tagsPageLink": "查看所有标签",
|
"theme.tags.tagsPageLink": "查看所有标签",
|
||||||
"theme.tags.tagsPageTitle": "标签"
|
"theme.tags.tagsPageTitle": "标签",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@
|
||||||
"theme.navbar.mobileVersionsDropdown.label": "選擇版本",
|
"theme.navbar.mobileVersionsDropdown.label": "選擇版本",
|
||||||
"theme.tags.tagsListLabel": "標籤:",
|
"theme.tags.tagsListLabel": "標籤:",
|
||||||
"theme.tags.tagsPageLink": "檢視所有標籤",
|
"theme.tags.tagsPageLink": "檢視所有標籤",
|
||||||
"theme.tags.tagsPageTitle": "標籤"
|
"theme.tags.tagsPageTitle": "標籤",
|
||||||
|
"theme.unlistedContent.message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||||
|
"theme.unlistedContent.title": "Unlisted page"
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,16 @@ exports[`validation schemas admonitionsSchema: for value={"unknownAttribute":"va
|
||||||
|
|
||||||
exports[`validation schemas admonitionsSchema: for value=3 1`] = `""value" does not look like a valid admonitions config"`;
|
exports[`validation schemas admonitionsSchema: for value=3 1`] = `""value" does not look like a valid admonitions config"`;
|
||||||
|
|
||||||
|
exports[`validation schemas contentVisibilitySchema: for value={"draft":"bad string"} 1`] = `""draft" must be a boolean"`;
|
||||||
|
|
||||||
|
exports[`validation schemas contentVisibilitySchema: for value={"draft":42} 1`] = `""draft" must be a boolean"`;
|
||||||
|
|
||||||
|
exports[`validation schemas contentVisibilitySchema: for value={"draft":true,"unlisted":true} 1`] = `"Can't be draft and unlisted at the same time."`;
|
||||||
|
|
||||||
|
exports[`validation schemas contentVisibilitySchema: for value={"unlisted":"bad string"} 1`] = `""unlisted" must be a boolean"`;
|
||||||
|
|
||||||
|
exports[`validation schemas contentVisibilitySchema: for value={"unlisted":42} 1`] = `""unlisted" must be a boolean"`;
|
||||||
|
|
||||||
exports[`validation schemas pathnameSchema: for value="foo" 1`] = `""value" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
exports[`validation schemas pathnameSchema: for value="foo" 1`] = `""value" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||||
|
|
||||||
exports[`validation schemas pathnameSchema: for value="https://github.com/foo" 1`] = `""value" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
exports[`validation schemas pathnameSchema: for value="https://github.com/foo" 1`] = `""value" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
PluginIdSchema,
|
PluginIdSchema,
|
||||||
URISchema,
|
URISchema,
|
||||||
PathnameSchema,
|
PathnameSchema,
|
||||||
|
ContentVisibilitySchema,
|
||||||
} from '../validationSchemas';
|
} from '../validationSchemas';
|
||||||
|
|
||||||
function createTestHelpers({
|
function createTestHelpers({
|
||||||
|
@ -166,4 +167,28 @@ describe('validation schemas', () => {
|
||||||
testFail('foo');
|
testFail('foo');
|
||||||
testFail('https://github.com/foo');
|
testFail('https://github.com/foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('contentVisibilitySchema', () => {
|
||||||
|
const {testFail, testOK} = createTestHelpers({
|
||||||
|
schema: ContentVisibilitySchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
testOK({});
|
||||||
|
testOK({draft: false});
|
||||||
|
testOK({draft: true});
|
||||||
|
testOK({unlisted: false});
|
||||||
|
testOK({unlisted: true});
|
||||||
|
|
||||||
|
testOK({draft: false, unlisted: false});
|
||||||
|
testOK({draft: true, unlisted: false});
|
||||||
|
testOK({draft: false, unlisted: true});
|
||||||
|
testOK({draft: true, unlisted: undefined});
|
||||||
|
testOK({draft: undefined, unlisted: true});
|
||||||
|
|
||||||
|
testFail({draft: 'bad string'});
|
||||||
|
testFail({draft: 42});
|
||||||
|
testFail({unlisted: 'bad string'});
|
||||||
|
testFail({unlisted: 42});
|
||||||
|
testFail({draft: true, unlisted: true});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,4 +24,5 @@ export {
|
||||||
PathnameSchema,
|
PathnameSchema,
|
||||||
FrontMatterTagsSchema,
|
FrontMatterTagsSchema,
|
||||||
FrontMatterTOCHeadingLevels,
|
FrontMatterTOCHeadingLevels,
|
||||||
|
ContentVisibilitySchema,
|
||||||
} from './validationSchemas';
|
} from './validationSchemas';
|
||||||
|
|
|
@ -126,3 +126,26 @@ export const FrontMatterTOCHeadingLevels = {
|
||||||
}),
|
}),
|
||||||
toc_max_heading_level: JoiFrontMatter.number().min(2).max(6),
|
toc_max_heading_level: JoiFrontMatter.number().min(2).max(6),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ContentVisibility = {
|
||||||
|
draft: boolean;
|
||||||
|
unlisted: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContentVisibilitySchema = JoiFrontMatter.object<ContentVisibility>(
|
||||||
|
{
|
||||||
|
draft: JoiFrontMatter.boolean(),
|
||||||
|
unlisted: JoiFrontMatter.boolean(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.custom((frontMatter: ContentVisibility, helpers) => {
|
||||||
|
if (frontMatter.draft && frontMatter.unlisted) {
|
||||||
|
return helpers.error('frontMatter.draftAndUnlistedError');
|
||||||
|
}
|
||||||
|
return frontMatter;
|
||||||
|
})
|
||||||
|
.messages({
|
||||||
|
'frontMatter.draftAndUnlistedError':
|
||||||
|
"Can't be draft and unlisted at the same time.",
|
||||||
|
})
|
||||||
|
.unknown();
|
||||||
|
|
|
@ -5,7 +5,12 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {normalizeFrontMatterTags, groupTaggedItems, type Tag} from '../tags';
|
import {
|
||||||
|
normalizeFrontMatterTags,
|
||||||
|
groupTaggedItems,
|
||||||
|
type Tag,
|
||||||
|
getTagVisibility,
|
||||||
|
} from '../tags';
|
||||||
|
|
||||||
describe('normalizeFrontMatterTags', () => {
|
describe('normalizeFrontMatterTags', () => {
|
||||||
it('normalizes simple string tag', () => {
|
it('normalizes simple string tag', () => {
|
||||||
|
@ -183,3 +188,52 @@ describe('groupTaggedItems', () => {
|
||||||
expect(groupItems(input)).toEqual(expectedOutput);
|
expect(groupItems(input)).toEqual(expectedOutput);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getTagVisibility', () => {
|
||||||
|
type Item = {id: string; unlisted: boolean};
|
||||||
|
|
||||||
|
function isUnlisted(item: Item): boolean {
|
||||||
|
return item.unlisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item1: Item = {id: '1', unlisted: false};
|
||||||
|
const item2: Item = {id: '2', unlisted: true};
|
||||||
|
const item3: Item = {id: '3', unlisted: false};
|
||||||
|
const item4: Item = {id: '4', unlisted: true};
|
||||||
|
|
||||||
|
it('works for some unlisted', () => {
|
||||||
|
expect(
|
||||||
|
getTagVisibility({
|
||||||
|
items: [item1, item2, item3, item4],
|
||||||
|
isUnlisted,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
listedItems: [item1, item3],
|
||||||
|
unlisted: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works for all unlisted', () => {
|
||||||
|
expect(
|
||||||
|
getTagVisibility({
|
||||||
|
items: [item2, item4],
|
||||||
|
isUnlisted,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
listedItems: [item2, item4],
|
||||||
|
unlisted: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works for all listed', () => {
|
||||||
|
expect(
|
||||||
|
getTagVisibility({
|
||||||
|
items: [item1, item3],
|
||||||
|
isUnlisted,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
listedItems: [item1, item3],
|
||||||
|
unlisted: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
54
packages/docusaurus-utils/src/contentVisibilityUtils.ts
Normal file
54
packages/docusaurus-utils/src/contentVisibilityUtils.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Env = 'production' | 'development';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To easily work on draft/unlisted in dev mode, use this env variable!
|
||||||
|
* SIMULATE_PRODUCTION_VISIBILITY=true yarn start:website
|
||||||
|
*/
|
||||||
|
const simulateProductionVisibility =
|
||||||
|
process.env.SIMULATE_PRODUCTION_VISIBILITY === 'true';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draft/unlisted is a production-only concept
|
||||||
|
* In dev it is ignored and all content files are included
|
||||||
|
*/
|
||||||
|
function isProduction(env: Env | undefined): boolean {
|
||||||
|
return (
|
||||||
|
simulateProductionVisibility ||
|
||||||
|
(env ?? process.env.NODE_ENV) === 'production'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A draft content will not be included in the production build
|
||||||
|
*/
|
||||||
|
export function isDraft({
|
||||||
|
frontMatter,
|
||||||
|
env,
|
||||||
|
}: {
|
||||||
|
frontMatter: {draft?: boolean};
|
||||||
|
env?: Env;
|
||||||
|
}): boolean {
|
||||||
|
return (isProduction(env) && frontMatter.draft) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An unlisted content will be included in the production build, but hidden.
|
||||||
|
* It is excluded from sitemap, has noIndex, does not appear in lists etc...
|
||||||
|
* Only users having the link can find it.
|
||||||
|
*/
|
||||||
|
export function isUnlisted({
|
||||||
|
frontMatter,
|
||||||
|
env,
|
||||||
|
}: {
|
||||||
|
frontMatter: {unlisted?: boolean};
|
||||||
|
env?: Env;
|
||||||
|
}): boolean {
|
||||||
|
return (isProduction(env) && frontMatter.unlisted) ?? false;
|
||||||
|
}
|
|
@ -62,6 +62,7 @@ export {
|
||||||
type FrontMatterTag,
|
type FrontMatterTag,
|
||||||
normalizeFrontMatterTags,
|
normalizeFrontMatterTags,
|
||||||
groupTaggedItems,
|
groupTaggedItems,
|
||||||
|
getTagVisibility,
|
||||||
} from './tags';
|
} from './tags';
|
||||||
export {
|
export {
|
||||||
parseMarkdownHeadingId,
|
parseMarkdownHeadingId,
|
||||||
|
@ -103,3 +104,4 @@ export {
|
||||||
findFolderContainingFile,
|
findFolderContainingFile,
|
||||||
getFolderContainingFile,
|
getFolderContainingFile,
|
||||||
} from './dataFileUtils';
|
} from './dataFileUtils';
|
||||||
|
export {isDraft, isUnlisted} from './contentVisibilityUtils';
|
||||||
|
|
|
@ -25,6 +25,8 @@ export type TagsListItem = Tag & {
|
||||||
export type TagModule = TagsListItem & {
|
export type TagModule = TagsListItem & {
|
||||||
/** The tags list page's permalink. */
|
/** The tags list page's permalink. */
|
||||||
allTagsPath: string;
|
allTagsPath: string;
|
||||||
|
/** Is this tag unlisted? (when it only contains unlisted items) */
|
||||||
|
unlisted: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FrontMatterTag = string | Tag;
|
export type FrontMatterTag = string | Tag;
|
||||||
|
@ -128,3 +130,32 @@ export function groupTaggedItems<Item>(
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permits to get the "tag visibility" (hard to find a better name)
|
||||||
|
* IE, is this tag listed or unlisted
|
||||||
|
* And which items should be listed when this tag is browsed
|
||||||
|
*/
|
||||||
|
export function getTagVisibility<Item>({
|
||||||
|
items,
|
||||||
|
isUnlisted,
|
||||||
|
}: {
|
||||||
|
items: Item[];
|
||||||
|
isUnlisted: (item: Item) => boolean;
|
||||||
|
}): {
|
||||||
|
unlisted: boolean;
|
||||||
|
listedItems: Item[];
|
||||||
|
} {
|
||||||
|
const allItemsUnlisted = items.every(isUnlisted);
|
||||||
|
// When a tag is full of unlisted items, we display all the items
|
||||||
|
// when tag is browsed, but we mark the tag as unlisted
|
||||||
|
if (allItemsUnlisted) {
|
||||||
|
return {unlisted: true, listedItems: items};
|
||||||
|
}
|
||||||
|
// When a tag has some listed items, the tag remains listed
|
||||||
|
// but we filter its unlisted items
|
||||||
|
return {
|
||||||
|
unlisted: false,
|
||||||
|
listedItems: items.filter((item) => !isUnlisted(item)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -359,6 +359,7 @@ typesense
|
||||||
unflat
|
unflat
|
||||||
unist
|
unist
|
||||||
unlinkable
|
unlinkable
|
||||||
|
unlisteds
|
||||||
unlocalized
|
unlocalized
|
||||||
unmatch
|
unmatch
|
||||||
unnormalized
|
unnormalized
|
||||||
|
|
10
website/_dogfooding/_blog tests/2022-08-24-post-unlisted.md
Normal file
10
website/_dogfooding/_blog tests/2022-08-24-post-unlisted.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
title: Unlisted blog post
|
||||||
|
unlisted: true
|
||||||
|
tags: [blog, visibility, unlisted]
|
||||||
|
slug: /unlisted-post
|
||||||
|
---
|
||||||
|
|
||||||
|
This unlisted blog post should be "hidden" in production, but remain accessible.
|
||||||
|
|
||||||
|
It is filtered from the sidebar, sitemap, SEO indexation...
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue