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:
Sébastien Lorber 2020-08-17 17:50:22 +02:00 committed by GitHub
parent d17df954b5
commit a4c8a7f55b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 3219 additions and 2724 deletions

View file

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