diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags-unlisted.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags-unlisted.md new file mode 100644 index 000000000000..36e186ceca2e --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags-unlisted.md @@ -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 diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/unlisted.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/unlisted.md new file mode 100644 index 000000000000..0c5ca5d55022 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/unlisted.md @@ -0,0 +1,6 @@ +--- +date: 2020-02-27 +unlisted: true +--- + +this post is unlisted diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/unlisted/index.html b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/unlisted/index.html new file mode 100644 index 000000000000..7167942e8fff --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/unlisted/index.html @@ -0,0 +1,18 @@ + + + + + + + +Unlisted | My Site + + + + +
+
Skip to main content
+ + + + diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap index c19351278b5f..b884dddfc605 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap @@ -30,6 +30,7 @@ exports[`blog plugin works on blog tags without pagination 1`] = ` }, ], "permalink": "/blog/tags/tag-1", + "unlisted": false, }, "/blog/tags/tag-2": { "items": [ @@ -57,6 +58,33 @@ exports[`blog plugin works on blog tags without pagination 1`] = ` }, ], "permalink": "/blog/tags/tag-2", + "unlisted": false, + }, + "/blog/tags/unlisted": { + "items": [ + "/another/blog-with-tags-unlisted", + ], + "label": "unlisted", + "pages": [ + { + "items": [ + "/another/blog-with-tags-unlisted", + ], + "metadata": { + "blogDescription": "Blog", + "blogTitle": "Blog", + "nextPage": undefined, + "page": 1, + "permalink": "/blog/tags/unlisted", + "postsPerPage": 1, + "previousPage": undefined, + "totalCount": 1, + "totalPages": 1, + }, + }, + ], + "permalink": "/blog/tags/unlisted", + "unlisted": false, }, } `; @@ -106,6 +134,7 @@ exports[`blog plugin works with blog tags 1`] = ` }, ], "permalink": "/blog/tags/tag-1", + "unlisted": false, }, "/blog/tags/tag-2": { "items": [ @@ -133,6 +162,33 @@ exports[`blog plugin works with blog tags 1`] = ` }, ], "permalink": "/blog/tags/tag-2", + "unlisted": false, + }, + "/blog/tags/unlisted": { + "items": [ + "/another/blog-with-tags-unlisted", + ], + "label": "unlisted", + "pages": [ + { + "items": [ + "/another/blog-with-tags-unlisted", + ], + "metadata": { + "blogDescription": "Blog", + "blogTitle": "Blog", + "nextPage": undefined, + "page": 1, + "permalink": "/blog/tags/unlisted", + "postsPerPage": 2, + "previousPage": undefined, + "totalCount": 1, + "totalPages": 1, + }, + }, + ], + "permalink": "/blog/tags/unlisted", + "unlisted": false, }, } `; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index e56e35d17620..83d0af77157c 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -54,7 +54,7 @@ async function testGenerateFeeds( ); await createBlogFeedFiles({ - blogPosts: blogPosts.filter((post) => !post.metadata.frontMatter.draft), + blogPosts, options, siteConfig: context.siteConfig, outDir: context.outDir, diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts index 7d769afe3f69..3e3580d92c5c 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts @@ -12,7 +12,7 @@ import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; // TODO this abstraction reduce verbosity but it makes it harder to debug // It would be preferable to just expose helper methods function testField(params: { - fieldName: keyof BlogPostFrontMatter; + prefix: string; validFrontMatters: BlogPostFrontMatter[]; convertibleFrontMatter?: [ ConvertibleFrontMatter: {[key: string]: unknown}, @@ -23,7 +23,7 @@ function testField(params: { ErrorMessage: string, ][]; }) { - describe(`"${params.fieldName}" field`, () => { + describe(`"${params.prefix}" field`, () => { it('accept valid values', () => { params.validFrontMatters.forEach((frontMatter) => { expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter); @@ -44,15 +44,12 @@ function testField(params: { params.invalidFrontMatters?.forEach(([frontMatter, message]) => { try { validateBlogPostFrontMatter(frontMatter); - // eslint-disable-next-line jest/no-jasmine-globals - fail( - new Error( - `Blog front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( - frontMatter, - null, - 2, - )}`, - ), + throw new Error( + `Blog front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( + frontMatter, + null, + 2, + )}`, ); } catch (err) { // eslint-disable-next-line jest/no-conditional-expect @@ -79,7 +76,7 @@ describe('validateBlogPostFrontMatter', () => { describe('validateBlogPostFrontMatter description', () => { testField({ - fieldName: 'description', + prefix: 'description', validFrontMatters: [ // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 {description: ''}, @@ -90,7 +87,7 @@ describe('validateBlogPostFrontMatter description', () => { describe('validateBlogPostFrontMatter title', () => { testField({ - fieldName: 'title', + prefix: 'title', validFrontMatters: [ // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 {title: ''}, @@ -101,7 +98,7 @@ describe('validateBlogPostFrontMatter title', () => { describe('validateBlogPostFrontMatter id', () => { testField({ - fieldName: 'id', + prefix: 'id', validFrontMatters: [{id: '123'}, {id: 'id'}], invalidFrontMatters: [[{id: ''}, 'not allowed to be empty']], }); @@ -132,7 +129,7 @@ describe('validateBlogPostFrontMatter handles legacy/new author front matter', ( describe('validateBlogPostFrontMatter author', () => { testField({ - fieldName: 'author', + prefix: 'author', validFrontMatters: [{author: '123'}, {author: 'author'}], invalidFrontMatters: [[{author: ''}, 'not allowed to be empty']], }); @@ -140,7 +137,7 @@ describe('validateBlogPostFrontMatter author', () => { describe('validateBlogPostFrontMatter author_title', () => { testField({ - fieldName: 'author_title', + prefix: 'author_title', validFrontMatters: [ {author: '123', author_title: '123'}, {author: '123', author_title: 'author_title'}, @@ -149,7 +146,7 @@ describe('validateBlogPostFrontMatter author_title', () => { }); testField({ - fieldName: 'authorTitle', + prefix: 'authorTitle', validFrontMatters: [{authorTitle: '123'}, {authorTitle: 'authorTitle'}], invalidFrontMatters: [[{authorTitle: ''}, 'not allowed to be empty']], }); @@ -157,7 +154,7 @@ describe('validateBlogPostFrontMatter author_title', () => { describe('validateBlogPostFrontMatter author_url', () => { testField({ - fieldName: 'author_url', + prefix: 'author_url', validFrontMatters: [ {author_url: 'https://docusaurus.io'}, {author_url: '../../relative'}, @@ -172,7 +169,7 @@ describe('validateBlogPostFrontMatter author_url', () => { }); testField({ - fieldName: 'authorURL', + prefix: 'authorURL', validFrontMatters: [ {authorURL: 'https://docusaurus.io'}, {authorURL: '../../relative'}, @@ -190,7 +187,7 @@ describe('validateBlogPostFrontMatter author_url', () => { describe('validateBlogPostFrontMatter author_image_url', () => { testField({ - fieldName: 'author_image_url', + prefix: 'author_image_url', validFrontMatters: [ {author_image_url: 'https://docusaurus.io/asset/image.png'}, {author_image_url: '../../relative'}, @@ -205,7 +202,7 @@ describe('validateBlogPostFrontMatter author_image_url', () => { }); testField({ - fieldName: 'authorImageURL', + prefix: 'authorImageURL', validFrontMatters: [ {authorImageURL: 'https://docusaurus.io/asset/image.png'}, {authorImageURL: '../../relative'}, @@ -222,7 +219,7 @@ describe('validateBlogPostFrontMatter author_image_url', () => { describe('validateBlogPostFrontMatter authors', () => { testField({ - fieldName: 'author', + prefix: 'author', validFrontMatters: [ {authors: []}, {authors: 'authorKey'}, @@ -270,7 +267,7 @@ describe('validateBlogPostFrontMatter authors', () => { describe('validateBlogPostFrontMatter slug', () => { testField({ - fieldName: 'slug', + prefix: 'slug', validFrontMatters: [ {slug: 'blog/'}, {slug: '/blog'}, @@ -287,7 +284,7 @@ describe('validateBlogPostFrontMatter slug', () => { describe('validateBlogPostFrontMatter image', () => { testField({ - fieldName: 'image', + prefix: 'image', validFrontMatters: [ {image: 'https://docusaurus.io/image.png'}, {image: 'blog/'}, @@ -307,7 +304,7 @@ describe('validateBlogPostFrontMatter image', () => { describe('validateBlogPostFrontMatter tags', () => { testField({ - fieldName: 'tags', + prefix: 'tags', validFrontMatters: [ {tags: []}, {tags: ['hello']}, @@ -335,7 +332,7 @@ describe('validateBlogPostFrontMatter tags', () => { describe('validateBlogPostFrontMatter keywords', () => { testField({ - fieldName: 'keywords', + prefix: 'keywords', validFrontMatters: [ {keywords: ['hello']}, {keywords: ['hello', 'world']}, @@ -352,7 +349,7 @@ describe('validateBlogPostFrontMatter keywords', () => { describe('validateBlogPostFrontMatter draft', () => { testField({ - fieldName: 'draft', + prefix: 'draft', validFrontMatters: [{draft: true}, {draft: false}], convertibleFrontMatter: [ [{draft: 'true'}, {draft: true}], @@ -365,9 +362,43 @@ describe('validateBlogPostFrontMatter draft', () => { }); }); +describe('validateBlogPostFrontMatter unlisted', () => { + testField({ + prefix: 'unlisted', + validFrontMatters: [{unlisted: true}, {unlisted: false}], + convertibleFrontMatter: [ + [{unlisted: 'true'}, {unlisted: true}], + [{unlisted: 'false'}, {unlisted: false}], + ], + invalidFrontMatters: [ + [{unlisted: 'yes'}, 'must be a boolean'], + [{unlisted: 'no'}, 'must be a boolean'], + ], + }); +}); + +describe('validateDocFrontMatter draft XOR unlisted', () => { + testField({ + prefix: 'draft XOR unlisted', + validFrontMatters: [ + {draft: false}, + {unlisted: false}, + {draft: false, unlisted: false}, + {draft: true, unlisted: false}, + {draft: false, unlisted: true}, + ], + invalidFrontMatters: [ + [ + {draft: true, unlisted: true}, + "Can't be draft and unlisted at the same time.", + ], + ], + }); +}); + describe('validateBlogPostFrontMatter hide_table_of_contents', () => { testField({ - fieldName: 'hide_table_of_contents', + prefix: 'hide_table_of_contents', validFrontMatters: [ {hide_table_of_contents: true}, {hide_table_of_contents: false}, @@ -385,7 +416,7 @@ describe('validateBlogPostFrontMatter hide_table_of_contents', () => { describe('validateBlogPostFrontMatter date', () => { testField({ - fieldName: 'date', + prefix: 'date', validFrontMatters: [ {date: new Date('2020-01-01')}, {date: '2020-01-01'}, diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 64998082ff53..ee7c68680cb5 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -172,6 +172,7 @@ describe('blog plugin', () => { title: 'Happy 1st Birthday Slash! (translated)', }, hasTruncateMarker: false, + unlisted: false, }); expect( @@ -215,6 +216,7 @@ describe('blog plugin', () => { title: 'date-matter', }, hasTruncateMarker: false, + unlisted: false, }); expect({ @@ -252,6 +254,7 @@ describe('blog plugin', () => { }, ], hasTruncateMarker: false, + unlisted: false, }); expect({ @@ -289,6 +292,7 @@ describe('blog plugin', () => { }, tags: [], hasTruncateMarker: false, + unlisted: false, }); expect({ @@ -314,13 +318,14 @@ describe('blog plugin', () => { title: 'date-matter', }, hasTruncateMarker: false, + unlisted: false, }); }); it('builds simple website blog with localized dates', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const blogPostsFrench = await getBlogPosts(siteDir, {}, getI18n('fr')); - expect(blogPostsFrench).toHaveLength(8); + expect(blogPostsFrench).toHaveLength(9); expect(blogPostsFrench[0]!.metadata.formattedDate).toMatchInlineSnapshot( `"6 mars 2021"`, ); @@ -337,13 +342,13 @@ describe('blog plugin', () => { `"27 février 2020"`, ); expect(blogPostsFrench[5]!.metadata.formattedDate).toMatchInlineSnapshot( - `"2 janvier 2019"`, + `"27 février 2020"`, ); expect(blogPostsFrench[6]!.metadata.formattedDate).toMatchInlineSnapshot( - `"1 janvier 2019"`, + `"2 janvier 2019"`, ); expect(blogPostsFrench[7]!.metadata.formattedDate).toMatchInlineSnapshot( - `"14 décembre 2018"`, + `"1 janvier 2019"`, ); }); @@ -372,7 +377,7 @@ describe('blog plugin', () => { expect(blogPost.metadata.editUrl).toEqual(hardcodedEditUrl); }); - expect(editUrlFunction).toHaveBeenCalledTimes(8); + expect(editUrlFunction).toHaveBeenCalledTimes(9); expect(editUrlFunction).toHaveBeenCalledWith({ blogDirPath: 'blog', @@ -471,6 +476,7 @@ describe('blog plugin', () => { prevItem: undefined, nextItem: undefined, hasTruncateMarker: false, + unlisted: false, }); }); @@ -495,7 +501,7 @@ describe('blog plugin', () => { postsPerPage: 2, }); - expect(Object.keys(blogTags)).toHaveLength(2); + expect(Object.keys(blogTags)).toHaveLength(3); expect(blogTags).toMatchSnapshot(); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/props.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/props.test.ts new file mode 100644 index 000000000000..3446a3601468 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/props.test.ts @@ -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[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, + }, + ]); + }); +}); diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 17e0dbb0bffc..36028495381c 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -21,8 +21,11 @@ import { Globby, normalizeFrontMatterTags, groupTaggedItems, + getTagVisibility, getFileCommitDate, getContentPathList, + isUnlisted, + isDraft, } from '@docusaurus/utils'; import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; @@ -96,6 +99,10 @@ export function paginateBlogPosts({ return pages; } +export function shouldBeListed(blogPost: BlogPost): boolean { + return !blogPost.metadata.unlisted; +} + export function getBlogTags({ blogPosts, ...params @@ -109,17 +116,23 @@ export function getBlogTags({ blogPosts, (blogPost) => blogPost.metadata.tags, ); - - return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({ - label: tag.label, - items: tagBlogPosts.map((item) => item.id), - permalink: tag.permalink, - pages: paginateBlogPosts({ - blogPosts: tagBlogPosts, - basePageUrl: tag.permalink, - ...params, - }), - })); + return _.mapValues(groups, ({tag, items: tagBlogPosts}) => { + const tagVisibility = getTagVisibility({ + items: tagBlogPosts, + isUnlisted: (item) => item.metadata.unlisted, + }); + return { + label: tag.label, + items: tagVisibility.listedItems.map((item) => item.id), + permalink: tag.permalink, + pages: paginateBlogPosts({ + blogPosts: tagVisibility.listedItems, + basePageUrl: tag.permalink, + ...params, + }), + unlisted: tagVisibility.unlisted, + }; + }); } const DATE_FILENAME_REGEX = @@ -219,7 +232,10 @@ async function processBlogSourceFile( const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir); - if (frontMatter.draft && process.env.NODE_ENV === 'production') { + const draft = isDraft({frontMatter}); + const unlisted = isUnlisted({frontMatter}); + + if (draft) { return undefined; } @@ -326,6 +342,7 @@ async function processBlogSourceFile( hasTruncateMarker: truncateMarker.test(content), authors, frontMatter, + unlisted, }, content, }; @@ -352,23 +369,25 @@ export async function generateBlogPosts( authorsMapPath: options.authorsMapPath, }); + async function doProcessBlogSourceFile(blogSourceFile: string) { + try { + return await processBlogSourceFile( + blogSourceFile, + contentPaths, + context, + options, + authorsMap, + ); + } catch (err) { + throw new Error( + `Processing of blog source file path=${blogSourceFile} failed.`, + {cause: err as Error}, + ); + } + } + const blogPosts = ( - await Promise.all( - blogSourceFiles.map(async (blogSourceFile: string) => { - try { - return await processBlogSourceFile( - blogSourceFile, - contentPaths, - context, - options, - authorsMap, - ); - } catch (err) { - logger.error`Processing of blog source file path=${blogSourceFile} failed.`; - throw err; - } - }), - ) + await Promise.all(blogSourceFiles.map(doProcessBlogSourceFile)) ).filter(Boolean) as BlogPost[]; blogPosts.sort( diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts index 222fcb84b2b5..81d7d45fe99f 100644 --- a/packages/docusaurus-plugin-content-blog/src/feed.ts +++ b/packages/docusaurus-plugin-content-blog/src/feed.ts @@ -133,8 +133,15 @@ async function createBlogFeedFile({ } } +function shouldBeInFeed(blogPost: BlogPost): boolean { + const excluded = + blogPost.metadata.frontMatter.draft || + blogPost.metadata.frontMatter.unlisted; + return !excluded; +} + export async function createBlogFeedFiles({ - blogPosts, + blogPosts: allBlogPosts, options, siteConfig, outDir, @@ -146,6 +153,8 @@ export async function createBlogFeedFiles({ outDir: string; locale: string; }): Promise { + const blogPosts = allBlogPosts.filter(shouldBeInFeed); + const feed = await generateBlogFeed({ blogPosts, options, diff --git a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts index 417bf1df00fb..73b4d37d25ee 100644 --- a/packages/docusaurus-plugin-content-blog/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts @@ -11,6 +11,7 @@ import { validateFrontMatter, FrontMatterTagsSchema, FrontMatterTOCHeadingLevels, + ContentVisibilitySchema, } from '@docusaurus/utils-validation'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; @@ -32,7 +33,6 @@ const BlogFrontMatterSchema = Joi.object({ title: Joi.string().allow(''), description: Joi.string().allow(''), tags: FrontMatterTagsSchema, - draft: Joi.boolean(), date: Joi.date().raw(), // New multi-authors front matter: @@ -69,10 +69,12 @@ const BlogFrontMatterSchema = Joi.object({ hide_table_of_contents: Joi.boolean(), ...FrontMatterTOCHeadingLevels, -}).messages({ - 'deprecate.error': - '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.', -}); +}) + .messages({ + 'deprecate.error': + '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.', + }) + .concat(ContentVisibilitySchema); export function validateBlogPostFrontMatter(frontMatter: { [key: string]: unknown; diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 51bdf7516c32..a86b835e0556 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -18,19 +18,19 @@ import { getContentPathList, getDataFilePath, DEFAULT_PLUGIN_ID, - type TagsListItem, - type TagModule, } from '@docusaurus/utils'; import { generateBlogPosts, getSourceToPermalink, getBlogTags, paginateBlogPosts, + shouldBeListed, } from './blogUtils'; import footnoteIDFixer from './remark/footnoteIDFixer'; import {translateContent, getTranslationFiles} from './translations'; import {createBlogFeedFiles} from './feed'; +import {toTagProp, toTagsProp} from './props'; import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types'; import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types'; import type { @@ -112,6 +112,7 @@ export default async function pluginContentBlog( const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]); const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]); const blogPosts = await generateBlogPosts(contentPaths, context, options); + const listedBlogPosts = blogPosts.filter(shouldBeListed); if (!blogPosts.length) { return { @@ -125,8 +126,8 @@ export default async function pluginContentBlog( } // Colocate next and prev metadata. - blogPosts.forEach((blogPost, index) => { - const prevItem = index > 0 ? blogPosts[index - 1] : null; + listedBlogPosts.forEach((blogPost, index) => { + const prevItem = index > 0 ? listedBlogPosts[index - 1] : null; if (prevItem) { blogPost.metadata.prevItem = { title: prevItem.metadata.title, @@ -135,7 +136,9 @@ export default async function pluginContentBlog( } const nextItem = - index < blogPosts.length - 1 ? blogPosts[index + 1] : null; + index < listedBlogPosts.length - 1 + ? listedBlogPosts[index + 1] + : null; if (nextItem) { blogPost.metadata.nextItem = { title: nextItem.metadata.title, @@ -145,7 +148,7 @@ export default async function pluginContentBlog( }); const blogListPaginated: BlogPaginated[] = paginateBlogPosts({ - blogPosts, + blogPosts: listedBlogPosts, blogTitle, blogDescription, postsPerPageOption, @@ -242,6 +245,7 @@ export default async function pluginContentBlog( items: sidebarBlogPosts.map((blogPost) => ({ title: blogPost.metadata.title, permalink: blogPost.metadata.permalink, + unlisted: blogPost.metadata.unlisted, })), }, null, @@ -303,17 +307,10 @@ export default async function pluginContentBlog( } async function createTagsListPage() { - const tagsProp: TagsListItem[] = Object.values(blogTags).map((tag) => ({ - label: tag.label, - permalink: tag.permalink, - count: tag.items.length, - })); - const tagsPropPath = await createData( `${docuHash(`${blogTagsListPath}-tags`)}.json`, - JSON.stringify(tagsProp, null, 2), + JSON.stringify(toTagsProp({blogTags}), null, 2), ); - addRoute({ path: blogTagsListPath, component: blogTagsListComponent, @@ -329,15 +326,9 @@ export default async function pluginContentBlog( await Promise.all( tag.pages.map(async (blogPaginated) => { const {metadata, items} = blogPaginated; - const tagProp: TagModule = { - label: tag.label, - permalink: tag.permalink, - allTagsPath: blogTagsListPath, - count: tag.items.length, - }; const tagPropPath = await createData( `${docuHash(metadata.permalink)}.json`, - JSON.stringify(tagProp, null, 2), + JSON.stringify(toTagProp({tag, blogTagsListPath}), null, 2), ); const listMetadataPath = await createData( diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index 63d31d5f58c7..96c288d335f4 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -90,6 +90,10 @@ declare module '@docusaurus/plugin-content-blog' { * Marks the post as draft and excludes it from the production build. */ draft?: boolean; + /** + * Marks the post as unlisted and visibly hides it unless directly accessed. + */ + unlisted?: boolean; /** * Will override the default publish date inferred from git/filename. Yaml * only converts standard yyyy-MM-dd format to dates, so this may stay as a @@ -222,6 +226,10 @@ declare module '@docusaurus/plugin-content-blog' { readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown}; /** Tags, normalized. */ readonly tags: Tag[]; + /** + * Marks the post as unlisted and visibly hides it unless directly accessed. + */ + readonly unlisted: boolean; }; /** * @returns The edit URL that's directly plugged into metadata. @@ -407,9 +415,15 @@ declare module '@docusaurus/plugin-content-blog' { } >; + export type BlogSidebarItem = { + title: string; + permalink: string; + unlisted: boolean; + }; + export type BlogSidebar = { title: string; - items: {title: string; permalink: string}[]; + items: BlogSidebarItem[]; }; export type BlogContent = { @@ -428,6 +442,7 @@ declare module '@docusaurus/plugin-content-blog' { /** Blog post permalinks. */ items: string[]; pages: BlogPaginated[]; + unlisted: boolean; }; export type BlogPost = { diff --git a/packages/docusaurus-plugin-content-blog/src/props.ts b/packages/docusaurus-plugin-content-blog/src/props.ts new file mode 100644 index 000000000000..8946bc15d580 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/props.ts @@ -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, + }; +} diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/doc-unlisted.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/doc-unlisted.md new file mode 100644 index 000000000000..54090b5a48b0 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/doc-unlisted.md @@ -0,0 +1,5 @@ +--- +unlisted: true +--- + +This is an unlisted document diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/ipsum.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/ipsum.md index 706687dbc53b..6bac3bd5a49a 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/ipsum.md +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/ipsum.md @@ -1,5 +1,6 @@ --- custom_edit_url: null +pagination_next: doc-unlisted --- Lorem ipsum. diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/unlisted-category/index.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/unlisted-category/index.md new file mode 100644 index 000000000000..440e184ebcca --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/unlisted-category/index.md @@ -0,0 +1,6 @@ +--- +id: unlisted-category-index +unlisted: true +--- + +This is an unlisted category index diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/unlisted-category/unlisted-category-doc.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/unlisted-category/unlisted-category-doc.md new file mode 100644 index 000000000000..9f91c9baaf5f --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/unlisted-category/unlisted-category-doc.md @@ -0,0 +1,6 @@ +--- +id: unlisted-category-doc +unlisted: true +--- + +This is an unlisted category doc diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json index 7f74d2280b67..19ff4b802bf7 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/sidebars.json @@ -4,7 +4,16 @@ { "type": "category", "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", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap index edff61da5b8b..1aa0e1d12474 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap @@ -10,11 +10,23 @@ exports[`docsVersion first time versioning 1`] = ` { "items": [ "foo/bar", + "doc-unlisted", "foo/baz", ], "label": "foo", "type": "category", }, + { + "items": [ + "unlisted-category/unlisted-category-doc", + ], + "label": "Unlisted category", + "link": { + "id": "unlisted-category/unlisted-category-index", + "type": "doc", + }, + "type": "category", + }, { "items": [ "rootAbsoluteSlug", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap index 69401a06a04c..a9c3c1d46336 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`simple site custom pagination 1`] = ` +exports[`simple site custom pagination - development 1`] = ` { "pagination": [ { @@ -25,14 +25,25 @@ exports[`simple site custom pagination 1`] = ` { "id": "doc-draft", "next": { - "permalink": "/docs/foo/bar", - "title": "Bar", + "permalink": "/docs/doc-unlisted", + "title": "doc-unlisted", }, "prev": { "permalink": "/docs/doc with space", "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", "next": undefined, @@ -74,9 +85,356 @@ exports[`simple site custom pagination 1`] = ` { "id": "ipsum", "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", "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": { "permalink": "/docs/", "title": "Hello sidebar_label", @@ -200,6 +558,16 @@ exports[`simple site custom pagination 1`] = ` "title": "resolvedSlug", }, }, + { + "id": "unlisted-category/unlisted-category-doc", + "next": undefined, + "prev": undefined, + }, + { + "id": "unlisted-category/unlisted-category-index", + "next": undefined, + "prev": undefined, + }, ], "sidebars": { "defaultSidebar": [ @@ -215,6 +583,10 @@ exports[`simple site custom pagination 1`] = ` "id": "doc-draft", "type": "doc", }, + { + "id": "doc-unlisted", + "type": "doc", + }, { "collapsed": false, "collapsible": true, @@ -298,6 +670,22 @@ exports[`simple site custom pagination 1`] = ` "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", + }, ], }, } diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/globalData.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/globalData.test.ts.snap index 0bd0862b0f8d..b8bcdf05873b 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/globalData.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/globalData.test.ts.snap @@ -13,6 +13,12 @@ exports[`toGlobalDataVersion generates the right docs, sidebars, and metadata 1` "path": "/current/doc", "sidebar": "tutorial", }, + { + "id": "docNoSidebarUnlisted", + "path": "/current/docNoSidebarUnlisted", + "sidebar": undefined, + "unlisted": true, + }, { "id": "/current/generated", "path": "/current/generated", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap index 244c608c1c0b..0e1e7a59263f 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap @@ -25,6 +25,7 @@ Available document ids are: - customLastUpdate - doc with space - doc-draft +- doc-unlisted - foo/bar - foo/baz - headingAsTitle @@ -40,7 +41,9 @@ Available document ids are: - slugs/absoluteSlug - slugs/relativeSlug - slugs/resolvedSlug -- slugs/tryToEscapeSlug" +- slugs/tryToEscapeSlug +- unlisted-category/unlisted-category-doc +- unlisted-category/unlisted-category-index" `; exports[`simple website content 1`] = ` @@ -67,13 +70,13 @@ exports[`simple website content 1`] = ` "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, "next": { - "permalink": "/docs/category/slugs", - "title": "Slugs", + "permalink": "/docs/unlisted-category/", + "title": "unlisted-category-index", }, "permalink": "/docs/foo/bazSlug.html", "previous": { - "permalink": "/docs/foo/bar", - "title": "Bar", + "permalink": "/docs/doc-unlisted", + "title": "doc-unlisted", }, "sidebar": "docs", "sidebarPosition": undefined, @@ -91,6 +94,7 @@ exports[`simple website content 1`] = ` }, ], "title": "baz", + "unlisted": false, "unversionedId": "foo/baz", "version": "current", } @@ -137,6 +141,7 @@ exports[`simple website content 2`] = ` }, ], "title": "Hello, World !", + "unlisted": false, "unversionedId": "hello", "version": "current", } @@ -168,6 +173,7 @@ exports[`simple website content 3`] = ` "sourceDirName": "foo", "tags": [], "title": "Bar", + "unlisted": false, "unversionedId": "foo/bar", "version": "current", } @@ -188,6 +194,10 @@ exports[`simple website content 4`] = ` "id": "foo/bar", "type": "doc", }, + { + "id": "doc-unlisted", + "type": "doc", + }, { "id": "foo/baz", "type": "doc", @@ -197,6 +207,22 @@ exports[`simple website content 4`] = ` "link": undefined, "type": "category", }, + { + "collapsed": true, + "collapsible": true, + "items": [ + { + "id": "unlisted-category/unlisted-category-doc", + "type": "doc", + }, + ], + "label": "Unlisted category", + "link": { + "id": "unlisted-category/unlisted-category-index", + "type": "doc", + }, + "type": "category", + }, { "collapsed": true, "collapsible": true, @@ -285,6 +311,11 @@ exports[`simple website content 5`] = ` "path": "/docs/doc-draft", "sidebar": undefined, }, + { + "id": "doc-unlisted", + "path": "/docs/doc-unlisted", + "sidebar": "docs", + }, { "id": "foo/bar", "path": "/docs/foo/bar", @@ -365,6 +396,16 @@ exports[`simple website content 5`] = ` "path": "/docs/tryToEscapeSlug", "sidebar": undefined, }, + { + "id": "unlisted-category/unlisted-category-doc", + "path": "/docs/unlisted-category/unlisted-category-doc", + "sidebar": "docs", + }, + { + "id": "unlisted-category/unlisted-category-index", + "path": "/docs/unlisted-category/", + "sidebar": "docs", + }, { "id": "/category/slugs", "path": "/docs/category/slugs", @@ -400,8 +441,8 @@ exports[`simple website content: data 1`] = ` "permalink": "/docs/category/slugs", "navigation": { "previous": { - "title": "baz pagination_label", - "permalink": "/docs/foo/bazSlug.html" + "title": "unlisted-category-doc", + "permalink": "/docs/unlisted-category/unlisted-category-doc" }, "next": { "title": "rootAbsoluteSlug", @@ -419,6 +460,7 @@ exports[`simple website content: data 1`] = ` "slug": "/customLastUpdate", "permalink": "/docs/customLastUpdate", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -439,11 +481,38 @@ exports[`simple website content: data 1`] = ` "slug": "/doc-draft", "permalink": "/docs/doc-draft", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { "draft": true } +}", + "site-docs-doc-unlisted-md-80b.json": "{ + "unversionedId": "doc-unlisted", + "id": "doc-unlisted", + "title": "doc-unlisted", + "description": "This is an unlisted document", + "source": "@site/docs/doc-unlisted.md", + "sourceDirName": ".", + "slug": "/doc-unlisted", + "permalink": "/docs/doc-unlisted", + "draft": false, + "unlisted": false, + "tags": [], + "version": "current", + "frontMatter": { + "unlisted": true + }, + "sidebar": "docs", + "previous": { + "title": "Bar", + "permalink": "/docs/foo/bar" + }, + "next": { + "title": "baz pagination_label", + "permalink": "/docs/foo/bazSlug.html" + } }", "site-docs-doc-with-space-md-e90.json": "{ "unversionedId": "doc with space", @@ -455,6 +524,7 @@ exports[`simple website content: data 1`] = ` "slug": "/doc with space", "permalink": "/docs/doc with space", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": {} @@ -469,6 +539,7 @@ exports[`simple website content: data 1`] = ` "slug": "/foo/bar", "permalink": "/docs/foo/bar", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -490,6 +561,7 @@ exports[`simple website content: data 1`] = ` "slug": "/foo/bazSlug.html", "permalink": "/docs/foo/bazSlug.html", "draft": false, + "unlisted": false, "tags": [ { "label": "tag 1", @@ -517,12 +589,12 @@ exports[`simple website content: data 1`] = ` }, "sidebar": "docs", "previous": { - "title": "Bar", - "permalink": "/docs/foo/bar" + "title": "doc-unlisted", + "permalink": "/docs/doc-unlisted" }, "next": { - "title": "Slugs", - "permalink": "/docs/category/slugs" + "title": "unlisted-category-index", + "permalink": "/docs/unlisted-category/" } }", "site-docs-heading-as-title-md-c6d.json": "{ @@ -535,6 +607,7 @@ exports[`simple website content: data 1`] = ` "slug": "/headingAsTitle", "permalink": "/docs/headingAsTitle", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": {}, @@ -558,6 +631,7 @@ exports[`simple website content: data 1`] = ` "slug": "/", "permalink": "/docs/", "draft": false, + "unlisted": false, "tags": [ { "label": "tag-1", @@ -595,11 +669,17 @@ exports[`simple website content: data 1`] = ` "slug": "/ipsum", "permalink": "/docs/ipsum", "draft": false, + "unlisted": false, "editUrl": null, "tags": [], "version": "current", "frontMatter": { - "custom_edit_url": null + "custom_edit_url": null, + "pagination_next": "doc-unlisted" + }, + "next": { + "title": "doc-unlisted", + "permalink": "/docs/doc-unlisted" } }", "site-docs-last-update-author-only-md-352.json": "{ @@ -612,6 +692,7 @@ exports[`simple website content: data 1`] = ` "slug": "/lastUpdateAuthorOnly", "permalink": "/docs/lastUpdateAuthorOnly", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -631,6 +712,7 @@ exports[`simple website content: data 1`] = ` "slug": "/lastUpdateDateOnly", "permalink": "/docs/lastUpdateDateOnly", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -650,6 +732,7 @@ exports[`simple website content: data 1`] = ` "slug": "/lorem", "permalink": "/docs/lorem", "draft": false, + "unlisted": false, "editUrl": "https://github.com/customUrl/docs/lorem.md", "tags": [], "version": "current", @@ -668,6 +751,7 @@ exports[`simple website content: data 1`] = ` "slug": "/rootAbsoluteSlug", "permalink": "/docs/rootAbsoluteSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -695,6 +779,7 @@ exports[`simple website content: data 1`] = ` "slug": "/rootRelativeSlug", "permalink": "/docs/rootRelativeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -722,6 +807,7 @@ exports[`simple website content: data 1`] = ` "slug": "/hey/rootResolvedSlug", "permalink": "/docs/hey/rootResolvedSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -749,6 +835,7 @@ exports[`simple website content: data 1`] = ` "slug": "/rootTryToEscapeSlug", "permalink": "/docs/rootTryToEscapeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -776,6 +863,7 @@ exports[`simple website content: data 1`] = ` "slug": "/absoluteSlug", "permalink": "/docs/absoluteSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -792,6 +880,7 @@ exports[`simple website content: data 1`] = ` "slug": "/slugs/relativeSlug", "permalink": "/docs/slugs/relativeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -808,6 +897,7 @@ exports[`simple website content: data 1`] = ` "slug": "/slugs/hey/resolvedSlug", "permalink": "/docs/slugs/hey/resolvedSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -824,11 +914,66 @@ exports[`simple website content: data 1`] = ` "slug": "/tryToEscapeSlug", "permalink": "/docs/tryToEscapeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { "slug": "../../../../../../../../tryToEscapeSlug" } +}", + "site-docs-unlisted-category-index-md-efa.json": "{ + "unversionedId": "unlisted-category/unlisted-category-index", + "id": "unlisted-category/unlisted-category-index", + "title": "unlisted-category-index", + "description": "This is an unlisted category index", + "source": "@site/docs/unlisted-category/index.md", + "sourceDirName": "unlisted-category", + "slug": "/unlisted-category/", + "permalink": "/docs/unlisted-category/", + "draft": false, + "unlisted": false, + "tags": [], + "version": "current", + "frontMatter": { + "id": "unlisted-category-index", + "unlisted": true + }, + "sidebar": "docs", + "previous": { + "title": "baz pagination_label", + "permalink": "/docs/foo/bazSlug.html" + }, + "next": { + "title": "unlisted-category-doc", + "permalink": "/docs/unlisted-category/unlisted-category-doc" + } +}", + "site-docs-unlisted-category-unlisted-category-doc-md-bd6.json": "{ + "unversionedId": "unlisted-category/unlisted-category-doc", + "id": "unlisted-category/unlisted-category-doc", + "title": "unlisted-category-doc", + "description": "This is an unlisted category doc", + "source": "@site/docs/unlisted-category/unlisted-category-doc.md", + "sourceDirName": "unlisted-category", + "slug": "/unlisted-category/unlisted-category-doc", + "permalink": "/docs/unlisted-category/unlisted-category-doc", + "draft": false, + "unlisted": false, + "tags": [], + "version": "current", + "frontMatter": { + "id": "unlisted-category-doc", + "unlisted": true + }, + "sidebar": "docs", + "previous": { + "title": "unlisted-category-index", + "permalink": "/docs/unlisted-category/" + }, + "next": { + "title": "Slugs", + "permalink": "/docs/category/slugs" + } }", "tag-docs-tags-tag-1-b3f.json": "{ "label": "tag 1", @@ -848,7 +993,8 @@ exports[`simple website content: data 1`] = ` "description": "Hi, Endilie here :)", "permalink": "/docs/" } - ] + ], + "unlisted": false }", "tag-docs-tags-tag-2-custom-permalink-825.json": "{ "label": "tag 2", @@ -862,7 +1008,8 @@ exports[`simple website content: data 1`] = ` "description": "Images", "permalink": "/docs/foo/bazSlug.html" } - ] + ], + "unlisted": false }", "tag-docs-tags-tag-3-ab5.json": "{ "label": "tag 3", @@ -876,7 +1023,8 @@ exports[`simple website content: data 1`] = ` "description": "Hi, Endilie here :)", "permalink": "/docs/" } - ] + ], + "unlisted": false }", "tags-list-current-prop-15a.json": "[ { @@ -918,18 +1066,43 @@ exports[`simple website content: data 1`] = ` "type": "link", "label": "Bar", "href": "/docs/foo/bar", - "docId": "foo/bar" + "docId": "foo/bar", + "unlisted": false + }, + { + "type": "link", + "label": "doc-unlisted", + "href": "/docs/doc-unlisted", + "docId": "doc-unlisted", + "unlisted": false }, { "type": "link", "label": "baz", "href": "/docs/foo/bazSlug.html", - "docId": "foo/baz" + "docId": "foo/baz", + "unlisted": false } ], "collapsed": true, "collapsible": true }, + { + "type": "category", + "label": "Unlisted category", + "items": [ + { + "type": "link", + "label": "unlisted-category-doc", + "href": "/docs/unlisted-category/unlisted-category-doc", + "docId": "unlisted-category/unlisted-category-doc", + "unlisted": false + } + ], + "collapsed": true, + "collapsible": true, + "href": "/docs/unlisted-category/" + }, { "type": "category", "label": "Slugs", @@ -938,25 +1111,29 @@ exports[`simple website content: data 1`] = ` "type": "link", "label": "rootAbsoluteSlug", "href": "/docs/rootAbsoluteSlug", - "docId": "rootAbsoluteSlug" + "docId": "rootAbsoluteSlug", + "unlisted": false }, { "type": "link", "label": "rootRelativeSlug", "href": "/docs/rootRelativeSlug", - "docId": "rootRelativeSlug" + "docId": "rootRelativeSlug", + "unlisted": false }, { "type": "link", "label": "rootResolvedSlug", "href": "/docs/hey/rootResolvedSlug", - "docId": "rootResolvedSlug" + "docId": "rootResolvedSlug", + "unlisted": false }, { "type": "link", "label": "rootTryToEscapeSlug", "href": "/docs/rootTryToEscapeSlug", - "docId": "rootTryToEscapeSlug" + "docId": "rootTryToEscapeSlug", + "unlisted": false } ], "collapsed": true, @@ -967,7 +1144,8 @@ exports[`simple website content: data 1`] = ` "type": "link", "label": "My heading as title", "href": "/docs/headingAsTitle", - "docId": "headingAsTitle" + "docId": "headingAsTitle", + "unlisted": false }, { "type": "link", @@ -978,7 +1156,8 @@ exports[`simple website content: data 1`] = ` "type": "link", "label": "Hello sidebar_label", "href": "/docs/", - "docId": "hello" + "docId": "hello", + "unlisted": false } ], "collapsed": true, @@ -992,7 +1171,8 @@ exports[`simple website content: data 1`] = ` "type": "link", "label": "Hello sidebar_label", "href": "/docs/", - "docId": "hello" + "docId": "hello", + "unlisted": false } ], "collapsed": true, @@ -1016,6 +1196,12 @@ exports[`simple website content: data 1`] = ` "title": "doc-draft", "description": "This is a draft document" }, + "doc-unlisted": { + "id": "doc-unlisted", + "title": "doc-unlisted", + "description": "This is an unlisted document", + "sidebar": "docs" + }, "foo/bar": { "id": "foo/bar", "title": "Bar", @@ -1103,6 +1289,18 @@ exports[`simple website content: data 1`] = ` "id": "slugs/tryToEscapeSlug", "title": "tryToEscapeSlug", "description": "Lorem" + }, + "unlisted-category/unlisted-category-doc": { + "id": "unlisted-category/unlisted-category-doc", + "title": "unlisted-category-doc", + "description": "This is an unlisted category doc", + "sidebar": "docs" + }, + "unlisted-category/unlisted-category-index": { + "id": "unlisted-category/unlisted-category-index", + "title": "unlisted-category-index", + "description": "This is an unlisted category index", + "sidebar": "docs" } } }", @@ -1133,6 +1331,11 @@ exports[`simple website content: global data 1`] = ` "path": "/docs/doc-draft", "sidebar": undefined, }, + { + "id": "doc-unlisted", + "path": "/docs/doc-unlisted", + "sidebar": "docs", + }, { "id": "foo/bar", "path": "/docs/foo/bar", @@ -1213,6 +1416,16 @@ exports[`simple website content: global data 1`] = ` "path": "/docs/tryToEscapeSlug", "sidebar": undefined, }, + { + "id": "unlisted-category/unlisted-category-doc", + "path": "/docs/unlisted-category/unlisted-category-doc", + "sidebar": "docs", + }, + { + "id": "unlisted-category/unlisted-category-index", + "path": "/docs/unlisted-category/", + "sidebar": "docs", + }, { "id": "/category/slugs", "path": "/docs/category/slugs", @@ -1343,6 +1556,15 @@ exports[`simple website content: route config 1`] = ` }, "path": "/docs/doc-draft", }, + { + "component": "@theme/DocItem", + "exact": true, + "modules": { + "content": "@site/docs/doc-unlisted.md", + }, + "path": "/docs/doc-unlisted", + "sidebar": "docs", + }, { "component": "@theme/DocItem", "exact": true, @@ -1462,6 +1684,24 @@ exports[`simple website content: route config 1`] = ` }, "path": "/docs/tryToEscapeSlug", }, + { + "component": "@theme/DocItem", + "exact": true, + "modules": { + "content": "@site/docs/unlisted-category/index.md", + }, + "path": "/docs/unlisted-category/", + "sidebar": "docs", + }, + { + "component": "@theme/DocItem", + "exact": true, + "modules": { + "content": "@site/docs/unlisted-category/unlisted-category-doc.md", + }, + "path": "/docs/unlisted-category/unlisted-category-doc", + "sidebar": "docs", + }, ], }, ], @@ -1787,6 +2027,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": ".", "tags": [], "title": "Getting Started", + "unlisted": false, "unversionedId": "getting-started", "version": "current", } @@ -1818,6 +2059,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": ".", "tags": [], "title": "Installation", + "unlisted": false, "unversionedId": "installation", "version": "current", } @@ -1852,6 +2094,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "Guides", "tags": [], "title": "Guide 1", + "unlisted": false, "unversionedId": "Guides/guide1", "version": "current", } @@ -1885,6 +2128,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "Guides", "tags": [], "title": "Guide 2", + "unlisted": false, "unversionedId": "Guides/guide2", "version": "current", } @@ -1919,6 +2163,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "Guides", "tags": [], "title": "Guide 2.5", + "unlisted": false, "unversionedId": "Guides/guide2.5", "version": "current", } @@ -1953,6 +2198,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "Guides", "tags": [], "title": "Guide 3", + "unlisted": false, "unversionedId": "Guides/guide3", "version": "current", } @@ -1986,6 +2232,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "Guides", "tags": [], "title": "Guide 4", + "unlisted": false, "unversionedId": "Guides/guide4", "version": "current", } @@ -2019,6 +2266,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "Guides", "tags": [], "title": "Guide 5", + "unlisted": false, "unversionedId": "Guides/guide5", "version": "current", } @@ -2050,6 +2298,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "3-API", "tags": [], "title": "API Overview", + "unlisted": false, "unversionedId": "API/api-overview", "version": "current", } @@ -2081,6 +2330,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "3-API/01_Core APIs", "tags": [], "title": "Client API", + "unlisted": false, "unversionedId": "API/Core APIs/Client API", "version": "current", } @@ -2112,6 +2362,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "3-API/01_Core APIs", "tags": [], "title": "Server API", + "unlisted": false, "unversionedId": "API/Core APIs/Server API", "version": "current", } @@ -2143,6 +2394,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "3-API/02_Extension APIs", "tags": [], "title": "Plugin API", + "unlisted": false, "unversionedId": "API/Extension APIs/Plugin API", "version": "current", } @@ -2174,6 +2426,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "3-API/02_Extension APIs", "tags": [], "title": "Theme API", + "unlisted": false, "unversionedId": "API/Extension APIs/Theme API", "version": "current", } @@ -2202,6 +2455,7 @@ exports[`site with full autogenerated sidebar docs in fully generated sidebar ha "sourceDirName": "3-API", "tags": [], "title": "API End", + "unlisted": false, "unversionedId": "API/api-end", "version": "current", } @@ -2382,6 +2636,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si "sourceDirName": "3-API", "tags": [], "title": "API End", + "unlisted": false, "unversionedId": "API/api-end", "version": "current", } @@ -2413,6 +2668,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si "sourceDirName": "3-API", "tags": [], "title": "API Overview", + "unlisted": false, "unversionedId": "API/api-overview", "version": "current", } @@ -2444,6 +2700,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si "sourceDirName": "3-API/02_Extension APIs", "tags": [], "title": "Plugin API", + "unlisted": false, "unversionedId": "API/Extension APIs/Plugin API", "version": "current", } @@ -2472,6 +2729,7 @@ exports[`site with partial autogenerated sidebars docs in partially generated si "sourceDirName": "3-API/02_Extension APIs", "tags": [], "title": "Theme API", + "unlisted": false, "unversionedId": "API/Extension APIs/Theme API", "version": "current", } @@ -2531,6 +2789,7 @@ exports[`versioned website (community) content 1`] = ` "sourceDirName": ".", "tags": [], "title": "Team title translated", + "unlisted": false, "unversionedId": "team", "version": "current", } @@ -2556,6 +2815,7 @@ exports[`versioned website (community) content 2`] = ` "sourceDirName": ".", "tags": [], "title": "team", + "unlisted": false, "unversionedId": "team", "version": "1.0.0", } @@ -2595,6 +2855,7 @@ exports[`versioned website (community) content: data 1`] = ` "slug": "/team", "permalink": "/community/team", "draft": false, + "unlisted": false, "tags": [], "version": "1.0.0", "frontMatter": {}, @@ -2610,6 +2871,7 @@ exports[`versioned website (community) content: data 1`] = ` "slug": "/team", "permalink": "/community/next/team", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -2632,7 +2894,8 @@ exports[`versioned website (community) content: data 1`] = ` "type": "link", "label": "team", "href": "/community/team", - "docId": "team" + "docId": "team", + "unlisted": false } ] }, @@ -2660,7 +2923,8 @@ exports[`versioned website (community) content: data 1`] = ` "type": "link", "label": "Team title translated", "href": "/community/next/team", - "docId": "team" + "docId": "team", + "unlisted": false } ] }, @@ -2860,6 +3124,7 @@ exports[`versioned website content 1`] = ` }, ], "title": "bar", + "unlisted": false, "unversionedId": "foo/bar", "version": "current", } @@ -2888,6 +3153,7 @@ exports[`versioned website content 2`] = ` "sourceDirName": "foo", "tags": [], "title": "bar", + "unlisted": false, "unversionedId": "foo/bar", "version": "1.0.1", } @@ -2918,6 +3184,7 @@ exports[`versioned website content 3`] = ` "sourceDirName": ".", "tags": [], "title": "hello", + "unlisted": false, "unversionedId": "hello", "version": "current", } @@ -2948,6 +3215,7 @@ exports[`versioned website content 4`] = ` "sourceDirName": ".", "tags": [], "title": "hello", + "unlisted": false, "unversionedId": "hello", "version": "1.0.1", } @@ -2979,6 +3247,7 @@ exports[`versioned website content 5`] = ` "sourceDirName": "foo", "tags": [], "title": "baz", + "unlisted": false, "unversionedId": "foo/baz", "version": "1.0.0", } @@ -3099,6 +3368,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/foo/barSlug", "permalink": "/docs/next/foo/barSlug", "draft": false, + "unlisted": false, "tags": [ { "label": "barTag 1", @@ -3141,6 +3411,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/", "permalink": "/docs/next/", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -3162,6 +3433,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/absoluteSlug", "permalink": "/docs/next/absoluteSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -3178,6 +3450,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/slugs/relativeSlug", "permalink": "/docs/next/slugs/relativeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -3194,6 +3467,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/slugs/hey/resolvedSlug", "permalink": "/docs/next/slugs/hey/resolvedSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -3210,6 +3484,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/tryToEscapeSlug", "permalink": "/docs/next/tryToEscapeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "current", "frontMatter": { @@ -3226,6 +3501,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/", "permalink": "/docs/1.0.0/", "draft": false, + "unlisted": false, "tags": [], "version": "1.0.0", "frontMatter": { @@ -3247,6 +3523,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/foo/barSlug", "permalink": "/docs/1.0.0/foo/barSlug", "draft": false, + "unlisted": false, "tags": [], "version": "1.0.0", "frontMatter": { @@ -3268,6 +3545,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/foo/baz", "permalink": "/docs/1.0.0/foo/baz", "draft": false, + "unlisted": false, "tags": [], "version": "1.0.0", "frontMatter": {}, @@ -3291,6 +3569,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/foo/bar", "permalink": "/docs/foo/bar", "draft": false, + "unlisted": false, "tags": [], "version": "1.0.1", "frontMatter": {}, @@ -3310,6 +3589,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/", "permalink": "/docs/", "draft": false, + "unlisted": false, "tags": [], "version": "1.0.1", "frontMatter": { @@ -3331,6 +3611,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/rootAbsoluteSlug", "permalink": "/docs/withSlugs/rootAbsoluteSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3348,6 +3629,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/rootRelativeSlug", "permalink": "/docs/withSlugs/rootRelativeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3364,6 +3646,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/hey/rootResolvedSlug", "permalink": "/docs/withSlugs/hey/rootResolvedSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3380,6 +3663,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/rootTryToEscapeSlug", "permalink": "/docs/withSlugs/rootTryToEscapeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3396,6 +3680,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/absoluteSlug", "permalink": "/docs/withSlugs/absoluteSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3412,6 +3697,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/slugs/relativeSlug", "permalink": "/docs/withSlugs/slugs/relativeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3428,6 +3714,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/slugs/hey/resolvedSlug", "permalink": "/docs/withSlugs/slugs/hey/resolvedSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3444,6 +3731,7 @@ exports[`versioned website content: data 1`] = ` "slug": "/tryToEscapeSlug", "permalink": "/docs/withSlugs/tryToEscapeSlug", "draft": false, + "unlisted": false, "tags": [], "version": "withSlugs", "frontMatter": { @@ -3462,7 +3750,8 @@ exports[`versioned website content: data 1`] = ` "description": "This is next version of bar.", "permalink": "/docs/next/foo/barSlug" } - ] + ], + "unlisted": false }", "tag-docs-next-tags-bar-tag-2-216.json": "{ "label": "barTag-2", @@ -3476,7 +3765,8 @@ exports[`versioned website content: data 1`] = ` "description": "This is next version of bar.", "permalink": "/docs/next/foo/barSlug" } - ] + ], + "unlisted": false }", "tag-docs-next-tags-bar-tag-3-permalink-94a.json": "{ "label": "barTag 3", @@ -3490,7 +3780,8 @@ exports[`versioned website content: data 1`] = ` "description": "This is next version of bar.", "permalink": "/docs/next/foo/barSlug" } - ] + ], + "unlisted": false }", "tags-list-current-prop-15a.json": "[ { @@ -3528,13 +3819,15 @@ exports[`versioned website content: data 1`] = ` "type": "link", "label": "bar", "href": "/docs/1.0.0/foo/barSlug", - "docId": "foo/bar" + "docId": "foo/bar", + "unlisted": false }, { "type": "link", "label": "baz", "href": "/docs/1.0.0/foo/baz", - "docId": "foo/baz" + "docId": "foo/baz", + "unlisted": false } ], "collapsed": true, @@ -3548,7 +3841,8 @@ exports[`versioned website content: data 1`] = ` "type": "link", "label": "hello", "href": "/docs/1.0.0/", - "docId": "hello" + "docId": "hello", + "unlisted": false } ], "collapsed": true, @@ -3596,7 +3890,8 @@ exports[`versioned website content: data 1`] = ` "type": "link", "label": "bar", "href": "/docs/foo/bar", - "docId": "foo/bar" + "docId": "foo/bar", + "unlisted": false } ], "collapsed": true, @@ -3610,7 +3905,8 @@ exports[`versioned website content: data 1`] = ` "type": "link", "label": "hello", "href": "/docs/", - "docId": "hello" + "docId": "hello", + "unlisted": false } ], "collapsed": true, @@ -3652,7 +3948,8 @@ exports[`versioned website content: data 1`] = ` "type": "link", "label": "bar", "href": "/docs/next/foo/barSlug", - "docId": "foo/bar" + "docId": "foo/bar", + "unlisted": false } ], "collapsed": true, @@ -3666,7 +3963,8 @@ exports[`versioned website content: data 1`] = ` "type": "link", "label": "hello", "href": "/docs/next/", - "docId": "hello" + "docId": "hello", + "unlisted": false } ], "collapsed": true, @@ -3728,7 +4026,8 @@ exports[`versioned website content: data 1`] = ` "type": "link", "label": "rootAbsoluteSlug", "href": "/docs/withSlugs/rootAbsoluteSlug", - "docId": "rootAbsoluteSlug" + "docId": "rootAbsoluteSlug", + "unlisted": false } ], "collapsed": true, diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts index f0971546e594..831b655a2721 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts @@ -153,7 +153,7 @@ function createTestUtils({ versionMetadata, context, options, - env: 'production', + env, }), ), ); @@ -173,11 +173,11 @@ function createTestUtils({ const sidebarsUtils = createSidebarsUtils(sidebars); return { - pagination: addDocNavigation( - rawDocs, + pagination: addDocNavigation({ + docs: rawDocs, sidebarsUtils, - versionMetadata.sidebarFilePath as string, - ).map((doc) => ({prev: doc.previous, next: doc.next, id: doc.id})), + sidebarFilePath: versionMetadata.sidebarFilePath as string, + }).map((doc) => ({prev: doc.previous, next: doc.next, id: doc.id})), sidebars, }; } @@ -247,6 +247,7 @@ describe('simple site', () => { 'headingAsTitle.md', 'doc with space.md', 'doc-draft.md', + 'doc-unlisted.md', 'customLastUpdate.md', 'lastUpdateAuthorOnly.md', 'lastUpdateDateOnly.md', @@ -256,6 +257,8 @@ describe('simple site', () => { 'slugs/relativeSlug.md', 'slugs/resolvedSlug.md', 'slugs/tryToEscapeSlug.md', + 'unlisted-category/index.md', + 'unlisted-category/unlisted-category-doc.md', ].sort(), ); }); @@ -279,6 +282,7 @@ describe('simple site', () => { pagination_prev: null, }, tags: [], + unlisted: false, }); await defaultTestUtils.testMeta(path.join('hello.md'), { version: 'current', @@ -306,6 +310,7 @@ describe('simple site', () => { permalink: '/docs/tags/tag-3', }, ], + unlisted: false, }); }); @@ -356,6 +361,7 @@ describe('simple site', () => { permalink: '/docs/tags/tag2-custom-permalink', }, ], + unlisted: false, }); }); @@ -377,6 +383,7 @@ describe('simple site', () => { unrelated_front_matter: "won't be part of metadata", }, tags: [], + unlisted: false, }); }); @@ -430,6 +437,7 @@ describe('simple site', () => { permalink: '/docs/tags/tag2-custom-permalink', }, ], + unlisted: false, }); expect(editUrlFunction).toHaveBeenCalledTimes(1); @@ -476,6 +484,7 @@ describe('simple site', () => { formattedLastUpdatedAt: 'Oct 14, 2018', lastUpdatedBy: 'Author', 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 () => { const {siteDir, context, options, currentVersion, createTestUtilsPartial} = await loadSite({ @@ -538,6 +585,7 @@ describe('simple site', () => { lastUpdatedBy: 'Custom Author', sidebarPosition: undefined, tags: [], + unlisted: false, }); }); @@ -577,6 +625,7 @@ describe('simple site', () => { lastUpdatedBy: 'Custom Author', sidebarPosition: undefined, tags: [], + unlisted: false, }); }); @@ -616,6 +665,7 @@ describe('simple site', () => { lastUpdatedBy: 'Author', sidebarPosition: undefined, tags: [], + unlisted: false, }); }); @@ -656,6 +706,7 @@ describe('simple site', () => { lastUpdatedBy: undefined, sidebarPosition: undefined, tags: [], + unlisted: false, }); }); @@ -718,12 +769,20 @@ describe('simple site', () => { ); }); - it('custom pagination', async () => { - const {defaultTestUtils, options, versionsMetadata} = await loadSite(); + it('custom pagination - production', async () => { + const {createTestUtilsPartial, options, versionsMetadata} = + await loadSite(); + const testUtils = createTestUtilsPartial({env: 'production'}); const docs = await readVersionDocs(versionsMetadata[0]!, options); - await expect( - defaultTestUtils.generateNavigation(docs), - ).resolves.toMatchSnapshot(); + await expect(testUtils.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 () => { @@ -847,6 +906,7 @@ describe('versioned site', () => { permalink: '/docs/next/tags/barTag-3-permalink', }, ], + unlisted: false, }); await currentVersionTestUtils.testMeta(path.join('hello.md'), { id: 'hello', @@ -861,6 +921,7 @@ describe('versioned site', () => { slug: '/', }, tags: [], + unlisted: false, }); }); @@ -878,6 +939,7 @@ describe('versioned site', () => { frontMatter: {slug: 'barSlug'}, version: '1.0.0', tags: [], + unlisted: false, }); await version100TestUtils.testMeta(path.join('hello.md'), { id: 'version-1.0.0/hello', @@ -894,6 +956,7 @@ describe('versioned site', () => { source: '@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md', tags: [], + unlisted: false, }); await version101TestUtils.testMeta(path.join('foo', 'bar.md'), { id: 'version-1.0.1/foo/bar', @@ -906,6 +969,7 @@ describe('versioned site', () => { version: '1.0.1', frontMatter: {}, tags: [], + unlisted: false, }); await version101TestUtils.testMeta(path.join('hello.md'), { id: 'version-1.0.1/hello', @@ -920,6 +984,7 @@ describe('versioned site', () => { slug: '/', }, tags: [], + unlisted: false, }); }); @@ -1016,6 +1081,7 @@ describe('versioned site', () => { '@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md', editUrl: hardcodedEditUrl, tags: [], + unlisted: false, }); expect(editUrlFunction).toHaveBeenCalledTimes(1); @@ -1059,6 +1125,7 @@ describe('versioned site', () => { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0/hello.md', tags: [], + unlisted: false, }); }); @@ -1094,6 +1161,7 @@ describe('versioned site', () => { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/docs/hello.md', tags: [], + unlisted: false, }); }); @@ -1130,6 +1198,7 @@ describe('versioned site', () => { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md', tags: [], + unlisted: false, }); }); @@ -1167,6 +1236,7 @@ describe('versioned site', () => { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/i18n/fr/docusaurus-plugin-content-docs/current/hello.md', tags: [], + unlisted: false, }); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts index 55da1682c725..7aeada8201b8 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts @@ -44,15 +44,12 @@ function testField(params: { params.invalidFrontMatters?.forEach(([frontMatter, message]) => { try { validateDocFrontMatter(frontMatter); - // eslint-disable-next-line jest/no-jasmine-globals - fail( - new Error( - `Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( - frontMatter, - null, - 2, - )}`, - ), + throw new Error( + `Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( + frontMatter, + null, + 2, + )}`, ); } catch (err) { // eslint-disable-next-line jest/no-conditional-expect @@ -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', () => { testField({ prefix: 'last_update', diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/globalData.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/globalData.test.ts index 35663e48b7aa..062c02404003 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/globalData.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/globalData.test.ts @@ -19,12 +19,21 @@ describe('toGlobalDataVersion', () => { permalink: '/current/main', sidebar: 'tutorial', frontMatter: {}, + unlisted: false, }, { unversionedId: 'doc', permalink: '/current/doc', sidebar: 'tutorial', frontMatter: {}, + unlisted: undefined, + }, + { + unversionedId: 'docNoSidebarUnlisted', + permalink: '/current/docNoSidebarUnlisted', + sidebar: undefined, + frontMatter: {}, + unlisted: true, }, ] as DocMetadata[]; const sidebars: Sidebars = { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts index 7103e3908d59..6a2f0f70d63f 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {toTagDocListProp} from '../props'; +import {toSidebarDocItemLinkProp, toTagDocListProp} from '../props'; describe('toTagDocListProp', () => { type Params = Parameters[0]; @@ -61,3 +61,74 @@ describe('toTagDocListProp', () => { }); }); }); + +describe('toSidebarDocItemLinkProp', () => { + type Params = Parameters[0]; + type Result = ReturnType; + 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); + }); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/client/index.ts b/packages/docusaurus-plugin-content-docs/src/client/index.ts index bf475ee00e9d..335729b898fd 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/index.ts @@ -37,7 +37,8 @@ export type GlobalDoc = { */ id: string; path: string; - sidebar: string | undefined; + sidebar?: string; + unlisted?: boolean; }; export type GlobalVersion = { diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 40fe33eb52da..da620f2876c4 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -18,6 +18,8 @@ import { posixPath, Globby, normalizeFrontMatterTags, + isUnlisted, + isDraft, } from '@docusaurus/utils'; import {getFileLastUpdate} from './lastUpdate'; @@ -35,7 +37,6 @@ import type { PropNavigationLink, LastUpdateData, VersionMetadata, - DocFrontMatter, LoadedVersion, FileChange, } from '@docusaurus/plugin-content-docs'; @@ -125,17 +126,6 @@ export async function readVersionDocs( 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({ docFile, versionMetadata, @@ -268,7 +258,8 @@ async function doProcessDocMetadata({ 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 => { try { @@ -299,6 +290,7 @@ async function doProcessDocMetadata({ slug: docSlug, permalink, draft, + unlisted, editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(), tags: normalizeFrontMatterTags(versionMetadata.tagsPath, frontMatter.tags), version: versionMetadata.versionName, @@ -333,25 +325,32 @@ export async function processDocMetadata(args: { } } -export function addDocNavigation( - docsBase: DocMetadataBase[], - sidebarsUtils: SidebarsUtils, - sidebarFilePath: string, -): LoadedVersion['docs'] { - const docsById = createDocsByIdIndex(docsBase); +function getUnlistedIds(docs: DocMetadataBase[]): Set { + return new Set(docs.filter((doc) => doc.unlisted).map((doc) => doc.id)); +} - sidebarsUtils.checkSidebarsDocIds( - docsBase.flatMap(getDocIds), - sidebarFilePath, - ); +export function addDocNavigation({ + docs, + 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 function addNavData(doc: DocMetadataBase): DocMetadata { - const navigation = sidebarsUtils.getDocNavigation( - doc.unversionedId, - doc.id, - doc.frontMatter.displayed_sidebar, - ); + const navigation = sidebarsUtils.getDocNavigation({ + unversionedId: doc.unversionedId, + versionedId: doc.id, + displayedSidebar: doc.frontMatter.displayed_sidebar, + unlistedIds, + }); const toNavigationLinkByDocId = ( 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}.`, ); } + // Gracefully handle explicitly providing an unlisted doc ID in production + if (navDoc.unlisted) { + return undefined; + } return toDocNavigationLink(navDoc); }; @@ -382,7 +385,7 @@ export function addDocNavigation( return {...doc, sidebar: navigation.sidebarName, previous, next}; } - const docsWithNavigation = docsBase.map(addNavData); + const docsWithNavigation = docs.map(addNavData); // Sort to ensure consistent output for tests docsWithNavigation.sort((a, b) => a.id.localeCompare(b.id)); return docsWithNavigation; diff --git a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts index 9d210a831b25..1cffac35f32c 100644 --- a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts @@ -11,6 +11,7 @@ import { FrontMatterTagsSchema, FrontMatterTOCHeadingLevels, validateFrontMatter, + ContentVisibilitySchema, } from '@docusaurus/utils-validation'; import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; @@ -43,7 +44,6 @@ const DocFrontMatterSchema = Joi.object({ parse_number_prefixes: Joi.boolean(), pagination_next: Joi.string().allow(null), pagination_prev: Joi.string().allow(null), - draft: Joi.boolean(), ...FrontMatterTOCHeadingLevels, last_update: Joi.object({ author: Joi.string(), @@ -54,7 +54,9 @@ const DocFrontMatterSchema = Joi.object({ 'object.missing': FrontMatterLastUpdateErrorMessage, 'object.base': FrontMatterLastUpdateErrorMessage, }), -}).unknown(); +}) + .unknown() + .concat(ContentVisibilitySchema); export function validateDocFrontMatter(frontMatter: { [key: string]: unknown; diff --git a/packages/docusaurus-plugin-content-docs/src/globalData.ts b/packages/docusaurus-plugin-content-docs/src/globalData.ts index 594e14c0f200..5679e2f68e52 100644 --- a/packages/docusaurus-plugin-content-docs/src/globalData.ts +++ b/packages/docusaurus-plugin-content-docs/src/globalData.ts @@ -23,6 +23,11 @@ function toGlobalDataDoc(doc: DocMetadata): GlobalDoc { return { id: doc.unversionedId, 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, }; } diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index 74eac5d38afe..cb34f5249a82 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -75,6 +75,9 @@ export default async function pluginContentDocs( const aliasedSource = (source: string) => `~docs/${posixPath(path.relative(pluginDataDirRoot, source))}`; + // TODO env should be injected into all plugins + const env = process.env.NODE_ENV as DocEnv; + return { name: 'docusaurus-plugin-content-docs', @@ -143,7 +146,7 @@ export default async function pluginContentDocs( versionMetadata, context, options, - env: process.env.NODE_ENV as DocEnv, + env, }); } return Promise.all(docFiles.map(processVersionDoc)); @@ -156,6 +159,9 @@ export default async function pluginContentDocs( 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 sidebars = await loadSidebars(versionMetadata.sidebarFilePath, { @@ -175,11 +181,11 @@ export default async function pluginContentDocs( return { ...versionMetadata, - docs: addDocNavigation( + docs: addDocNavigation({ docs, sidebarsUtils, - versionMetadata.sidebarFilePath as string, - ), + sidebarFilePath: versionMetadata.sidebarFilePath as string, + }), drafts, sidebars, }; diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index 3ae1e0fb7acc..9462c9c92a8a 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -398,6 +398,8 @@ declare module '@docusaurus/plugin-content-docs' { pagination_prev?: string | null; /** Should this doc be excluded from production builds? */ draft?: boolean; + /** Should this doc be accessible but hidden in production builds? */ + unlisted?: boolean; /** Allows overriding the last updated author and/or date. */ last_update?: FileChange; }; @@ -448,6 +450,11 @@ declare module '@docusaurus/plugin-content-docs' { * Draft docs will be excluded for production environment. */ 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 * or number prefix. diff --git a/packages/docusaurus-plugin-content-docs/src/props.ts b/packages/docusaurus-plugin-content-docs/src/props.ts index c9f58096ac50..c53329564a49 100644 --- a/packages/docusaurus-plugin-content-docs/src/props.ts +++ b/packages/docusaurus-plugin-content-docs/src/props.ts @@ -28,6 +28,37 @@ import type { LoadedVersion, } 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 { const docsById = createDocsByIdIndex(loadedVersion.docs); @@ -44,21 +75,8 @@ Available document ids are: } const convertDocLink = (item: SidebarItemDoc): PropSidebarItemLink => { - const docMetadata = getDocById(item.id); - const { - 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, - }; + const doc = getDocById(item.id); + return toSidebarDocItemLinkProp({item, doc}); }; 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( link: SidebarItemCategoryLink | undefined, ) { @@ -88,12 +115,14 @@ Available document ids are: function convertCategory(item: SidebarItemCategory): PropSidebarItemCategory { const {link, ...rest} = item; const href = getCategoryLinkHref(link); + const linkUnlisted = getCategoryLinkUnlisted(link); const customProps = item.customProps ?? getCategoryLinkCustomProps(link); return { ...rest, items: item.items.map(normalizeItem), ...(href && {href}), + ...(linkUnlisted && {linkUnlisted}), ...(customProps && {customProps}), }; } @@ -180,15 +209,18 @@ export function toTagDocListProp({ allTagsPath, count: tag.docIds.length, items: toDocListProp(), + unlisted: tag.unlisted, }; } export function toTagsListTagsProp( versionTags: VersionTags, ): PropTagsListPage['tags'] { - return Object.values(versionTags).map((tagValue) => ({ - label: tagValue.label, - permalink: tagValue.permalink, - count: tagValue.docIds.length, - })); + return Object.values(versionTags) + .filter((tagValue) => !tagValue.unlisted) + .map((tagValue) => ({ + label: tagValue.label, + permalink: tagValue.permalink, + count: tagValue.docIds.length, + })); } diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts index 60c30b96a889..bc6c50b74c86 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts @@ -153,7 +153,14 @@ describe('createSidebarsUtils', () => { }); it('getDocNavigation', () => { - expect(getDocNavigation('doc1', 'doc1', undefined)).toEqual({ + expect( + getDocNavigation({ + unversionedId: 'doc1', + versionedId: 'doc1', + displayedSidebar: undefined, + unlistedIds: new Set(), + }), + ).toEqual({ sidebarName: 'sidebar1', previous: undefined, next: { @@ -161,7 +168,14 @@ describe('createSidebarsUtils', () => { id: 'doc2', }, }); - expect(getDocNavigation('doc2', 'doc2', undefined)).toEqual({ + expect( + getDocNavigation({ + unversionedId: 'doc2', + versionedId: 'doc2', + displayedSidebar: undefined, + unlistedIds: new Set(), + }), + ).toEqual({ sidebarName: 'sidebar1', previous: { type: 'doc', @@ -170,7 +184,14 @@ describe('createSidebarsUtils', () => { next: undefined, }); - expect(getDocNavigation('doc3', 'doc3', undefined)).toEqual({ + expect( + getDocNavigation({ + unversionedId: 'doc3', + versionedId: 'doc3', + displayedSidebar: undefined, + unlistedIds: new Set(), + }), + ).toEqual({ sidebarName: 'sidebar2', previous: undefined, next: { @@ -178,7 +199,14 @@ describe('createSidebarsUtils', () => { id: 'doc4', }, }); - expect(getDocNavigation('doc4', 'doc4', undefined)).toEqual({ + expect( + getDocNavigation({ + unversionedId: 'doc4', + versionedId: 'doc4', + displayedSidebar: undefined, + unlistedIds: new Set(), + }), + ).toEqual({ sidebarName: 'sidebar2', previous: { type: 'doc', @@ -188,7 +216,14 @@ describe('createSidebarsUtils', () => { next: undefined, }); - expect(getDocNavigation('doc5', 'doc5', undefined)).toMatchObject({ + expect( + getDocNavigation({ + unversionedId: 'doc5', + versionedId: 'doc5', + displayedSidebar: undefined, + unlistedIds: new Set(), + }), + ).toMatchObject({ sidebarName: 'sidebar3', previous: undefined, next: { @@ -196,7 +231,14 @@ describe('createSidebarsUtils', () => { label: 'S3 SubCategory', }, }); - expect(getDocNavigation('doc6', 'doc6', undefined)).toMatchObject({ + expect( + getDocNavigation({ + unversionedId: 'doc6', + versionedId: 'doc6', + displayedSidebar: undefined, + unlistedIds: new Set(), + }), + ).toMatchObject({ sidebarName: 'sidebar3', previous: { type: 'category', @@ -207,7 +249,14 @@ describe('createSidebarsUtils', () => { id: 'doc7', }, }); - expect(getDocNavigation('doc7', 'doc7', undefined)).toEqual({ + expect( + getDocNavigation({ + unversionedId: 'doc7', + versionedId: 'doc7', + displayedSidebar: undefined, + unlistedIds: new Set(), + }), + ).toEqual({ sidebarName: 'sidebar3', previous: { type: 'doc', @@ -215,17 +264,36 @@ describe('createSidebarsUtils', () => { }, next: undefined, }); - expect(getDocNavigation('doc3', 'doc3', null)).toEqual({ + expect( + getDocNavigation({ + unversionedId: 'doc3', + versionedId: 'doc3', + displayedSidebar: null, + unlistedIds: new Set(), + }), + ).toEqual({ sidebarName: undefined, previous: undefined, next: undefined, }); expect(() => - getDocNavigation('doc3', 'doc3', 'foo'), + getDocNavigation({ + unversionedId: 'doc3', + versionedId: 'doc3', + displayedSidebar: 'foo', + unlistedIds: new Set(), + }), ).toThrowErrorMatchingInlineSnapshot( `"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', previous: undefined, next: undefined, diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts index 555004846ea4..d6d7f07a7587 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts @@ -183,11 +183,18 @@ export type PropSidebarItemCategory = Expand< SidebarItemCategoryBase & { items: PropSidebarItem[]; 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 & { docId?: string; + unlisted?: boolean; }; export type PropSidebarItemHtml = SidebarItemHtml; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts index 1ac140cd627b..59d10f4a1430 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts @@ -135,11 +135,12 @@ export type SidebarsUtils = { sidebars: Sidebars; getFirstDocIdOfFirstSidebar: () => string | undefined; getSidebarNameByDocId: (docId: string) => string | undefined; - getDocNavigation: ( - unversionedId: string, - versionedId: string, - displayedSidebar: string | null | undefined, - ) => SidebarNavigation; + getDocNavigation: (params: { + unversionedId: string; + versionedId: string; + displayedSidebar: string | null | undefined; + unlistedIds: Set; + }) => SidebarNavigation; getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[]; getCategoryGeneratedIndexNavigation: ( categoryGeneratedIndexPermalink: string, @@ -192,11 +193,17 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils { }; } - function getDocNavigation( - unversionedId: string, - versionedId: string, - displayedSidebar: string | null | undefined, - ): SidebarNavigation { + function getDocNavigation({ + unversionedId, + versionedId, + displayedSidebar, + unlistedIds, + }: { + unversionedId: string; + versionedId: string; + displayedSidebar: string | null | undefined; + unlistedIds: Set; + }): SidebarNavigation { // TODO legacy id retro-compatibility! let docId = unversionedId; let sidebarName = @@ -211,12 +218,28 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils { if (!sidebarName) { return emptySidebarNavigation(); } - const navigationItems = sidebarNameToNavigationItems[sidebarName]; + let navigationItems = sidebarNameToNavigationItems[sidebarName]; if (!navigationItems) { throw new Error( `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) => { if (item.type === 'doc') { return item.id === docId; diff --git a/packages/docusaurus-plugin-content-docs/src/tags.ts b/packages/docusaurus-plugin-content-docs/src/tags.ts index dfa0e3284587..1fd784c213fa 100644 --- a/packages/docusaurus-plugin-content-docs/src/tags.ts +++ b/packages/docusaurus-plugin-content-docs/src/tags.ts @@ -6,15 +6,22 @@ */ import _ from 'lodash'; -import {groupTaggedItems} from '@docusaurus/utils'; +import {getTagVisibility, groupTaggedItems} from '@docusaurus/utils'; import type {VersionTags} from './types'; import type {DocMetadata} from '@docusaurus/plugin-content-docs'; export function getVersionTags(docs: DocMetadata[]): VersionTags { const groups = groupTaggedItems(docs, (doc) => doc.tags); - return _.mapValues(groups, (group) => ({ - label: group.tag.label, - docIds: group.items.map((item) => item.id), - permalink: group.tag.permalink, - })); + return _.mapValues(groups, ({tag, items: tagDocs}) => { + const tagVisibility = getTagVisibility({ + items: tagDocs, + isUnlisted: (item) => item.unlisted, + }); + return { + label: tag.label, + docIds: tagVisibility.listedItems.map((item) => item.id), + permalink: tag.permalink, + unlisted: tagVisibility.unlisted, + }; + }); } diff --git a/packages/docusaurus-plugin-content-docs/src/types.ts b/packages/docusaurus-plugin-content-docs/src/types.ts index 4c4568517fbb..d78521c71684 100644 --- a/packages/docusaurus-plugin-content-docs/src/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/types.ts @@ -27,6 +27,7 @@ export type SourceToPermalink = { export type VersionTag = Tag & { /** All doc ids having this tag. */ docIds: string[]; + unlisted: boolean; }; export type VersionTags = { [permalink: string]: VersionTag; diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap index c4c22e849260..56028498771e 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap @@ -19,6 +19,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` "source": "@site/src/pages/hello/index.md", "title": "Index", "type": "mdx", + "unlisted": false, }, { "description": "my MDX page", @@ -30,6 +31,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` "source": "@site/src/pages/hello/mdxPage.mdx", "title": "MDX page", "type": "mdx", + "unlisted": false, }, { "permalink": "/hello/translatedJs", @@ -43,6 +45,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` "source": "@site/src/pages/hello/translatedMd.md", "title": undefined, "type": "mdx", + "unlisted": false, }, { "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", "title": "Index", "type": "mdx", + "unlisted": false, }, { "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", "title": "MDX page", "type": "mdx", + "unlisted": false, }, { "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", "title": undefined, "type": "mdx", + "unlisted": false, }, { "permalink": "/fr/hello/world", diff --git a/packages/docusaurus-plugin-content-pages/src/frontMatter.ts b/packages/docusaurus-plugin-content-pages/src/frontMatter.ts index 8c9247da710a..87562de53e34 100644 --- a/packages/docusaurus-plugin-content-pages/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-pages/src/frontMatter.ts @@ -9,6 +9,7 @@ import { Joi, validateFrontMatter, FrontMatterTOCHeadingLevels, + ContentVisibilitySchema, } from '@docusaurus/utils-validation'; import type {FrontMatter} from '@docusaurus/plugin-content-pages'; @@ -18,7 +19,7 @@ const PageFrontMatterSchema = Joi.object({ wrapperClassName: Joi.string(), hide_table_of_contents: Joi.boolean(), ...FrontMatterTOCHeadingLevels, -}); +}).concat(ContentVisibilitySchema); export function validatePageFrontMatter(frontMatter: { [key: string]: unknown; diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index c3c51b883283..ab1b3c70ccf3 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -20,6 +20,8 @@ import { normalizeUrl, DEFAULT_PLUGIN_ID, parseMarkdownString, + isUnlisted, + isDraft, } from '@docusaurus/utils'; import {validatePageFrontMatter} from './frontMatter'; @@ -82,7 +84,9 @@ export default function pluginContentPages( ignore: options.exclude, }); - async function toMetadata(relativeSource: string): Promise { + async function processPageSourceFile( + relativeSource: string, + ): Promise { // Lookup in localized folder in priority const contentPath = await getFolderContainingFile( getContentPathList(contentPaths), @@ -110,6 +114,12 @@ export default function pluginContentPages( excerpt, } = parseMarkdownString(content); const frontMatter = validatePageFrontMatter(unsafeFrontMatter); + + if (isDraft({frontMatter})) { + return undefined; + } + const unlisted = isUnlisted({frontMatter}); + return { type: 'mdx', permalink, @@ -117,10 +127,24 @@ export default function pluginContentPages( title: frontMatter.title ?? contentTitle, description: frontMatter.description ?? excerpt, 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}) { diff --git a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts index f60623c7de65..09279828392a 100644 --- a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts +++ b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts @@ -27,6 +27,8 @@ declare module '@docusaurus/plugin-content-pages' { readonly hide_table_of_contents?: string; readonly toc_min_heading_level?: number; readonly toc_max_heading_level?: number; + readonly draft?: boolean; + readonly unlisted?: boolean; }; export type JSXPageMetadata = { @@ -42,6 +44,7 @@ declare module '@docusaurus/plugin-content-pages' { frontMatter: FrontMatter & {[key: string]: unknown}; title?: string; description?: string; + unlisted: boolean; }; export type Metadata = JSXPageMetadata | MDXPageMetadata; diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 2e89d14c8ddd..be4cf3fa94e2 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -1494,6 +1494,14 @@ declare module '@theme/Tag' { 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' { import type * as PrismNamespace from 'prismjs'; diff --git a/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx index 2dec817ef470..f640f603347b 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogPostPage/index.tsx @@ -15,6 +15,7 @@ import BlogPostPaginator from '@theme/BlogPostPaginator'; import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata'; import TOC from '@theme/TOC'; import type {Props} from '@theme/BlogPostPage'; +import Unlisted from '@theme/Unlisted'; import type {BlogSidebar} from '@docusaurus/plugin-content-blog'; function BlogPostPageContent({ @@ -25,7 +26,7 @@ function BlogPostPageContent({ children: ReactNode; }): JSX.Element { const {metadata, toc} = useBlogPost(); - const {nextItem, prevItem, frontMatter} = metadata; + const {nextItem, prevItem, frontMatter, unlisted} = metadata; const { hide_table_of_contents: hideTableOfContents, toc_min_heading_level: tocMinHeadingLevel, @@ -43,6 +44,7 @@ function BlogPostPageContent({ /> ) : undefined }> + {unlisted && } {children} {(nextItem || prevItem) && ( diff --git a/packages/docusaurus-theme-classic/src/theme/BlogSidebar/Desktop/index.tsx b/packages/docusaurus-theme-classic/src/theme/BlogSidebar/Desktop/index.tsx index f010d2144836..8488a49e3116 100644 --- a/packages/docusaurus-theme-classic/src/theme/BlogSidebar/Desktop/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BlogSidebar/Desktop/index.tsx @@ -9,11 +9,13 @@ import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; import {translate} from '@docusaurus/Translate'; +import {useVisibleBlogSidebarItems} from '@docusaurus/theme-common/internal'; import type {Props} from '@theme/BlogSidebar/Desktop'; import styles from './styles.module.css'; export default function BlogSidebarDesktop({sidebar}: Props): JSX.Element { + const items = useVisibleBlogSidebarItems(sidebar.items); return (