diff --git a/docs/api-pages.md b/docs/api-pages.md index 7f4821d15ae4..f80aeb6ea3bc 100644 --- a/docs/api-pages.md +++ b/docs/api-pages.md @@ -5,6 +5,25 @@ title: Pages and Styles Docusaurus provides support for writing pages as React components inside the `website/pages` directory which will share the same header, footer, and styles as the rest of the site. +## Provided Props + +Docusaurus provides your [siteConfig.js](api-site-config.md) as a `config` props. Hence, you can access `baseUrl` or `title` through this props. + +Example + +```js +const React = require('react'); + +class MyPage extends React.Component { + render() { + const siteConfig = this.props.config; + return
{siteConfig.title}
; + } +} + +module.exports = MyPage; +``` + ## URLs for Pages Any `.js` files in `website/pages` will be rendered to static HTML using the path of the file after `pages`. Files in `website/pages/en` will also get copied out into `pages` and will OVERRIDE any files of the same name in `pages`. For example, the page for the `website/pages/en/help.js` file will be found at the URL `${baseUrl}en/help.js` as well as the URL `${baseUrl}help.js`, where `${baseUrl}` is the `baseUrl` field set in your [siteConfig.js file](api-site-config.md). @@ -31,7 +50,7 @@ module.exports = MyPage; ## Description for Pages -By default, the description your page is `tagline` set in [`siteConfig.js`](api-site-config.md). If you want to set a specific description for your custom pages, add a `description` class property on your exported React component. +By default, the description your page is `tagline` set in [`siteConfig.js`](api-site-config.md). If you want to set a specific description for your custom pages, add a `description` class property on your exported React component. Example: @@ -138,7 +157,7 @@ A React component to organize text and images. className="myCustomClass" contents={[ { - title: `[Learn](${siteConfig.baseUrl}docs/tutorial.html)`, + title: `[Learn](${siteConfig.baseUrl}${siteConfig.docsUrl}/tutorial.html)`, content: 'Learn how to use this project', image: siteConfig.baseUrl + 'img/learn.png', imageAlt: 'Learn how to use this project', diff --git a/docs/api-site-config.md b/docs/api-site-config.md index 667c93353364..fe18a7ceac59 100644 --- a/docs/api-site-config.md +++ b/docs/api-site-config.md @@ -64,10 +64,6 @@ headerLinks: [ ], ``` -#### `noIndex` [boolean] - -Boolean. If true, Docusaurus will politely ask crawlers and search engines to avoid indexing your site. This is done with a header tag and so only applies to docs and pages. Will not attempt to hide static resources. This is a best effort request. Malicious crawlers can and will still index your site. - #### `organizationName` [string] GitHub username of the organization or user hosting this project. This is used by the publishing script to determine where your GitHub pages website will be hosted. @@ -130,6 +126,11 @@ customDocsPath: 'website-docs'; The default version for the site to be shown. If this is not set, the latest version will be shown. +#### `docsUrl` [string] + +The base url for all docs file. Set this field to `''` to remove the `docs` prefix of the documentation URL. +If unset, it is defaulted to `docs`. + #### `disableHeaderTitle` [boolean] An option to disable showing the title in the header next to the header icon. Exclude this field to keep the header as normal, otherwise set to `true`. @@ -246,6 +247,10 @@ Path to your web app manifest (e.g., `manifest.json`). This will add a `` An array of plugins to be loaded by Remarkable, the markdown parser and renderer used by Docusaurus. The plugin will receive a reference to the Remarkable instance, allowing custom parsing and rendering rules to be defined. +#### `noIndex` [boolean] + +Boolean. If true, Docusaurus will politely ask crawlers and search engines to avoid indexing your site. This is done with a header tag and so only applies to docs and pages. Will not attempt to hide static resources. This is a best effort request. Malicious crawlers can and will still index your site. + #### `ogImage` [string] Local path to an Open Graph image (e.g., `img/myImage.png`). This image will show up when your site is shared on Facebook and other websites/apps where the Open Graph protocol is supported. diff --git a/docs/guides-translation.md b/docs/guides-translation.md index 14c57d45fa8a..d915a1f77d1b 100644 --- a/docs/guides-translation.md +++ b/docs/guides-translation.md @@ -65,7 +65,7 @@ You can also include an optional description attribute to give more context to a

``` -> The `` tag generally works well on pure strings. If you have a string like "Docusaurus currently provides support to help your website use [translations](${siteConfig.baseUrl}docs/${this.props.language}/translation.html)", wrapping the `` tag around that entire string will cause issues because of the markdown linking, etc. Your options are to not translate those strings, or spread a bunch of `` tags amongst the pure substrings of that string. +> The `` tag generally works well on pure strings. If you have a string like "Docusaurus currently provides support to help your website use [translations](${siteConfig.baseUrl}${siteConfig.docsUrl}/${this.props.language}/translation.html)", wrapping the `` tag around that entire string will cause issues because of the markdown linking, etc. Your options are to not translate those strings, or spread a bunch of `` tags amongst the pure substrings of that string. ## Gathering Strings to Translate diff --git a/v1/examples/basics/core/Footer.js b/v1/examples/basics/core/Footer.js index a9595b6f3eb6..7a13d73275c9 100644 --- a/v1/examples/basics/core/Footer.js +++ b/v1/examples/basics/core/Footer.js @@ -10,7 +10,10 @@ const React = require('react'); class Footer extends React.Component { docUrl(doc, language) { const baseUrl = this.props.config.baseUrl; - return `${baseUrl}docs/${language ? `${language}/` : ''}${doc}`; + const docsUrl = this.props.config.docsUrl; + const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; + const langPart = `${language ? `${language}/` : ''}`; + return `${baseUrl}${docsPart}${langPart}${doc}`; } pageUrl(doc, language) { diff --git a/v1/examples/basics/pages/en/help.js b/v1/examples/basics/pages/en/help.js index 512e335a99cb..2b790e46037f 100755 --- a/v1/examples/basics/pages/en/help.js +++ b/v1/examples/basics/pages/en/help.js @@ -12,47 +12,43 @@ const CompLibrary = require('../../core/CompLibrary.js'); const Container = CompLibrary.Container; const GridBlock = CompLibrary.GridBlock; -const siteConfig = require(`${process.cwd()}/siteConfig.js`); - -function docUrl(doc, language) { - return `${siteConfig.baseUrl}docs/${language ? `${language}/` : ''}${doc}`; -} - -class Help extends React.Component { - render() { - const language = this.props.language || ''; - const supportLinks = [ - { - content: `Learn more using the [documentation on this site.](${docUrl( - 'doc1.html', - language, - )})`, - title: 'Browse Docs', - }, - { - content: 'Ask questions about the documentation and project', - title: 'Join the community', - }, - { - content: "Find out what's new with this project", - title: 'Stay up to date', - }, - ]; - - return ( -

- -
-
-

Need help?

-
-

This project is maintained by a dedicated group of people.

- -
-
-
- ); - } +function Help(props) { + const {config: siteConfig, language = ''} = props; + const {baseUrl, docsUrl} = siteConfig; + const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; + const langPart = `${language ? `${language}/` : ''}`; + const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`; + + const supportLinks = [ + { + content: `Learn more using the [documentation on this site.](${docUrl( + 'doc1.html', + )})`, + title: 'Browse Docs', + }, + { + content: 'Ask questions about the documentation and project', + title: 'Join the community', + }, + { + content: "Find out what's new with this project", + title: 'Stay up to date', + }, + ]; + + return ( +
+ +
+
+

Need help?

+
+

This project is maintained by a dedicated group of people.

+ +
+
+
+ ); } module.exports = Help; diff --git a/v1/examples/basics/pages/en/index.js b/v1/examples/basics/pages/en/index.js index 62f284c8a462..8f61cde6aa23 100755 --- a/v1/examples/basics/pages/en/index.js +++ b/v1/examples/basics/pages/en/index.js @@ -13,77 +13,60 @@ const MarkdownBlock = CompLibrary.MarkdownBlock; /* Used to read markdown */ const Container = CompLibrary.Container; const GridBlock = CompLibrary.GridBlock; -const siteConfig = require(`${process.cwd()}/siteConfig.js`); +class HomeSplash extends React.Component { + render() { + const {siteConfig, language = ''} = this.props; + const {baseUrl, docsUrl} = siteConfig; + const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; + const langPart = `${language ? `${language}/` : ''}`; + const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`; + + const SplashContainer = props => ( +
+
+
{props.children}
+
+
+ ); -function imgUrl(img) { - return `${siteConfig.baseUrl}img/${img}`; -} + const Logo = props => ( +
+ Project Logo +
+ ); -function docUrl(doc, language) { - return `${siteConfig.baseUrl}docs/${language ? `${language}/` : ''}${doc}`; -} + const ProjectTitle = () => ( +

+ {siteConfig.title} + {siteConfig.tagline} +

+ ); -function pageUrl(page, language) { - return siteConfig.baseUrl + (language ? `${language}/` : '') + page; -} + const PromoSection = props => ( +
+
+
{props.children}
+
+
+ ); -class Button extends React.Component { - render() { - return ( + const Button = props => (
- - {this.props.children} + + {props.children}
); - } -} - -Button.defaultProps = { - target: '_self', -}; - -const SplashContainer = props => ( -
-
-
{props.children}
-
-
-); - -const Logo = props => ( -
- Project Logo -
-); - -const ProjectTitle = () => ( -

- {siteConfig.title} - {siteConfig.tagline} -

-); - -const PromoSection = props => ( -
-
-
{props.children}
-
-
-); -class HomeSplash extends React.Component { - render() { - const language = this.props.language || ''; return ( - +
- + - - + +
@@ -91,121 +74,131 @@ class HomeSplash extends React.Component { } } -const Block = props => ( - - - -); - -const Features = () => ( - - {[ - { - content: 'This is the content of my feature', - image: imgUrl('docusaurus.svg'), - imageAlign: 'top', - title: 'Feature One', - }, - { - content: 'The content of my second feature', - image: imgUrl('docusaurus.svg'), - imageAlign: 'top', - title: 'Feature Two', - }, - ]} - -); - -const FeatureCallout = () => ( -
-

Feature Callout

- These are features of this project -
-); - -const LearnHow = () => ( - - {[ - { - content: 'Talk about learning how to use this', - image: imgUrl('docusaurus.svg'), - imageAlign: 'right', - title: 'Learn How', - }, - ]} - -); - -const TryOut = () => ( - - {[ - { - content: 'Talk about trying this out', - image: imgUrl('docusaurus.svg'), - imageAlign: 'left', - title: 'Try it Out', - }, - ]} - -); - -const Description = () => ( - - {[ - { - content: 'This is another description of how this project is useful', - image: imgUrl('docusaurus.svg'), - imageAlign: 'right', - title: 'Description', - }, - ]} - -); - -const Showcase = props => { - if ((siteConfig.users || []).length === 0) { - return null; - } +class Index extends React.Component { + render() { + const {config: siteConfig, language = ''} = this.props; + const {baseUrl} = siteConfig; + + const Block = props => ( + + + + ); - const showcase = siteConfig.users.filter(user => user.pinned).map(user => ( - - {user.caption} - - )); - - return ( -
-

Who is Using This?

-

This project is used by all these people

-
{showcase}
-
- - More {siteConfig.title} Users - + const FeatureCallout = () => ( +
+

Feature Callout

+ These are features of this project
-
- ); -}; + ); -class Index extends React.Component { - render() { - const language = this.props.language || ''; + const TryOut = () => ( + + {[ + { + content: 'Talk about trying this out', + image: `${baseUrl}img/docusaurus.svg`, + imageAlign: 'left', + title: 'Try it Out', + }, + ]} + + ); + + const Description = () => ( + + {[ + { + content: + 'This is another description of how this project is useful', + image: `${baseUrl}img/docusaurus.svg`, + imageAlign: 'right', + title: 'Description', + }, + ]} + + ); + + const LearnHow = () => ( + + {[ + { + content: 'Talk about learning how to use this', + image: `${baseUrl}img/docusaurus.svg`, + imageAlign: 'right', + title: 'Learn How', + }, + ]} + + ); + + const Features = () => ( + + {[ + { + content: 'This is the content of my feature', + image: `${baseUrl}img/docusaurus.svg`, + imageAlign: 'top', + title: 'Feature One', + }, + { + content: 'The content of my second feature', + image: `${baseUrl}img/docusaurus.svg`, + imageAlign: 'top', + title: 'Feature Two', + }, + ]} + + ); + + const Showcase = () => { + if ((siteConfig.users || []).length === 0) { + return null; + } + + const showcase = siteConfig.users + .filter(user => user.pinned) + .map(user => ( + + {user.caption} + + )); + + const pageUrl = page => baseUrl + (language ? `${language}/` : '') + page; + + return ( +
+

Who is Using This?

+

This project is used by all these people

+
{showcase}
+
+ + More {siteConfig.title} Users + +
+
+ ); + }; return (
- +
- +
); diff --git a/v1/examples/basics/pages/en/users.js b/v1/examples/basics/pages/en/users.js index b03fb812addf..039dc39ffa50 100644 --- a/v1/examples/basics/pages/en/users.js +++ b/v1/examples/basics/pages/en/users.js @@ -11,10 +11,9 @@ const CompLibrary = require('../../core/CompLibrary.js'); const Container = CompLibrary.Container; -const siteConfig = require(`${process.cwd()}/siteConfig.js`); - class Users extends React.Component { render() { + const {config: siteConfig} = this.props; if ((siteConfig.users || []).length === 0) { return null; } diff --git a/v1/examples/versions/pages/en/versions.js b/v1/examples/versions/pages/en/versions.js index 286464fc177c..b3f7e8fbdd4a 100644 --- a/v1/examples/versions/pages/en/versions.js +++ b/v1/examples/versions/pages/en/versions.js @@ -13,10 +13,10 @@ const Container = CompLibrary.Container; const CWD = process.cwd(); -const siteConfig = require(`${CWD}/siteConfig.js`); const versions = require(`${CWD}/versions.json`); -function Versions() { +function Versions(props) { + const {config: siteConfig} = props; const latestVersion = versions[0]; const repoUrl = `https://github.com/${siteConfig.organizationName}/${ siteConfig.projectName diff --git a/v1/lib/__tests__/build-files.test.js b/v1/lib/__tests__/build-files.test.js index 127f87fe3217..2d08755879ef 100644 --- a/v1/lib/__tests__/build-files.test.js +++ b/v1/lib/__tests__/build-files.test.js @@ -17,7 +17,9 @@ const CWD = process.cwd(); const utils = require('../server/utils'); -const siteConfig = require(`${CWD}/website/siteConfig.js`); +const loadConfig = require('../server/config'); + +const siteConfig = loadConfig(`${CWD}/website/siteConfig.js`); const buildDir = `${CWD}/website/build`; const docsDir = `${CWD}/docs`; const staticCSSDir = `${CWD}/website/static/css`; diff --git a/v1/lib/core/nav/HeaderNav.js b/v1/lib/core/nav/HeaderNav.js index 177a84ce0f5b..aa49f0542e5b 100644 --- a/v1/lib/core/nav/HeaderNav.js +++ b/v1/lib/core/nav/HeaderNav.js @@ -11,7 +11,9 @@ const React = require('react'); const fs = require('fs'); const classNames = require('classnames'); -const siteConfig = require(`${CWD}/siteConfig.js`); +const loadConfig = require('../../server/config'); + +const siteConfig = loadConfig(`${CWD}/siteConfig.js`); const translation = require('../../server/translation.js'); const env = require('../../server/env.js'); @@ -33,6 +35,7 @@ class LanguageDropDown extends React.Component { const helpTranslateString = translate( 'Help Translate|recruit community translators for your project', ); + const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`; // add all enabled languages to dropdown const enabledLanguages = env.translation .enabledLanguages() @@ -48,8 +51,8 @@ class LanguageDropDown extends React.Component { href = siteConfig.baseUrl + this.props.current.permalink.replace( - `/${this.props.language}/`, - `/${lang.tag}/`, + new RegExp(`^${docsPart}${this.props.language}/`), + `${docsPart}${lang.tag}/`, ); } else if (this.props.current.id && this.props.current.id !== 'index') { href = `${siteConfig.baseUrl + lang.tag}/${this.props.current.id}`; diff --git a/v1/lib/server/__tests__/docs.test.js b/v1/lib/server/__tests__/docs.test.js index 38d2664273a2..0b496175549b 100644 --- a/v1/lib/server/__tests__/docs.test.js +++ b/v1/lib/server/__tests__/docs.test.js @@ -66,7 +66,11 @@ const rawContent3 = metadataUtils.extractMetadata(doc3).rawContent; const rawContentRefLinks = metadataUtils.extractMetadata(refLinks).rawContent; describe('mdToHtmlify', () => { - const mdToHtml = metadataUtils.mdToHtml(Metadata, '/'); + const siteConfig = { + baseUrl: '/', + docsUrl: 'docs', + }; + const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig); test('transform nothing', () => { const content1 = docs.mdToHtmlify( @@ -100,7 +104,7 @@ describe('mdToHtmlify', () => { language: 'en', }, }; - const customMdToHtml = metadataUtils.mdToHtml(customMetadata, '/'); + const customMdToHtml = metadataUtils.mdToHtml(customMetadata, siteConfig); const content3 = docs.mdToHtmlify( rawContent3, customMdToHtml, diff --git a/v1/lib/server/__tests__/readMetadata.test.js b/v1/lib/server/__tests__/readMetadata.test.js index 97ec2702a0bc..c89bf237da39 100644 --- a/v1/lib/server/__tests__/readMetadata.test.js +++ b/v1/lib/server/__tests__/readMetadata.test.js @@ -30,7 +30,7 @@ jest.mock('../env', () => ({ }, })); -jest.mock(`${process.cwd()}/siteConfig.js`, () => true, {virtual: true}); +jest.mock(`${process.cwd()}/siteConfig.js`, () => ({}), {virtual: true}); jest.mock(`${process.cwd()}/sidebar.json`, () => true, {virtual: true}); describe('readMetadata', () => { diff --git a/v1/lib/server/__tests__/routing.test.js b/v1/lib/server/__tests__/routing.test.js index 594c5206ab6a..ae3d2b06417b 100644 --- a/v1/lib/server/__tests__/routing.test.js +++ b/v1/lib/server/__tests__/routing.test.js @@ -4,11 +4,11 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -const routing = require('../routing'); +const routing = require('../routing.js'); describe('Blog routing', () => { - const blogRegex = routing.blog('/'); - const blogRegex2 = routing.blog('/react/'); + const blogRegex = routing.blog({baseUrl: '/'}); + const blogRegex2 = routing.blog({baseUrl: '/react/'}); test('valid blog', () => { expect('/blog/test.html').toMatch(blogRegex); @@ -34,8 +34,8 @@ describe('Blog routing', () => { }); describe('Docs routing', () => { - const docsRegex = routing.docs('/'); - const docsRegex2 = routing.docs('/reason/'); + const docsRegex = routing.docs({baseUrl: '/', docsUrl: 'docs'}); + const docsRegex2 = routing.docs({baseUrl: '/reason/', docsUrl: 'docs'}); test('valid docs', () => { expect('/docs/en/test.html').toMatch(docsRegex); @@ -87,8 +87,8 @@ describe('Dot routing', () => { }); describe('Feed routing', () => { - const feedRegex = routing.feed('/'); - const feedRegex2 = routing.feed('/reason/'); + const feedRegex = routing.feed({baseUrl: '/'}); + const feedRegex2 = routing.feed({baseUrl: '/reason/'}); test('valid feed url', () => { expect('/blog/atom.xml').toMatch(feedRegex); @@ -137,8 +137,8 @@ describe('Extension-less url routing', () => { }); describe('Page routing', () => { - const pageRegex = routing.page('/'); - const pageRegex2 = routing.page('/reason/'); + const pageRegex = routing.page({baseUrl: '/', docsUrl: 'docs'}); + const pageRegex2 = routing.page({baseUrl: '/reason/', docsUrl: 'docs'}); test('valid page url', () => { expect('/index.html').toMatch(pageRegex); @@ -164,8 +164,8 @@ describe('Page routing', () => { }); describe('Sitemap routing', () => { - const sitemapRegex = routing.sitemap('/'); - const sitemapRegex2 = routing.sitemap('/reason/'); + const sitemapRegex = routing.sitemap({baseUrl: '/'}); + const sitemapRegex2 = routing.sitemap({baseUrl: '/reason/'}); test('valid sitemap url', () => { expect('/sitemap.xml').toMatch(sitemapRegex); diff --git a/v1/lib/server/config.js b/v1/lib/server/config.js new file mode 100644 index 000000000000..714d76fd0589 --- /dev/null +++ b/v1/lib/server/config.js @@ -0,0 +1,31 @@ +/** + * 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 fs = require('fs-extra'); + +module.exports = function loadConfig(configPath, deleteCache = true) { + if (deleteCache) { + delete require.cache[configPath]; + } + let config = {}; + if (fs.existsSync(configPath)) { + config = require(configPath); // eslint-disable-line + } + + /* Fill default value */ + const defaultConfig = { + customDocsPath: 'docs', + docsUrl: 'docs', + }; + Object.keys(defaultConfig).forEach(field => { + if (!(field in config)) { + config[field] = defaultConfig[field]; + } + }); + + return config; +}; diff --git a/v1/lib/server/docs.js b/v1/lib/server/docs.js index 3371a7928829..825cb1474aae 100644 --- a/v1/lib/server/docs.js +++ b/v1/lib/server/docs.js @@ -5,16 +5,20 @@ * LICENSE file in the root directory of this source tree. */ const CWD = process.cwd(); -const siteConfig = require(`${CWD}/siteConfig.js`); const {join} = require('path'); const fs = require('fs-extra'); const React = require('react'); +const loadConfig = require('./config'); + +const siteConfig = loadConfig(`${CWD}/siteConfig.js`); const env = require('./env.js'); const {renderToStaticMarkupWithDoctype} = require('./renderUtils'); const readMetadata = require('./readMetadata.js'); const {insertTOC} = require('../core/toc.js'); const {getPath} = require('../core/utils.js'); +const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`; + function getFilePath(metadata) { if (!metadata) { return null; @@ -125,7 +129,10 @@ function replaceAssetsLink(oldContent) { } return fencedBlock ? line - : line.replace(/\]\(assets\//g, `](${siteConfig.baseUrl}docs/assets/`); + : line.replace( + /\]\(assets\//g, + `](${siteConfig.baseUrl}${docsPart}assets/`, + ); }); return lines.join('\n'); } @@ -152,7 +159,10 @@ function getMarkup(rawContent, mdToHtml, metadata) { } function getRedirectMarkup(metadata) { - if (!env.translation.enabled || !metadata.permalink.includes('docs/en')) { + if ( + !env.translation.enabled || + !metadata.permalink.includes(`${docsPart}en`) + ) { return null; } const Redirect = require('../core/Redirect.js'); diff --git a/v1/lib/server/generate.js b/v1/lib/server/generate.js index 7160d0c315b8..ff3019843788 100644 --- a/v1/lib/server/generate.js +++ b/v1/lib/server/generate.js @@ -21,7 +21,8 @@ async function execute() { const chalk = require('chalk'); const Site = require('../core/Site.js'); const env = require('./env.js'); - const siteConfig = require(`${CWD}/siteConfig.js`); + const loadConfig = require('./config.js'); + const siteConfig = loadConfig(`${CWD}/siteConfig.js`); const translate = require('./translate.js'); const feed = require('./feed.js'); const sitemap = require('./sitemap.js'); @@ -68,7 +69,7 @@ async function execute() { fs.removeSync(join(CWD, 'build')); // create html files for all docs by going through all doc ids - const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig.baseUrl); + const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig); Object.keys(Metadata).forEach(id => { const metadata = Metadata[id]; const file = docs.getFile(metadata); @@ -85,9 +86,13 @@ async function execute() { if (!redirectMarkup) { return; } + const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`; const redirectFile = join( buildDir, - metadata.permalink.replace('docs/en', 'docs'), + metadata.permalink.replace( + new RegExp(`^${docsPart}en`), + siteConfig.docsUrl, + ), ); writeFileAndCreateFolder(redirectFile, redirectMarkup); }); @@ -332,7 +337,7 @@ async function execute() { title={ReactComp.title} description={ReactComp.description} metadata={{id: pageID}}> - + , ); writeFileAndCreateFolder( @@ -353,7 +358,7 @@ async function execute() { config={siteConfig} description={ReactComp.description} metadata={{id: pageID}}> - + , ); writeFileAndCreateFolder( @@ -371,7 +376,7 @@ async function execute() { config={siteConfig} description={ReactComp.description} metadata={{id: pageID}}> - + , ); writeFileAndCreateFolder( diff --git a/v1/lib/server/metadataUtils.js b/v1/lib/server/metadataUtils.js index c868379f1dc4..77eaff9ec672 100644 --- a/v1/lib/server/metadataUtils.js +++ b/v1/lib/server/metadataUtils.js @@ -65,7 +65,8 @@ function extractMetadata(content) { // 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 -function mdToHtml(Metadata, baseUrl) { +function mdToHtml(Metadata, siteConfig) { + const {baseUrl, docsUrl} = siteConfig; const result = {}; Object.keys(Metadata).forEach(id => { const metadata = Metadata[id]; @@ -73,10 +74,15 @@ function mdToHtml(Metadata, baseUrl) { return; } let htmlLink = baseUrl + metadata.permalink.replace('/next/', '/'); - if (htmlLink.includes('/docs/en/')) { - htmlLink = htmlLink.replace('/docs/en/', '/docs/en/VERSION/'); + + const baseDocsPart = `${baseUrl}${docsUrl ? `${docsUrl}/` : ''}`; + + const i18nDocsRegex = new RegExp(`^${baseDocsPart}en/`); + const docsRegex = new RegExp(`^${baseDocsPart}`); + if (i18nDocsRegex.test(htmlLink)) { + htmlLink = htmlLink.replace(i18nDocsRegex, `${baseDocsPart}en/VERSION/`); } else { - htmlLink = htmlLink.replace('/docs/', '/docs/VERSION/'); + htmlLink = htmlLink.replace(docsRegex, `${baseDocsPart}VERSION/`); } result[metadata.source] = htmlLink; }); diff --git a/v1/lib/server/readMetadata.js b/v1/lib/server/readMetadata.js index c916809b8c2b..7c2048dd44af 100644 --- a/v1/lib/server/readMetadata.js +++ b/v1/lib/server/readMetadata.js @@ -16,10 +16,14 @@ const metadataUtils = require('./metadataUtils'); const env = require('./env.js'); const blog = require('./blog.js'); -const siteConfig = require(`${CWD}/siteConfig.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', @@ -166,7 +170,9 @@ function processMetadata(file, refDir) { versionPart = 'next/'; } - metadata.permalink = `docs/${langPart}${versionPart}${metadata.id}.html`; + metadata.permalink = `${docsPart}${langPart}${versionPart}${ + metadata.id + }.html`; // change ids previous, next metadata.localized_id = metadata.id; @@ -238,18 +244,24 @@ function generateMetadataDocs() { baseMetadata.id = baseMetadata.id .toString() .replace(/^en-/, `${currentLanguage}-`); - if (baseMetadata.permalink) + if (baseMetadata.permalink) { baseMetadata.permalink = baseMetadata.permalink .toString() - .replace(/^docs\/en\//, `docs/${currentLanguage}/`); - if (baseMetadata.next) + .replace( + new RegExp(`^${docsPart}en/`), + `${docsPart}${currentLanguage}/`, + ); + } + if (baseMetadata.next) { baseMetadata.next = baseMetadata.next .toString() .replace(/^en-/, `${currentLanguage}-`); - if (baseMetadata.previous) + } + if (baseMetadata.previous) { baseMetadata.previous = baseMetadata.previous .toString() .replace(/^en-/, `${currentLanguage}-`); + } baseMetadata.language = currentLanguage; defaultMetadatas[baseMetadata.id] = baseMetadata; }); diff --git a/v1/lib/server/routing.js b/v1/lib/server/routing.js index cd5d99815d1c..156ccec3ce15 100644 --- a/v1/lib/server/routing.js +++ b/v1/lib/server/routing.js @@ -4,35 +4,44 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -function blog(baseUrl) { - return new RegExp(`^${baseUrl}blog/.*html$`); +function blog(siteConfig) { + return new RegExp(`^${siteConfig.baseUrl}blog/.*html$`); } -function docs(baseUrl) { - return new RegExp(`^${baseUrl}docs/.*html$`); +function docs(siteConfig) { + const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`; + return new RegExp(`^${siteConfig.baseUrl}${docsPart}.*html$`); } function dotfiles() { return /(?!.*html$)^\/.*\.[^\n/]+$/; } -function feed(baseUrl) { - return new RegExp(`^${baseUrl}blog/(feed.xml|atom.xml)$`); +function feed(siteConfig) { + return new RegExp(`^${siteConfig.baseUrl}blog/(feed.xml|atom.xml)$`); } function noExtension() { return /\/[^.]*\/?$/; } -function page(baseUrl) { +function page(siteConfig) { const gr = regex => regex.toString().replace(/(^\/|\/$)/gm, ''); + + if (siteConfig.docsUrl === '') { + return new RegExp( + `(?!${gr(blog(siteConfig))})^${siteConfig.baseUrl}.*.html$`, + ); + } return new RegExp( - `(?!${gr(docs(baseUrl))}|${gr(blog(baseUrl))})^${baseUrl}.*.html$`, + `(?!${gr(blog(siteConfig))}|${gr(docs(siteConfig))})^${ + siteConfig.baseUrl + }.*.html$`, ); } -function sitemap(baseUrl) { - return new RegExp(`^${baseUrl}sitemap.xml$`); +function sitemap(siteConfig) { + return new RegExp(`^${siteConfig.baseUrl}sitemap.xml$`); } module.exports = { diff --git a/v1/lib/server/server.js b/v1/lib/server/server.js index 8b0d14e83dbe..4273e988f370 100644 --- a/v1/lib/server/server.js +++ b/v1/lib/server/server.js @@ -27,6 +27,7 @@ function execute(port) { const feed = require('./feed'); const sitemap = require('./sitemap'); const routing = require('./routing'); + const loadConfig = require('./config'); const CWD = process.cwd(); const join = path.join; const sep = path.sep; @@ -76,12 +77,9 @@ function execute(port) { } function reloadSiteConfig() { - removeModuleAndChildrenFromCache(join(CWD, 'siteConfig.js')); - siteConfig = require(join(CWD, 'siteConfig.js')); - - if (siteConfig.highlight && siteConfig.highlight.hljs) { - siteConfig.highlight.hljs(require('highlight.js')); - } + const siteConfigPath = join(CWD, 'siteConfig.js'); + removeModuleAndChildrenFromCache(siteConfigPath); + siteConfig = loadConfig(siteConfigPath); } function requestFile(url, res, notFoundCallback) { @@ -109,12 +107,13 @@ function execute(port) { const app = express(); - app.get(routing.docs(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.docs(siteConfig), (req, res, next) => { const url = decodeURI(req.path.toString().replace(siteConfig.baseUrl, '')); const metadata = Metadata[ Object.keys(Metadata).find(id => Metadata[id].permalink === url) ]; + const file = docs.getFile(metadata); if (!file) { next(); @@ -122,11 +121,11 @@ function execute(port) { } const rawContent = metadataUtils.extractMetadata(file).rawContent; removeModuleAndChildrenFromCache('../core/DocsLayout.js'); - const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig.baseUrl); + const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig); res.send(docs.getMarkup(rawContent, mdToHtml, metadata)); }); - app.get(routing.sitemap(siteConfig.baseUrl), (req, res) => { + app.get(routing.sitemap(siteConfig), (req, res) => { sitemap((err, xml) => { if (err) { res.status(500).send('Sitemap error'); @@ -137,7 +136,7 @@ function execute(port) { }); }); - app.get(routing.feed(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.feed(siteConfig), (req, res, next) => { res.set('Content-Type', 'application/rss+xml'); const file = req.path .toString() @@ -151,7 +150,7 @@ function execute(port) { next(); }); - app.get(routing.blog(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.blog(siteConfig), (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(); @@ -177,7 +176,7 @@ function execute(port) { } }); - app.get(routing.page(siteConfig.baseUrl), (req, res, next) => { + app.get(routing.page(siteConfig), (req, res, next) => { // Look for user-provided HTML file first. let htmlFile = req.path.toString().replace(siteConfig.baseUrl, ''); htmlFile = join(CWD, 'pages', htmlFile); @@ -232,6 +231,7 @@ function execute(port) { language = parts[i]; } } + let englishFile = join(CWD, 'pages', file); if (language && language !== 'en') { englishFile = englishFile.replace(sep + language + sep, `${sep}en${sep}`); @@ -272,7 +272,7 @@ function execute(port) { title={ReactComp.title} description={ReactComp.description} metadata={{id: path.basename(userFile, '.js')}}> - + , ); @@ -339,7 +339,9 @@ function execute(port) { // serve static assets from these locations app.use( - `${siteConfig.baseUrl}docs/assets`, + `${siteConfig.baseUrl}${ + siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : '' + }assets`, express.static(join(CWD, '..', readMetadata.getDocsPath(), 'assets')), ); app.use( diff --git a/v1/lib/server/sitemap.js b/v1/lib/server/sitemap.js index 1ae41a5fd554..7da57493bea0 100644 --- a/v1/lib/server/sitemap.js +++ b/v1/lib/server/sitemap.js @@ -14,7 +14,9 @@ const CWD = process.cwd(); const sitemap = require('sitemap'); const utils = require('../core/utils'); -const siteConfig = require(`${CWD}/siteConfig.js`); +const loadConfig = require('./config'); + +const siteConfig = loadConfig(`${CWD}/siteConfig.js`); const readMetadata = require('./readMetadata.js'); @@ -70,8 +72,12 @@ module.exports = function(callback) { .forEach(key => { const doc = Metadata[key]; const docUrl = utils.getPath(doc.permalink, siteConfig.cleanUrl); + const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`; const links = enabledLanguages.map(lang => { - const langUrl = docUrl.replace('docs/en/', `docs/${lang.tag}/`); + const langUrl = docUrl.replace( + new RegExp(`^${docsPart}en/`), + `${docsPart}${lang.tag}/`, + ); return {lang: lang.tag, url: langUrl}; }); urls.push({ diff --git a/v1/lib/server/versionFallback.js b/v1/lib/server/versionFallback.js index 914325fb54b9..cf001a4d3490 100644 --- a/v1/lib/server/versionFallback.js +++ b/v1/lib/server/versionFallback.js @@ -14,8 +14,9 @@ const metadataUtils = require('./metadataUtils'); const env = require('./env.js'); const utils = require('./utils.js'); +const loadConfig = require('./config'); -const siteConfig = require(`${CWD}/siteConfig.js`); +const siteConfig = loadConfig(`${CWD}/siteConfig.js`); const ENABLE_TRANSLATION = fs.existsSync(`${CWD}/languages.js`); @@ -187,14 +188,16 @@ function processVersionMetadata(file, version, useVersion, language) { const latestVersion = versions[0]; + const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`; + const versionPart = `${version !== latestVersion ? `${version}/` : ''}`; if (!ENABLE_TRANSLATION && !siteConfig.useEnglishUrl) { - metadata.permalink = `docs/${ - version !== latestVersion ? `${version}/` : '' - }${metadata.original_id}.html`; + metadata.permalink = `${docsPart}${versionPart}${ + metadata.original_id + }.html`; } else { - metadata.permalink = `docs/${language}/${ - version !== latestVersion ? `${version}/` : '' - }${metadata.original_id}.html`; + metadata.permalink = `${docsPart}${language}/${versionPart}${ + metadata.original_id + }.html`; } metadata.id = metadata.id.replace( `version-${useVersion}-`, diff --git a/v1/website/core/Footer.js b/v1/website/core/Footer.js index e78beb52d712..eaf9f2b05bac 100644 --- a/v1/website/core/Footer.js +++ b/v1/website/core/Footer.js @@ -58,6 +58,9 @@ SocialFooter.propTypes = { class Footer extends React.Component { render() { + const docsPart = `${ + this.props.config.docsUrl ? `${this.props.config.docsUrl}/` : '' + }`; return (