fix(content-docs): always sort autogenerated sidebar items by file/folder name by default (#6700)

This commit is contained in:
Joshua Chen 2022-03-03 19:39:54 +08:00 committed by GitHub
parent f04cb7abb9
commit c90d25ca2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 66 deletions

View file

@ -6,8 +6,8 @@ Object {
Object {
"id": "doc with space",
"next": Object {
"permalink": "/docs/headingAsTitle",
"title": "My heading as title",
"permalink": "/docs/foo/bar",
"title": "Bar",
},
"prev": undefined,
},
@ -19,8 +19,8 @@ Object {
Object {
"id": "foo/baz",
"next": Object {
"permalink": "/docs/absoluteSlug",
"title": "absoluteSlug",
"permalink": "/docs/headingAsTitle",
"title": "My heading as title",
},
"prev": Object {
"permalink": "/docs/foo/bar",
@ -34,8 +34,8 @@ Object {
"title": "Hello sidebar_label",
},
"prev": Object {
"permalink": "/docs/doc with space",
"title": "Hoo hoo, if this path tricks you...",
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
},
},
Object {
@ -122,8 +122,8 @@ Object {
"title": "relativeSlug",
},
"prev": Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
"permalink": "/docs/rootTryToEscapeSlug",
"title": "rootTryToEscapeSlug",
},
},
Object {
@ -163,6 +163,23 @@ Object {
"id": "doc with space",
"type": "doc",
},
Object {
"collapsed": false,
"collapsible": true,
"items": Array [
Object {
"id": "foo/bar",
"type": "doc",
},
Object {
"id": "foo/baz",
"type": "doc",
},
],
"label": "foo",
"link": undefined,
"type": "category",
},
Object {
"id": "headingAsTitle",
"type": "doc",
@ -196,23 +213,6 @@ Object {
"id": "rootTryToEscapeSlug",
"type": "doc",
},
Object {
"collapsed": false,
"collapsible": true,
"items": Array [
Object {
"id": "foo/bar",
"type": "doc",
},
Object {
"id": "foo/baz",
"type": "doc",
},
],
"label": "foo",
"link": undefined,
"type": "category",
},
Object {
"collapsed": false,
"collapsible": true,

View file

@ -142,7 +142,7 @@ describe('DefaultSidebarItemsGenerator', () => {
id: 'intro',
source: '@site/docs/intro.md',
sourceDirName: '.',
sidebarPosition: 1,
sidebarPosition: 0,
frontMatter: {},
},
{
@ -183,7 +183,7 @@ describe('DefaultSidebarItemsGenerator', () => {
id: 'guide1',
source: '@site/docs/02-Guides/guide1.md',
sourceDirName: '02-Guides',
sidebarPosition: 1,
sidebarPosition: 0,
frontMatter: {
sidebar_class_name: 'foo',
},
@ -406,7 +406,7 @@ describe('DefaultSidebarItemsGenerator', () => {
},
{
id: 'parent/doc2',
source: '@site/docs/Category/index.md',
source: '@site/docs/Category/doc2.md',
sourceDirName: 'Category',
frontMatter: {},
},
@ -451,11 +451,12 @@ describe('DefaultSidebarItemsGenerator', () => {
},
items: [
{
id: 'parent/doc1',
id: 'parent/doc2',
type: 'doc',
},
// doc1 is below doc2, because its file name is index.md
{
id: 'parent/doc2',
id: 'parent/doc1',
type: 'doc',
},
],
@ -469,11 +470,11 @@ describe('DefaultSidebarItemsGenerator', () => {
type: 'doc',
},
{
id: 'parent/doc5',
id: 'parent/doc6',
type: 'doc',
},
{
id: 'parent/doc6',
id: 'parent/doc5',
type: 'doc',
},
],
@ -508,7 +509,7 @@ describe('DefaultSidebarItemsGenerator', () => {
id: 'intro',
source: '@site/docs/intro.md',
sourceDirName: '.',
sidebarPosition: 1,
sidebarPosition: 0,
frontMatter: {},
},
{

View file

@ -20,8 +20,6 @@ import path from 'path';
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
const BreadcrumbSeparator = '/';
// To avoid possible name clashes with a folder of the same name as the ID
const docIdPrefix = '$doc$/';
// Just an alias to the make code more explicit
function getLocalDocId(docId: string): string {
@ -31,16 +29,20 @@ function getLocalDocId(docId: string): string {
export const CategoryMetadataFilenameBase = '_category_';
export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
type WithPosition<T> = T & {position?: number};
type WithPosition<T> = T & {
position?: number;
/** The source is the file/folder name */
source?: string;
};
/**
* A representation of the fs structure. For each object entry:
* If it's a folder, the key is the directory name, and value is the directory
* content; If it's a doc file, the key is the doc id prefixed with '$doc$/',
* and value is null
* content; If it's a doc file, the key is the doc's source file name, and value
* is the doc ID
*/
type Dir = {
[item: string]: Dir | null;
[item: string]: Dir | string;
};
// Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
@ -108,14 +110,16 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
const treeRoot: Dir = {};
docs.forEach((doc) => {
const breadcrumb = getRelativeBreadcrumb(doc);
let currentDir = treeRoot; // We walk down the file's path to generate the fs structure
// 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.
currentDir = currentDir[dir] as Dir; // Go into the subdirectory.
});
currentDir[`${docIdPrefix}${doc.id}`] = null; // We've walked through the file path. Register the file in this directory.
// We've walked through the path. Register the file in this directory.
currentDir[path.basename(doc.source)] = doc.id;
});
return treeRoot;
}
@ -126,8 +130,12 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
*/
function generateSidebar(
fsModel: Dir,
): Promise<WithPosition<NormalizedSidebarItem>[]> {
function createDocItem(id: string): WithPosition<SidebarItemDoc> {
): WithPosition<NormalizedSidebarItem>[] {
function createDocItem(
id: string,
fullPath: string,
fileName: string,
): WithPosition<SidebarItemDoc> {
const {
sidebarPosition: position,
frontMatter: {sidebar_label: label, sidebar_class_name: className},
@ -136,25 +144,24 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
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}),
};
}
async function createCategoryItem(
function createCategoryItem(
dir: Dir,
fullPath: string,
folderName: string,
): Promise<WithPosition<NormalizedSidebarItemCategory>> {
): WithPosition<NormalizedSidebarItemCategory> {
const categoryMetadata =
categoriesMetadata[posixPath(path.join(autogenDir, fullPath))];
const className = categoryMetadata?.className;
const {filename, numberPrefix} = numberPrefixParser(folderName);
const allItems = await Promise.all(
Object.entries(dir).map(([key, content]) =>
dirToItem(content, key, `${fullPath}/${key}`),
),
const allItems = Object.entries(dir).map(([key, content]) =>
dirToItem(content, key, `${fullPath}/${key}`),
);
// Try to match a doc inside the category folder,
@ -211,24 +218,23 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
collapsible: categoryMetadata?.collapsible,
collapsed: categoryMetadata?.collapsed,
position: categoryMetadata?.position ?? numberPrefix,
source: folderName,
...(className !== undefined && {className}),
items,
...(link && {link}),
};
}
async function dirToItem(
dir: Dir | null, // The directory item to be transformed.
itemKey: string, // For docs, it's the doc ID; for categories, it's used to generate the next `relativePath`.
function dirToItem(
dir: Dir | string, // The directory item to be transformed.
itemKey: string, // File/folder name; for categories, it's used to generate the next `relativePath`.
fullPath: string, // `dir`'s full path relative to the autogen dir.
): Promise<WithPosition<NormalizedSidebarItem>> {
return dir
): WithPosition<NormalizedSidebarItem> {
return typeof dir === 'object'
? createCategoryItem(dir, fullPath, itemKey)
: createDocItem(itemKey.substring(docIdPrefix.length));
: createDocItem(dir, fullPath, itemKey);
}
return Promise.all(
Object.entries(fsModel).map(([key, content]) =>
dirToItem(content, key, key),
),
return Object.entries(fsModel).map(([key, content]) =>
dirToItem(content, key, key),
);
}
@ -248,16 +254,16 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
}
return item;
});
const sortedSidebarItems = _.sortBy(
processedSidebarItems,
(item) => item.position,
);
return sortedSidebarItems.map(({position, ...item}) => item);
const sortedSidebarItems = _.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 = await generateSidebar(fsModel);
const sidebarWithPosition = generateSidebar(fsModel);
const sortedSidebar = sortItems(sidebarWithPosition);
return sortedSidebar;
};