mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 07:37:19 +02:00
Add ability to specify sub categories in sidebar.json (#891)
This commit is contained in:
parent
dd9d05c84d
commit
16087b4428
14 changed files with 534 additions and 87 deletions
|
@ -94,6 +94,29 @@ You should provide `directory/id` instead of `id` in `sidebars.json`.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Adding Sub Categories
|
||||||
|
|
||||||
|
It is possibile to add sub categories to a sidebar. Instead of passing an array to the category like the previous examples you can pass an object where
|
||||||
|
the keys will be the sub category name. You can then pass an array of document ids to the sub category.
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"examples-sidebar" : {
|
||||||
|
"My Example Category" : {
|
||||||
|
"My Example Sub Category" : [
|
||||||
|
"my-examples",
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"My Next Sub Category" : [
|
||||||
|
"some-other-examples"
|
||||||
|
]
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Adding New Sidebars
|
### Adding New Sidebars
|
||||||
|
|
||||||
You can also put a document in a new sidebar. In the following example, we are creating an `examples-sidebar` sidebar within `sidebars.json` that has a category called `My Example Category` containing a document with an `id` of `my-examples`.
|
You can also put a document in a new sidebar. In the following example, we are creating an `examples-sidebar` sidebar within `sidebars.json` that has a category called `My Example Category` containing a document with an `id` of `my-examples`.
|
||||||
|
|
|
@ -6,14 +6,30 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const fs = require('fs');
|
||||||
const Container = require('./Container.js');
|
const Container = require('./Container.js');
|
||||||
const SideNav = require('./nav/SideNav.js');
|
const SideNav = require('./nav/SideNav.js');
|
||||||
|
const Metadata = require('../core/metadata.js');
|
||||||
const readCategories = require('../server/readCategories.js');
|
const readCategories = require('../server/readCategories.js');
|
||||||
|
|
||||||
|
let languages;
|
||||||
|
|
||||||
|
if (fs.existsSync(`../server/languages.js`)) {
|
||||||
|
languages = require(`../server/languages.js`);
|
||||||
|
} else {
|
||||||
|
languages = [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
name: 'English',
|
||||||
|
tag: 'en',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
class DocsSidebar extends React.Component {
|
class DocsSidebar extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const sidebar = this.props.metadata.sidebar;
|
const sidebar = this.props.metadata.sidebar;
|
||||||
const docsCategories = readCategories(sidebar);
|
const docsCategories = readCategories(sidebar, Metadata, languages);
|
||||||
const categoryName = docsCategories[this.props.metadata.language][0].name;
|
const categoryName = docsCategories[this.props.metadata.language][0].name;
|
||||||
if (!categoryName) {
|
if (!categoryName) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -65,7 +65,22 @@ class SideNav extends React.Component {
|
||||||
<h3 className="navGroupCategoryTitle">
|
<h3 className="navGroupCategoryTitle">
|
||||||
{this.getLocalizedCategoryString(category.name)}
|
{this.getLocalizedCategoryString(category.name)}
|
||||||
</h3>
|
</h3>
|
||||||
<ul>{category.links.map(this.renderItemLink, this)}</ul>
|
<ul>
|
||||||
|
{category.links.map(this.renderItemLink, this)}
|
||||||
|
{category.sub_categories &&
|
||||||
|
category.sub_categories.map(this.renderSubCategory, this)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSubCategory(subCategory) {
|
||||||
|
return (
|
||||||
|
<div className="navGroup subNavGroup" key={subCategory.name}>
|
||||||
|
<h4 className="navGroupSubCategoryTitle">
|
||||||
|
{this.getLocalizedCategoryString(subCategory.name)}
|
||||||
|
</h4>
|
||||||
|
<ul>{subCategory.links.map(this.renderItemLink, this)}</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
65
lib/server/__tests__/__fixtures__/metadata-subcategories.js
Normal file
65
lib/server/__tests__/__fixtures__/metadata-subcategories.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
module.exports = {
|
||||||
|
'en-doc1': {
|
||||||
|
id: 'en-doc1',
|
||||||
|
title: 'Document 1',
|
||||||
|
source: 'doc1.md',
|
||||||
|
version: 'next',
|
||||||
|
permalink: 'docs/en/next/doc1.html',
|
||||||
|
localized_id: 'doc1',
|
||||||
|
language: 'en',
|
||||||
|
sidebar: 'docs',
|
||||||
|
category: 'Test',
|
||||||
|
next_id: 'doc2',
|
||||||
|
next: 'en-doc2',
|
||||||
|
next_title: 'Document 2',
|
||||||
|
sub_category: 'Sub Cat 1',
|
||||||
|
sort: 1,
|
||||||
|
},
|
||||||
|
'en-doc2': {
|
||||||
|
id: 'en-doc2',
|
||||||
|
title: 'Document 2',
|
||||||
|
source: 'doc2.md',
|
||||||
|
version: 'next',
|
||||||
|
permalink: 'docs/en/next/doc2.html',
|
||||||
|
localized_id: 'doc2',
|
||||||
|
language: 'en',
|
||||||
|
sidebar: 'docs',
|
||||||
|
category: 'Test',
|
||||||
|
previous_id: 'doc1',
|
||||||
|
previous: 'en-doc1',
|
||||||
|
previous_title: 'Document 1',
|
||||||
|
sub_category: 'Sub Cat 1',
|
||||||
|
sort: 2,
|
||||||
|
},
|
||||||
|
'en-doc3': {
|
||||||
|
id: 'en-doc3',
|
||||||
|
title: 'Document 3',
|
||||||
|
source: 'doc3.md',
|
||||||
|
version: 'next',
|
||||||
|
permalink: 'docs/en/next/doc3.html',
|
||||||
|
localized_id: 'doc3',
|
||||||
|
language: 'en',
|
||||||
|
sidebar: 'docs',
|
||||||
|
category: 'Test',
|
||||||
|
previous_id: 'doc2',
|
||||||
|
previous: 'en-doc2',
|
||||||
|
previous_title: 'Document 2',
|
||||||
|
sub_category: 'Sub Cat 2',
|
||||||
|
sort: 3,
|
||||||
|
},
|
||||||
|
'en-doc4': {
|
||||||
|
id: 'en-doc4',
|
||||||
|
title: 'Document 4',
|
||||||
|
source: 'doc4.md',
|
||||||
|
version: 'next',
|
||||||
|
permalink: 'docs/en/next/doc4.html',
|
||||||
|
localized_id: 'doc4',
|
||||||
|
language: 'en',
|
||||||
|
sidebar: 'docs',
|
||||||
|
category: 'Test 2',
|
||||||
|
previous_id: 'doc3',
|
||||||
|
previous: 'en-doc3',
|
||||||
|
previous_title: 'Document 3',
|
||||||
|
sort: 4,
|
||||||
|
},
|
||||||
|
};
|
|
@ -12,6 +12,7 @@ module.exports = {
|
||||||
next_id: 'doc2',
|
next_id: 'doc2',
|
||||||
next: 'en-doc2',
|
next: 'en-doc2',
|
||||||
next_title: 'Document 2',
|
next_title: 'Document 2',
|
||||||
|
sort: 1,
|
||||||
},
|
},
|
||||||
'en-doc2': {
|
'en-doc2': {
|
||||||
id: 'en-doc2',
|
id: 'en-doc2',
|
||||||
|
@ -26,6 +27,22 @@ module.exports = {
|
||||||
previous_id: 'doc1',
|
previous_id: 'doc1',
|
||||||
previous: 'en-doc1',
|
previous: 'en-doc1',
|
||||||
previous_title: 'Document 1',
|
previous_title: 'Document 1',
|
||||||
|
sort: 2,
|
||||||
|
},
|
||||||
|
'en-doc3': {
|
||||||
|
id: 'en-doc3',
|
||||||
|
title: 'Document 3',
|
||||||
|
source: 'doc3.md',
|
||||||
|
version: 'next',
|
||||||
|
permalink: 'docs/en/next/doc3.html',
|
||||||
|
localized_id: 'doc3',
|
||||||
|
language: 'en',
|
||||||
|
sidebar: 'docs',
|
||||||
|
category: 'Test 2',
|
||||||
|
previous_id: 'doc2',
|
||||||
|
previous: 'en-doc2',
|
||||||
|
previous_title: 'Document 2',
|
||||||
|
sort: 3,
|
||||||
},
|
},
|
||||||
'ko-doc1': {
|
'ko-doc1': {
|
||||||
id: 'ko-doc1',
|
id: 'ko-doc1',
|
||||||
|
|
11
lib/server/__tests__/__fixtures__/sidebar-subcategories.js
Normal file
11
lib/server/__tests__/__fixtures__/sidebar-subcategories.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
docs: {
|
||||||
|
'First Category': {
|
||||||
|
'Sub Cat One': ['doc2', 'doc1'],
|
||||||
|
'Sub Cat Two': ['doc3', 'doc5'],
|
||||||
|
},
|
||||||
|
'Second Category': {
|
||||||
|
Hello: ['doc4'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
6
lib/server/__tests__/__fixtures__/sidebar.js
Normal file
6
lib/server/__tests__/__fixtures__/sidebar.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
docs: {
|
||||||
|
'First Category': ['doc1', 'doc2'],
|
||||||
|
'Second Category': ['doc4', 'doc3'],
|
||||||
|
},
|
||||||
|
};
|
91
lib/server/__tests__/readCategories.test.js
Normal file
91
lib/server/__tests__/readCategories.test.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2017-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const readCategories = require('../readCategories');
|
||||||
|
const generalMetadata = require('./__fixtures__/metadata.js');
|
||||||
|
const subCategoryMetadata = require('./__fixtures__/metadata-subcategories.js');
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
name: 'English',
|
||||||
|
tag: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
name: 'Foo',
|
||||||
|
tag: 'ko',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const languagesMultiple = [
|
||||||
|
{
|
||||||
|
enabled: false,
|
||||||
|
name: 'English',
|
||||||
|
tag: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
name: 'Foo',
|
||||||
|
tag: 'ko',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('readCategories', () => {
|
||||||
|
test('should return proper categories and their pages', () => {
|
||||||
|
const categories = readCategories('docs', generalMetadata, languages);
|
||||||
|
|
||||||
|
expect(categories.en).toBeDefined();
|
||||||
|
expect(categories.en.length).toBe(2);
|
||||||
|
|
||||||
|
expect(categories.en[0].name).toBe('Test');
|
||||||
|
expect(categories.en[0].links.length).toBe(2);
|
||||||
|
expect(categories.en[0].links[0].id).toBe('en-doc1');
|
||||||
|
expect(categories.en[0].links[1].id).toBe('en-doc2');
|
||||||
|
|
||||||
|
expect(categories.en[1].name).toBe('Test 2');
|
||||||
|
expect(categories.en[1].links.length).toBe(1);
|
||||||
|
expect(categories.en[1].links[0].id).toBe('en-doc3');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return proper data with categories and sub categories', () => {
|
||||||
|
const categories = readCategories('docs', subCategoryMetadata, languages);
|
||||||
|
|
||||||
|
expect(categories.en).toBeDefined();
|
||||||
|
expect(categories.ko).toBeDefined();
|
||||||
|
expect(categories.en.length).toBe(2);
|
||||||
|
|
||||||
|
expect(categories.en[0].name).toBe('Test');
|
||||||
|
expect(categories.en[0].links.length).toBe(0);
|
||||||
|
expect(categories.en[0].sub_categories.length).toBe(2);
|
||||||
|
|
||||||
|
expect(categories.en[0].sub_categories[0].name).toBe('Sub Cat 1');
|
||||||
|
expect(categories.en[0].sub_categories[0].links.length).toBe(2);
|
||||||
|
expect(categories.en[0].sub_categories[0].links[0].id).toBe('en-doc1');
|
||||||
|
expect(categories.en[0].sub_categories[0].links[1].id).toBe('en-doc2');
|
||||||
|
|
||||||
|
expect(categories.en[0].sub_categories[1].name).toBe('Sub Cat 2');
|
||||||
|
expect(categories.en[0].sub_categories[1].links.length).toBe(1);
|
||||||
|
expect(categories.en[0].sub_categories[1].links[0].id).toBe('en-doc3');
|
||||||
|
|
||||||
|
expect(categories.en[1].name).toBe('Test 2');
|
||||||
|
expect(categories.en[1].links.length).toBe(1);
|
||||||
|
expect(categories.en[1].links[0].id).toBe('en-doc4');
|
||||||
|
expect(categories.en[1].sub_categories).not.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return proper languages when not enabled', () => {
|
||||||
|
const categories = readCategories(
|
||||||
|
'docs',
|
||||||
|
generalMetadata,
|
||||||
|
languagesMultiple
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(categories.en).not.toBeDefined();
|
||||||
|
expect(categories.ko).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
77
lib/server/__tests__/readMetadata.test.js
Normal file
77
lib/server/__tests__/readMetadata.test.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2017-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const {readSidebar} = require('../readMetadata');
|
||||||
|
const sidebar = require('./__fixtures__/sidebar');
|
||||||
|
const sidebarSubCategories = require('./__fixtures__/sidebar-subcategories');
|
||||||
|
|
||||||
|
jest.mock('../env', () => ({
|
||||||
|
translation: {
|
||||||
|
enabled: true,
|
||||||
|
enabledLanguages: () => [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
name: 'English',
|
||||||
|
tag: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
name: '한국어',
|
||||||
|
tag: 'ko',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
versioning: {
|
||||||
|
enabled: true,
|
||||||
|
defaultVersion: '1.0.0',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock(`${process.cwd()}/siteConfig.js`, () => true, {virtual: true});
|
||||||
|
jest.mock(`${process.cwd()}/sidebar.json`, () => true, {virtual: true});
|
||||||
|
|
||||||
|
describe('readMetadata', () => {
|
||||||
|
describe('readSidebar', () => {
|
||||||
|
it('should verify regular category data and verify sort', () => {
|
||||||
|
const order = readSidebar(sidebar);
|
||||||
|
|
||||||
|
// Put in this order to verify sort
|
||||||
|
['doc1', 'doc2', 'doc4', 'doc3'].forEach((id, index) => {
|
||||||
|
expect(order[id]).toBeDefined();
|
||||||
|
expect(order[id].sort).toBe(index + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(order.doc1.previous).toBeUndefined();
|
||||||
|
expect(order.doc2.previous).toBe('doc1');
|
||||||
|
|
||||||
|
expect(order.doc1.next).toBe('doc2');
|
||||||
|
expect(order.doc2.next).toBe('doc4');
|
||||||
|
|
||||||
|
expect(order.doc1.sub_category).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should verify sub category data and verify sort', () => {
|
||||||
|
const order = readSidebar(sidebarSubCategories);
|
||||||
|
|
||||||
|
// Put in this order to verify sort
|
||||||
|
['doc2', 'doc1', 'doc3', 'doc5', 'doc4'].forEach((id, index) => {
|
||||||
|
expect(order[id]).toBeDefined();
|
||||||
|
expect(order[id].sort).toBe(index + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(order.doc2.sidebar).toBe('docs');
|
||||||
|
expect(order.doc2.category).toBe('First Category');
|
||||||
|
expect(order.doc2.sub_category).toBe('Sub Cat One');
|
||||||
|
|
||||||
|
expect(order.doc1.category).toBe('First Category');
|
||||||
|
expect(order.doc1.sub_category).toBe('Sub Cat One');
|
||||||
|
|
||||||
|
expect(order.doc3.category).toBe('First Category');
|
||||||
|
expect(order.doc3.sub_category).toBe('Sub Cat Two');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -88,4 +88,63 @@ describe('server utils', () => {
|
||||||
expect(utils.getSubDir(docE, docsDir)).toBeNull();
|
expect(utils.getSubDir(docE, docsDir)).toBeNull();
|
||||||
expect(utils.getSubDir(docE, translatedDir)).toEqual('lol/lah');
|
expect(utils.getSubDir(docE, translatedDir)).toEqual('lol/lah');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('validateSidebar', () => {
|
||||||
|
test('should throw an error for invalid pages', () => {
|
||||||
|
const metadata = {
|
||||||
|
id: 'doc1',
|
||||||
|
sidebar: 'docs',
|
||||||
|
next_id: 'doc2',
|
||||||
|
next: 'doc2',
|
||||||
|
};
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
doc1: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
utils.validateSidebar(metadata, pages);
|
||||||
|
}).toThrow(
|
||||||
|
`Improper sidebars.json file, document with id 'doc2' not found. Make sure that documents with the ids specified in sidebars.json exist and that no ids are repeated.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw an error for invalid version pages', () => {
|
||||||
|
const metadata = {
|
||||||
|
id: 'doc1',
|
||||||
|
version: 'foo',
|
||||||
|
sidebar: 'docs',
|
||||||
|
next_id: 'doc2',
|
||||||
|
next: 'doc2',
|
||||||
|
};
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
doc1: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
utils.validateSidebar(metadata, pages);
|
||||||
|
}).toThrow(
|
||||||
|
`Improper sidebars file for version foo, document with id 'doc2' not found. Make sure that all documents with ids specified in this version's sidebar file exist and that no ids are repeated.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pass validate', () => {
|
||||||
|
const metadata = {
|
||||||
|
id: 'doc1',
|
||||||
|
sidebar: 'docs',
|
||||||
|
next_id: 'doc2',
|
||||||
|
next: 'doc2',
|
||||||
|
};
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
doc1: {},
|
||||||
|
doc2: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
utils.validateSidebar(metadata, pages);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,101 +5,112 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs');
|
const {validateSidebar} = require('./utils');
|
||||||
|
|
||||||
const Metadata = require('../core/metadata.js');
|
|
||||||
|
|
||||||
const CWD = process.cwd();
|
|
||||||
let languages;
|
|
||||||
if (fs.existsSync(`${CWD}/languages.js`)) {
|
|
||||||
languages = require(`${CWD}/languages.js`);
|
|
||||||
} else {
|
|
||||||
languages = [
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
name: 'English',
|
|
||||||
tag: 'en',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns data broken up into categories for a sidebar
|
// returns data broken up into categories for a sidebar
|
||||||
function readCategories(sidebar) {
|
function readCategories(sidebar, allMetadata, languages) {
|
||||||
const enabledLanguages = languages
|
const enabledLanguages = languages
|
||||||
.filter(lang => lang.enabled)
|
.filter(lang => lang.enabled)
|
||||||
.map(lang => lang.tag);
|
.map(lang => lang.tag);
|
||||||
|
|
||||||
const allCategories = {};
|
const allCategories = {};
|
||||||
|
|
||||||
|
// Go through each language that might be defined
|
||||||
for (let k = 0; k < enabledLanguages.length; ++k) {
|
for (let k = 0; k < enabledLanguages.length; ++k) {
|
||||||
const language = enabledLanguages[k];
|
const language = enabledLanguages[k];
|
||||||
|
|
||||||
const metadatas = [];
|
const metadatas = [];
|
||||||
Object.keys(Metadata).forEach(id => {
|
const categories = [];
|
||||||
const metadata = Metadata[id];
|
const pages = {};
|
||||||
|
|
||||||
|
// Get the metadata for the current sidebar
|
||||||
|
Object.keys(allMetadata).forEach(id => {
|
||||||
|
const metadata = allMetadata[id];
|
||||||
if (metadata.sidebar === sidebar && metadata.language === language) {
|
if (metadata.sidebar === sidebar && metadata.language === language) {
|
||||||
metadatas.push(metadata);
|
metadatas.push(metadata);
|
||||||
|
pages[metadata.id] = metadata;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build a hashmap of article_id -> metadata
|
// Sort the metadata
|
||||||
const articles = {};
|
metadatas.sort((a, b) => a.sort - b.sort);
|
||||||
|
|
||||||
|
// Store the correct sort of categories and sub categories for later
|
||||||
|
const sortedCategories = [];
|
||||||
|
const sortedSubCategories = [];
|
||||||
for (let i = 0; i < metadatas.length; ++i) {
|
for (let i = 0; i < metadatas.length; ++i) {
|
||||||
const metadata = metadatas[i];
|
const metadata = metadatas[i];
|
||||||
articles[metadata.id] = metadata;
|
const category = metadata.category;
|
||||||
|
const subCategory = metadata.sub_category;
|
||||||
|
|
||||||
|
if (!sortedCategories.includes(category)) {
|
||||||
|
sortedCategories.push(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subCategory && !sortedSubCategories.includes(subCategory)) {
|
||||||
|
sortedSubCategories.push(subCategory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a hashmap of article_id -> previous_id
|
// Index categories and sub categories with all of their documents
|
||||||
const previous = {};
|
const indexedCategories = {};
|
||||||
for (let i = 0; i < metadatas.length; ++i) {
|
const indexedSubCategories = {};
|
||||||
|
for (let i = 0; i < metadatas.length; i++) {
|
||||||
const metadata = metadatas[i];
|
const metadata = metadatas[i];
|
||||||
if (metadata.next) {
|
const category = metadata.category;
|
||||||
if (!articles[metadata.next]) {
|
const subCategory = metadata.sub_category;
|
||||||
throw new Error(
|
|
||||||
metadata.version
|
// Validate pages in the sidebar
|
||||||
? `Improper sidebars file for version ${
|
validateSidebar(metadata, pages);
|
||||||
metadata.version
|
|
||||||
}, document with id '${
|
if (!indexedCategories[category]) {
|
||||||
metadata.next
|
indexedCategories[category] = [];
|
||||||
}' not found. Make sure that all documents with ids specified in this version's sidebar file exist and that no ids are repeated.`
|
}
|
||||||
: `Improper sidebars.json file, document with id '${
|
|
||||||
metadata.next
|
if (!subCategory) {
|
||||||
}' not found. Make sure that documents with the ids specified in sidebars.json exist and that no ids are repeated.`
|
indexedCategories[category].push(metadata);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (subCategory) {
|
||||||
|
if (!indexedSubCategories[category]) {
|
||||||
|
indexedSubCategories[category] = {};
|
||||||
}
|
}
|
||||||
previous[articles[metadata.next].id] = metadata.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the first element which doesn't have any previous
|
if (!indexedSubCategories[category][subCategory]) {
|
||||||
let first = null;
|
indexedSubCategories[category][subCategory] = [];
|
||||||
for (let i = 0; i < metadatas.length; ++i) {
|
|
||||||
const metadata = metadatas[i];
|
|
||||||
if (!previous[metadata.id]) {
|
|
||||||
first = metadata;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const categories = [];
|
|
||||||
let currentCategory = null;
|
|
||||||
|
|
||||||
let metadata = first;
|
|
||||||
let i = 0;
|
|
||||||
while (metadata && i++ < 1000) {
|
|
||||||
if (!currentCategory || metadata.category !== currentCategory.name) {
|
|
||||||
if (currentCategory) {
|
|
||||||
categories.push(currentCategory);
|
|
||||||
}
|
}
|
||||||
currentCategory = {
|
|
||||||
name: metadata.category,
|
indexedSubCategories[category][subCategory].push(metadata);
|
||||||
links: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
currentCategory.links.push(metadata);
|
|
||||||
metadata = articles[metadata.next];
|
|
||||||
}
|
}
|
||||||
categories.push(currentCategory);
|
|
||||||
|
// Generate data for each category and sub categories
|
||||||
|
for (let i = 0; i < sortedCategories.length; i++) {
|
||||||
|
const category = sortedCategories[i];
|
||||||
|
const currentCategory = {
|
||||||
|
name: category,
|
||||||
|
links: indexedCategories[category],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let ii = 0; ii < sortedSubCategories.length; ii++) {
|
||||||
|
const subCategory = sortedSubCategories[ii];
|
||||||
|
|
||||||
|
if (
|
||||||
|
indexedSubCategories[category] &&
|
||||||
|
indexedSubCategories[category][subCategory]
|
||||||
|
) {
|
||||||
|
if (!currentCategory.sub_categories) {
|
||||||
|
currentCategory.sub_categories = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCategory.sub_categories.push({
|
||||||
|
name: subCategory,
|
||||||
|
links: indexedSubCategories[category][subCategory],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
categories.push(currentCategory);
|
||||||
|
}
|
||||||
|
|
||||||
allCategories[language] = categories;
|
allCategories[language] = categories;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,13 @@ const SupportedHeaderFields = new Set([
|
||||||
'custom_edit_url',
|
'custom_edit_url',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
let allSidebars;
|
||||||
|
if (fs.existsSync(`${CWD}/sidebars.json`)) {
|
||||||
|
allSidebars = require(`${CWD}/sidebars.json`);
|
||||||
|
} else {
|
||||||
|
allSidebars = {};
|
||||||
|
}
|
||||||
|
|
||||||
// Can have a custom docs path. Top level folder still needs to be in directory
|
// Can have a custom docs path. Top level folder still needs to be in directory
|
||||||
// at the same level as `website`, not inside `website`.
|
// at the same level as `website`, not inside `website`.
|
||||||
// e.g., docs/whereDocsReallyExist
|
// e.g., docs/whereDocsReallyExist
|
||||||
|
@ -42,27 +49,36 @@ const SupportedHeaderFields = new Set([
|
||||||
function getDocsPath() {
|
function getDocsPath() {
|
||||||
return siteConfig.customDocsPath ? siteConfig.customDocsPath : 'docs';
|
return siteConfig.customDocsPath ? siteConfig.customDocsPath : 'docs';
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns map from id to object containing sidebar ordering info
|
// returns map from id to object containing sidebar ordering info
|
||||||
function readSidebar() {
|
function readSidebar(sidebars = {}) {
|
||||||
let allSidebars;
|
Object.assign(sidebars, versionFallback.sidebarData());
|
||||||
if (fs.existsSync(`${CWD}/sidebars.json`)) {
|
|
||||||
allSidebars = require(`${CWD}/sidebars.json`);
|
|
||||||
} else {
|
|
||||||
allSidebars = {};
|
|
||||||
}
|
|
||||||
Object.assign(allSidebars, versionFallback.sidebarData());
|
|
||||||
|
|
||||||
const order = {};
|
const order = {};
|
||||||
|
|
||||||
Object.keys(allSidebars).forEach(sidebar => {
|
Object.keys(sidebars).forEach(sidebar => {
|
||||||
const categories = allSidebars[sidebar];
|
const categories = sidebars[sidebar];
|
||||||
|
|
||||||
let ids = [];
|
let ids = [];
|
||||||
const categoryOrder = [];
|
const categoryOrder = [];
|
||||||
|
const subCategoryOrder = [];
|
||||||
Object.keys(categories).forEach(category => {
|
Object.keys(categories).forEach(category => {
|
||||||
ids = ids.concat(categories[category]);
|
if (Array.isArray(categories[category])) {
|
||||||
for (let i = 0; i < categories[category].length; i++) {
|
ids = ids.concat(categories[category]);
|
||||||
categoryOrder.push(category);
|
|
||||||
|
for (let i = 0; i < categories[category].length; i++) {
|
||||||
|
categoryOrder.push(category);
|
||||||
|
subCategoryOrder.push('');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.keys(categories[category]).forEach(subCategory => {
|
||||||
|
ids = ids.concat(categories[category][subCategory]);
|
||||||
|
|
||||||
|
for (let i = 0; i < categories[category][subCategory].length; i++) {
|
||||||
|
categoryOrder.push(category);
|
||||||
|
subCategoryOrder.push(subCategory);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,16 +86,22 @@ function readSidebar() {
|
||||||
const id = ids[i];
|
const id = ids[i];
|
||||||
let previous;
|
let previous;
|
||||||
let next;
|
let next;
|
||||||
|
|
||||||
if (i > 0) previous = ids[i - 1];
|
if (i > 0) previous = ids[i - 1];
|
||||||
|
|
||||||
if (i < ids.length - 1) next = ids[i + 1];
|
if (i < ids.length - 1) next = ids[i + 1];
|
||||||
|
|
||||||
order[id] = {
|
order[id] = {
|
||||||
previous,
|
previous,
|
||||||
next,
|
next,
|
||||||
sidebar,
|
sidebar,
|
||||||
category: categoryOrder[i],
|
category: categoryOrder[i],
|
||||||
|
sub_category: subCategoryOrder[i],
|
||||||
|
sort: i + 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,12 +161,14 @@ function processMetadata(file, refDir) {
|
||||||
metadata.id = (env.translation.enabled ? `${language}-` : '') + metadata.id;
|
metadata.id = (env.translation.enabled ? `${language}-` : '') + metadata.id;
|
||||||
metadata.language = env.translation.enabled ? language : 'en';
|
metadata.language = env.translation.enabled ? language : 'en';
|
||||||
|
|
||||||
const order = readSidebar();
|
const order = readSidebar(allSidebars);
|
||||||
const id = metadata.localized_id;
|
const id = metadata.localized_id;
|
||||||
|
|
||||||
if (order[id]) {
|
if (order[id]) {
|
||||||
metadata.sidebar = order[id].sidebar;
|
metadata.sidebar = order[id].sidebar;
|
||||||
metadata.category = order[id].category;
|
metadata.category = order[id].category;
|
||||||
|
metadata.sub_category = order[id].sub_category;
|
||||||
|
metadata.sort = order[id].sort;
|
||||||
|
|
||||||
if (order[id].next) {
|
if (order[id].next) {
|
||||||
metadata.next_id = order[id].next;
|
metadata.next_id = order[id].next;
|
||||||
|
@ -165,7 +189,7 @@ function processMetadata(file, refDir) {
|
||||||
function generateMetadataDocs() {
|
function generateMetadataDocs() {
|
||||||
let order;
|
let order;
|
||||||
try {
|
try {
|
||||||
order = readSidebar();
|
order = readSidebar(allSidebars);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -248,6 +272,9 @@ function generateMetadataDocs() {
|
||||||
if (order[id]) {
|
if (order[id]) {
|
||||||
metadata.sidebar = order[id].sidebar;
|
metadata.sidebar = order[id].sidebar;
|
||||||
metadata.category = order[id].category;
|
metadata.category = order[id].category;
|
||||||
|
metadata.sub_category = order[id].sub_category;
|
||||||
|
metadata.sort = order[id].sort;
|
||||||
|
|
||||||
if (order[id].next) {
|
if (order[id].next) {
|
||||||
metadata.next_id = order[id].next.replace(
|
metadata.next_id = order[id].next.replace(
|
||||||
`version-${metadata.version}-`,
|
`version-${metadata.version}-`,
|
||||||
|
|
|
@ -64,10 +64,30 @@ function autoPrefixCss(cssContent) {
|
||||||
.then(result => result.css);
|
.then(result => result.css);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the docs in the sidebar are valid
|
||||||
|
function validateSidebar(metadata, pages) {
|
||||||
|
if (metadata.next) {
|
||||||
|
if (!pages[metadata.next]) {
|
||||||
|
throw new Error(
|
||||||
|
metadata.version
|
||||||
|
? `Improper sidebars file for version ${
|
||||||
|
metadata.version
|
||||||
|
}, document with id '${
|
||||||
|
metadata.next
|
||||||
|
}' not found. Make sure that all documents with ids specified in this version's sidebar file exist and that no ids are repeated.`
|
||||||
|
: `Improper sidebars.json file, document with id '${
|
||||||
|
metadata.next
|
||||||
|
}' not found. Make sure that documents with the ids specified in sidebars.json exist and that no ids are repeated.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getSubDir,
|
getSubDir,
|
||||||
getLanguage,
|
getLanguage,
|
||||||
isSeparateCss,
|
isSeparateCss,
|
||||||
minifyCss,
|
minifyCss,
|
||||||
autoPrefixCss,
|
autoPrefixCss,
|
||||||
|
validateSidebar,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1718,7 +1718,12 @@ input::placeholder {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc .toggleNav .navGroup .navGroupCategoryTitle {
|
.toc .toggleNav .subNavGroup {
|
||||||
|
margin-top : 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc .toggleNav .navGroup .navGroupCategoryTitle,
|
||||||
|
.toc .toggleNav .navGroup .navGroupSubCategoryTitle {
|
||||||
color: #393939;
|
color: #393939;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -1727,6 +1732,10 @@ input::placeholder {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toc .toggleNav .navGroup .navGroupSubCategoryTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.toc .toggleNav .navGroup .navListItem {
|
.toc .toggleNav .navGroup .navListItem {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue