Skip to content

Commit d04b3ca

Browse files
endilieyJoelMarcey
authored andcommitted
Enable sub-directories in docs/ (facebook#705)
1 parent 49c27b7 commit d04b3ca

10 files changed

+135
-36
lines changed

crowdin.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ preserve_hierarchy: true
55

66
files:
77
-
8-
source: '/docs/*.md'
9-
translation: '/website/translated_docs/%locale%/%original_file_name%'
8+
source: '/docs/**/*.md'
9+
translation: '/website/translated_docs/%locale%/**/%original_file_name%'
1010
languages_mapping: &anchor
1111
locale:
1212
'af': 'af'

docs/guides-translation.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ preserve_hierarchy: true
140140

141141
files:
142142
-
143-
source: '/docs/*.md'
144-
translation: '/website/translated_docs/%locale%/%original_file_name%'
143+
source: '/docs/**/*.md'
144+
translation: '/website/translated_docs/%locale%/**/%original_file_name%'
145145
languages_mapping: &anchor
146146
locale:
147147
'de': 'de'

lib/__tests__/build-files.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ beforeAll(() => {
4242
generateSite();
4343
return Promise.all([
4444
glob(docsDir + '/**/*.md'),
45-
glob(buildDir + '/' + siteConfig.projectName + '/docs/*.html'),
45+
glob(buildDir + '/' + siteConfig.projectName + '/docs/**/*.html'),
4646
glob(docsDir + '/assets/*'),
4747
glob(buildDir + '/' + siteConfig.projectName + '/img/*'),
4848
]).then(function(results) {

lib/core/DocsLayout.js

+19-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,20 @@ const DocsSidebar = require('./DocsSidebar.js');
1212
const OnPageNav = require('./nav/OnPageNav.js');
1313
const Site = require('./Site.js');
1414
const translation = require('../server/translation.js');
15+
const path = require('path');
1516

1617
// component used to generate whole webpage for docs, including sidebar/header/footer
1718
class DocsLayout extends React.Component {
19+
getRelativeURL = (from, to) => {
20+
const extension = this.props.config.cleanUrl ? '' : '.html';
21+
return (
22+
path
23+
.relative(from, to)
24+
.replace('\\', '/')
25+
.replace(/^\.\.\//, '') + extension
26+
);
27+
};
28+
1829
render() {
1930
const metadata = this.props.metadata;
2031
const content = this.props.children;
@@ -28,7 +39,6 @@ class DocsLayout extends React.Component {
2839
this.props.metadata.localized_id
2940
] || this.props.metadata.title
3041
: this.props.metadata.title;
31-
const extension = this.props.config.cleanUrl ? '' : '.html';
3242
return (
3343
<Site
3444
config={this.props.config}
@@ -55,7 +65,10 @@ class DocsLayout extends React.Component {
5565
{metadata.previous_id && (
5666
<a
5767
className="docs-prev button"
58-
href={metadata.previous_id + extension}>
68+
href={this.getRelativeURL(
69+
metadata.localized_id,
70+
metadata.previous_id
71+
)}>
5972
{' '}
6073
{i18n
6174
? translation[this.props.metadata.language][
@@ -71,7 +84,10 @@ class DocsLayout extends React.Component {
7184
{metadata.next_id && (
7285
<a
7386
className="docs-next button"
74-
href={metadata.next_id + extension}>
87+
href={this.getRelativeURL(
88+
metadata.localized_id,
89+
metadata.next_id
90+
)}>
7591
{i18n
7692
? translation[this.props.metadata.language][
7793
'localized-strings'

lib/server/readMetadata.js

+24-22
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const chalk = require('chalk');
1515
const env = require('./env.js');
1616
const siteConfig = require(CWD + '/siteConfig.js');
1717
const versionFallback = require('./versionFallback.js');
18-
const escapeStringRegexp = require('escape-string-regexp');
18+
const utils = require('./utils.js');
1919

2020
const SupportedHeaderFields = new Set([
2121
'id',
@@ -121,17 +121,10 @@ function extractMetadata(content) {
121121
return {metadata, rawContent: both.content};
122122
}
123123

124-
// process the metadata for a document found in the docs folder
125-
function processMetadata(file) {
124+
// process the metadata for a document found in either 'docs' or 'translated_docs'
125+
function processMetadata(file, refDir) {
126126
const result = extractMetadata(fs.readFileSync(file, 'utf8'));
127-
128-
let regexSubFolder = new RegExp(
129-
'/' + escapeStringRegexp(getDocsPath()) + '/(.*)/.*/'
130-
);
131-
132-
const match = regexSubFolder.exec(file);
133-
let language = match ? match[1] : 'en';
134-
127+
const language = utils.getLanguage(file, refDir) || 'en';
135128
const metadata = {};
136129
for (const fieldName of Object.keys(result.metadata)) {
137130
if (SupportedHeaderFields.has(fieldName)) {
@@ -142,14 +135,28 @@ function processMetadata(file) {
142135
}
143136

144137
const rawContent = result.rawContent;
145-
metadata.source = path.basename(file);
146138

147139
if (!metadata.id) {
148140
metadata.id = path.basename(file, path.extname(file));
149141
}
150142
if (metadata.id.includes('/')) {
151143
throw new Error('Document id cannot include "/".');
152144
}
145+
146+
// If a file is located in a subdirectory, prepend the subdir to it's ID
147+
// Example:
148+
// (file: 'docusaurus/docs/projectA/test.md', ID 'test', refDir: 'docs')
149+
// returns 'projectA/test'
150+
const subDir = utils.getSubDir(file, refDir);
151+
if (subDir) {
152+
metadata.id = `${subDir}/${metadata.id}`;
153+
}
154+
155+
// Example: `docs/projectA/test.md` source is `projectA/test.md`
156+
metadata.source = subDir
157+
? `${subDir}/${path.basename(file)}`
158+
: path.basename(file);
159+
153160
if (!metadata.title) {
154161
metadata.title = metadata.id;
155162
}
@@ -209,14 +216,15 @@ function generateMetadataDocs() {
209216
const defaultMetadatas = {};
210217

211218
// metadata for english files
219+
const docsDir = path.join(CWD, '../', getDocsPath());
212220
let files = glob.sync(CWD + '/../' + getDocsPath() + '/**');
213221
files.forEach(file => {
214222
let language = 'en';
215223

216224
const extension = path.extname(file);
217225

218226
if (extension === '.md' || extension === '.markdown') {
219-
const res = processMetadata(file);
227+
const res = processMetadata(file, docsDir);
220228

221229
if (!res) {
222230
return;
@@ -255,23 +263,17 @@ function generateMetadataDocs() {
255263
});
256264

257265
// metadata for non-english docs
258-
const regexSubFolder = /translated_docs\/(.*?)\/.*/;
266+
const translatedDir = path.join(CWD, 'translated_docs');
259267
files = glob.sync(CWD + '/translated_docs/**');
260268
files.forEach(file => {
261-
let language = 'en';
262-
const match = regexSubFolder.exec(file);
263-
if (match) {
264-
language = match[1];
265-
}
266-
267-
if (enabledLanguages.indexOf(language) === -1) {
269+
if (!utils.getLanguage(file, translatedDir)) {
268270
return;
269271
}
270272

271273
const extension = path.extname(file);
272274

273275
if (extension === '.md' || extension === '.markdown') {
274-
const res = processMetadata(file);
276+
const res = processMetadata(file, translatedDir);
275277
if (!res) {
276278
return;
277279
}

lib/server/server.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ function execute(port) {
130130
// handle all requests for document pages
131131
const app = express();
132132

133-
app.get(/docs\/.*html$/, (req, res, next) => {
133+
app.get(/^\/docs\/.*html$/, (req, res, next) => {
134134
let url = req.path.toString().replace(siteConfig.baseUrl, '');
135135

136136
// links is a map from a permalink to an id for each document

lib/server/utils.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) 2017-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
const path = require('path');
8+
const escapeStringRegexp = require('escape-string-regexp');
9+
const env = require('./env.js');
10+
11+
// Return the subdirectory path from a reference directory
12+
// Example:
13+
// (file: 'docs/projectA/test.md', refDir: 'subDir')
14+
// returns 'projectA'
15+
function getSubDir(file, refDir) {
16+
let subDir = path.dirname(path.relative(refDir, file));
17+
subDir = subDir.replace('\\', '/');
18+
return subDir !== '.' ? subDir : null;
19+
}
20+
21+
// Get the corresponding enabled language locale of a file.
22+
// Example:
23+
// (file: '/website/translated_docs/ko/projectA/test.md', refDir: 'website/translated_docs')
24+
// returns 'ko'
25+
function getLanguage(file, refDir) {
26+
let regexSubFolder = new RegExp(
27+
'/' + escapeStringRegexp(path.basename(refDir)) + '/(.*)/.*/'
28+
);
29+
const match = regexSubFolder.exec(file);
30+
31+
// Avoid misinterpreting subdirectory as language
32+
if (match && env.translation.enabled) {
33+
const enabledLanguages = env.translation
34+
.enabledLanguages()
35+
.map(language => language.tag);
36+
if (enabledLanguages.indexOf(match[1]) !== -1) {
37+
return match[1];
38+
}
39+
}
40+
return null;
41+
}
42+
43+
module.exports = {
44+
getSubDir,
45+
getLanguage,
46+
};

lib/server/versionFallback.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const path = require('path');
1212
const assert = require('assert');
1313

1414
const env = require('./env.js');
15+
const utils = require('./utils.js');
1516
const siteConfig = require(CWD + '/siteConfig.js');
1617

1718
const ENABLE_TRANSLATION = fs.existsSync(CWD + '/languages.js');
@@ -194,7 +195,25 @@ function diffLatestDoc(file, id) {
194195
// the version of the file to be used, and its language
195196
function processVersionMetadata(file, version, useVersion, language) {
196197
const metadata = extractMetadata(fs.readFileSync(file, 'utf8')).metadata;
197-
metadata.source = 'version-' + useVersion + '/' + path.basename(file);
198+
199+
// Add subdirectory information to versioned_doc metadata
200+
// Example: `versioned_docs/version-1.1.6/projectA/readme.md` file with id `version-1.1.6-readme`
201+
// and original_id `readme` will have metadata id of `version-1.1.6-projectA/readme` and original_id `projectA/readme`
202+
const subDir = utils.getSubDir(
203+
file,
204+
path.join(CWD, 'versioned_docs', `version-${useVersion}`)
205+
);
206+
if (subDir) {
207+
metadata.original_id = `${subDir}/${metadata.original_id}`;
208+
metadata.id = metadata.id.replace(
209+
`version-${useVersion}-`,
210+
`version-${useVersion}-${subDir}/`
211+
);
212+
}
213+
214+
metadata.source = subDir
215+
? `version-${useVersion}/${subDir}/${path.basename(file)}`
216+
: `version-${useVersion}/${path.basename(file)}`;
198217

199218
const latestVersion = versions[0];
200219

lib/version.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const path = require('path');
1313
const mkdirp = require('mkdirp');
1414
const chalk = require('chalk');
1515
const readMetadata = require('./server/readMetadata.js');
16+
const utils = require('./server/utils.js');
1617
const versionFallback = require('./server/versionFallback.js');
1718
const env = require('./server/env.js');
1819

@@ -66,12 +67,18 @@ function makeHeader(metadata) {
6667
return header;
6768
}
6869

70+
function writeFileAndCreateFolder(file, content, encoding) {
71+
mkdirp.sync(path.dirname(file));
72+
73+
fs.writeFileSync(file, content, encoding);
74+
}
75+
6976
const versionFolder = CWD + '/versioned_docs/version-' + version;
7077

7178
mkdirp.sync(versionFolder);
7279

7380
// copy necessary files to new version, changing some of its metadata to reflect the versioning
74-
let files = glob.sync(CWD + '/../' + readMetadata.getDocsPath() + '/*');
81+
let files = glob.sync(CWD + '/../' + readMetadata.getDocsPath() + '/**');
7582
files.forEach(file => {
7683
const ext = path.extname(file);
7784
if (ext !== '.md' && ext !== '.markdown') {
@@ -102,9 +109,17 @@ files.forEach(file => {
102109
metadata.original_id = metadata.id;
103110
metadata.id = 'version-' + version + '-' + metadata.id;
104111

105-
const targetFile = versionFolder + '/' + path.basename(file);
112+
const docsDir = path.join(CWD, '../', readMetadata.getDocsPath());
113+
const subDir = utils.getSubDir(file, docsDir);
114+
const targetFile = subDir
115+
? `${versionFolder}/${subDir}/${path.basename(file)}`
116+
: `${versionFolder}/${path.basename(file)}`;
106117

107-
fs.writeFileSync(targetFile, makeHeader(metadata) + rawContent, 'utf8');
118+
writeFileAndCreateFolder(
119+
targetFile,
120+
makeHeader(metadata) + rawContent,
121+
'utf8'
122+
);
108123
});
109124

110125
// copy sidebar if necessary

lib/write-translations.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ function execute() {
4646
};
4747

4848
// look through markdown headers of docs for titles and categories to translate
49+
const docsDir = path.join(CWD, '../', readMetadata.getDocsPath());
4950
let files = glob.sync(CWD + '/../' + readMetadata.getDocsPath() + '/**');
5051
files.forEach(file => {
5152
const extension = path.extname(file);
5253
if (extension === '.md' || extension === '.markdown') {
5354
let res;
5455
try {
55-
res = readMetadata.processMetadata(file);
56+
res = readMetadata.processMetadata(file, docsDir);
5657
} catch (e) {
5758
console.error(e);
5859
process.exit(1);

0 commit comments

Comments
 (0)