feat: docs plugin options sidebarCollapsible + sidebarCollapsed (#5203)

* Add prop

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Add `collapsible` option to sidebar item

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Add eslint-ignore

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Move new page

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Allow in autogenerated

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Fix tests

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Move config options to plugin-docs

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Make non-collapsible items always expanded

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* docs versioning cli should receive a single options object

* Update cli.test.ts

* revert validateCategoryMetadataFile change

* remove theme usage of themeConfig.sidebarCollapsible

* better handling of sidebar item category inconsistencies + add warning message

* Update snapshot

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Handle plugin option inconsistencies

* improve doc for new sidebarCollapsible doc options

* remove warning in fixSidebarItemInconsistencies as it will be annoyed for versioned sites, as "collapsed" is already persisted in sidebar json files

Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
Joshua Chen 2021-07-23 20:24:36 +08:00 committed by GitHub
parent b38c35a36d
commit 24156efcfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 487 additions and 109 deletions

View file

@ -5,9 +5,11 @@ Object {
"version-1.0.0/docs": Array [ "version-1.0.0/docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-1.0.0/foo/bar", "id": "version-1.0.0/foo/bar",
@ -36,6 +38,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-1.0.0/hello", "id": "version-1.0.0/hello",
@ -54,6 +57,7 @@ Object {
"version-2.0.0/docs": Array [ "version-2.0.0/docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-2.0.0/foo/bar", "id": "version-2.0.0/foo/bar",
@ -65,6 +69,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-2.0.0/hello", "id": "version-2.0.0/hello",

View file

@ -27,9 +27,11 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "foo/bar", "id": "foo/bar",
@ -58,6 +60,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "hello", "id": "hello",
@ -410,12 +413,12 @@ Object {
\\"docsSidebars\\": { \\"docsSidebars\\": {
\\"docs\\": [ \\"docs\\": [
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Test\\", \\"label\\": \\"Test\\",
\\"items\\": [ \\"items\\": [
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"label\\": \\"foo\\", \\"label\\": \\"foo\\",
\\"items\\": [ \\"items\\": [
@ -429,7 +432,9 @@ Object {
\\"label\\": \\"baz\\", \\"label\\": \\"baz\\",
\\"href\\": \\"/docs/foo/bazSlug.html\\" \\"href\\": \\"/docs/foo/bazSlug.html\\"
} }
] ],
\\"collapsible\\": true,
\\"collapsed\\": true
}, },
{ {
\\"type\\": \\"link\\", \\"type\\": \\"link\\",
@ -444,8 +449,9 @@ Object {
] ]
}, },
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Guides\\", \\"label\\": \\"Guides\\",
\\"items\\": [ \\"items\\": [
{ {
@ -806,6 +812,10 @@ Object {
"type": "autogenerated", "type": "autogenerated",
}, },
"numberPrefixParser": [Function], "numberPrefixParser": [Function],
"options": Object {
"sidebarCollapsed": true,
"sidebarCollapsible": true,
},
"version": Object { "version": Object {
"contentPath": "docs", "contentPath": "docs",
"versionName": "current", "versionName": "current",
@ -992,6 +1002,7 @@ Object {
"version-1.0.0/docs": Array [ "version-1.0.0/docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-1.0.0/foo/bar", "id": "version-1.0.0/foo/bar",
@ -1007,6 +1018,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-1.0.0/hello", "id": "version-1.0.0/hello",
@ -1025,6 +1037,7 @@ Object {
"version-1.0.1/docs": Array [ "version-1.0.1/docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-1.0.1/foo/bar", "id": "version-1.0.1/foo/bar",
@ -1036,6 +1049,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-1.0.1/hello", "id": "version-1.0.1/hello",
@ -1054,6 +1068,7 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "foo/bar", "id": "foo/bar",
@ -1065,6 +1080,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "hello", "id": "hello",
@ -1404,8 +1420,9 @@ Object {
\\"docsSidebars\\": { \\"docsSidebars\\": {
\\"version-1.0.0/docs\\": [ \\"version-1.0.0/docs\\": [
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Test\\", \\"label\\": \\"Test\\",
\\"items\\": [ \\"items\\": [
{ {
@ -1421,8 +1438,9 @@ Object {
] ]
}, },
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Guides\\", \\"label\\": \\"Guides\\",
\\"items\\": [ \\"items\\": [
{ {
@ -1444,8 +1462,9 @@ Object {
\\"docsSidebars\\": { \\"docsSidebars\\": {
\\"version-1.0.1/docs\\": [ \\"version-1.0.1/docs\\": [
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Test\\", \\"label\\": \\"Test\\",
\\"items\\": [ \\"items\\": [
{ {
@ -1456,8 +1475,9 @@ Object {
] ]
}, },
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Guides\\", \\"label\\": \\"Guides\\",
\\"items\\": [ \\"items\\": [
{ {
@ -1479,8 +1499,9 @@ Object {
\\"docsSidebars\\": { \\"docsSidebars\\": {
\\"docs\\": [ \\"docs\\": [
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Test\\", \\"label\\": \\"Test\\",
\\"items\\": [ \\"items\\": [
{ {
@ -1491,8 +1512,9 @@ Object {
] ]
}, },
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Guides\\", \\"label\\": \\"Guides\\",
\\"items\\": [ \\"items\\": [
{ {
@ -1514,8 +1536,9 @@ Object {
\\"docsSidebars\\": { \\"docsSidebars\\": {
\\"version-1.0.1/docs\\": [ \\"version-1.0.1/docs\\": [
{ {
\\"collapsed\\": true,
\\"type\\": \\"category\\", \\"type\\": \\"category\\",
\\"collapsed\\": true,
\\"collapsible\\": true,
\\"label\\": \\"Test\\", \\"label\\": \\"Test\\",
\\"items\\": [ \\"items\\": [
{ {
@ -1888,6 +1911,7 @@ Object {
"version-1.0.1/docs": Array [ "version-1.0.1/docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-withSlugs/rootAbsoluteSlug", "id": "version-withSlugs/rootAbsoluteSlug",

View file

@ -5,6 +5,7 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"href": "https://github.com", "href": "https://github.com",
@ -24,9 +25,11 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"collapsed": false, "collapsed": false,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "doc1", "id": "doc1",
@ -42,9 +45,11 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"collapsed": false, "collapsed": false,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "doc2", "id": "doc2",
@ -67,6 +72,7 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": false, "collapsed": false,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "doc1", "id": "doc1",
@ -78,6 +84,7 @@ Object {
}, },
Object { Object {
"collapsed": false, "collapsed": false,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "doc2", "id": "doc2",
@ -96,6 +103,7 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "a", "id": "a",
@ -103,9 +111,11 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "c", "id": "c",
@ -113,6 +123,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "d", "id": "d",
@ -120,6 +131,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "e", "id": "e",
@ -158,6 +170,7 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "greeting", "id": "greeting",
@ -180,6 +193,7 @@ Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "foo/bar", "id": "foo/bar",
@ -204,6 +218,7 @@ Object {
}, },
Object { Object {
"collapsed": true, "collapsed": true,
"collapsible": true,
"items": Array [ "items": Array [
Object { Object {
"id": "hello", "id": "hello",

View file

@ -7,7 +7,7 @@
import path from 'path'; import path from 'path';
import {cliDocsVersionCommand} from '../cli'; import {cliDocsVersionCommand} from '../cli';
import {PathOptions} from '../types'; import {PathOptions, SidebarOptions} from '../types';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { import {
getVersionedDocsDirPath, getVersionedDocsDirPath,
@ -21,9 +21,12 @@ const fixtureDir = path.join(__dirname, '__fixtures__');
describe('docsVersion', () => { describe('docsVersion', () => {
const simpleSiteDir = path.join(fixtureDir, 'simple-site'); const simpleSiteDir = path.join(fixtureDir, 'simple-site');
const versionedSiteDir = path.join(fixtureDir, 'versioned-site'); const versionedSiteDir = path.join(fixtureDir, 'versioned-site');
const DEFAULT_OPTIONS: PathOptions = {
const DEFAULT_OPTIONS: PathOptions & SidebarOptions = {
path: 'docs', path: 'docs',
sidebarPath: '', sidebarPath: '',
sidebarCollapsed: true,
sidebarCollapsible: true,
}; };
test('no version tag provided', () => { test('no version tag provided', () => {
@ -186,17 +189,17 @@ describe('docsVersion', () => {
let versionedSidebarPath; let versionedSidebarPath;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
versionedSidebarPath = filepath; versionedSidebarPath = filepath;
versionedSidebar = JSON.parse(content); versionedSidebar = JSON.parse(content as string);
}); });
let versionsPath; let versionsPath;
let versions; let versions;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
versionsPath = filepath; versionsPath = filepath;
versions = JSON.parse(content); versions = JSON.parse(content as string);
}); });
const consoleMock = jest.spyOn(console, 'log').mockImplementation(); const consoleMock = jest.spyOn(console, 'log').mockImplementation();
const options = { const options = {
path: 'docs', ...DEFAULT_OPTIONS,
sidebarPath: path.join(simpleSiteDir, 'sidebars.json'), sidebarPath: path.join(simpleSiteDir, 'sidebars.json'),
}; };
cliDocsVersionCommand('1.0.0', simpleSiteDir, DEFAULT_PLUGIN_ID, options); cliDocsVersionCommand('1.0.0', simpleSiteDir, DEFAULT_PLUGIN_ID, options);
@ -234,17 +237,17 @@ describe('docsVersion', () => {
let versionedSidebarPath; let versionedSidebarPath;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
versionedSidebarPath = filepath; versionedSidebarPath = filepath;
versionedSidebar = JSON.parse(content); versionedSidebar = JSON.parse(content as string);
}); });
let versionsPath; let versionsPath;
let versions; let versions;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
versionsPath = filepath; versionsPath = filepath;
versions = JSON.parse(content); versions = JSON.parse(content as string);
}); });
const consoleMock = jest.spyOn(console, 'log').mockImplementation(); const consoleMock = jest.spyOn(console, 'log').mockImplementation();
const options = { const options = {
path: 'docs', ...DEFAULT_OPTIONS,
sidebarPath: path.join(versionedSiteDir, 'sidebars.json'), sidebarPath: path.join(versionedSiteDir, 'sidebars.json'),
}; };
cliDocsVersionCommand( cliDocsVersionCommand(
@ -289,16 +292,17 @@ describe('docsVersion', () => {
let versionedSidebarPath; let versionedSidebarPath;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
versionedSidebarPath = filepath; versionedSidebarPath = filepath;
versionedSidebar = JSON.parse(content); versionedSidebar = JSON.parse(content as string);
}); });
let versionsPath; let versionsPath;
let versions; let versions;
writeMock.mockImplementationOnce((filepath, content) => { writeMock.mockImplementationOnce((filepath, content) => {
versionsPath = filepath; versionsPath = filepath;
versions = JSON.parse(content); versions = JSON.parse(content as string);
}); });
const consoleMock = jest.spyOn(console, 'log').mockImplementation(); const consoleMock = jest.spyOn(console, 'log').mockImplementation();
const options = { const options = {
...DEFAULT_OPTIONS,
path: 'community', path: 'community',
sidebarPath: path.join(versionedSiteDir, 'community_sidebars.json'), sidebarPath: path.join(versionedSiteDir, 'community_sidebars.json'),
}; };

View file

@ -270,6 +270,8 @@ describe('simple website', () => {
expect(mock).toHaveBeenCalledWith('1.0.0', siteDir, DEFAULT_PLUGIN_ID, { expect(mock).toHaveBeenCalledWith('1.0.0', siteDir, DEFAULT_PLUGIN_ID, {
path: 'docs', path: 'docs',
sidebarPath, sidebarPath,
sidebarCollapsed: true,
sidebarCollapsible: true,
}); });
mock.mockRestore(); mock.mockRestore();
}); });
@ -478,6 +480,8 @@ describe('versioned website', () => {
expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, DEFAULT_PLUGIN_ID, { expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, DEFAULT_PLUGIN_ID, {
path: routeBasePath, path: routeBasePath,
sidebarPath, sidebarPath,
sidebarCollapsed: true,
sidebarCollapsible: true,
}); });
mock.mockRestore(); mock.mockRestore();
}); });
@ -729,6 +733,8 @@ describe('versioned website (community)', () => {
expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, pluginId, { expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, pluginId, {
path: routeBasePath, path: routeBasePath,
sidebarPath, sidebarPath,
sidebarCollapsed: true,
sidebarCollapsible: true,
}); });
mock.mockRestore(); mock.mockRestore();
}); });
@ -907,6 +913,7 @@ describe('site with full autogenerated sidebar', () => {
type: 'category', type: 'category',
label: 'Guides', label: 'Guides',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -938,6 +945,7 @@ describe('site with full autogenerated sidebar', () => {
type: 'category', type: 'category',
label: 'API (label from _category_.json)', label: 'API (label from _category_.json)',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -947,6 +955,7 @@ describe('site with full autogenerated sidebar', () => {
type: 'category', type: 'category',
label: 'Core APIs', label: 'Core APIs',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -963,6 +972,7 @@ describe('site with full autogenerated sidebar', () => {
type: 'category', type: 'category',
label: 'Extension APIs (label from _category_.yml)', label: 'Extension APIs (label from _category_.yml)',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -1461,6 +1471,7 @@ describe('site with partial autogenerated sidebars', () => {
type: 'category', type: 'category',
label: 'Some category', label: 'Some category',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -1650,6 +1661,7 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => {
type: 'category', type: 'category',
label: 'Core APIs', label: 'Core APIs',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -1666,6 +1678,7 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => {
type: 'category', type: 'category',
label: 'Extension APIs (label from _category_.yml)', // Fix #4638 label: 'Extension APIs (label from _category_.yml)', // Fix #4638
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -1782,6 +1795,7 @@ describe('site with custom sidebar items generator', () => {
type: 'category', type: 'category',
label: 'API (label from _category_.json)', label: 'API (label from _category_.json)',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -1791,6 +1805,7 @@ describe('site with custom sidebar items generator', () => {
type: 'category', type: 'category',
label: 'Extension APIs (label from _category_.yml)', label: 'Extension APIs (label from _category_.yml)',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -1806,6 +1821,7 @@ describe('site with custom sidebar items generator', () => {
type: 'category', type: 'category',
label: 'Core APIs', label: 'Core APIs',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',
@ -1827,6 +1843,7 @@ describe('site with custom sidebar items generator', () => {
type: 'category', type: 'category',
label: 'Guides', label: 'Guides',
collapsed: true, collapsed: true,
collapsible: true,
items: [ items: [
{ {
type: 'doc', type: 'doc',

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {OptionsSchema, DEFAULT_OPTIONS} from '../options'; import {OptionsSchema, DEFAULT_OPTIONS, validateOptions} from '../options';
import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator'; import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator';
import { import {
@ -13,11 +13,22 @@ import {
DisabledNumberPrefixParser, DisabledNumberPrefixParser,
} from '../numberPrefix'; } from '../numberPrefix';
import {GlobExcludeDefault} from '@docusaurus/utils'; import {GlobExcludeDefault} from '@docusaurus/utils';
import {PluginOptions} from '../types';
// the type of remark/rehype plugins is function // the type of remark/rehype plugins is function
const markdownPluginsFunctionStub = () => {}; const markdownPluginsFunctionStub = () => {};
const markdownPluginsObjectStub = {}; const markdownPluginsObjectStub = {};
function testValidateOptions(options: Partial<PluginOptions>) {
return validateOptions({
options: {
...DEFAULT_OPTIONS,
...options,
},
validate: normalizePluginOptions,
});
}
describe('normalizeDocsPluginOptions', () => { describe('normalizeDocsPluginOptions', () => {
test('should return default options for undefined user options', async () => { test('should return default options for undefined user options', async () => {
const {value, error} = await OptionsSchema.validate({}); const {value, error} = await OptionsSchema.validate({});
@ -58,6 +69,8 @@ describe('normalizeDocsPluginOptions', () => {
label: 'world', label: 'world',
}, },
}, },
sidebarCollapsible: false,
sidebarCollapsed: false,
}; };
const {value, error} = await OptionsSchema.validate(userOptions); const {value, error} = await OptionsSchema.validate(userOptions);
expect(value).toEqual(userOptions); expect(value).toEqual(userOptions);
@ -230,4 +243,30 @@ describe('normalizeDocsPluginOptions', () => {
`"\\"versions.current.hey\\" is not allowed"`, `"\\"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);
});
}); });

View file

@ -9,14 +9,13 @@ import {
CategoryMetadatasFile, CategoryMetadatasFile,
DefaultSidebarItemsGenerator, DefaultSidebarItemsGenerator,
} from '../sidebarItemsGenerator'; } from '../sidebarItemsGenerator';
import {DefaultCategoryCollapsedValue} from '../sidebars';
import {Sidebar, SidebarItemsGenerator} from '../types'; import {Sidebar, SidebarItemsGenerator} from '../types';
import fs from 'fs-extra'; import fs from 'fs-extra';
import {DefaultNumberPrefixParser} from '../numberPrefix'; import {DefaultNumberPrefixParser} from '../numberPrefix';
describe('DefaultSidebarItemsGenerator', () => { describe('DefaultSidebarItemsGenerator', () => {
function testDefaultSidebarItemsGenerator( function testDefaultSidebarItemsGenerator(
options: Partial<Parameters<SidebarItemsGenerator>[0]>, params: Partial<Parameters<SidebarItemsGenerator>[0]>,
) { ) {
return DefaultSidebarItemsGenerator({ return DefaultSidebarItemsGenerator({
numberPrefixParser: DefaultNumberPrefixParser, numberPrefixParser: DefaultNumberPrefixParser,
@ -29,7 +28,11 @@ describe('DefaultSidebarItemsGenerator', () => {
contentPath: 'docs', contentPath: 'docs',
}, },
docs: [], docs: [],
...options, options: {
sidebarCollapsed: true,
sidebarCollapsible: true,
},
...params,
}); });
} }
@ -110,6 +113,10 @@ describe('DefaultSidebarItemsGenerator', () => {
frontMatter: {}, frontMatter: {},
}, },
], ],
options: {
sidebarCollapsed: true,
sidebarCollapsible: true,
},
}); });
expect(sidebarSlice).toEqual([ expect(sidebarSlice).toEqual([
@ -190,6 +197,10 @@ describe('DefaultSidebarItemsGenerator', () => {
frontMatter: {}, frontMatter: {},
}, },
], ],
options: {
sidebarCollapsed: true,
sidebarCollapsible: true,
},
}); });
expect(sidebarSlice).toEqual([ expect(sidebarSlice).toEqual([
@ -197,7 +208,8 @@ describe('DefaultSidebarItemsGenerator', () => {
{ {
type: 'category', type: 'category',
label: 'Tutorials', label: 'Tutorials',
collapsed: DefaultCategoryCollapsedValue, collapsed: true,
collapsible: true,
items: [ items: [
{type: 'doc', id: 'tutorial1'}, {type: 'doc', id: 'tutorial1'},
{type: 'doc', id: 'tutorial2'}, {type: 'doc', id: 'tutorial2'},
@ -207,12 +219,14 @@ describe('DefaultSidebarItemsGenerator', () => {
type: 'category', type: 'category',
label: 'Guides', label: 'Guides',
collapsed: false, collapsed: false,
collapsible: true,
items: [ items: [
{type: 'doc', id: 'guide1'}, {type: 'doc', id: 'guide1'},
{ {
type: 'category', type: 'category',
label: 'SubGuides (metadata file label)', label: 'SubGuides (metadata file label)',
collapsed: DefaultCategoryCollapsedValue, collapsed: true,
collapsible: true,
items: [{type: 'doc', id: 'nested-guide'}], items: [{type: 'doc', id: 'nested-guide'}],
}, },
{type: 'doc', id: 'guide2'}, {type: 'doc', id: 'guide2'},
@ -233,6 +247,7 @@ describe('DefaultSidebarItemsGenerator', () => {
'subfolder/subsubfolder/subsubsubfolder3/_category_.json': { 'subfolder/subsubfolder/subsubsubfolder3/_category_.json': {
position: 1, position: 1,
label: 'subsubsubfolder3 (_category_.json label)', label: 'subsubsubfolder3 (_category_.json label)',
collapsible: false,
collapsed: false, collapsed: false,
}, },
}); });
@ -305,6 +320,10 @@ describe('DefaultSidebarItemsGenerator', () => {
frontMatter: {}, frontMatter: {},
}, },
], ],
options: {
sidebarCollapsed: true,
sidebarCollapsible: true,
},
}); });
expect(sidebarSlice).toEqual([ expect(sidebarSlice).toEqual([
@ -312,6 +331,7 @@ describe('DefaultSidebarItemsGenerator', () => {
type: 'category', type: 'category',
label: 'subsubsubfolder3 (_category_.json label)', label: 'subsubsubfolder3 (_category_.json label)',
collapsed: false, collapsed: false,
collapsible: false,
items: [ items: [
{type: 'doc', id: 'doc8'}, {type: 'doc', id: 'doc8'},
{type: 'doc', id: 'doc7'}, {type: 'doc', id: 'doc7'},
@ -321,6 +341,7 @@ describe('DefaultSidebarItemsGenerator', () => {
type: 'category', type: 'category',
label: 'subsubsubfolder2 (_category_.yml label)', label: 'subsubsubfolder2 (_category_.yml label)',
collapsed: true, collapsed: true,
collapsible: true,
items: [{type: 'doc', id: 'doc6'}], items: [{type: 'doc', id: 'doc6'}],
}, },
{type: 'doc', id: 'doc1'}, {type: 'doc', id: 'doc1'},
@ -329,6 +350,7 @@ describe('DefaultSidebarItemsGenerator', () => {
type: 'category', type: 'category',
label: 'subsubsubfolder', label: 'subsubsubfolder',
collapsed: true, collapsed: true,
collapsible: true,
items: [{type: 'doc', id: 'doc5'}], items: [{type: 'doc', id: 'doc5'}],
}, },
] as Sidebar); ] as Sidebar);

View file

@ -17,6 +17,7 @@ import {
processSidebars, processSidebars,
DefaultSidebars, DefaultSidebars,
DisabledSidebars, DisabledSidebars,
fixSidebarItemInconsistencies,
} from '../sidebars'; } from '../sidebars';
import { import {
Sidebar, Sidebar,
@ -24,6 +25,8 @@ import {
SidebarItemsGenerator, SidebarItemsGenerator,
Sidebars, Sidebars,
UnprocessedSidebars, UnprocessedSidebars,
SidebarOptions,
SidebarItemCategory,
} from '../types'; } from '../types';
import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator'; import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator';
@ -31,15 +34,19 @@ import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator';
describe('loadSidebars', () => { describe('loadSidebars', () => {
const fixtureDir = path.join(__dirname, '__fixtures__', 'sidebars'); const fixtureDir = path.join(__dirname, '__fixtures__', 'sidebars');
const options: SidebarOptions = {
sidebarCollapsed: true,
sidebarCollapsible: true,
};
test('sidebars with known sidebar item type', async () => { test('sidebars with known sidebar item type', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars.json'); const sidebarPath = path.join(fixtureDir, 'sidebars.json');
const result = loadSidebars(sidebarPath); const result = loadSidebars(sidebarPath, options);
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
test('sidebars with deep level of category', async () => { test('sidebars with deep level of category', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-category.js'); const sidebarPath = path.join(fixtureDir, 'sidebars-category.js');
const result = loadSidebars(sidebarPath); const result = loadSidebars(sidebarPath, options);
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
@ -49,8 +56,8 @@ describe('loadSidebars', () => {
fixtureDir, fixtureDir,
'sidebars-category-shorthand.js', 'sidebars-category-shorthand.js',
); );
const sidebar1 = loadSidebars(sidebarPath1); const sidebar1 = loadSidebars(sidebarPath1, options);
const sidebar2 = loadSidebars(sidebarPath2); const sidebar2 = loadSidebars(sidebarPath2, options);
expect(sidebar1).toEqual(sidebar2); expect(sidebar1).toEqual(sidebar2);
}); });
@ -59,7 +66,9 @@ describe('loadSidebars', () => {
fixtureDir, fixtureDir,
'sidebars-category-wrong-items.json', '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."`, `"Error loading {\\"type\\":\\"category\\",\\"label\\":\\"Category Label\\",\\"items\\":\\"doc1\\"}: \\"items\\" must be an array."`,
); );
}); });
@ -69,7 +78,9 @@ describe('loadSidebars', () => {
fixtureDir, fixtureDir,
'sidebars-category-wrong-label.json', '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."`, `"Error loading {\\"type\\":\\"category\\",\\"label\\":true,\\"items\\":[\\"doc1\\"]}: \\"label\\" must be a string."`,
); );
}); });
@ -79,7 +90,9 @@ describe('loadSidebars', () => {
fixtureDir, fixtureDir,
'sidebars-doc-id-not-string.json', '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."`, `"Error loading {\\"type\\":\\"doc\\",\\"id\\":[\\"doc1\\"]}: \\"id\\" must be a string."`,
); );
}); });
@ -89,33 +102,38 @@ describe('loadSidebars', () => {
fixtureDir, fixtureDir,
'sidebars-first-level-not-category.js', 'sidebars-first-level-not-category.js',
); );
const result = loadSidebars(sidebarPath); const result = loadSidebars(sidebarPath, options);
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
test('sidebars link', async () => { test('sidebars link', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-link.json'); const sidebarPath = path.join(fixtureDir, 'sidebars-link.json');
const result = loadSidebars(sidebarPath); const result = loadSidebars(sidebarPath, options);
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
test('sidebars link wrong label', async () => { test('sidebars link wrong label', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-label.json'); 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."`, `"Error loading {\\"type\\":\\"link\\",\\"label\\":false,\\"href\\":\\"https://github.com\\"}: \\"label\\" must be a string."`,
); );
}); });
test('sidebars link wrong href', async () => { test('sidebars link wrong href', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-href.json'); 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."`, `"Error loading {\\"type\\":\\"link\\",\\"label\\":\\"GitHub\\",\\"href\\":[\\"example.com\\"]}: \\"href\\" must be a string."`,
); );
}); });
test('sidebars with unknown sidebar item type', async () => { test('sidebars with unknown sidebar item type', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-unknown-type.json'); 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\\"}. "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 () => { test('sidebars with known sidebar item type but wrong field', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-wrong-field.json'); 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\\"}"`, `"Unknown sidebar item keys: href. Item: {\\"type\\":\\"category\\",\\"label\\":\\"category\\",\\"href\\":\\"https://github.com\\"}"`,
); );
}); });
test('unexisting path', () => { test('unexisting path', () => {
expect(loadSidebars('badpath')).toEqual(DisabledSidebars); expect(loadSidebars('badpath', options)).toEqual(DisabledSidebars);
}); });
test('undefined path', () => { test('undefined path', () => {
expect(loadSidebars(undefined)).toEqual(DefaultSidebars); expect(loadSidebars(undefined, options)).toEqual(DefaultSidebars);
}); });
test('literal false path', () => { test('literal false path', () => {
expect(loadSidebars(false)).toEqual(DisabledSidebars); expect(loadSidebars(false, options)).toEqual(DisabledSidebars);
}); });
test('sidebars with category.collapsed property', async () => { test('sidebars with category.collapsed property', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-collapsed.json'); const sidebarPath = path.join(fixtureDir, 'sidebars-collapsed.json');
const result = loadSidebars(sidebarPath); const result = loadSidebars(sidebarPath, options);
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
@ -151,7 +171,7 @@ describe('loadSidebars', () => {
fixtureDir, fixtureDir,
'sidebars-collapsed-first-level.json', 'sidebars-collapsed-first-level.json',
); );
const result = loadSidebars(sidebarPath); const result = loadSidebars(sidebarPath, options);
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
}); });
@ -162,23 +182,27 @@ describe('collectSidebarDocItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category1', label: 'Category1',
items: [ items: [
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 1', label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}], items: [{type: 'doc', id: 'doc1'}],
}, },
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 2', label: 'Subcategory 2',
items: [ items: [
{type: 'doc', id: 'doc2'}, {type: 'doc', id: 'doc2'},
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Sub sub category 1', label: 'Sub sub category 1',
items: [{type: 'doc', id: 'doc3'}], items: [{type: 'doc', id: 'doc3'}],
}, },
@ -189,6 +213,7 @@ describe('collectSidebarDocItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category2', label: 'Category2',
items: [ items: [
{type: 'doc', id: 'doc4'}, {type: 'doc', id: 'doc4'},
@ -213,23 +238,27 @@ describe('collectSidebarCategories', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category1', label: 'Category1',
items: [ items: [
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 1', label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}], items: [{type: 'doc', id: 'doc1'}],
}, },
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 2', label: 'Subcategory 2',
items: [ items: [
{type: 'doc', id: 'doc2'}, {type: 'doc', id: 'doc2'},
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Sub sub category 1', label: 'Sub sub category 1',
items: [{type: 'doc', id: 'doc3'}], items: [{type: 'doc', id: 'doc3'}],
}, },
@ -240,6 +269,7 @@ describe('collectSidebarCategories', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category2', label: 'Category2',
items: [ items: [
{type: 'doc', id: 'doc4'}, {type: 'doc', id: 'doc4'},
@ -266,6 +296,7 @@ describe('collectSidebarLinks', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category1', label: 'Category1',
items: [ items: [
{ {
@ -276,6 +307,7 @@ describe('collectSidebarLinks', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 2', label: 'Subcategory 2',
items: [ items: [
{ {
@ -302,11 +334,13 @@ describe('collectSidebarsDocIds', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category1', label: 'Category1',
items: [ items: [
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 1', label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}], items: [{type: 'doc', id: 'doc1'}],
}, },
@ -319,6 +353,7 @@ describe('collectSidebarsDocIds', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category2', label: 'Category2',
items: [ items: [
{type: 'doc', id: 'doc3'}, {type: 'doc', id: 'doc3'},
@ -345,11 +380,13 @@ describe('transformSidebarItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category1', label: 'Category1',
items: [ items: [
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 1', label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}], items: [{type: 'doc', id: 'doc1'}],
customProps: {fakeProp: false}, customProps: {fakeProp: false},
@ -357,12 +394,14 @@ describe('transformSidebarItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 2', label: 'Subcategory 2',
items: [ items: [
{type: 'doc', id: 'doc2'}, {type: 'doc', id: 'doc2'},
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Sub sub category 1', label: 'Sub sub category 1',
items: [ items: [
{type: 'doc', id: 'doc3', customProps: {lorem: 'ipsum'}}, {type: 'doc', id: 'doc3', customProps: {lorem: 'ipsum'}},
@ -375,6 +414,7 @@ describe('transformSidebarItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category2', label: 'Category2',
items: [ items: [
{type: 'doc', id: 'doc4'}, {type: 'doc', id: 'doc4'},
@ -394,11 +434,13 @@ describe('transformSidebarItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'MODIFIED LABEL: Category1', label: 'MODIFIED LABEL: Category1',
items: [ items: [
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'MODIFIED LABEL: Subcategory 1', label: 'MODIFIED LABEL: Subcategory 1',
items: [{type: 'doc', id: 'doc1'}], items: [{type: 'doc', id: 'doc1'}],
customProps: {fakeProp: false}, customProps: {fakeProp: false},
@ -406,12 +448,14 @@ describe('transformSidebarItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'MODIFIED LABEL: Subcategory 2', label: 'MODIFIED LABEL: Subcategory 2',
items: [ items: [
{type: 'doc', id: 'doc2'}, {type: 'doc', id: 'doc2'},
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'MODIFIED LABEL: Sub sub category 1', label: 'MODIFIED LABEL: Sub sub category 1',
items: [ items: [
{type: 'doc', id: 'doc3', customProps: {lorem: 'ipsum'}}, {type: 'doc', id: 'doc3', customProps: {lorem: 'ipsum'}},
@ -424,6 +468,7 @@ describe('transformSidebarItems', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'MODIFIED LABEL: Category2', label: 'MODIFIED LABEL: Category2',
items: [ items: [
{type: 'doc', id: 'doc4'}, {type: 'doc', id: 'doc4'},
@ -463,6 +508,7 @@ describe('processSidebars', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc2'}], items: [{type: 'doc', id: 'doc2'}],
label: 'Category', label: 'Category',
}, },
@ -474,6 +520,7 @@ describe('processSidebars', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc4'}], items: [{type: 'doc', id: 'doc4'}],
label: 'Category', label: 'Category',
}, },
@ -491,6 +538,7 @@ describe('processSidebars', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
items: [ items: [
{type: 'doc', id: 'doc2'}, {type: 'doc', id: 'doc2'},
{type: 'autogenerated', dirName: 'dir1'}, {type: 'autogenerated', dirName: 'dir1'},
@ -507,6 +555,7 @@ describe('processSidebars', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc4'}], items: [{type: 'doc', id: 'doc4'}],
label: 'Category', label: 'Category',
}, },
@ -541,6 +590,7 @@ describe('processSidebars', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc2'}, ...StaticGeneratedSidebarSlice], items: [{type: 'doc', id: 'doc2'}, ...StaticGeneratedSidebarSlice],
label: 'Category', label: 'Category',
}, },
@ -554,6 +604,7 @@ describe('processSidebars', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc4'}], items: [{type: 'doc', id: 'doc4'}],
label: 'Category', label: 'Category',
}, },
@ -567,11 +618,13 @@ describe('createSidebarsUtils', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category1', label: 'Category1',
items: [ items: [
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Subcategory 1', label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}], items: [{type: 'doc', id: 'doc1'}],
}, },
@ -584,6 +637,7 @@ describe('createSidebarsUtils', () => {
{ {
type: 'category', type: 'category',
collapsed: false, collapsed: false,
collapsible: true,
label: 'Category2', label: 'Category2',
items: [ items: [
{type: 'doc', id: 'doc3'}, {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,
},
],
});
});
});

View file

@ -16,6 +16,7 @@ import {
PathOptions, PathOptions,
UnprocessedSidebarItem, UnprocessedSidebarItem,
UnprocessedSidebars, UnprocessedSidebars,
SidebarOptions,
} from './types'; } from './types';
import {loadSidebars, resolveSidebarPathOption} from './sidebars'; import {loadSidebars, resolveSidebarPathOption} from './sidebars';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
@ -25,14 +26,16 @@ function createVersionedSidebarFile({
pluginId, pluginId,
sidebarPath, sidebarPath,
version, version,
options,
}: { }: {
siteDir: string; siteDir: string;
pluginId: string; pluginId: string;
sidebarPath: string | false | undefined; sidebarPath: string | false | undefined;
version: string; version: string;
options: SidebarOptions;
}) { }) {
// Load current sidebar and create a new versioned sidebars file (if needed). // 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) // Do not create a useless versioned sidebars file if sidebars file is empty or sidebars are disabled/false)
const shouldCreateVersionedSidebarFile = const shouldCreateVersionedSidebarFile =
@ -87,7 +90,7 @@ export function cliDocsVersionCommand(
version: string | null | undefined, version: string | null | undefined,
siteDir: string, siteDir: string,
pluginId: string, pluginId: string,
options: PathOptions, options: PathOptions & SidebarOptions,
): void { ): void {
// It wouldn't be very user-friendly to show a [default] log prefix, // It wouldn't be very user-friendly to show a [default] log prefix,
// so we use [docs] instead of [default] // so we use [docs] instead of [default]
@ -159,6 +162,7 @@ export function cliDocsVersionCommand(
pluginId, pluginId,
version, version,
sidebarPath: resolveSidebarPathOption(siteDir, sidebarPath), sidebarPath: resolveSidebarPathOption(siteDir, sidebarPath),
options,
}); });
// Update versions.json file. // Update versions.json file.

View file

@ -100,6 +100,8 @@ export default function pluginContentDocs(
cliDocsVersionCommand(version, siteDir, pluginId, { cliDocsVersionCommand(version, siteDir, pluginId, {
path: options.path, path: options.path,
sidebarPath: options.sidebarPath, sidebarPath: options.sidebarPath,
sidebarCollapsed: options.sidebarCollapsed,
sidebarCollapsible: options.sidebarCollapsible,
}); });
}); });
}, },
@ -168,6 +170,10 @@ export default function pluginContentDocs(
): Promise<LoadedVersion> { ): Promise<LoadedVersion> {
const unprocessedSidebars = loadSidebars( const unprocessedSidebars = loadSidebars(
versionMetadata.sidebarFilePath, versionMetadata.sidebarFilePath,
{
sidebarCollapsed: options.sidebarCollapsed,
sidebarCollapsible: options.sidebarCollapsible,
},
); );
const docsBase: DocMetadataBase[] = await loadVersionDocsBase( const docsBase: DocMetadataBase[] = await loadVersionDocsBase(
@ -184,6 +190,10 @@ export default function pluginContentDocs(
unprocessedSidebars, unprocessedSidebars,
docs: docsBase, docs: docsBase,
version: versionMetadata, version: versionMetadata,
options: {
sidebarCollapsed: options.sidebarCollapsed,
sidebarCollapsible: options.sidebarCollapsible,
},
}); });
const sidebarsUtils = createSidebarsUtils(sidebars); const sidebarsUtils = createSidebarsUtils(sidebars);

View file

@ -46,6 +46,8 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
versions: {}, versions: {},
editCurrentVersion: false, editCurrentVersion: false,
editLocalizedFiles: false, editLocalizedFiles: false,
sidebarCollapsible: true,
sidebarCollapsed: true,
}; };
const VersionOptionsSchema = Joi.object({ const VersionOptionsSchema = Joi.object({
@ -77,6 +79,8 @@ export const OptionsSchema = Joi.object({
sidebarItemsGenerator: Joi.function().default( sidebarItemsGenerator: Joi.function().default(
() => DEFAULT_OPTIONS.sidebarItemsGenerator, () => DEFAULT_OPTIONS.sidebarItemsGenerator,
), ),
sidebarCollapsible: Joi.boolean().default(DEFAULT_OPTIONS.sidebarCollapsible),
sidebarCollapsed: Joi.boolean().default(DEFAULT_OPTIONS.sidebarCollapsed),
numberPrefixParser: Joi.alternatives() numberPrefixParser: Joi.alternatives()
.try( .try(
Joi.function(), Joi.function(),
@ -116,8 +120,32 @@ export const OptionsSchema = Joi.object({
export function validateOptions({ export function validateOptions({
validate, validate,
options, options: userOptions,
}: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> { }: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
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 // TODO remove homePageId before end of 2020
// "slug: /" is better because the home doc can be different across versions // "slug: /" is better because the home doc can be different across versions
if (options.homePageId) { if (options.homePageId) {

View file

@ -33,7 +33,8 @@ declare module '@docusaurus/plugin-content-docs-types' {
type: 'category'; type: 'category';
label: string; label: string;
items: PropSidebarItem[]; items: PropSidebarItem[];
collapsed?: boolean; collapsed: boolean;
collapsible: boolean;
}; };
export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory; export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory;

View file

@ -19,7 +19,6 @@ import chalk from 'chalk';
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import Yaml from 'js-yaml'; import Yaml from 'js-yaml';
import {DefaultCategoryCollapsedValue} from './sidebars';
const BreadcrumbSeparator = '/'; const BreadcrumbSeparator = '/';
@ -30,6 +29,7 @@ export type CategoryMetadatasFile = {
label?: string; label?: string;
position?: number; position?: number;
collapsed?: boolean; collapsed?: boolean;
collapsible?: boolean;
// TODO should we allow "items" here? how would this work? would an "autogenerated" type be allowed? // 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/ // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/
@ -43,6 +43,7 @@ const CategoryMetadatasFileSchema = Joi.object<CategoryMetadatasFile>({
label: Joi.string(), label: Joi.string(),
position: Joi.number(), position: Joi.number(),
collapsed: Joi.boolean(), 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 // 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, docs: allDocs,
version, version,
numberPrefixParser, numberPrefixParser,
}): Promise<SidebarItem[]> { options,
}) {
// Doc at the root of the autogenerated sidebar dir // Doc at the root of the autogenerated sidebar dir
function isRootDoc(doc: SidebarItemsGeneratorDoc) { function isRootDoc(doc: SidebarItemsGeneratorDoc) {
return doc.sourceDirName === item.dirName; return doc.sourceDirName === item.dirName;
@ -199,11 +201,16 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async functio
const position = categoryMetadatas?.position ?? numberPrefix; const position = categoryMetadatas?.position ?? numberPrefix;
const collapsible =
categoryMetadatas?.collapsible ?? options.sidebarCollapsible;
const collapsed = categoryMetadatas?.collapsed ?? options.sidebarCollapsed;
return { return {
type: 'category', type: 'category',
label: categoryMetadatas?.label ?? filename, label: categoryMetadatas?.label ?? filename,
items: [], items: [],
collapsed: categoryMetadatas?.collapsed ?? DefaultCategoryCollapsedValue, collapsed,
collapsible,
...(typeof position !== 'undefined' && {position}), ...(typeof position !== 'undefined' && {position}),
}; };
} }
@ -268,6 +275,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async functio
} }
// async process made sequential on purpose! order matters // async process made sequential on purpose! order matters
// eslint-disable-next-line no-restricted-syntax
for (const doc of docs) { for (const doc of docs) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await handleDocItem(doc); await handleDocItem(doc);

View file

@ -25,6 +25,7 @@ import {
SidebarItemsGeneratorVersion, SidebarItemsGeneratorVersion,
NumberPrefixParser, NumberPrefixParser,
SidebarItemsGeneratorOption, SidebarItemsGeneratorOption,
SidebarOptions,
PluginOptions, PluginOptions,
} from './types'; } from './types';
import {mapValues, flatten, flatMap, difference, pick, memoize} from 'lodash'; import {mapValues, flatten, flatMap, difference, pick, memoize} from 'lodash';
@ -38,6 +39,7 @@ type SidebarItemCategoryJSON = SidebarItemBase & {
label: string; label: string;
items: SidebarItemJSON[]; items: SidebarItemJSON[];
collapsed?: boolean; collapsed?: boolean;
collapsible?: boolean;
}; };
type SidebarItemAutogeneratedJSON = SidebarItemBase & { type SidebarItemAutogeneratedJSON = SidebarItemBase & {
@ -74,18 +76,17 @@ function isCategoryShorthand(
return typeof item !== 'string' && !item.type; 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 * Convert {category1: [item1,item2]} shorthand syntax to long-form syntax
*/ */
function normalizeCategoryShorthand( function normalizeCategoryShorthand(
sidebar: SidebarCategoryShorthandJSON, sidebar: SidebarCategoryShorthandJSON,
options: SidebarOptions,
): SidebarItemCategoryJSON[] { ): SidebarItemCategoryJSON[] {
return Object.entries(sidebar).map(([label, items]) => ({ return Object.entries(sidebar).map(([label, items]) => ({
type: 'category', type: 'category',
collapsed: DefaultCategoryCollapsedValue, collapsed: options.sidebarCollapsed,
collapsible: options.sidebarCollapsible,
label, label,
items, items,
})); }));
@ -115,7 +116,13 @@ function assertItem<K extends string>(
function assertIsCategory( function assertIsCategory(
item: Record<string, unknown>, item: Record<string, unknown>,
): asserts item is SidebarItemCategoryJSON { ): asserts item is SidebarItemCategoryJSON {
assertItem(item, ['items', 'label', 'collapsed', 'customProps']); assertItem(item, [
'items',
'label',
'collapsed',
'collapsible',
'customProps',
]);
if (typeof item.label !== 'string') { if (typeof item.label !== 'string') {
throw new Error( throw new Error(
`Error loading ${JSON.stringify(item)}: "label" must be a string.`, `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.`, `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( function assertIsAutogenerated(
@ -192,7 +207,10 @@ function assertIsLink(
* Normalizes recursively item and all its children. Ensures that at the end * Normalizes recursively item and all its children. Ensures that at the end
* each item will be an object with the corresponding type. * 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') { if (typeof item === 'string') {
return [ return [
{ {
@ -202,16 +220,21 @@ function normalizeItem(item: SidebarItemJSON): UnprocessedSidebarItem[] {
]; ];
} }
if (isCategoryShorthand(item)) { if (isCategoryShorthand(item)) {
return flatMap(normalizeCategoryShorthand(item), normalizeItem); return flatMap(normalizeCategoryShorthand(item, options), (subitem) =>
normalizeItem(subitem, options),
);
} }
switch (item.type) { switch (item.type) {
case 'category': case 'category':
assertIsCategory(item); assertIsCategory(item);
return [ return [
{ {
collapsed: DefaultCategoryCollapsedValue,
...item, ...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': 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) const normalizedSidebar: SidebarItemJSON[] = Array.isArray(sidebar)
? sidebar ? sidebar
: normalizeCategoryShorthand(sidebar); : normalizeCategoryShorthand(sidebar, options);
return flatMap(normalizedSidebar, normalizeItem); return flatMap(normalizedSidebar, (subitem) =>
normalizeItem(subitem, options),
);
} }
function normalizeSidebars(sidebars: SidebarsJSON): UnprocessedSidebars { function normalizeSidebars(
return mapValues(sidebars, normalizeSidebar); sidebars: SidebarsJSON,
options: SidebarOptions,
): UnprocessedSidebars {
return mapValues(sidebars, (subitem) => normalizeSidebar(subitem, options));
} }
export const DefaultSidebars: UnprocessedSidebars = { export const DefaultSidebars: UnprocessedSidebars = {
@ -276,6 +307,7 @@ export function resolveSidebarPathOption(
// Note: sidebarFilePath must be absolute, use resolveSidebarPathOption // Note: sidebarFilePath must be absolute, use resolveSidebarPathOption
export function loadSidebars( export function loadSidebars(
sidebarFilePath: string | false | undefined, sidebarFilePath: string | false | undefined,
options: SidebarOptions,
): UnprocessedSidebars { ): UnprocessedSidebars {
// false => no sidebars // false => no sidebars
if (sidebarFilePath === false) { if (sidebarFilePath === false) {
@ -297,7 +329,7 @@ export function loadSidebars(
// We don't want sidebars to be cached because of hot reloading. // We don't want sidebars to be cached because of hot reloading.
const sidebarJson = importFresh(sidebarFilePath) as SidebarsJSON; const sidebarJson = importFresh(sidebarFilePath) as SidebarsJSON;
return normalizeSidebars(sidebarJson); return normalizeSidebars(sidebarJson, options);
} }
export function toSidebarItemsGeneratorDoc( export function toSidebarItemsGeneratorDoc(
@ -317,19 +349,44 @@ export function toSidebarItemsGeneratorVersion(
return pick(version, ['versionName', 'contentPath']); 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({ export async function processSidebar({
sidebarItemsGenerator, sidebarItemsGenerator,
numberPrefixParser, numberPrefixParser,
unprocessedSidebar, unprocessedSidebar,
docs, docs,
version, version,
options,
}: { }: {
sidebarItemsGenerator: SidebarItemsGeneratorOption; sidebarItemsGenerator: SidebarItemsGeneratorOption;
numberPrefixParser: NumberPrefixParser; numberPrefixParser: NumberPrefixParser;
unprocessedSidebar: UnprocessedSidebar; unprocessedSidebar: UnprocessedSidebar;
docs: DocMetadataBase[]; docs: DocMetadataBase[];
version: VersionMetadata; version: VersionMetadata;
options: SidebarOptions;
}): Promise<Sidebar> { }): Promise<Sidebar> {
// Just a minor lazy transformation optimization // Just a minor lazy transformation optimization
const getSidebarItemsGeneratorDocsAndVersion = memoize(() => ({ const getSidebarItemsGeneratorDocsAndVersion = memoize(() => ({
@ -337,14 +394,16 @@ export async function processSidebar({
version: toSidebarItemsGeneratorVersion(version), version: toSidebarItemsGeneratorVersion(version),
})); }));
async function processRecursive( async function handleAutoGeneratedItems(
item: UnprocessedSidebarItem, item: UnprocessedSidebarItem,
): Promise<SidebarItem[]> { ): Promise<SidebarItem[]> {
if (item.type === 'category') { if (item.type === 'category') {
return [ return [
{ {
...item, ...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, numberPrefixParser,
defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator, defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator,
...getSidebarItemsGeneratorDocsAndVersion(), ...getSidebarItemsGeneratorDocsAndVersion(),
options,
}); });
} }
return [item]; 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({ export async function processSidebars({
@ -368,12 +432,14 @@ export async function processSidebars({
unprocessedSidebars, unprocessedSidebars,
docs, docs,
version, version,
options,
}: { }: {
sidebarItemsGenerator: SidebarItemsGeneratorOption; sidebarItemsGenerator: SidebarItemsGeneratorOption;
numberPrefixParser: NumberPrefixParser; numberPrefixParser: NumberPrefixParser;
unprocessedSidebars: UnprocessedSidebars; unprocessedSidebars: UnprocessedSidebars;
docs: DocMetadataBase[]; docs: DocMetadataBase[];
version: VersionMetadata; version: VersionMetadata;
options: SidebarOptions;
}): Promise<Sidebars> { }): Promise<Sidebars> {
return combinePromises( return combinePromises(
mapValues(unprocessedSidebars, (unprocessedSidebar) => mapValues(unprocessedSidebars, (unprocessedSidebar) =>
@ -383,6 +449,7 @@ export async function processSidebars({
unprocessedSidebar, unprocessedSidebar,
docs, docs,
version, version,
options,
}), }),
), ),
); );

View file

@ -75,10 +75,16 @@ export type VersionsOptions = {
onlyIncludeVersions?: string[]; onlyIncludeVersions?: string[];
}; };
export type SidebarOptions = {
sidebarCollapsible: boolean;
sidebarCollapsed: boolean;
};
export type PluginOptions = MetadataOptions & export type PluginOptions = MetadataOptions &
PathOptions & PathOptions &
VersionsOptions & VersionsOptions &
RemarkAndRehypePluginOptions & { RemarkAndRehypePluginOptions &
SidebarOptions & {
id: string; id: string;
include: string[]; include: string[];
exclude: string[]; exclude: string[];
@ -111,6 +117,7 @@ export type SidebarItemCategory = SidebarItemBase & {
label: string; label: string;
items: SidebarItem[]; items: SidebarItem[];
collapsed: boolean; collapsed: boolean;
collapsible: boolean;
}; };
export type UnprocessedSidebarItemAutogenerated = { export type UnprocessedSidebarItemAutogenerated = {
@ -123,6 +130,7 @@ export type UnprocessedSidebarItemCategory = SidebarItemBase & {
label: string; label: string;
items: UnprocessedSidebarItem[]; items: UnprocessedSidebarItem[];
collapsed: boolean; collapsed: boolean;
collapsible: boolean;
}; };
export type UnprocessedSidebarItem = export type UnprocessedSidebarItem =
@ -160,6 +168,7 @@ export type SidebarItemsGeneratorArgs = {
version: SidebarItemsGeneratorVersion; version: SidebarItemsGeneratorVersion;
docs: SidebarItemsGeneratorDoc[]; docs: SidebarItemsGeneratorDoc[];
numberPrefixParser: NumberPrefixParser; numberPrefixParser: NumberPrefixParser;
options: SidebarOptions;
}; };
export type SidebarItemsGenerator = ( export type SidebarItemsGenerator = (
generatorArgs: SidebarItemsGeneratorArgs, generatorArgs: SidebarItemsGeneratorArgs,

View file

@ -36,7 +36,7 @@ function DocPageContent({
versionMetadata, versionMetadata,
children, children,
}: DocPageContentProps): JSX.Element { }: DocPageContentProps): JSX.Element {
const {siteConfig, isClient} = useDocusaurusContext(); const {isClient} = useDocusaurusContext();
const {pluginId, version} = versionMetadata; const {pluginId, version} = versionMetadata;
const sidebarName = currentDocRoute.sidebar; const sidebarName = currentDocRoute.sidebar;
@ -88,7 +88,6 @@ function DocPageContent({
} }
sidebar={sidebar} sidebar={sidebar}
path={currentDocRoute.path} path={currentDocRoute.path}
sidebarCollapsible={siteConfig.themeConfig.sidebarCollapsible}
onCollapse={toggleSidebar} onCollapse={toggleSidebar}
isHidden={hiddenSidebar} isHidden={hiddenSidebar}
/> />

View file

@ -58,13 +58,7 @@ function HideableSidebarButton({onClick}) {
); );
} }
function DocSidebarDesktop({ function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) {
path,
sidebar,
sidebarCollapsible,
onCollapse,
isHidden,
}: Props) {
const showAnnouncementBar = useShowAnnouncementBar(); const showAnnouncementBar = useShowAnnouncementBar();
const { const {
navbar: {hideOnScroll}, navbar: {hideOnScroll},
@ -85,11 +79,7 @@ function DocSidebarDesktop({
!isAnnouncementBarClosed && showAnnouncementBar, !isAnnouncementBarClosed && showAnnouncementBar,
})}> })}>
<ul className="menu__list"> <ul className="menu__list">
<DocSidebarItems <DocSidebarItems items={sidebar} activePath={path} />
items={sidebar}
collapsible={sidebarCollapsible}
activePath={path}
/>
</ul> </ul>
</nav> </nav>
{hideableSidebar && <HideableSidebarButton onClick={onCollapse} />} {hideableSidebar && <HideableSidebarButton onClick={onCollapse} />}
@ -100,14 +90,12 @@ function DocSidebarDesktop({
const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent<Props> = ({ const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent<Props> = ({
toggleSidebar, toggleSidebar,
sidebar, sidebar,
sidebarCollapsible,
path, path,
}) => { }) => {
return ( return (
<ul className="menu__list"> <ul className="menu__list">
<DocSidebarItems <DocSidebarItems
items={sidebar} items={sidebar}
collapsible={sidebarCollapsible}
activePath={path} activePath={path}
onItemClick={() => toggleSidebar()} onItemClick={() => toggleSidebar()}
/> />

View file

@ -95,11 +95,10 @@ function useAutoExpandActiveCategory({
function DocSidebarItemCategory({ function DocSidebarItemCategory({
item, item,
onItemClick, onItemClick,
collapsible = true,
activePath, activePath,
...props ...props
}: Props & {item: PropSidebarItemCategory}) { }: Props & {item: PropSidebarItemCategory}) {
const {items, label} = item; const {items, label, collapsible} = item;
const isActive = isActiveSidebarItem(item, activePath); const isActive = isActiveSidebarItem(item, activePath);
@ -110,7 +109,7 @@ function DocSidebarItemCategory({
if (!collapsible) { if (!collapsible) {
return false; return false;
} }
return isActive ? false : item.collapsed ?? true; return isActive ? false : item.collapsed;
}, },
}); });
@ -146,7 +145,6 @@ function DocSidebarItemCategory({
items={items} items={items}
tabIndex={collapsed ? -1 : 0} tabIndex={collapsed ? -1 : 0}
onItemClick={onItemClick} onItemClick={onItemClick}
collapsible={collapsible}
activePath={activePath} activePath={activePath}
/> />
</Collapsible> </Collapsible>
@ -158,7 +156,6 @@ function DocSidebarItemLink({
item, item,
onItemClick, onItemClick,
activePath, activePath,
collapsible: _collapsible,
...props ...props
}: Props & {item: PropSidebarItemLink}) { }: Props & {item: PropSidebarItemLink}) {
const {href, label} = item; const {href, label} = item;

View file

@ -93,7 +93,6 @@ declare module '@theme/DocSidebar' {
export type Props = { export type Props = {
readonly path: string; readonly path: string;
readonly sidebar: readonly PropSidebarItem[]; readonly sidebar: readonly PropSidebarItem[];
readonly sidebarCollapsible?: boolean;
readonly onCollapse: () => void; readonly onCollapse: () => void;
readonly isHidden: boolean; readonly isHidden: boolean;
}; };
@ -107,7 +106,6 @@ declare module '@theme/DocSidebarItem' {
type DocSidebarPropsBase = { type DocSidebarPropsBase = {
readonly activePath: string; readonly activePath: string;
readonly collapsible?: boolean;
readonly onItemClick?: () => void; readonly onItemClick?: () => void;
readonly tabIndex?: number; readonly tabIndex?: number;
}; };

View file

@ -40,7 +40,6 @@ const DEFAULT_CONFIG = {
items: [], items: [],
}, },
hideableSidebar: false, hideableSidebar: false,
sidebarCollapsible: true,
}; };
exports.DEFAULT_CONFIG = DEFAULT_CONFIG; exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
@ -310,7 +309,10 @@ const ThemeConfigSchema = Joi.object({
.default(DEFAULT_CONFIG.prism) .default(DEFAULT_CONFIG.prism)
.unknown(), .unknown(),
hideableSidebar: Joi.bool().default(DEFAULT_CONFIG.hideableSidebar), 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; exports.ThemeConfigSchema = ThemeConfigSchema;

View file

@ -46,7 +46,11 @@ type ContextValue = ReturnType<typeof useContextValue>;
const Context = createContext<ContextValue | null>(null); const Context = createContext<ContextValue | null>(null);
export function MobileSecondaryMenuProvider({children}: {children: ReactNode}) { export function MobileSecondaryMenuProvider({
children,
}: {
children: ReactNode;
}): JSX.Element {
return ( return (
<Context.Provider value={useContextValue()}>{children}</Context.Provider> <Context.Provider value={useContextValue()}>{children}</Context.Provider>
); );

View file

@ -0,0 +1 @@
# Another test page

View file

@ -12,6 +12,12 @@ module.exports = {
id: 'index', id: 'index',
label: 'Index', label: 'Index',
}, },
{
type: 'category',
label: 'section',
collapsible: false,
items: ['index', 'more-test'],
},
{ {
type: 'category', type: 'category',
label: 'Huge sidebar category', label: 'Huge sidebar category',

View file

@ -82,6 +82,16 @@ module.exports = {
* Path to sidebar configuration for showing a list of markdown pages. * Path to sidebar configuration for showing a list of markdown pages.
*/ */
sidebarPath: 'sidebars.js', 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" * Function used to replace the sidebar items of type "autogenerated"
* by real sidebar items (docs, categories, links...) * by real sidebar items (docs, categories, links...)

View file

@ -285,7 +285,8 @@ type SidebarItemCategory = {
items: SidebarItem[]; // Array of sidebar items. items: SidebarItem[]; // Array of sidebar items.
// Category options: // 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', type: 'category',
label: 'Guides', label: 'Guides',
collapsible: true,
collapsed: false, collapsed: false,
items: [ items: [
'creating-pages', 'creating-pages',
@ -332,15 +334,25 @@ module.exports = {
#### Collapsible categories {#collapsible-categories} #### 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" ```js title="docusaurus.config.js"
module.exports = { module.exports = {
themeConfig: { presets: [
[
'@docusaurus/preset-classic',
{
docs: {
// highlight-start // highlight-start
sidebarCollapsible: false, sidebarCollapsible: true,
sidebarCollapsed: false,
// highlight-end // highlight-end
}, },
},
],
],
}; };
``` ```
@ -356,6 +368,7 @@ module.exports = {
{ {
type: 'category', type: 'category',
label: 'Docs', label: 'Docs',
collapsible: true,
collapsed: false, collapsed: false,
items: ['markdown-features', 'sidebar', 'versioning'], items: ['markdown-features', 'sidebar', 'versioning'],
}, },
@ -465,6 +478,7 @@ This is the easy tutorial!
```yaml title="docs/tutorials/_category_.yml" ```yaml title="docs/tutorials/_category_.yml"
label: 'Tutorial' label: 'Tutorial'
position: 2.5 # float position is supported position: 2.5 # float position is supported
collapsible: true # make the category collapsible
collapsed: false # keep the category open by default collapsed: false # keep the category open by default
``` ```

View file

@ -229,8 +229,6 @@ module.exports = {
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`, // You can also put own HTML here. copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`, // You can also put own HTML here.
}, },
image: 'img/docusaurus.png', 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. - `defaultVersionShown` - Versioning is not ported yet. You'd be unable to migration to Docusaurus 2 if you are using versioning. Stay tuned.
- `disableHeaderTitle` - `disableHeaderTitle`
- `disableTitleTagline` - `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` - `facebookAppId`
- `facebookComments` - `facebookComments`
- `facebookPixelId` - `facebookPixelId`

View file

@ -227,6 +227,8 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging;
// routeBasePath: '/', // routeBasePath: '/',
path: 'docs', path: 'docs',
sidebarPath: 'sidebars.js', sidebarPath: 'sidebars.js',
// sidebarCollapsible: false,
// sidebarCollapsed: true,
editUrl: ({locale, docPath}) => { editUrl: ({locale, docPath}) => {
if (locale !== 'en') { if (locale !== 'en') {
return `https://crowdin.com/project/docusaurus-v2/${locale}`; return `https://crowdin.com/project/docusaurus-v2/${locale}`;
@ -285,7 +287,6 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging;
liveCodeBlock: { liveCodeBlock: {
playgroundPosition: 'bottom', playgroundPosition: 'bottom',
}, },
sidebarCollapsible: true,
hideableSidebar: true, hideableSidebar: true,
colorMode: { colorMode: {
defaultMode: 'light', defaultMode: 'light',