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 660a0ad549..9ed12034a2 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 @@ -5,9 +5,11 @@ Object { "version-1.0.0/docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-1.0.0/foo/bar", @@ -36,6 +38,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-1.0.0/hello", @@ -54,6 +57,7 @@ Object { "version-2.0.0/docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-2.0.0/foo/bar", @@ -65,6 +69,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-2.0.0/hello", 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 a412668ff4..477eecdb76 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 @@ -27,9 +27,11 @@ Object { "docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "foo/bar", @@ -58,6 +60,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "hello", @@ -410,12 +413,12 @@ Object { \\"docsSidebars\\": { \\"docs\\": [ { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { - \\"collapsed\\": true, \\"type\\": \\"category\\", \\"label\\": \\"foo\\", \\"items\\": [ @@ -429,7 +432,9 @@ Object { \\"label\\": \\"baz\\", \\"href\\": \\"/docs/foo/bazSlug.html\\" } - ] + ], + \\"collapsible\\": true, + \\"collapsed\\": true }, { \\"type\\": \\"link\\", @@ -444,8 +449,9 @@ Object { ] }, { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -806,6 +812,10 @@ Object { "type": "autogenerated", }, "numberPrefixParser": [Function], + "options": Object { + "sidebarCollapsed": true, + "sidebarCollapsible": true, + }, "version": Object { "contentPath": "docs", "versionName": "current", @@ -992,6 +1002,7 @@ Object { "version-1.0.0/docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-1.0.0/foo/bar", @@ -1007,6 +1018,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-1.0.0/hello", @@ -1025,6 +1037,7 @@ Object { "version-1.0.1/docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-1.0.1/foo/bar", @@ -1036,6 +1049,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-1.0.1/hello", @@ -1054,6 +1068,7 @@ Object { "docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "foo/bar", @@ -1065,6 +1080,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "hello", @@ -1404,8 +1420,9 @@ Object { \\"docsSidebars\\": { \\"version-1.0.0/docs\\": [ { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -1421,8 +1438,9 @@ Object { ] }, { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -1444,8 +1462,9 @@ Object { \\"docsSidebars\\": { \\"version-1.0.1/docs\\": [ { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -1456,8 +1475,9 @@ Object { ] }, { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -1479,8 +1499,9 @@ Object { \\"docsSidebars\\": { \\"docs\\": [ { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -1491,8 +1512,9 @@ Object { ] }, { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -1514,8 +1536,9 @@ Object { \\"docsSidebars\\": { \\"version-1.0.1/docs\\": [ { - \\"collapsed\\": true, \\"type\\": \\"category\\", + \\"collapsed\\": true, + \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -1888,6 +1911,7 @@ Object { "version-1.0.1/docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "version-withSlugs/rootAbsoluteSlug", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/sidebars.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/sidebars.test.ts.snap index edf6e3d583..24147bc07c 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/sidebars.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/sidebars.test.ts.snap @@ -5,6 +5,7 @@ Object { "docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "href": "https://github.com", @@ -24,9 +25,11 @@ Object { "docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "collapsed": false, + "collapsible": true, "items": Array [ Object { "id": "doc1", @@ -42,9 +45,11 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "collapsed": false, + "collapsible": true, "items": Array [ Object { "id": "doc2", @@ -67,6 +72,7 @@ Object { "docs": Array [ Object { "collapsed": false, + "collapsible": true, "items": Array [ Object { "id": "doc1", @@ -78,6 +84,7 @@ Object { }, Object { "collapsed": false, + "collapsible": true, "items": Array [ Object { "id": "doc2", @@ -96,6 +103,7 @@ Object { "docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "a", @@ -103,9 +111,11 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "c", @@ -113,6 +123,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "d", @@ -120,6 +131,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "e", @@ -158,6 +170,7 @@ Object { "docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "greeting", @@ -180,6 +193,7 @@ Object { "docs": Array [ Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "foo/bar", @@ -204,6 +218,7 @@ Object { }, Object { "collapsed": true, + "collapsible": true, "items": Array [ Object { "id": "hello", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts index 0f6db9eded..ea004a7302 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts @@ -7,7 +7,7 @@ import path from 'path'; import {cliDocsVersionCommand} from '../cli'; -import {PathOptions} from '../types'; +import {PathOptions, SidebarOptions} from '../types'; import fs from 'fs-extra'; import { getVersionedDocsDirPath, @@ -21,9 +21,12 @@ const fixtureDir = path.join(__dirname, '__fixtures__'); describe('docsVersion', () => { const simpleSiteDir = path.join(fixtureDir, 'simple-site'); const versionedSiteDir = path.join(fixtureDir, 'versioned-site'); - const DEFAULT_OPTIONS: PathOptions = { + + const DEFAULT_OPTIONS: PathOptions & SidebarOptions = { path: 'docs', sidebarPath: '', + sidebarCollapsed: true, + sidebarCollapsible: true, }; test('no version tag provided', () => { @@ -186,17 +189,17 @@ describe('docsVersion', () => { let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { versionedSidebarPath = filepath; - versionedSidebar = JSON.parse(content); + versionedSidebar = JSON.parse(content as string); }); let versionsPath; let versions; writeMock.mockImplementationOnce((filepath, content) => { versionsPath = filepath; - versions = JSON.parse(content); + versions = JSON.parse(content as string); }); const consoleMock = jest.spyOn(console, 'log').mockImplementation(); const options = { - path: 'docs', + ...DEFAULT_OPTIONS, sidebarPath: path.join(simpleSiteDir, 'sidebars.json'), }; cliDocsVersionCommand('1.0.0', simpleSiteDir, DEFAULT_PLUGIN_ID, options); @@ -234,17 +237,17 @@ describe('docsVersion', () => { let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { versionedSidebarPath = filepath; - versionedSidebar = JSON.parse(content); + versionedSidebar = JSON.parse(content as string); }); let versionsPath; let versions; writeMock.mockImplementationOnce((filepath, content) => { versionsPath = filepath; - versions = JSON.parse(content); + versions = JSON.parse(content as string); }); const consoleMock = jest.spyOn(console, 'log').mockImplementation(); const options = { - path: 'docs', + ...DEFAULT_OPTIONS, sidebarPath: path.join(versionedSiteDir, 'sidebars.json'), }; cliDocsVersionCommand( @@ -289,16 +292,17 @@ describe('docsVersion', () => { let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { versionedSidebarPath = filepath; - versionedSidebar = JSON.parse(content); + versionedSidebar = JSON.parse(content as string); }); let versionsPath; let versions; writeMock.mockImplementationOnce((filepath, content) => { versionsPath = filepath; - versions = JSON.parse(content); + versions = JSON.parse(content as string); }); const consoleMock = jest.spyOn(console, 'log').mockImplementation(); const options = { + ...DEFAULT_OPTIONS, path: 'community', sidebarPath: path.join(versionedSiteDir, 'community_sidebars.json'), }; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index 9caa702f7e..a3382b2621 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -270,6 +270,8 @@ describe('simple website', () => { expect(mock).toHaveBeenCalledWith('1.0.0', siteDir, DEFAULT_PLUGIN_ID, { path: 'docs', sidebarPath, + sidebarCollapsed: true, + sidebarCollapsible: true, }); mock.mockRestore(); }); @@ -478,6 +480,8 @@ describe('versioned website', () => { expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, DEFAULT_PLUGIN_ID, { path: routeBasePath, sidebarPath, + sidebarCollapsed: true, + sidebarCollapsible: true, }); mock.mockRestore(); }); @@ -729,6 +733,8 @@ describe('versioned website (community)', () => { expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, pluginId, { path: routeBasePath, sidebarPath, + sidebarCollapsed: true, + sidebarCollapsible: true, }); mock.mockRestore(); }); @@ -907,6 +913,7 @@ describe('site with full autogenerated sidebar', () => { type: 'category', label: 'Guides', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -938,6 +945,7 @@ describe('site with full autogenerated sidebar', () => { type: 'category', label: 'API (label from _category_.json)', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -947,6 +955,7 @@ describe('site with full autogenerated sidebar', () => { type: 'category', label: 'Core APIs', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -963,6 +972,7 @@ describe('site with full autogenerated sidebar', () => { type: 'category', label: 'Extension APIs (label from _category_.yml)', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -1461,6 +1471,7 @@ describe('site with partial autogenerated sidebars', () => { type: 'category', label: 'Some category', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -1650,6 +1661,7 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => { type: 'category', label: 'Core APIs', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -1666,6 +1678,7 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => { type: 'category', label: 'Extension APIs (label from _category_.yml)', // Fix #4638 collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -1782,6 +1795,7 @@ describe('site with custom sidebar items generator', () => { type: 'category', label: 'API (label from _category_.json)', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -1791,6 +1805,7 @@ describe('site with custom sidebar items generator', () => { type: 'category', label: 'Extension APIs (label from _category_.yml)', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -1806,6 +1821,7 @@ describe('site with custom sidebar items generator', () => { type: 'category', label: 'Core APIs', collapsed: true, + collapsible: true, items: [ { type: 'doc', @@ -1827,6 +1843,7 @@ describe('site with custom sidebar items generator', () => { type: 'category', label: 'Guides', collapsed: true, + collapsible: true, items: [ { type: 'doc', diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts index 3abdc6d9df..cb50e80d62 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {OptionsSchema, DEFAULT_OPTIONS} from '../options'; +import {OptionsSchema, DEFAULT_OPTIONS, validateOptions} from '../options'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator'; import { @@ -13,11 +13,22 @@ import { DisabledNumberPrefixParser, } from '../numberPrefix'; import {GlobExcludeDefault} from '@docusaurus/utils'; +import {PluginOptions} from '../types'; // the type of remark/rehype plugins is function const markdownPluginsFunctionStub = () => {}; const markdownPluginsObjectStub = {}; +function testValidateOptions(options: Partial) { + return validateOptions({ + options: { + ...DEFAULT_OPTIONS, + ...options, + }, + validate: normalizePluginOptions, + }); +} + describe('normalizeDocsPluginOptions', () => { test('should return default options for undefined user options', async () => { const {value, error} = await OptionsSchema.validate({}); @@ -58,6 +69,8 @@ describe('normalizeDocsPluginOptions', () => { label: 'world', }, }, + sidebarCollapsible: false, + sidebarCollapsed: false, }; const {value, error} = await OptionsSchema.validate(userOptions); expect(value).toEqual(userOptions); @@ -230,4 +243,30 @@ describe('normalizeDocsPluginOptions', () => { `"\\"versions.current.hey\\" is not allowed"`, ); }); + + test('should handle sidebarCollapsed option inconsistencies', () => { + expect( + testValidateOptions({ + ...DEFAULT_OPTIONS, + sidebarCollapsible: true, + sidebarCollapsed: undefined, + }).sidebarCollapsed, + ).toEqual(true); + + expect( + testValidateOptions({ + ...DEFAULT_OPTIONS, + sidebarCollapsible: false, + sidebarCollapsed: undefined, + }).sidebarCollapsed, + ).toEqual(false); + + expect( + testValidateOptions({ + ...DEFAULT_OPTIONS, + sidebarCollapsible: false, + sidebarCollapsed: true, + }).sidebarCollapsed, + ).toEqual(false); + }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebarItemsGenerator.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebarItemsGenerator.test.ts index 69b26ba2ce..2794285022 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebarItemsGenerator.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebarItemsGenerator.test.ts @@ -9,14 +9,13 @@ import { CategoryMetadatasFile, DefaultSidebarItemsGenerator, } from '../sidebarItemsGenerator'; -import {DefaultCategoryCollapsedValue} from '../sidebars'; import {Sidebar, SidebarItemsGenerator} from '../types'; import fs from 'fs-extra'; import {DefaultNumberPrefixParser} from '../numberPrefix'; describe('DefaultSidebarItemsGenerator', () => { function testDefaultSidebarItemsGenerator( - options: Partial[0]>, + params: Partial[0]>, ) { return DefaultSidebarItemsGenerator({ numberPrefixParser: DefaultNumberPrefixParser, @@ -29,7 +28,11 @@ describe('DefaultSidebarItemsGenerator', () => { contentPath: 'docs', }, docs: [], - ...options, + options: { + sidebarCollapsed: true, + sidebarCollapsible: true, + }, + ...params, }); } @@ -110,6 +113,10 @@ describe('DefaultSidebarItemsGenerator', () => { frontMatter: {}, }, ], + options: { + sidebarCollapsed: true, + sidebarCollapsible: true, + }, }); expect(sidebarSlice).toEqual([ @@ -190,6 +197,10 @@ describe('DefaultSidebarItemsGenerator', () => { frontMatter: {}, }, ], + options: { + sidebarCollapsed: true, + sidebarCollapsible: true, + }, }); expect(sidebarSlice).toEqual([ @@ -197,7 +208,8 @@ describe('DefaultSidebarItemsGenerator', () => { { type: 'category', label: 'Tutorials', - collapsed: DefaultCategoryCollapsedValue, + collapsed: true, + collapsible: true, items: [ {type: 'doc', id: 'tutorial1'}, {type: 'doc', id: 'tutorial2'}, @@ -207,12 +219,14 @@ describe('DefaultSidebarItemsGenerator', () => { type: 'category', label: 'Guides', collapsed: false, + collapsible: true, items: [ {type: 'doc', id: 'guide1'}, { type: 'category', label: 'SubGuides (metadata file label)', - collapsed: DefaultCategoryCollapsedValue, + collapsed: true, + collapsible: true, items: [{type: 'doc', id: 'nested-guide'}], }, {type: 'doc', id: 'guide2'}, @@ -233,6 +247,7 @@ describe('DefaultSidebarItemsGenerator', () => { 'subfolder/subsubfolder/subsubsubfolder3/_category_.json': { position: 1, label: 'subsubsubfolder3 (_category_.json label)', + collapsible: false, collapsed: false, }, }); @@ -305,6 +320,10 @@ describe('DefaultSidebarItemsGenerator', () => { frontMatter: {}, }, ], + options: { + sidebarCollapsed: true, + sidebarCollapsible: true, + }, }); expect(sidebarSlice).toEqual([ @@ -312,6 +331,7 @@ describe('DefaultSidebarItemsGenerator', () => { type: 'category', label: 'subsubsubfolder3 (_category_.json label)', collapsed: false, + collapsible: false, items: [ {type: 'doc', id: 'doc8'}, {type: 'doc', id: 'doc7'}, @@ -321,6 +341,7 @@ describe('DefaultSidebarItemsGenerator', () => { type: 'category', label: 'subsubsubfolder2 (_category_.yml label)', collapsed: true, + collapsible: true, items: [{type: 'doc', id: 'doc6'}], }, {type: 'doc', id: 'doc1'}, @@ -329,6 +350,7 @@ describe('DefaultSidebarItemsGenerator', () => { type: 'category', label: 'subsubsubfolder', collapsed: true, + collapsible: true, items: [{type: 'doc', id: 'doc5'}], }, ] as Sidebar); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts index 7f33856441..72042351f4 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/sidebars.test.ts @@ -17,6 +17,7 @@ import { processSidebars, DefaultSidebars, DisabledSidebars, + fixSidebarItemInconsistencies, } from '../sidebars'; import { Sidebar, @@ -24,6 +25,8 @@ import { SidebarItemsGenerator, Sidebars, UnprocessedSidebars, + SidebarOptions, + SidebarItemCategory, } from '../types'; import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator'; @@ -31,15 +34,19 @@ import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator'; describe('loadSidebars', () => { const fixtureDir = path.join(__dirname, '__fixtures__', 'sidebars'); + const options: SidebarOptions = { + sidebarCollapsed: true, + sidebarCollapsible: true, + }; test('sidebars with known sidebar item type', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars.json'); - const result = loadSidebars(sidebarPath); + const result = loadSidebars(sidebarPath, options); expect(result).toMatchSnapshot(); }); test('sidebars with deep level of category', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-category.js'); - const result = loadSidebars(sidebarPath); + const result = loadSidebars(sidebarPath, options); expect(result).toMatchSnapshot(); }); @@ -49,8 +56,8 @@ describe('loadSidebars', () => { fixtureDir, 'sidebars-category-shorthand.js', ); - const sidebar1 = loadSidebars(sidebarPath1); - const sidebar2 = loadSidebars(sidebarPath2); + const sidebar1 = loadSidebars(sidebarPath1, options); + const sidebar2 = loadSidebars(sidebarPath2, options); expect(sidebar1).toEqual(sidebar2); }); @@ -59,7 +66,9 @@ describe('loadSidebars', () => { fixtureDir, 'sidebars-category-wrong-items.json', ); - expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot( + expect(() => + loadSidebars(sidebarPath, options), + ).toThrowErrorMatchingInlineSnapshot( `"Error loading {\\"type\\":\\"category\\",\\"label\\":\\"Category Label\\",\\"items\\":\\"doc1\\"}: \\"items\\" must be an array."`, ); }); @@ -69,7 +78,9 @@ describe('loadSidebars', () => { fixtureDir, 'sidebars-category-wrong-label.json', ); - expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot( + expect(() => + loadSidebars(sidebarPath, options), + ).toThrowErrorMatchingInlineSnapshot( `"Error loading {\\"type\\":\\"category\\",\\"label\\":true,\\"items\\":[\\"doc1\\"]}: \\"label\\" must be a string."`, ); }); @@ -79,7 +90,9 @@ describe('loadSidebars', () => { fixtureDir, 'sidebars-doc-id-not-string.json', ); - expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot( + expect(() => + loadSidebars(sidebarPath, options), + ).toThrowErrorMatchingInlineSnapshot( `"Error loading {\\"type\\":\\"doc\\",\\"id\\":[\\"doc1\\"]}: \\"id\\" must be a string."`, ); }); @@ -89,33 +102,38 @@ describe('loadSidebars', () => { fixtureDir, 'sidebars-first-level-not-category.js', ); - const result = loadSidebars(sidebarPath); + const result = loadSidebars(sidebarPath, options); expect(result).toMatchSnapshot(); }); test('sidebars link', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-link.json'); - const result = loadSidebars(sidebarPath); + const result = loadSidebars(sidebarPath, options); expect(result).toMatchSnapshot(); }); test('sidebars link wrong label', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-label.json'); - expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot( + expect(() => + loadSidebars(sidebarPath, options), + ).toThrowErrorMatchingInlineSnapshot( `"Error loading {\\"type\\":\\"link\\",\\"label\\":false,\\"href\\":\\"https://github.com\\"}: \\"label\\" must be a string."`, ); }); test('sidebars link wrong href', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-href.json'); - expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot( + expect(() => + loadSidebars(sidebarPath, options), + ).toThrowErrorMatchingInlineSnapshot( `"Error loading {\\"type\\":\\"link\\",\\"label\\":\\"GitHub\\",\\"href\\":[\\"example.com\\"]}: \\"href\\" must be a string."`, ); }); test('sidebars with unknown sidebar item type', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-unknown-type.json'); - expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot(` + expect(() => loadSidebars(sidebarPath, options)) + .toThrowErrorMatchingInlineSnapshot(` "Unknown sidebar item type \\"superman\\". Sidebar item is {\\"type\\":\\"superman\\"}. " `); @@ -123,26 +141,28 @@ describe('loadSidebars', () => { test('sidebars with known sidebar item type but wrong field', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-wrong-field.json'); - expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot( + expect(() => + loadSidebars(sidebarPath, options), + ).toThrowErrorMatchingInlineSnapshot( `"Unknown sidebar item keys: href. Item: {\\"type\\":\\"category\\",\\"label\\":\\"category\\",\\"href\\":\\"https://github.com\\"}"`, ); }); test('unexisting path', () => { - expect(loadSidebars('badpath')).toEqual(DisabledSidebars); + expect(loadSidebars('badpath', options)).toEqual(DisabledSidebars); }); test('undefined path', () => { - expect(loadSidebars(undefined)).toEqual(DefaultSidebars); + expect(loadSidebars(undefined, options)).toEqual(DefaultSidebars); }); test('literal false path', () => { - expect(loadSidebars(false)).toEqual(DisabledSidebars); + expect(loadSidebars(false, options)).toEqual(DisabledSidebars); }); test('sidebars with category.collapsed property', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-collapsed.json'); - const result = loadSidebars(sidebarPath); + const result = loadSidebars(sidebarPath, options); expect(result).toMatchSnapshot(); }); @@ -151,7 +171,7 @@ describe('loadSidebars', () => { fixtureDir, 'sidebars-collapsed-first-level.json', ); - const result = loadSidebars(sidebarPath); + const result = loadSidebars(sidebarPath, options); expect(result).toMatchSnapshot(); }); }); @@ -162,23 +182,27 @@ describe('collectSidebarDocItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category1', items: [ { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 1', items: [{type: 'doc', id: 'doc1'}], }, { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 2', items: [ {type: 'doc', id: 'doc2'}, { type: 'category', collapsed: false, + collapsible: true, label: 'Sub sub category 1', items: [{type: 'doc', id: 'doc3'}], }, @@ -189,6 +213,7 @@ describe('collectSidebarDocItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category2', items: [ {type: 'doc', id: 'doc4'}, @@ -213,23 +238,27 @@ describe('collectSidebarCategories', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category1', items: [ { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 1', items: [{type: 'doc', id: 'doc1'}], }, { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 2', items: [ {type: 'doc', id: 'doc2'}, { type: 'category', collapsed: false, + collapsible: true, label: 'Sub sub category 1', items: [{type: 'doc', id: 'doc3'}], }, @@ -240,6 +269,7 @@ describe('collectSidebarCategories', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category2', items: [ {type: 'doc', id: 'doc4'}, @@ -266,6 +296,7 @@ describe('collectSidebarLinks', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category1', items: [ { @@ -276,6 +307,7 @@ describe('collectSidebarLinks', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 2', items: [ { @@ -302,11 +334,13 @@ describe('collectSidebarsDocIds', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category1', items: [ { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 1', items: [{type: 'doc', id: 'doc1'}], }, @@ -319,6 +353,7 @@ describe('collectSidebarsDocIds', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category2', items: [ {type: 'doc', id: 'doc3'}, @@ -345,11 +380,13 @@ describe('transformSidebarItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category1', items: [ { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 1', items: [{type: 'doc', id: 'doc1'}], customProps: {fakeProp: false}, @@ -357,12 +394,14 @@ describe('transformSidebarItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 2', items: [ {type: 'doc', id: 'doc2'}, { type: 'category', collapsed: false, + collapsible: true, label: 'Sub sub category 1', items: [ {type: 'doc', id: 'doc3', customProps: {lorem: 'ipsum'}}, @@ -375,6 +414,7 @@ describe('transformSidebarItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category2', items: [ {type: 'doc', id: 'doc4'}, @@ -394,11 +434,13 @@ describe('transformSidebarItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'MODIFIED LABEL: Category1', items: [ { type: 'category', collapsed: false, + collapsible: true, label: 'MODIFIED LABEL: Subcategory 1', items: [{type: 'doc', id: 'doc1'}], customProps: {fakeProp: false}, @@ -406,12 +448,14 @@ describe('transformSidebarItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'MODIFIED LABEL: Subcategory 2', items: [ {type: 'doc', id: 'doc2'}, { type: 'category', collapsed: false, + collapsible: true, label: 'MODIFIED LABEL: Sub sub category 1', items: [ {type: 'doc', id: 'doc3', customProps: {lorem: 'ipsum'}}, @@ -424,6 +468,7 @@ describe('transformSidebarItems', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'MODIFIED LABEL: Category2', items: [ {type: 'doc', id: 'doc4'}, @@ -463,6 +508,7 @@ describe('processSidebars', () => { { type: 'category', collapsed: false, + collapsible: true, items: [{type: 'doc', id: 'doc2'}], label: 'Category', }, @@ -474,6 +520,7 @@ describe('processSidebars', () => { { type: 'category', collapsed: false, + collapsible: true, items: [{type: 'doc', id: 'doc4'}], label: 'Category', }, @@ -491,6 +538,7 @@ describe('processSidebars', () => { { type: 'category', collapsed: false, + collapsible: true, items: [ {type: 'doc', id: 'doc2'}, {type: 'autogenerated', dirName: 'dir1'}, @@ -507,6 +555,7 @@ describe('processSidebars', () => { { type: 'category', collapsed: false, + collapsible: true, items: [{type: 'doc', id: 'doc4'}], label: 'Category', }, @@ -541,6 +590,7 @@ describe('processSidebars', () => { { type: 'category', collapsed: false, + collapsible: true, items: [{type: 'doc', id: 'doc2'}, ...StaticGeneratedSidebarSlice], label: 'Category', }, @@ -554,6 +604,7 @@ describe('processSidebars', () => { { type: 'category', collapsed: false, + collapsible: true, items: [{type: 'doc', id: 'doc4'}], label: 'Category', }, @@ -567,11 +618,13 @@ describe('createSidebarsUtils', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category1', items: [ { type: 'category', collapsed: false, + collapsible: true, label: 'Subcategory 1', items: [{type: 'doc', id: 'doc1'}], }, @@ -584,6 +637,7 @@ describe('createSidebarsUtils', () => { { type: 'category', collapsed: false, + collapsible: true, label: 'Category2', items: [ {type: 'doc', id: 'doc3'}, @@ -637,3 +691,56 @@ describe('createSidebarsUtils', () => { }); }); }); + +describe('fixSidebarItemInconsistencies', () => { + test('should not fix good category', () => { + const category: SidebarItemCategory = { + type: 'category', + label: 'Cat', + items: [], + collapsible: true, + collapsed: true, + }; + expect(fixSidebarItemInconsistencies(category)).toEqual(category); + }); + + test('should fix bad category', () => { + const category: SidebarItemCategory = { + type: 'category', + label: 'Cat', + items: [], + collapsible: false, + collapsed: true, // Bad because collapsible=false + }; + expect(fixSidebarItemInconsistencies(category)).toEqual({ + ...category, + collapsed: false, + }); + }); + + test('should fix bad subcategory', () => { + const subCategory: SidebarItemCategory = { + type: 'category', + label: 'SubCat', + items: [], + collapsible: false, + collapsed: true, // Bad because collapsible=false + }; + const category: SidebarItemCategory = { + type: 'category', + label: 'Cat', + items: [subCategory], + collapsible: true, + collapsed: true, + }; + expect(fixSidebarItemInconsistencies(category)).toEqual({ + ...category, + items: [ + { + ...subCategory, + collapsed: false, + }, + ], + }); + }); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts index cabadff8d5..41de537f1c 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -16,6 +16,7 @@ import { PathOptions, UnprocessedSidebarItem, UnprocessedSidebars, + SidebarOptions, } from './types'; import {loadSidebars, resolveSidebarPathOption} from './sidebars'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants'; @@ -25,14 +26,16 @@ function createVersionedSidebarFile({ pluginId, sidebarPath, version, + options, }: { siteDir: string; pluginId: string; sidebarPath: string | false | undefined; version: string; + options: SidebarOptions; }) { // Load current sidebar and create a new versioned sidebars file (if needed). - const loadedSidebars = loadSidebars(sidebarPath); + const loadedSidebars = loadSidebars(sidebarPath, options); // Do not create a useless versioned sidebars file if sidebars file is empty or sidebars are disabled/false) const shouldCreateVersionedSidebarFile = @@ -87,7 +90,7 @@ export function cliDocsVersionCommand( version: string | null | undefined, siteDir: string, pluginId: string, - options: PathOptions, + options: PathOptions & SidebarOptions, ): void { // It wouldn't be very user-friendly to show a [default] log prefix, // so we use [docs] instead of [default] @@ -159,6 +162,7 @@ export function cliDocsVersionCommand( pluginId, version, sidebarPath: resolveSidebarPathOption(siteDir, sidebarPath), + options, }); // Update versions.json file. diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index 7705820095..a539931fa9 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -100,6 +100,8 @@ export default function pluginContentDocs( cliDocsVersionCommand(version, siteDir, pluginId, { path: options.path, sidebarPath: options.sidebarPath, + sidebarCollapsed: options.sidebarCollapsed, + sidebarCollapsible: options.sidebarCollapsible, }); }); }, @@ -168,6 +170,10 @@ export default function pluginContentDocs( ): Promise { const unprocessedSidebars = loadSidebars( versionMetadata.sidebarFilePath, + { + sidebarCollapsed: options.sidebarCollapsed, + sidebarCollapsible: options.sidebarCollapsible, + }, ); const docsBase: DocMetadataBase[] = await loadVersionDocsBase( @@ -184,6 +190,10 @@ export default function pluginContentDocs( unprocessedSidebars, docs: docsBase, version: versionMetadata, + options: { + sidebarCollapsed: options.sidebarCollapsed, + sidebarCollapsible: options.sidebarCollapsible, + }, }); const sidebarsUtils = createSidebarsUtils(sidebars); diff --git a/packages/docusaurus-plugin-content-docs/src/options.ts b/packages/docusaurus-plugin-content-docs/src/options.ts index 754a5e60d0..49891129d6 100644 --- a/packages/docusaurus-plugin-content-docs/src/options.ts +++ b/packages/docusaurus-plugin-content-docs/src/options.ts @@ -46,6 +46,8 @@ export const DEFAULT_OPTIONS: Omit = { versions: {}, editCurrentVersion: false, editLocalizedFiles: false, + sidebarCollapsible: true, + sidebarCollapsed: true, }; const VersionOptionsSchema = Joi.object({ @@ -77,6 +79,8 @@ export const OptionsSchema = Joi.object({ sidebarItemsGenerator: Joi.function().default( () => DEFAULT_OPTIONS.sidebarItemsGenerator, ), + sidebarCollapsible: Joi.boolean().default(DEFAULT_OPTIONS.sidebarCollapsible), + sidebarCollapsed: Joi.boolean().default(DEFAULT_OPTIONS.sidebarCollapsed), numberPrefixParser: Joi.alternatives() .try( Joi.function(), @@ -116,8 +120,32 @@ export const OptionsSchema = Joi.object({ export function validateOptions({ validate, - options, + options: userOptions, }: OptionValidationContext): ValidationResult { + let options = userOptions; + + if (options.sidebarCollapsible === false) { + // When sidebarCollapsible=false and sidebarCollapsed=undefined, we don't want to have the inconsistency warning + // We let options.sidebarCollapsible become the default value for options.sidebarCollapsed + if (typeof options.sidebarCollapsed === 'undefined') { + options = { + ...options, + sidebarCollapsed: false, + }; + } + if (options.sidebarCollapsed) { + console.warn( + chalk.yellow( + 'The docs plugin config is inconsistent. It does not make sense to use sidebarCollapsible=false and sidebarCollapsed=true at the same time. sidebarCollapsed=false will be ignored.', + ), + ); + options = { + ...options, + sidebarCollapsed: false, + }; + } + } + // TODO remove homePageId before end of 2020 // "slug: /" is better because the home doc can be different across versions if (options.homePageId) { 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 3eb8c317c0..01a9477667 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 @@ -33,7 +33,8 @@ declare module '@docusaurus/plugin-content-docs-types' { type: 'category'; label: string; items: PropSidebarItem[]; - collapsed?: boolean; + collapsed: boolean; + collapsible: boolean; }; export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebarItemsGenerator.ts b/packages/docusaurus-plugin-content-docs/src/sidebarItemsGenerator.ts index da80765cde..716b1a91ed 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebarItemsGenerator.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebarItemsGenerator.ts @@ -19,7 +19,6 @@ import chalk from 'chalk'; import path from 'path'; import fs from 'fs-extra'; import Yaml from 'js-yaml'; -import {DefaultCategoryCollapsedValue} from './sidebars'; const BreadcrumbSeparator = '/'; @@ -30,6 +29,7 @@ export type CategoryMetadatasFile = { label?: string; position?: number; collapsed?: boolean; + collapsible?: boolean; // TODO should we allow "items" here? how would this work? would an "autogenerated" type be allowed? // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/ @@ -43,6 +43,7 @@ const CategoryMetadatasFileSchema = Joi.object({ label: Joi.string(), position: Joi.number(), collapsed: Joi.boolean(), + collapsible: Joi.boolean(), }); // TODO I now believe we should read all the category metadata files ahead of time: we may need this metadata to customize docs metadata @@ -107,7 +108,8 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async functio docs: allDocs, version, numberPrefixParser, -}): Promise { + options, +}) { // Doc at the root of the autogenerated sidebar dir function isRootDoc(doc: SidebarItemsGeneratorDoc) { return doc.sourceDirName === item.dirName; @@ -199,11 +201,16 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async functio const position = categoryMetadatas?.position ?? numberPrefix; + const collapsible = + categoryMetadatas?.collapsible ?? options.sidebarCollapsible; + const collapsed = categoryMetadatas?.collapsed ?? options.sidebarCollapsed; + return { type: 'category', label: categoryMetadatas?.label ?? filename, items: [], - collapsed: categoryMetadatas?.collapsed ?? DefaultCategoryCollapsedValue, + collapsed, + collapsible, ...(typeof position !== 'undefined' && {position}), }; } @@ -268,6 +275,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async functio } // async process made sequential on purpose! order matters + // eslint-disable-next-line no-restricted-syntax for (const doc of docs) { // eslint-disable-next-line no-await-in-loop await handleDocItem(doc); diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars.ts b/packages/docusaurus-plugin-content-docs/src/sidebars.ts index 86d06312b3..dad516e9f8 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars.ts @@ -25,6 +25,7 @@ import { SidebarItemsGeneratorVersion, NumberPrefixParser, SidebarItemsGeneratorOption, + SidebarOptions, PluginOptions, } from './types'; import {mapValues, flatten, flatMap, difference, pick, memoize} from 'lodash'; @@ -38,6 +39,7 @@ type SidebarItemCategoryJSON = SidebarItemBase & { label: string; items: SidebarItemJSON[]; collapsed?: boolean; + collapsible?: boolean; }; type SidebarItemAutogeneratedJSON = SidebarItemBase & { @@ -74,18 +76,17 @@ function isCategoryShorthand( return typeof item !== 'string' && !item.type; } -// categories are collapsed by default, unless user set collapsed = false -export const DefaultCategoryCollapsedValue = true; - /** * Convert {category1: [item1,item2]} shorthand syntax to long-form syntax */ function normalizeCategoryShorthand( sidebar: SidebarCategoryShorthandJSON, + options: SidebarOptions, ): SidebarItemCategoryJSON[] { return Object.entries(sidebar).map(([label, items]) => ({ type: 'category', - collapsed: DefaultCategoryCollapsedValue, + collapsed: options.sidebarCollapsed, + collapsible: options.sidebarCollapsible, label, items, })); @@ -115,7 +116,13 @@ function assertItem( function assertIsCategory( item: Record, ): asserts item is SidebarItemCategoryJSON { - assertItem(item, ['items', 'label', 'collapsed', 'customProps']); + assertItem(item, [ + 'items', + 'label', + 'collapsed', + 'collapsible', + 'customProps', + ]); if (typeof item.label !== 'string') { throw new Error( `Error loading ${JSON.stringify(item)}: "label" must be a string.`, @@ -135,6 +142,14 @@ function assertIsCategory( `Error loading ${JSON.stringify(item)}: "collapsed" must be a boolean.`, ); } + if ( + typeof item.collapsible !== 'undefined' && + typeof item.collapsible !== 'boolean' + ) { + throw new Error( + `Error loading ${JSON.stringify(item)}: "collapsible" must be a boolean.`, + ); + } } function assertIsAutogenerated( @@ -192,7 +207,10 @@ function assertIsLink( * Normalizes recursively item and all its children. Ensures that at the end * each item will be an object with the corresponding type. */ -function normalizeItem(item: SidebarItemJSON): UnprocessedSidebarItem[] { +function normalizeItem( + item: SidebarItemJSON, + options: SidebarOptions, +): UnprocessedSidebarItem[] { if (typeof item === 'string') { return [ { @@ -202,16 +220,21 @@ function normalizeItem(item: SidebarItemJSON): UnprocessedSidebarItem[] { ]; } if (isCategoryShorthand(item)) { - return flatMap(normalizeCategoryShorthand(item), normalizeItem); + return flatMap(normalizeCategoryShorthand(item, options), (subitem) => + normalizeItem(subitem, options), + ); } switch (item.type) { case 'category': assertIsCategory(item); return [ { - collapsed: DefaultCategoryCollapsedValue, ...item, - items: flatMap(item.items, normalizeItem), + items: flatMap(item.items, (subItem) => + normalizeItem(subItem, options), + ), + collapsible: item.collapsible ?? options.sidebarCollapsible, + collapsed: item.collapsed ?? options.sidebarCollapsed, }, ]; case 'autogenerated': @@ -238,16 +261,24 @@ function normalizeItem(item: SidebarItemJSON): UnprocessedSidebarItem[] { } } -function normalizeSidebar(sidebar: SidebarJSON): UnprocessedSidebar { +function normalizeSidebar( + sidebar: SidebarJSON, + options: SidebarOptions, +): UnprocessedSidebar { const normalizedSidebar: SidebarItemJSON[] = Array.isArray(sidebar) ? sidebar - : normalizeCategoryShorthand(sidebar); + : normalizeCategoryShorthand(sidebar, options); - return flatMap(normalizedSidebar, normalizeItem); + return flatMap(normalizedSidebar, (subitem) => + normalizeItem(subitem, options), + ); } -function normalizeSidebars(sidebars: SidebarsJSON): UnprocessedSidebars { - return mapValues(sidebars, normalizeSidebar); +function normalizeSidebars( + sidebars: SidebarsJSON, + options: SidebarOptions, +): UnprocessedSidebars { + return mapValues(sidebars, (subitem) => normalizeSidebar(subitem, options)); } export const DefaultSidebars: UnprocessedSidebars = { @@ -276,6 +307,7 @@ export function resolveSidebarPathOption( // Note: sidebarFilePath must be absolute, use resolveSidebarPathOption export function loadSidebars( sidebarFilePath: string | false | undefined, + options: SidebarOptions, ): UnprocessedSidebars { // false => no sidebars if (sidebarFilePath === false) { @@ -297,7 +329,7 @@ export function loadSidebars( // We don't want sidebars to be cached because of hot reloading. const sidebarJson = importFresh(sidebarFilePath) as SidebarsJSON; - return normalizeSidebars(sidebarJson); + return normalizeSidebars(sidebarJson, options); } export function toSidebarItemsGeneratorDoc( @@ -317,19 +349,44 @@ export function toSidebarItemsGeneratorVersion( return pick(version, ['versionName', 'contentPath']); } -// Handle the generation of autogenerated sidebar items +export function fixSidebarItemInconsistencies(item: SidebarItem): SidebarItem { + function fixCategoryInconsistencies( + category: SidebarItemCategory, + ): SidebarItemCategory { + // A non-collapsible category can't be collapsed! + if (!category.collapsible && category.collapsed) { + return { + ...category, + collapsed: false, + }; + } + return category; + } + + if (item.type === 'category') { + return { + ...fixCategoryInconsistencies(item), + items: item.items.map(fixSidebarItemInconsistencies), + }; + } + return item; +} + +// Handle the generation of autogenerated sidebar items and other post-processing checks export async function processSidebar({ sidebarItemsGenerator, numberPrefixParser, unprocessedSidebar, docs, version, + options, }: { sidebarItemsGenerator: SidebarItemsGeneratorOption; numberPrefixParser: NumberPrefixParser; unprocessedSidebar: UnprocessedSidebar; docs: DocMetadataBase[]; version: VersionMetadata; + options: SidebarOptions; }): Promise { // Just a minor lazy transformation optimization const getSidebarItemsGeneratorDocsAndVersion = memoize(() => ({ @@ -337,14 +394,16 @@ export async function processSidebar({ version: toSidebarItemsGeneratorVersion(version), })); - async function processRecursive( + async function handleAutoGeneratedItems( item: UnprocessedSidebarItem, ): Promise { if (item.type === 'category') { return [ { ...item, - items: (await Promise.all(item.items.map(processRecursive))).flat(), + items: ( + await Promise.all(item.items.map(handleAutoGeneratedItems)) + ).flat(), }, ]; } @@ -354,12 +413,17 @@ export async function processSidebar({ numberPrefixParser, defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator, ...getSidebarItemsGeneratorDocsAndVersion(), + options, }); } return [item]; } - return (await Promise.all(unprocessedSidebar.map(processRecursive))).flat(); + const processedSidebar = ( + await Promise.all(unprocessedSidebar.map(handleAutoGeneratedItems)) + ).flat(); + + return processedSidebar.map(fixSidebarItemInconsistencies); } export async function processSidebars({ @@ -368,12 +432,14 @@ export async function processSidebars({ unprocessedSidebars, docs, version, + options, }: { sidebarItemsGenerator: SidebarItemsGeneratorOption; numberPrefixParser: NumberPrefixParser; unprocessedSidebars: UnprocessedSidebars; docs: DocMetadataBase[]; version: VersionMetadata; + options: SidebarOptions; }): Promise { return combinePromises( mapValues(unprocessedSidebars, (unprocessedSidebar) => @@ -383,6 +449,7 @@ export async function processSidebars({ unprocessedSidebar, docs, version, + options, }), ), ); diff --git a/packages/docusaurus-plugin-content-docs/src/types.ts b/packages/docusaurus-plugin-content-docs/src/types.ts index e3d994aaa9..414d83e25c 100644 --- a/packages/docusaurus-plugin-content-docs/src/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/types.ts @@ -75,10 +75,16 @@ export type VersionsOptions = { onlyIncludeVersions?: string[]; }; +export type SidebarOptions = { + sidebarCollapsible: boolean; + sidebarCollapsed: boolean; +}; + export type PluginOptions = MetadataOptions & PathOptions & VersionsOptions & - RemarkAndRehypePluginOptions & { + RemarkAndRehypePluginOptions & + SidebarOptions & { id: string; include: string[]; exclude: string[]; @@ -111,6 +117,7 @@ export type SidebarItemCategory = SidebarItemBase & { label: string; items: SidebarItem[]; collapsed: boolean; + collapsible: boolean; }; export type UnprocessedSidebarItemAutogenerated = { @@ -123,6 +130,7 @@ export type UnprocessedSidebarItemCategory = SidebarItemBase & { label: string; items: UnprocessedSidebarItem[]; collapsed: boolean; + collapsible: boolean; }; export type UnprocessedSidebarItem = @@ -160,6 +168,7 @@ export type SidebarItemsGeneratorArgs = { version: SidebarItemsGeneratorVersion; docs: SidebarItemsGeneratorDoc[]; numberPrefixParser: NumberPrefixParser; + options: SidebarOptions; }; export type SidebarItemsGenerator = ( generatorArgs: SidebarItemsGeneratorArgs, diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx index 83e38c2ac4..28b35177a1 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx @@ -36,7 +36,7 @@ function DocPageContent({ versionMetadata, children, }: DocPageContentProps): JSX.Element { - const {siteConfig, isClient} = useDocusaurusContext(); + const {isClient} = useDocusaurusContext(); const {pluginId, version} = versionMetadata; const sidebarName = currentDocRoute.sidebar; @@ -88,7 +88,6 @@ function DocPageContent({ } sidebar={sidebar} path={currentDocRoute.path} - sidebarCollapsible={siteConfig.themeConfig.sidebarCollapsible} onCollapse={toggleSidebar} isHidden={hiddenSidebar} /> diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx index 33faa9cb20..82f0b48ba1 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx @@ -58,13 +58,7 @@ function HideableSidebarButton({onClick}) { ); } -function DocSidebarDesktop({ - path, - sidebar, - sidebarCollapsible, - onCollapse, - isHidden, -}: Props) { +function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) { const showAnnouncementBar = useShowAnnouncementBar(); const { navbar: {hideOnScroll}, @@ -85,11 +79,7 @@ function DocSidebarDesktop({ !isAnnouncementBarClosed && showAnnouncementBar, })}>
    - +
{hideableSidebar && } @@ -100,14 +90,12 @@ function DocSidebarDesktop({ const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent = ({ toggleSidebar, sidebar, - sidebarCollapsible, path, }) => { return (
    toggleSidebar()} /> diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx index 117b6a57d1..6570f69c72 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx @@ -95,11 +95,10 @@ function useAutoExpandActiveCategory({ function DocSidebarItemCategory({ item, onItemClick, - collapsible = true, activePath, ...props }: Props & {item: PropSidebarItemCategory}) { - const {items, label} = item; + const {items, label, collapsible} = item; const isActive = isActiveSidebarItem(item, activePath); @@ -110,7 +109,7 @@ function DocSidebarItemCategory({ if (!collapsible) { return false; } - return isActive ? false : item.collapsed ?? true; + return isActive ? false : item.collapsed; }, }); @@ -146,7 +145,6 @@ function DocSidebarItemCategory({ items={items} tabIndex={collapsed ? -1 : 0} onItemClick={onItemClick} - collapsible={collapsible} activePath={activePath} /> @@ -158,7 +156,6 @@ function DocSidebarItemLink({ item, onItemClick, activePath, - collapsible: _collapsible, ...props }: Props & {item: PropSidebarItemLink}) { const {href, label} = item; diff --git a/packages/docusaurus-theme-classic/src/types.d.ts b/packages/docusaurus-theme-classic/src/types.d.ts index d568c22f9b..1b8df834e0 100644 --- a/packages/docusaurus-theme-classic/src/types.d.ts +++ b/packages/docusaurus-theme-classic/src/types.d.ts @@ -93,7 +93,6 @@ declare module '@theme/DocSidebar' { export type Props = { readonly path: string; readonly sidebar: readonly PropSidebarItem[]; - readonly sidebarCollapsible?: boolean; readonly onCollapse: () => void; readonly isHidden: boolean; }; @@ -107,7 +106,6 @@ declare module '@theme/DocSidebarItem' { type DocSidebarPropsBase = { readonly activePath: string; - readonly collapsible?: boolean; readonly onItemClick?: () => void; readonly tabIndex?: number; }; diff --git a/packages/docusaurus-theme-classic/src/validateThemeConfig.js b/packages/docusaurus-theme-classic/src/validateThemeConfig.js index 12b9a36ed6..33505b2bcb 100644 --- a/packages/docusaurus-theme-classic/src/validateThemeConfig.js +++ b/packages/docusaurus-theme-classic/src/validateThemeConfig.js @@ -40,7 +40,6 @@ const DEFAULT_CONFIG = { items: [], }, hideableSidebar: false, - sidebarCollapsible: true, }; exports.DEFAULT_CONFIG = DEFAULT_CONFIG; @@ -310,7 +309,10 @@ const ThemeConfigSchema = Joi.object({ .default(DEFAULT_CONFIG.prism) .unknown(), hideableSidebar: Joi.bool().default(DEFAULT_CONFIG.hideableSidebar), - sidebarCollapsible: Joi.bool().default(DEFAULT_CONFIG.sidebarCollapsible), + sidebarCollapsible: Joi.forbidden().messages({ + 'any.unknown': + 'The themeConfig.sidebarCollapsible has been moved to docs plugin options. See: https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs', + }), }); exports.ThemeConfigSchema = ThemeConfigSchema; diff --git a/packages/docusaurus-theme-common/src/utils/mobileSecondaryMenu.tsx b/packages/docusaurus-theme-common/src/utils/mobileSecondaryMenu.tsx index db429854f6..27b58404a1 100644 --- a/packages/docusaurus-theme-common/src/utils/mobileSecondaryMenu.tsx +++ b/packages/docusaurus-theme-common/src/utils/mobileSecondaryMenu.tsx @@ -46,7 +46,11 @@ type ContextValue = ReturnType; const Context = createContext(null); -export function MobileSecondaryMenuProvider({children}: {children: ReactNode}) { +export function MobileSecondaryMenuProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { return ( {children} ); diff --git a/website/_dogfooding/_docs-tests/more-test.md b/website/_dogfooding/_docs-tests/more-test.md new file mode 100644 index 0000000000..0f3856b736 --- /dev/null +++ b/website/_dogfooding/_docs-tests/more-test.md @@ -0,0 +1 @@ +# Another test page diff --git a/website/_dogfooding/docs-tests-sidebars.js b/website/_dogfooding/docs-tests-sidebars.js index 48d321c078..b841c1e3eb 100644 --- a/website/_dogfooding/docs-tests-sidebars.js +++ b/website/_dogfooding/docs-tests-sidebars.js @@ -12,6 +12,12 @@ module.exports = { id: 'index', label: 'Index', }, + { + type: 'category', + label: 'section', + collapsible: false, + items: ['index', 'more-test'], + }, { type: 'category', label: 'Huge sidebar category', diff --git a/website/docs/api/plugins/plugin-content-docs.md b/website/docs/api/plugins/plugin-content-docs.md index 555ebdd914..39038cc71f 100644 --- a/website/docs/api/plugins/plugin-content-docs.md +++ b/website/docs/api/plugins/plugin-content-docs.md @@ -82,6 +82,16 @@ module.exports = { * Path to sidebar configuration for showing a list of markdown pages. */ sidebarPath: 'sidebars.js', + /** + * By default, all sidebar categories will be collapsible. + * This can be overriden per-category. + */ + sidebarCollapsible: true, + /** + * By default, all sidebar categories will be initialized in a collapsed state. + * This can be overriden per-category. + */ + sidebarCollapsed: false, /** * Function used to replace the sidebar items of type "autogenerated" * by real sidebar items (docs, categories, links...) diff --git a/website/docs/guides/docs/sidebar.md b/website/docs/guides/docs/sidebar.md index c0799f662e..c31a3d28a3 100644 --- a/website/docs/guides/docs/sidebar.md +++ b/website/docs/guides/docs/sidebar.md @@ -285,7 +285,8 @@ type SidebarItemCategory = { items: SidebarItem[]; // Array of sidebar items. // Category options: - collapsed: boolean; // Set the category to be collapsed or open by default + collapsible: boolean; // Set the category to be collapsible + collapsed: boolean; // Set the category to be initially collapsed or open by default }; ``` @@ -297,6 +298,7 @@ module.exports = { { type: 'category', label: 'Guides', + collapsible: true, collapsed: false, items: [ 'creating-pages', @@ -332,15 +334,25 @@ module.exports = { #### Collapsible categories {#collapsible-categories} -For sites with a sizable amount of content, we support the option to expand/collapse a category to toggle the display of its contents. Categories are collapsible by default. If you want them to be always expanded, set `themeConfig.sidebarCollapsible` to `false`: +By default, categories are collapsible and collapsed. + +The docs plugin options allow to change these defaults globally: ```js title="docusaurus.config.js" module.exports = { - themeConfig: { - // highlight-start - sidebarCollapsible: false, - // highlight-end - }, + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + // highlight-start + sidebarCollapsible: true, + sidebarCollapsed: false, + // highlight-end + }, + }, + ], + ], }; ``` @@ -356,6 +368,7 @@ module.exports = { { type: 'category', label: 'Docs', + collapsible: true, collapsed: false, items: ['markdown-features', 'sidebar', 'versioning'], }, @@ -465,6 +478,7 @@ This is the easy tutorial! ```yaml title="docs/tutorials/_category_.yml" label: 'Tutorial' position: 2.5 # float position is supported +collapsible: true # make the category collapsible collapsed: false # keep the category open by default ``` diff --git a/website/docs/migration/migration-manual.md b/website/docs/migration/migration-manual.md index 34611bb837..825bc1474f 100644 --- a/website/docs/migration/migration-manual.md +++ b/website/docs/migration/migration-manual.md @@ -229,8 +229,6 @@ module.exports = { copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`, // You can also put own HTML here. }, image: 'img/docusaurus.png', - // Equivalent to `docsSideNavCollapsible`. - sidebarCollapsible: false, // ... }, }; @@ -399,7 +397,7 @@ The following fields are all deprecated, you may remove from your configuration - `defaultVersionShown` - Versioning is not ported yet. You'd be unable to migration to Docusaurus 2 if you are using versioning. Stay tuned. - `disableHeaderTitle` - `disableTitleTagline` -- `docsSideNavCollapsible` is available at `themeConfig.sidebarCollapsible`, and this is turned on by default now. +- `docsSideNavCollapsible` is available at `docsPluginOptions.sidebarCollapsible`, and this is turned on by default now. - `facebookAppId` - `facebookComments` - `facebookPixelId` diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 20da8a121e..59cb0bf5cc 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -227,6 +227,8 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging; // routeBasePath: '/', path: 'docs', sidebarPath: 'sidebars.js', + // sidebarCollapsible: false, + // sidebarCollapsed: true, editUrl: ({locale, docPath}) => { if (locale !== 'en') { return `https://crowdin.com/project/docusaurus-v2/${locale}`; @@ -285,7 +287,6 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging; liveCodeBlock: { playgroundPosition: 'bottom', }, - sidebarCollapsible: true, hideableSidebar: true, colorMode: { defaultMode: 'light',