feat(v2): allow nested sidebar category shorthand syntax (#2444)

* feat(v2): allow sidebar category shorthand syntax also for nested elements

* Update sidebars-category-shorthand.js

* Update sidebars-category-shorthand.js

Co-authored-by: Yangshun Tay <tay.yang.shun@gmail.com>
This commit is contained in:
Sébastien Lorber 2020-03-25 11:59:51 -05:00 committed by GitHub
parent 85c124e3f1
commit 201c663318
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 116 additions and 52 deletions

View file

@ -27,5 +27,5 @@ module.exports = {
transform: { transform: {
'^.+\\.[jt]sx?$': 'babel-jest', '^.+\\.[jt]sx?$': 'babel-jest',
}, },
setupFiles: ['./jest/stylelint-rule-test.js'], setupFiles: ['./jest/stylelint-rule-test.js', 'array-flat-polyfill'],
}; };

View file

@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
docs: [
{
'level 1': [
'a',
{
'level 2': [
{
'level 3': [
'c',
{
'level 4': [
'd',
{
'deeper more more': ['e'],
},
],
},
],
},
'f',
],
},
],
},
],
};

View file

@ -1,8 +0,0 @@
{
"docs": [
{
"a": "b",
"c": "d"
}
]
}

View file

@ -24,6 +24,17 @@ describe('loadSidebars', () => {
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
test('sidebars shortand and longform lead to exact same sidebar', async () => {
const sidebarPath1 = path.join(fixtureDir, 'sidebars-category.js');
const sidebarPath2 = path.join(
fixtureDir,
'sidebars-category-shorthand.js',
);
const sidebar1 = loadSidebars([sidebarPath1]);
const sidebar2 = loadSidebars([sidebarPath2]);
expect(sidebar1).toEqual(sidebar2);
});
test('sidebars with category but category.items is not an array', async () => { test('sidebars with category but category.items is not an array', async () => {
const sidebarPath = path.join( const sidebarPath = path.join(
fixtureDir, fixtureDir,
@ -93,15 +104,6 @@ describe('loadSidebars', () => {
); );
}); });
test('sidebars with invalid sidebar item', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-invalid-item.json');
expect(() =>
loadSidebars([sidebarPath]),
).toThrowErrorMatchingInlineSnapshot(
`"Unknown sidebar item \\"{\\"a\\":\\"b\\",\\"c\\":\\"d\\"}\\"."`,
);
});
test('sidebars with unknown sidebar item type', async () => { test('sidebars with unknown sidebar item type', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-unknown-type.json'); const sidebarPath = path.join(fixtureDir, 'sidebars-unknown-type.json');
expect(() => expect(() =>

View file

@ -15,8 +15,28 @@ import {
SidebarItemRaw, SidebarItemRaw,
SidebarItemLink, SidebarItemLink,
SidebarItemDoc, SidebarItemDoc,
SidebarCategoryShorthandRaw,
} from './types'; } from './types';
function isCategoryShorthand(
item: SidebarItemRaw,
): item is SidebarCategoryShorthandRaw {
return typeof item !== 'string' && !item.type;
}
/**
* Convert {category1: [item1,item2]} shorthand syntax to long-form syntax
*/
function normalizeCategoryShorthand(
sidebar: SidebarCategoryShorthandRaw,
): SidebarItemCategoryRaw[] {
return Object.entries(sidebar).map(([label, items]) => ({
type: 'category',
label,
items,
}));
}
/** /**
* Check that item contains only allowed keys. * Check that item contains only allowed keys.
*/ */
@ -75,27 +95,29 @@ function assertIsLink(item: any): asserts item is SidebarItemLink {
* Normalizes recursively item and all its children. Ensures that at the end * Normalizes recursively item and all its children. Ensures that at the end
* each item will be an object with the corresponding type. * each item will be an object with the corresponding type.
*/ */
function normalizeItem(item: SidebarItemRaw): SidebarItem { function normalizeItem(item: SidebarItemRaw): SidebarItem[] {
if (typeof item === 'string') { if (typeof item === 'string') {
return { return [
type: 'doc', {
id: item, type: 'doc',
}; id: item,
},
];
} }
if (!item.type) { if (isCategoryShorthand(item)) {
throw new Error(`Unknown sidebar item "${JSON.stringify(item)}".`); return normalizeCategoryShorthand(item).flatMap(normalizeItem);
} }
switch (item.type) { switch (item.type) {
case 'category': case 'category':
assertIsCategory(item); assertIsCategory(item);
return {...item, items: item.items.map(normalizeItem)}; return [{...item, items: item.items.flatMap(normalizeItem)}];
case 'link': case 'link':
assertIsLink(item); assertIsLink(item);
return item; return [item];
case 'ref': case 'ref':
case 'doc': case 'doc':
assertIsDoc(item); assertIsDoc(item);
return item; return [item];
default: default:
throw new Error(`Unknown sidebar item type: ${item.type}`); throw new Error(`Unknown sidebar item type: ${item.type}`);
} }
@ -107,20 +129,11 @@ function normalizeItem(item: SidebarItemRaw): SidebarItem {
function normalizeSidebar(sidebars: SidebarRaw): Sidebar { function normalizeSidebar(sidebars: SidebarRaw): Sidebar {
return Object.entries(sidebars).reduce( return Object.entries(sidebars).reduce(
(acc: Sidebar, [sidebarId, sidebar]) => { (acc: Sidebar, [sidebarId, sidebar]) => {
let normalizedSidebar: SidebarItemRaw[]; const normalizedSidebar: SidebarItemRaw[] = Array.isArray(sidebar)
? sidebar
: normalizeCategoryShorthand(sidebar);
if (!Array.isArray(sidebar)) { acc[sidebarId] = normalizedSidebar.flatMap(normalizeItem);
// Convert sidebar to a more generic structure.
normalizedSidebar = Object.entries(sidebar).map(([label, items]) => ({
type: 'category',
label,
items,
}));
} else {
normalizedSidebar = sidebar;
}
acc[sidebarId] = normalizedSidebar.map(normalizeItem);
return acc; return acc;
}, },

View file

@ -55,6 +55,7 @@ export type SidebarItem =
export type SidebarItemRaw = export type SidebarItemRaw =
| string | string
| SidebarCategoryShorthandRaw
| SidebarItemDoc | SidebarItemDoc
| SidebarItemLink | SidebarItemLink
| SidebarItemCategoryRaw | SidebarItemCategoryRaw
@ -63,13 +64,13 @@ export type SidebarItemRaw =
[key: string]: any; [key: string]: any;
}; };
export interface SidebarCategoryShorthandRaw {
[sidebarCategory: string]: SidebarItemRaw[];
}
// Sidebar given by user that is not normalized yet. e.g: sidebars.json // Sidebar given by user that is not normalized yet. e.g: sidebars.json
export interface SidebarRaw { export interface SidebarRaw {
[sidebarId: string]: [sidebarId: string]: SidebarCategoryShorthandRaw | SidebarItemRaw[];
| {
[sidebarCategory: string]: SidebarItemRaw[];
}
| SidebarItemRaw[];
} }
export interface Sidebar { export interface Sidebar {

View file

@ -41,6 +41,7 @@
"@babel/runtime": "^7.7.4", "@babel/runtime": "^7.7.4",
"@docusaurus/utils": "^2.0.0-alpha.48", "@docusaurus/utils": "^2.0.0-alpha.48",
"@endiliey/static-site-generator-webpack-plugin": "^4.0.0", "@endiliey/static-site-generator-webpack-plugin": "^4.0.0",
"array-flat-polyfill": "^1.0.1",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babel-plugin-dynamic-import-node": "^2.3.0", "babel-plugin-dynamic-import-node": "^2.3.0",
"cache-loader": "^4.1.0", "cache-loader": "^4.1.0",

View file

@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import 'array-flat-polyfill';
export {build} from './commands/build'; export {build} from './commands/build';
export {start} from './commands/start'; export {start} from './commands/start';
export {swizzle} from './commands/swizzle'; export {swizzle} from './commands/swizzle';

View file

@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"target": "es2017", "target": "es2017",
"module": "commonjs", "module": "commonjs",
"lib": ["es2017"], "lib": ["es2017","es2019.array"],
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,

View file

@ -50,7 +50,7 @@ module.exports = {
}; };
``` ```
If you don't want to rely on iteration order of JavaScript object keys for the category name, the following sidebar object is also equivalent of the above. Keep in mind that EcmaScript does not guarantee `Object.keys({a,b}) === ['a','b']` (yet, this is generally true). If you don't want to rely on iteration order of JavaScript object keys for the category name, the following sidebar object is also equivalent of the above shorthand syntax.
```js ```js
// sidebars.js // sidebars.js
@ -221,7 +221,23 @@ module.exports = {
{ {
type: 'category', type: 'category',
label: 'Docs', label: 'Docs',
items: ['markdown-features', 'sidebar'], items: ['markdown-features', 'sidebar', 'versioning'],
},
],
},
};
```
**Note**: it's possible to use the shorthand syntax to create nested categories:
```js
// sidebars.js
module.exports = {
docs: {
Guides: [
'creating-pages',
{
Docs: ['markdown-features', 'sidebar', 'versioning'],
}, },
], ],
}, },

View file

@ -19,9 +19,7 @@ module.exports = {
'styling-layout', 'styling-layout',
'static-assets', 'static-assets',
{ {
type: 'category', Docs: ['markdown-features', 'sidebar', 'versioning'],
label: 'Docs',
items: ['markdown-features', 'sidebar', 'versioning'],
}, },
'blog', 'blog',
'search', 'search',

View file

@ -3147,6 +3147,11 @@ array-find-index@^1.0.1:
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
array-flat-polyfill@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz#1e3a4255be619dfbffbfd1d635c1cf357cd034e7"
integrity sha512-hfJmKupmQN0lwi0xG6FQ5U8Rd97RnIERplymOv/qpq8AoNKPPAnxJadjFA23FNWm88wykh9HmpLJUUwUtNU/iw==
array-flatten@1.1.1: array-flatten@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"