feat: Allow modifying docs url prefix (#914)

* Allow other routes than /docs in the URL

siteConfig.js has a new mandatory field named *docsRoute* which default
value is 'docs' and that can be customized by the user.

This change will allow users who uses the library to host guides and
tutorials to customize their websites by assign 'docsRoute' values
like 'tutorials' or 'guides'.

Fixes #879

* Make "docsRoute" field optional

* Isolate docsRoute login in getDocsRoute function

* Rename docsRoute to docsUrl

* Run prettier

* Remove old folders

* fix: Restore docusaurus reference link

* fix: Add `docsUrl` param fallback. Refactor multiple function calls

* Fix linting errors

* Update description for docsUrl field

* Reduce redundant calls to getDocsUrl

* Replace a missed use case for `docsUrl` instead of the function call

* Move `getDocsUrl` out from `server/routing.js` to `server/utils.js`

**Why?**
Because `routing.js` is exporting all router RegEx's, and the
`getDocsUrl` suffices more as a util

* WiP: Align leading slashes and fix routing around `docsUrl`

Checklist:
- [x] Added `removeDuplicateLeadingSlashes` util to make sure there is only
one leading slash
- [-] Fix edge cases for routing:
  - [x] `docsUrl: ''`
  - [ ] `docsUrl: '/'`
  - [ ] make it work with languages
  - [ ] make it work with versioning

* Make leading slashes canonical cross routing and generated links

This ensures correct routing for customized `baseUrl` and `docsUrl`.

- Changed all routing functions to take `siteConfig` instead of
`siteConfig.baseUrl`
- Updated tests accordingly

* Alternative fallback for `docsUrl`

* rework/ fix implementation

* cleanup

* refactor and add docs for config props

* fix typo

* fix broken url
This commit is contained in:
Dom Corvasce 2018-11-28 08:34:16 +01:00 committed by Endilie Yacop Sucipto
parent ff22074ff7
commit 61078e38a9
28 changed files with 423 additions and 319 deletions

View file

@ -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 <div>{siteConfig.title}</div>;
}
}
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',

View file

@ -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 `<link>`
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.

View file

@ -65,7 +65,7 @@ You can also include an optional description attribute to give more context to a
<p>
```
> The `<translate>` 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 `<translation>` 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 `<translate>` tags amongst the pure substrings of that string.
> The `<translate>` 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 `<translation>` 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 `<translate>` tags amongst the pure substrings of that string.
## Gathering Strings to Translate

View file

@ -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) {

View file

@ -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 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}`;
function docUrl(doc, language) {
return `${siteConfig.baseUrl}docs/${language ? `${language}/` : ''}${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',
},
];
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 (
<div className="docMainWrapper wrapper">
<Container className="mainContainer documentContainer postContainer">
<div className="post">
<header className="postHeader">
<h1>Need help?</h1>
</header>
<p>This project is maintained by a dedicated group of people.</p>
<GridBlock contents={supportLinks} layout="threeColumn" />
</div>
</Container>
</div>
);
}
return (
<div className="docMainWrapper wrapper">
<Container className="mainContainer documentContainer postContainer">
<div className="post">
<header className="postHeader">
<h1>Need help?</h1>
</header>
<p>This project is maintained by a dedicated group of people.</p>
<GridBlock contents={supportLinks} layout="threeColumn" />
</div>
</Container>
</div>
);
}
module.exports = Help;

View file

@ -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`);
function imgUrl(img) {
return `${siteConfig.baseUrl}img/${img}`;
}
function docUrl(doc, language) {
return `${siteConfig.baseUrl}docs/${language ? `${language}/` : ''}${doc}`;
}
function pageUrl(page, language) {
return siteConfig.baseUrl + (language ? `${language}/` : '') + page;
}
class Button extends React.Component {
class HomeSplash extends React.Component {
render() {
return (
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 => (
<div className="homeContainer">
<div className="homeSplashFade">
<div className="wrapper homeWrapper">{props.children}</div>
</div>
</div>
);
const Logo = props => (
<div className="projectLogo">
<img src={props.img_src} alt="Project Logo" />
</div>
);
const ProjectTitle = () => (
<h2 className="projectTitle">
{siteConfig.title}
<small>{siteConfig.tagline}</small>
</h2>
);
const PromoSection = props => (
<div className="section promoSection">
<div className="promoRow">
<div className="pluginRowBlock">{props.children}</div>
</div>
</div>
);
const Button = props => (
<div className="pluginWrapper buttonWrapper">
<a className="button" href={this.props.href} target={this.props.target}>
{this.props.children}
<a className="button" href={props.href} target={props.target}>
{props.children}
</a>
</div>
);
}
}
Button.defaultProps = {
target: '_self',
};
const SplashContainer = props => (
<div className="homeContainer">
<div className="homeSplashFade">
<div className="wrapper homeWrapper">{props.children}</div>
</div>
</div>
);
const Logo = props => (
<div className="projectLogo">
<img src={props.img_src} alt="Project Logo" />
</div>
);
const ProjectTitle = () => (
<h2 className="projectTitle">
{siteConfig.title}
<small>{siteConfig.tagline}</small>
</h2>
);
const PromoSection = props => (
<div className="section promoSection">
<div className="promoRow">
<div className="pluginRowBlock">{props.children}</div>
</div>
</div>
);
class HomeSplash extends React.Component {
render() {
const language = this.props.language || '';
return (
<SplashContainer>
<Logo img_src={imgUrl('docusaurus.svg')} />
<Logo img_src={`${baseUrl}img/docusaurus.svg`} />
<div className="inner">
<ProjectTitle />
<ProjectTitle siteConfig={siteConfig} />
<PromoSection>
<Button href="#try">Try It Out</Button>
<Button href={docUrl('doc1.html', language)}>Example Link</Button>
<Button href={docUrl('doc2.html', language)}>Example Link 2</Button>
<Button href={docUrl('doc1.html')}>Example Link</Button>
<Button href={docUrl('doc2.html')}>Example Link 2</Button>
</PromoSection>
</div>
</SplashContainer>
@ -91,121 +74,131 @@ class HomeSplash extends React.Component {
}
}
const Block = props => (
<Container
padding={['bottom', 'top']}
id={props.id}
background={props.background}>
<GridBlock align="center" contents={props.children} layout={props.layout} />
</Container>
);
const Features = () => (
<Block layout="fourColumn">
{[
{
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',
},
]}
</Block>
);
const FeatureCallout = () => (
<div
className="productShowcaseSection paddingBottom"
style={{textAlign: 'center'}}>
<h2>Feature Callout</h2>
<MarkdownBlock>These are features of this project</MarkdownBlock>
</div>
);
const LearnHow = () => (
<Block background="light">
{[
{
content: 'Talk about learning how to use this',
image: imgUrl('docusaurus.svg'),
imageAlign: 'right',
title: 'Learn How',
},
]}
</Block>
);
const TryOut = () => (
<Block id="try">
{[
{
content: 'Talk about trying this out',
image: imgUrl('docusaurus.svg'),
imageAlign: 'left',
title: 'Try it Out',
},
]}
</Block>
);
const Description = () => (
<Block background="dark">
{[
{
content: 'This is another description of how this project is useful',
image: imgUrl('docusaurus.svg'),
imageAlign: 'right',
title: 'Description',
},
]}
</Block>
);
const Showcase = props => {
if ((siteConfig.users || []).length === 0) {
return null;
}
const showcase = siteConfig.users.filter(user => user.pinned).map(user => (
<a href={user.infoLink} key={user.infoLink}>
<img src={user.image} alt={user.caption} title={user.caption} />
</a>
));
return (
<div className="productShowcaseSection paddingBottom">
<h2>Who is Using This?</h2>
<p>This project is used by all these people</p>
<div className="logos">{showcase}</div>
<div className="more-users">
<a className="button" href={pageUrl('users.html', props.language)}>
More {siteConfig.title} Users
</a>
</div>
</div>
);
};
class Index extends React.Component {
render() {
const language = this.props.language || '';
const {config: siteConfig, language = ''} = this.props;
const {baseUrl} = siteConfig;
const Block = props => (
<Container
padding={['bottom', 'top']}
id={props.id}
background={props.background}>
<GridBlock
align="center"
contents={props.children}
layout={props.layout}
/>
</Container>
);
const FeatureCallout = () => (
<div
className="productShowcaseSection paddingBottom"
style={{textAlign: 'center'}}>
<h2>Feature Callout</h2>
<MarkdownBlock>These are features of this project</MarkdownBlock>
</div>
);
const TryOut = () => (
<Block id="try">
{[
{
content: 'Talk about trying this out',
image: `${baseUrl}img/docusaurus.svg`,
imageAlign: 'left',
title: 'Try it Out',
},
]}
</Block>
);
const Description = () => (
<Block background="dark">
{[
{
content:
'This is another description of how this project is useful',
image: `${baseUrl}img/docusaurus.svg`,
imageAlign: 'right',
title: 'Description',
},
]}
</Block>
);
const LearnHow = () => (
<Block background="light">
{[
{
content: 'Talk about learning how to use this',
image: `${baseUrl}img/docusaurus.svg`,
imageAlign: 'right',
title: 'Learn How',
},
]}
</Block>
);
const Features = () => (
<Block layout="fourColumn">
{[
{
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',
},
]}
</Block>
);
const Showcase = () => {
if ((siteConfig.users || []).length === 0) {
return null;
}
const showcase = siteConfig.users
.filter(user => user.pinned)
.map(user => (
<a href={user.infoLink} key={user.infoLink}>
<img src={user.image} alt={user.caption} title={user.caption} />
</a>
));
const pageUrl = page => baseUrl + (language ? `${language}/` : '') + page;
return (
<div className="productShowcaseSection paddingBottom">
<h2>Who is Using This?</h2>
<p>This project is used by all these people</p>
<div className="logos">{showcase}</div>
<div className="more-users">
<a className="button" href={pageUrl('users.html')}>
More {siteConfig.title} Users
</a>
</div>
</div>
);
};
return (
<div>
<HomeSplash language={language} />
<HomeSplash siteConfig={siteConfig} language={language} />
<div className="mainContainer">
<Features />
<FeatureCallout />
<LearnHow />
<TryOut />
<Description />
<Showcase language={language} />
<Showcase />
</div>
</div>
);

View file

@ -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;
}

View file

@ -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

View file

@ -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`;

View file

@ -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}`;

View file

@ -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,

View file

@ -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', () => {

View file

@ -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);

31
v1/lib/server/config.js Normal file
View file

@ -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;
};

View file

@ -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');

View file

@ -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}}>
<ReactComp language={language} />
<ReactComp config={siteConfig} language={language} />
</Site>,
);
writeFileAndCreateFolder(
@ -353,7 +358,7 @@ async function execute() {
config={siteConfig}
description={ReactComp.description}
metadata={{id: pageID}}>
<ReactComp language={language} />
<ReactComp config={siteConfig} language={language} />
</Site>,
);
writeFileAndCreateFolder(
@ -371,7 +376,7 @@ async function execute() {
config={siteConfig}
description={ReactComp.description}
metadata={{id: pageID}}>
<ReactComp language={language} />
<ReactComp config={siteConfig} language={language} />
</Site>,
);
writeFileAndCreateFolder(

View file

@ -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;
});

View file

@ -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;
});

View file

@ -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 = {

View file

@ -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')}}>
<ReactComp language={language} />
<ReactComp config={siteConfig} language={language} />
</Site>,
);
@ -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(

View file

@ -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({

View file

@ -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}-`,

View file

@ -58,6 +58,9 @@ SocialFooter.propTypes = {
class Footer extends React.Component {
render() {
const docsPart = `${
this.props.config.docsUrl ? `${this.props.config.docsUrl}/` : ''
}`;
return (
<footer className="nav-footer" id="footer">
<section className="sitemap">
@ -77,28 +80,28 @@ class Footer extends React.Component {
<h5>Docs</h5>
<a
href={`
${this.props.config.baseUrl}docs/${
${this.props.config.baseUrl}${docsPart}${
this.props.language
}/installation`}>
Getting Started
</a>
<a
href={`
${this.props.config.baseUrl}docs/${
${this.props.config.baseUrl}${docsPart}${
this.props.language
}/versioning`}>
Versioning
</a>
<a
href={`
${this.props.config.baseUrl}docs/${
${this.props.config.baseUrl}${docsPart}${
this.props.language
}/translation`}>
Localization
</a>
<a
href={`
${this.props.config.baseUrl}docs/${
${this.props.config.baseUrl}${docsPart}${
this.props.language
}/search`}>
Adding Search

View file

@ -11,11 +11,11 @@ const React = require('react');
const CompLibrary = require('../../core/CompLibrary.js');
const Container = CompLibrary.Container;
const siteConfig = require(`${process.cwd()}/siteConfig.js`);
const translate = require('../../server/translate.js').translate;
class AboutSlash extends React.Component {
render() {
const {config: siteConfig} = this.props;
return (
<div className="pageContainer">
<Container className="mainContainer documentContainer postContainer">

View file

@ -10,17 +10,19 @@ const CompLibrary = require('../../core/CompLibrary.js');
const Container = CompLibrary.Container;
const GridBlock = CompLibrary.GridBlock;
const siteConfig = require(`${process.cwd()}/siteConfig.js`);
const translate = require('../../server/translate.js').translate;
class Help extends React.Component {
render() {
const {config: siteConfig} = this.props;
const supportLinks = [
{
title: <translate>Browse the docs</translate>,
content: `Learn more about Docusaurus using the [official documentation](${
siteConfig.baseUrl
}docs/${this.props.language}/installation).`,
}${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}${
this.props.language
}/installation).`,
},
{
title: <translate>Discord</translate>,

View file

@ -12,27 +12,20 @@ const CompLibrary = require('../../core/CompLibrary.js');
const Container = CompLibrary.Container;
const GridBlock = CompLibrary.GridBlock;
const Showcase = require(`${process.cwd()}/core/Showcase.js`);
const siteConfig = require(`${process.cwd()}/siteConfig.js`);
const translate = require('../../server/translate.js').translate;
class Button extends React.Component {
render() {
return (
<div className="pluginWrapper buttonWrapper">
<a className="button" href={this.props.href} target={this.props.target}>
{this.props.children}
</a>
</div>
);
}
}
Button.defaultProps = {
target: '_self',
};
class HomeSplash extends React.Component {
render() {
const {siteConfig, language} = this.props;
const Button = props => (
<div className="pluginWrapper buttonWrapper">
<a className="button" href={props.href} target={props.target}>
{props.children}
</a>
</div>
);
return (
<div className="homeContainer">
<div className="homeSplashFade">
@ -53,9 +46,7 @@ class HomeSplash extends React.Component {
<div className="pluginRowBlock">
<Button
href={`
${siteConfig.baseUrl}docs/${
this.props.language
}/installation
${siteConfig.baseUrl}docs/${language}/installation
`}>
<translate>Get Started</translate>
</Button>
@ -75,12 +66,12 @@ class HomeSplash extends React.Component {
class Index extends React.Component {
render() {
const language = this.props.language || 'en';
const {config: siteConfig, language = 'en'} = this.props;
const pinnedUsersToShowcase = siteConfig.users.filter(user => user.pinned);
return (
<div>
<HomeSplash language={language} />
<HomeSplash siteConfig={siteConfig} language={language} />
<div className="mainContainer">
<Container padding={['bottom', 'top']} background="light">
<GridBlock

View file

@ -10,11 +10,11 @@ const CompLibrary = require('../../core/CompLibrary.js');
const Container = CompLibrary.Container;
const Showcase = require(`${process.cwd()}/core/Showcase.js`);
const siteConfig = require(`${process.cwd()}/siteConfig.js`);
const translate = require('../../server/translate.js').translate;
class Users extends React.Component {
render() {
const {config: siteConfig} = this.props;
const fbUsersToShowcase = siteConfig.users.filter(
user => user.fbOpenSource,
);

View file

@ -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(props) {
const {config: siteConfig} = props;
const latestVersion = versions[0];
const repoUrl = `https://github.com/${siteConfig.organizationName}/${
siteConfig.projectName
@ -36,7 +36,7 @@ function Versions(props) {
<th>{latestVersion}</th>
<td>
<a
href={`${siteConfig.baseUrl}docs/${
href={`${siteConfig.baseUrl}${siteConfig.docsUrl}/${
props.language
}/installation`}>
Documentation
@ -58,7 +58,7 @@ function Versions(props) {
<th>master</th>
<td>
<a
href={`${siteConfig.baseUrl}docs/${
href={`${siteConfig.baseUrl}${siteConfig.docsUrl}/${
props.language
}/next/installation`}>
Documentation
@ -83,7 +83,7 @@ function Versions(props) {
<th>{version}</th>
<td>
<a
href={`${siteConfig.baseUrl}docs/${
href={`${siteConfig.baseUrl}${siteConfig.docsUrl}/${
props.language
}/${version}/installation`}>
Documentation