docusaurus/packages/docusaurus-1.x/lib/server/readMetadata.js
Yangshun Tay a690d34af0
feat(v1): allow specifying meta desc in front matter (#1859)
* feat(v1): allow specifying meta desc in front matter

* misc(v2): sync with v1
2019-10-20 11:17:10 -07:00

408 lines
12 KiB
JavaScript

/**
* 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 CWD = process.cwd();
const path = require('path');
const fs = require('fs');
const glob = require('glob');
const program = require('commander');
const metadataUtils = require('./metadataUtils');
const env = require('./env.js');
const blog = require('./blog.js');
const loadConfig = require('./config');
const siteConfig = loadConfig(`${CWD}/siteConfig.js`);
const versionFallback = require('./versionFallback.js');
const utils = require('./utils.js');
const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`;
const SupportedHeaderFields = new Set([
'id',
'title',
'author',
'authorURL',
'authorFBID',
'sidebar_label',
'original_id',
'hide_title',
'layout',
'custom_edit_url',
'description',
]);
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
// at the same level as `website`, not inside `website`.
// e.g., docs/whereDocsReallyExist
// website-docs/
// All .md docs still (currently) must be in one flat directory hierarchy.
// e.g., docs/whereDocsReallyExist/*.md (all .md files in this dir)
function getDocsPath() {
return siteConfig.customDocsPath ? siteConfig.customDocsPath : 'docs';
}
function shouldGenerateNextReleaseDocs() {
return !(env.versioning.enabled && program.skipNextRelease);
}
// returns map from id to object containing sidebar ordering info
function readSidebar(sidebars = {}) {
Object.assign(sidebars, versionFallback.sidebarData());
const items = {};
Object.keys(sidebars).forEach(sidebar => {
const categories = sidebars[sidebar];
const sidebarItems = [];
Object.keys(categories).forEach(category => {
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 < sidebarItems.length; i++) {
const item = sidebarItems[i];
let previous = null;
let next = null;
if (i > 0) {
previous = sidebarItems[i - 1].id;
}
if (i < sidebarItems.length - 1) {
next = sidebarItems[i + 1].id;
}
items[item.id] = {
previous,
next,
sidebar,
category: item.category,
subcategory: item.subcategory,
order: item.order,
};
}
});
return items;
}
// process the metadata for a document found in either 'docs' or 'translated_docs'
function processMetadata(file, refDir) {
const result = metadataUtils.extractMetadata(fs.readFileSync(file, 'utf8'));
const language = utils.getLanguage(file, refDir) || 'en';
const metadata = {};
Object.keys(result.metadata).forEach(fieldName => {
if (SupportedHeaderFields.has(fieldName)) {
metadata[fieldName] = result.metadata[fieldName];
} else {
console.warn(`Header field "${fieldName}" in ${file} is not supported.`);
}
});
const rawContent = result.rawContent;
if (!metadata.id) {
metadata.id = path.parse(file).name;
}
if (metadata.id.includes('/')) {
throw new Error('Document id cannot include "/".');
}
// If a file is located in a subdirectory, prepend the subdir to it's ID
// Example:
// (file: 'docusaurus/docs/projectA/test.md', ID 'test', refDir: 'docusaurus/docs')
// returns 'projectA/test'
const subDir = utils.getSubDir(file, refDir);
if (subDir) {
metadata.id = `${subDir}/${metadata.id}`;
}
// Example: `docs/projectA/test.md` source is `projectA/test.md`
metadata.source = subDir
? `${subDir}/${path.basename(file)}`
: path.basename(file);
if (!metadata.title) {
metadata.title = metadata.id;
}
const langPart =
env.translation.enabled || siteConfig.useEnglishUrl ? `${language}/` : '';
let versionPart = '';
if (env.versioning.enabled) {
metadata.version = 'next';
versionPart = 'next/';
}
metadata.permalink = `${docsPart}${langPart}${versionPart}${metadata.id}.html`;
// change ids previous, next
metadata.localized_id = metadata.id;
metadata.id = (env.translation.enabled ? `${language}-` : '') + metadata.id;
metadata.language = env.translation.enabled ? language : 'en';
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 (item.next) {
metadata.next_id = item.next;
metadata.next =
(env.translation.enabled ? `${language}-` : '') + item.next;
}
if (item.previous) {
metadata.previous_id = item.previous;
metadata.previous =
(env.translation.enabled ? `${language}-` : '') + item.previous;
}
}
return {metadata, rawContent};
}
// process metadata for all docs and save into core/metadata.js
function generateMetadataDocs() {
let order;
try {
order = readSidebar(allSidebars);
} catch (e) {
console.error(e);
process.exit(1);
}
const enabledLanguages = env.translation
.enabledLanguages()
.map(language => language.tag);
const metadatas = {};
const defaultMetadatas = {};
if (shouldGenerateNextReleaseDocs()) {
// metadata for english files
const docsDir = path.join(CWD, '../', getDocsPath());
let files = glob.sync(`${docsDir}/**`);
files.forEach(file => {
const extension = path.extname(file);
if (extension === '.md' || extension === '.markdown') {
const res = processMetadata(file, docsDir);
if (!res) {
return;
}
const metadata = res.metadata;
metadatas[metadata.id] = metadata;
// create a default list of documents for each enabled language based on docs in English
// these will get replaced if/when the localized file is downloaded from crowdin
enabledLanguages
.filter(currentLanguage => currentLanguage !== 'en')
.forEach(currentLanguage => {
const baseMetadata = Object.assign({}, metadata);
baseMetadata.id = baseMetadata.id
.toString()
.replace(/^en-/, `${currentLanguage}-`);
if (baseMetadata.permalink) {
baseMetadata.permalink = baseMetadata.permalink
.toString()
.replace(
new RegExp(`^${docsPart}en/`),
`${docsPart}${currentLanguage}/`,
);
}
if (baseMetadata.next) {
baseMetadata.next = baseMetadata.next
.toString()
.replace(/^en-/, `${currentLanguage}-`);
}
if (baseMetadata.previous) {
baseMetadata.previous = baseMetadata.previous
.toString()
.replace(/^en-/, `${currentLanguage}-`);
}
baseMetadata.language = currentLanguage;
defaultMetadatas[baseMetadata.id] = baseMetadata;
});
Object.assign(metadatas, defaultMetadatas);
}
});
// metadata for non-english docs
const translatedDir = path.join(CWD, 'translated_docs');
files = glob.sync(`${CWD}/translated_docs/**`);
files.forEach(file => {
if (!utils.getLanguage(file, translatedDir)) {
return;
}
const extension = path.extname(file);
if (extension === '.md' || extension === '.markdown') {
const res = processMetadata(file, translatedDir);
if (!res) {
return;
}
const metadata = res.metadata;
metadatas[metadata.id] = metadata;
}
});
}
// metadata for versioned docs
const versionData = versionFallback.docData();
versionData.forEach(metadata => {
const id = metadata.localized_id;
if (order[id]) {
metadata.sidebar = order[id].sidebar;
metadata.category = order[id].category;
metadata.subcategory = order[id].subcategory;
metadata.order = order[id].order;
if (order[id].next) {
metadata.next_id = order[id].next.replace(
`version-${metadata.version}-`,
'',
);
metadata.next =
(env.translation.enabled ? `${metadata.language}-` : '') +
order[id].next;
}
if (order[id].previous) {
metadata.previous_id = order[id].previous.replace(
`version-${metadata.version}-`,
'',
);
metadata.previous =
(env.translation.enabled ? `${metadata.language}-` : '') +
order[id].previous;
}
}
metadatas[metadata.id] = metadata;
});
// Get the titles of the previous and next ids so that we can use them in
// navigation buttons in DocsLayout.js
Object.keys(metadatas).forEach(metadata => {
if (metadatas[metadata].previous) {
if (metadatas[metadatas[metadata].previous]) {
metadatas[metadata].previous_title =
metadatas[metadatas[metadata].previous].title;
} else {
metadatas[metadata].previous_title = 'Previous';
}
}
if (metadatas[metadata].next) {
if (metadatas[metadatas[metadata].next]) {
metadatas[metadata].next_title =
metadatas[metadatas[metadata].next].title;
} else {
metadatas[metadata].next_title = 'Next';
}
}
});
fs.writeFileSync(
path.join(__dirname, '/../core/metadata.js'),
`${'/**\n' +
' * @' +
'generated\n' + // separate this out for Nuclide treating @generated as readonly
' */\n' +
'module.exports = '}${JSON.stringify(metadatas, null, 2)};\n`,
);
}
// process metadata for blog posts and save into core/MetadataBlog.js
function generateMetadataBlog(config = siteConfig) {
const metadatas = [];
const files = glob.sync(`${CWD}/blog/**/*.*`);
files
.sort()
.reverse()
.forEach(file => {
const extension = path.extname(file);
if (extension !== '.md' && extension !== '.markdown') {
return;
}
const metadata = blog.getMetadata(file, config);
// Extract, YYYY, MM, DD from the file name
const filePathDateArr = path.basename(file).split('-');
metadata.date = new Date(
`${filePathDateArr[0]}-${filePathDateArr[1]}-${
filePathDateArr[2]
}T06:00:00.000Z`,
);
// allow easier sorting of blog by providing seconds since epoch
metadata.seconds = Math.round(metadata.date.getTime() / 1000);
metadatas.push(metadata);
});
const sortedMetadatas = metadatas.sort(
(a, b) => parseInt(b.seconds, 10) - parseInt(a.seconds, 10),
);
fs.writeFileSync(
path.join(__dirname, '/../core/MetadataBlog.js'),
`${'/**\n' +
' * @' +
'generated\n' + // separate this out for Nuclide treating @generated as readonly
' */\n' +
'module.exports = '}${JSON.stringify(sortedMetadatas, null, 2)};\n`,
);
}
module.exports = {
getDocsPath,
readSidebar,
processMetadata,
generateMetadataDocs,
generateMetadataBlog,
};