mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 11:18:24 +02:00
fix(content-docs): always sort autogenerated sidebar items by file/folder name by default (#6700)
This commit is contained in:
parent
f04cb7abb9
commit
c90d25ca2f
3 changed files with 73 additions and 66 deletions
|
@ -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,
|
||||
|
|
|
@ -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: {},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue