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 [
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",

View file

@ -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",

View file

@ -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",

View file

@ -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'),
};

View file

@ -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',

View file

@ -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<PluginOptions>) {
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);
});
});

View file

@ -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<Parameters<SidebarItemsGenerator>[0]>,
params: Partial<Parameters<SidebarItemsGenerator>[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);

View file

@ -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,
},
],
});
});
});

View file

@ -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.

View file

@ -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<LoadedVersion> {
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);

View file

@ -46,6 +46,8 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
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<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
// "slug: /" is better because the home doc can be different across versions
if (options.homePageId) {

View file

@ -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;

View file

@ -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<CategoryMetadatasFile>({
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<SidebarItem[]> {
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);

View file

@ -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<K extends string>(
function assertIsCategory(
item: Record<string, unknown>,
): 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<Sidebar> {
// 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<SidebarItem[]> {
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<Sidebars> {
return combinePromises(
mapValues(unprocessedSidebars, (unprocessedSidebar) =>
@ -383,6 +449,7 @@ export async function processSidebars({
unprocessedSidebar,
docs,
version,
options,
}),
),
);

View file

@ -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,

View file

@ -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}
/>

View file

@ -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,
})}>
<ul className="menu__list">
<DocSidebarItems
items={sidebar}
collapsible={sidebarCollapsible}
activePath={path}
/>
<DocSidebarItems items={sidebar} activePath={path} />
</ul>
</nav>
{hideableSidebar && <HideableSidebarButton onClick={onCollapse} />}
@ -100,14 +90,12 @@ function DocSidebarDesktop({
const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent<Props> = ({
toggleSidebar,
sidebar,
sidebarCollapsible,
path,
}) => {
return (
<ul className="menu__list">
<DocSidebarItems
items={sidebar}
collapsible={sidebarCollapsible}
activePath={path}
onItemClick={() => toggleSidebar()}
/>

View file

@ -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}
/>
</Collapsible>
@ -158,7 +156,6 @@ function DocSidebarItemLink({
item,
onItemClick,
activePath,
collapsible: _collapsible,
...props
}: Props & {item: PropSidebarItemLink}) {
const {href, label} = item;

View file

@ -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;
};

View file

@ -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;

View file

@ -46,7 +46,11 @@ type ContextValue = ReturnType<typeof useContextValue>;
const Context = createContext<ContextValue | null>(null);
export function MobileSecondaryMenuProvider({children}: {children: ReactNode}) {
export function MobileSecondaryMenuProvider({
children,
}: {
children: ReactNode;
}): JSX.Element {
return (
<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',
label: 'Index',
},
{
type: 'category',
label: 'section',
collapsible: false,
items: ['index', 'more-test'],
},
{
type: 'category',
label: 'Huge sidebar category',

View file

@ -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...)

View file

@ -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
```

View file

@ -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`

View file

@ -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',