fix: change subcategory format (#1026)

* fix: change subcategory format

* Fix sidebars

* Refactor

* Fix implementation

* Change format
This commit is contained in:
Yangshun Tay 2018-10-10 08:33:20 -07:00 committed by GitHub
parent c277f46a60
commit fe500dea82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 493 additions and 15047 deletions

View file

@ -12,7 +12,7 @@ module.exports = {
next_id: 'doc2',
next: 'en-doc2',
next_title: 'Document 2',
sub_category: 'Sub Cat 1',
subcategory: 'Sub Cat 1',
sort: 1,
},
'en-doc2': {
@ -28,7 +28,7 @@ module.exports = {
previous_id: 'doc1',
previous: 'en-doc1',
previous_title: 'Document 1',
sub_category: 'Sub Cat 1',
subcategory: 'Sub Cat 1',
sort: 2,
},
'en-doc3': {
@ -44,7 +44,7 @@ module.exports = {
previous_id: 'doc2',
previous: 'en-doc2',
previous_title: 'Document 2',
sub_category: 'Sub Cat 2',
subcategory: 'Sub Cat 2',
sort: 3,
},
'en-doc4': {

View file

@ -1,11 +1,26 @@
module.exports = {
docs: {
'First Category': {
'Sub Cat One': ['doc2', 'doc1'],
'Sub Cat Two': ['doc3', 'doc5'],
},
'Second Category': {
Hello: ['doc4'],
},
'First Category': ['doc1', 'doc2'],
'Second Category': [
'doc3',
{
type: 'subcategory',
label: 'First Subcategory',
ids: ['doc4'],
},
'doc5',
],
'Third Category': [
{
type: 'subcategory',
label: 'Second Subcategory',
ids: ['doc6', 'doc7'],
},
{
type: 'subcategory',
label: 'Third Subcategory',
ids: ['doc8'],
},
],
},
};

View file

@ -0,0 +1,176 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`readCategories should return proper categories and their pages 1`] = `
Array [
Object {
"children": Array [
Object {
"item": Object {
"category": "Test",
"id": "en-doc1",
"language": "en",
"localized_id": "doc1",
"next": "en-doc2",
"next_id": "doc2",
"next_title": "Document 2",
"permalink": "docs/en/next/doc1.html",
"sidebar": "docs",
"sort": 1,
"source": "doc1.md",
"title": "Document 1",
"version": "next",
},
"type": "LINK",
},
Object {
"item": Object {
"category": "Test",
"id": "en-doc2",
"language": "en",
"localized_id": "doc2",
"permalink": "docs/en/next/doc2.html",
"previous": "en-doc1",
"previous_id": "doc1",
"previous_title": "Document 1",
"sidebar": "docs",
"sort": 2,
"source": "doc2.md",
"title": "Document 2",
"version": "next",
},
"type": "LINK",
},
],
"title": "Test",
"type": "CATEGORY",
},
Object {
"children": Array [
Object {
"item": Object {
"category": "Test 2",
"id": "en-doc3",
"language": "en",
"localized_id": "doc3",
"permalink": "docs/en/next/doc3.html",
"previous": "en-doc2",
"previous_id": "doc2",
"previous_title": "Document 2",
"sidebar": "docs",
"sort": 3,
"source": "doc3.md",
"title": "Document 3",
"version": "next",
},
"type": "LINK",
},
],
"title": "Test 2",
"type": "CATEGORY",
},
]
`;
exports[`readCategories should return proper data with categories and sub categories 1`] = `
Array [
Object {
"children": Array [
Object {
"children": Array [
Object {
"item": Object {
"category": "Test",
"id": "en-doc1",
"language": "en",
"localized_id": "doc1",
"next": "en-doc2",
"next_id": "doc2",
"next_title": "Document 2",
"permalink": "docs/en/next/doc1.html",
"sidebar": "docs",
"sort": 1,
"source": "doc1.md",
"subcategory": "Sub Cat 1",
"title": "Document 1",
"version": "next",
},
"type": "LINK",
},
Object {
"item": Object {
"category": "Test",
"id": "en-doc2",
"language": "en",
"localized_id": "doc2",
"permalink": "docs/en/next/doc2.html",
"previous": "en-doc1",
"previous_id": "doc1",
"previous_title": "Document 1",
"sidebar": "docs",
"sort": 2,
"source": "doc2.md",
"subcategory": "Sub Cat 1",
"title": "Document 2",
"version": "next",
},
"type": "LINK",
},
],
"title": "Sub Cat 1",
"type": "SUBCATEGORY",
},
Object {
"children": Array [
Object {
"item": Object {
"category": "Test",
"id": "en-doc3",
"language": "en",
"localized_id": "doc3",
"permalink": "docs/en/next/doc3.html",
"previous": "en-doc2",
"previous_id": "doc2",
"previous_title": "Document 2",
"sidebar": "docs",
"sort": 3,
"source": "doc3.md",
"subcategory": "Sub Cat 2",
"title": "Document 3",
"version": "next",
},
"type": "LINK",
},
],
"title": "Sub Cat 2",
"type": "SUBCATEGORY",
},
],
"title": "Test",
"type": "CATEGORY",
},
Object {
"children": Array [
Object {
"item": Object {
"category": "Test 2",
"id": "en-doc4",
"language": "en",
"localized_id": "doc4",
"permalink": "docs/en/next/doc4.html",
"previous": "en-doc3",
"previous_id": "doc3",
"previous_title": "Document 3",
"sidebar": "docs",
"sort": 4,
"source": "doc4.md",
"title": "Document 4",
"version": "next",
},
"type": "LINK",
},
],
"title": "Test 2",
"type": "CATEGORY",
},
]
`;

View file

@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`readMetadata readSidebar should verify sub category data and verify order 1`] = `
Object {
"doc1": Object {
"category": "First Category",
"next": "doc2",
"order": 1,
"previous": null,
"sidebar": "docs",
"subcategory": null,
},
"doc2": Object {
"category": "First Category",
"next": "doc3",
"order": 2,
"previous": "doc1",
"sidebar": "docs",
"subcategory": null,
},
"doc3": Object {
"category": "Second Category",
"next": "doc4",
"order": 3,
"previous": "doc2",
"sidebar": "docs",
"subcategory": null,
},
"doc4": Object {
"category": "Second Category",
"next": "doc5",
"order": 4,
"previous": "doc3",
"sidebar": "docs",
"subcategory": "First Subcategory",
},
"doc5": Object {
"category": "Second Category",
"next": "doc6",
"order": 5,
"previous": "doc4",
"sidebar": "docs",
"subcategory": null,
},
"doc6": Object {
"category": "Third Category",
"next": "doc7",
"order": 6,
"previous": "doc5",
"sidebar": "docs",
"subcategory": "Second Subcategory",
},
"doc7": Object {
"category": "Third Category",
"next": "doc8",
"order": 7,
"previous": "doc6",
"sidebar": "docs",
"subcategory": "Second Subcategory",
},
"doc8": Object {
"category": "Third Category",
"next": null,
"order": 8,
"previous": "doc7",
"sidebar": "docs",
"subcategory": "Third Subcategory",
},
}
`;

View file

@ -41,15 +41,7 @@ describe('readCategories', () => {
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');
expect(categories.en).toMatchSnapshot();
});
test('should return proper data with categories and sub categories', () => {
@ -58,24 +50,7 @@ describe('readCategories', () => {
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();
expect(categories.en).toMatchSnapshot();
});
test('should return proper languages when not enabled', () => {

View file

@ -6,8 +6,7 @@
*/
const {readSidebar} = require('../readMetadata');
const sidebar = require('./__fixtures__/sidebar');
const sidebarSubCategories = require('./__fixtures__/sidebar-subcategories');
const sidebarSubcategories = require('./__fixtures__/sidebar-subcategories');
jest.mock('../env', () => ({
translation: {
@ -36,42 +35,9 @@ 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');
test('should verify sub category data and verify order', () => {
const items = readSidebar(sidebarSubcategories);
expect(items).toMatchSnapshot();
});
});
});

View file

@ -88,63 +88,4 @@ describe('server utils', () => {
expect(utils.getSubDir(docE, docsDir)).toBeNull();
expect(utils.getSubDir(docE, translatedDir)).toEqual('lol/lah');
});
describe('validateSidebar', () => {
test('should throw an error for invalid sidebarMetadatas', () => {
const metadata = {
id: 'doc1',
sidebar: 'docs',
next_id: 'doc2',
next: 'doc2',
};
const sidebarMetadatas = {
doc1: {},
};
expect(() => {
utils.validateSidebar(metadata, sidebarMetadatas);
}).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 sidebarMetadatas', () => {
const metadata = {
id: 'doc1',
version: 'foo',
sidebar: 'docs',
next_id: 'doc2',
next: 'doc2',
};
const sidebarMetadatas = {
doc1: {},
};
expect(() => {
utils.validateSidebar(metadata, sidebarMetadatas);
}).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 sidebarMetadatas = {
doc1: {},
doc2: {},
};
expect(() => {
utils.validateSidebar(metadata, sidebarMetadatas);
}).not.toThrow();
});
});
});

View file

@ -5,115 +5,82 @@
* LICENSE file in the root directory of this source tree.
*/
const {validateSidebar} = require('./utils');
const _ = require('lodash');
// returns data broken up into categories for a sidebar
function readCategories(sidebar, allMetadata, languages) {
const enabledLanguages = languages
.filter(lang => lang.enabled)
.map(lang => lang.tag);
const allCategories = {};
// Go through each language that might be defined
for (let k = 0; k < enabledLanguages.length; ++k) {
const language = enabledLanguages[k];
const metadatas = [];
const categories = [];
const sidebarMetadatas = {};
// Go through each language that might be defined.
languages
.filter(lang => lang.enabled)
.map(lang => lang.tag)
.forEach(language => {
// Get all related metadata for the current sidebar and specific to the language.
const metadatas = Object.values(allMetadata)
.filter(
metadata =>
metadata.sidebar === sidebar && metadata.language === language,
)
.sort((a, b) => a.order - b.order);
// Get all related metadata for the current sidebar
Object.keys(allMetadata).forEach(id => {
const metadata = allMetadata[id];
if (metadata.sidebar === sidebar && metadata.language === language) {
metadatas.push(metadata);
sidebarMetadatas[metadata.id] = metadata;
}
});
// Define the correct order of categories.
const sortedCategories = _.uniq(
metadatas.map(metadata => metadata.category),
);
// Sort the metadata
metadatas.sort((a, b) => a.sort - b.sort);
const metadatasGroupedByCategory = _.chain(metadatas)
.groupBy(metadata => metadata.category)
.mapValues(categoryItems => {
// Process subcategories.
const metadatasGroupedBySubcategory = _.groupBy(
categoryItems,
item => item.subcategory,
);
const result = [];
const seenSubcategories = new Set();
// categoryItems can be links or subcategories. Handle separately.
categoryItems.forEach(item => {
// Has no subcategory.
if (item.subcategory == null) {
result.push({
type: 'LINK',
item,
});
return;
}
// Store the correct sort of categories and sub categories for later
const sortedCategories = [];
const sortedSubCategories = [];
for (let i = 0; i < metadatas.length; ++i) {
const metadata = metadatas[i];
const category = metadata.category;
const subCategory = metadata.sub_category;
const {subcategory} = item;
// Subcategory has been processed, we can skip it.
if (seenSubcategories.has(subcategory)) {
return;
}
if (!sortedCategories.includes(category)) {
sortedCategories.push(category);
}
if (subCategory && !sortedSubCategories.includes(subCategory)) {
sortedSubCategories.push(subCategory);
}
}
// Index categories and sub categories with all of their documents
const indexedCategories = {};
const indexedSubCategories = {};
for (let i = 0; i < metadatas.length; i++) {
const metadata = metadatas[i];
const category = metadata.category;
const subCategory = metadata.sub_category;
// Validate sidebarMetadatas in the sidebar
validateSidebar(metadata, sidebarMetadatas);
if (!indexedCategories[category]) {
indexedCategories[category] = [];
}
if (!subCategory) {
indexedCategories[category].push(metadata);
}
if (subCategory) {
if (!indexedSubCategories[category]) {
indexedSubCategories[category] = {};
}
if (!indexedSubCategories[category][subCategory]) {
indexedSubCategories[category][subCategory] = [];
}
indexedSubCategories[category][subCategory].push(metadata);
}
}
// 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],
seenSubcategories.add(subcategory);
const subcategoryLinks = metadatasGroupedBySubcategory[
subcategory
].map(subcategoryItem => ({
type: 'LINK',
item: subcategoryItem,
}));
result.push({
type: 'SUBCATEGORY',
title: subcategory,
children: subcategoryLinks,
});
});
}
}
categories.push(currentCategory);
}
return result;
})
.value();
allCategories[language] = categories;
}
const categories = sortedCategories.map(category => ({
type: 'CATEGORY',
title: category,
children: metadatasGroupedByCategory[category],
}));
allCategories[language] = categories;
});
return allCategories;
}

View file

@ -54,55 +54,67 @@ function getDocsPath() {
function readSidebar(sidebars = {}) {
Object.assign(sidebars, versionFallback.sidebarData());
const order = {};
const items = {};
Object.keys(sidebars).forEach(sidebar => {
const categories = sidebars[sidebar];
const sidebarItems = [];
let ids = [];
const categoryOrder = [];
const subCategoryOrder = [];
Object.keys(categories).forEach(category => {
if (Array.isArray(categories[category])) {
ids = ids.concat(categories[category]);
for (let i = 0; i < categories[category].length; i++) {
categoryOrder.push(category);
subCategoryOrder.push(undefined);
}
} 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);
const categoryItems = categories[category];
categoryItems.forEach(categoryItem => {
if (typeof categoryItem === 'object') {
switch (categoryItem.type) {
case 'subcategory':
categoryItem.ids.forEach(subcategoryItem => {
sidebarItems.push({
id: subcategoryItem,
category,
subcategory: categoryItem.label,
order: sidebarItems.length + 1,
});
});
return;
default:
return;
}
}
// Is a regular id value.
sidebarItems.push({
id: categoryItem,
category,
subcategory: null,
order: sidebarItems.length + 1,
});
}
});
});
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
let previous;
let next;
for (let i = 0; i < sidebarItems.length; i++) {
const item = sidebarItems[i];
let previous = null;
let next = null;
if (i > 0) previous = ids[i - 1];
if (i > 0) {
previous = sidebarItems[i - 1].id;
}
if (i < ids.length - 1) next = ids[i + 1];
if (i < sidebarItems.length - 1) {
next = sidebarItems[i + 1].id;
}
order[id] = {
items[item.id] = {
previous,
next,
sidebar,
category: categoryOrder[i],
sub_category: subCategoryOrder[i],
sort: i + 1,
category: item.category,
subcategory: item.subcategory,
order: item.order,
};
}
});
return order;
return items;
}
// process the metadata for a document found in either 'docs' or 'translated_docs'
@ -161,24 +173,24 @@ function processMetadata(file, refDir) {
metadata.id = (env.translation.enabled ? `${language}-` : '') + metadata.id;
metadata.language = env.translation.enabled ? language : 'en';
const order = readSidebar(allSidebars);
const items = readSidebar(allSidebars);
const id = metadata.localized_id;
const item = items[id];
if (item) {
metadata.sidebar = item.sidebar;
metadata.category = item.category;
metadata.subcategory = item.subcategory;
metadata.order = item.order;
if (order[id]) {
metadata.sidebar = order[id].sidebar;
metadata.category = order[id].category;
metadata.sub_category = order[id].sub_category;
metadata.sort = order[id].sort;
if (order[id].next) {
metadata.next_id = order[id].next;
if (item.next) {
metadata.next_id = item.next;
metadata.next =
(env.translation.enabled ? `${language}-` : '') + order[id].next;
(env.translation.enabled ? `${language}-` : '') + item.next;
}
if (order[id].previous) {
metadata.previous_id = order[id].previous;
if (item.previous) {
metadata.previous_id = item.previous;
metadata.previous =
(env.translation.enabled ? `${language}-` : '') + order[id].previous;
(env.translation.enabled ? `${language}-` : '') + item.previous;
}
}
@ -272,8 +284,8 @@ function generateMetadataDocs() {
if (order[id]) {
metadata.sidebar = order[id].sidebar;
metadata.category = order[id].category;
metadata.sub_category = order[id].sub_category;
metadata.sort = order[id].sort;
metadata.subcategory = order[id].subcategory;
metadata.order = order[id].order;
if (order[id].next) {
metadata.next_id = order[id].next.replace(

View file

@ -66,30 +66,10 @@ function autoPrefixCss(cssContent) {
.then(result => result.css);
}
// Validate the docs in the sidebar are valid
function validateSidebar(metadata, sidebarMetadatas) {
if (metadata.next) {
if (!sidebarMetadatas[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 = {
getSubDir,
getLanguage,
isSeparateCss,
minifyCss,
autoPrefixCss,
validateSidebar,
};