mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 18:27:56 +02:00
Enable sub-directories in docs/ (#705)
This commit is contained in:
parent
49c27b733b
commit
d04b3ca87b
10 changed files with 135 additions and 36 deletions
|
@ -5,8 +5,8 @@ preserve_hierarchy: true
|
||||||
|
|
||||||
files:
|
files:
|
||||||
-
|
-
|
||||||
source: '/docs/*.md'
|
source: '/docs/**/*.md'
|
||||||
translation: '/website/translated_docs/%locale%/%original_file_name%'
|
translation: '/website/translated_docs/%locale%/**/%original_file_name%'
|
||||||
languages_mapping: &anchor
|
languages_mapping: &anchor
|
||||||
locale:
|
locale:
|
||||||
'af': 'af'
|
'af': 'af'
|
||||||
|
|
|
@ -140,8 +140,8 @@ preserve_hierarchy: true
|
||||||
|
|
||||||
files:
|
files:
|
||||||
-
|
-
|
||||||
source: '/docs/*.md'
|
source: '/docs/**/*.md'
|
||||||
translation: '/website/translated_docs/%locale%/%original_file_name%'
|
translation: '/website/translated_docs/%locale%/**/%original_file_name%'
|
||||||
languages_mapping: &anchor
|
languages_mapping: &anchor
|
||||||
locale:
|
locale:
|
||||||
'de': 'de'
|
'de': 'de'
|
||||||
|
|
|
@ -42,7 +42,7 @@ beforeAll(() => {
|
||||||
generateSite();
|
generateSite();
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
glob(docsDir + '/**/*.md'),
|
glob(docsDir + '/**/*.md'),
|
||||||
glob(buildDir + '/' + siteConfig.projectName + '/docs/*.html'),
|
glob(buildDir + '/' + siteConfig.projectName + '/docs/**/*.html'),
|
||||||
glob(docsDir + '/assets/*'),
|
glob(docsDir + '/assets/*'),
|
||||||
glob(buildDir + '/' + siteConfig.projectName + '/img/*'),
|
glob(buildDir + '/' + siteConfig.projectName + '/img/*'),
|
||||||
]).then(function(results) {
|
]).then(function(results) {
|
||||||
|
|
|
@ -12,9 +12,20 @@ const DocsSidebar = require('./DocsSidebar.js');
|
||||||
const OnPageNav = require('./nav/OnPageNav.js');
|
const OnPageNav = require('./nav/OnPageNav.js');
|
||||||
const Site = require('./Site.js');
|
const Site = require('./Site.js');
|
||||||
const translation = require('../server/translation.js');
|
const translation = require('../server/translation.js');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
// component used to generate whole webpage for docs, including sidebar/header/footer
|
// component used to generate whole webpage for docs, including sidebar/header/footer
|
||||||
class DocsLayout extends React.Component {
|
class DocsLayout extends React.Component {
|
||||||
|
getRelativeURL = (from, to) => {
|
||||||
|
const extension = this.props.config.cleanUrl ? '' : '.html';
|
||||||
|
return (
|
||||||
|
path
|
||||||
|
.relative(from, to)
|
||||||
|
.replace('\\', '/')
|
||||||
|
.replace(/^\.\.\//, '') + extension
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const metadata = this.props.metadata;
|
const metadata = this.props.metadata;
|
||||||
const content = this.props.children;
|
const content = this.props.children;
|
||||||
|
@ -28,7 +39,6 @@ class DocsLayout extends React.Component {
|
||||||
this.props.metadata.localized_id
|
this.props.metadata.localized_id
|
||||||
] || this.props.metadata.title
|
] || this.props.metadata.title
|
||||||
: this.props.metadata.title;
|
: this.props.metadata.title;
|
||||||
const extension = this.props.config.cleanUrl ? '' : '.html';
|
|
||||||
return (
|
return (
|
||||||
<Site
|
<Site
|
||||||
config={this.props.config}
|
config={this.props.config}
|
||||||
|
@ -55,7 +65,10 @@ class DocsLayout extends React.Component {
|
||||||
{metadata.previous_id && (
|
{metadata.previous_id && (
|
||||||
<a
|
<a
|
||||||
className="docs-prev button"
|
className="docs-prev button"
|
||||||
href={metadata.previous_id + extension}>
|
href={this.getRelativeURL(
|
||||||
|
metadata.localized_id,
|
||||||
|
metadata.previous_id
|
||||||
|
)}>
|
||||||
←{' '}
|
←{' '}
|
||||||
{i18n
|
{i18n
|
||||||
? translation[this.props.metadata.language][
|
? translation[this.props.metadata.language][
|
||||||
|
@ -71,7 +84,10 @@ class DocsLayout extends React.Component {
|
||||||
{metadata.next_id && (
|
{metadata.next_id && (
|
||||||
<a
|
<a
|
||||||
className="docs-next button"
|
className="docs-next button"
|
||||||
href={metadata.next_id + extension}>
|
href={this.getRelativeURL(
|
||||||
|
metadata.localized_id,
|
||||||
|
metadata.next_id
|
||||||
|
)}>
|
||||||
{i18n
|
{i18n
|
||||||
? translation[this.props.metadata.language][
|
? translation[this.props.metadata.language][
|
||||||
'localized-strings'
|
'localized-strings'
|
||||||
|
|
|
@ -15,7 +15,7 @@ const chalk = require('chalk');
|
||||||
const env = require('./env.js');
|
const env = require('./env.js');
|
||||||
const siteConfig = require(CWD + '/siteConfig.js');
|
const siteConfig = require(CWD + '/siteConfig.js');
|
||||||
const versionFallback = require('./versionFallback.js');
|
const versionFallback = require('./versionFallback.js');
|
||||||
const escapeStringRegexp = require('escape-string-regexp');
|
const utils = require('./utils.js');
|
||||||
|
|
||||||
const SupportedHeaderFields = new Set([
|
const SupportedHeaderFields = new Set([
|
||||||
'id',
|
'id',
|
||||||
|
@ -121,17 +121,10 @@ function extractMetadata(content) {
|
||||||
return {metadata, rawContent: both.content};
|
return {metadata, rawContent: both.content};
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the metadata for a document found in the docs folder
|
// process the metadata for a document found in either 'docs' or 'translated_docs'
|
||||||
function processMetadata(file) {
|
function processMetadata(file, refDir) {
|
||||||
const result = extractMetadata(fs.readFileSync(file, 'utf8'));
|
const result = extractMetadata(fs.readFileSync(file, 'utf8'));
|
||||||
|
const language = utils.getLanguage(file, refDir) || 'en';
|
||||||
let regexSubFolder = new RegExp(
|
|
||||||
'/' + escapeStringRegexp(getDocsPath()) + '/(.*)/.*/'
|
|
||||||
);
|
|
||||||
|
|
||||||
const match = regexSubFolder.exec(file);
|
|
||||||
let language = match ? match[1] : 'en';
|
|
||||||
|
|
||||||
const metadata = {};
|
const metadata = {};
|
||||||
for (const fieldName of Object.keys(result.metadata)) {
|
for (const fieldName of Object.keys(result.metadata)) {
|
||||||
if (SupportedHeaderFields.has(fieldName)) {
|
if (SupportedHeaderFields.has(fieldName)) {
|
||||||
|
@ -142,7 +135,6 @@ function processMetadata(file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawContent = result.rawContent;
|
const rawContent = result.rawContent;
|
||||||
metadata.source = path.basename(file);
|
|
||||||
|
|
||||||
if (!metadata.id) {
|
if (!metadata.id) {
|
||||||
metadata.id = path.basename(file, path.extname(file));
|
metadata.id = path.basename(file, path.extname(file));
|
||||||
|
@ -150,6 +142,21 @@ function processMetadata(file) {
|
||||||
if (metadata.id.includes('/')) {
|
if (metadata.id.includes('/')) {
|
||||||
throw new Error('Document id cannot include "/".');
|
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: '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) {
|
if (!metadata.title) {
|
||||||
metadata.title = metadata.id;
|
metadata.title = metadata.id;
|
||||||
}
|
}
|
||||||
|
@ -209,6 +216,7 @@ function generateMetadataDocs() {
|
||||||
const defaultMetadatas = {};
|
const defaultMetadatas = {};
|
||||||
|
|
||||||
// metadata for english files
|
// metadata for english files
|
||||||
|
const docsDir = path.join(CWD, '../', getDocsPath());
|
||||||
let files = glob.sync(CWD + '/../' + getDocsPath() + '/**');
|
let files = glob.sync(CWD + '/../' + getDocsPath() + '/**');
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
let language = 'en';
|
let language = 'en';
|
||||||
|
@ -216,7 +224,7 @@ function generateMetadataDocs() {
|
||||||
const extension = path.extname(file);
|
const extension = path.extname(file);
|
||||||
|
|
||||||
if (extension === '.md' || extension === '.markdown') {
|
if (extension === '.md' || extension === '.markdown') {
|
||||||
const res = processMetadata(file);
|
const res = processMetadata(file, docsDir);
|
||||||
|
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return;
|
return;
|
||||||
|
@ -255,23 +263,17 @@ function generateMetadataDocs() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// metadata for non-english docs
|
// metadata for non-english docs
|
||||||
const regexSubFolder = /translated_docs\/(.*?)\/.*/;
|
const translatedDir = path.join(CWD, 'translated_docs');
|
||||||
files = glob.sync(CWD + '/translated_docs/**');
|
files = glob.sync(CWD + '/translated_docs/**');
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
let language = 'en';
|
if (!utils.getLanguage(file, translatedDir)) {
|
||||||
const match = regexSubFolder.exec(file);
|
|
||||||
if (match) {
|
|
||||||
language = match[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enabledLanguages.indexOf(language) === -1) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const extension = path.extname(file);
|
const extension = path.extname(file);
|
||||||
|
|
||||||
if (extension === '.md' || extension === '.markdown') {
|
if (extension === '.md' || extension === '.markdown') {
|
||||||
const res = processMetadata(file);
|
const res = processMetadata(file, translatedDir);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@ function execute(port) {
|
||||||
// handle all requests for document pages
|
// handle all requests for document pages
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.get(/docs\/.*html$/, (req, res, next) => {
|
app.get(/^\/docs\/.*html$/, (req, res, next) => {
|
||||||
let url = req.path.toString().replace(siteConfig.baseUrl, '');
|
let url = req.path.toString().replace(siteConfig.baseUrl, '');
|
||||||
|
|
||||||
// links is a map from a permalink to an id for each document
|
// links is a map from a permalink to an id for each document
|
||||||
|
|
46
lib/server/utils.js
Normal file
46
lib/server/utils.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* 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 path = require('path');
|
||||||
|
const escapeStringRegexp = require('escape-string-regexp');
|
||||||
|
const env = require('./env.js');
|
||||||
|
|
||||||
|
// Return the subdirectory path from a reference directory
|
||||||
|
// Example:
|
||||||
|
// (file: 'docs/projectA/test.md', refDir: 'subDir')
|
||||||
|
// returns 'projectA'
|
||||||
|
function getSubDir(file, refDir) {
|
||||||
|
let subDir = path.dirname(path.relative(refDir, file));
|
||||||
|
subDir = subDir.replace('\\', '/');
|
||||||
|
return subDir !== '.' ? subDir : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the corresponding enabled language locale of a file.
|
||||||
|
// Example:
|
||||||
|
// (file: '/website/translated_docs/ko/projectA/test.md', refDir: 'website/translated_docs')
|
||||||
|
// returns 'ko'
|
||||||
|
function getLanguage(file, refDir) {
|
||||||
|
let regexSubFolder = new RegExp(
|
||||||
|
'/' + escapeStringRegexp(path.basename(refDir)) + '/(.*)/.*/'
|
||||||
|
);
|
||||||
|
const match = regexSubFolder.exec(file);
|
||||||
|
|
||||||
|
// Avoid misinterpreting subdirectory as language
|
||||||
|
if (match && env.translation.enabled) {
|
||||||
|
const enabledLanguages = env.translation
|
||||||
|
.enabledLanguages()
|
||||||
|
.map(language => language.tag);
|
||||||
|
if (enabledLanguages.indexOf(match[1]) !== -1) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getSubDir,
|
||||||
|
getLanguage,
|
||||||
|
};
|
|
@ -12,6 +12,7 @@ const path = require('path');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
const env = require('./env.js');
|
const env = require('./env.js');
|
||||||
|
const utils = require('./utils.js');
|
||||||
const siteConfig = require(CWD + '/siteConfig.js');
|
const siteConfig = require(CWD + '/siteConfig.js');
|
||||||
|
|
||||||
const ENABLE_TRANSLATION = fs.existsSync(CWD + '/languages.js');
|
const ENABLE_TRANSLATION = fs.existsSync(CWD + '/languages.js');
|
||||||
|
@ -194,7 +195,25 @@ function diffLatestDoc(file, id) {
|
||||||
// the version of the file to be used, and its language
|
// the version of the file to be used, and its language
|
||||||
function processVersionMetadata(file, version, useVersion, language) {
|
function processVersionMetadata(file, version, useVersion, language) {
|
||||||
const metadata = extractMetadata(fs.readFileSync(file, 'utf8')).metadata;
|
const metadata = extractMetadata(fs.readFileSync(file, 'utf8')).metadata;
|
||||||
metadata.source = 'version-' + useVersion + '/' + path.basename(file);
|
|
||||||
|
// Add subdirectory information to versioned_doc metadata
|
||||||
|
// Example: `versioned_docs/version-1.1.6/projectA/readme.md` file with id `version-1.1.6-readme`
|
||||||
|
// and original_id `readme` will have metadata id of `version-1.1.6-projectA/readme` and original_id `projectA/readme`
|
||||||
|
const subDir = utils.getSubDir(
|
||||||
|
file,
|
||||||
|
path.join(CWD, 'versioned_docs', `version-${useVersion}`)
|
||||||
|
);
|
||||||
|
if (subDir) {
|
||||||
|
metadata.original_id = `${subDir}/${metadata.original_id}`;
|
||||||
|
metadata.id = metadata.id.replace(
|
||||||
|
`version-${useVersion}-`,
|
||||||
|
`version-${useVersion}-${subDir}/`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.source = subDir
|
||||||
|
? `version-${useVersion}/${subDir}/${path.basename(file)}`
|
||||||
|
: `version-${useVersion}/${path.basename(file)}`;
|
||||||
|
|
||||||
const latestVersion = versions[0];
|
const latestVersion = versions[0];
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ const path = require('path');
|
||||||
const mkdirp = require('mkdirp');
|
const mkdirp = require('mkdirp');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const readMetadata = require('./server/readMetadata.js');
|
const readMetadata = require('./server/readMetadata.js');
|
||||||
|
const utils = require('./server/utils.js');
|
||||||
const versionFallback = require('./server/versionFallback.js');
|
const versionFallback = require('./server/versionFallback.js');
|
||||||
const env = require('./server/env.js');
|
const env = require('./server/env.js');
|
||||||
|
|
||||||
|
@ -66,12 +67,18 @@ function makeHeader(metadata) {
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeFileAndCreateFolder(file, content, encoding) {
|
||||||
|
mkdirp.sync(path.dirname(file));
|
||||||
|
|
||||||
|
fs.writeFileSync(file, content, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
const versionFolder = CWD + '/versioned_docs/version-' + version;
|
const versionFolder = CWD + '/versioned_docs/version-' + version;
|
||||||
|
|
||||||
mkdirp.sync(versionFolder);
|
mkdirp.sync(versionFolder);
|
||||||
|
|
||||||
// copy necessary files to new version, changing some of its metadata to reflect the versioning
|
// copy necessary files to new version, changing some of its metadata to reflect the versioning
|
||||||
let files = glob.sync(CWD + '/../' + readMetadata.getDocsPath() + '/*');
|
let files = glob.sync(CWD + '/../' + readMetadata.getDocsPath() + '/**');
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
const ext = path.extname(file);
|
const ext = path.extname(file);
|
||||||
if (ext !== '.md' && ext !== '.markdown') {
|
if (ext !== '.md' && ext !== '.markdown') {
|
||||||
|
@ -102,9 +109,17 @@ files.forEach(file => {
|
||||||
metadata.original_id = metadata.id;
|
metadata.original_id = metadata.id;
|
||||||
metadata.id = 'version-' + version + '-' + metadata.id;
|
metadata.id = 'version-' + version + '-' + metadata.id;
|
||||||
|
|
||||||
const targetFile = versionFolder + '/' + path.basename(file);
|
const docsDir = path.join(CWD, '../', readMetadata.getDocsPath());
|
||||||
|
const subDir = utils.getSubDir(file, docsDir);
|
||||||
|
const targetFile = subDir
|
||||||
|
? `${versionFolder}/${subDir}/${path.basename(file)}`
|
||||||
|
: `${versionFolder}/${path.basename(file)}`;
|
||||||
|
|
||||||
fs.writeFileSync(targetFile, makeHeader(metadata) + rawContent, 'utf8');
|
writeFileAndCreateFolder(
|
||||||
|
targetFile,
|
||||||
|
makeHeader(metadata) + rawContent,
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// copy sidebar if necessary
|
// copy sidebar if necessary
|
||||||
|
|
|
@ -46,13 +46,14 @@ function execute() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// look through markdown headers of docs for titles and categories to translate
|
// look through markdown headers of docs for titles and categories to translate
|
||||||
|
const docsDir = path.join(CWD, '../', readMetadata.getDocsPath());
|
||||||
let files = glob.sync(CWD + '/../' + readMetadata.getDocsPath() + '/**');
|
let files = glob.sync(CWD + '/../' + readMetadata.getDocsPath() + '/**');
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
const extension = path.extname(file);
|
const extension = path.extname(file);
|
||||||
if (extension === '.md' || extension === '.markdown') {
|
if (extension === '.md' || extension === '.markdown') {
|
||||||
let res;
|
let res;
|
||||||
try {
|
try {
|
||||||
res = readMetadata.processMetadata(file);
|
res = readMetadata.processMetadata(file, docsDir);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
Loading…
Add table
Reference in a new issue