diff --git a/crowdin.yaml b/crowdin.yaml
index 299c305ead43..1ebe5fbde688 100644
--- a/crowdin.yaml
+++ b/crowdin.yaml
@@ -43,6 +43,10 @@ files:
'vi': 'vi'
'zh-CN': 'zh-CN'
'zh-TW': 'zh-TW'
+ -
+ source: '/website/versioned_docs/**/*.md'
+ translation: '/website/translated_docs/%locale%/**/%original_file_name%'
+ languages_mapping: *anchor
-
source: '/website/i18n/en.json'
translation: '/website/i18n/%locale%.json'
diff --git a/docs/api-site-config.md b/docs/api-site-config.md
index 8f36a49377b1..95fbe89495f6 100644
--- a/docs/api-site-config.md
+++ b/docs/api-site-config.md
@@ -74,6 +74,8 @@ headerLinks: [
`blogSidebarCount` - Control the number of blog posts that show up in the sidebar. See the [adding a blog docs](guides-blog.md#changing-how-many-blog-posts-show-on-sidebar) for more information.
+`cleanUrl` - If `true`, allow URLs with no `html` extension. Example: request to URL https://docusaurus.io/docs/installation will returns the same result as https://docusaurus.io/docs/installation.html.
+
`cname` - The CNAME for your website. It will go into a `CNAME` file when your site it built.
`customDocsPath` - By default, Docusaurus expects your documentation to be in a directory called `docs`. This directory is at the same level as the `website` directory (i.e., not inside the `website` directory). You can specify a custom path to your documentation with this field. **Note that all of your documentation `*.md` files must still reside in a flat hierarchy. You cannot have your documents in nested directories**.
@@ -153,6 +155,10 @@ h1 {
`separateCss` - Folders inside which any `css` files will not be processed and concatenated to Docusaurus' styles. This is to support static `html` pages that may be separate from Docusaurus with completely separate styles.
+`scrollToTop` - Set this to `true` if you want to enable the scroll to top button at the bottom of your site.
+
+`scrollToTopOptions` - Optional options configuration for the scroll to top button. You do not need to use this, even if you set `scrollToTop` to `true`; it just provides you more configuration control of the button. You can find more options [here](https://github.com/vfeskov/vanilla-back-to-top/blob/v7.1.14/OPTIONS.md). By default, we set the zIndex option to 100.
+
`stylesheets` - Array of CSS sources to load. The link tag will be inserted in the HTML head.
`translationRecruitingLink` - URL for the `Help Translate` tab of language selection when languages besides English are enabled. This can be included you are using translations but does not have to be.
@@ -246,6 +252,11 @@ const siteConfig = {
twitterUsername: 'docusaurus',
twitterImage: 'img/docusaurus.png',
ogImage: 'img/docusaurus.png',
+ cleanUrl: true,
+ scrollToTop: true,
+ scrollToTopOptions: {
+ zIndex: 100
+ }
};
module.exports = siteConfig;
diff --git a/examples/basics/gitignore b/examples/basics/gitignore
index 0a65d6d4da57..5395ea795d62 100644
--- a/examples/basics/gitignore
+++ b/examples/basics/gitignore
@@ -10,4 +10,3 @@ website/build/
website/yarn.lock
website/node_modules
website/i18n/*
-!website/i18n/en.json
diff --git a/lib/core/BlogPageLayout.js b/lib/core/BlogPageLayout.js
index 77cf2116de26..e3a815f4e713 100644
--- a/lib/core/BlogPageLayout.js
+++ b/lib/core/BlogPageLayout.js
@@ -11,6 +11,7 @@ const Container = require('./Container.js');
const MetadataBlog = require('./MetadataBlog.js');
const React = require('react');
const Site = require('./Site.js');
+const utils = require('./utils.js');
// used to generate entire blog pages, i.e. collection of truncated blog posts
class BlogPageLayout extends React.Component {
@@ -45,7 +46,10 @@ class BlogPageLayout extends React.Component {
post={post}
content={post.content}
truncate={true}
- key={post.path + post.title}
+ key={
+ utils.getPath(post.path, this.props.config.cleanUrl) +
+ post.title
+ }
config={this.props.config}
/>
);
diff --git a/lib/core/BlogPost.js b/lib/core/BlogPost.js
index 09c051b4f769..19126dcb94b0 100644
--- a/lib/core/BlogPost.js
+++ b/lib/core/BlogPost.js
@@ -8,7 +8,7 @@
const MarkdownBlock = require('./MarkdownBlock.js');
const React = require('react');
-const utils = require('./utils');
+const utils = require('./utils.js');
// inner blog component for the article itself, without sidebar/header/footer
class BlogPost extends React.Component {
@@ -24,7 +24,12 @@ class BlogPost extends React.Component {
Read More
@@ -73,7 +78,12 @@ class BlogPost extends React.Component {
const post = this.props.post;
return (
diff --git a/lib/core/BlogPostLayout.js b/lib/core/BlogPostLayout.js
index 9b1e0c6f983f..e4327a6efaa8 100644
--- a/lib/core/BlogPostLayout.js
+++ b/lib/core/BlogPostLayout.js
@@ -10,11 +10,14 @@ const BlogPost = require('./BlogPost.js');
const BlogSidebar = require('./BlogSidebar.js');
const Container = require('./Container.js');
const Site = require('./Site.js');
+const OnPageNav = require('./nav/OnPageNav.js');
+const utils = require('./utils.js');
// used for entire blog posts, i.e., each written blog article with sidebar with site header/footer
class BlogPostLayout extends React.Component {
renderSocialButtons() {
- const post = this.props.metadata;
+ let post = this.props.metadata;
+ post.path = utils.getPath(post.path, this.props.config.cleanUrl);
const fbComment = this.props.config.facebookAppId &&
this.props.config.facebookComments && (
@@ -92,10 +95,12 @@ class BlogPostLayout extends React.Component {
}
render() {
+ let post = this.props.metadata;
+ post.path = utils.getPath(post.path, this.props.config.cleanUrl);
return (
+ {this.props.config.onPageNav == 'separate' && (
+
+ )}
);
diff --git a/lib/core/DocsLayout.js b/lib/core/DocsLayout.js
index 06503c76c018..6314d3ba4426 100644
--- a/lib/core/DocsLayout.js
+++ b/lib/core/DocsLayout.js
@@ -17,11 +17,12 @@ const path = require('path');
// component used to generate whole webpage for docs, including sidebar/header/footer
class DocsLayout extends React.Component {
getRelativeURL = (from, to) => {
+ const extension = this.props.config.cleanUrl ? '' : '.html';
return (
path
.relative(from, to)
.replace('\\', '/')
- .replace(/^\.\.\//, '') + '.html'
+ .replace(/^\.\.\//, '') + extension
);
};
diff --git a/lib/core/Head.js b/lib/core/Head.js
index 04d68e080bd6..1334796bef7d 100644
--- a/lib/core/Head.js
+++ b/lib/core/Head.js
@@ -149,6 +149,33 @@ class Head extends React.Component {
);
})}
+ {this.props.config.scrollToTop && (
+
+ )}
+ {this.props.config.scrollToTop && (
+
+ )}
+
{/* Site defined code. Keep these at the end to avoid overriding. */}
))}
+
{process.env.NODE_ENV === 'development' && (
);
const redirectStr = renderToStaticMarkupWithDoctype(redirectComp);
diff --git a/lib/server/server.js b/lib/server/server.js
index 5a9c9e194905..e4f6f8db20dc 100644
--- a/lib/server/server.js
+++ b/lib/server/server.js
@@ -199,6 +199,7 @@ function execute(port) {
// replace any links to markdown files to their website html links
Object.keys(mdToHtml).forEach(function(key, index) {
let link = mdToHtml[key];
+ link = siteConfig.cleanUrl ? link.replace(/\.html$/, '') : link;
link = link.replace('/en/', '/' + language + '/');
link = link.replace(
'/VERSION/',
@@ -271,7 +272,7 @@ function execute(port) {
});
// Handle all requests for blog pages and posts.
- app.get(/blog\/.*html$/, (req, res) => {
+ app.get(/^\/blog\/.*html$/, (req, res) => {
// Regenerate the blog metadata in case it has changed. Consider improving
// this to regenerate on file save rather than on page request.
reloadMetadataBlog();
@@ -305,7 +306,7 @@ function execute(port) {
// send corresponding blog page if appropriate
if (parts[1] === 'index.html') {
res.send(blogPages['/index.html']);
- } else if (parts[1].endsWith('/index.html')) {
+ } else if (parts[1].endsWith('/index.html') && blogPages[parts[1]]) {
res.send(blogPages[parts[1]]);
} else if (parts[1].match(/page([0-9]+)/)) {
if (parts[1].endsWith('/')) {
@@ -314,9 +315,14 @@ function execute(port) {
res.send(blogPages[parts[1] + '/index.html']);
}
} else {
- // else send corresponding blog post
+ // send corresponding blog post. Ex: request to "blog/test/index.html" or
+ // "blog/test.html" will return html rendered version of "blog/test.md"
let file = parts[1];
- file = file.replace(/\.html$/, '.md');
+ if (file.endsWith('/index.html')) {
+ file = file.replace(/\/index.html$/, '.md');
+ } else {
+ file = file.replace(/\.html$/, '.md');
+ }
file = file.replace(new RegExp('/', 'g'), '-');
file = join(CWD, 'blog', file);
@@ -527,23 +533,35 @@ function execute(port) {
app.use(siteConfig.baseUrl, express.static(join(__dirname, '..', 'static')));
// "redirect" requests to pages ending with "/" or no extension so that,
- // for example, request to "blog" returns same result as "blog/index.html"
+ // for example, request to "blog" returns "blog/index.html" or "blog.html"
app.get(/\/[^\.]*\/?$/, (req, res) => {
- let slash = req.path.toString().endsWith('/') ? '' : '/';
- request.get(
- 'http://localhost:' + port + req.path + slash + 'index.html',
- (err, response, body) => {
- if (!err) {
+ const requestFile = (url, notFoundCallback) => {
+ request.get(url, (error, response, body) => {
+ if (!error) {
if (response) {
- res.status(response.statusCode).send(body);
+ if (response.statusCode === 404 && notFoundCallback) {
+ notFoundCallback();
+ } else {
+ res.status(response.statusCode).send(body);
+ }
} else {
console.error('No response');
}
} else {
- console.error('Request failed:', err);
+ console.error('Request failed:', error);
}
- }
- );
+ });
+ };
+ let slash = req.path.toString().endsWith('/') ? '' : '/';
+ let requestUrl = 'http://localhost:' + port + req.path;
+ requestFile(requestUrl + slash + 'index.html', () => {
+ requestFile(
+ slash === '/'
+ ? requestUrl + '.html'
+ : requestUrl.replace(/\/$/, '.html'),
+ null
+ );
+ });
});
// Start LiveReload server.
diff --git a/website/languages.js b/website/languages.js
index 52bf8a130dfa..59a17d380b31 100644
--- a/website/languages.js
+++ b/website/languages.js
@@ -52,7 +52,7 @@ const languages = [
tag: "el"
},
{
- enabled: false,
+ enabled: true,
name: "Español",
tag: "es-ES"
},
@@ -132,7 +132,7 @@ const languages = [
tag: "pt-BR"
},
{
- enabled: false,
+ enabled: true,
name: "Română",
tag: "ro"
},
@@ -157,7 +157,7 @@ const languages = [
tag: "sv-SE"
},
{
- enabled: false,
+ enabled: true,
name: "Türkçe",
tag: "tr"
},
@@ -172,7 +172,7 @@ const languages = [
tag: "vi"
},
{
- enabled: false,
+ enabled: true,
name: "简体中文",
tag: "zh-CN"
},
diff --git a/website/pages/en/help.js b/website/pages/en/help.js
index c99f1af83cd8..7edd14a4f845 100755
--- a/website/pages/en/help.js
+++ b/website/pages/en/help.js
@@ -18,7 +18,7 @@ class Help extends React.Component {
{
title: Browse the docs,
content: (
- `Learn more about Docusaurus using the [official documentation](${siteConfig.baseUrl}docs/${this.props.language}/installation.html).`
+ `Learn more about Docusaurus using the [official documentation](${siteConfig.baseUrl}docs/${this.props.language}/installation).`
),
},
{
diff --git a/website/pages/en/index.js b/website/pages/en/index.js
index 73587e4d6f1d..0450d565e5e0 100755
--- a/website/pages/en/index.js
+++ b/website/pages/en/index.js
@@ -48,7 +48,7 @@ class HomeSplash extends React.Component {
@@ -92,7 +92,7 @@ class Index extends React.Component {
{
content: (
`Save time and focus on your project's documentation. Simply
- write docs and blog posts with [Markdown](${siteConfig.baseUrl}docs/${this.props.language}/doc-markdown.html)
+ write docs and blog posts with [Markdown](${siteConfig.baseUrl}docs/${this.props.language}/doc-markdown)
and Docusaurus will publish a set of static html files ready
to serve.`
),
@@ -103,7 +103,7 @@ class Index extends React.Component {
},
{
content: (
- `[Extend or customize](${siteConfig.baseUrl}docs/${this.props.language}/api-pages.html)
+ `[Extend or customize](${siteConfig.baseUrl}docs/${this.props.language}/api-pages)
your project's layout by reusing React. Docusaurus can be
extended while reusing the same header and footer.`
),
@@ -114,7 +114,7 @@ class Index extends React.Component {
},
{
content: (
- `[Localization](${siteConfig.baseUrl}docs/${this.props.language}/translation.html)
+ `[Localization](${siteConfig.baseUrl}docs/${this.props.language}/translation)
comes pre-configured. Use [Crowdin](https://crowdin.com/) to translate your docs
into over 70 languages.`
),
@@ -134,7 +134,7 @@ class Index extends React.Component {
{
content: (
`Support users on all versions of your project. Document
- [versioning](${siteConfig.baseUrl}docs/${this.props.language}/versioning.html)
+ [versioning](${siteConfig.baseUrl}docs/${this.props.language}/versioning)
helps you keep documentation in sync with project releases.`
),
image: `${siteConfig.baseUrl}img/versioning.svg`,
@@ -144,7 +144,7 @@ class Index extends React.Component {
},
{
content: (
- `Make it easy for your community to [find](${siteConfig.baseUrl}docs/${this.props.language}/search.html) what they need in your documentation.
+ `Make it easy for your community to [find](${siteConfig.baseUrl}docs/${this.props.language}/search) what they need in your documentation.
We proudly support [Algolia documentation search](https://www.algolia.com/).`
),
image: `${siteConfig.baseUrl}img/search.svg`,
@@ -161,7 +161,7 @@ class Index extends React.Component {
contents={[
{
content: (
- `Get [up and running](${siteConfig.baseUrl}docs/${this.props.language}/site-creation.html)
+ `Get [up and running](${siteConfig.baseUrl}docs/${this.props.language}/site-creation)
quickly without having to worry about site design.`
),
imageAlign: "right",
@@ -180,7 +180,7 @@ class Index extends React.Component {
content: (
`Make design and documentation changes by using the included
[live server](${siteConfig.baseUrl}docs/${this.props.language}/site-preparation#verifying-installation).
- [Publish](${siteConfig.baseUrl}docs/${this.props.language}/publishing.html)
+ [Publish](${siteConfig.baseUrl}docs/${this.props.language}/publishing)
your site to GitHub pages or other static file hosts
manually, using a script, or with continuous integration
like CircleCI.`
@@ -200,10 +200,10 @@ class Index extends React.Component {
{
content: (
`Docusaurus currently provides support to help your website
- use [translations](${siteConfig.baseUrl}docs/${this.props.language}/translation.html),
- [search](${siteConfig.baseUrl}docs/${this.props.language}/search.html),
- and [versioning](${siteConfig.baseUrl}docs/${this.props.language}/versioning.html),
- along with some other special [documentation markdown features](${siteConfig.baseUrl}docs/${this.props.language}/doc-markdown.html).
+ use [translations](${siteConfig.baseUrl}docs/${this.props.language}/translation),
+ [search](${siteConfig.baseUrl}docs/${this.props.language}/search),
+ and [versioning](${siteConfig.baseUrl}docs/${this.props.language}/versioning),
+ along with some other special [documentation markdown features](${siteConfig.baseUrl}docs/${this.props.language}/doc-markdown).
If you have ideas for useful features, feel free to
contribute on [GitHub](https://github.com/facebook/docusaurus)!`
),
@@ -223,7 +223,7 @@ class Index extends React.Component {