mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
feat(v2): introduce docs slug in front matter (#2771)
* feat: docs pathname frontmatter (for #2697) * feat: docs pathname frontmatter (for #2697) * chore: comment typo * feat: add slug frontmatter for docs * test: add tests for versioned sites slugs * docs: document slug feature * test: fix tests and snapshots about doc slug feature * docs(v2): doc slug wording * Update website/docs/docs.md Co-authored-by: Alexey Pyltsyn <lex61rus@gmail.com> Co-authored-by: Alexey Pyltsyn <lex61rus@gmail.com>
This commit is contained in:
parent
a32422caa2
commit
b8de9c6ded
10 changed files with 71 additions and 24 deletions
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: hello/super
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
id: baz
|
id: baz
|
||||||
title: baz
|
title: baz
|
||||||
|
slug: bazSlug.html
|
||||||
---
|
---
|
||||||
|
|
||||||
## Images
|
## Images
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
---
|
||||||
|
slug: barSlug
|
||||||
|
---
|
||||||
This is `next` version of bar.
|
This is `next` version of bar.
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
---
|
||||||
|
slug: barSlug
|
||||||
|
---
|
||||||
Bar `1.0.0` !
|
Bar `1.0.0` !
|
||||||
|
|
|
@ -14,7 +14,7 @@ Object {
|
||||||
"type": "link",
|
"type": "link",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"href": "/docs/foo/baz",
|
"href": "/docs/foo/bazSlug.html",
|
||||||
"label": "baz",
|
"label": "baz",
|
||||||
"type": "link",
|
"type": "link",
|
||||||
},
|
},
|
||||||
|
@ -85,7 +85,7 @@ Array [
|
||||||
"modules": Object {
|
"modules": Object {
|
||||||
"content": "@site/docs/foo/baz.md",
|
"content": "@site/docs/foo/baz.md",
|
||||||
},
|
},
|
||||||
"path": "/docs/foo/baz",
|
"path": "/docs/foo/bazSlug.html",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": "@theme/DocItem",
|
"component": "@theme/DocItem",
|
||||||
|
@ -159,7 +159,7 @@ Array [
|
||||||
"modules": Object {
|
"modules": Object {
|
||||||
"content": "@site/versioned_docs/version-1.0.0/foo/bar.md",
|
"content": "@site/versioned_docs/version-1.0.0/foo/bar.md",
|
||||||
},
|
},
|
||||||
"path": "/docs/1.0.0/foo/bar",
|
"path": "/docs/1.0.0/foo/barSlug",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": "@theme/DocItem",
|
"component": "@theme/DocItem",
|
||||||
|
@ -193,7 +193,7 @@ Array [
|
||||||
"modules": Object {
|
"modules": Object {
|
||||||
"content": "@site/docs/foo/bar.md",
|
"content": "@site/docs/foo/bar.md",
|
||||||
},
|
},
|
||||||
"path": "/docs/next/foo/bar",
|
"path": "/docs/next/foo/barSlug",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": "@theme/DocItem",
|
"component": "@theme/DocItem",
|
||||||
|
@ -241,7 +241,7 @@ Object {
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
"href": "/docs/next/foo/bar",
|
"href": "/docs/next/foo/barSlug",
|
||||||
"label": "bar",
|
"label": "bar",
|
||||||
"type": "link",
|
"type": "link",
|
||||||
},
|
},
|
||||||
|
@ -267,7 +267,7 @@ Object {
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
"href": "/docs/1.0.0/foo/bar",
|
"href": "/docs/1.0.0/foo/barSlug",
|
||||||
"label": "bar",
|
"label": "bar",
|
||||||
"type": "link",
|
"type": "link",
|
||||||
},
|
},
|
||||||
|
@ -330,7 +330,7 @@ Object {
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
"href": "/docs/1.0.0/foo/bar",
|
"href": "/docs/1.0.0/foo/barSlug",
|
||||||
"label": "bar",
|
"label": "bar",
|
||||||
"type": "link",
|
"type": "link",
|
||||||
},
|
},
|
||||||
|
@ -358,7 +358,7 @@ Object {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"permalinkToSidebar": Object {
|
"permalinkToSidebar": Object {
|
||||||
"/docs/1.0.0/foo/bar": "version-1.0.0/docs",
|
"/docs/1.0.0/foo/barSlug": "version-1.0.0/docs",
|
||||||
"/docs/1.0.0/foo/baz": "version-1.0.0/docs",
|
"/docs/1.0.0/foo/baz": "version-1.0.0/docs",
|
||||||
"/docs/1.0.0/hello": "version-1.0.0/docs",
|
"/docs/1.0.0/hello": "version-1.0.0/docs",
|
||||||
},
|
},
|
||||||
|
@ -412,7 +412,7 @@ Object {
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"items": Array [
|
"items": Array [
|
||||||
Object {
|
Object {
|
||||||
"href": "/docs/next/foo/bar",
|
"href": "/docs/next/foo/barSlug",
|
||||||
"label": "bar",
|
"label": "bar",
|
||||||
"type": "link",
|
"type": "link",
|
||||||
},
|
},
|
||||||
|
@ -435,7 +435,7 @@ Object {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"permalinkToSidebar": Object {
|
"permalinkToSidebar": Object {
|
||||||
"/docs/next/foo/bar": "docs",
|
"/docs/next/foo/barSlug": "docs",
|
||||||
"/docs/next/hello": "docs",
|
"/docs/next/hello": "docs",
|
||||||
},
|
},
|
||||||
"version": "next",
|
"version": "next",
|
||||||
|
|
|
@ -163,7 +163,7 @@ describe('simple website', () => {
|
||||||
permalink: '/docs/hello',
|
permalink: '/docs/hello',
|
||||||
previous: {
|
previous: {
|
||||||
title: 'baz',
|
title: 'baz',
|
||||||
permalink: '/docs/foo/baz',
|
permalink: '/docs/foo/bazSlug.html',
|
||||||
},
|
},
|
||||||
sidebar: 'docs',
|
sidebar: 'docs',
|
||||||
source: path.join('@site', pluginPath, 'hello.md'),
|
source: path.join('@site', pluginPath, 'hello.md'),
|
||||||
|
@ -175,7 +175,7 @@ describe('simple website', () => {
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
next: {
|
next: {
|
||||||
title: 'baz',
|
title: 'baz',
|
||||||
permalink: '/docs/foo/baz',
|
permalink: '/docs/foo/bazSlug.html',
|
||||||
},
|
},
|
||||||
permalink: '/docs/foo/bar',
|
permalink: '/docs/foo/bar',
|
||||||
sidebar: 'docs',
|
sidebar: 'docs',
|
||||||
|
@ -300,7 +300,7 @@ describe('versioned website', () => {
|
||||||
expect(docsMetadata['version-1.0.1/foo/baz']).toBeUndefined();
|
expect(docsMetadata['version-1.0.1/foo/baz']).toBeUndefined();
|
||||||
expect(docsMetadata['foo/bar']).toEqual({
|
expect(docsMetadata['foo/bar']).toEqual({
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
permalink: '/docs/next/foo/bar',
|
permalink: '/docs/next/foo/barSlug',
|
||||||
source: path.join('@site', routeBasePath, 'foo', 'bar.md'),
|
source: path.join('@site', routeBasePath, 'foo', 'bar.md'),
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
description: 'This is next version of bar.',
|
description: 'This is next version of bar.',
|
||||||
|
@ -321,7 +321,7 @@ describe('versioned website', () => {
|
||||||
sidebar: 'docs',
|
sidebar: 'docs',
|
||||||
previous: {
|
previous: {
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
permalink: '/docs/next/foo/bar',
|
permalink: '/docs/next/foo/barSlug',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(docsMetadata['version-1.0.1/hello']).toEqual({
|
expect(docsMetadata['version-1.0.1/hello']).toEqual({
|
||||||
|
@ -363,7 +363,7 @@ describe('versioned website', () => {
|
||||||
},
|
},
|
||||||
previous: {
|
previous: {
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
permalink: '/docs/1.0.0/foo/bar',
|
permalink: '/docs/1.0.0/foo/barSlug',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ describe('simple site', () => {
|
||||||
|
|
||||||
expect(data).toEqual({
|
expect(data).toEqual({
|
||||||
id: 'foo/baz',
|
id: 'foo/baz',
|
||||||
permalink: '/docs/foo/baz',
|
permalink: '/docs/foo/bazSlug.html',
|
||||||
source: path.join('@site', routeBasePath, source),
|
source: path.join('@site', routeBasePath, source),
|
||||||
title: 'baz',
|
title: 'baz',
|
||||||
editUrl:
|
editUrl:
|
||||||
|
@ -172,7 +172,7 @@ describe('simple site', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs with invalid id', async () => {
|
test('docs with invalid id', async () => {
|
||||||
const badSiteDir = path.join(fixtureDir, 'bad-site');
|
const badSiteDir = path.join(fixtureDir, 'bad-id-site');
|
||||||
const options = {
|
const options = {
|
||||||
routeBasePath,
|
routeBasePath,
|
||||||
};
|
};
|
||||||
|
@ -189,6 +189,25 @@ describe('simple site', () => {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('docs with invalid slug', async () => {
|
||||||
|
const badSiteDir = path.join(fixtureDir, 'bad-slug-site');
|
||||||
|
const options = {
|
||||||
|
routeBasePath,
|
||||||
|
};
|
||||||
|
|
||||||
|
return processMetadata({
|
||||||
|
source: 'invalid-slug.md',
|
||||||
|
refDir: path.join(badSiteDir, 'docs'),
|
||||||
|
context,
|
||||||
|
options,
|
||||||
|
env,
|
||||||
|
}).catch((e) =>
|
||||||
|
expect(e).toMatchInlineSnapshot(
|
||||||
|
`[Error: Document slug cannot include "/".]`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('versioned site', () => {
|
describe('versioned site', () => {
|
||||||
|
@ -225,7 +244,7 @@ describe('versioned site', () => {
|
||||||
|
|
||||||
expect(dataA).toEqual({
|
expect(dataA).toEqual({
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
permalink: '/docs/next/foo/bar',
|
permalink: '/docs/next/foo/barSlug',
|
||||||
source: path.join('@site', routeBasePath, sourceA),
|
source: path.join('@site', routeBasePath, sourceA),
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
description: 'This is next version of bar.',
|
description: 'This is next version of bar.',
|
||||||
|
@ -283,7 +302,7 @@ describe('versioned site', () => {
|
||||||
|
|
||||||
expect(dataA).toEqual({
|
expect(dataA).toEqual({
|
||||||
id: 'version-1.0.0/foo/bar',
|
id: 'version-1.0.0/foo/bar',
|
||||||
permalink: '/docs/1.0.0/foo/bar',
|
permalink: '/docs/1.0.0/foo/barSlug',
|
||||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceA),
|
source: path.join('@site', path.relative(siteDir, versionedDir), sourceA),
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
description: 'Bar 1.0.0 !',
|
description: 'Bar 1.0.0 !',
|
||||||
|
|
|
@ -98,13 +98,18 @@ export default async function processMetadata({
|
||||||
// Default base id is the file name.
|
// Default base id is the file name.
|
||||||
const baseID: string =
|
const baseID: string =
|
||||||
frontMatter.id || path.basename(source, path.extname(source));
|
frontMatter.id || path.basename(source, path.extname(source));
|
||||||
|
|
||||||
if (baseID.includes('/')) {
|
if (baseID.includes('/')) {
|
||||||
throw new Error('Document id cannot include "/".');
|
throw new Error('Document id cannot include "/".');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append subdirectory as part of id.
|
const baseSlug: string = frontMatter.slug || baseID;
|
||||||
|
if (baseSlug.includes('/')) {
|
||||||
|
throw new Error('Document slug cannot include "/".');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append subdirectory as part of id/slug.
|
||||||
const id = dirName !== '.' ? `${dirName}/${baseID}` : baseID;
|
const id = dirName !== '.' ? `${dirName}/${baseID}` : baseID;
|
||||||
|
const slug = dirName !== '.' ? `${dirName}/${baseSlug}` : baseSlug;
|
||||||
|
|
||||||
// Default title is the id.
|
// Default title is the id.
|
||||||
const title: string = frontMatter.title || baseID;
|
const title: string = frontMatter.title || baseID;
|
||||||
|
@ -114,8 +119,9 @@ export default async function processMetadata({
|
||||||
// The last portion of the url path. Eg: 'foo/bar', 'bar'.
|
// The last portion of the url path. Eg: 'foo/bar', 'bar'.
|
||||||
const routePath =
|
const routePath =
|
||||||
version && version !== 'next'
|
version && version !== 'next'
|
||||||
? id.replace(new RegExp(`^version-${version}/`), '')
|
? slug.replace(new RegExp(`^version-${version}/`), '')
|
||||||
: id;
|
: slug;
|
||||||
|
|
||||||
const permalink = normalizeUrl([
|
const permalink = normalizeUrl([
|
||||||
baseUrl,
|
baseUrl,
|
||||||
routeBasePath,
|
routeBasePath,
|
||||||
|
|
|
@ -20,7 +20,7 @@ website # Root directory of your site
|
||||||
└── hello.md
|
└── hello.md
|
||||||
```
|
```
|
||||||
|
|
||||||
However, the last part of the `id` can be defined by user in the front matter. For example, if `guide/hello.md`'s content is defined as below, its final `id` is `guide/part1`.
|
However, the **last part** of the `id` can be defined by user in the front matter. For example, if `guide/hello.md`'s content is defined as below, its final `id` is `guide/part1`.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
---
|
---
|
||||||
|
@ -29,6 +29,16 @@ id: part1
|
||||||
Lorem ipsum
|
Lorem ipsum
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you want more control over the last part of the document URL, it is possible to add a `slug` (defaults to the `id`).
|
||||||
|
|
||||||
|
```yml
|
||||||
|
---
|
||||||
|
id: part1
|
||||||
|
slug: part1.html
|
||||||
|
---
|
||||||
|
Lorem ipsum
|
||||||
|
```
|
||||||
|
|
||||||
## Home page docs
|
## Home page docs
|
||||||
|
|
||||||
Using the `homePageId` property, you can create a home page of your docs. To do this, you can create a new document, especially for this purpose with the id as `_index`, or you could specify an existing document id.
|
Using the `homePageId` property, you can create a home page of your docs. To do this, you can create a new document, especially for this purpose with the id as `_index`, or you could specify an existing document id.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue