mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-03 04:07:32 +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 {
|
Object {
|
||||||
"id": "doc with space",
|
"id": "doc with space",
|
||||||
"next": Object {
|
"next": Object {
|
||||||
"permalink": "/docs/headingAsTitle",
|
"permalink": "/docs/foo/bar",
|
||||||
"title": "My heading as title",
|
"title": "Bar",
|
||||||
},
|
},
|
||||||
"prev": undefined,
|
"prev": undefined,
|
||||||
},
|
},
|
||||||
|
@ -19,8 +19,8 @@ Object {
|
||||||
Object {
|
Object {
|
||||||
"id": "foo/baz",
|
"id": "foo/baz",
|
||||||
"next": Object {
|
"next": Object {
|
||||||
"permalink": "/docs/absoluteSlug",
|
"permalink": "/docs/headingAsTitle",
|
||||||
"title": "absoluteSlug",
|
"title": "My heading as title",
|
||||||
},
|
},
|
||||||
"prev": Object {
|
"prev": Object {
|
||||||
"permalink": "/docs/foo/bar",
|
"permalink": "/docs/foo/bar",
|
||||||
|
@ -34,8 +34,8 @@ Object {
|
||||||
"title": "Hello sidebar_label",
|
"title": "Hello sidebar_label",
|
||||||
},
|
},
|
||||||
"prev": Object {
|
"prev": Object {
|
||||||
"permalink": "/docs/doc with space",
|
"permalink": "/docs/foo/bazSlug.html",
|
||||||
"title": "Hoo hoo, if this path tricks you...",
|
"title": "baz pagination_label",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
@ -122,8 +122,8 @@ Object {
|
||||||
"title": "relativeSlug",
|
"title": "relativeSlug",
|
||||||
},
|
},
|
||||||
"prev": Object {
|
"prev": Object {
|
||||||
"permalink": "/docs/foo/bazSlug.html",
|
"permalink": "/docs/rootTryToEscapeSlug",
|
||||||
"title": "baz pagination_label",
|
"title": "rootTryToEscapeSlug",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
@ -163,6 +163,23 @@ Object {
|
||||||
"id": "doc with space",
|
"id": "doc with space",
|
||||||
"type": "doc",
|
"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 {
|
Object {
|
||||||
"id": "headingAsTitle",
|
"id": "headingAsTitle",
|
||||||
"type": "doc",
|
"type": "doc",
|
||||||
|
@ -196,23 +213,6 @@ Object {
|
||||||
"id": "rootTryToEscapeSlug",
|
"id": "rootTryToEscapeSlug",
|
||||||
"type": "doc",
|
"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 {
|
Object {
|
||||||
"collapsed": false,
|
"collapsed": false,
|
||||||
"collapsible": true,
|
"collapsible": true,
|
||||||
|
|
|
@ -142,7 +142,7 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
id: 'intro',
|
id: 'intro',
|
||||||
source: '@site/docs/intro.md',
|
source: '@site/docs/intro.md',
|
||||||
sourceDirName: '.',
|
sourceDirName: '.',
|
||||||
sidebarPosition: 1,
|
sidebarPosition: 0,
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -183,7 +183,7 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
id: 'guide1',
|
id: 'guide1',
|
||||||
source: '@site/docs/02-Guides/guide1.md',
|
source: '@site/docs/02-Guides/guide1.md',
|
||||||
sourceDirName: '02-Guides',
|
sourceDirName: '02-Guides',
|
||||||
sidebarPosition: 1,
|
sidebarPosition: 0,
|
||||||
frontMatter: {
|
frontMatter: {
|
||||||
sidebar_class_name: 'foo',
|
sidebar_class_name: 'foo',
|
||||||
},
|
},
|
||||||
|
@ -406,7 +406,7 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'parent/doc2',
|
id: 'parent/doc2',
|
||||||
source: '@site/docs/Category/index.md',
|
source: '@site/docs/Category/doc2.md',
|
||||||
sourceDirName: 'Category',
|
sourceDirName: 'Category',
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
},
|
},
|
||||||
|
@ -451,11 +451,12 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: 'parent/doc1',
|
id: 'parent/doc2',
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
},
|
},
|
||||||
|
// doc1 is below doc2, because its file name is index.md
|
||||||
{
|
{
|
||||||
id: 'parent/doc2',
|
id: 'parent/doc1',
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -469,11 +470,11 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'parent/doc5',
|
id: 'parent/doc6',
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'parent/doc6',
|
id: 'parent/doc5',
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -508,7 +509,7 @@ describe('DefaultSidebarItemsGenerator', () => {
|
||||||
id: 'intro',
|
id: 'intro',
|
||||||
source: '@site/docs/intro.md',
|
source: '@site/docs/intro.md',
|
||||||
sourceDirName: '.',
|
sourceDirName: '.',
|
||||||
sidebarPosition: 1,
|
sidebarPosition: 0,
|
||||||
frontMatter: {},
|
frontMatter: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,8 +20,6 @@ import path from 'path';
|
||||||
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
|
import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs';
|
||||||
|
|
||||||
const BreadcrumbSeparator = '/';
|
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
|
// Just an alias to the make code more explicit
|
||||||
function getLocalDocId(docId: string): string {
|
function getLocalDocId(docId: string): string {
|
||||||
|
@ -31,16 +29,20 @@ function getLocalDocId(docId: string): string {
|
||||||
export const CategoryMetadataFilenameBase = '_category_';
|
export const CategoryMetadataFilenameBase = '_category_';
|
||||||
export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
|
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:
|
* 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
|
* 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$/',
|
* content; If it's a doc file, the key is the doc's source file name, and value
|
||||||
* and value is null
|
* is the doc ID
|
||||||
*/
|
*/
|
||||||
type Dir = {
|
type Dir = {
|
||||||
[item: string]: Dir | null;
|
[item: string]: Dir | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
|
// 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 = {};
|
const treeRoot: Dir = {};
|
||||||
docs.forEach((doc) => {
|
docs.forEach((doc) => {
|
||||||
const breadcrumb = getRelativeBreadcrumb(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) => {
|
breadcrumb.forEach((dir) => {
|
||||||
if (typeof currentDir[dir] === 'undefined') {
|
if (typeof currentDir[dir] === 'undefined') {
|
||||||
currentDir[dir] = {}; // Create new folder.
|
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;
|
return treeRoot;
|
||||||
}
|
}
|
||||||
|
@ -126,8 +130,12 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
|
||||||
*/
|
*/
|
||||||
function generateSidebar(
|
function generateSidebar(
|
||||||
fsModel: Dir,
|
fsModel: Dir,
|
||||||
): Promise<WithPosition<NormalizedSidebarItem>[]> {
|
): WithPosition<NormalizedSidebarItem>[] {
|
||||||
function createDocItem(id: string): WithPosition<SidebarItemDoc> {
|
function createDocItem(
|
||||||
|
id: string,
|
||||||
|
fullPath: string,
|
||||||
|
fileName: string,
|
||||||
|
): WithPosition<SidebarItemDoc> {
|
||||||
const {
|
const {
|
||||||
sidebarPosition: position,
|
sidebarPosition: position,
|
||||||
frontMatter: {sidebar_label: label, sidebar_class_name: className},
|
frontMatter: {sidebar_label: label, sidebar_class_name: className},
|
||||||
|
@ -136,25 +144,24 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
id,
|
id,
|
||||||
position,
|
position,
|
||||||
|
source: fileName,
|
||||||
// We don't want these fields to magically appear in the generated
|
// We don't want these fields to magically appear in the generated
|
||||||
// sidebar
|
// sidebar
|
||||||
...(label !== undefined && {label}),
|
...(label !== undefined && {label}),
|
||||||
...(className !== undefined && {className}),
|
...(className !== undefined && {className}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async function createCategoryItem(
|
function createCategoryItem(
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
fullPath: string,
|
fullPath: string,
|
||||||
folderName: string,
|
folderName: string,
|
||||||
): Promise<WithPosition<NormalizedSidebarItemCategory>> {
|
): WithPosition<NormalizedSidebarItemCategory> {
|
||||||
const categoryMetadata =
|
const categoryMetadata =
|
||||||
categoriesMetadata[posixPath(path.join(autogenDir, fullPath))];
|
categoriesMetadata[posixPath(path.join(autogenDir, fullPath))];
|
||||||
const className = categoryMetadata?.className;
|
const className = categoryMetadata?.className;
|
||||||
const {filename, numberPrefix} = numberPrefixParser(folderName);
|
const {filename, numberPrefix} = numberPrefixParser(folderName);
|
||||||
const allItems = await Promise.all(
|
const allItems = Object.entries(dir).map(([key, content]) =>
|
||||||
Object.entries(dir).map(([key, content]) =>
|
dirToItem(content, key, `${fullPath}/${key}`),
|
||||||
dirToItem(content, key, `${fullPath}/${key}`),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to match a doc inside the category folder,
|
// Try to match a doc inside the category folder,
|
||||||
|
@ -211,24 +218,23 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
|
||||||
collapsible: categoryMetadata?.collapsible,
|
collapsible: categoryMetadata?.collapsible,
|
||||||
collapsed: categoryMetadata?.collapsed,
|
collapsed: categoryMetadata?.collapsed,
|
||||||
position: categoryMetadata?.position ?? numberPrefix,
|
position: categoryMetadata?.position ?? numberPrefix,
|
||||||
|
source: folderName,
|
||||||
...(className !== undefined && {className}),
|
...(className !== undefined && {className}),
|
||||||
items,
|
items,
|
||||||
...(link && {link}),
|
...(link && {link}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async function dirToItem(
|
function dirToItem(
|
||||||
dir: Dir | null, // The directory item to be transformed.
|
dir: Dir | string, // 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`.
|
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.
|
fullPath: string, // `dir`'s full path relative to the autogen dir.
|
||||||
): Promise<WithPosition<NormalizedSidebarItem>> {
|
): WithPosition<NormalizedSidebarItem> {
|
||||||
return dir
|
return typeof dir === 'object'
|
||||||
? createCategoryItem(dir, fullPath, itemKey)
|
? createCategoryItem(dir, fullPath, itemKey)
|
||||||
: createDocItem(itemKey.substring(docIdPrefix.length));
|
: createDocItem(dir, fullPath, itemKey);
|
||||||
}
|
}
|
||||||
return Promise.all(
|
return Object.entries(fsModel).map(([key, content]) =>
|
||||||
Object.entries(fsModel).map(([key, content]) =>
|
dirToItem(content, key, key),
|
||||||
dirToItem(content, key, key),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,16 +254,16 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
const sortedSidebarItems = _.sortBy(
|
const sortedSidebarItems = _.sortBy(processedSidebarItems, [
|
||||||
processedSidebarItems,
|
'position',
|
||||||
(item) => item.position,
|
'source',
|
||||||
);
|
]);
|
||||||
return sortedSidebarItems.map(({position, ...item}) => item);
|
return sortedSidebarItems.map(({position, source, ...item}) => item);
|
||||||
}
|
}
|
||||||
// TODO: the whole code is designed for pipeline operator
|
// TODO: the whole code is designed for pipeline operator
|
||||||
const docs = getAutogenDocs();
|
const docs = getAutogenDocs();
|
||||||
const fsModel = treeify(docs);
|
const fsModel = treeify(docs);
|
||||||
const sidebarWithPosition = await generateSidebar(fsModel);
|
const sidebarWithPosition = generateSidebar(fsModel);
|
||||||
const sortedSidebar = sortItems(sidebarWithPosition);
|
const sortedSidebar = sortItems(sidebarWithPosition);
|
||||||
return sortedSidebar;
|
return sortedSidebar;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue