mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-29 22:47:52 +02:00
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:
parent
b38c35a36d
commit
24156efcfb
27 changed files with 487 additions and 109 deletions
|
@ -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,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue