Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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",
Expand All @@ -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 {
Expand Down Expand Up @@ -122,8 +122,8 @@ Object {
"title": "relativeSlug",
},
"prev": Object {
"permalink": "/docs/foo/bazSlug.html",
"title": "baz pagination_label",
"permalink": "/docs/rootTryToEscapeSlug",
"title": "rootTryToEscapeSlug",
},
},
Object {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('DefaultSidebarItemsGenerator', () => {
id: 'intro',
source: '@site/docs/intro.md',
sourceDirName: '.',
sidebarPosition: 1,
sidebarPosition: 0,
frontMatter: {},
},
{
Expand Down Expand Up @@ -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',
},
Expand Down Expand Up @@ -406,7 +406,7 @@ describe('DefaultSidebarItemsGenerator', () => {
},
{
id: 'parent/doc2',
source: '@site/docs/Category/index.md',
source: '@site/docs/Category/doc2.md',
sourceDirName: 'Category',
frontMatter: {},
},
Expand Down Expand Up @@ -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',
},
],
Expand All @@ -469,11 +470,11 @@ describe('DefaultSidebarItemsGenerator', () => {
type: 'doc',
},
{
id: 'parent/doc5',
id: 'parent/doc6',
type: 'doc',
},
{
id: 'parent/doc6',
id: 'parent/doc5',
type: 'doc',
},
],
Expand Down Expand Up @@ -508,7 +509,7 @@ describe('DefaultSidebarItemsGenerator', () => {
id: 'intro',
source: '@site/docs/intro.md',
sourceDirName: '.',
sidebarPosition: 1,
sidebarPosition: 0,
frontMatter: {},
},
{
Expand Down
72 changes: 39 additions & 33 deletions packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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},
Expand All @@ -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,
Expand Down Expand Up @@ -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),
);
}

Expand All @@ -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;
};