Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable clean / extension-less url #677

Merged
merged 11 commits into from
Jun 6, 2018
3 changes: 3 additions & 0 deletions docs/api-site-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**.
Expand Down Expand Up @@ -250,6 +252,7 @@ const siteConfig = {
twitterUsername: 'docusaurus',
twitterImage: 'img/docusaurus.png',
ogImage: 'img/docusaurus.png',
cleanUrl: true,
scrollToTop: true,
scrollToTopOptions: {
zIndex: 100
Expand Down
6 changes: 5 additions & 1 deletion lib/core/BlogPageLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}
/>
);
Expand Down
16 changes: 13 additions & 3 deletions lib/core/BlogPost.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -24,7 +24,12 @@ class BlogPost extends React.Component {
<a
className="button"
href={
this.props.config.baseUrl + 'blog/' + this.props.post.path
this.props.config.baseUrl +
'blog/' +
utils.getPath(
this.props.post.path,
this.props.config.cleanUrl
)
}>
Read More
</a>
Expand Down Expand Up @@ -73,7 +78,12 @@ class BlogPost extends React.Component {
const post = this.props.post;
return (
<h1>
<a href={this.props.config.baseUrl + 'blog/' + post.path}>
<a
href={
this.props.config.baseUrl +
'blog/' +
utils.getPath(post.path, this.props.config.cleanUrl)
}>
{post.title}
</a>
</h1>
Expand Down
12 changes: 8 additions & 4 deletions lib/core/BlogPostLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ const BlogPost = require('./BlogPost.js');
const BlogSidebar = require('./BlogSidebar.js');
const Container = require('./Container.js');
const Site = require('./Site.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 && (
Expand Down Expand Up @@ -92,10 +94,12 @@ class BlogPostLayout extends React.Component {
}

render() {
let post = this.props.metadata;
post.path = utils.getPath(post.path, this.props.config.cleanUrl);
return (
<Site
className="sideNavVisible"
url={'blog/' + this.props.metadata.path}
url={'blog/' + post.path}
title={this.props.metadata.title}
language={'en'}
description={this.getDescription()}
Expand All @@ -104,13 +108,13 @@ class BlogPostLayout extends React.Component {
<div className="docMainWrapper wrapper">
<BlogSidebar
language={'en'}
current={this.props.metadata}
current={post}
config={this.props.config}
/>
<Container className="mainContainer documentContainer postContainer blogContainer">
<div className="lonePost">
<BlogPost
post={this.props.metadata}
post={post}
content={this.props.children}
language={'en'}
config={this.props.config}
Expand Down
5 changes: 3 additions & 2 deletions lib/core/DocsLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class DocsLayout extends React.Component {
this.props.metadata.localized_id
] || this.props.metadata.title
: this.props.metadata.title;
const extension = this.props.config.cleanUrl ? '' : '.html';
return (
<Site
config={this.props.config}
Expand All @@ -54,7 +55,7 @@ class DocsLayout extends React.Component {
{metadata.previous_id && (
<a
className="docs-prev button"
href={metadata.previous_id + '.html'}>
href={metadata.previous_id + extension}>
←{' '}
{i18n
? translation[this.props.metadata.language][
Expand All @@ -70,7 +71,7 @@ class DocsLayout extends React.Component {
{metadata.next_id && (
<a
className="docs-next button"
href={metadata.next_id + '.html'}>
href={metadata.next_id + extension}>
{i18n
? translation[this.props.metadata.language][
'localized-strings'
Expand Down
10 changes: 7 additions & 3 deletions lib/core/nav/HeaderNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const setLanguage = require('../../server/translate.js').setLanguage;
const readMetadata = require('../../server/readMetadata.js');
readMetadata.generateMetadataDocs();
const Metadata = require('../metadata.js');
const utils = require('../utils.js');

// language dropdown nav item for when translations are enabled
class LanguageDropDown extends React.Component {
Expand Down Expand Up @@ -169,22 +170,25 @@ class HeaderNav extends React.Component {
}
throw new Error(errorStr);
}
href = this.props.config.baseUrl + Metadata[id].permalink;
href =
this.props.config.baseUrl +
utils.getPath(Metadata[id].permalink, this.props.config.cleanUrl);

const {id: currentID, sidebar} = this.props.current;
docItemActive = currentID && currentID === id;
docGroupActive = sidebar && sidebar === Metadata[id].sidebar;
} else if (link.page) {
// set link to page with current page's language if appropriate
const language = this.props.language || '';
const extension = siteConfig.cleanUrl ? '' : '.html';
if (fs.existsSync(CWD + '/pages/en/' + link.page + '.js')) {
href =
siteConfig.baseUrl +
(language ? language + '/' : '') +
link.page +
'.html';
extension;
} else {
href = siteConfig.baseUrl + link.page + '.html';
href = siteConfig.baseUrl + link.page + extension;
}
} else if (link.href) {
// set link to specified href
Expand Down
15 changes: 11 additions & 4 deletions lib/core/nav/SideNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const classNames = require('classnames');

const siteConfig = require(process.cwd() + '/siteConfig.js');
const translation = require('../../server/translation.js');
const utils = require('../utils.js');

class SideNav extends React.Component {
render() {
Expand Down Expand Up @@ -81,16 +82,22 @@ class SideNav extends React.Component {
}
return localizedString;
}

// return link to doc in sidebar
getLink(metadata) {
if (metadata.permalink) {
if (metadata.permalink.match(/^https?:/)) {
return metadata.permalink;
const targetLink = utils.getPath(metadata.permalink, siteConfig.cleanUrl);
if (targetLink.match(/^https?:/)) {
return targetLink;
}
return siteConfig.baseUrl + metadata.permalink;
return siteConfig.baseUrl + targetLink;
}
if (metadata.path) {
return siteConfig.baseUrl + 'blog/' + metadata.path;
return (
siteConfig.baseUrl +
'blog/' +
utils.getPath(metadata.path, siteConfig.cleanUrl)
);
}
return null;
}
Expand Down
12 changes: 12 additions & 0 deletions lib/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,20 @@ function extractBlogPostSummary(content) {
return content.substring(0, BLOG_POST_SUMMARY_LENGTH);
}

function getPath(path, cleanUrl = false) {
if (cleanUrl) {
if (path.endsWith('/index.html')) {
return path.replace(/\/index.html$/, '');
} else {
return path.replace(/\.html$/, '');
}
}
return path;
}

module.exports = {
blogPostHasTruncateMarker,
extractBlogPostBeforeTruncate,
extractBlogPostSummary,
getPath,
};
14 changes: 12 additions & 2 deletions lib/server/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,14 @@ async function execute() {
// create the folder path for a file if it does not exist, then write the file
function writeFileAndCreateFolder(file, content) {
mkdirp.sync(path.dirname(file));

fs.writeFileSync(file, content);

// build extra file for extension-less url if "cleanUrl" siteConfig is true
if (siteConfig.cleanUrl && file.indexOf('index.html') === -1) {
const extraFile = file.replace(/\.html$/, '/index.html');
mkdirp.sync(path.dirname(extraFile));
fs.writeFileSync(extraFile, content);
}
}

const TABLE_OF_CONTENTS_TOKEN = '<AUTOGENERATED_TABLE_OF_CONTENTS>';
Expand Down Expand Up @@ -156,6 +162,7 @@ async function execute() {
// 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/',
Expand Down Expand Up @@ -196,12 +203,15 @@ async function execute() {
env.translation.enabled &&
metadata.permalink.indexOf('docs/en') !== -1
) {
const redirectlink = siteConfig.cleanUrl
? metadata.permalink.replace(/\.html$/, '')
: metadata.permalink;
const redirectComp = (
<Redirect
metadata={metadata}
language={language}
config={siteConfig}
redirect={siteConfig.baseUrl + metadata.permalink}
redirect={siteConfig.baseUrl + redirectlink}
/>
);
const redirectStr = renderToStaticMarkupWithDoctype(redirectComp);
Expand Down
46 changes: 32 additions & 14 deletions lib/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/',
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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('/')) {
Expand All @@ -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);

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion website/pages/en/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Help extends React.Component {
{
title: <translate>Browse the docs</translate>,
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).`
),
},
{
Expand Down
Loading