This commit is contained in:
2024-03-22 03:47:51 +05:30
parent 8bcf3d211e
commit 89819f6fe2
28440 changed files with 3211033 additions and 2 deletions

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { SidebarItemsGenerator } from './types';
export declare const CategoryMetadataFilenameBase = "_category_";
export declare const CategoryMetadataFilenamePattern = "_category_.{json,yml,yaml}";
export declare const DefaultSidebarItemsGenerator: SidebarItemsGenerator;

View File

@@ -0,0 +1,210 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultSidebarItemsGenerator = exports.CategoryMetadataFilenamePattern = exports.CategoryMetadataFilenameBase = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const docs_1 = require("../docs");
const BreadcrumbSeparator = '/';
// Just an alias to the make code more explicit
function getLocalDocId(docId) {
return lodash_1.default.last(docId.split('/'));
}
exports.CategoryMetadataFilenameBase = '_category_';
exports.CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
// Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
const DefaultSidebarItemsGenerator = ({ numberPrefixParser, isCategoryIndex, docs: allDocs, item: { dirName: autogenDir }, categoriesMetadata, }) => {
const docsById = (0, docs_1.createDocsByIdIndex)(allDocs);
const findDoc = (docId) => docsById[docId];
const getDoc = (docId) => {
const doc = findDoc(docId);
if (!doc) {
throw new Error(`Can't find any doc with ID ${docId}.
Available doc IDs:
- ${Object.keys(docsById).join('\n- ')}`);
}
return doc;
};
/**
* Step 1. Extract the docs that are in the autogen dir.
*/
function getAutogenDocs() {
function isInAutogeneratedDir(doc) {
return (
// Doc at the root of the autogenerated sidebar dir
doc.sourceDirName === autogenDir ||
// Autogen dir is . and doc is in subfolder
autogenDir === '.' ||
// Autogen dir is not . and doc is in subfolder
// "api/myDoc" startsWith "api/" (note "api2/myDoc" is not included)
doc.sourceDirName.startsWith((0, utils_1.addTrailingSlash)(autogenDir)));
}
const docs = allDocs.filter(isInAutogeneratedDir);
if (docs.length === 0) {
logger_1.default.warn `No docs found in path=${autogenDir}: can't auto-generate a sidebar.`;
}
return docs;
}
/**
* Step 2. Turn the linear file list into a tree structure.
*/
function treeify(docs) {
// Get the category breadcrumb of a doc (relative to the dir of the
// autogenerated sidebar item)
// autogenDir=a/b and docDir=a/b/c/d => returns [c, d]
// autogenDir=a/b and docDir=a/b => returns []
// TODO: try to use path.relative()
function getRelativeBreadcrumb(doc) {
return autogenDir === doc.sourceDirName
? []
: doc.sourceDirName
.replace((0, utils_1.addTrailingSlash)(autogenDir), '')
.split(BreadcrumbSeparator);
}
const treeRoot = {};
docs.forEach((doc) => {
const breadcrumb = getRelativeBreadcrumb(doc);
// We walk down the file's path to generate the fs structure
let currentDir = treeRoot;
breadcrumb.forEach((dir) => {
if (typeof currentDir[dir] === 'undefined') {
currentDir[dir] = {}; // Create new folder.
}
currentDir = currentDir[dir]; // Go into the subdirectory.
});
// We've walked through the path. Register the file in this directory.
currentDir[path_1.default.basename(doc.source)] = doc.id;
});
return treeRoot;
}
/**
* Step 3. Recursively transform the tree-like structure to sidebar items.
* (From a record to an array of items, akin to normalizing shorthand)
*/
function generateSidebar(fsModel) {
function createDocItem(id, fullPath, fileName) {
const { sidebarPosition: position, frontMatter: { sidebar_label: label, sidebar_class_name: className, sidebar_custom_props: customProps, }, } = getDoc(id);
return {
type: 'doc',
id,
position,
source: fileName,
// We don't want these fields to magically appear in the generated
// sidebar
...(label !== undefined && { label }),
...(className !== undefined && { className }),
...(customProps !== undefined && { customProps }),
};
}
function createCategoryItem(dir, fullPath, folderName) {
const categoryMetadata = categoriesMetadata[path_1.default.posix.join(autogenDir, fullPath)];
const allItems = Object.entries(dir).map(([key, content]) => dirToItem(content, key, `${fullPath}/${key}`));
// Try to match a doc inside the category folder,
// using the "local id" (myDoc) or "qualified id" (dirName/myDoc)
function findDocByLocalId(localId) {
return allItems.find((item) => item.type === 'doc' && getLocalDocId(item.id) === localId);
}
function findConventionalCategoryDocLink() {
return allItems.find((item) => {
if (item.type !== 'doc') {
return false;
}
const doc = getDoc(item.id);
return isCategoryIndex((0, docs_1.toCategoryIndexMatcherParam)(doc));
});
}
// In addition to the ID, this function also retrieves metadata of the
// linked doc that could be used as fallback values for category metadata
function getCategoryLinkedDocMetadata() {
const link = categoryMetadata?.link;
if (link !== undefined && link?.type !== 'doc') {
// If a link is explicitly specified, we won't apply conventions
return undefined;
}
const id = link
? findDocByLocalId(link.id)?.id ?? getDoc(link.id).id
: findConventionalCategoryDocLink()?.id;
if (!id) {
return undefined;
}
const doc = getDoc(id);
return {
id,
position: doc.sidebarPosition,
label: doc.frontMatter.sidebar_label ?? doc.title,
customProps: doc.frontMatter.sidebar_custom_props,
className: doc.frontMatter.sidebar_class_name,
};
}
const categoryLinkedDoc = getCategoryLinkedDocMetadata();
const link = categoryLinkedDoc
? {
type: 'doc',
id: categoryLinkedDoc.id, // We "remap" a potentially "local id" to a "qualified id"
}
: categoryMetadata?.link;
// If a doc is linked, remove it from the category subItems
const items = allItems.filter((item) => !(item.type === 'doc' && item.id === categoryLinkedDoc?.id));
const className = categoryMetadata?.className ?? categoryLinkedDoc?.className;
const customProps = categoryMetadata?.customProps ?? categoryLinkedDoc?.customProps;
const { filename, numberPrefix } = numberPrefixParser(folderName);
return {
type: 'category',
label: categoryMetadata?.label ?? categoryLinkedDoc?.label ?? filename,
collapsible: categoryMetadata?.collapsible,
collapsed: categoryMetadata?.collapsed,
position: categoryMetadata?.position ??
categoryLinkedDoc?.position ??
numberPrefix,
source: folderName,
...(customProps !== undefined && { customProps }),
...(className !== undefined && { className }),
items,
...(link && { link }),
};
}
function dirToItem(dir, // The directory item to be transformed.
itemKey, // File/folder name; for categories, it's used to generate the next `relativePath`.
fullPath) {
return typeof dir === 'object'
? createCategoryItem(dir, fullPath, itemKey)
: createDocItem(dir, fullPath, itemKey);
}
return Object.entries(fsModel).map(([key, content]) => dirToItem(content, key, key));
}
/**
* Step 4. Recursively sort the categories/docs + remove the "position"
* attribute from final output. Note: the "position" is only used to sort
* "inside" a sidebar slice. It is not used to sort across multiple
* consecutive sidebar slices (i.e. a whole category composed of multiple
* autogenerated items)
*/
function sortItems(sidebarItems) {
const processedSidebarItems = sidebarItems.map((item) => {
if (item.type === 'category') {
return { ...item, items: sortItems(item.items) };
}
return item;
});
const sortedSidebarItems = lodash_1.default.sortBy(processedSidebarItems, [
'position',
'source',
]);
return sortedSidebarItems.map(({ position, source, ...item }) => item);
}
// TODO: the whole code is designed for pipeline operator
const docs = getAutogenDocs();
const fsModel = treeify(docs);
const sidebarWithPosition = generateSidebar(fsModel);
const sortedSidebar = sortItems(sidebarWithPosition);
return sortedSidebar;
};
exports.DefaultSidebarItemsGenerator = DefaultSidebarItemsGenerator;

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { PluginOptions } from '@docusaurus/plugin-content-docs';
import type { SidebarsConfig, Sidebars, SidebarProcessorParams } from './types';
export declare const DefaultSidebars: SidebarsConfig;
export declare const DisabledSidebars: SidebarsConfig;
export declare function resolveSidebarPathOption(siteDir: string, sidebarPathOption: PluginOptions['sidebarPath']): PluginOptions['sidebarPath'];
export declare function loadSidebarsFile(sidebarFilePath: string | false | undefined): Promise<SidebarsConfig>;
export declare function loadSidebars(sidebarFilePath: string | false | undefined, options: SidebarProcessorParams): Promise<Sidebars>;

View File

@@ -0,0 +1,98 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadSidebars = exports.loadSidebarsFile = exports.resolveSidebarPathOption = exports.DisabledSidebars = exports.DefaultSidebars = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
const combine_promises_1 = tslib_1.__importDefault(require("combine-promises"));
const validation_1 = require("./validation");
const normalization_1 = require("./normalization");
const processor_1 = require("./processor");
const postProcessor_1 = require("./postProcessor");
exports.DefaultSidebars = {
defaultSidebar: [
{
type: 'autogenerated',
dirName: '.',
},
],
};
exports.DisabledSidebars = {};
// If a path is provided, make it absolute
function resolveSidebarPathOption(siteDir, sidebarPathOption) {
return sidebarPathOption
? path_1.default.resolve(siteDir, sidebarPathOption)
: sidebarPathOption;
}
exports.resolveSidebarPathOption = resolveSidebarPathOption;
async function readCategoriesMetadata(contentPath) {
const categoryFiles = await (0, utils_1.Globby)('**/_category_.{json,yml,yaml}', {
cwd: contentPath,
});
const categoryToFile = lodash_1.default.groupBy(categoryFiles, path_1.default.dirname);
return (0, combine_promises_1.default)(lodash_1.default.mapValues(categoryToFile, async (files, folder) => {
const filePath = files[0];
if (files.length > 1) {
logger_1.default.warn `There are more than one category metadata files for path=${folder}: ${files.join(', ')}. The behavior is undetermined.`;
}
const content = await fs_extra_1.default.readFile(path_1.default.join(contentPath, filePath), 'utf-8');
try {
return (0, validation_1.validateCategoryMetadataFile)(js_yaml_1.default.load(content));
}
catch (err) {
logger_1.default.error `The docs sidebar category metadata file path=${filePath} looks invalid!`;
throw err;
}
}));
}
async function loadSidebarsFileUnsafe(sidebarFilePath) {
// false => no sidebars
if (sidebarFilePath === false) {
return exports.DisabledSidebars;
}
// undefined => defaults to autogenerated sidebars
if (typeof sidebarFilePath === 'undefined') {
return exports.DefaultSidebars;
}
// Non-existent sidebars file: no sidebars
// Note: this edge case can happen on versioned docs, not current version
// We avoid creating empty versioned sidebars file with the CLI
if (!(await fs_extra_1.default.pathExists(sidebarFilePath))) {
return exports.DisabledSidebars;
}
// We don't want sidebars to be cached because of hot reloading.
const module = await (0, utils_1.loadFreshModule)(sidebarFilePath);
// TODO unsafe, need to refactor and improve validation
return module;
}
async function loadSidebarsFile(sidebarFilePath) {
const sidebars = await loadSidebarsFileUnsafe(sidebarFilePath);
// TODO unsafe, need to refactor and improve validation
return sidebars;
}
exports.loadSidebarsFile = loadSidebarsFile;
async function loadSidebars(sidebarFilePath, options) {
try {
const sidebarsConfig = await loadSidebarsFileUnsafe(sidebarFilePath);
const normalizedSidebars = (0, normalization_1.normalizeSidebars)(sidebarsConfig);
(0, validation_1.validateSidebars)(normalizedSidebars);
const categoriesMetadata = await readCategoriesMetadata(options.version.contentPath);
const processedSidebars = await (0, processor_1.processSidebars)(normalizedSidebars, categoriesMetadata, options);
return (0, postProcessor_1.postProcessSidebars)(processedSidebars, options);
}
catch (err) {
logger_1.default.error `Sidebars file at path=${sidebarFilePath} failed to be loaded.`;
throw err;
}
}
exports.loadSidebars = loadSidebars;

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { NormalizedSidebarItem, NormalizedSidebars, SidebarItemConfig, SidebarsConfig } from './types';
/**
* Normalizes recursively item and all its children. Ensures that at the end
* each item will be an object with the corresponding type.
*/
export declare function normalizeItem(item: SidebarItemConfig): NormalizedSidebarItem[];
export declare function normalizeSidebars(sidebars: SidebarsConfig): NormalizedSidebars;

View File

@@ -0,0 +1,59 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeSidebars = exports.normalizeItem = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("./utils");
function normalizeCategoriesShorthand(sidebar) {
return Object.entries(sidebar).map(([label, items]) => ({
type: 'category',
label,
items,
}));
}
/**
* 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) {
if (typeof item === 'string') {
return [{ type: 'doc', id: item }];
}
if ((0, utils_1.isCategoriesShorthand)(item)) {
// This will never throw anyways
return normalizeSidebar(item, 'sidebar items slice');
}
if ((item.type === 'doc' || item.type === 'ref') &&
typeof item.label === 'string') {
return [{ ...item, translatable: true }];
}
if (item.type === 'category') {
const normalizedCategory = {
...item,
items: normalizeSidebar(item.items, logger_1.default.interpolate `code=${'items'} of the category name=${item.label}`),
};
return [normalizedCategory];
}
return [item];
}
exports.normalizeItem = normalizeItem;
function normalizeSidebar(sidebar, place) {
if (!Array.isArray(sidebar) && !(0, utils_1.isCategoriesShorthand)(sidebar)) {
throw new Error(logger_1.default.interpolate `Invalid sidebar items collection code=${JSON.stringify(sidebar)} in ${place}: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a code=${'type'} property). See url=${'https://docusaurus.io/docs/sidebar/items'} for all valid syntaxes.`);
}
const normalizedSidebar = Array.isArray(sidebar)
? sidebar
: normalizeCategoriesShorthand(sidebar);
return normalizedSidebar.flatMap((subItem) => normalizeItem(subItem));
}
function normalizeSidebars(sidebars) {
return lodash_1.default.mapValues(sidebars, (sidebar, id) => normalizeSidebar(sidebar, logger_1.default.interpolate `sidebar name=${id}`));
}
exports.normalizeSidebars = normalizeSidebars;

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { Sidebars, SidebarProcessorParams, ProcessedSidebars } from './types';
export type SidebarPostProcessorParams = SidebarProcessorParams & {
draftIds: Set<string>;
};
export declare function postProcessSidebars(sidebars: ProcessedSidebars, params: SidebarProcessorParams): Sidebars;

View File

@@ -0,0 +1,80 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.postProcessSidebars = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
function normalizeCategoryLink(category, params) {
if (category.link?.type === 'doc' && params.draftIds.has(category.link.id)) {
return undefined;
}
if (category.link?.type === 'generated-index') {
// Default slug logic can be improved
const getDefaultSlug = () => `/category/${params.categoryLabelSlugger.slug(category.label)}`;
const slug = category.link.slug ?? getDefaultSlug();
const permalink = (0, utils_1.normalizeUrl)([params.version.path, slug]);
return {
...category.link,
slug,
permalink,
};
}
return category.link;
}
function postProcessSidebarItem(item, params) {
if (item.type === 'category') {
// Fail-fast if there's actually no subitems, no because all subitems are
// drafts. This is likely a configuration mistake.
if (item.items.length === 0 && !item.link) {
throw new Error(`Sidebar category ${item.label} has neither any subitem nor a link. This makes this item not able to link to anything.`);
}
const category = {
...item,
collapsed: item.collapsed ?? params.sidebarOptions.sidebarCollapsed,
collapsible: item.collapsible ?? params.sidebarOptions.sidebarCollapsible,
link: normalizeCategoryLink(item, params),
items: item.items
.map((subItem) => postProcessSidebarItem(subItem, params))
.filter((v) => Boolean(v)),
};
// If the current category doesn't have subitems, we render a normal link
// instead.
if (category.items.length === 0) {
// Doesn't make sense to render an empty generated index page, so we
// filter the entire category out as well.
if (!category.link ||
category.link.type === 'generated-index' ||
params.draftIds.has(category.link.id)) {
return null;
}
return {
type: 'doc',
label: category.label,
id: category.link.id,
};
}
// A non-collapsible category can't be collapsed!
if (!category.collapsible) {
category.collapsed = false;
}
return category;
}
if ((item.type === 'doc' || item.type === 'ref') &&
params.draftIds.has(item.id)) {
return null;
}
return item;
}
function postProcessSidebars(sidebars, params) {
const draftIds = new Set(params.drafts.map((d) => d.id));
return lodash_1.default.mapValues(sidebars, (sidebar) => sidebar
.map((item) => postProcessSidebarItem(item, { ...params, draftIds }))
.filter((v) => Boolean(v)));
}
exports.postProcessSidebars = postProcessSidebars;

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { NormalizedSidebars, ProcessedSidebars, SidebarProcessorParams, CategoryMetadataFile } from './types';
export declare function processSidebars(unprocessedSidebars: NormalizedSidebars, categoriesMetadata: {
[filePath: string]: CategoryMetadataFile;
}, params: SidebarProcessorParams): Promise<ProcessedSidebars>;

View File

@@ -0,0 +1,78 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.processSidebars = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const combine_promises_1 = tslib_1.__importDefault(require("combine-promises"));
const generator_1 = require("./generator");
const validation_1 = require("./validation");
const docs_1 = require("../docs");
function toSidebarItemsGeneratorDoc(doc) {
return lodash_1.default.pick(doc, [
'id',
'title',
'frontMatter',
'source',
'sourceDirName',
'sidebarPosition',
]);
}
function toSidebarItemsGeneratorVersion(version) {
return lodash_1.default.pick(version, ['versionName', 'contentPath']);
}
// Handle the generation of autogenerated sidebar items and other
// post-processing checks
async function processSidebar(unprocessedSidebar, categoriesMetadata, params) {
const { sidebarItemsGenerator, numberPrefixParser, docs, version } = params;
// Just a minor lazy transformation optimization
const getSidebarItemsGeneratorDocsAndVersion = lodash_1.default.memoize(() => ({
docs: docs.map(toSidebarItemsGeneratorDoc),
version: toSidebarItemsGeneratorVersion(version),
}));
async function processAutoGeneratedItem(item) {
const generatedItems = await sidebarItemsGenerator({
item,
numberPrefixParser,
defaultSidebarItemsGenerator: generator_1.DefaultSidebarItemsGenerator,
isCategoryIndex: docs_1.isCategoryIndex,
...getSidebarItemsGeneratorDocsAndVersion(),
categoriesMetadata,
});
// Process again... weird but sidebar item generated might generate some
// auto-generated items?
// TODO repeatedly process & unwrap autogenerated items until there are no
// more autogenerated items, or when loop count (e.g. 10) is reached
return processItems(generatedItems);
}
async function processItem(item) {
if (item.type === 'category') {
return [
{
...item,
items: (await Promise.all(item.items.map(processItem))).flat(),
},
];
}
if (item.type === 'autogenerated') {
return processAutoGeneratedItem(item);
}
return [item];
}
async function processItems(items) {
return (await Promise.all(items.map(processItem))).flat();
}
const processedSidebar = await processItems(unprocessedSidebar);
return processedSidebar;
}
async function processSidebars(unprocessedSidebars, categoriesMetadata, params) {
const processedSidebars = await (0, combine_promises_1.default)(lodash_1.default.mapValues(unprocessedSidebars, (unprocessedSidebar) => processSidebar(unprocessedSidebar, categoriesMetadata, params)));
(0, validation_1.validateSidebars)(processedSidebars);
return processedSidebars;
}
exports.processSidebars = processSidebars;

View File

@@ -0,0 +1,188 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { Optional, Required } from 'utility-types';
import type { NumberPrefixParser, SidebarOptions, CategoryIndexMatcher, DocMetadataBase, VersionMetadata } from '@docusaurus/plugin-content-docs';
import type { Slugger } from '@docusaurus/utils';
type Expand<T extends {
[x: string]: unknown;
}> = {
[P in keyof T]: T[P];
};
export type SidebarItemBase = {
className?: string;
customProps?: {
[key: string]: unknown;
};
};
export type SidebarItemDoc = SidebarItemBase & {
type: 'doc' | 'ref';
label?: string;
id: string;
/**
* This is an internal marker. Items with labels defined in the config needs
* to be translated with JSON
*/
translatable?: true;
};
export type SidebarItemHtml = SidebarItemBase & {
type: 'html';
value: string;
defaultStyle?: boolean;
};
export type SidebarItemLink = SidebarItemBase & {
type: 'link';
href: string;
label: string;
autoAddBaseUrl?: boolean;
description?: string;
};
export type SidebarItemAutogenerated = SidebarItemBase & {
type: 'autogenerated';
dirName: string;
};
type SidebarItemCategoryBase = SidebarItemBase & {
type: 'category';
label: string;
collapsed: boolean;
collapsible: boolean;
description?: string;
};
export type SidebarItemCategoryLinkDoc = {
type: 'doc';
id: string;
};
export type SidebarItemCategoryLinkGeneratedIndexConfig = {
type: 'generated-index';
slug?: string;
title?: string;
description?: string;
image?: string;
keywords?: string | readonly string[];
};
export type SidebarItemCategoryLinkGeneratedIndex = {
type: 'generated-index';
slug: string;
permalink: string;
title?: string;
description?: string;
image?: string;
keywords?: string | readonly string[];
};
export type SidebarItemCategoryLinkConfig = SidebarItemCategoryLinkDoc | SidebarItemCategoryLinkGeneratedIndexConfig;
export type SidebarItemCategoryLink = SidebarItemCategoryLinkDoc | SidebarItemCategoryLinkGeneratedIndex;
export type SidebarItemCategoryConfig = Expand<Optional<SidebarItemCategoryBase, 'collapsed' | 'collapsible'> & {
items: SidebarCategoriesShorthand | SidebarItemConfig[];
link?: SidebarItemCategoryLinkConfig;
}>;
export type SidebarCategoriesShorthand = {
[sidebarCategory: string]: SidebarCategoriesShorthand | SidebarItemConfig[];
};
export type SidebarItemConfig = Omit<SidebarItemDoc, 'translatable'> | SidebarItemHtml | SidebarItemLink | SidebarItemAutogenerated | SidebarItemCategoryConfig | string | SidebarCategoriesShorthand;
export type SidebarConfig = SidebarCategoriesShorthand | SidebarItemConfig[];
export type SidebarsConfig = {
[sidebarId: string]: SidebarConfig;
};
export type NormalizedSidebarItemCategory = Expand<Optional<SidebarItemCategoryBase, 'collapsed' | 'collapsible'> & {
items: NormalizedSidebarItem[];
link?: SidebarItemCategoryLinkConfig;
}>;
export type NormalizedSidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | NormalizedSidebarItemCategory | SidebarItemAutogenerated;
export type NormalizedSidebar = NormalizedSidebarItem[];
export type NormalizedSidebars = {
[sidebarId: string]: NormalizedSidebar;
};
export type ProcessedSidebarItemCategory = Expand<Optional<SidebarItemCategoryBase, 'collapsed' | 'collapsible'> & {
items: ProcessedSidebarItem[];
link?: SidebarItemCategoryLinkConfig;
}>;
export type ProcessedSidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | ProcessedSidebarItemCategory;
export type ProcessedSidebar = ProcessedSidebarItem[];
export type ProcessedSidebars = {
[sidebarId: string]: ProcessedSidebar;
};
export type SidebarItemCategory = Expand<SidebarItemCategoryBase & {
items: SidebarItem[];
link?: SidebarItemCategoryLink;
}>;
export type SidebarItemCategoryWithLink = Required<SidebarItemCategory, 'link'>;
export type SidebarItemCategoryWithGeneratedIndex = SidebarItemCategoryWithLink & {
link: SidebarItemCategoryLinkGeneratedIndex;
};
export type SidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | SidebarItemCategory;
export type SidebarNavigationItem = SidebarItemDoc | SidebarItemCategoryWithLink;
export type Sidebar = SidebarItem[];
export type SidebarItemType = SidebarItem['type'];
export type Sidebars = {
[sidebarId: string]: Sidebar;
};
export type PropSidebarItemCategory = Expand<SidebarItemCategoryBase & {
items: PropSidebarItem[];
href?: string;
linkUnlisted?: boolean;
}>;
export type PropSidebarItemLink = SidebarItemLink & {
docId?: string;
unlisted?: boolean;
};
export type PropSidebarItemHtml = SidebarItemHtml;
export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory | PropSidebarItemHtml;
export type PropSidebar = PropSidebarItem[];
export type PropSidebars = {
[sidebarId: string]: PropSidebar;
};
export type PropSidebarBreadcrumbsItem = PropSidebarItemLink | PropSidebarItemCategory;
export type CategoryMetadataFile = {
label?: string;
position?: number;
collapsed?: boolean;
collapsible?: boolean;
className?: string;
link?: SidebarItemCategoryLinkConfig | null;
customProps?: {
[key: string]: unknown;
};
};
export type SidebarItemsGeneratorDoc = Pick<DocMetadataBase, 'id' | 'title' | 'frontMatter' | 'source' | 'sourceDirName' | 'sidebarPosition'>;
export type SidebarItemsGeneratorVersion = Pick<VersionMetadata, 'versionName' | 'contentPath'>;
export type SidebarItemsGeneratorArgs = {
/** The sidebar item with type "autogenerated" to be transformed. */
item: SidebarItemAutogenerated;
/** Useful metadata for the version this sidebar belongs to. */
version: SidebarItemsGeneratorVersion;
/** All the docs of that version (unfiltered). */
docs: SidebarItemsGeneratorDoc[];
/** Number prefix parser configured for this plugin. */
numberPrefixParser: NumberPrefixParser;
/** The default category index matcher which you can override. */
isCategoryIndex: CategoryIndexMatcher;
/**
* Key is the path relative to the doc content directory, value is the
* category metadata file's content.
*/
categoriesMetadata: {
[filePath: string]: CategoryMetadataFile;
};
};
export type SidebarItemsGenerator = (generatorArgs: SidebarItemsGeneratorArgs) => NormalizedSidebar | Promise<NormalizedSidebar>;
export type SidebarItemsGeneratorOption = (generatorArgs: {
/**
* Useful to re-use/enhance the default sidebar generation logic from
* Docusaurus.
* @see https://github.com/facebook/docusaurus/issues/4640#issuecomment-822292320
*/
defaultSidebarItemsGenerator: SidebarItemsGenerator;
} & SidebarItemsGeneratorArgs) => NormalizedSidebar | Promise<NormalizedSidebar>;
export type SidebarProcessorParams = {
sidebarItemsGenerator: SidebarItemsGeneratorOption;
numberPrefixParser: NumberPrefixParser;
docs: DocMetadataBase[];
drafts: DocMetadataBase[];
version: VersionMetadata;
categoryLabelSlugger: Slugger;
sidebarOptions: SidebarOptions;
};
export {};

View File

@@ -0,0 +1,8 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { Sidebars, Sidebar, SidebarItem, SidebarItemCategory, SidebarItemLink, SidebarItemDoc, SidebarCategoriesShorthand, SidebarItemConfig, SidebarItemCategoryWithGeneratedIndex, SidebarNavigationItem } from './types';
import type { DocMetadataBase, PropNavigationLink, VersionMetadata } from '@docusaurus/plugin-content-docs';
export declare function isCategoriesShorthand(item: SidebarItemConfig): item is SidebarCategoriesShorthand;
export declare function transformSidebarItems(sidebar: Sidebar, updateFn: (item: SidebarItem) => SidebarItem): Sidebar;
export declare function collectSidebarDocItems(sidebar: Sidebar): SidebarItemDoc[];
export declare function collectSidebarCategories(sidebar: Sidebar): SidebarItemCategory[];
export declare function collectSidebarLinks(sidebar: Sidebar): SidebarItemLink[];
export declare function collectSidebarRefs(sidebar: Sidebar): SidebarItemDoc[];
export declare function collectSidebarDocIds(sidebar: Sidebar): string[];
export declare function collectSidebarNavigation(sidebar: Sidebar): SidebarNavigationItem[];
export declare function collectSidebarsDocIds(sidebars: Sidebars): {
[sidebarId: string]: string[];
};
export declare function collectSidebarsNavigations(sidebars: Sidebars): {
[sidebarId: string]: SidebarNavigationItem[];
};
export type SidebarNavigation = {
sidebarName: string | undefined;
previous: SidebarNavigationItem | undefined;
next: SidebarNavigationItem | undefined;
};
export type SidebarsUtils = {
sidebars: Sidebars;
getFirstDocIdOfFirstSidebar: () => string | undefined;
getSidebarNameByDocId: (docId: string) => string | undefined;
getDocNavigation: (params: {
docId: string;
displayedSidebar: string | null | undefined;
unlistedIds: Set<string>;
}) => SidebarNavigation;
getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
getCategoryGeneratedIndexNavigation: (categoryGeneratedIndexPermalink: string) => SidebarNavigation;
/**
* This function may return undefined. This is usually a user mistake, because
* it means this sidebar will never be displayed; however, we can still use
* `displayed_sidebar` to make it displayed. Pretty weird but valid use-case
*/
getFirstLink: (sidebarId: string) => {
type: 'doc';
id: string;
label: string;
} | {
type: 'generated-index';
permalink: string;
label: string;
} | undefined;
checkLegacyVersionedSidebarNames: ({ versionMetadata, }: {
sidebarFilePath: string;
versionMetadata: VersionMetadata;
}) => void;
checkSidebarsDocIds: ({ allDocIds, sidebarFilePath, versionMetadata, }: {
allDocIds: string[];
sidebarFilePath: string;
versionMetadata: VersionMetadata;
}) => void;
};
export declare function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils;
export declare function toDocNavigationLink(doc: DocMetadataBase): PropNavigationLink;
export declare function toNavigationLink(navigationItem: SidebarNavigationItem | undefined, docsById: {
[docId: string]: DocMetadataBase;
}): PropNavigationLink | undefined;

View File

@@ -0,0 +1,323 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.toNavigationLink = exports.toDocNavigationLink = exports.createSidebarsUtils = exports.collectSidebarsNavigations = exports.collectSidebarsDocIds = exports.collectSidebarNavigation = exports.collectSidebarDocIds = exports.collectSidebarRefs = exports.collectSidebarLinks = exports.collectSidebarCategories = exports.collectSidebarDocItems = exports.transformSidebarItems = exports.isCategoriesShorthand = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
function isCategoriesShorthand(item) {
return typeof item === 'object' && !item.type;
}
exports.isCategoriesShorthand = isCategoriesShorthand;
function transformSidebarItems(sidebar, updateFn) {
function transformRecursive(item) {
if (item.type === 'category') {
return updateFn({
...item,
items: item.items.map(transformRecursive),
});
}
return updateFn(item);
}
return sidebar.map(transformRecursive);
}
exports.transformSidebarItems = transformSidebarItems;
/**
* Flatten sidebar items into a single flat array (containing categories/docs on
* the same level). Order matters (useful for next/prev nav), top categories
* appear before their child elements
*/
function flattenSidebarItems(items) {
function flattenRecursive(item) {
return item.type === 'category'
? [item, ...item.items.flatMap(flattenRecursive)]
: [item];
}
return items.flatMap(flattenRecursive);
}
function collectSidebarItemsOfType(type, sidebar) {
return flattenSidebarItems(sidebar).filter((item) => item.type === type);
}
function collectSidebarDocItems(sidebar) {
return collectSidebarItemsOfType('doc', sidebar);
}
exports.collectSidebarDocItems = collectSidebarDocItems;
function collectSidebarCategories(sidebar) {
return collectSidebarItemsOfType('category', sidebar);
}
exports.collectSidebarCategories = collectSidebarCategories;
function collectSidebarLinks(sidebar) {
return collectSidebarItemsOfType('link', sidebar);
}
exports.collectSidebarLinks = collectSidebarLinks;
function collectSidebarRefs(sidebar) {
return collectSidebarItemsOfType('ref', sidebar);
}
exports.collectSidebarRefs = collectSidebarRefs;
// /!\ docId order matters for navigation!
function collectSidebarDocIds(sidebar) {
return flattenSidebarItems(sidebar).flatMap((item) => {
if (item.type === 'category') {
return item.link?.type === 'doc' ? [item.link.id] : [];
}
if (item.type === 'doc') {
return [item.id];
}
return [];
});
}
exports.collectSidebarDocIds = collectSidebarDocIds;
function collectSidebarNavigation(sidebar) {
return flattenSidebarItems(sidebar).flatMap((item) => {
if (item.type === 'category' && item.link) {
return [item];
}
if (item.type === 'doc') {
return [item];
}
return [];
});
}
exports.collectSidebarNavigation = collectSidebarNavigation;
function collectSidebarsDocIds(sidebars) {
return lodash_1.default.mapValues(sidebars, collectSidebarDocIds);
}
exports.collectSidebarsDocIds = collectSidebarsDocIds;
function collectSidebarsNavigations(sidebars) {
return lodash_1.default.mapValues(sidebars, collectSidebarNavigation);
}
exports.collectSidebarsNavigations = collectSidebarsNavigations;
function createSidebarsUtils(sidebars) {
const sidebarNameToDocIds = collectSidebarsDocIds(sidebars);
const sidebarNameToNavigationItems = collectSidebarsNavigations(sidebars);
// Reverse mapping
const docIdToSidebarName = Object.fromEntries(Object.entries(sidebarNameToDocIds).flatMap(([sidebarName, docIds]) => docIds.map((docId) => [docId, sidebarName])));
function getFirstDocIdOfFirstSidebar() {
return Object.values(sidebarNameToDocIds)[0]?.[0];
}
function getSidebarNameByDocId(docId) {
return docIdToSidebarName[docId];
}
function emptySidebarNavigation() {
return {
sidebarName: undefined,
previous: undefined,
next: undefined,
};
}
function getDocNavigation({ docId, displayedSidebar, unlistedIds, }) {
const sidebarName = displayedSidebar === undefined
? getSidebarNameByDocId(docId)
: displayedSidebar;
if (!sidebarName) {
return emptySidebarNavigation();
}
let navigationItems = sidebarNameToNavigationItems[sidebarName];
if (!navigationItems) {
throw new Error(`Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`);
}
// Filter unlisted items from navigation
navigationItems = navigationItems.filter((item) => {
if (item.type === 'doc' && unlistedIds.has(item.id)) {
return false;
}
if (item.type === 'category' &&
item.link.type === 'doc' &&
unlistedIds.has(item.link.id)) {
return false;
}
return true;
});
const currentItemIndex = navigationItems.findIndex((item) => {
if (item.type === 'doc') {
return item.id === docId;
}
if (item.type === 'category' && item.link.type === 'doc') {
return item.link.id === docId;
}
return false;
});
if (currentItemIndex === -1) {
return { sidebarName, next: undefined, previous: undefined };
}
return {
sidebarName,
previous: navigationItems[currentItemIndex - 1],
next: navigationItems[currentItemIndex + 1],
};
}
function getCategoryGeneratedIndexList() {
return Object.values(sidebarNameToNavigationItems)
.flat()
.flatMap((item) => {
if (item.type === 'category' && item.link.type === 'generated-index') {
return [item];
}
return [];
});
}
/**
* We identity the category generated index by its permalink (should be
* unique). More reliable than using object identity
*/
function getCategoryGeneratedIndexNavigation(categoryGeneratedIndexPermalink) {
function isCurrentCategoryGeneratedIndexItem(item) {
return (item.type === 'category' &&
item.link.type === 'generated-index' &&
item.link.permalink === categoryGeneratedIndexPermalink);
}
const sidebarName = Object.entries(sidebarNameToNavigationItems).find(([, navigationItems]) => navigationItems.find(isCurrentCategoryGeneratedIndexItem))[0];
const navigationItems = sidebarNameToNavigationItems[sidebarName];
const currentItemIndex = navigationItems.findIndex(isCurrentCategoryGeneratedIndexItem);
return {
sidebarName,
previous: navigationItems[currentItemIndex - 1],
next: navigationItems[currentItemIndex + 1],
};
}
// TODO remove in Docusaurus v4
function getLegacyVersionedPrefix(versionMetadata) {
return `version-${versionMetadata.versionName}/`;
}
// In early v2, sidebar names used to be versioned
// example: "version-2.0.0-alpha.66/my-sidebar-name"
// In v3 it's not the case anymore and we throw an error to explain
// TODO remove in Docusaurus v4
function checkLegacyVersionedSidebarNames({ versionMetadata, sidebarFilePath, }) {
const illegalPrefix = getLegacyVersionedPrefix(versionMetadata);
const legacySidebarNames = Object.keys(sidebars).filter((sidebarName) => sidebarName.startsWith(illegalPrefix));
if (legacySidebarNames.length > 0) {
throw new Error(`Invalid sidebar file at "${(0, utils_1.toMessageRelativeFilePath)(sidebarFilePath)}".
These legacy versioned sidebar names are not supported anymore in Docusaurus v3:
- ${legacySidebarNames.sort().join('\n- ')}
The sidebar names you should now use are:
- ${legacySidebarNames
.sort()
.map((legacyName) => legacyName.split('/').splice(1).join('/'))
.join('\n- ')}
Please remove the "${illegalPrefix}" prefix from your versioned sidebar file.
This breaking change is documented on Docusaurus v3 release notes: https://docusaurus.io/blog/releases/3.0
`);
}
}
// throw a better error message for Docusaurus v3 breaking change
// TODO this can be removed in Docusaurus v4
function handleLegacyVersionedDocIds({ invalidDocIds, sidebarFilePath, versionMetadata, }) {
const illegalPrefix = getLegacyVersionedPrefix(versionMetadata);
// In older v2.0 alpha/betas, versioned docs had a legacy versioned prefix
// Example: "version-1.4/my-doc-id"
//
const legacyVersionedDocIds = invalidDocIds.filter((docId) => docId.startsWith(illegalPrefix));
if (legacyVersionedDocIds.length > 0) {
throw new Error(`Invalid sidebar file at "${(0, utils_1.toMessageRelativeFilePath)(sidebarFilePath)}".
These legacy versioned document ids are not supported anymore in Docusaurus v3:
- ${legacyVersionedDocIds.sort().join('\n- ')}
The document ids you should now use are:
- ${legacyVersionedDocIds
.sort()
.map((legacyId) => legacyId.split('/').splice(1).join('/'))
.join('\n- ')}
Please remove the "${illegalPrefix}" prefix from your versioned sidebar file.
This breaking change is documented on Docusaurus v3 release notes: https://docusaurus.io/blog/releases/3.0
`);
}
}
function checkSidebarsDocIds({ allDocIds, sidebarFilePath, versionMetadata, }) {
const allSidebarDocIds = Object.values(sidebarNameToDocIds).flat();
const invalidDocIds = lodash_1.default.difference(allSidebarDocIds, allDocIds);
if (invalidDocIds.length > 0) {
handleLegacyVersionedDocIds({
invalidDocIds,
sidebarFilePath,
versionMetadata,
});
throw new Error(`Invalid sidebar file at "${(0, utils_1.toMessageRelativeFilePath)(sidebarFilePath)}".
These sidebar document ids do not exist:
- ${invalidDocIds.sort().join('\n- ')}
Available document ids are:
- ${lodash_1.default.uniq(allDocIds).sort().join('\n- ')}
`);
}
}
function getFirstLink(sidebar) {
for (const item of sidebar) {
if (item.type === 'doc') {
return {
type: 'doc',
id: item.id,
label: item.label ?? item.id,
};
}
else if (item.type === 'category') {
if (item.link?.type === 'doc') {
return {
type: 'doc',
id: item.link.id,
label: item.label,
};
}
else if (item.link?.type === 'generated-index') {
return {
type: 'generated-index',
permalink: item.link.permalink,
label: item.label,
};
}
const firstSubItem = getFirstLink(item.items);
if (firstSubItem) {
return firstSubItem;
}
}
}
return undefined;
}
return {
sidebars,
getFirstDocIdOfFirstSidebar,
getSidebarNameByDocId,
getDocNavigation,
getCategoryGeneratedIndexList,
getCategoryGeneratedIndexNavigation,
checkLegacyVersionedSidebarNames,
checkSidebarsDocIds,
getFirstLink: (id) => getFirstLink(sidebars[id]),
};
}
exports.createSidebarsUtils = createSidebarsUtils;
function toDocNavigationLink(doc) {
const { title, permalink, frontMatter: { pagination_label: paginationLabel, sidebar_label: sidebarLabel, }, } = doc;
return { title: paginationLabel ?? sidebarLabel ?? title, permalink };
}
exports.toDocNavigationLink = toDocNavigationLink;
function toNavigationLink(navigationItem, docsById) {
function getDocById(docId) {
const doc = docsById[docId];
if (!doc) {
throw new Error(`Can't create navigation link: no doc found with id=${docId}`);
}
return doc;
}
if (!navigationItem) {
return undefined;
}
if (navigationItem.type === 'category') {
return navigationItem.link.type === 'doc'
? toDocNavigationLink(getDocById(navigationItem.link.id))
: {
title: navigationItem.label,
permalink: navigationItem.link.permalink,
};
}
return toDocNavigationLink(getDocById(navigationItem.id));
}
exports.toNavigationLink = toNavigationLink;

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { NormalizedSidebars, CategoryMetadataFile } from './types';
export declare function validateSidebars(sidebars: {
[sidebarId: string]: unknown;
}): asserts sidebars is NormalizedSidebars;
export declare function validateCategoryMetadataFile(unsafeContent: unknown): CategoryMetadataFile;

View File

@@ -0,0 +1,150 @@
"use strict";
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateCategoryMetadataFile = exports.validateSidebars = void 0;
const utils_validation_1 = require("@docusaurus/utils-validation");
// NOTE: we don't add any default values during validation on purpose!
// Config types are exposed to users for typechecking and we use the same type
// in normalization
const sidebarItemBaseSchema = utils_validation_1.Joi.object({
className: utils_validation_1.Joi.string(),
customProps: utils_validation_1.Joi.object().unknown(),
});
const sidebarItemAutogeneratedSchema = sidebarItemBaseSchema.append({
type: 'autogenerated',
dirName: utils_validation_1.Joi.string()
.required()
.pattern(/^[^/](?:.*[^/])?$/)
.message('"dirName" must be a dir path relative to the docs folder root, and should not start or end with slash'),
});
const sidebarItemDocSchema = sidebarItemBaseSchema.append({
type: utils_validation_1.Joi.string().valid('doc', 'ref').required(),
id: utils_validation_1.Joi.string().required(),
label: utils_validation_1.Joi.string(),
translatable: utils_validation_1.Joi.boolean(),
});
const sidebarItemHtmlSchema = sidebarItemBaseSchema.append({
type: 'html',
value: utils_validation_1.Joi.string().required(),
defaultStyle: utils_validation_1.Joi.boolean(),
});
const sidebarItemLinkSchema = sidebarItemBaseSchema.append({
type: 'link',
href: utils_validation_1.URISchema.required(),
autoAddBaseUrl: utils_validation_1.Joi.boolean(),
label: utils_validation_1.Joi.string()
.required()
.messages({ 'any.unknown': '"label" must be a string' }),
description: utils_validation_1.Joi.string().optional().messages({
'any.unknown': '"description" must be a string',
}),
});
const sidebarItemCategoryLinkSchema = utils_validation_1.Joi.object()
.allow(null)
.when('.type', {
switch: [
{
is: 'doc',
then: utils_validation_1.Joi.object({
type: 'doc',
id: utils_validation_1.Joi.string().required(),
}),
},
{
is: 'generated-index',
then: utils_validation_1.Joi.object({
type: 'generated-index',
slug: utils_validation_1.Joi.string().optional(),
// This one is not in the user config, only in the normalized version
// permalink: Joi.string().optional(),
title: utils_validation_1.Joi.string().optional(),
description: utils_validation_1.Joi.string().optional(),
image: utils_validation_1.Joi.string().optional(),
keywords: [utils_validation_1.Joi.string(), utils_validation_1.Joi.array().items(utils_validation_1.Joi.string())],
}),
},
{
is: utils_validation_1.Joi.required(),
then: utils_validation_1.Joi.forbidden().messages({
'any.unknown': 'Unknown sidebar category link type "{.type}".',
}),
},
],
});
const sidebarItemCategorySchema = sidebarItemBaseSchema.append({
type: 'category',
label: utils_validation_1.Joi.string()
.required()
.messages({ 'any.unknown': '"label" must be a string' }),
items: utils_validation_1.Joi.array()
.required()
.messages({ 'any.unknown': '"items" must be an array' }),
// TODO: Joi doesn't allow mutual recursion. See https://github.com/sideway/joi/issues/2611
// .items(Joi.link('#sidebarItemSchema')),
link: sidebarItemCategoryLinkSchema,
collapsed: utils_validation_1.Joi.boolean().messages({
'any.unknown': '"collapsed" must be a boolean',
}),
collapsible: utils_validation_1.Joi.boolean().messages({
'any.unknown': '"collapsible" must be a boolean',
}),
description: utils_validation_1.Joi.string().optional().messages({
'any.unknown': '"description" must be a string',
}),
});
const sidebarItemSchema = utils_validation_1.Joi.object().when('.type', {
switch: [
{ is: 'link', then: sidebarItemLinkSchema },
{
is: utils_validation_1.Joi.string().valid('doc', 'ref').required(),
then: sidebarItemDocSchema,
},
{ is: 'html', then: sidebarItemHtmlSchema },
{ is: 'autogenerated', then: sidebarItemAutogeneratedSchema },
{ is: 'category', then: sidebarItemCategorySchema },
{
is: utils_validation_1.Joi.any().required(),
then: utils_validation_1.Joi.forbidden().messages({
'any.unknown': 'Unknown sidebar item type "{.type}".',
}),
},
],
});
// .id('sidebarItemSchema');
function validateSidebarItem(item) {
// TODO: remove once with proper Joi support
// Because we can't use Joi to validate nested items (see above), we do it
// manually
utils_validation_1.Joi.assert(item, sidebarItemSchema);
if (item.type === 'category') {
item.items.forEach(validateSidebarItem);
}
}
function validateSidebars(sidebars) {
Object.values(sidebars).forEach((sidebar) => {
sidebar.forEach(validateSidebarItem);
});
}
exports.validateSidebars = validateSidebars;
const categoryMetadataFileSchema = utils_validation_1.Joi.object({
label: utils_validation_1.Joi.string(),
position: utils_validation_1.Joi.number(),
collapsed: utils_validation_1.Joi.boolean(),
collapsible: utils_validation_1.Joi.boolean(),
className: utils_validation_1.Joi.string(),
link: sidebarItemCategoryLinkSchema,
customProps: utils_validation_1.Joi.object().unknown(),
});
function validateCategoryMetadataFile(unsafeContent) {
const { error, value } = categoryMetadataFileSchema.validate(unsafeContent);
if (error) {
throw error;
}
return value;
}
exports.validateCategoryMetadataFile = validateCategoryMetadataFile;