diff --git a/lib/core/__tests__/__fixtures__/insertTOC.md b/lib/core/__tests__/__fixtures__/insertTOC.md new file mode 100644 index 0000000000..d459867a8f --- /dev/null +++ b/lib/core/__tests__/__fixtures__/insertTOC.md @@ -0,0 +1,28 @@ +--- +id: pokemon-commands +title: Pokemon Commands +--- + +## Commands + + + +--- + +## Reference + +### `pokemon-run` + +Alias: `run`. + +### `pokemon-fight` + +Alias: `fight` + +### `pokemon-bag` + +Alias: `bag` + +### `pokemon-rename` + +Alias: `rename` \ No newline at end of file diff --git a/lib/core/__tests__/__snapshots__/getTOC.test.js.snap b/lib/core/__tests__/__snapshots__/toc.test.js.snap similarity index 80% rename from lib/core/__tests__/__snapshots__/getTOC.test.js.snap rename to lib/core/__tests__/__snapshots__/toc.test.js.snap index 89176ac5f9..c6517e7e92 100644 --- a/lib/core/__tests__/__snapshots__/getTOC.test.js.snap +++ b/lib/core/__tests__/__snapshots__/toc.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`with custom heading levels 1`] = ` +exports[`getTOC with custom heading levels 1`] = ` Array [ Object { "children": Array [ @@ -105,7 +105,7 @@ Array [ ] `; -exports[`with defaults 1`] = ` +exports[`getTOC with defaults 1`] = ` Array [ Object { "children": Array [ @@ -185,3 +185,57 @@ Array [ }, ] `; + +exports[`insertTOC AUTOGENERATED_TABLE_OF_CONTENTS does not exist 1`] = ` +"## foo +### foo +### foo 1 +## foo 1 +## foo 2 +### foo +#### 4th level headings +All 4th level headings should not be shown by default + +## bar +### bar +#### bar +4th level heading should be ignored by default, but is should be always taken +into account, when generating slugs +### \`bar\` +#### \`bar\` +## bar +### bar +#### bar +## bar +" +`; + +exports[`insertTOC AUTOGENERATED_TABLE_OF_CONTENTS exists 1`] = ` +" +## Commands + + - [\`pokemon-run\`](#pokemon-run) + - [\`pokemon-fight\`](#pokemon-fight) + - [\`pokemon-bag\`](#pokemon-bag) + - [\`pokemon-rename\`](#pokemon-rename) + +--- + +## Reference + +### \`pokemon-run\` + +Alias: \`run\`. + +### \`pokemon-fight\` + +Alias: \`fight\` + +### \`pokemon-bag\` + +Alias: \`bag\` + +### \`pokemon-rename\` + +Alias: \`rename\`" +`; diff --git a/lib/core/__tests__/getTOC.test.js b/lib/core/__tests__/getTOC.test.js deleted file mode 100644 index 0f329b8a36..0000000000 --- a/lib/core/__tests__/getTOC.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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 readFileSync = require('fs').readFileSync; -const getTOC = require('../getTOC'); - -const mdContents = readFileSync( - path.join(__dirname, '__fixtures__', 'getTOC.md'), - 'utf8' -); - -test('with defaults', () => { - const headings = getTOC(mdContents); - const headingsJson = JSON.stringify(headings); - - expect(headings).toMatchSnapshot(); - expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8 - expect(headingsJson).not.toContain('4th level headings'); -}); - -test('with custom heading levels', () => { - const headings = getTOC(mdContents, 'h2', ['h3', 'h4']); - const headingsJson = JSON.stringify(headings); - - expect(headings).toMatchSnapshot(); - expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8 - expect(headingsJson).toContain('4th level headings'); -}); diff --git a/lib/core/__tests__/toc.test.js b/lib/core/__tests__/toc.test.js new file mode 100644 index 0000000000..6eaa87b570 --- /dev/null +++ b/lib/core/__tests__/toc.test.js @@ -0,0 +1,60 @@ +/** + * 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 readFileSync = require('fs').readFileSync; +const {getTOC, insertTOC} = require('../toc'); +const {extractMetadata} = require('../../server/metadataUtils'); + +const getTOCmd = readFileSync( + path.join(__dirname, '__fixtures__', 'getTOC.md'), + 'utf8' +); + +const insertTOCmd = readFileSync( + path.join(__dirname, '__fixtures__', 'insertTOC.md'), + 'utf8' +); + +describe('getTOC', () => { + test('with defaults', () => { + const headings = getTOC(getTOCmd); + const headingsJson = JSON.stringify(headings); + + expect(headings).toMatchSnapshot(); + expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8 + expect(headingsJson).not.toContain('4th level headings'); + }); + + test('with custom heading levels', () => { + const headings = getTOC(getTOCmd, 'h2', ['h3', 'h4']); + const headingsJson = JSON.stringify(headings); + + expect(headings).toMatchSnapshot(); + expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8 + expect(headingsJson).toContain('4th level headings'); + }); +}); + +describe('insertTOC', () => { + test('null or undefined content', () => { + expect(insertTOC(null)).toBeNull(); + expect(insertTOC(undefined)).toBeUndefined(); + }); + + test('AUTOGENERATED_TABLE_OF_CONTENTS does not exist', () => { + const rawContent = extractMetadata(getTOCmd).rawContent; + expect(insertTOC(rawContent)).toMatchSnapshot(); + expect(insertTOC(rawContent)).toEqual(rawContent); + }); + + test('AUTOGENERATED_TABLE_OF_CONTENTS exists', () => { + const rawContent = extractMetadata(insertTOCmd).rawContent; + expect(insertTOC(rawContent)).toMatchSnapshot(); + expect(insertTOC(rawContent)).not.toEqual(rawContent); + }); +}); diff --git a/lib/core/nav/OnPageNav.js b/lib/core/nav/OnPageNav.js index 2ca47a260b..40070f8594 100644 --- a/lib/core/nav/OnPageNav.js +++ b/lib/core/nav/OnPageNav.js @@ -8,7 +8,7 @@ const React = require('react'); const siteConfig = require(`${process.cwd()}/siteConfig.js`); -const getTOC = require('../getTOC'); +const {getTOC} = require('../toc'); const Link = ({hashLink, content}) => ( Number(tag.slice(1)); +const TABLE_OF_CONTENTS_TOKEN = ''; /** * Returns a table of content from the headings @@ -18,16 +18,15 @@ const tagToLevel = tag => Number(tag.slice(1)); * Array of heading objects with `hashLink`, `content` and `children` fields * */ -module.exports = (content, headingTags = 'h2', subHeadingTags = 'h3') => { +function getTOC(content, headingTags = 'h2', subHeadingTags = 'h3') { + const tagToLevel = tag => Number(tag.slice(1)); const headingLevels = [].concat(headingTags).map(tagToLevel); const subHeadingLevels = subHeadingTags ? [].concat(subHeadingTags).map(tagToLevel) : []; const allowedHeadingLevels = headingLevels.concat(subHeadingLevels); - const md = new Remarkable(); const headings = mdToc(content).json; - const toc = []; const context = {}; let current; @@ -36,11 +35,9 @@ module.exports = (content, headingTags = 'h2', subHeadingTags = 'h3') => { // we need always generate slugs to ensure, that we will have consistent // slug indexes for headings with the same names const hashLink = toSlug(heading.content, context); - if (!allowedHeadingLevels.includes(heading.lvl)) { return; } - const rawContent = mdToc.titleize(heading.content); const entry = { hashLink, @@ -48,7 +45,6 @@ module.exports = (content, headingTags = 'h2', subHeadingTags = 'h3') => { content: md.renderInline(rawContent), children: [], }; - if (headingLevels.includes(heading.lvl)) { toc.push(entry); current = entry; @@ -56,6 +52,25 @@ module.exports = (content, headingTags = 'h2', subHeadingTags = 'h3') => { current.children.push(entry); } }); - return toc; +} + +// takes the content of a doc article and returns the content with a table of +// contents inserted +function insertTOC(rawContent) { + if (!rawContent || rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) === -1) { + return rawContent; + } + const filterRe = /^`[^`]*`/; + const headers = getTOC(rawContent, 'h3', null); + const tableOfContents = headers + .filter(header => filterRe.test(header.rawContent)) + .map(header => ` - [${header.rawContent}](#${header.hashLink})`) + .join('\n'); + return rawContent.replace(TABLE_OF_CONTENTS_TOKEN, tableOfContents); +} + +module.exports = { + getTOC, + insertTOC, }; diff --git a/lib/core/utils.js b/lib/core/utils.js index ce8e6f4e50..855f38906a 100644 --- a/lib/core/utils.js +++ b/lib/core/utils.js @@ -30,7 +30,6 @@ function getPath(path, cleanUrl = false) { ? path.replace(/\/index.html$/, '') : removeExtension(path); } - return path; } diff --git a/lib/core/__tests__/routing.test.js b/lib/server/__tests__/routing.test.js similarity index 90% rename from lib/core/__tests__/routing.test.js rename to lib/server/__tests__/routing.test.js index 949b61a584..61e94d2895 100644 --- a/lib/core/__tests__/routing.test.js +++ b/lib/server/__tests__/routing.test.js @@ -5,19 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -const { - blogRouting, - docsRouting, - dotRouting, - feedRouting, - noExtRouting, - pageRouting, - sitemapRouting, -} = require('../routing'); +const routing = require('../routing'); describe('Blog routing', () => { - const blogRegex = blogRouting('/'); - const blogRegex2 = blogRouting('/react/'); + const blogRegex = routing.blog('/'); + const blogRegex2 = routing.blog('/react/'); test('valid blog', () => { expect('/blog/test.html').toMatch(blogRegex); @@ -43,8 +35,8 @@ describe('Blog routing', () => { }); describe('Docs routing', () => { - const docsRegex = docsRouting('/'); - const docsRegex2 = docsRouting('/reason/'); + const docsRegex = routing.docs('/'); + const docsRegex2 = routing.docs('/reason/'); test('valid docs', () => { expect('/docs/en/test.html').toMatch(docsRegex); @@ -70,7 +62,7 @@ describe('Docs routing', () => { }); describe('Dot routing', () => { - const dotRegex = dotRouting(); + const dotRegex = routing.dotfiles(); test('valid url with dot after last slash', () => { expect('/docs/en/test.23').toMatch(dotRegex); @@ -96,8 +88,8 @@ describe('Dot routing', () => { }); describe('Feed routing', () => { - const feedRegex = feedRouting('/'); - const feedRegex2 = feedRouting('/reason/'); + const feedRegex = routing.feed('/'); + const feedRegex2 = routing.feed('/reason/'); test('valid feed url', () => { expect('/blog/atom.xml').toMatch(feedRegex); @@ -126,7 +118,7 @@ describe('Feed routing', () => { }); describe('Extension-less url routing', () => { - const noExtRegex = noExtRouting(); + const noExtRegex = routing.noExtension(); test('valid no extension url', () => { expect('/test').toMatch(noExtRegex); @@ -146,8 +138,8 @@ describe('Extension-less url routing', () => { }); describe('Page routing', () => { - const pageRegex = pageRouting('/'); - const pageRegex2 = pageRouting('/reason/'); + const pageRegex = routing.page('/'); + const pageRegex2 = routing.page('/reason/'); test('valid page url', () => { expect('/index.html').toMatch(pageRegex); @@ -173,8 +165,8 @@ describe('Page routing', () => { }); describe('Sitemap routing', () => { - const sitemapRegex = sitemapRouting('/'); - const sitemapRegex2 = sitemapRouting('/reason/'); + const sitemapRegex = routing.sitemap('/'); + const sitemapRegex2 = routing.sitemap('/reason/'); test('valid sitemap url', () => { expect('/sitemap.xml').toMatch(sitemapRegex); diff --git a/lib/server/__tests__/utils.test.js b/lib/server/__tests__/utils.test.js index 8459edcd40..42c7280cf6 100644 --- a/lib/server/__tests__/utils.test.js +++ b/lib/server/__tests__/utils.test.js @@ -8,6 +8,24 @@ const path = require('path'); const fs = require('fs'); const utils = require('../utils'); +jest.mock('../env', () => ({ + translation: { + enabled: true, + enabledLanguages: () => [ + { + enabled: true, + name: 'English', + tag: 'en', + }, + { + enabled: true, + name: '日本語', + tag: 'ja', + }, + ], + }, +})); + describe('server utils', () => { test('minify css', () => { const testCss = fs.readFileSync( @@ -21,4 +39,44 @@ describe('server utils', () => { utils.minifyCss(testCss).then(css => expect(css).toMatchSnapshot()); utils.minifyCss(notCss).catch(e => expect(e).toMatchSnapshot()); }); + + test('getLanguage', () => { + const testDocEnglish = path.join('translated_docs', 'en', 'test.md'); + const testDocJapanese = path.join('translated_docs', 'ja', 'test.md'); + const testDocJapaneseInSubfolder = path.join( + 'translated_docs', + 'ja', + 'en', + 'test.md' + ); + const testDocInSubfolder = path.join('docs', 'ro', 'test.md'); + const testDocNoLanguage = path.join('docs', 'test.md'); + expect(utils.getLanguage(testDocEnglish, 'translated_docs')).toBe('en'); + expect(utils.getLanguage(testDocJapanese, 'translated_docs')).toBe('ja'); + expect( + utils.getLanguage(testDocJapaneseInSubfolder, 'translated_docs') + ).toBe('ja'); + expect(utils.getLanguage(testDocInSubfolder, 'docs')).toBeNull(); + expect(utils.getLanguage(testDocNoLanguage, 'docs')).toBeNull(); + }); + + test('getSubdir', () => { + const docA = path.join('docs', 'endiliey', 'a.md'); + const docB = path.join('docs', 'nus', 'hackers', 'b.md'); + const docC = path.join('docs', 'c.md'); + const docD = path.join('website', 'translated_docs', 'wow', 'd.md'); + const docE = path.join('website', 'translated_docs', 'lol', 'lah', 'e.md'); + const docsDir = path.join('docs'); + const translatedDir = path.join('website', 'translated_docs'); + expect(utils.getSubDir(docA, docsDir)).toEqual('endiliey'); + expect(utils.getSubDir(docA, translatedDir)).toBeNull(); + expect(utils.getSubDir(docB, docsDir)).toEqual('nus/hackers'); + expect(utils.getSubDir(docB, translatedDir)).toBeNull(); + expect(utils.getSubDir(docC, docsDir)).toBeNull(); + expect(utils.getSubDir(docC, translatedDir)).toBeNull(); + expect(utils.getSubDir(docD, docsDir)).toBeNull(); + expect(utils.getSubDir(docD, translatedDir)).toEqual('wow'); + expect(utils.getSubDir(docE, docsDir)).toBeNull(); + expect(utils.getSubDir(docE, translatedDir)).toEqual('lol/lah'); + }); }); diff --git a/lib/server/generate.js b/lib/server/generate.js index 9931bbd71f..4886818242 100644 --- a/lib/server/generate.js +++ b/lib/server/generate.js @@ -14,9 +14,9 @@ async function execute() { const fs = require('fs-extra'); const readMetadata = require('./readMetadata.js'); const path = require('path'); - const getTOC = require('../core/getTOC'); - const utils = require('../core/utils.js'); - const serverUtils = require('./utils'); + const {insertTOC} = require('../core/toc'); + const {getPath} = require('../core/utils.js'); + const {minifyCss, isSeparateCss} = require('./utils'); const React = require('react'); const mkdirp = require('mkdirp'); const glob = require('glob'); @@ -53,36 +53,6 @@ async function execute() { } } - const TABLE_OF_CONTENTS_TOKEN = ''; - - // takes the content of a doc article and returns the content with a table of - // contents inserted - const insertTableOfContents = rawContent => { - const filterRe = /^`[^`]*`/; - const headers = getTOC(rawContent, 'h3', null); - - const tableOfContents = headers - .filter(header => filterRe.test(header.rawContent)) - .map(header => ` - [${header.rawContent}](#${header.hashLink})`) - .join('\n'); - - return rawContent.replace(TABLE_OF_CONTENTS_TOKEN, tableOfContents); - }; - - // returns true if a file should be excluded from concatentation to - // default Docusaurus styles - function isSeparateCss(file) { - if (!siteConfig.separateCss) { - return false; - } - for (let i = 0; i < siteConfig.separateCss.length; i++) { - if (file.includes(siteConfig.separateCss[i])) { - return true; - } - } - return false; - } - console.log('generate.js triggered...'); // array of tags of enabled languages @@ -103,23 +73,7 @@ async function execute() { const buildDir = join(CWD, 'build', siteConfig.projectName); - // mdToHtml is a map from a markdown file name to its html link, used to - // change relative markdown links that work on GitHub into actual site links - const mdToHtml = {}; - Object.keys(Metadata).forEach(id => { - const metadata = Metadata[id]; - if (metadata.language !== 'en' || metadata.original_id) { - return; - } - let htmlLink = - siteConfig.baseUrl + metadata.permalink.replace('/next/', '/'); - if (htmlLink.includes('/docs/en/')) { - htmlLink = htmlLink.replace('/docs/en/', '/docs/en/VERSION/'); - } else { - htmlLink = htmlLink.replace('/docs/', '/docs/VERSION/'); - } - mdToHtml[metadata.source] = htmlLink; - }); + const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig.baseUrl); const DocsLayout = require('../core/DocsLayout.js'); const Redirect = require('../core/Redirect.js'); @@ -154,16 +108,14 @@ async function execute() { const language = metadata.language; // generate table of contents if appropriate - if (rawContent && rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) !== -1) { - rawContent = insertTableOfContents(rawContent); - } + rawContent = insertTOC(rawContent); const defaultVersion = env.versioning.defaultVersion; // replace any links to markdown files to their website html links Object.keys(mdToHtml).forEach(key => { let link = mdToHtml[key]; - link = utils.getPath(link, siteConfig.cleanUrl); + link = getPath(link, siteConfig.cleanUrl); link = link.replace('/en/', `/${language}/`); link = link.replace( '/VERSION/', @@ -171,14 +123,9 @@ async function execute() { ? `/${metadata.version}/` : '/' ); - // replace relative links without "./" + // replace relative links with & without "./" rawContent = rawContent.replace( - new RegExp(`\\]\\(${key}`, 'g'), - `](${link}` - ); - // replace relative links with "./" - rawContent = rawContent.replace( - new RegExp(`\\]\\(\\./${key}`, 'g'), + new RegExp(`\\]\\((${key}|\\./${key})`, 'g'), `](${link}` ); }); @@ -204,10 +151,7 @@ async function execute() { env.translation.enabled && metadata.permalink.indexOf('docs/en') !== -1 ) { - const redirectlink = utils.getPath( - metadata.permalink, - siteConfig.cleanUrl - ); + const redirectlink = getPath(metadata.permalink, siteConfig.cleanUrl); const redirectComp = ( { + const metadata = Metadata[id]; + if (metadata.language !== 'en' || metadata.original_id) { + return; + } + let htmlLink = baseUrl + metadata.permalink.replace('/next/', '/'); + if (htmlLink.includes('/docs/en/')) { + htmlLink = htmlLink.replace('/docs/en/', '/docs/en/VERSION/'); + } else { + htmlLink = htmlLink.replace('/docs/', '/docs/VERSION/'); + } + result[metadata.source] = htmlLink; + }); + return result; +} + module.exports = { extractMetadata, + mdToHtml, }; diff --git a/lib/core/routing.js b/lib/server/routing.js similarity index 58% rename from lib/core/routing.js rename to lib/server/routing.js index 76c2e5d1d1..41039088cc 100644 --- a/lib/core/routing.js +++ b/lib/server/routing.js @@ -4,45 +4,43 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -function blogRouting(baseUrl) { +function blog(baseUrl) { return new RegExp(`^${baseUrl}blog/.*html$`); } -function docsRouting(baseUrl) { +function docs(baseUrl) { return new RegExp(`^${baseUrl}docs/.*html$`); } -function dotRouting() { +function dotfiles() { return /(?!.*html$)^\/.*\.[^\n/]+$/; } -function feedRouting(baseUrl) { +function feed(baseUrl) { return new RegExp(`^${baseUrl}blog/(feed.xml|atom.xml)$`); } -function noExtRouting() { +function noExtension() { return /\/[^.]*\/?$/; } -function pageRouting(baseUrl) { +function page(baseUrl) { const gr = regex => regex.toString().replace(/(^\/|\/$)/gm, ''); return new RegExp( - `(?!${gr(docsRouting(baseUrl))}|${gr( - blogRouting(baseUrl) - )})^${baseUrl}.*.html$` + `(?!${gr(docs(baseUrl))}|${gr(blog(baseUrl))})^${baseUrl}.*.html$` ); } -function sitemapRouting(baseUrl) { +function sitemap(baseUrl) { return new RegExp(`^${baseUrl}sitemap.xml$`); } module.exports = { - blogRouting, - docsRouting, - dotRouting, - feedRouting, - pageRouting, - noExtRouting, - sitemapRouting, + blog, + docs, + dotfiles, + feed, + page, + noExtension, + sitemap, }; diff --git a/lib/server/server.js b/lib/server/server.js index 4deb5b7291..171ce3961c 100644 --- a/lib/server/server.js +++ b/lib/server/server.js @@ -18,17 +18,9 @@ function execute(port, options) { const request = require('request'); const fs = require('fs-extra'); const path = require('path'); - const getTOC = require('../core/getTOC'); - const utils = require('../core/utils'); - const { - blogRouting, - docsRouting, - dotRouting, - feedRouting, - pageRouting, - noExtRouting, - sitemapRouting, - } = require('../core/routing'); + const {insertTOC} = require('../core/toc'); + const {getPath} = require('../core/utils'); + const {isSeparateCss} = require('./utils'); const mkdirp = require('mkdirp'); const glob = require('glob'); const chalk = require('chalk'); @@ -41,6 +33,7 @@ function execute(port, options) { const feed = require('./feed'); const sitemap = require('./sitemap'); + const routing = require('./routing'); const CWD = process.cwd(); @@ -101,32 +94,6 @@ function execute(port, options) { } } - const TABLE_OF_CONTENTS_TOKEN = ''; - - const insertTableOfContents = rawContent => { - const filterRe = /^`[^`]*`/; - const headers = getTOC(rawContent, 'h3', null); - - const tableOfContents = headers - .filter(header => filterRe.test(header.rawContent)) - .map(header => ` - [${header.rawContent}](#${header.hashLink})`) - .join('\n'); - - return rawContent.replace(TABLE_OF_CONTENTS_TOKEN, tableOfContents); - }; - - function isSeparateCss(file) { - if (!siteConfig.separateCss) { - return false; - } - for (let i = 0; i < siteConfig.separateCss.length; i++) { - if (file.includes(siteConfig.separateCss[i])) { - return true; - } - } - return false; - } - function requestFile(url, res, notFoundCallback) { request.get(url, (error, response, body) => { if (!error) { @@ -183,7 +150,7 @@ function execute(port, options) { const app = express(); // handle all requests for document pages - app.get(docsRouting(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.docs(siteConfig.baseUrl), (req, res, next) => { const url = req.path.toString().replace(siteConfig.baseUrl, ''); // links is a map from a permalink to an id for each document @@ -193,23 +160,7 @@ function execute(port, options) { links[metadata.permalink] = id; }); - // mdToHtml is a map from a markdown file name to its html link, used to - // change relative markdown links that work on GitHub into actual site links - const mdToHtml = {}; - Object.keys(Metadata).forEach(id => { - const metadata = Metadata[id]; - if (metadata.language !== 'en' || metadata.original_id) { - return; - } - let htmlLink = - siteConfig.baseUrl + metadata.permalink.replace('/next/', '/'); - if (htmlLink.includes('/docs/en/')) { - htmlLink = htmlLink.replace('/docs/en/', '/docs/en/VERSION/'); - } else { - htmlLink = htmlLink.replace('/docs/', '/docs/VERSION/'); - } - mdToHtml[metadata.source] = htmlLink; - }); + const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig.baseUrl); const metadata = Metadata[links[url]]; if (!metadata) { @@ -242,16 +193,14 @@ function execute(port, options) { ).rawContent; // generate table of contents if appropriate - if (rawContent && rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) !== -1) { - rawContent = insertTableOfContents(rawContent); - } + rawContent = insertTOC(rawContent); const defaultVersion = env.versioning.defaultVersion; // replace any links to markdown files to their website html links Object.keys(mdToHtml).forEach(key => { let link = mdToHtml[key]; - link = utils.getPath(link, siteConfig.cleanUrl); + link = getPath(link, siteConfig.cleanUrl); link = link.replace('/en/', `/${language}/`); link = link.replace( '/VERSION/', @@ -259,14 +208,9 @@ function execute(port, options) { ? `/${metadata.version}/` : '/' ); - // replace relative links without "./" + // replace relative links with & without "./" rawContent = rawContent.replace( - new RegExp(`\\]\\(${key}`, 'g'), - `](${link}` - ); - // replace relative links with "./" - rawContent = rawContent.replace( - new RegExp(`\\]\\(\\./${key}`, 'g'), + new RegExp(`\\]\\((${key}|\\./${key})`, 'g'), `](${link}` ); }); @@ -305,7 +249,7 @@ function execute(port, options) { res.send(renderToStaticMarkupWithDoctype(docComp)); }); - app.get(sitemapRouting(siteConfig.baseUrl), (req, res) => { + app.get(routing.sitemap(siteConfig.baseUrl), (req, res) => { sitemap((err, xml) => { if (err) { res.status(500).send('Sitemap error'); @@ -316,7 +260,7 @@ function execute(port, options) { }); }); - app.get(feedRouting(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.feed(siteConfig.baseUrl), (req, res, next) => { res.set('Content-Type', 'application/rss+xml'); const file = req.path .toString() @@ -331,7 +275,7 @@ function execute(port, options) { }); // Handle all requests for blog pages and posts. - app.get(blogRouting(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.blog(siteConfig.baseUrl), (req, res, next) => { // Regenerate the blog metadata in case it has changed. Consider improving // this to regenerate on file save rather than on page request. reloadMetadataBlog(); @@ -421,7 +365,7 @@ function execute(port, options) { }); // handle all other main pages - app.get(pageRouting(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.page(siteConfig.baseUrl), (req, res, next) => { // Look for user-provided HTML file first. let htmlFile = req.path.toString().replace(siteConfig.baseUrl, ''); htmlFile = join(CWD, 'pages', htmlFile); @@ -541,7 +485,7 @@ function execute(port, options) { const files = glob.sync(join(CWD, 'static', '**', '*.css')); files.forEach(file => { - if (isSeparateCss(file)) { + if (isSeparateCss(file, siteConfig.separateCss)) { return; } cssContent = `${cssContent}\n${fs.readFileSync(file, { @@ -596,7 +540,7 @@ function execute(port, options) { // "redirect" requests to pages ending with "/" or no extension so that, // for example, request to "blog" returns "blog/index.html" or "blog.html" - app.get(noExtRouting(), (req, res, next) => { + app.get(routing.noExtension(), (req, res, next) => { const slash = req.path.toString().endsWith('/') ? '' : '/'; const requestUrl = `http://localhost:${port}${req.path}`; requestFile(`${requestUrl + slash}index.html`, res, () => { @@ -612,7 +556,7 @@ function execute(port, options) { // handle special cleanUrl case like '/blog/1.2.3' & '/blog.robots.hai' // where we should try to serve 'blog/1.2.3.html' & '/blog.robots.hai.html' - app.get(dotRouting(), (req, res, next) => { + app.get(routing.dotfiles(), (req, res, next) => { if (!siteConfig.cleanUrl) { next(); return; diff --git a/lib/server/utils.js b/lib/server/utils.js index c10814d784..acd5c13373 100644 --- a/lib/server/utils.js +++ b/lib/server/utils.js @@ -9,23 +9,14 @@ const cssnano = require('cssnano'); const path = require('path'); const escapeStringRegexp = require('escape-string-regexp'); -// Return the subdirectory path from a reference directory -// Example: -// (file: 'docs/projectA/test.md', refDir: 'docs') -// returns 'projectA' function getSubDir(file, refDir) { - let subDir = path.dirname(path.relative(refDir, file)); - subDir = subDir.replace('\\', '/'); - return subDir !== '.' ? subDir : null; + const subDir = path.dirname(path.relative(refDir, file)).replace('\\', '/'); + return subDir !== '.' && !subDir.includes('..') ? 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) { const regexSubFolder = new RegExp( - `/${escapeStringRegexp(path.basename(refDir))}/(.*)/.*/` + `${escapeStringRegexp(path.basename(refDir))}/(.*?)/.*` ); const match = regexSubFolder.exec(file); @@ -42,6 +33,18 @@ function getLanguage(file, refDir) { return null; } +function isSeparateCss(file, separateDirs) { + if (!separateDirs) { + return false; + } + for (let i = 0; i < separateDirs.length; i++) { + if (file.includes(separateDirs[i])) { + return true; + } + } + return false; +} + function minifyCss(cssContent) { return cssnano .process(cssContent, { @@ -54,5 +57,6 @@ function minifyCss(cssContent) { module.exports = { getSubDir, getLanguage, + isSeparateCss, minifyCss, };