mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-30 10:48:05 +02:00
Enable clean / extension-less url (#677)
This commit is contained in:
parent
aee255219b
commit
31f0c27f81
14 changed files with 124 additions and 50 deletions
|
@ -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**.
|
||||
|
@ -250,6 +252,7 @@ const siteConfig = {
|
|||
twitterUsername: 'docusaurus',
|
||||
twitterImage: 'img/docusaurus.png',
|
||||
ogImage: 'img/docusaurus.png',
|
||||
cleanUrl: true,
|
||||
scrollToTop: true,
|
||||
scrollToTopOptions: {
|
||||
zIndex: 100
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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 {
|
|||
<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>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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 && (
|
||||
|
@ -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()}
|
||||
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
@ -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][
|
||||
|
@ -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'
|
||||
|
|
|
@ -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 {
|
||||
|
@ -169,7 +170,9 @@ 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;
|
||||
|
@ -177,14 +180,15 @@ class HeaderNav extends React.Component {
|
|||
} 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
|
||||
|
|
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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>';
|
||||
|
@ -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/',
|
||||
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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).`
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -48,7 +48,7 @@ class HomeSplash extends React.Component {
|
|||
<div className="pluginRowBlock">
|
||||
<Button
|
||||
href={`
|
||||
${siteConfig.baseUrl}docs/${this.props.language}/installation.html
|
||||
${siteConfig.baseUrl}docs/${this.props.language}/installation
|
||||
`}>
|
||||
<translate>Get Started</translate>
|
||||
</Button>
|
||||
|
@ -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 {
|
|||
<div className="more-users">
|
||||
<a
|
||||
className="button"
|
||||
href={`${siteConfig.baseUrl}${this.props.language}/users.html`}>
|
||||
href={`${siteConfig.baseUrl}${this.props.language}/users`}>
|
||||
<translate>All Docusaurus Users</translate>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -33,7 +33,7 @@ class Versions extends React.Component {
|
|||
<tr>
|
||||
<th>{latestVersion}</th>
|
||||
<td>
|
||||
<a href={`${siteConfig.baseUrl}docs/en/installation.html`}>Documentation</a>
|
||||
<a href={`${siteConfig.baseUrl}docs/en/installation`}>Documentation</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href={`https://github.com/facebook/Docusaurus/releases/tag/v${latestVersion}`}>Release Notes</a>
|
||||
|
@ -51,7 +51,7 @@ class Versions extends React.Component {
|
|||
<a
|
||||
href={`${
|
||||
siteConfig.baseUrl
|
||||
}docs/en/next/installation.html`}
|
||||
}docs/en/next/installation`}
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
|
@ -75,7 +75,7 @@ class Versions extends React.Component {
|
|||
<a
|
||||
href={`${
|
||||
siteConfig.baseUrl
|
||||
}docs/en/${version}/installation.html`}
|
||||
}docs/en/${version}/installation`}
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
|
|
|
@ -62,6 +62,7 @@ const siteConfig = {
|
|||
ogImage: 'img/docusaurus.png',
|
||||
twitterImage: 'img/docusaurus.png',
|
||||
onPageNav: 'separate',
|
||||
cleanUrl: true,
|
||||
scrollToTop: true,
|
||||
scrollToTopOptions: {
|
||||
zIndex: 100
|
||||
|
|
Loading…
Add table
Reference in a new issue