feat(theme-classic, plugin-docs): sidebar item level-specific className + allow customization (#5642)

* Initial work

* Complete function

* Avoid duplication

* More dedupe

* Make everything constants

* Change casing & docs
This commit is contained in:
Joshua Chen 2021-10-07 22:59:02 +08:00 committed by GitHub
parent f6ec757aa0
commit eaacb0e98a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 79 additions and 11 deletions

View file

@ -29,6 +29,7 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
slug: Joi.string(),
sidebar_label: Joi.string(),
sidebar_position: Joi.number(),
sidebar_class_name: Joi.string(),
tags: FrontMatterTagsSchema,
pagination_label: Joi.string(),
custom_edit_url: URISchema.allow('', null),

View file

@ -30,6 +30,7 @@ declare module '@docusaurus/plugin-content-docs-types' {
};
type PropsSidebarItemBase = {
className?: string;
customProps?: Record<string, unknown>;
};

View file

@ -47,6 +47,7 @@ Available document ids are:
type: 'link',
label: sidebarLabel || item.label || title,
href: permalink,
className: item.className,
customProps: item.customProps,
};
};

View file

@ -30,6 +30,7 @@ export type CategoryMetadatasFile = {
position?: number;
collapsed?: boolean;
collapsible?: boolean;
className?: string;
// 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/
@ -44,6 +45,7 @@ const CategoryMetadatasFileSchema = Joi.object<CategoryMetadatasFile>({
position: Joi.number(),
collapsed: Joi.boolean(),
collapsible: Joi.boolean(),
className: Joi.string(),
});
// TODO I now believe we should read all the category metadata files ahead of time: we may need this metadata to customize docs metadata
@ -177,6 +179,9 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
...(doc.frontMatter.sidebar_label && {
label: doc.frontMatter.sidebar_label,
}),
...(doc.frontMatter.sidebar_class_name && {
className: doc.frontMatter.sidebar_class_name,
}),
...(typeof doc.sidebarPosition !== 'undefined' && {
position: doc.sidebarPosition,
}),
@ -205,6 +210,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
const collapsible =
categoryMetadatas?.collapsible ?? options.sidebarCollapsible;
const collapsed = categoryMetadatas?.collapsed ?? options.sidebarCollapsed;
const className = categoryMetadatas?.className;
return {
type: 'category',
@ -213,6 +219,7 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
collapsed,
collapsible,
...(typeof position !== 'undefined' && {position}),
...(typeof className !== 'undefined' && {className}),
};
}
@ -311,5 +318,5 @@ function sortSidebarItems(
['asc'],
);
return sortedSidebarItems.map(({position: _removed, ...item}) => item);
return sortedSidebarItems.map(({position, ...item}) => item);
}

View file

@ -40,6 +40,7 @@ type SidebarItemCategoryJSON = SidebarItemBase & {
items: SidebarItemJSON[];
collapsed?: boolean;
collapsible?: boolean;
className?: string;
};
type SidebarItemAutogeneratedJSON = SidebarItemBase & {
@ -100,8 +101,7 @@ function assertItem<K extends string>(
keys: K[],
): asserts item is Record<K, unknown> {
const unknownKeys = Object.keys(item).filter(
// @ts-expect-error: key is always string
(key) => !keys.includes(key as string) && key !== 'type',
(key) => !keys.includes(key as K) && key !== 'type',
);
if (unknownKeys.length) {
@ -121,6 +121,7 @@ function assertIsCategory(
'label',
'collapsed',
'collapsible',
'className',
'customProps',
]);
if (typeof item.label !== 'string') {
@ -150,6 +151,14 @@ function assertIsCategory(
`Error loading ${JSON.stringify(item)}: "collapsible" must be a boolean.`,
);
}
if (
typeof item.className !== 'undefined' &&
typeof item.className !== 'string'
) {
throw new Error(
`Error loading ${JSON.stringify(item)}: "className" must be a string.`,
);
}
}
function assertIsAutogenerated(
@ -173,24 +182,33 @@ function assertIsAutogenerated(
function assertIsDoc(
item: Record<string, unknown>,
): asserts item is SidebarItemDoc {
assertItem(item, ['id', 'label', 'customProps']);
assertItem(item, ['id', 'label', 'className', 'customProps']);
if (typeof item.id !== 'string') {
throw new Error(
`Error loading ${JSON.stringify(item)}: "id" must be a string.`,
);
}
if (item.label && typeof item.label !== 'string') {
if (typeof item.label !== 'undefined' && typeof item.label !== 'string') {
throw new Error(
`Error loading ${JSON.stringify(item)}: "label" must be a string.`,
);
}
if (
typeof item.className !== 'undefined' &&
typeof item.className !== 'string'
) {
throw new Error(
`Error loading ${JSON.stringify(item)}: "className" must be a string.`,
);
}
}
function assertIsLink(
item: Record<string, unknown>,
): asserts item is SidebarItemLink {
assertItem(item, ['href', 'label', 'customProps']);
assertItem(item, ['href', 'label', 'className', 'customProps']);
if (typeof item.href !== 'string') {
throw new Error(
`Error loading ${JSON.stringify(item)}: "href" must be a string.`,
@ -201,6 +219,14 @@ function assertIsLink(
`Error loading ${JSON.stringify(item)}: "label" must be a string.`,
);
}
if (
typeof item.className !== 'undefined' &&
typeof item.className !== 'string'
) {
throw new Error(
`Error loading ${JSON.stringify(item)}: "className" must be a string.`,
);
}
}
/**

View file

@ -105,6 +105,7 @@ export type PluginOptions = MetadataOptions &
};
export type SidebarItemBase = {
className?: string;
customProps?: Record<string, unknown>;
};
@ -217,6 +218,7 @@ export type DocFrontMatter = {
slug?: string;
sidebar_label?: string;
sidebar_position?: number;
sidebar_class_name?: string;
pagination_label?: string;
custom_edit_url?: string | null;
parse_number_prefixes?: boolean;