mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-26 13:08:58 +02:00
refactor(v2): docs plugin refactor (#3245)
* safe refactorings * safe refactors * add code to read versions more generically * refactor docs plugin * refactors * stable docs refactor * progress on refactor * stable docs refactor * stable docs refactor * stable docs refactor * attempt to fix admonition :( * configureWebpack docs: better typing * more refactors * rename cli * refactor docs metadata processing => move to pure function * stable docs refactor * stable docs refactor * named exports * basic sidebars refactor * add getElementsAround utils * refactor sidebar + ordering/navigation logic * stable retrocompatible refactor * add proper versions metadata tests * fix docs metadata tests * fix docs tests * fix test due to absolute path * fix webpack tests * refactor linkify + add broken markdown links warning * fix DOM warning due to forwarding legacy prop to div element * add todo
This commit is contained in:
parent
d17df954b5
commit
a4c8a7f55b
54 changed files with 3219 additions and 2724 deletions
|
@ -9,19 +9,47 @@ import flatMap from 'lodash.flatmap';
|
|||
import fs from 'fs-extra';
|
||||
import importFresh from 'import-fresh';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarRaw,
|
||||
Sidebars,
|
||||
SidebarItem,
|
||||
SidebarItemCategoryRaw,
|
||||
SidebarItemRaw,
|
||||
SidebarItemLink,
|
||||
SidebarItemDoc,
|
||||
SidebarCategoryShorthandRaw,
|
||||
Sidebar,
|
||||
} from './types';
|
||||
import {mapValues, flatten, difference} from 'lodash';
|
||||
import {getElementsAround} from '@docusaurus/utils';
|
||||
|
||||
type SidebarItemCategoryJSON = {
|
||||
type: 'category';
|
||||
label: string;
|
||||
items: SidebarItemJSON[];
|
||||
collapsed?: boolean;
|
||||
};
|
||||
|
||||
type SidebarItemJSON =
|
||||
| string
|
||||
| SidebarCategoryShorthandJSON
|
||||
| SidebarItemDoc
|
||||
| SidebarItemLink
|
||||
| SidebarItemCategoryJSON
|
||||
| {
|
||||
type: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
type SidebarCategoryShorthandJSON = {
|
||||
[sidebarCategory: string]: SidebarItemJSON[];
|
||||
};
|
||||
|
||||
type SidebarJSON = SidebarCategoryShorthandJSON | SidebarItemJSON[];
|
||||
|
||||
// Sidebar given by user that is not normalized yet. e.g: sidebars.json
|
||||
type SidebarsJSON = {
|
||||
[sidebarId: string]: SidebarJSON;
|
||||
};
|
||||
|
||||
function isCategoryShorthand(
|
||||
item: SidebarItemRaw,
|
||||
): item is SidebarCategoryShorthandRaw {
|
||||
item: SidebarItemJSON,
|
||||
): item is SidebarCategoryShorthandJSON {
|
||||
return typeof item !== 'string' && !item.type;
|
||||
}
|
||||
|
||||
|
@ -32,8 +60,8 @@ const defaultCategoryCollapsedValue = true;
|
|||
* Convert {category1: [item1,item2]} shorthand syntax to long-form syntax
|
||||
*/
|
||||
function normalizeCategoryShorthand(
|
||||
sidebar: SidebarCategoryShorthandRaw,
|
||||
): SidebarItemCategoryRaw[] {
|
||||
sidebar: SidebarCategoryShorthandJSON,
|
||||
): SidebarItemCategoryJSON[] {
|
||||
return Object.entries(sidebar).map(([label, items]) => ({
|
||||
type: 'category',
|
||||
collapsed: defaultCategoryCollapsedValue,
|
||||
|
@ -65,7 +93,7 @@ function assertItem<K extends string>(
|
|||
|
||||
function assertIsCategory(
|
||||
item: unknown,
|
||||
): asserts item is SidebarItemCategoryRaw {
|
||||
): asserts item is SidebarItemCategoryJSON {
|
||||
assertItem(item, ['items', 'label', 'collapsed']);
|
||||
if (typeof item.label !== 'string') {
|
||||
throw new Error(
|
||||
|
@ -112,7 +140,7 @@ function assertIsLink(item: unknown): asserts item is SidebarItemLink {
|
|||
* 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: SidebarItemRaw): SidebarItem[] {
|
||||
function normalizeItem(item: SidebarItemJSON): SidebarItem[] {
|
||||
if (typeof item === 'string') {
|
||||
return [
|
||||
{
|
||||
|
@ -155,38 +183,119 @@ function normalizeItem(item: SidebarItemRaw): SidebarItem[] {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts sidebars object to mapping to arrays of sidebar item objects.
|
||||
*/
|
||||
function normalizeSidebar(sidebars: SidebarRaw): Sidebar {
|
||||
return Object.entries(sidebars).reduce(
|
||||
(acc: Sidebar, [sidebarId, sidebar]) => {
|
||||
const normalizedSidebar: SidebarItemRaw[] = Array.isArray(sidebar)
|
||||
? sidebar
|
||||
: normalizeCategoryShorthand(sidebar);
|
||||
function normalizeSidebar(sidebar: SidebarJSON) {
|
||||
const normalizedSidebar: SidebarItemJSON[] = Array.isArray(sidebar)
|
||||
? sidebar
|
||||
: normalizeCategoryShorthand(sidebar);
|
||||
|
||||
acc[sidebarId] = flatMap(normalizedSidebar, normalizeItem);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
return flatMap(normalizedSidebar, normalizeItem);
|
||||
}
|
||||
|
||||
export default function loadSidebars(sidebarPaths?: string[]): Sidebar {
|
||||
// We don't want sidebars to be cached because of hot reloading.
|
||||
const allSidebars: SidebarRaw = {};
|
||||
function normalizeSidebars(sidebars: SidebarsJSON): Sidebars {
|
||||
return mapValues(sidebars, normalizeSidebar);
|
||||
}
|
||||
|
||||
if (!sidebarPaths || !sidebarPaths.length) {
|
||||
return {} as Sidebar;
|
||||
// TODO refactor: make async
|
||||
export function loadSidebars(sidebarFilePath: string): Sidebars {
|
||||
if (!sidebarFilePath) {
|
||||
throw new Error(`sidebarFilePath not provided: ${sidebarFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(sidebarFilePath)) {
|
||||
throw new Error(`No sidebar file exist at path: ${sidebarFilePath}`);
|
||||
}
|
||||
// We don't want sidebars to be cached because of hot reloading.
|
||||
const sidebarJson = importFresh(sidebarFilePath) as SidebarsJSON;
|
||||
return normalizeSidebars(sidebarJson);
|
||||
}
|
||||
|
||||
// traverse the sidebar tree in depth to find all doc items, in correct order
|
||||
export function collectSidebarDocItems(sidebar: Sidebar): SidebarItemDoc[] {
|
||||
function collectRecursive(item: SidebarItem): SidebarItemDoc[] {
|
||||
if (item.type === 'doc') {
|
||||
return [item];
|
||||
}
|
||||
if (item.type === 'category') {
|
||||
return flatten(item.items.map(collectRecursive));
|
||||
}
|
||||
// Refs and links should not be shown in navigation.
|
||||
if (item.type === 'ref' || item.type === 'link') {
|
||||
return [];
|
||||
}
|
||||
throw new Error(`unknown sidebar item type = ${item.type}`);
|
||||
}
|
||||
|
||||
sidebarPaths.forEach((sidebarPath) => {
|
||||
if (sidebarPath && fs.existsSync(sidebarPath)) {
|
||||
const sidebar = importFresh(sidebarPath) as SidebarRaw;
|
||||
Object.assign(allSidebars, sidebar);
|
||||
}
|
||||
});
|
||||
|
||||
return normalizeSidebar(allSidebars);
|
||||
return flatten(sidebar.map(collectRecursive));
|
||||
}
|
||||
|
||||
export function collectSidebarsDocIds(
|
||||
sidebars: Sidebars,
|
||||
): Record<string, string[]> {
|
||||
return mapValues(sidebars, (sidebar) => {
|
||||
return collectSidebarDocItems(sidebar).map((docItem) => docItem.id);
|
||||
});
|
||||
}
|
||||
|
||||
export function createSidebarsUtils(sidebars: Sidebars) {
|
||||
const sidebarNameToDocIds = collectSidebarsDocIds(sidebars);
|
||||
|
||||
function getFirstDocIdOfFirstSidebar(): string | undefined {
|
||||
return Object.values(sidebarNameToDocIds)[0]?.[0];
|
||||
}
|
||||
|
||||
function getSidebarNameByDocId(docId: string): string | undefined {
|
||||
// TODO lookup speed can be optimized
|
||||
const entry = Object.entries(
|
||||
sidebarNameToDocIds,
|
||||
).find(([_sidebarName, docIds]) => docIds.includes(docId));
|
||||
|
||||
return entry?.[0];
|
||||
}
|
||||
|
||||
function getDocNavigation(
|
||||
docId: string,
|
||||
): {
|
||||
sidebarName: string | undefined;
|
||||
previousId: string | undefined;
|
||||
nextId: string | undefined;
|
||||
} {
|
||||
const sidebarName = getSidebarNameByDocId(docId);
|
||||
if (sidebarName) {
|
||||
const docIds = sidebarNameToDocIds[sidebarName];
|
||||
const currentIndex = docIds.indexOf(docId);
|
||||
const {previous, next} = getElementsAround(docIds, currentIndex);
|
||||
return {
|
||||
sidebarName,
|
||||
previousId: previous,
|
||||
nextId: next,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sidebarName: undefined,
|
||||
previousId: undefined,
|
||||
nextId: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function checkSidebarsDocIds(validDocIds: string[]) {
|
||||
const allSidebarDocIds = flatten(Object.values(sidebarNameToDocIds));
|
||||
const invalidSidebarDocIds = difference(allSidebarDocIds, validDocIds);
|
||||
if (invalidSidebarDocIds.length > 0) {
|
||||
throw new Error(
|
||||
`Bad sidebars file.
|
||||
These sidebar document ids do not exist:
|
||||
- ${invalidSidebarDocIds.sort().join('\n- ')}\`,
|
||||
|
||||
Available document ids=
|
||||
- ${validDocIds.sort().join('\n- ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getFirstDocIdOfFirstSidebar,
|
||||
getSidebarNameByDocId,
|
||||
getDocNavigation,
|
||||
checkSidebarsDocIds,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue