mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-02 11:47:23 +02:00
feat(v2): auto-generated sidebars, frontmatter-less sites (#4582)
* POC of autogenerated sidebars
* use combine-promises utility lib
* autogenerated sidebar poc working
* Revert "autogenerated sidebar poc working"
This reverts commit c81da980
* POC of auto-generated sidebars for community docs
* update tests
* add initial test suite for autogenerated sidebars + fix some edge cases
* Improve autogen sidebars: strip more number prefixes in folder breadcrumb + slugs
* fix typo!
* Add tests for partially generated sidebars + fix edge cases + extract sidebar generation code
* Ability to read category metadatas file from a file in the category
* fix tests
* change position of API
* ability to extract number prefix
* stable system to enable position frontmatter
* fix tests for autogen sidebar position
* renamings
* restore community sidebars
* rename frontmatter position -> sidebar_position
* make sidebarItemsGenerator fn configurable
* minor changes
* rename dirPath => dirName
* Make the init template use autogenerated sidebars
* fix options
* fix docusaurus site: remove test docs
* add _category_ file to docs pathsToWatch
* add _category_ file to docs pathsToWatch
* tutorial: use sidebar_position instead of file number prefixes
* Adapt Docusaurus tutorial for autogenerated sidebars
* remove slug: /
* polish the homepage template
* rename _category_ sidebar_position to just "position"
* test for custom sidebarItemsGenerator fn
* fix category metadata + add link to report tutorial issues
* fix absolute path breaking tests
* fix absolute path breaking tests
* Add test for floating number sidebar_position
* add sidebarItemsGenerator unit tests
* add processSidebars unit tests
* Fix init template broken links
* windows test
* increase code translations test timeout
* cleanup mockCategoryMetadataFiles after windows test fixed
* update init template positions
* fix windows tests
* fix comment
* Add autogenerated sidebar items documentation + rewrite the full sidebars page doc
* add useful comment
* fix code block title
This commit is contained in:
parent
836f92708a
commit
db79d462ab
67 changed files with 2887 additions and 306 deletions
|
@ -1,38 +0,0 @@
|
||||||
---
|
|
||||||
title: Create a Document
|
|
||||||
---
|
|
||||||
|
|
||||||
Documents are a **group of pages** connected through a **sidebar**, a **previous/next navigation** and **versioning**.
|
|
||||||
|
|
||||||
## Create a Document
|
|
||||||
|
|
||||||
Create a markdown file at `docs/my-doc.md`:
|
|
||||||
|
|
||||||
```mdx title="docs/hello.md"
|
|
||||||
---
|
|
||||||
title: Hello, World!
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hello, World!
|
|
||||||
|
|
||||||
This is your first document in **Docusaurus**, Congratulations!
|
|
||||||
```
|
|
||||||
|
|
||||||
A new document is now available at `http://localhost:3000/docs/hello`.
|
|
||||||
|
|
||||||
## Add your document to the sidebar
|
|
||||||
|
|
||||||
Add `hello` to the `sidebars.js` file:
|
|
||||||
|
|
||||||
```diff title="sidebars.js"
|
|
||||||
module.exports = {
|
|
||||||
docs: [
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Docusaurus Tutorial',
|
|
||||||
- items: ['getting-started', 'create-a-doc', ...],
|
|
||||||
+ items: ['getting-started', 'create-a-doc', 'hello', ...],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
```
|
|
|
@ -1,11 +1,16 @@
|
||||||
---
|
---
|
||||||
title: Getting Started
|
sidebar_position: 1
|
||||||
slug: /
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Get started by **creating a new site**
|
# Tutorial Intro
|
||||||
|
|
||||||
Or **try Docusaurus immediately** with **[new.docusaurus.io](https://new.docusaurus.io)** (CodeSandbox).
|
Let's discover **Docusaurus in less than 5 minutes**.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Get started by **creating a new site**.
|
||||||
|
|
||||||
|
Or **try Docusaurus immediately** with **[new.docusaurus.io](https://new.docusaurus.io)**.
|
||||||
|
|
||||||
## Generate a new site
|
## Generate a new site
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"label": "Tutorial - Basics",
|
||||||
|
"position": 2
|
||||||
|
}
|
|
@ -1,14 +1,16 @@
|
||||||
---
|
---
|
||||||
title: Congratulations!
|
sidebar_position: 6
|
||||||
---
|
---
|
||||||
|
|
||||||
Congratulations on making it this far!
|
# Congratulations!
|
||||||
|
|
||||||
You have learned the **basics of Docusaurus** and made some changes to the **initial template**.
|
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
|
||||||
|
|
||||||
Docusaurus has **much more to offer**!
|
Docusaurus has **much more to offer**!
|
||||||
|
|
||||||
Have 5 more minutes? Take a look at **[versioning](./manage-docs-versions.md)** and **[i18n](./translate-your-site.md)**.
|
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
|
||||||
|
|
||||||
|
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
|
||||||
|
|
||||||
## What's next?
|
## What's next?
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
---
|
---
|
||||||
title: Create a Blog Post
|
sidebar_position: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Create a Blog Post
|
||||||
|
|
||||||
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
|
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
|
||||||
|
|
||||||
## Create a Blog Post
|
## Create your first Post
|
||||||
|
|
||||||
Create a file at `blog/2021-02-28-greetings.md`:
|
Create a file at `blog/2021-02-28-greetings.md`:
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create a Document
|
||||||
|
|
||||||
|
Documents are **groups of pages** connected through:
|
||||||
|
|
||||||
|
- a **sidebar**
|
||||||
|
- **previous/next navigation**
|
||||||
|
- **versioning**
|
||||||
|
|
||||||
|
## Create your first Doc
|
||||||
|
|
||||||
|
Create a markdown file at `docs/hello.md`:
|
||||||
|
|
||||||
|
```md title="docs/hello.md"
|
||||||
|
# Hello
|
||||||
|
|
||||||
|
This is my **first Docusaurus document**!
|
||||||
|
```
|
||||||
|
|
||||||
|
A new document is now available at `http://localhost:3000/docs/hello`.
|
||||||
|
|
||||||
|
## Configure the Sidebar
|
||||||
|
|
||||||
|
Docusaurus automatically **creates a sidebar** from the `docs` folder.
|
||||||
|
|
||||||
|
Add metadatas to customize the sidebar label and position:
|
||||||
|
|
||||||
|
```diff title="docs/hello.md"
|
||||||
|
+ ---
|
||||||
|
+ sidebar_label: "Hi!"
|
||||||
|
+ sidebar_position: 3
|
||||||
|
+ ---
|
||||||
|
|
||||||
|
|
||||||
|
# Hello
|
||||||
|
|
||||||
|
This is my **first Docusaurus document**!
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to create your sidebar explicitly in `sidebars.js`:
|
||||||
|
|
||||||
|
```diff title="sidebars.js"
|
||||||
|
module.exports = {
|
||||||
|
tutorialSidebar: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Tutorial',
|
||||||
|
- items: [...],
|
||||||
|
+ items: ['hello'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
|
@ -1,14 +1,16 @@
|
||||||
---
|
---
|
||||||
title: Create a Page
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
Add **Markdown or React** files to `src/pages` to create **standalone pages**:
|
# Create a Page
|
||||||
|
|
||||||
|
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
|
||||||
|
|
||||||
- `src/pages/index.js` -> `localhost:3000/`
|
- `src/pages/index.js` -> `localhost:3000/`
|
||||||
- `src/pages/foo.md` -> `localhost:3000/foo`
|
- `src/pages/foo.md` -> `localhost:3000/foo`
|
||||||
- `src/pages/foo/bar.js` -> `localhost:3000/foo/bar`
|
- `src/pages/foo/bar.js` -> `localhost:3000/foo/bar`
|
||||||
|
|
||||||
## Create a React Page
|
## Create your first React Page
|
||||||
|
|
||||||
Create a file at `src/pages/my-react-page.js`:
|
Create a file at `src/pages/my-react-page.js`:
|
||||||
|
|
||||||
|
@ -28,15 +30,11 @@ export default function MyReactPage() {
|
||||||
|
|
||||||
A new page is now available at `http://localhost:3000/my-react-page`.
|
A new page is now available at `http://localhost:3000/my-react-page`.
|
||||||
|
|
||||||
## Create a Markdown Page
|
## Create your first Markdown Page
|
||||||
|
|
||||||
Create a file at `src/pages/my-markdown-page.md`:
|
Create a file at `src/pages/my-markdown-page.md`:
|
||||||
|
|
||||||
```mdx title="src/pages/my-markdown-page.md"
|
```mdx title="src/pages/my-markdown-page.md"
|
||||||
---
|
|
||||||
title: My Markdown page
|
|
||||||
---
|
|
||||||
|
|
||||||
# My Markdown page
|
# My Markdown page
|
||||||
|
|
||||||
This is a Markdown page
|
This is a Markdown page
|
|
@ -1,8 +1,12 @@
|
||||||
---
|
---
|
||||||
title: Deploy your site
|
sidebar_position: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
Docusaurus is a **static-site-generator** (also called [Jamstack](https://jamstack.org/)), and builds your site as **static HTML, JavaScript and CSS files**.
|
# Deploy your site
|
||||||
|
|
||||||
|
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
|
||||||
|
|
||||||
|
It builds your site as simple **static HTML, JavaScript and CSS files**.
|
||||||
|
|
||||||
## Build your site
|
## Build your site
|
||||||
|
|
||||||
|
@ -12,7 +16,7 @@ Build your site **for production**:
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
The static files are generated in the `build` directory.
|
The static files are generated in the `build` folder.
|
||||||
|
|
||||||
## Deploy your site
|
## Deploy your site
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
---
|
---
|
||||||
title: Markdown Features
|
sidebar_position: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Markdown Features
|
||||||
|
|
||||||
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
|
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
|
||||||
|
|
||||||
## Front Matter
|
## Front Matter
|
||||||
|
|
||||||
Markdown documents have metadata at the very top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
|
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
|
||||||
|
|
||||||
```md
|
```text title="my-doc.md"
|
||||||
|
// highlight-start
|
||||||
---
|
---
|
||||||
id: my-doc
|
id: my-doc-id
|
||||||
title: My document title
|
title: My document title
|
||||||
description: My document description
|
description: My document description
|
||||||
sidebar_label: My doc
|
slug: /my-custom-url
|
||||||
---
|
---
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
## Markdown heading
|
## Markdown heading
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"label": "Tutorial - Extras",
|
||||||
|
"position": 3
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
---
|
---
|
||||||
title: Manage Docs Versions
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Manage Docs Versions
|
||||||
|
|
||||||
Docusaurus can manage multiple versions of your docs.
|
Docusaurus can manage multiple versions of your docs.
|
||||||
|
|
||||||
## Create a docs version
|
## Create a docs version
|
||||||
|
@ -12,7 +14,7 @@ Release a version 1.0 of your project:
|
||||||
npm run docusaurus docs:version 1.0
|
npm run docusaurus docs:version 1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
The `docs` directory is copied into `versioned_docs/version-1.0` and `versions.json` is created.
|
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
|
||||||
|
|
||||||
Your docs now have 2 versions:
|
Your docs now have 2 versions:
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
---
|
---
|
||||||
title: Translate your site
|
sidebar_position: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Translate your site
|
||||||
|
|
||||||
Let's translate `docs/getting-started.md` to French.
|
Let's translate `docs/getting-started.md` to French.
|
||||||
|
|
||||||
## Configure i18n
|
## Configure i18n
|
||||||
|
@ -19,7 +21,7 @@ module.exports = {
|
||||||
|
|
||||||
## Translate a doc
|
## Translate a doc
|
||||||
|
|
||||||
Copy the `docs/getting-started.md` file to the `i18n/fr` directory:
|
Copy the `docs/getting-started.md` file to the `i18n/fr` folder:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
|
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
|
||||||
|
@ -39,7 +41,7 @@ npm run start -- --locale fr
|
||||||
|
|
||||||
Your localized site is accessible at `http://localhost:3000/fr/` and the `Getting Started` page is translated.
|
Your localized site is accessible at `http://localhost:3000/fr/` and the `Getting Started` page is translated.
|
||||||
|
|
||||||
:::warning
|
:::caution
|
||||||
|
|
||||||
In development, you can only use one locale at a same time.
|
In development, you can only use one locale at a same time.
|
||||||
|
|
|
@ -19,7 +19,7 @@ module.exports = {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
docId: 'getting-started',
|
docId: 'intro',
|
||||||
position: 'left',
|
position: 'left',
|
||||||
label: 'Tutorial',
|
label: 'Tutorial',
|
||||||
},
|
},
|
||||||
|
@ -38,8 +38,8 @@ module.exports = {
|
||||||
title: 'Docs',
|
title: 'Docs',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Getting Started',
|
label: 'Tutorial',
|
||||||
to: '/docs/',
|
to: '/docs/intro',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Creating a sidebar enables you to:
|
||||||
|
- create an ordered group of docs
|
||||||
|
- render a sidebar for each doc of that group
|
||||||
|
- provide next/previous navigation
|
||||||
|
|
||||||
|
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||||
|
|
||||||
|
Create as many sidebars as you want.
|
||||||
|
*/
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
tutorial: [
|
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||||
|
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||||
|
|
||||||
|
// But you can create a sidebar manually
|
||||||
|
/*
|
||||||
|
tutorialSidebar: [
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
label: 'Tutorial - Basics',
|
label: 'Tutorial',
|
||||||
items: [
|
items: ['hello'],
|
||||||
'getting-started',
|
|
||||||
'create-a-page',
|
|
||||||
'create-a-document',
|
|
||||||
'create-a-blog-post',
|
|
||||||
'markdown-features',
|
|
||||||
'deploy-your-site',
|
|
||||||
'congratulations',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Tutorial - Extras',
|
|
||||||
items: ['manage-docs-versions', 'translate-your-site'],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,8 +41,10 @@ function Feature({Svg, title, description}) {
|
||||||
<div className="text--center">
|
<div className="text--center">
|
||||||
<Svg className={styles.featureSvg} alt={title} />
|
<Svg className={styles.featureSvg} alt={title} />
|
||||||
</div>
|
</div>
|
||||||
<h3>{title}</h3>
|
<div className="text--center padding-horiz--md">
|
||||||
<p>{description}</p>
|
<h3>{title}</h3>
|
||||||
|
<p>{description}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,10 @@ function HomepageHeader() {
|
||||||
<h1 className="hero__title">{siteConfig.title}</h1>
|
<h1 className="hero__title">{siteConfig.title}</h1>
|
||||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<Link className="button button--secondary button--lg" to="/docs">
|
<Link
|
||||||
Get Started - Docusaurus Tutorial
|
className="button button--secondary button--lg"
|
||||||
|
to="/docs/intro">
|
||||||
|
Docusaurus Tutorial - 5min ⏱️
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "2.0.0-alpha.72",
|
"@docusaurus/module-type-aliases": "2.0.0-alpha.72",
|
||||||
"@types/picomatch": "^2.2.1",
|
"@types/picomatch": "^2.2.1",
|
||||||
|
"@types/js-yaml": "^4.0.0",
|
||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
"picomatch": "^2.1.1"
|
"picomatch": "^2.1.1"
|
||||||
},
|
},
|
||||||
|
@ -30,10 +31,12 @@
|
||||||
"@docusaurus/utils": "2.0.0-alpha.72",
|
"@docusaurus/utils": "2.0.0-alpha.72",
|
||||||
"@docusaurus/utils-validation": "2.0.0-alpha.72",
|
"@docusaurus/utils-validation": "2.0.0-alpha.72",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
|
"combine-promises": "^1.1.0",
|
||||||
"execa": "^5.0.0",
|
"execa": "^5.0.0",
|
||||||
"fs-extra": "^9.1.0",
|
"fs-extra": "^9.1.0",
|
||||||
"globby": "^11.0.2",
|
"globby": "^11.0.2",
|
||||||
"import-fresh": "^3.2.2",
|
"import-fresh": "^3.2.2",
|
||||||
|
"js-yaml": "^4.0.0",
|
||||||
"loader-utils": "^1.2.3",
|
"loader-utils": "^1.2.3",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"remark-admonitions": "^1.2.1",
|
"remark-admonitions": "^1.2.1",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
Getting started text
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Installation text
|
|
@ -0,0 +1,3 @@
|
||||||
|
# API Overview
|
||||||
|
|
||||||
|
API Overview text
|
|
@ -0,0 +1 @@
|
||||||
|
Client API text
|
|
@ -0,0 +1 @@
|
||||||
|
Server API text
|
|
@ -0,0 +1 @@
|
||||||
|
Plugin API text
|
|
@ -0,0 +1 @@
|
||||||
|
Theme API text
|
|
@ -0,0 +1 @@
|
||||||
|
label: 'Extension APIs (label from _category_.yml)'
|
|
@ -0,0 +1,3 @@
|
||||||
|
# API End
|
||||||
|
|
||||||
|
API End text
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"label": "API (label from _category_.json)"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
id: guide2.5
|
||||||
|
sidebar_position: 2.5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Guide 2.5
|
||||||
|
|
||||||
|
Guide 2.5 text
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
id: guide2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Guide 2
|
||||||
|
|
||||||
|
Guide 2 text
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"position": 2
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
id: guide4
|
||||||
|
---
|
||||||
|
|
||||||
|
# Guide 4
|
||||||
|
|
||||||
|
Guide 4 text
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
id: guide5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Guide 5
|
||||||
|
|
||||||
|
Guide 5 text
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
id: guide3
|
||||||
|
sidebar_position: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Guide 3
|
||||||
|
|
||||||
|
Guide 3 text
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
id: guide1
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Guide 1
|
||||||
|
|
||||||
|
Guide 1 text
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
title: 'My Site',
|
||||||
|
tagline: 'The tagline of my site',
|
||||||
|
url: 'https://your-docusaurus-test-site.com',
|
||||||
|
baseUrl: '/',
|
||||||
|
favicon: 'img/favicon.ico',
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
someSidebar: [
|
||||||
|
{type: 'doc', id: 'API/api-end'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Some category',
|
||||||
|
items: [
|
||||||
|
{type: 'doc', id: 'API/api-overview'},
|
||||||
|
{
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: '3-API/02_Extension APIs',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -149,6 +149,7 @@ Object {
|
||||||
\\"title\\": \\"Bar\\",
|
\\"title\\": \\"Bar\\",
|
||||||
\\"description\\": \\"This is custom description\\",
|
\\"description\\": \\"This is custom description\\",
|
||||||
\\"source\\": \\"@site/docs/foo/bar.md\\",
|
\\"source\\": \\"@site/docs/foo/bar.md\\",
|
||||||
|
\\"sourceDirName\\": \\"foo\\",
|
||||||
\\"slug\\": \\"/foo/bar\\",
|
\\"slug\\": \\"/foo/bar\\",
|
||||||
\\"permalink\\": \\"/docs/foo/bar\\",
|
\\"permalink\\": \\"/docs/foo/bar\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -170,6 +171,7 @@ Object {
|
||||||
\\"title\\": \\"baz\\",
|
\\"title\\": \\"baz\\",
|
||||||
\\"description\\": \\"Images\\",
|
\\"description\\": \\"Images\\",
|
||||||
\\"source\\": \\"@site/docs/foo/baz.md\\",
|
\\"source\\": \\"@site/docs/foo/baz.md\\",
|
||||||
|
\\"sourceDirName\\": \\"foo\\",
|
||||||
\\"slug\\": \\"/foo/bazSlug.html\\",
|
\\"slug\\": \\"/foo/bazSlug.html\\",
|
||||||
\\"permalink\\": \\"/docs/foo/bazSlug.html\\",
|
\\"permalink\\": \\"/docs/foo/bazSlug.html\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -195,6 +197,7 @@ Object {
|
||||||
\\"title\\": \\"My heading as title\\",
|
\\"title\\": \\"My heading as title\\",
|
||||||
\\"description\\": \\"\\",
|
\\"description\\": \\"\\",
|
||||||
\\"source\\": \\"@site/docs/headingAsTitle.md\\",
|
\\"source\\": \\"@site/docs/headingAsTitle.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/headingAsTitle\\",
|
\\"slug\\": \\"/headingAsTitle\\",
|
||||||
\\"permalink\\": \\"/docs/headingAsTitle\\",
|
\\"permalink\\": \\"/docs/headingAsTitle\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -207,6 +210,7 @@ Object {
|
||||||
\\"title\\": \\"Hello, World !\\",
|
\\"title\\": \\"Hello, World !\\",
|
||||||
\\"description\\": \\"Hi, Endilie here :)\\",
|
\\"description\\": \\"Hi, Endilie here :)\\",
|
||||||
\\"source\\": \\"@site/docs/hello.md\\",
|
\\"source\\": \\"@site/docs/hello.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/\\",
|
\\"slug\\": \\"/\\",
|
||||||
\\"permalink\\": \\"/docs/\\",
|
\\"permalink\\": \\"/docs/\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -227,6 +231,7 @@ Object {
|
||||||
\\"title\\": \\"ipsum\\",
|
\\"title\\": \\"ipsum\\",
|
||||||
\\"description\\": \\"Lorem ipsum.\\",
|
\\"description\\": \\"Lorem ipsum.\\",
|
||||||
\\"source\\": \\"@site/docs/ipsum.md\\",
|
\\"source\\": \\"@site/docs/ipsum.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/ipsum\\",
|
\\"slug\\": \\"/ipsum\\",
|
||||||
\\"permalink\\": \\"/docs/ipsum\\",
|
\\"permalink\\": \\"/docs/ipsum\\",
|
||||||
\\"editUrl\\": null,
|
\\"editUrl\\": null,
|
||||||
|
@ -242,6 +247,7 @@ Object {
|
||||||
\\"title\\": \\"lorem\\",
|
\\"title\\": \\"lorem\\",
|
||||||
\\"description\\": \\"Lorem ipsum.\\",
|
\\"description\\": \\"Lorem ipsum.\\",
|
||||||
\\"source\\": \\"@site/docs/lorem.md\\",
|
\\"source\\": \\"@site/docs/lorem.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/lorem\\",
|
\\"slug\\": \\"/lorem\\",
|
||||||
\\"permalink\\": \\"/docs/lorem\\",
|
\\"permalink\\": \\"/docs/lorem\\",
|
||||||
\\"editUrl\\": \\"https://github.com/customUrl/docs/lorem.md\\",
|
\\"editUrl\\": \\"https://github.com/customUrl/docs/lorem.md\\",
|
||||||
|
@ -258,6 +264,7 @@ Object {
|
||||||
\\"title\\": \\"rootAbsoluteSlug\\",
|
\\"title\\": \\"rootAbsoluteSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/rootAbsoluteSlug.md\\",
|
\\"source\\": \\"@site/docs/rootAbsoluteSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/rootAbsoluteSlug\\",
|
\\"slug\\": \\"/rootAbsoluteSlug\\",
|
||||||
\\"permalink\\": \\"/docs/rootAbsoluteSlug\\",
|
\\"permalink\\": \\"/docs/rootAbsoluteSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -272,6 +279,7 @@ Object {
|
||||||
\\"title\\": \\"rootRelativeSlug\\",
|
\\"title\\": \\"rootRelativeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/rootRelativeSlug.md\\",
|
\\"source\\": \\"@site/docs/rootRelativeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/rootRelativeSlug\\",
|
\\"slug\\": \\"/rootRelativeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/rootRelativeSlug\\",
|
\\"permalink\\": \\"/docs/rootRelativeSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -286,6 +294,7 @@ Object {
|
||||||
\\"title\\": \\"rootResolvedSlug\\",
|
\\"title\\": \\"rootResolvedSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/rootResolvedSlug.md\\",
|
\\"source\\": \\"@site/docs/rootResolvedSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/hey/rootResolvedSlug\\",
|
\\"slug\\": \\"/hey/rootResolvedSlug\\",
|
||||||
\\"permalink\\": \\"/docs/hey/rootResolvedSlug\\",
|
\\"permalink\\": \\"/docs/hey/rootResolvedSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -300,6 +309,7 @@ Object {
|
||||||
\\"title\\": \\"rootTryToEscapeSlug\\",
|
\\"title\\": \\"rootTryToEscapeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/rootTryToEscapeSlug.md\\",
|
\\"source\\": \\"@site/docs/rootTryToEscapeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/rootTryToEscapeSlug\\",
|
\\"slug\\": \\"/rootTryToEscapeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/rootTryToEscapeSlug\\",
|
\\"permalink\\": \\"/docs/rootTryToEscapeSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -314,6 +324,7 @@ Object {
|
||||||
\\"title\\": \\"absoluteSlug\\",
|
\\"title\\": \\"absoluteSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/absoluteSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/absoluteSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/absoluteSlug\\",
|
\\"slug\\": \\"/absoluteSlug\\",
|
||||||
\\"permalink\\": \\"/docs/absoluteSlug\\",
|
\\"permalink\\": \\"/docs/absoluteSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -328,6 +339,7 @@ Object {
|
||||||
\\"title\\": \\"relativeSlug\\",
|
\\"title\\": \\"relativeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/relativeSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/relativeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/slugs/relativeSlug\\",
|
\\"slug\\": \\"/slugs/relativeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/slugs/relativeSlug\\",
|
\\"permalink\\": \\"/docs/slugs/relativeSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -342,6 +354,7 @@ Object {
|
||||||
\\"title\\": \\"resolvedSlug\\",
|
\\"title\\": \\"resolvedSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/resolvedSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/resolvedSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/slugs/hey/resolvedSlug\\",
|
\\"slug\\": \\"/slugs/hey/resolvedSlug\\",
|
||||||
\\"permalink\\": \\"/docs/slugs/hey/resolvedSlug\\",
|
\\"permalink\\": \\"/docs/slugs/hey/resolvedSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -356,6 +369,7 @@ Object {
|
||||||
\\"title\\": \\"tryToEscapeSlug\\",
|
\\"title\\": \\"tryToEscapeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/tryToEscapeSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/tryToEscapeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/tryToEscapeSlug\\",
|
\\"slug\\": \\"/tryToEscapeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/tryToEscapeSlug\\",
|
\\"permalink\\": \\"/docs/tryToEscapeSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -646,6 +660,134 @@ Array [
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`site with custom sidebar items generator sidebarItemsGenerator is called with appropriate data 1`] = `
|
||||||
|
Object {
|
||||||
|
"docs": Array [
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "API/Core APIs/Client API",
|
||||||
|
"sidebarPosition": 0,
|
||||||
|
"source": "@site/docs/3-API/01_Core APIs/0 --- Client API.md",
|
||||||
|
"sourceDirName": "3-API/01_Core APIs",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "API/Core APIs/Server API",
|
||||||
|
"sidebarPosition": 1,
|
||||||
|
"source": "@site/docs/3-API/01_Core APIs/1 --- Server API.md",
|
||||||
|
"sourceDirName": "3-API/01_Core APIs",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "API/Extension APIs/Plugin API",
|
||||||
|
"sidebarPosition": 0,
|
||||||
|
"source": "@site/docs/3-API/02_Extension APIs/0. Plugin API.md",
|
||||||
|
"sourceDirName": "3-API/02_Extension APIs",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "API/Extension APIs/Theme API",
|
||||||
|
"sidebarPosition": 1,
|
||||||
|
"source": "@site/docs/3-API/02_Extension APIs/1. Theme API.md",
|
||||||
|
"sourceDirName": "3-API/02_Extension APIs",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "API/api-end",
|
||||||
|
"sidebarPosition": 3,
|
||||||
|
"source": "@site/docs/3-API/03_api-end.md",
|
||||||
|
"sourceDirName": "3-API",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "API/api-overview",
|
||||||
|
"sidebarPosition": 0,
|
||||||
|
"source": "@site/docs/3-API/00_api-overview.md",
|
||||||
|
"sourceDirName": "3-API",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {
|
||||||
|
"id": "guide1",
|
||||||
|
"sidebar_position": 1,
|
||||||
|
},
|
||||||
|
"id": "Guides/guide1",
|
||||||
|
"sidebarPosition": 1,
|
||||||
|
"source": "@site/docs/Guides/z-guide1.md",
|
||||||
|
"sourceDirName": "Guides",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {
|
||||||
|
"id": "guide2",
|
||||||
|
},
|
||||||
|
"id": "Guides/guide2",
|
||||||
|
"sidebarPosition": 2,
|
||||||
|
"source": "@site/docs/Guides/02-guide2.md",
|
||||||
|
"sourceDirName": "Guides",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {
|
||||||
|
"id": "guide2.5",
|
||||||
|
"sidebar_position": 2.5,
|
||||||
|
},
|
||||||
|
"id": "Guides/guide2.5",
|
||||||
|
"sidebarPosition": 2.5,
|
||||||
|
"source": "@site/docs/Guides/0-guide2.5.md",
|
||||||
|
"sourceDirName": "Guides",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {
|
||||||
|
"id": "guide3",
|
||||||
|
"sidebar_position": 3,
|
||||||
|
},
|
||||||
|
"id": "Guides/guide3",
|
||||||
|
"sidebarPosition": 3,
|
||||||
|
"source": "@site/docs/Guides/guide3.md",
|
||||||
|
"sourceDirName": "Guides",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {
|
||||||
|
"id": "guide4",
|
||||||
|
},
|
||||||
|
"id": "Guides/guide4",
|
||||||
|
"sidebarPosition": undefined,
|
||||||
|
"source": "@site/docs/Guides/a-guide4.md",
|
||||||
|
"sourceDirName": "Guides",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {
|
||||||
|
"id": "guide5",
|
||||||
|
},
|
||||||
|
"id": "Guides/guide5",
|
||||||
|
"sidebarPosition": undefined,
|
||||||
|
"source": "@site/docs/Guides/b-guide5.md",
|
||||||
|
"sourceDirName": "Guides",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "getting-started",
|
||||||
|
"sidebarPosition": 0,
|
||||||
|
"source": "@site/docs/0-getting-started.md",
|
||||||
|
"sourceDirName": ".",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"frontMatter": Object {},
|
||||||
|
"id": "installation",
|
||||||
|
"sidebarPosition": 1,
|
||||||
|
"source": "@site/docs/1-installation.md",
|
||||||
|
"sourceDirName": ".",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"item": Object {
|
||||||
|
"dirName": ".",
|
||||||
|
"type": "autogenerated",
|
||||||
|
},
|
||||||
|
"version": Object {
|
||||||
|
"contentPath": "docs",
|
||||||
|
"versionName": "current",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`site with wrong sidebar file 1`] = `
|
exports[`site with wrong sidebar file 1`] = `
|
||||||
"Bad sidebars file.
|
"Bad sidebars file.
|
||||||
These sidebar document ids do not exist:
|
These sidebar document ids do not exist:
|
||||||
|
@ -699,6 +841,7 @@ Object {
|
||||||
\\"title\\": \\"team\\",
|
\\"title\\": \\"team\\",
|
||||||
\\"description\\": \\"Team 1.0.0\\",
|
\\"description\\": \\"Team 1.0.0\\",
|
||||||
\\"source\\": \\"@site/community_versioned_docs/version-1.0.0/team.md\\",
|
\\"source\\": \\"@site/community_versioned_docs/version-1.0.0/team.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/team\\",
|
\\"slug\\": \\"/team\\",
|
||||||
\\"permalink\\": \\"/community/team\\",
|
\\"permalink\\": \\"/community/team\\",
|
||||||
\\"version\\": \\"1.0.0\\",
|
\\"version\\": \\"1.0.0\\",
|
||||||
|
@ -712,6 +855,7 @@ Object {
|
||||||
\\"title\\": \\"Team title translated\\",
|
\\"title\\": \\"Team title translated\\",
|
||||||
\\"description\\": \\"Team current version (translated)\\",
|
\\"description\\": \\"Team current version (translated)\\",
|
||||||
\\"source\\": \\"@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md\\",
|
\\"source\\": \\"@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/team\\",
|
\\"slug\\": \\"/team\\",
|
||||||
\\"permalink\\": \\"/community/next/team\\",
|
\\"permalink\\": \\"/community/next/team\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -942,6 +1086,7 @@ Object {
|
||||||
\\"title\\": \\"bar\\",
|
\\"title\\": \\"bar\\",
|
||||||
\\"description\\": \\"This is next version of bar.\\",
|
\\"description\\": \\"This is next version of bar.\\",
|
||||||
\\"source\\": \\"@site/docs/foo/bar.md\\",
|
\\"source\\": \\"@site/docs/foo/bar.md\\",
|
||||||
|
\\"sourceDirName\\": \\"foo\\",
|
||||||
\\"slug\\": \\"/foo/barSlug\\",
|
\\"slug\\": \\"/foo/barSlug\\",
|
||||||
\\"permalink\\": \\"/docs/next/foo/barSlug\\",
|
\\"permalink\\": \\"/docs/next/foo/barSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -961,6 +1106,7 @@ Object {
|
||||||
\\"title\\": \\"hello\\",
|
\\"title\\": \\"hello\\",
|
||||||
\\"description\\": \\"Hello next !\\",
|
\\"description\\": \\"Hello next !\\",
|
||||||
\\"source\\": \\"@site/docs/hello.md\\",
|
\\"source\\": \\"@site/docs/hello.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/\\",
|
\\"slug\\": \\"/\\",
|
||||||
\\"permalink\\": \\"/docs/next/\\",
|
\\"permalink\\": \\"/docs/next/\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -978,6 +1124,7 @@ Object {
|
||||||
\\"title\\": \\"absoluteSlug\\",
|
\\"title\\": \\"absoluteSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/absoluteSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/absoluteSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/absoluteSlug\\",
|
\\"slug\\": \\"/absoluteSlug\\",
|
||||||
\\"permalink\\": \\"/docs/next/absoluteSlug\\",
|
\\"permalink\\": \\"/docs/next/absoluteSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -992,6 +1139,7 @@ Object {
|
||||||
\\"title\\": \\"relativeSlug\\",
|
\\"title\\": \\"relativeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/relativeSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/relativeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/slugs/relativeSlug\\",
|
\\"slug\\": \\"/slugs/relativeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/next/slugs/relativeSlug\\",
|
\\"permalink\\": \\"/docs/next/slugs/relativeSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -1006,6 +1154,7 @@ Object {
|
||||||
\\"title\\": \\"resolvedSlug\\",
|
\\"title\\": \\"resolvedSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/resolvedSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/resolvedSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/slugs/hey/resolvedSlug\\",
|
\\"slug\\": \\"/slugs/hey/resolvedSlug\\",
|
||||||
\\"permalink\\": \\"/docs/next/slugs/hey/resolvedSlug\\",
|
\\"permalink\\": \\"/docs/next/slugs/hey/resolvedSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -1020,6 +1169,7 @@ Object {
|
||||||
\\"title\\": \\"tryToEscapeSlug\\",
|
\\"title\\": \\"tryToEscapeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/docs/slugs/tryToEscapeSlug.md\\",
|
\\"source\\": \\"@site/docs/slugs/tryToEscapeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/tryToEscapeSlug\\",
|
\\"slug\\": \\"/tryToEscapeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/next/tryToEscapeSlug\\",
|
\\"permalink\\": \\"/docs/next/tryToEscapeSlug\\",
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
@ -1034,6 +1184,7 @@ Object {
|
||||||
\\"title\\": \\"hello\\",
|
\\"title\\": \\"hello\\",
|
||||||
\\"description\\": \\"Hello 1.0.0 ! (translated en)\\",
|
\\"description\\": \\"Hello 1.0.0 ! (translated en)\\",
|
||||||
\\"source\\": \\"@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md\\",
|
\\"source\\": \\"@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/\\",
|
\\"slug\\": \\"/\\",
|
||||||
\\"permalink\\": \\"/docs/1.0.0/\\",
|
\\"permalink\\": \\"/docs/1.0.0/\\",
|
||||||
\\"version\\": \\"1.0.0\\",
|
\\"version\\": \\"1.0.0\\",
|
||||||
|
@ -1051,6 +1202,7 @@ Object {
|
||||||
\\"title\\": \\"bar\\",
|
\\"title\\": \\"bar\\",
|
||||||
\\"description\\": \\"Bar 1.0.0 !\\",
|
\\"description\\": \\"Bar 1.0.0 !\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-1.0.0/foo/bar.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-1.0.0/foo/bar.md\\",
|
||||||
|
\\"sourceDirName\\": \\"foo\\",
|
||||||
\\"slug\\": \\"/foo/barSlug\\",
|
\\"slug\\": \\"/foo/barSlug\\",
|
||||||
\\"permalink\\": \\"/docs/1.0.0/foo/barSlug\\",
|
\\"permalink\\": \\"/docs/1.0.0/foo/barSlug\\",
|
||||||
\\"version\\": \\"1.0.0\\",
|
\\"version\\": \\"1.0.0\\",
|
||||||
|
@ -1070,6 +1222,7 @@ Object {
|
||||||
\\"title\\": \\"baz\\",
|
\\"title\\": \\"baz\\",
|
||||||
\\"description\\": \\"Baz 1.0.0 ! This will be deleted in next subsequent versions.\\",
|
\\"description\\": \\"Baz 1.0.0 ! This will be deleted in next subsequent versions.\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-1.0.0/foo/baz.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-1.0.0/foo/baz.md\\",
|
||||||
|
\\"sourceDirName\\": \\"foo\\",
|
||||||
\\"slug\\": \\"/foo/baz\\",
|
\\"slug\\": \\"/foo/baz\\",
|
||||||
\\"permalink\\": \\"/docs/1.0.0/foo/baz\\",
|
\\"permalink\\": \\"/docs/1.0.0/foo/baz\\",
|
||||||
\\"version\\": \\"1.0.0\\",
|
\\"version\\": \\"1.0.0\\",
|
||||||
|
@ -1091,6 +1244,7 @@ Object {
|
||||||
\\"title\\": \\"bar\\",
|
\\"title\\": \\"bar\\",
|
||||||
\\"description\\": \\"Bar 1.0.1 !\\",
|
\\"description\\": \\"Bar 1.0.1 !\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-1.0.1/foo/bar.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-1.0.1/foo/bar.md\\",
|
||||||
|
\\"sourceDirName\\": \\"foo\\",
|
||||||
\\"slug\\": \\"/foo/bar\\",
|
\\"slug\\": \\"/foo/bar\\",
|
||||||
\\"permalink\\": \\"/docs/foo/bar\\",
|
\\"permalink\\": \\"/docs/foo/bar\\",
|
||||||
\\"version\\": \\"1.0.1\\",
|
\\"version\\": \\"1.0.1\\",
|
||||||
|
@ -1108,6 +1262,7 @@ Object {
|
||||||
\\"title\\": \\"hello\\",
|
\\"title\\": \\"hello\\",
|
||||||
\\"description\\": \\"Hello 1.0.1 !\\",
|
\\"description\\": \\"Hello 1.0.1 !\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-1.0.1/hello.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-1.0.1/hello.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/\\",
|
\\"slug\\": \\"/\\",
|
||||||
\\"permalink\\": \\"/docs/\\",
|
\\"permalink\\": \\"/docs/\\",
|
||||||
\\"version\\": \\"1.0.1\\",
|
\\"version\\": \\"1.0.1\\",
|
||||||
|
@ -1125,6 +1280,7 @@ Object {
|
||||||
\\"title\\": \\"rootAbsoluteSlug\\",
|
\\"title\\": \\"rootAbsoluteSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootAbsoluteSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootAbsoluteSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/rootAbsoluteSlug\\",
|
\\"slug\\": \\"/rootAbsoluteSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/rootAbsoluteSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/rootAbsoluteSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
@ -1140,6 +1296,7 @@ Object {
|
||||||
\\"title\\": \\"rootRelativeSlug\\",
|
\\"title\\": \\"rootRelativeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootRelativeSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootRelativeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/rootRelativeSlug\\",
|
\\"slug\\": \\"/rootRelativeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/rootRelativeSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/rootRelativeSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
@ -1154,6 +1311,7 @@ Object {
|
||||||
\\"title\\": \\"rootResolvedSlug\\",
|
\\"title\\": \\"rootResolvedSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootResolvedSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootResolvedSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/hey/rootResolvedSlug\\",
|
\\"slug\\": \\"/hey/rootResolvedSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/hey/rootResolvedSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/hey/rootResolvedSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
@ -1168,6 +1326,7 @@ Object {
|
||||||
\\"title\\": \\"rootTryToEscapeSlug\\",
|
\\"title\\": \\"rootTryToEscapeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootTryToEscapeSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/rootTryToEscapeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\".\\",
|
||||||
\\"slug\\": \\"/rootTryToEscapeSlug\\",
|
\\"slug\\": \\"/rootTryToEscapeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/rootTryToEscapeSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/rootTryToEscapeSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
@ -1182,6 +1341,7 @@ Object {
|
||||||
\\"title\\": \\"absoluteSlug\\",
|
\\"title\\": \\"absoluteSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/absoluteSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/absoluteSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/absoluteSlug\\",
|
\\"slug\\": \\"/absoluteSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/absoluteSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/absoluteSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
@ -1196,6 +1356,7 @@ Object {
|
||||||
\\"title\\": \\"relativeSlug\\",
|
\\"title\\": \\"relativeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/relativeSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/relativeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/slugs/relativeSlug\\",
|
\\"slug\\": \\"/slugs/relativeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/slugs/relativeSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/slugs/relativeSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
@ -1210,6 +1371,7 @@ Object {
|
||||||
\\"title\\": \\"resolvedSlug\\",
|
\\"title\\": \\"resolvedSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/resolvedSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/resolvedSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/slugs/hey/resolvedSlug\\",
|
\\"slug\\": \\"/slugs/hey/resolvedSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/slugs/hey/resolvedSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/slugs/hey/resolvedSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
@ -1224,6 +1386,7 @@ Object {
|
||||||
\\"title\\": \\"tryToEscapeSlug\\",
|
\\"title\\": \\"tryToEscapeSlug\\",
|
||||||
\\"description\\": \\"Lorem\\",
|
\\"description\\": \\"Lorem\\",
|
||||||
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/tryToEscapeSlug.md\\",
|
\\"source\\": \\"@site/versioned_docs/version-withSlugs/slugs/tryToEscapeSlug.md\\",
|
||||||
|
\\"sourceDirName\\": \\"slugs\\",
|
||||||
\\"slug\\": \\"/tryToEscapeSlug\\",
|
\\"slug\\": \\"/tryToEscapeSlug\\",
|
||||||
\\"permalink\\": \\"/docs/withSlugs/tryToEscapeSlug\\",
|
\\"permalink\\": \\"/docs/withSlugs/tryToEscapeSlug\\",
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
|
|
@ -177,6 +177,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/foo/bar',
|
permalink: '/docs/foo/bar',
|
||||||
slug: '/foo/bar',
|
slug: '/foo/bar',
|
||||||
|
@ -192,6 +193,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/hello',
|
permalink: '/docs/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -220,6 +222,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: true,
|
isDocsHomePage: true,
|
||||||
permalink: '/docs/',
|
permalink: '/docs/',
|
||||||
slug: '/',
|
slug: '/',
|
||||||
|
@ -248,6 +251,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: true,
|
isDocsHomePage: true,
|
||||||
permalink: '/docs/',
|
permalink: '/docs/',
|
||||||
slug: '/',
|
slug: '/',
|
||||||
|
@ -279,6 +283,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'foo/baz',
|
id: 'foo/baz',
|
||||||
unversionedId: 'foo/baz',
|
unversionedId: 'foo/baz',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/foo/bazSlug.html',
|
permalink: '/docs/foo/bazSlug.html',
|
||||||
slug: '/foo/bazSlug.html',
|
slug: '/foo/bazSlug.html',
|
||||||
|
@ -301,6 +306,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'lorem',
|
id: 'lorem',
|
||||||
unversionedId: 'lorem',
|
unversionedId: 'lorem',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/lorem',
|
permalink: '/docs/lorem',
|
||||||
slug: '/lorem',
|
slug: '/lorem',
|
||||||
|
@ -336,6 +342,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'foo/baz',
|
id: 'foo/baz',
|
||||||
unversionedId: 'foo/baz',
|
unversionedId: 'foo/baz',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/foo/bazSlug.html',
|
permalink: '/docs/foo/bazSlug.html',
|
||||||
slug: '/foo/bazSlug.html',
|
slug: '/foo/bazSlug.html',
|
||||||
|
@ -378,6 +385,7 @@ describe('simple site', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'lorem',
|
id: 'lorem',
|
||||||
unversionedId: 'lorem',
|
unversionedId: 'lorem',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/lorem',
|
permalink: '/docs/lorem',
|
||||||
slug: '/lorem',
|
slug: '/lorem',
|
||||||
|
@ -549,6 +557,7 @@ describe('versioned site', () => {
|
||||||
await currentVersionTestUtils.testMeta(path.join('foo', 'bar.md'), {
|
await currentVersionTestUtils.testMeta(path.join('foo', 'bar.md'), {
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/next/foo/barSlug',
|
permalink: '/docs/next/foo/barSlug',
|
||||||
slug: '/foo/barSlug',
|
slug: '/foo/barSlug',
|
||||||
|
@ -560,6 +569,7 @@ describe('versioned site', () => {
|
||||||
await currentVersionTestUtils.testMeta(path.join('hello.md'), {
|
await currentVersionTestUtils.testMeta(path.join('hello.md'), {
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/next/hello',
|
permalink: '/docs/next/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -576,6 +586,7 @@ describe('versioned site', () => {
|
||||||
await version100TestUtils.testMeta(path.join('foo', 'bar.md'), {
|
await version100TestUtils.testMeta(path.join('foo', 'bar.md'), {
|
||||||
id: 'version-1.0.0/foo/bar',
|
id: 'version-1.0.0/foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/foo/barSlug',
|
permalink: '/docs/1.0.0/foo/barSlug',
|
||||||
slug: '/foo/barSlug',
|
slug: '/foo/barSlug',
|
||||||
|
@ -587,6 +598,7 @@ describe('versioned site', () => {
|
||||||
await version100TestUtils.testMeta(path.join('hello.md'), {
|
await version100TestUtils.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/hello',
|
permalink: '/docs/1.0.0/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -600,6 +612,7 @@ describe('versioned site', () => {
|
||||||
await version101TestUtils.testMeta(path.join('foo', 'bar.md'), {
|
await version101TestUtils.testMeta(path.join('foo', 'bar.md'), {
|
||||||
id: 'version-1.0.1/foo/bar',
|
id: 'version-1.0.1/foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/foo/bar',
|
permalink: '/docs/foo/bar',
|
||||||
slug: '/foo/bar',
|
slug: '/foo/bar',
|
||||||
|
@ -611,6 +624,7 @@ describe('versioned site', () => {
|
||||||
await version101TestUtils.testMeta(path.join('hello.md'), {
|
await version101TestUtils.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.1/hello',
|
id: 'version-1.0.1/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/hello',
|
permalink: '/docs/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -701,6 +715,7 @@ describe('versioned site', () => {
|
||||||
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/hello',
|
permalink: '/docs/1.0.0/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -741,6 +756,7 @@ describe('versioned site', () => {
|
||||||
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/hello',
|
permalink: '/docs/1.0.0/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -773,6 +789,7 @@ describe('versioned site', () => {
|
||||||
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/hello',
|
permalink: '/docs/1.0.0/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -806,6 +823,7 @@ describe('versioned site', () => {
|
||||||
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/fr/docs/1.0.0/hello',
|
permalink: '/fr/docs/1.0.0/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
@ -840,6 +858,7 @@ describe('versioned site', () => {
|
||||||
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/fr/docs/1.0.0/hello',
|
permalink: '/fr/docs/1.0.0/hello',
|
||||||
slug: '/hello',
|
slug: '/hello',
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {isMatch} from 'picomatch';
|
import {isMatch} from 'picomatch';
|
||||||
import commander from 'commander';
|
import commander from 'commander';
|
||||||
import {kebabCase} from 'lodash';
|
import {kebabCase, orderBy} from 'lodash';
|
||||||
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import pluginContentDocs from '../index';
|
import pluginContentDocs from '../index';
|
||||||
|
@ -24,7 +24,7 @@ import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
||||||
import * as cliDocs from '../cli';
|
import * as cliDocs from '../cli';
|
||||||
import {OptionsSchema} from '../options';
|
import {OptionsSchema} from '../options';
|
||||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
import {DocMetadata, LoadedVersion} from '../types';
|
import {DocMetadata, LoadedVersion, SidebarItemsGenerator} from '../types';
|
||||||
import {toSidebarsProp} from '../props';
|
import {toSidebarsProp} from '../props';
|
||||||
|
|
||||||
// @ts-expect-error: TODO typedefs missing?
|
// @ts-expect-error: TODO typedefs missing?
|
||||||
|
@ -33,6 +33,17 @@ import {validate} from 'webpack';
|
||||||
function findDocById(version: LoadedVersion, unversionedId: string) {
|
function findDocById(version: LoadedVersion, unversionedId: string) {
|
||||||
return version.docs.find((item) => item.unversionedId === unversionedId);
|
return version.docs.find((item) => item.unversionedId === unversionedId);
|
||||||
}
|
}
|
||||||
|
function getDocById(version: LoadedVersion, unversionedId: string) {
|
||||||
|
const doc = findDocById(version, unversionedId);
|
||||||
|
if (!doc) {
|
||||||
|
throw new Error(
|
||||||
|
`No doc found with id=${unversionedId} in version ${version.versionName}.
|
||||||
|
Available ids=\n- ${version.docs.map((d) => d.unversionedId).join('\n- ')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
const defaultDocMetadata: Partial<DocMetadata> = {
|
const defaultDocMetadata: Partial<DocMetadata> = {
|
||||||
next: undefined,
|
next: undefined,
|
||||||
previous: undefined,
|
previous: undefined,
|
||||||
|
@ -40,6 +51,7 @@ const defaultDocMetadata: Partial<DocMetadata> = {
|
||||||
lastUpdatedAt: undefined,
|
lastUpdatedAt: undefined,
|
||||||
lastUpdatedBy: undefined,
|
lastUpdatedBy: undefined,
|
||||||
sidebar_label: undefined,
|
sidebar_label: undefined,
|
||||||
|
formattedLastUpdatedAt: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFakeActions = (contentDir: string) => {
|
const createFakeActions = (contentDir: string) => {
|
||||||
|
@ -203,6 +215,7 @@ describe('simple website', () => {
|
||||||
"sidebars.json",
|
"sidebars.json",
|
||||||
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
|
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
|
||||||
"docs/**/*.{md,mdx}",
|
"docs/**/*.{md,mdx}",
|
||||||
|
"docs/**/_category_.{json,yml,yaml}",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
expect(isMatch('docs/hello.md', matchPattern)).toEqual(true);
|
expect(isMatch('docs/hello.md', matchPattern)).toEqual(true);
|
||||||
|
@ -247,6 +260,7 @@ describe('simple website', () => {
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: true,
|
isDocsHomePage: true,
|
||||||
permalink: '/docs/',
|
permalink: '/docs/',
|
||||||
slug: '/',
|
slug: '/',
|
||||||
|
@ -268,11 +282,12 @@ describe('simple website', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(findDocById(currentVersion, 'foo/bar')).toEqual({
|
expect(getDocById(currentVersion, 'foo/bar')).toEqual({
|
||||||
...defaultDocMetadata,
|
...defaultDocMetadata,
|
||||||
version: 'current',
|
version: 'current',
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
next: {
|
next: {
|
||||||
title: 'baz',
|
title: 'baz',
|
||||||
|
@ -368,15 +383,19 @@ describe('versioned website', () => {
|
||||||
"sidebars.json",
|
"sidebars.json",
|
||||||
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
|
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
|
||||||
"docs/**/*.{md,mdx}",
|
"docs/**/*.{md,mdx}",
|
||||||
|
"docs/**/_category_.{json,yml,yaml}",
|
||||||
"versioned_sidebars/version-1.0.1-sidebars.json",
|
"versioned_sidebars/version-1.0.1-sidebars.json",
|
||||||
"i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}",
|
"i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}",
|
||||||
"versioned_docs/version-1.0.1/**/*.{md,mdx}",
|
"versioned_docs/version-1.0.1/**/*.{md,mdx}",
|
||||||
|
"versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}",
|
||||||
"versioned_sidebars/version-1.0.0-sidebars.json",
|
"versioned_sidebars/version-1.0.0-sidebars.json",
|
||||||
"i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}",
|
"i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}",
|
||||||
"versioned_docs/version-1.0.0/**/*.{md,mdx}",
|
"versioned_docs/version-1.0.0/**/*.{md,mdx}",
|
||||||
|
"versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}",
|
||||||
"versioned_sidebars/version-withSlugs-sidebars.json",
|
"versioned_sidebars/version-withSlugs-sidebars.json",
|
||||||
"i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}",
|
"i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}",
|
||||||
"versioned_docs/version-withSlugs/**/*.{md,mdx}",
|
"versioned_docs/version-withSlugs/**/*.{md,mdx}",
|
||||||
|
"versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
expect(isMatch('docs/hello.md', matchPattern)).toEqual(true);
|
expect(isMatch('docs/hello.md', matchPattern)).toEqual(true);
|
||||||
|
@ -427,10 +446,11 @@ describe('versioned website', () => {
|
||||||
expect(findDocById(version101, 'foo/baz')).toBeUndefined();
|
expect(findDocById(version101, 'foo/baz')).toBeUndefined();
|
||||||
expect(findDocById(versionWithSlugs, 'foo/baz')).toBeUndefined();
|
expect(findDocById(versionWithSlugs, 'foo/baz')).toBeUndefined();
|
||||||
|
|
||||||
expect(findDocById(currentVersion, 'foo/bar')).toEqual({
|
expect(getDocById(currentVersion, 'foo/bar')).toEqual({
|
||||||
...defaultDocMetadata,
|
...defaultDocMetadata,
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/next/foo/barSlug',
|
permalink: '/docs/next/foo/barSlug',
|
||||||
slug: '/foo/barSlug',
|
slug: '/foo/barSlug',
|
||||||
|
@ -452,10 +472,11 @@ describe('versioned website', () => {
|
||||||
permalink: '/docs/next/',
|
permalink: '/docs/next/',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(findDocById(currentVersion, 'hello')).toEqual({
|
expect(getDocById(currentVersion, 'hello')).toEqual({
|
||||||
...defaultDocMetadata,
|
...defaultDocMetadata,
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: true,
|
isDocsHomePage: true,
|
||||||
permalink: '/docs/next/',
|
permalink: '/docs/next/',
|
||||||
slug: '/',
|
slug: '/',
|
||||||
|
@ -474,10 +495,11 @@ describe('versioned website', () => {
|
||||||
permalink: '/docs/next/foo/barSlug',
|
permalink: '/docs/next/foo/barSlug',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(findDocById(version101, 'hello')).toEqual({
|
expect(getDocById(version101, 'hello')).toEqual({
|
||||||
...defaultDocMetadata,
|
...defaultDocMetadata,
|
||||||
id: 'version-1.0.1/hello',
|
id: 'version-1.0.1/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: true,
|
isDocsHomePage: true,
|
||||||
permalink: '/docs/',
|
permalink: '/docs/',
|
||||||
slug: '/',
|
slug: '/',
|
||||||
|
@ -496,10 +518,11 @@ describe('versioned website', () => {
|
||||||
permalink: '/docs/foo/bar',
|
permalink: '/docs/foo/bar',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(findDocById(version100, 'foo/baz')).toEqual({
|
expect(getDocById(version100, 'foo/baz')).toEqual({
|
||||||
...defaultDocMetadata,
|
...defaultDocMetadata,
|
||||||
id: 'version-1.0.0/foo/baz',
|
id: 'version-1.0.0/foo/baz',
|
||||||
unversionedId: 'foo/baz',
|
unversionedId: 'foo/baz',
|
||||||
|
sourceDirName: 'foo',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/foo/baz',
|
permalink: '/docs/1.0.0/foo/baz',
|
||||||
slug: '/foo/baz',
|
slug: '/foo/baz',
|
||||||
|
@ -611,9 +634,11 @@ describe('versioned website (community)', () => {
|
||||||
"community_sidebars.json",
|
"community_sidebars.json",
|
||||||
"i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}",
|
"i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}",
|
||||||
"community/**/*.{md,mdx}",
|
"community/**/*.{md,mdx}",
|
||||||
|
"community/**/_category_.{json,yml,yaml}",
|
||||||
"community_versioned_sidebars/version-1.0.0-sidebars.json",
|
"community_versioned_sidebars/version-1.0.0-sidebars.json",
|
||||||
"i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}",
|
"i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}",
|
||||||
"community_versioned_docs/version-1.0.0/**/*.{md,mdx}",
|
"community_versioned_docs/version-1.0.0/**/*.{md,mdx}",
|
||||||
|
"community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
expect(isMatch('community/team.md', matchPattern)).toEqual(true);
|
expect(isMatch('community/team.md', matchPattern)).toEqual(true);
|
||||||
|
@ -644,10 +669,11 @@ describe('versioned website (community)', () => {
|
||||||
expect(content.loadedVersions.length).toEqual(2);
|
expect(content.loadedVersions.length).toEqual(2);
|
||||||
const [currentVersion, version100] = content.loadedVersions;
|
const [currentVersion, version100] = content.loadedVersions;
|
||||||
|
|
||||||
expect(findDocById(currentVersion, 'team')).toEqual({
|
expect(getDocById(currentVersion, 'team')).toEqual({
|
||||||
...defaultDocMetadata,
|
...defaultDocMetadata,
|
||||||
id: 'team',
|
id: 'team',
|
||||||
unversionedId: 'team',
|
unversionedId: 'team',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/community/next/team',
|
permalink: '/community/next/team',
|
||||||
slug: '/team',
|
slug: '/team',
|
||||||
|
@ -659,10 +685,11 @@ describe('versioned website (community)', () => {
|
||||||
sidebar: 'community',
|
sidebar: 'community',
|
||||||
frontMatter: {title: 'Team title translated'},
|
frontMatter: {title: 'Team title translated'},
|
||||||
});
|
});
|
||||||
expect(findDocById(version100, 'team')).toEqual({
|
expect(getDocById(version100, 'team')).toEqual({
|
||||||
...defaultDocMetadata,
|
...defaultDocMetadata,
|
||||||
id: 'version-1.0.0/team',
|
id: 'version-1.0.0/team',
|
||||||
unversionedId: 'team',
|
unversionedId: 'team',
|
||||||
|
sourceDirName: '.',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/community/team',
|
permalink: '/community/team',
|
||||||
slug: '/team',
|
slug: '/team',
|
||||||
|
@ -709,7 +736,7 @@ describe('site with doc label', () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = await plugin.loadContent();
|
const content = (await plugin.loadContent?.())!;
|
||||||
|
|
||||||
return {content};
|
return {content};
|
||||||
}
|
}
|
||||||
|
@ -730,3 +757,807 @@ describe('site with doc label', () => {
|
||||||
expect(sidebarProps.docs[1].label).toBe('Hello 2 From Doc');
|
expect(sidebarProps.docs[1].label).toBe('Hello 2 From Doc');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('site with full autogenerated sidebar', () => {
|
||||||
|
async function loadSite() {
|
||||||
|
const siteDir = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__',
|
||||||
|
'site-with-autogenerated-sidebar',
|
||||||
|
);
|
||||||
|
const context = await loadContext(siteDir);
|
||||||
|
const plugin = pluginContentDocs(
|
||||||
|
context,
|
||||||
|
normalizePluginOptions(OptionsSchema, {
|
||||||
|
path: 'docs',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = (await plugin.loadContent?.())!;
|
||||||
|
|
||||||
|
return {content, siteDir};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sidebar is fully autogenerated', async () => {
|
||||||
|
const {content} = await loadSite();
|
||||||
|
const version = content.loadedVersions[0];
|
||||||
|
|
||||||
|
expect(version.sidebars).toEqual({
|
||||||
|
defaultSidebar: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'getting-started',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'installation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Guides',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'Guides/guide1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'Guides/guide2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'Guides/guide2.5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'Guides/guide3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'Guides/guide4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'Guides/guide5',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'API (label from _category_.json)',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/api-overview',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Core APIs',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
|
||||||
|
id: 'API/Core APIs/Client API',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/Core APIs/Server API',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Extension APIs (label from _category_.yml)',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/Extension APIs/Plugin API',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/Extension APIs/Theme API',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/api-end',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('docs in fully generated sidebar have correct metadatas', async () => {
|
||||||
|
const {content, siteDir} = await loadSite();
|
||||||
|
const version = content.loadedVersions[0];
|
||||||
|
|
||||||
|
expect(getDocById(version, 'getting-started')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'getting-started',
|
||||||
|
unversionedId: 'getting-started',
|
||||||
|
sourceDirName: '.',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/getting-started',
|
||||||
|
slug: '/getting-started',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'0-getting-started.md',
|
||||||
|
),
|
||||||
|
title: 'Getting Started',
|
||||||
|
description: 'Getting started text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 0,
|
||||||
|
previous: undefined,
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/installation',
|
||||||
|
title: 'Installation',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'installation')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'installation',
|
||||||
|
unversionedId: 'installation',
|
||||||
|
sourceDirName: '.',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/installation',
|
||||||
|
slug: '/installation',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'1-installation.md',
|
||||||
|
),
|
||||||
|
title: 'Installation',
|
||||||
|
description: 'Installation text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 1,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/getting-started',
|
||||||
|
title: 'Getting Started',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/Guides/guide1',
|
||||||
|
title: 'Guide 1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'Guides/guide1')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'Guides/guide1',
|
||||||
|
unversionedId: 'Guides/guide1',
|
||||||
|
sourceDirName: 'Guides',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/Guides/guide1',
|
||||||
|
slug: '/Guides/guide1',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'Guides',
|
||||||
|
'z-guide1.md',
|
||||||
|
),
|
||||||
|
title: 'Guide 1',
|
||||||
|
description: 'Guide 1 text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {
|
||||||
|
id: 'guide1',
|
||||||
|
sidebar_position: 1,
|
||||||
|
},
|
||||||
|
sidebarPosition: 1,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/installation',
|
||||||
|
title: 'Installation',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/Guides/guide2',
|
||||||
|
title: 'Guide 2',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'Guides/guide2')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'Guides/guide2',
|
||||||
|
unversionedId: 'Guides/guide2',
|
||||||
|
sourceDirName: 'Guides',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/Guides/guide2',
|
||||||
|
slug: '/Guides/guide2',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'Guides',
|
||||||
|
'02-guide2.md',
|
||||||
|
),
|
||||||
|
title: 'Guide 2',
|
||||||
|
description: 'Guide 2 text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {
|
||||||
|
id: 'guide2',
|
||||||
|
},
|
||||||
|
sidebarPosition: 2,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/Guides/guide1',
|
||||||
|
title: 'Guide 1',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/Guides/guide2.5',
|
||||||
|
title: 'Guide 2.5',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'Guides/guide2.5')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'Guides/guide2.5',
|
||||||
|
unversionedId: 'Guides/guide2.5',
|
||||||
|
sourceDirName: 'Guides',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/Guides/guide2.5',
|
||||||
|
slug: '/Guides/guide2.5',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'Guides',
|
||||||
|
'0-guide2.5.md',
|
||||||
|
),
|
||||||
|
title: 'Guide 2.5',
|
||||||
|
description: 'Guide 2.5 text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {
|
||||||
|
id: 'guide2.5',
|
||||||
|
sidebar_position: 2.5,
|
||||||
|
},
|
||||||
|
sidebarPosition: 2.5,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/Guides/guide2',
|
||||||
|
title: 'Guide 2',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/Guides/guide3',
|
||||||
|
title: 'Guide 3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'Guides/guide3')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'Guides/guide3',
|
||||||
|
unversionedId: 'Guides/guide3',
|
||||||
|
sourceDirName: 'Guides',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/Guides/guide3',
|
||||||
|
slug: '/Guides/guide3',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'Guides',
|
||||||
|
'guide3.md',
|
||||||
|
),
|
||||||
|
title: 'Guide 3',
|
||||||
|
description: 'Guide 3 text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {
|
||||||
|
id: 'guide3',
|
||||||
|
sidebar_position: 3,
|
||||||
|
},
|
||||||
|
sidebarPosition: 3,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/Guides/guide2.5',
|
||||||
|
title: 'Guide 2.5',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/Guides/guide4',
|
||||||
|
title: 'Guide 4',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'Guides/guide4')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'Guides/guide4',
|
||||||
|
unversionedId: 'Guides/guide4',
|
||||||
|
sourceDirName: 'Guides',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/Guides/guide4',
|
||||||
|
slug: '/Guides/guide4',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'Guides',
|
||||||
|
'a-guide4.md',
|
||||||
|
),
|
||||||
|
title: 'Guide 4',
|
||||||
|
description: 'Guide 4 text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {
|
||||||
|
id: 'guide4',
|
||||||
|
},
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/Guides/guide3',
|
||||||
|
title: 'Guide 3',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/Guides/guide5',
|
||||||
|
title: 'Guide 5',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'Guides/guide5')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'Guides/guide5',
|
||||||
|
unversionedId: 'Guides/guide5',
|
||||||
|
sourceDirName: 'Guides',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/Guides/guide5',
|
||||||
|
slug: '/Guides/guide5',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'Guides',
|
||||||
|
'b-guide5.md',
|
||||||
|
),
|
||||||
|
title: 'Guide 5',
|
||||||
|
description: 'Guide 5 text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {
|
||||||
|
id: 'guide5',
|
||||||
|
},
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/Guides/guide4',
|
||||||
|
title: 'Guide 4',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/api-overview',
|
||||||
|
title: 'API Overview',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/api-overview')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/api-overview',
|
||||||
|
unversionedId: 'API/api-overview',
|
||||||
|
sourceDirName: '3-API',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/api-overview',
|
||||||
|
slug: '/API/api-overview',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'00_api-overview.md',
|
||||||
|
),
|
||||||
|
title: 'API Overview',
|
||||||
|
description: 'API Overview text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 0,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/Guides/guide5',
|
||||||
|
title: 'Guide 5',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/Core APIs/Client API',
|
||||||
|
title: 'Client API',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/Core APIs/Client API')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/Core APIs/Client API',
|
||||||
|
unversionedId: 'API/Core APIs/Client API',
|
||||||
|
sourceDirName: '3-API/01_Core APIs',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/Core APIs/Client API',
|
||||||
|
slug: '/API/Core APIs/Client API',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'01_Core APIs',
|
||||||
|
'0 --- Client API.md',
|
||||||
|
),
|
||||||
|
title: 'Client API',
|
||||||
|
description: 'Client API text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 0,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/api-overview',
|
||||||
|
title: 'API Overview',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/Core APIs/Server API',
|
||||||
|
title: 'Server API',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/Core APIs/Server API')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/Core APIs/Server API',
|
||||||
|
unversionedId: 'API/Core APIs/Server API',
|
||||||
|
sourceDirName: '3-API/01_Core APIs',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/Core APIs/Server API',
|
||||||
|
slug: '/API/Core APIs/Server API',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'01_Core APIs',
|
||||||
|
'1 --- Server API.md',
|
||||||
|
),
|
||||||
|
title: 'Server API',
|
||||||
|
description: 'Server API text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 1,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/Core APIs/Client API',
|
||||||
|
title: 'Client API',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/Extension APIs/Plugin API',
|
||||||
|
title: 'Plugin API',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/Extension APIs/Plugin API')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/Extension APIs/Plugin API',
|
||||||
|
unversionedId: 'API/Extension APIs/Plugin API',
|
||||||
|
sourceDirName: '3-API/02_Extension APIs',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/Extension APIs/Plugin API',
|
||||||
|
slug: '/API/Extension APIs/Plugin API',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'02_Extension APIs',
|
||||||
|
'0. Plugin API.md',
|
||||||
|
),
|
||||||
|
title: 'Plugin API',
|
||||||
|
description: 'Plugin API text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 0,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/Core APIs/Server API',
|
||||||
|
title: 'Server API',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/Extension APIs/Theme API',
|
||||||
|
title: 'Theme API',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/Extension APIs/Theme API')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/Extension APIs/Theme API',
|
||||||
|
unversionedId: 'API/Extension APIs/Theme API',
|
||||||
|
sourceDirName: '3-API/02_Extension APIs',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/Extension APIs/Theme API',
|
||||||
|
slug: '/API/Extension APIs/Theme API',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'02_Extension APIs',
|
||||||
|
'1. Theme API.md',
|
||||||
|
),
|
||||||
|
title: 'Theme API',
|
||||||
|
description: 'Theme API text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 1,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/Extension APIs/Plugin API',
|
||||||
|
title: 'Plugin API',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/api-end',
|
||||||
|
title: 'API End',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/api-end')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/api-end',
|
||||||
|
unversionedId: 'API/api-end',
|
||||||
|
sourceDirName: '3-API',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/api-end',
|
||||||
|
slug: '/API/api-end',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'03_api-end.md',
|
||||||
|
),
|
||||||
|
title: 'API End',
|
||||||
|
description: 'API End text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'defaultSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 3,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/Extension APIs/Theme API',
|
||||||
|
title: 'Theme API',
|
||||||
|
},
|
||||||
|
next: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('site with partial autogenerated sidebars', () => {
|
||||||
|
async function loadSite() {
|
||||||
|
const siteDir = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__',
|
||||||
|
'site-with-autogenerated-sidebar',
|
||||||
|
);
|
||||||
|
const context = await loadContext(siteDir, {});
|
||||||
|
const plugin = pluginContentDocs(
|
||||||
|
context,
|
||||||
|
normalizePluginOptions(OptionsSchema, {
|
||||||
|
path: 'docs',
|
||||||
|
sidebarPath: path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__',
|
||||||
|
'site-with-autogenerated-sidebar',
|
||||||
|
'partialAutogeneratedSidebars.js',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = (await plugin.loadContent?.())!;
|
||||||
|
|
||||||
|
return {content, siteDir};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sidebar is partially autogenerated', async () => {
|
||||||
|
const {content} = await loadSite();
|
||||||
|
const version = content.loadedVersions[0];
|
||||||
|
|
||||||
|
expect(version.sidebars).toEqual({
|
||||||
|
someSidebar: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/api-end',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Some category',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/api-overview',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/Extension APIs/Plugin API',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'API/Extension APIs/Theme API',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('docs in partially generated sidebar have correct metadatas', async () => {
|
||||||
|
const {content, siteDir} = await loadSite();
|
||||||
|
const version = content.loadedVersions[0];
|
||||||
|
|
||||||
|
// Only looking at the docs of the autogen sidebar, others metadatas should not be affected
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/api-end')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/api-end',
|
||||||
|
unversionedId: 'API/api-end',
|
||||||
|
sourceDirName: '3-API',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/api-end',
|
||||||
|
slug: '/API/api-end',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'03_api-end.md',
|
||||||
|
),
|
||||||
|
title: 'API End',
|
||||||
|
description: 'API End text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'someSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 3, // ignored (not part of the autogenerated sidebar slice)
|
||||||
|
previous: undefined,
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/api-overview',
|
||||||
|
title: 'API Overview',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/api-overview')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/api-overview',
|
||||||
|
unversionedId: 'API/api-overview',
|
||||||
|
sourceDirName: '3-API',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/api-overview',
|
||||||
|
slug: '/API/api-overview',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'00_api-overview.md',
|
||||||
|
),
|
||||||
|
title: 'API Overview',
|
||||||
|
description: 'API Overview text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'someSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 0, // ignored (not part of the autogenerated sidebar slice)
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/api-end',
|
||||||
|
title: 'API End',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/Extension APIs/Plugin API',
|
||||||
|
title: 'Plugin API',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/Extension APIs/Plugin API')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/Extension APIs/Plugin API',
|
||||||
|
unversionedId: 'API/Extension APIs/Plugin API',
|
||||||
|
sourceDirName: '3-API/02_Extension APIs',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/Extension APIs/Plugin API',
|
||||||
|
slug: '/API/Extension APIs/Plugin API',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'02_Extension APIs',
|
||||||
|
'0. Plugin API.md',
|
||||||
|
),
|
||||||
|
title: 'Plugin API',
|
||||||
|
description: 'Plugin API text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'someSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 0,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/api-overview',
|
||||||
|
title: 'API Overview',
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
permalink: '/docs/API/Extension APIs/Theme API',
|
||||||
|
title: 'Theme API',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getDocById(version, 'API/Extension APIs/Theme API')).toEqual({
|
||||||
|
...defaultDocMetadata,
|
||||||
|
id: 'API/Extension APIs/Theme API',
|
||||||
|
unversionedId: 'API/Extension APIs/Theme API',
|
||||||
|
sourceDirName: '3-API/02_Extension APIs',
|
||||||
|
isDocsHomePage: false,
|
||||||
|
permalink: '/docs/API/Extension APIs/Theme API',
|
||||||
|
slug: '/API/Extension APIs/Theme API',
|
||||||
|
source: path.posix.join(
|
||||||
|
'@site',
|
||||||
|
posixPath(path.relative(siteDir, version.contentPath)),
|
||||||
|
'3-API',
|
||||||
|
'02_Extension APIs',
|
||||||
|
'1. Theme API.md',
|
||||||
|
),
|
||||||
|
title: 'Theme API',
|
||||||
|
description: 'Theme API text',
|
||||||
|
version: 'current',
|
||||||
|
sidebar: 'someSidebar',
|
||||||
|
frontMatter: {},
|
||||||
|
sidebarPosition: 1,
|
||||||
|
previous: {
|
||||||
|
permalink: '/docs/API/Extension APIs/Plugin API',
|
||||||
|
title: 'Plugin API',
|
||||||
|
},
|
||||||
|
next: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('site with custom sidebar items generator', () => {
|
||||||
|
async function loadSite(sidebarItemsGenerator: SidebarItemsGenerator) {
|
||||||
|
const siteDir = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__',
|
||||||
|
'site-with-autogenerated-sidebar',
|
||||||
|
);
|
||||||
|
const context = await loadContext(siteDir);
|
||||||
|
const plugin = pluginContentDocs(
|
||||||
|
context,
|
||||||
|
normalizePluginOptions(OptionsSchema, {
|
||||||
|
path: 'docs',
|
||||||
|
sidebarItemsGenerator,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const content = (await plugin.loadContent?.())!;
|
||||||
|
return {content, siteDir};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sidebar is autogenerated according to custom sidebarItemsGenerator', async () => {
|
||||||
|
const customSidebarItemsGenerator: SidebarItemsGenerator = async () => {
|
||||||
|
return [
|
||||||
|
{type: 'doc', id: 'API/api-overview'},
|
||||||
|
{type: 'doc', id: 'API/api-end'},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const customSidebarItemsGeneratorMock: SidebarItemsGenerator = jest.fn(
|
||||||
|
customSidebarItemsGenerator,
|
||||||
|
);
|
||||||
|
|
||||||
|
const {content} = await loadSite(customSidebarItemsGeneratorMock);
|
||||||
|
const version = content.loadedVersions[0];
|
||||||
|
|
||||||
|
expect(version.sidebars).toEqual({
|
||||||
|
defaultSidebar: [
|
||||||
|
{type: 'doc', id: 'API/api-overview'},
|
||||||
|
{type: 'doc', id: 'API/api-end'},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sidebarItemsGenerator is called with appropriate data', async () => {
|
||||||
|
type GeneratorArg = Parameters<SidebarItemsGenerator>[0];
|
||||||
|
|
||||||
|
const customSidebarItemsGeneratorMock = jest.fn(
|
||||||
|
async (_arg: GeneratorArg) => [],
|
||||||
|
);
|
||||||
|
const {siteDir} = await loadSite(customSidebarItemsGeneratorMock);
|
||||||
|
|
||||||
|
const generatorArg: GeneratorArg =
|
||||||
|
customSidebarItemsGeneratorMock.mock.calls[0][0];
|
||||||
|
|
||||||
|
// Make test pass even if docs are in different order and paths are absolutes
|
||||||
|
function makeDeterministic(arg: GeneratorArg): GeneratorArg {
|
||||||
|
return {
|
||||||
|
...arg,
|
||||||
|
docs: orderBy(arg.docs, 'id'),
|
||||||
|
version: {
|
||||||
|
...arg.version,
|
||||||
|
contentPath: path.relative(siteDir, arg.version.contentPath),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(makeDeterministic(generatorArg)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
extractNumberPrefix,
|
||||||
|
stripNumberPrefix,
|
||||||
|
stripPathNumberPrefixes,
|
||||||
|
} from '../numberPrefix';
|
||||||
|
|
||||||
|
const BadNumberPrefixPatterns = [
|
||||||
|
'a1-My Doc',
|
||||||
|
'My Doc-000',
|
||||||
|
'00abc01-My Doc',
|
||||||
|
'My 001- Doc',
|
||||||
|
'My -001 Doc',
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('stripNumberPrefix', () => {
|
||||||
|
test('should strip number prefix if present', () => {
|
||||||
|
expect(stripNumberPrefix('1-My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('01-My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001-My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 - My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 - My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('999 - My Doc')).toEqual('My Doc');
|
||||||
|
//
|
||||||
|
expect(stripNumberPrefix('1---My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('01---My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001---My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 --- My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 --- My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('999 --- My Doc')).toEqual('My Doc');
|
||||||
|
//
|
||||||
|
expect(stripNumberPrefix('1___My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('01___My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001___My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 ___ My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 ___ My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('999 ___ My Doc')).toEqual('My Doc');
|
||||||
|
//
|
||||||
|
expect(stripNumberPrefix('1.My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('01.My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001.My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 . My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('001 . My Doc')).toEqual('My Doc');
|
||||||
|
expect(stripNumberPrefix('999 . My Doc')).toEqual('My Doc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not strip number prefix if pattern does not match', () => {
|
||||||
|
BadNumberPrefixPatterns.forEach((badPattern) => {
|
||||||
|
expect(stripNumberPrefix(badPattern)).toEqual(badPattern);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('stripPathNumberPrefix', () => {
|
||||||
|
test('should strip number prefixes in paths', () => {
|
||||||
|
expect(
|
||||||
|
stripPathNumberPrefixes(
|
||||||
|
'0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3',
|
||||||
|
),
|
||||||
|
).toEqual('MyRootFolder0/MySubFolder1/MyDeepFolder2/MyDoc3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('extractNumberPrefix', () => {
|
||||||
|
test('should extract number prefix if present', () => {
|
||||||
|
expect(extractNumberPrefix('0-My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 0,
|
||||||
|
});
|
||||||
|
expect(extractNumberPrefix('1-My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 1,
|
||||||
|
});
|
||||||
|
expect(extractNumberPrefix('01-My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 1,
|
||||||
|
});
|
||||||
|
expect(extractNumberPrefix('001-My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 1,
|
||||||
|
});
|
||||||
|
expect(extractNumberPrefix('001 - My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 1,
|
||||||
|
});
|
||||||
|
expect(extractNumberPrefix('001 - My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 1,
|
||||||
|
});
|
||||||
|
expect(extractNumberPrefix('999 - My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 999,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(extractNumberPrefix('0046036 - My Doc')).toEqual({
|
||||||
|
filename: 'My Doc',
|
||||||
|
numberPrefix: 46036,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not extract number prefix if pattern does not match', () => {
|
||||||
|
BadNumberPrefixPatterns.forEach((badPattern) => {
|
||||||
|
expect(extractNumberPrefix(badPattern)).toEqual({
|
||||||
|
filename: badPattern,
|
||||||
|
numberPrefix: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import {OptionsSchema, DEFAULT_OPTIONS} from '../options';
|
import {OptionsSchema, DEFAULT_OPTIONS} from '../options';
|
||||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
|
import {DefaultSidebarItemsGenerator} from '../sidebarItemsGenerator';
|
||||||
|
|
||||||
// the type of remark/rehype plugins is function
|
// the type of remark/rehype plugins is function
|
||||||
const markdownPluginsFunctionStub = () => {};
|
const markdownPluginsFunctionStub = () => {};
|
||||||
|
@ -26,6 +27,7 @@ describe('normalizeDocsPluginOptions', () => {
|
||||||
homePageId: 'home', // Document id for docs home page.
|
homePageId: 'home', // Document id for docs home page.
|
||||||
include: ['**/*.{md,mdx}'], // Extensions to include.
|
include: ['**/*.{md,mdx}'], // Extensions to include.
|
||||||
sidebarPath: 'my-sidebar', // Path to sidebar configuration for showing a list of markdown pages.
|
sidebarPath: 'my-sidebar', // Path to sidebar configuration for showing a list of markdown pages.
|
||||||
|
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
||||||
docLayoutComponent: '@theme/DocPage',
|
docLayoutComponent: '@theme/DocPage',
|
||||||
docItemComponent: '@theme/DocItem',
|
docItemComponent: '@theme/DocItem',
|
||||||
remarkPlugins: [markdownPluginsObjectStub],
|
remarkPlugins: [markdownPluginsObjectStub],
|
||||||
|
|
|
@ -0,0 +1,268 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
CategoryMetadatasFile,
|
||||||
|
DefaultSidebarItemsGenerator,
|
||||||
|
} from '../sidebarItemsGenerator';
|
||||||
|
import {DefaultCategoryCollapsedValue} from '../sidebars';
|
||||||
|
import {Sidebar, SidebarItemsGenerator} from '../types';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
|
describe('DefaultSidebarItemsGenerator', () => {
|
||||||
|
function testDefaultSidebarItemsGenerator(
|
||||||
|
options: Partial<Parameters<SidebarItemsGenerator>[0]>,
|
||||||
|
) {
|
||||||
|
return DefaultSidebarItemsGenerator({
|
||||||
|
item: {
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: '.',
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
versionName: 'current',
|
||||||
|
contentPath: 'docs',
|
||||||
|
},
|
||||||
|
docs: [],
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockCategoryMetadataFiles(
|
||||||
|
categoryMetadataFiles: Record<string, Partial<CategoryMetadatasFile>>,
|
||||||
|
) {
|
||||||
|
jest.spyOn(fs, 'pathExists').mockImplementation((metadataFilePath) => {
|
||||||
|
return typeof categoryMetadataFiles[metadataFilePath] !== 'undefined';
|
||||||
|
});
|
||||||
|
jest.spyOn(fs, 'readFile').mockImplementation(
|
||||||
|
// @ts-expect-error: annoying TS error due to overrides
|
||||||
|
async (metadataFilePath: string) => {
|
||||||
|
return JSON.stringify(categoryMetadataFiles[metadataFilePath]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('generates empty sidebar slice when no docs and emit a warning', async () => {
|
||||||
|
const consoleWarn = jest.spyOn(console, 'warn');
|
||||||
|
const sidebarSlice = await testDefaultSidebarItemsGenerator({
|
||||||
|
docs: [],
|
||||||
|
});
|
||||||
|
expect(sidebarSlice).toEqual([]);
|
||||||
|
expect(consoleWarn).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(
|
||||||
|
/No docs found in dir .: can't auto-generate a sidebar/,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generates simple flat sidebar', async () => {
|
||||||
|
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
||||||
|
item: {
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: '.',
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
versionName: 'current',
|
||||||
|
contentPath: '',
|
||||||
|
},
|
||||||
|
docs: [
|
||||||
|
{
|
||||||
|
id: 'doc1',
|
||||||
|
source: 'doc1.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: 2,
|
||||||
|
frontMatter: {
|
||||||
|
sidebar_label: 'doc1 sidebar label',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doc2',
|
||||||
|
source: 'doc2.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: 3,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doc3',
|
||||||
|
source: 'doc3.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: 1,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doc4',
|
||||||
|
source: 'doc4.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: 1.5,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doc5',
|
||||||
|
source: 'doc5.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sidebarSlice).toEqual([
|
||||||
|
{type: 'doc', id: 'doc3'},
|
||||||
|
{type: 'doc', id: 'doc4'},
|
||||||
|
{type: 'doc', id: 'doc1', label: 'doc1 sidebar label'},
|
||||||
|
{type: 'doc', id: 'doc2'},
|
||||||
|
{type: 'doc', id: 'doc5'},
|
||||||
|
] as Sidebar);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generates complex nested sidebar', async () => {
|
||||||
|
mockCategoryMetadataFiles({
|
||||||
|
'02-Guides/_category_.json': {collapsed: false},
|
||||||
|
'02-Guides/01-SubGuides/_category_.yml': {
|
||||||
|
label: 'SubGuides (metadata file label)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
||||||
|
item: {
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: '.',
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
versionName: 'current',
|
||||||
|
contentPath: '',
|
||||||
|
},
|
||||||
|
docs: [
|
||||||
|
{
|
||||||
|
id: 'intro',
|
||||||
|
source: 'intro.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: 1,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tutorial2',
|
||||||
|
source: 'tutorial2.md',
|
||||||
|
sourceDirName: '01-Tutorials',
|
||||||
|
sidebarPosition: 2,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tutorial1',
|
||||||
|
source: 'tutorial1.md',
|
||||||
|
sourceDirName: '01-Tutorials',
|
||||||
|
sidebarPosition: 1,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'guide2',
|
||||||
|
source: 'guide2.md',
|
||||||
|
sourceDirName: '02-Guides',
|
||||||
|
sidebarPosition: 2,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'guide1',
|
||||||
|
source: 'guide1.md',
|
||||||
|
sourceDirName: '02-Guides',
|
||||||
|
sidebarPosition: 1,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'nested-guide',
|
||||||
|
source: 'nested-guide.md',
|
||||||
|
sourceDirName: '02-Guides/01-SubGuides',
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'end',
|
||||||
|
source: 'end.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: 3,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sidebarSlice).toEqual([
|
||||||
|
{type: 'doc', id: 'intro'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Tutorials',
|
||||||
|
collapsed: DefaultCategoryCollapsedValue,
|
||||||
|
items: [
|
||||||
|
{type: 'doc', id: 'tutorial1'},
|
||||||
|
{type: 'doc', id: 'tutorial2'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Guides',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{type: 'doc', id: 'guide1'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'SubGuides (metadata file label)',
|
||||||
|
collapsed: DefaultCategoryCollapsedValue,
|
||||||
|
items: [{type: 'doc', id: 'nested-guide'}],
|
||||||
|
},
|
||||||
|
{type: 'doc', id: 'guide2'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{type: 'doc', id: 'end'},
|
||||||
|
] as Sidebar);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generates subfolder sidebar', async () => {
|
||||||
|
const sidebarSlice = await DefaultSidebarItemsGenerator({
|
||||||
|
item: {
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: 'subfolder/subsubfolder',
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
versionName: 'current',
|
||||||
|
contentPath: '',
|
||||||
|
},
|
||||||
|
docs: [
|
||||||
|
{
|
||||||
|
id: 'doc1',
|
||||||
|
source: 'doc1.md',
|
||||||
|
sourceDirName: 'subfolder/subsubfolder',
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doc2',
|
||||||
|
source: 'doc2.md',
|
||||||
|
sourceDirName: 'subfolder',
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doc3',
|
||||||
|
source: 'doc3.md',
|
||||||
|
sourceDirName: '.',
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'doc4',
|
||||||
|
source: 'doc4.md',
|
||||||
|
sourceDirName: 'subfolder/subsubfolder',
|
||||||
|
sidebarPosition: undefined,
|
||||||
|
frontMatter: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sidebarSlice).toEqual([
|
||||||
|
{type: 'doc', id: 'doc1'},
|
||||||
|
{type: 'doc', id: 'doc4'},
|
||||||
|
] as Sidebar);
|
||||||
|
});
|
||||||
|
});
|
|
@ -14,8 +14,16 @@ import {
|
||||||
collectSidebarCategories,
|
collectSidebarCategories,
|
||||||
collectSidebarLinks,
|
collectSidebarLinks,
|
||||||
transformSidebarItems,
|
transformSidebarItems,
|
||||||
|
DefaultSidebars,
|
||||||
|
processSidebars,
|
||||||
} from '../sidebars';
|
} from '../sidebars';
|
||||||
import {Sidebar, Sidebars} from '../types';
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarItem,
|
||||||
|
SidebarItemsGenerator,
|
||||||
|
Sidebars,
|
||||||
|
UnprocessedSidebars,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
/* eslint-disable global-require, import/no-dynamic-require */
|
/* eslint-disable global-require, import/no-dynamic-require */
|
||||||
|
|
||||||
|
@ -124,7 +132,7 @@ describe('loadSidebars', () => {
|
||||||
);
|
);
|
||||||
*/
|
*/
|
||||||
// See https://github.com/facebook/docusaurus/issues/3366
|
// See https://github.com/facebook/docusaurus/issues/3366
|
||||||
expect(loadSidebars('badpath')).toEqual({});
|
expect(loadSidebars('badpath')).toEqual(DefaultSidebars);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('undefined path', () => {
|
test('undefined path', () => {
|
||||||
|
@ -443,6 +451,131 @@ describe('transformSidebarItems', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('processSidebars', () => {
|
||||||
|
const StaticGeneratedSidebarSlice: SidebarItem[] = [
|
||||||
|
{type: 'doc', id: 'doc-generated-id-1'},
|
||||||
|
{type: 'doc', id: 'doc-generated-id-2'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const StaticSidebarItemsGenerator: SidebarItemsGenerator = jest.fn(
|
||||||
|
async () => {
|
||||||
|
return StaticGeneratedSidebarSlice;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
async function testProcessSidebars(unprocessedSidebars: UnprocessedSidebars) {
|
||||||
|
return processSidebars({
|
||||||
|
sidebarItemsGenerator: StaticSidebarItemsGenerator,
|
||||||
|
unprocessedSidebars,
|
||||||
|
docs: [],
|
||||||
|
// @ts-expect-error: useless for this test
|
||||||
|
version: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('let sidebars without autogenerated items untouched', async () => {
|
||||||
|
const unprocessedSidebars: UnprocessedSidebars = {
|
||||||
|
someSidebar: [
|
||||||
|
{type: 'doc', id: 'doc1'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
collapsed: false,
|
||||||
|
items: [{type: 'doc', id: 'doc2'}],
|
||||||
|
label: 'Category',
|
||||||
|
},
|
||||||
|
{type: 'link', href: 'https://facebook.com', label: 'FB'},
|
||||||
|
],
|
||||||
|
secondSidebar: [
|
||||||
|
{type: 'doc', id: 'doc3'},
|
||||||
|
{type: 'link', href: 'https://instagram.com', label: 'IG'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
collapsed: false,
|
||||||
|
items: [{type: 'doc', id: 'doc4'}],
|
||||||
|
label: 'Category',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedSidebar = await testProcessSidebars(unprocessedSidebars);
|
||||||
|
expect(processedSidebar).toEqual(unprocessedSidebars);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('replace autogenerated items by generated sidebars slices', async () => {
|
||||||
|
const unprocessedSidebars: UnprocessedSidebars = {
|
||||||
|
someSidebar: [
|
||||||
|
{type: 'doc', id: 'doc1'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{type: 'doc', id: 'doc2'},
|
||||||
|
{type: 'autogenerated', dirName: 'dir1'},
|
||||||
|
],
|
||||||
|
label: 'Category',
|
||||||
|
},
|
||||||
|
{type: 'link', href: 'https://facebook.com', label: 'FB'},
|
||||||
|
],
|
||||||
|
secondSidebar: [
|
||||||
|
{type: 'doc', id: 'doc3'},
|
||||||
|
{type: 'autogenerated', dirName: 'dir2'},
|
||||||
|
{type: 'link', href: 'https://instagram.com', label: 'IG'},
|
||||||
|
{type: 'autogenerated', dirName: 'dir3'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
collapsed: false,
|
||||||
|
items: [{type: 'doc', id: 'doc4'}],
|
||||||
|
label: 'Category',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedSidebar = await testProcessSidebars(unprocessedSidebars);
|
||||||
|
|
||||||
|
expect(StaticSidebarItemsGenerator).toHaveBeenCalledTimes(3);
|
||||||
|
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
|
||||||
|
item: {type: 'autogenerated', dirName: 'dir1'},
|
||||||
|
docs: [],
|
||||||
|
version: {},
|
||||||
|
});
|
||||||
|
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
|
||||||
|
item: {type: 'autogenerated', dirName: 'dir2'},
|
||||||
|
docs: [],
|
||||||
|
version: {},
|
||||||
|
});
|
||||||
|
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
|
||||||
|
item: {type: 'autogenerated', dirName: 'dir3'},
|
||||||
|
docs: [],
|
||||||
|
version: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(processedSidebar).toEqual({
|
||||||
|
someSidebar: [
|
||||||
|
{type: 'doc', id: 'doc1'},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
collapsed: false,
|
||||||
|
items: [{type: 'doc', id: 'doc2'}, ...StaticGeneratedSidebarSlice],
|
||||||
|
label: 'Category',
|
||||||
|
},
|
||||||
|
{type: 'link', href: 'https://facebook.com', label: 'FB'},
|
||||||
|
],
|
||||||
|
secondSidebar: [
|
||||||
|
{type: 'doc', id: 'doc3'},
|
||||||
|
...StaticGeneratedSidebarSlice,
|
||||||
|
{type: 'link', href: 'https://instagram.com', label: 'IG'},
|
||||||
|
...StaticGeneratedSidebarSlice,
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
collapsed: false,
|
||||||
|
items: [{type: 'doc', id: 'doc4'}],
|
||||||
|
label: 'Category',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as Sidebars);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('createSidebarsUtils', () => {
|
describe('createSidebarsUtils', () => {
|
||||||
const sidebar1: Sidebar = [
|
const sidebar1: Sidebar = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,6 +15,23 @@ describe('getSlug', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can strip dir number prefixes', () => {
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'doc',
|
||||||
|
dirName: '/001-dir1/002-dir2',
|
||||||
|
stripDirNumberPrefixes: true,
|
||||||
|
}),
|
||||||
|
).toEqual('/dir1/dir2/doc');
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'doc',
|
||||||
|
dirName: '/001-dir1/002-dir2',
|
||||||
|
stripDirNumberPrefixes: false,
|
||||||
|
}),
|
||||||
|
).toEqual('/001-dir1/002-dir2/doc');
|
||||||
|
});
|
||||||
|
|
||||||
// See https://github.com/facebook/docusaurus/issues/3223
|
// See https://github.com/facebook/docusaurus/issues/3223
|
||||||
test('should handle special chars in doc path', () => {
|
test('should handle special chars in doc path', () => {
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -12,7 +12,11 @@ import {
|
||||||
} from './versions';
|
} from './versions';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {Sidebars, PathOptions, SidebarItem} from './types';
|
import {
|
||||||
|
PathOptions,
|
||||||
|
UnprocessedSidebarItem,
|
||||||
|
UnprocessedSidebars,
|
||||||
|
} from './types';
|
||||||
import {loadSidebars} from './sidebars';
|
import {loadSidebars} from './sidebars';
|
||||||
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
||||||
|
|
||||||
|
@ -90,10 +94,14 @@ export function cliDocsVersionCommand(
|
||||||
|
|
||||||
// Load current sidebar and create a new versioned sidebars file.
|
// Load current sidebar and create a new versioned sidebars file.
|
||||||
if (fs.existsSync(sidebarPath)) {
|
if (fs.existsSync(sidebarPath)) {
|
||||||
const loadedSidebars: Sidebars = loadSidebars(sidebarPath);
|
const loadedSidebars = loadSidebars(sidebarPath);
|
||||||
|
|
||||||
|
// TODO @slorber: this "version prefix" in versioned sidebars looks like a bad idea to me
|
||||||
|
// TODO try to get rid of it
|
||||||
// Transform id in original sidebar to versioned id.
|
// Transform id in original sidebar to versioned id.
|
||||||
const normalizeItem = (item: SidebarItem): SidebarItem => {
|
const normalizeItem = (
|
||||||
|
item: UnprocessedSidebarItem,
|
||||||
|
): UnprocessedSidebarItem => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'category':
|
case 'category':
|
||||||
return {...item, items: item.items.map(normalizeItem)};
|
return {...item, items: item.items.map(normalizeItem)};
|
||||||
|
@ -108,14 +116,13 @@ export function cliDocsVersionCommand(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const versionedSidebar: Sidebars = Object.entries(loadedSidebars).reduce(
|
const versionedSidebar: UnprocessedSidebars = Object.entries(
|
||||||
(acc: Sidebars, [sidebarId, sidebarItems]) => {
|
loadedSidebars,
|
||||||
const newVersionedSidebarId = `version-${version}/${sidebarId}`;
|
).reduce((acc: UnprocessedSidebars, [sidebarId, sidebarItems]) => {
|
||||||
acc[newVersionedSidebarId] = sidebarItems.map(normalizeItem);
|
const newVersionedSidebarId = `version-${version}/${sidebarId}`;
|
||||||
return acc;
|
acc[newVersionedSidebarId] = sidebarItems.map(normalizeItem);
|
||||||
},
|
return acc;
|
||||||
{},
|
}, {});
|
||||||
);
|
|
||||||
|
|
||||||
const versionedSidebarsDir = getVersionedSidebarsDirPath(siteDir, pluginId);
|
const versionedSidebarsDir = getVersionedSidebarsDirPath(siteDir, pluginId);
|
||||||
const newSidebarFile = path.join(
|
const newSidebarFile = path.join(
|
||||||
|
|
|
@ -14,7 +14,9 @@ type DocFrontMatter = {
|
||||||
description?: string;
|
description?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
sidebar_label?: string;
|
sidebar_label?: string;
|
||||||
|
sidebar_position?: number;
|
||||||
custom_edit_url?: string;
|
custom_edit_url?: string;
|
||||||
|
strip_number_prefixes?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
||||||
|
@ -23,7 +25,9 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
||||||
description: Joi.string(),
|
description: Joi.string(),
|
||||||
slug: Joi.string(),
|
slug: Joi.string(),
|
||||||
sidebar_label: Joi.string(),
|
sidebar_label: Joi.string(),
|
||||||
|
sidebar_position: Joi.number(),
|
||||||
custom_edit_url: Joi.string().allow(null),
|
custom_edit_url: Joi.string().allow(null),
|
||||||
|
strip_number_prefixes: Joi.boolean(),
|
||||||
}).unknown();
|
}).unknown();
|
||||||
|
|
||||||
export function assertDocFrontMatter(
|
export function assertDocFrontMatter(
|
||||||
|
|
|
@ -30,6 +30,7 @@ import getSlug from './slug';
|
||||||
import {CURRENT_VERSION_NAME} from './constants';
|
import {CURRENT_VERSION_NAME} from './constants';
|
||||||
import globby from 'globby';
|
import globby from 'globby';
|
||||||
import {getDocsDirPaths} from './versions';
|
import {getDocsDirPaths} from './versions';
|
||||||
|
import {extractNumberPrefix, stripPathNumberPrefixes} from './numberPrefix';
|
||||||
import {assertDocFrontMatter} from './docFrontMatter';
|
import {assertDocFrontMatter} from './docFrontMatter';
|
||||||
|
|
||||||
type LastUpdateOptions = Pick<
|
type LastUpdateOptions = Pick<
|
||||||
|
@ -121,37 +122,66 @@ export function processDocMetadata({
|
||||||
});
|
});
|
||||||
assertDocFrontMatter(frontMatter);
|
assertDocFrontMatter(frontMatter);
|
||||||
|
|
||||||
// ex: api/myDoc -> api
|
|
||||||
// ex: myDoc -> .
|
|
||||||
const docsFileDirName = path.dirname(source);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sidebar_label: sidebarLabel,
|
sidebar_label: sidebarLabel,
|
||||||
custom_edit_url: customEditURL,
|
custom_edit_url: customEditURL,
|
||||||
|
|
||||||
|
// Strip number prefixes by default (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) by default,
|
||||||
|
// but ability to disable this behavior with frontmatterr
|
||||||
|
strip_number_prefixes: stripNumberPrefixes = true,
|
||||||
} = frontMatter;
|
} = frontMatter;
|
||||||
|
|
||||||
const baseID: string =
|
// ex: api/plugins/myDoc -> myDoc
|
||||||
frontMatter.id || path.basename(source, path.extname(source));
|
// ex: myDoc -> myDoc
|
||||||
|
const sourceFileNameWithoutExtension = path.basename(
|
||||||
|
source,
|
||||||
|
path.extname(source),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ex: api/plugins/myDoc -> api/plugins
|
||||||
|
// ex: myDoc -> .
|
||||||
|
const sourceDirName = path.dirname(source);
|
||||||
|
|
||||||
|
const {filename: unprefixedFileName, numberPrefix} = stripNumberPrefixes
|
||||||
|
? extractNumberPrefix(sourceFileNameWithoutExtension)
|
||||||
|
: {filename: sourceFileNameWithoutExtension, numberPrefix: undefined};
|
||||||
|
|
||||||
|
const baseID: string = frontMatter.id ?? unprefixedFileName;
|
||||||
if (baseID.includes('/')) {
|
if (baseID.includes('/')) {
|
||||||
throw new Error(`Document id [${baseID}] cannot include "/".`);
|
throw new Error(`Document id [${baseID}] cannot include "/".`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For autogenerated sidebars, sidebar position can come from filename number prefix or frontmatter
|
||||||
|
const sidebarPosition: number | undefined =
|
||||||
|
frontMatter.sidebar_position ?? numberPrefix;
|
||||||
|
|
||||||
// TODO legacy retrocompatibility
|
// TODO legacy retrocompatibility
|
||||||
// The same doc in 2 distinct version could keep the same id,
|
// The same doc in 2 distinct version could keep the same id,
|
||||||
// we just need to namespace the data by version
|
// we just need to namespace the data by version
|
||||||
const versionIdPart =
|
const versionIdPrefix =
|
||||||
versionMetadata.versionName === CURRENT_VERSION_NAME
|
versionMetadata.versionName === CURRENT_VERSION_NAME
|
||||||
? ''
|
? undefined
|
||||||
: `version-${versionMetadata.versionName}/`;
|
: `version-${versionMetadata.versionName}`;
|
||||||
|
|
||||||
// TODO legacy retrocompatibility
|
// TODO legacy retrocompatibility
|
||||||
// I think it's bad to affect the frontmatter id with the dirname
|
// I think it's bad to affect the frontmatter id with the dirname?
|
||||||
const dirNameIdPart = docsFileDirName === '.' ? '' : `${docsFileDirName}/`;
|
function computeDirNameIdPrefix() {
|
||||||
|
if (sourceDirName === '.') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// Eventually remove the number prefixes from intermediate directories
|
||||||
|
return stripNumberPrefixes
|
||||||
|
? stripPathNumberPrefixes(sourceDirName)
|
||||||
|
: sourceDirName;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO legacy composite id, requires a breaking change to modify this
|
const unversionedId = [computeDirNameIdPrefix(), baseID]
|
||||||
const id = `${versionIdPart}${dirNameIdPart}${baseID}`;
|
.filter(Boolean)
|
||||||
|
.join('/');
|
||||||
|
|
||||||
const unversionedId = `${dirNameIdPart}${baseID}`;
|
// TODO is versioning the id very useful in practice?
|
||||||
|
// legacy versioned id, requires a breaking change to modify this
|
||||||
|
const id = [versionIdPrefix, unversionedId].filter(Boolean).join('/');
|
||||||
|
|
||||||
// TODO remove soon, deprecated homePageId
|
// TODO remove soon, deprecated homePageId
|
||||||
const isDocsHomePage = unversionedId === (homePageId ?? '_index');
|
const isDocsHomePage = unversionedId === (homePageId ?? '_index');
|
||||||
|
@ -165,8 +195,9 @@ export function processDocMetadata({
|
||||||
? '/'
|
? '/'
|
||||||
: getSlug({
|
: getSlug({
|
||||||
baseID,
|
baseID,
|
||||||
dirName: docsFileDirName,
|
dirName: sourceDirName,
|
||||||
frontmatterSlug: frontMatter.slug,
|
frontmatterSlug: frontMatter.slug,
|
||||||
|
stripDirNumberPrefixes: stripNumberPrefixes,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default title is the id.
|
// Default title is the id.
|
||||||
|
@ -212,6 +243,7 @@ export function processDocMetadata({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
source: aliasedSitePath(filePath, siteDir),
|
source: aliasedSitePath(filePath, siteDir),
|
||||||
|
sourceDirName,
|
||||||
slug: docSlug,
|
slug: docSlug,
|
||||||
permalink,
|
permalink,
|
||||||
editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
|
editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
|
||||||
|
@ -224,6 +256,7 @@ export function processDocMetadata({
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
sidebar_label: sidebarLabel,
|
sidebar_label: sidebarLabel,
|
||||||
|
sidebarPosition,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,7 @@ import {
|
||||||
addTrailingPathSeparator,
|
addTrailingPathSeparator,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {LoadContext, Plugin, RouteConfig} from '@docusaurus/types';
|
import {LoadContext, Plugin, RouteConfig} from '@docusaurus/types';
|
||||||
|
import {loadSidebars, createSidebarsUtils, processSidebars} from './sidebars';
|
||||||
import {loadSidebars, createSidebarsUtils} from './sidebars';
|
|
||||||
import {readVersionDocs, processDocMetadata} from './docs';
|
import {readVersionDocs, processDocMetadata} from './docs';
|
||||||
import {getDocsDirPaths, readVersionsMetadata} from './versions';
|
import {getDocsDirPaths, readVersionsMetadata} from './versions';
|
||||||
|
|
||||||
|
@ -49,6 +48,7 @@ import {
|
||||||
translateLoadedContent,
|
translateLoadedContent,
|
||||||
getLoadedContentTranslationFiles,
|
getLoadedContentTranslationFiles,
|
||||||
} from './translations';
|
} from './translations';
|
||||||
|
import {CategoryMetadataFilenamePattern} from './sidebarItemsGenerator';
|
||||||
|
|
||||||
export default function pluginContentDocs(
|
export default function pluginContentDocs(
|
||||||
context: LoadContext,
|
context: LoadContext,
|
||||||
|
@ -127,6 +127,7 @@ export default function pluginContentDocs(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
`${version.contentPath}/**/${CategoryMetadataFilenamePattern}`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,8 +163,9 @@ export default function pluginContentDocs(
|
||||||
async function loadVersion(
|
async function loadVersion(
|
||||||
versionMetadata: VersionMetadata,
|
versionMetadata: VersionMetadata,
|
||||||
): Promise<LoadedVersion> {
|
): Promise<LoadedVersion> {
|
||||||
const sidebars = loadSidebars(versionMetadata.sidebarFilePath);
|
const unprocessedSidebars = loadSidebars(
|
||||||
const sidebarsUtils = createSidebarsUtils(sidebars);
|
versionMetadata.sidebarFilePath,
|
||||||
|
);
|
||||||
|
|
||||||
const docsBase: DocMetadataBase[] = await loadVersionDocsBase(
|
const docsBase: DocMetadataBase[] = await loadVersionDocsBase(
|
||||||
versionMetadata,
|
versionMetadata,
|
||||||
|
@ -173,6 +175,15 @@ export default function pluginContentDocs(
|
||||||
(doc) => doc.id,
|
(doc) => doc.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sidebars = await processSidebars({
|
||||||
|
sidebarItemsGenerator: options.sidebarItemsGenerator,
|
||||||
|
unprocessedSidebars,
|
||||||
|
docs: docsBase,
|
||||||
|
version: versionMetadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sidebarsUtils = createSidebarsUtils(sidebars);
|
||||||
|
|
||||||
const validDocIds = Object.keys(docsBaseById);
|
const validDocIds = Object.keys(docsBaseById);
|
||||||
sidebarsUtils.checkSidebarsDocIds(validDocIds);
|
sidebarsUtils.checkSidebarsDocIds(validDocIds);
|
||||||
|
|
||||||
|
|
35
packages/docusaurus-plugin-content-docs/src/numberPrefix.ts
Normal file
35
packages/docusaurus-plugin-content-docs/src/numberPrefix.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NumberPrefixRegex = /^(?<numberPrefix>\d+)(?<separator>\s*[-_.]+\s*)(?<suffix>.*)$/;
|
||||||
|
|
||||||
|
// 0-myDoc => myDoc
|
||||||
|
export function stripNumberPrefix(str: string) {
|
||||||
|
return NumberPrefixRegex.exec(str)?.groups?.suffix ?? str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-myFolder/0-mySubfolder/0-myDoc => myFolder/mySubfolder/myDoc
|
||||||
|
export function stripPathNumberPrefixes(path: string) {
|
||||||
|
return path.split('/').map(stripNumberPrefix).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-myDoc => {filename: myDoc, numberPrefix: 0}
|
||||||
|
// 003 - myDoc => {filename: myDoc, numberPrefix: 3}
|
||||||
|
export function extractNumberPrefix(
|
||||||
|
filename: string,
|
||||||
|
): {filename: string; numberPrefix?: number} {
|
||||||
|
const match = NumberPrefixRegex.exec(filename);
|
||||||
|
const cleanFileName = match?.groups?.suffix ?? filename;
|
||||||
|
const numberPrefixString = match?.groups?.numberPrefix;
|
||||||
|
const numberPrefix = numberPrefixString
|
||||||
|
? parseInt(numberPrefixString, 10)
|
||||||
|
: undefined;
|
||||||
|
return {
|
||||||
|
filename: cleanFileName,
|
||||||
|
numberPrefix,
|
||||||
|
};
|
||||||
|
}
|
|
@ -15,13 +15,15 @@ import {
|
||||||
import {OptionValidationContext, ValidationResult} from '@docusaurus/types';
|
import {OptionValidationContext, ValidationResult} from '@docusaurus/types';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import admonitions from 'remark-admonitions';
|
import admonitions from 'remark-admonitions';
|
||||||
|
import {DefaultSidebarItemsGenerator} from './sidebarItemsGenerator';
|
||||||
|
|
||||||
export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
||||||
path: 'docs', // Path to data on filesystem, relative to site dir.
|
path: 'docs', // Path to data on filesystem, relative to site dir.
|
||||||
routeBasePath: 'docs', // URL Route.
|
routeBasePath: 'docs', // URL Route.
|
||||||
homePageId: undefined, // TODO remove soon, deprecated
|
homePageId: undefined, // TODO remove soon, deprecated
|
||||||
include: ['**/*.{md,mdx}'], // Extensions to include.
|
include: ['**/*.{md,mdx}'], // Extensions to include.
|
||||||
sidebarPath: 'sidebars.json', // Path to sidebar configuration for showing a list of markdown pages.
|
sidebarPath: 'sidebars.json', // Path to the sidebars configuration file
|
||||||
|
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
||||||
docLayoutComponent: '@theme/DocPage',
|
docLayoutComponent: '@theme/DocPage',
|
||||||
docItemComponent: '@theme/DocItem',
|
docItemComponent: '@theme/DocItem',
|
||||||
remarkPlugins: [],
|
remarkPlugins: [],
|
||||||
|
@ -61,6 +63,9 @@ export const OptionsSchema = Joi.object({
|
||||||
homePageId: Joi.string().optional(),
|
homePageId: Joi.string().optional(),
|
||||||
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
|
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
|
||||||
sidebarPath: Joi.string().allow('').default(DEFAULT_OPTIONS.sidebarPath),
|
sidebarPath: Joi.string().allow('').default(DEFAULT_OPTIONS.sidebarPath),
|
||||||
|
sidebarItemsGenerator: Joi.function().default(
|
||||||
|
() => DEFAULT_OPTIONS.sidebarItemsGenerator,
|
||||||
|
),
|
||||||
docLayoutComponent: Joi.string().default(DEFAULT_OPTIONS.docLayoutComponent),
|
docLayoutComponent: Joi.string().default(DEFAULT_OPTIONS.docLayoutComponent),
|
||||||
docItemComponent: Joi.string().default(DEFAULT_OPTIONS.docItemComponent),
|
docItemComponent: Joi.string().default(DEFAULT_OPTIONS.docItemComponent),
|
||||||
remarkPlugins: RemarkPluginsSchema.default(DEFAULT_OPTIONS.remarkPlugins),
|
remarkPlugins: RemarkPluginsSchema.default(DEFAULT_OPTIONS.remarkPlugins),
|
||||||
|
|
|
@ -0,0 +1,305 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
SidebarItem,
|
||||||
|
SidebarItemDoc,
|
||||||
|
SidebarItemCategory,
|
||||||
|
SidebarItemsGenerator,
|
||||||
|
SidebarItemsGeneratorDoc,
|
||||||
|
} from './types';
|
||||||
|
import {sortBy, take, last, orderBy} from 'lodash';
|
||||||
|
import {addTrailingSlash, posixPath} from '@docusaurus/utils';
|
||||||
|
import {Joi} from '@docusaurus/utils-validation';
|
||||||
|
import {extractNumberPrefix} from './numberPrefix';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import Yaml from 'js-yaml';
|
||||||
|
import {DefaultCategoryCollapsedValue} from './sidebars';
|
||||||
|
|
||||||
|
const BreadcrumbSeparator = '/';
|
||||||
|
|
||||||
|
export const CategoryMetadataFilenameBase = '_category_';
|
||||||
|
export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
|
||||||
|
|
||||||
|
export type CategoryMetadatasFile = {
|
||||||
|
label?: string;
|
||||||
|
position?: number;
|
||||||
|
collapsed?: boolean;
|
||||||
|
|
||||||
|
// TODO should we allow "items" here? how would this work? would an "autogenerated" type be allowed?
|
||||||
|
// This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/
|
||||||
|
// cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199
|
||||||
|
};
|
||||||
|
|
||||||
|
type WithPosition = {position?: number};
|
||||||
|
type SidebarItemWithPosition = SidebarItem & WithPosition;
|
||||||
|
|
||||||
|
const CategoryMetadatasFileSchema = Joi.object<CategoryMetadatasFile>({
|
||||||
|
label: Joi.string().optional(),
|
||||||
|
position: Joi.number().optional(),
|
||||||
|
collapsed: Joi.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO later if there is `CategoryFolder/index.md`, we may want to read the metadata as yaml on it
|
||||||
|
// see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
|
||||||
|
async function readCategoryMetadatasFile(
|
||||||
|
categoryDirPath: string,
|
||||||
|
): Promise<CategoryMetadatasFile | null> {
|
||||||
|
function assertCategoryMetadataFile(
|
||||||
|
content: unknown,
|
||||||
|
): asserts content is CategoryMetadatasFile {
|
||||||
|
Joi.attempt(content, CategoryMetadatasFileSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tryReadFile(
|
||||||
|
fileNameWithExtension: string,
|
||||||
|
parse: (content: string) => unknown,
|
||||||
|
): Promise<CategoryMetadatasFile | null> {
|
||||||
|
// Simpler to use only posix paths for mocking file metadatas in tests
|
||||||
|
const filePath = posixPath(
|
||||||
|
path.join(categoryDirPath, fileNameWithExtension),
|
||||||
|
);
|
||||||
|
if (await fs.pathExists(filePath)) {
|
||||||
|
const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
|
||||||
|
const unsafeContent: unknown = parse(contentString);
|
||||||
|
try {
|
||||||
|
assertCategoryMetadataFile(unsafeContent);
|
||||||
|
return unsafeContent;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
`The docs sidebar category metadata file looks invalid!\nPath=${filePath}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(await tryReadFile(`${CategoryMetadataFilenameBase}.json`, JSON.parse)) ??
|
||||||
|
(await tryReadFile(`${CategoryMetadataFilenameBase}.yml`, Yaml.load)) ??
|
||||||
|
// eslint-disable-next-line no-return-await
|
||||||
|
(await tryReadFile(`${CategoryMetadataFilenameBase}.yaml`, Yaml.load))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [...parents, tail]
|
||||||
|
function parseBreadcrumb(
|
||||||
|
breadcrumb: string[],
|
||||||
|
): {parents: string[]; tail: string} {
|
||||||
|
return {
|
||||||
|
parents: take(breadcrumb, breadcrumb.length - 1),
|
||||||
|
tail: last(breadcrumb)!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
|
||||||
|
export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async function defaultSidebarItemsGenerator({
|
||||||
|
item,
|
||||||
|
docs: allDocs,
|
||||||
|
version,
|
||||||
|
}): Promise<SidebarItem[]> {
|
||||||
|
// Doc at the root of the autogenerated sidebar dir
|
||||||
|
function isRootDoc(doc: SidebarItemsGeneratorDoc) {
|
||||||
|
return doc.sourceDirName === item.dirName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doc inside a subfolder of the autogenerated sidebar dir
|
||||||
|
function isCategoryDoc(doc: SidebarItemsGeneratorDoc) {
|
||||||
|
if (isRootDoc(doc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
// autogen dir is . and doc is in subfolder
|
||||||
|
item.dirName === '.' ||
|
||||||
|
// autogen dir is not . and doc is in subfolder
|
||||||
|
// "api/myDoc" startsWith "api/" (note "api2/myDoc" is not included)
|
||||||
|
doc.sourceDirName.startsWith(addTrailingSlash(item.dirName))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInAutogeneratedDir(doc: SidebarItemsGeneratorDoc) {
|
||||||
|
return isRootDoc(doc) || isCategoryDoc(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// autogenDir=a/b and docDir=a/b/c/d => returns c/d
|
||||||
|
// autogenDir=a/b and docDir=a/b => returns .
|
||||||
|
function getDocDirRelativeToAutogenDir(
|
||||||
|
doc: SidebarItemsGeneratorDoc,
|
||||||
|
): string {
|
||||||
|
if (!isInAutogeneratedDir(doc)) {
|
||||||
|
throw new Error(
|
||||||
|
'getDocDirRelativeToAutogenDir() can only be called for subdocs of the sidebar autogen dir',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Is there a node API to compare 2 relative paths more easily?
|
||||||
|
// path.relative() does not give good results
|
||||||
|
if (item.dirName === '.') {
|
||||||
|
return doc.sourceDirName;
|
||||||
|
} else if (item.dirName === doc.sourceDirName) {
|
||||||
|
return '.';
|
||||||
|
} else {
|
||||||
|
return doc.sourceDirName.replace(addTrailingSlash(item.dirName), '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get only docs in the autogen dir
|
||||||
|
// Sort by folder+filename at once
|
||||||
|
const docs = sortBy(allDocs.filter(isInAutogeneratedDir), (d) => d.source);
|
||||||
|
|
||||||
|
if (docs.length === 0) {
|
||||||
|
console.warn(
|
||||||
|
chalk.yellow(
|
||||||
|
`No docs found in dir ${item.dirName}: can't auto-generate a sidebar`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDocSidebarItem(
|
||||||
|
doc: SidebarItemsGeneratorDoc,
|
||||||
|
): SidebarItemDoc & WithPosition {
|
||||||
|
return {
|
||||||
|
type: 'doc',
|
||||||
|
id: doc.id,
|
||||||
|
...(doc.frontMatter.sidebar_label && {
|
||||||
|
label: doc.frontMatter.sidebar_label,
|
||||||
|
}),
|
||||||
|
...(typeof doc.sidebarPosition !== 'undefined' && {
|
||||||
|
position: doc.sidebarPosition,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCategorySidebarItem({
|
||||||
|
breadcrumb,
|
||||||
|
}: {
|
||||||
|
breadcrumb: string[];
|
||||||
|
}): Promise<SidebarItemCategory & WithPosition> {
|
||||||
|
const categoryDirPath = path.join(
|
||||||
|
version.contentPath,
|
||||||
|
breadcrumb.join(BreadcrumbSeparator),
|
||||||
|
);
|
||||||
|
|
||||||
|
const categoryMetadatas = await readCategoryMetadatasFile(categoryDirPath);
|
||||||
|
|
||||||
|
const {tail} = parseBreadcrumb(breadcrumb);
|
||||||
|
|
||||||
|
const {filename, numberPrefix} = extractNumberPrefix(tail);
|
||||||
|
|
||||||
|
const position = categoryMetadatas?.position ?? numberPrefix;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'category',
|
||||||
|
label: categoryMetadatas?.label ?? filename,
|
||||||
|
items: [],
|
||||||
|
collapsed: categoryMetadatas?.collapsed ?? DefaultCategoryCollapsedValue,
|
||||||
|
...(typeof position !== 'undefined' && {position}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not sure how to simplify this algorithm :/
|
||||||
|
async function autogenerateSidebarItems(): Promise<
|
||||||
|
SidebarItemWithPosition[]
|
||||||
|
> {
|
||||||
|
const sidebarItems: SidebarItem[] = []; // mutable result
|
||||||
|
|
||||||
|
const categoriesByBreadcrumb: Record<string, SidebarItemCategory> = {}; // mutable cache of categories already created
|
||||||
|
|
||||||
|
async function getOrCreateCategoriesForBreadcrumb(
|
||||||
|
breadcrumb: string[],
|
||||||
|
): Promise<SidebarItemCategory | null> {
|
||||||
|
if (breadcrumb.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const {parents} = parseBreadcrumb(breadcrumb);
|
||||||
|
const parentCategory = await getOrCreateCategoriesForBreadcrumb(parents);
|
||||||
|
const existingCategory =
|
||||||
|
categoriesByBreadcrumb[breadcrumb.join(BreadcrumbSeparator)];
|
||||||
|
|
||||||
|
if (existingCategory) {
|
||||||
|
return existingCategory;
|
||||||
|
} else {
|
||||||
|
const newCategory = await createCategorySidebarItem({
|
||||||
|
breadcrumb,
|
||||||
|
});
|
||||||
|
if (parentCategory) {
|
||||||
|
parentCategory.items.push(newCategory);
|
||||||
|
} else {
|
||||||
|
sidebarItems.push(newCategory);
|
||||||
|
}
|
||||||
|
categoriesByBreadcrumb[
|
||||||
|
breadcrumb.join(BreadcrumbSeparator)
|
||||||
|
] = newCategory;
|
||||||
|
return newCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the category breadcrumb of a doc (relative to the dir of the autogenerated sidebar item)
|
||||||
|
function getRelativeBreadcrumb(doc: SidebarItemsGeneratorDoc): string[] {
|
||||||
|
const relativeDirPath = getDocDirRelativeToAutogenDir(doc);
|
||||||
|
if (relativeDirPath === '.') {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return relativeDirPath.split(BreadcrumbSeparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDocItem(doc: SidebarItemsGeneratorDoc): Promise<void> {
|
||||||
|
const breadcrumb = getRelativeBreadcrumb(doc);
|
||||||
|
const category = await getOrCreateCategoriesForBreadcrumb(breadcrumb);
|
||||||
|
|
||||||
|
const docSidebarItem = createDocSidebarItem(doc);
|
||||||
|
if (category) {
|
||||||
|
category.items.push(docSidebarItem);
|
||||||
|
} else {
|
||||||
|
sidebarItems.push(docSidebarItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// async process made sequential on purpose! order matters
|
||||||
|
for (const doc of docs) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await handleDocItem(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sidebarItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarItems = await autogenerateSidebarItems();
|
||||||
|
|
||||||
|
return sortSidebarItems(sidebarItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recursively sort the categories/docs + remove the "position" attribute from final output
|
||||||
|
// Note: the "position" is only used to sort "inside" a sidebar slice
|
||||||
|
// It is not used to sort across multiple consecutive sidebar slices (ie a whole Category composed of multiple autogenerated items)
|
||||||
|
function sortSidebarItems(
|
||||||
|
sidebarItems: SidebarItemWithPosition[],
|
||||||
|
): SidebarItem[] {
|
||||||
|
const processedSidebarItems = sidebarItems.map((item) => {
|
||||||
|
if (item.type === 'category') {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
items: sortSidebarItems(item.items),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedSidebarItems = orderBy(
|
||||||
|
processedSidebarItems,
|
||||||
|
(item) => item.position,
|
||||||
|
['asc'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return sortedSidebarItems.map(({position: _removed, ...item}) => item);
|
||||||
|
}
|
|
@ -16,9 +16,18 @@ import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarItemCategory,
|
SidebarItemCategory,
|
||||||
SidebarItemType,
|
SidebarItemType,
|
||||||
|
UnprocessedSidebarItem,
|
||||||
|
UnprocessedSidebars,
|
||||||
|
UnprocessedSidebar,
|
||||||
|
DocMetadataBase,
|
||||||
|
VersionMetadata,
|
||||||
|
SidebarItemsGenerator,
|
||||||
|
SidebarItemsGeneratorDoc,
|
||||||
|
SidebarItemsGeneratorVersion,
|
||||||
} from './types';
|
} from './types';
|
||||||
import {mapValues, flatten, flatMap, difference} from 'lodash';
|
import {mapValues, flatten, flatMap, difference, pick, memoize} from 'lodash';
|
||||||
import {getElementsAround} from '@docusaurus/utils';
|
import {getElementsAround} from '@docusaurus/utils';
|
||||||
|
import combinePromises from 'combine-promises';
|
||||||
|
|
||||||
type SidebarItemCategoryJSON = SidebarItemBase & {
|
type SidebarItemCategoryJSON = SidebarItemBase & {
|
||||||
type: 'category';
|
type: 'category';
|
||||||
|
@ -27,12 +36,18 @@ type SidebarItemCategoryJSON = SidebarItemBase & {
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SidebarItemAutogeneratedJSON = SidebarItemBase & {
|
||||||
|
type: 'autogenerated';
|
||||||
|
dirName: string;
|
||||||
|
};
|
||||||
|
|
||||||
type SidebarItemJSON =
|
type SidebarItemJSON =
|
||||||
| string
|
| string
|
||||||
| SidebarCategoryShorthandJSON
|
| SidebarCategoryShorthandJSON
|
||||||
| SidebarItemDoc
|
| SidebarItemDoc
|
||||||
| SidebarItemLink
|
| SidebarItemLink
|
||||||
| SidebarItemCategoryJSON
|
| SidebarItemCategoryJSON
|
||||||
|
| SidebarItemAutogeneratedJSON
|
||||||
| {
|
| {
|
||||||
type: string;
|
type: string;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
@ -56,7 +71,7 @@ function isCategoryShorthand(
|
||||||
}
|
}
|
||||||
|
|
||||||
// categories are collapsed by default, unless user set collapsed = false
|
// categories are collapsed by default, unless user set collapsed = false
|
||||||
const defaultCategoryCollapsedValue = true;
|
export const DefaultCategoryCollapsedValue = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert {category1: [item1,item2]} shorthand syntax to long-form syntax
|
* Convert {category1: [item1,item2]} shorthand syntax to long-form syntax
|
||||||
|
@ -66,7 +81,7 @@ function normalizeCategoryShorthand(
|
||||||
): SidebarItemCategoryJSON[] {
|
): SidebarItemCategoryJSON[] {
|
||||||
return Object.entries(sidebar).map(([label, items]) => ({
|
return Object.entries(sidebar).map(([label, items]) => ({
|
||||||
type: 'category',
|
type: 'category',
|
||||||
collapsed: defaultCategoryCollapsedValue,
|
collapsed: DefaultCategoryCollapsedValue,
|
||||||
label,
|
label,
|
||||||
items,
|
items,
|
||||||
}));
|
}));
|
||||||
|
@ -78,7 +93,7 @@ function normalizeCategoryShorthand(
|
||||||
function assertItem<K extends string>(
|
function assertItem<K extends string>(
|
||||||
item: Record<string, unknown>,
|
item: Record<string, unknown>,
|
||||||
keys: K[],
|
keys: K[],
|
||||||
): asserts item is Record<K, never> {
|
): asserts item is Record<K, unknown> {
|
||||||
const unknownKeys = Object.keys(item).filter(
|
const unknownKeys = Object.keys(item).filter(
|
||||||
// @ts-expect-error: key is always string
|
// @ts-expect-error: key is always string
|
||||||
(key) => !keys.includes(key as string) && key !== 'type',
|
(key) => !keys.includes(key as string) && key !== 'type',
|
||||||
|
@ -115,6 +130,24 @@ function assertIsCategory(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertIsAutogenerated(
|
||||||
|
item: Record<string, unknown>,
|
||||||
|
): asserts item is SidebarItemAutogeneratedJSON {
|
||||||
|
assertItem(item, ['dirName', 'customProps']);
|
||||||
|
if (typeof item.dirName !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`Error loading ${JSON.stringify(item)}. "dirName" must be a string.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (item.dirName.startsWith('/') || item.dirName.endsWith('/')) {
|
||||||
|
throw new Error(
|
||||||
|
`Error loading ${JSON.stringify(
|
||||||
|
item,
|
||||||
|
)}. "dirName" must be a dir path relative to the docs folder root, and should not start or end with /`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function assertIsDoc(
|
function assertIsDoc(
|
||||||
item: Record<string, unknown>,
|
item: Record<string, unknown>,
|
||||||
): asserts item is SidebarItemDoc {
|
): asserts item is SidebarItemDoc {
|
||||||
|
@ -152,7 +185,7 @@ function assertIsLink(
|
||||||
* Normalizes recursively item and all its children. Ensures that at the end
|
* Normalizes recursively item and all its children. Ensures that at the end
|
||||||
* each item will be an object with the corresponding type.
|
* each item will be an object with the corresponding type.
|
||||||
*/
|
*/
|
||||||
function normalizeItem(item: SidebarItemJSON): SidebarItem[] {
|
function normalizeItem(item: SidebarItemJSON): UnprocessedSidebarItem[] {
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -169,11 +202,14 @@ function normalizeItem(item: SidebarItemJSON): SidebarItem[] {
|
||||||
assertIsCategory(item);
|
assertIsCategory(item);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
collapsed: defaultCategoryCollapsedValue,
|
collapsed: DefaultCategoryCollapsedValue,
|
||||||
...item,
|
...item,
|
||||||
items: flatMap(item.items, normalizeItem),
|
items: flatMap(item.items, normalizeItem),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
case 'autogenerated':
|
||||||
|
assertIsAutogenerated(item);
|
||||||
|
return [item];
|
||||||
case 'link':
|
case 'link':
|
||||||
assertIsLink(item);
|
assertIsLink(item);
|
||||||
return [item];
|
return [item];
|
||||||
|
@ -195,7 +231,7 @@ function normalizeItem(item: SidebarItemJSON): SidebarItem[] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSidebar(sidebar: SidebarJSON) {
|
function normalizeSidebar(sidebar: SidebarJSON): UnprocessedSidebar {
|
||||||
const normalizedSidebar: SidebarItemJSON[] = Array.isArray(sidebar)
|
const normalizedSidebar: SidebarItemJSON[] = Array.isArray(sidebar)
|
||||||
? sidebar
|
? sidebar
|
||||||
: normalizeCategoryShorthand(sidebar);
|
: normalizeCategoryShorthand(sidebar);
|
||||||
|
@ -203,21 +239,29 @@ function normalizeSidebar(sidebar: SidebarJSON) {
|
||||||
return flatMap(normalizedSidebar, normalizeItem);
|
return flatMap(normalizedSidebar, normalizeItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSidebars(sidebars: SidebarsJSON): Sidebars {
|
function normalizeSidebars(sidebars: SidebarsJSON): UnprocessedSidebars {
|
||||||
return mapValues(sidebars, normalizeSidebar);
|
return mapValues(sidebars, normalizeSidebar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DefaultSidebars: UnprocessedSidebars = {
|
||||||
|
defaultSidebar: [
|
||||||
|
{
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: '.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
// TODO refactor: make async
|
// TODO refactor: make async
|
||||||
export function loadSidebars(sidebarFilePath: string): Sidebars {
|
export function loadSidebars(sidebarFilePath: string): UnprocessedSidebars {
|
||||||
if (!sidebarFilePath) {
|
if (!sidebarFilePath) {
|
||||||
throw new Error(`sidebarFilePath not provided: ${sidebarFilePath}`);
|
throw new Error(`sidebarFilePath not provided: ${sidebarFilePath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sidebars file is optional, some users use docs without sidebars!
|
// No sidebars file: by default we use the file-system structure to generate the sidebar
|
||||||
// See https://github.com/facebook/docusaurus/issues/3366
|
// See https://github.com/facebook/docusaurus/pull/4582
|
||||||
if (!fs.existsSync(sidebarFilePath)) {
|
if (!fs.existsSync(sidebarFilePath)) {
|
||||||
// throw new Error(`No sidebar file exist at path: ${sidebarFilePath}`);
|
return DefaultSidebars;
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want sidebars to be cached because of hot reloading.
|
// We don't want sidebars to be cached because of hot reloading.
|
||||||
|
@ -225,6 +269,87 @@ export function loadSidebars(sidebarFilePath: string): Sidebars {
|
||||||
return normalizeSidebars(sidebarJson);
|
return normalizeSidebars(sidebarJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toSidebarItemsGeneratorDoc(
|
||||||
|
doc: DocMetadataBase,
|
||||||
|
): SidebarItemsGeneratorDoc {
|
||||||
|
return pick(doc, [
|
||||||
|
'id',
|
||||||
|
'frontMatter',
|
||||||
|
'source',
|
||||||
|
'sourceDirName',
|
||||||
|
'sidebarPosition',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
export function toSidebarItemsGeneratorVersion(
|
||||||
|
version: VersionMetadata,
|
||||||
|
): SidebarItemsGeneratorVersion {
|
||||||
|
return pick(version, ['versionName', 'contentPath']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the generation of autogenerated sidebar items
|
||||||
|
export async function processSidebar({
|
||||||
|
sidebarItemsGenerator,
|
||||||
|
unprocessedSidebar,
|
||||||
|
docs,
|
||||||
|
version,
|
||||||
|
}: {
|
||||||
|
sidebarItemsGenerator: SidebarItemsGenerator;
|
||||||
|
unprocessedSidebar: UnprocessedSidebar;
|
||||||
|
docs: DocMetadataBase[];
|
||||||
|
version: VersionMetadata;
|
||||||
|
}): Promise<Sidebar> {
|
||||||
|
// Just a minor lazy transformation optimization
|
||||||
|
const getSidebarItemsGeneratorDocsAndVersion = memoize(() => ({
|
||||||
|
docs: docs.map(toSidebarItemsGeneratorDoc),
|
||||||
|
version: toSidebarItemsGeneratorVersion(version),
|
||||||
|
}));
|
||||||
|
|
||||||
|
async function processRecursive(
|
||||||
|
item: UnprocessedSidebarItem,
|
||||||
|
): Promise<SidebarItem[]> {
|
||||||
|
if (item.type === 'category') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...item,
|
||||||
|
items: (await Promise.all(item.items.map(processRecursive))).flat(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (item.type === 'autogenerated') {
|
||||||
|
return sidebarItemsGenerator({
|
||||||
|
item,
|
||||||
|
...getSidebarItemsGeneratorDocsAndVersion(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [item];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await Promise.all(unprocessedSidebar.map(processRecursive))).flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function processSidebars({
|
||||||
|
sidebarItemsGenerator,
|
||||||
|
unprocessedSidebars,
|
||||||
|
docs,
|
||||||
|
version,
|
||||||
|
}: {
|
||||||
|
sidebarItemsGenerator: SidebarItemsGenerator;
|
||||||
|
unprocessedSidebars: UnprocessedSidebars;
|
||||||
|
docs: DocMetadataBase[];
|
||||||
|
version: VersionMetadata;
|
||||||
|
}): Promise<Sidebars> {
|
||||||
|
return combinePromises(
|
||||||
|
mapValues(unprocessedSidebars, (unprocessedSidebar) =>
|
||||||
|
processSidebar({
|
||||||
|
sidebarItemsGenerator,
|
||||||
|
unprocessedSidebar,
|
||||||
|
docs,
|
||||||
|
version,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function collectSidebarItemsOfType<
|
function collectSidebarItemsOfType<
|
||||||
Type extends SidebarItemType,
|
Type extends SidebarItemType,
|
||||||
Item extends SidebarItem & {type: SidebarItemType}
|
Item extends SidebarItem & {type: SidebarItemType}
|
||||||
|
|
|
@ -11,23 +11,31 @@ import {
|
||||||
isValidPathname,
|
isValidPathname,
|
||||||
resolvePathname,
|
resolvePathname,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
|
import {stripPathNumberPrefixes} from './numberPrefix';
|
||||||
|
|
||||||
export default function getSlug({
|
export default function getSlug({
|
||||||
baseID,
|
baseID,
|
||||||
frontmatterSlug,
|
frontmatterSlug,
|
||||||
dirName,
|
dirName,
|
||||||
|
stripDirNumberPrefixes = true,
|
||||||
}: {
|
}: {
|
||||||
baseID: string;
|
baseID: string;
|
||||||
frontmatterSlug?: string;
|
frontmatterSlug?: string;
|
||||||
dirName: string;
|
dirName: string;
|
||||||
|
stripDirNumberPrefixes?: boolean;
|
||||||
}): string {
|
}): string {
|
||||||
const baseSlug = frontmatterSlug || baseID;
|
const baseSlug = frontmatterSlug || baseID;
|
||||||
let slug: string;
|
let slug: string;
|
||||||
if (baseSlug.startsWith('/')) {
|
if (baseSlug.startsWith('/')) {
|
||||||
slug = baseSlug;
|
slug = baseSlug;
|
||||||
} else {
|
} else {
|
||||||
|
const dirNameStripped = stripDirNumberPrefixes
|
||||||
|
? stripPathNumberPrefixes(dirName)
|
||||||
|
: dirName;
|
||||||
const resolveDirname =
|
const resolveDirname =
|
||||||
dirName === '.' ? '/' : addLeadingSlash(addTrailingSlash(dirName));
|
dirName === '.'
|
||||||
|
? '/'
|
||||||
|
: addLeadingSlash(addTrailingSlash(dirNameStripped));
|
||||||
slug = resolvePathname(baseSlug, resolveDirname);
|
slug = resolvePathname(baseSlug, resolveDirname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@ export type PluginOptions = MetadataOptions &
|
||||||
disableVersioning: boolean;
|
disableVersioning: boolean;
|
||||||
excludeNextVersionDocs?: boolean;
|
excludeNextVersionDocs?: boolean;
|
||||||
includeCurrentVersion: boolean;
|
includeCurrentVersion: boolean;
|
||||||
|
sidebarItemsGenerator: SidebarItemsGenerator;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SidebarItemBase = {
|
export type SidebarItemBase = {
|
||||||
|
@ -108,6 +109,27 @@ export type SidebarItemCategory = SidebarItemBase & {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UnprocessedSidebarItemAutogenerated = {
|
||||||
|
type: 'autogenerated';
|
||||||
|
dirName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UnprocessedSidebarItemCategory = SidebarItemBase & {
|
||||||
|
type: 'category';
|
||||||
|
label: string;
|
||||||
|
items: UnprocessedSidebarItem[];
|
||||||
|
collapsed: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UnprocessedSidebarItem =
|
||||||
|
| SidebarItemDoc
|
||||||
|
| SidebarItemLink
|
||||||
|
| UnprocessedSidebarItemCategory
|
||||||
|
| UnprocessedSidebarItemAutogenerated;
|
||||||
|
|
||||||
|
export type UnprocessedSidebar = UnprocessedSidebarItem[];
|
||||||
|
export type UnprocessedSidebars = Record<string, UnprocessedSidebar>;
|
||||||
|
|
||||||
export type SidebarItem =
|
export type SidebarItem =
|
||||||
| SidebarItemDoc
|
| SidebarItemDoc
|
||||||
| SidebarItemLink
|
| SidebarItemLink
|
||||||
|
@ -115,9 +137,25 @@ export type SidebarItem =
|
||||||
|
|
||||||
export type Sidebar = SidebarItem[];
|
export type Sidebar = SidebarItem[];
|
||||||
export type SidebarItemType = SidebarItem['type'];
|
export type SidebarItemType = SidebarItem['type'];
|
||||||
|
|
||||||
export type Sidebars = Record<string, Sidebar>;
|
export type Sidebars = Record<string, Sidebar>;
|
||||||
|
|
||||||
|
// Reduce API surface for options.sidebarItemsGenerator
|
||||||
|
// The user-provided generator fn should receive only a subset of metadatas
|
||||||
|
// A change to any of these metadatas can be considered as a breaking change
|
||||||
|
export type SidebarItemsGeneratorDoc = Pick<
|
||||||
|
DocMetadataBase,
|
||||||
|
'id' | 'frontMatter' | 'source' | 'sourceDirName' | 'sidebarPosition'
|
||||||
|
>;
|
||||||
|
export type SidebarItemsGeneratorVersion = Pick<
|
||||||
|
VersionMetadata,
|
||||||
|
'versionName' | 'contentPath'
|
||||||
|
>;
|
||||||
|
export type SidebarItemsGenerator = (generatorArgs: {
|
||||||
|
item: UnprocessedSidebarItemAutogenerated;
|
||||||
|
version: SidebarItemsGeneratorVersion;
|
||||||
|
docs: SidebarItemsGeneratorDoc[];
|
||||||
|
}) => Promise<SidebarItem[]>;
|
||||||
|
|
||||||
export type OrderMetadata = {
|
export type OrderMetadata = {
|
||||||
previous?: string;
|
previous?: string;
|
||||||
next?: string;
|
next?: string;
|
||||||
|
@ -143,10 +181,12 @@ export type DocMetadataBase = LastUpdateData & {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
source: string;
|
source: string;
|
||||||
|
sourceDirName: string; // relative to the docs folder (can be ".")
|
||||||
slug: string;
|
slug: string;
|
||||||
permalink: string;
|
permalink: string;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
sidebar_label?: string;
|
sidebar_label?: string;
|
||||||
|
sidebarPosition?: number;
|
||||||
editUrl?: string | null;
|
editUrl?: string | null;
|
||||||
frontMatter: FrontMatter;
|
frontMatter: FrontMatter;
|
||||||
};
|
};
|
||||||
|
|
|
@ -434,6 +434,7 @@ function filterVersions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO make this async (requires plugin init to be async)
|
||||||
export function readVersionsMetadata({
|
export function readVersionsMetadata({
|
||||||
context,
|
context,
|
||||||
options,
|
options,
|
||||||
|
|
|
@ -91,6 +91,7 @@ export default function CodeBlock({
|
||||||
children,
|
children,
|
||||||
className: languageClassName,
|
className: languageClassName,
|
||||||
metastring,
|
metastring,
|
||||||
|
title,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const {prism} = useThemeConfig();
|
const {prism} = useThemeConfig();
|
||||||
|
|
||||||
|
@ -107,9 +108,13 @@ export default function CodeBlock({
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// TODO: the title is provided by MDX as props automatically
|
||||||
|
// so we probably don't need to parse the metastring
|
||||||
|
// (note: title="xyz" => title prop still has the quotes)
|
||||||
|
const codeBlockTitle = parseCodeBlockTitle(metastring) || title;
|
||||||
|
|
||||||
const button = useRef(null);
|
const button = useRef(null);
|
||||||
let highlightLines: number[] = [];
|
let highlightLines: number[] = [];
|
||||||
const codeBlockTitle = parseCodeBlockTitle(metastring);
|
|
||||||
|
|
||||||
const prismTheme = usePrismTheme();
|
const prismTheme = usePrismTheme();
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ declare module '@theme/CodeBlock' {
|
||||||
readonly children: string;
|
readonly children: string;
|
||||||
readonly className?: string;
|
readonly className?: string;
|
||||||
readonly metastring?: string;
|
readonly metastring?: string;
|
||||||
|
readonly title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CodeBlock: (props: Props) => JSX.Element;
|
const CodeBlock: (props: Props) => JSX.Element;
|
||||||
|
|
|
@ -10,6 +10,9 @@ const path = require('path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const {mapValues, pickBy} = require('lodash');
|
const {mapValues, pickBy} = require('lodash');
|
||||||
|
|
||||||
|
// Seems the 5s default timeout fails sometimes
|
||||||
|
jest.setTimeout(15000);
|
||||||
|
|
||||||
describe('update-code-translations', () => {
|
describe('update-code-translations', () => {
|
||||||
test(`to have base.json contain all the translations extracted from the theme. Please run "yarn workspace @docusaurus/theme-classic update-code-translations" to keep base.json up-to-date.`, async () => {
|
test(`to have base.json contain all the translations extracted from the theme. Please run "yarn workspace @docusaurus/theme-classic update-code-translations" to keep base.json up-to-date.`, async () => {
|
||||||
const baseMessages = pickBy(
|
const baseMessages = pickBy(
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
---
|
# Support
|
||||||
id: support
|
|
||||||
title: Support
|
|
||||||
slug: /support
|
|
||||||
---
|
|
||||||
|
|
||||||
Docusaurus has a community of thousands of developers.
|
Docusaurus has a community of thousands of developers.
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
---
|
# Team
|
||||||
id: team
|
|
||||||
title: Team
|
|
||||||
slug: /team
|
|
||||||
---
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveTeamRow,
|
ActiveTeamRow,
|
|
@ -1,8 +1,4 @@
|
||||||
---
|
# Awesome Resources
|
||||||
id: resources
|
|
||||||
title: Awesome Resources
|
|
||||||
slug: /resources
|
|
||||||
---
|
|
||||||
|
|
||||||
A curated list of interesting Docusaurus community projects.
|
A curated list of interesting Docusaurus community projects.
|
||||||
|
|
|
@ -70,9 +70,16 @@ module.exports = {
|
||||||
include: ['**/*.md', '**/*.mdx'], // Extensions to include.
|
include: ['**/*.md', '**/*.mdx'], // Extensions to include.
|
||||||
/**
|
/**
|
||||||
* Path to sidebar configuration for showing a list of markdown pages.
|
* Path to sidebar configuration for showing a list of markdown pages.
|
||||||
* Warning: will change
|
|
||||||
*/
|
*/
|
||||||
sidebarPath: '',
|
sidebarPath: 'sidebars.js',
|
||||||
|
/**
|
||||||
|
* Function used to replace the sidebar items of type "autogenerated"
|
||||||
|
* by real sidebar items (docs, categories, links...)
|
||||||
|
*/
|
||||||
|
sidebarItemsGenerator: function ({item, version, docs}) {
|
||||||
|
// Use the provided data to create a custom "sidebar slice"
|
||||||
|
return [{type: 'doc', id: 'doc1'}];
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Theme components used by the docs pages
|
* Theme components used by the docs pages
|
||||||
*/
|
*/
|
||||||
|
@ -154,14 +161,16 @@ Markdown documents can use the following markdown frontmatter metadata fields, e
|
||||||
|
|
||||||
- `id`: A unique document id. If this field is not present, the document's `id` will default to its file name (without the extension)
|
- `id`: A unique document id. If this field is not present, the document's `id` will default to its file name (without the extension)
|
||||||
- `title`: The title of your document. If this field is not present, the document's `title` will default to its `id`
|
- `title`: The title of your document. If this field is not present, the document's `title` will default to its `id`
|
||||||
- `hide_title`: Whether to hide the title at the top of the doc. By default it is `false`
|
- `hide_title`: Whether to hide the title at the top of the doc. By default, it is `false`
|
||||||
- `hide_table_of_contents`: Whether to hide the table of contents to the right. By default it is `false`
|
- `hide_table_of_contents`: Whether to hide the table of contents to the right. By default it is `false`
|
||||||
- `sidebar_label`: The text shown in the document sidebar and in the next/previous button for this document. If this field is not present, the document's `sidebar_label` will default to its `title`
|
- `sidebar_label`: The text shown in the document sidebar and in the next/previous button for this document. If this field is not present, the document's `sidebar_label` will default to its `title`
|
||||||
|
- `sidebar_position`: Permits to control the position of a doc inside the generated sidebar slice, when using `autogenerated` sidebar items. Can be Int or Float.
|
||||||
|
- `strip_number_prefixes`: When a document has a number prefix (`001 - My Doc.md`, `2. MyDoc.md`...), it is automatically removed, and the prefix is used as `sidebar_position`. Use `strip_number_prefixes: false` if you want to disable this behavior
|
||||||
- `custom_edit_url`: The URL for editing this document. If this field is not present, the document's edit URL will fall back to `editUrl` from options fields passed to `docusaurus-plugin-content-docs`
|
- `custom_edit_url`: The URL for editing this document. If this field is not present, the document's edit URL will fall back to `editUrl` from options fields passed to `docusaurus-plugin-content-docs`
|
||||||
- `keywords`: Keywords meta tag for the document page, for search engines
|
- `keywords`: Keywords meta tag for the document page, for search engines
|
||||||
- `description`: The description of your document, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. If this field is not present, it will default to the first line of the contents
|
- `description`: The description of your document, which will become the `<meta name="description" content="..."/>` and `<meta property="og:description" content="..."/>` in `<head>`, used by search engines. If this field is not present, it will default to the first line of the contents
|
||||||
- `image`: Cover or thumbnail image that will be used when displaying the link to your post
|
- `image`: Cover or thumbnail image that will be used when displaying the link to your post
|
||||||
- `slug`: Allows to customize the document url
|
- `slug`: Allows to customize the document url (`/<routeBasePath>/<slug>`). Support multiple patterns: `slug: my-doc`, `slug: /my/path/myDoc`, `slug: /`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,20 @@ title: Creating Pages
|
||||||
slug: /creating-pages
|
slug: /creating-pages
|
||||||
---
|
---
|
||||||
|
|
||||||
In this section, we will learn about creating ad-hoc pages in Docusaurus using React. This is most useful for creating one-off standalone pages like a showcase page, playground page or support page.
|
In this section, we will learn about creating pages in Docusaurus.
|
||||||
|
|
||||||
|
This is useful for creating **one-off standalone pages** like a showcase page, playground page or support page.
|
||||||
|
|
||||||
The functionality of pages is powered by `@docusaurus/plugin-content-pages`.
|
The functionality of pages is powered by `@docusaurus/plugin-content-pages`.
|
||||||
|
|
||||||
You can use React components, or Markdown.
|
You can use React components, or Markdown.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Pages do not have sidebars, only [docs](./docs/docs-introduction.md) have.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Add a React page {#add-a-react-page}
|
## Add a React page {#add-a-react-page}
|
||||||
|
|
||||||
Create a file `/src/pages/helloReact.js`:
|
Create a file `/src/pages/helloReact.js`:
|
||||||
|
|
|
@ -4,170 +4,241 @@ title: Sidebar
|
||||||
slug: /sidebar
|
slug: /sidebar
|
||||||
---
|
---
|
||||||
|
|
||||||
To generate a sidebar to your Docusaurus site:
|
Creating a sidebar is useful to:
|
||||||
|
|
||||||
|
- Group multiple **related documents**
|
||||||
|
- **Display a sidebar** on each of those documents
|
||||||
|
- Provide a **paginated navigation**, with next/previous button
|
||||||
|
|
||||||
|
To use sidebars on your Docusaurus site:
|
||||||
|
|
||||||
1. Define a file that exports a [sidebar object](#sidebar-object).
|
1. Define a file that exports a [sidebar object](#sidebar-object).
|
||||||
1. Pass this object into the `@docusaurus/plugin-docs` plugin directly or via `@docusaurus/preset-classic`.
|
1. Pass this object into the `@docusaurus/plugin-docs` plugin directly or via `@docusaurus/preset-classic`.
|
||||||
|
|
||||||
```js {8-9} title="docusaurus.config.js"
|
```js title="docusaurus.config.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// ...
|
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
'@docusaurus/preset-classic',
|
'@docusaurus/preset-classic',
|
||||||
{
|
{
|
||||||
docs: {
|
docs: {
|
||||||
// Sidebars filepath relative to the site dir.
|
// highlight-start
|
||||||
sidebarPath: require.resolve('./sidebars.js'),
|
sidebarPath: require.resolve('./sidebars.js'),
|
||||||
|
// highlight-end
|
||||||
},
|
},
|
||||||
// ...
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sidebar object {#sidebar-object}
|
## Default sidebar
|
||||||
|
|
||||||
A sidebar object contains [sidebar items](#understanding-sidebar-items) and it is defined like this:
|
By default, Docusaurus [automatically generates a sidebar](#sidebar-item-autogenerated) for you, by using the filesystem structure of the `docs` folder:
|
||||||
|
|
||||||
```typescript
|
|
||||||
type Sidebar = {
|
|
||||||
[sidebarId: string]:
|
|
||||||
| {
|
|
||||||
[sidebarCategory: string]: SidebarItem[];
|
|
||||||
}
|
|
||||||
| SidebarItem[];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```js title="sidebars.js"
|
```js title="sidebars.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
docs: [
|
mySidebar: [
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'autogenerated',
|
||||||
label: 'Getting Started',
|
dirName: '.', // generate sidebar slice from the docs folder (or versioned_docs/<version>)
|
||||||
items: ['greeting'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
label: 'Docusaurus',
|
|
||||||
items: ['doc1'],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, notice the following:
|
You can also define your sidebars explicitly.
|
||||||
|
|
||||||
- The key `docs` is the id of the sidebar. The id can be any value, not necessarily `docs`.
|
## Sidebar object {#sidebar-object}
|
||||||
- `Getting Started` is a category within the sidebar.
|
|
||||||
- `greeting` and `doc1` are both [sidebar item](#understanding-sidebar-items).
|
|
||||||
|
|
||||||
Shorthand notation can also be used:
|
A sidebar is a **tree of [sidebar items](#understanding-sidebar-items)**.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type Sidebar =
|
||||||
|
// Normal syntax
|
||||||
|
| SidebarItem[]
|
||||||
|
|
||||||
|
// Shorthand syntax
|
||||||
|
| Record<
|
||||||
|
string, // category label
|
||||||
|
SidebarItem[] // category items
|
||||||
|
>;
|
||||||
|
```
|
||||||
|
|
||||||
|
A sidebars file can contain **multiple sidebar objects**.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type SidebarsFile = Record<
|
||||||
|
string, // sidebar id
|
||||||
|
Sidebar
|
||||||
|
>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
```js title="sidebars.js"
|
```js title="sidebars.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
docs: {
|
mySidebar: [
|
||||||
'Getting started': ['greeting'],
|
{
|
||||||
Docusaurus: ['doc1'],
|
type: 'category',
|
||||||
},
|
label: 'Getting Started',
|
||||||
|
items: ['doc1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Docusaurus',
|
||||||
|
items: ['doc2', 'doc3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
:::note
|
Notice the following:
|
||||||
|
|
||||||
Shorthand notation relies on the iteration order of JavaScript object keys for the category name. When using this notation, keep in mind that EcmaScript does not guarantee `Object.keys({a,b}) === ['a','b']`, yet this is generally true.
|
- There is a single sidebar `mySidebar`, containing 5 [sidebar items](#understanding-sidebar-items)
|
||||||
|
- `Getting Started` and `Docusaurus` are sidebar categories
|
||||||
|
- `doc1`, `doc2` and `doc3` are sidebar documents
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
Use the **shorthand syntax** to express this sidebar more concisely:
|
||||||
|
|
||||||
|
```js title="sidebars.js"
|
||||||
|
module.exports = {
|
||||||
|
mySidebar: {
|
||||||
|
'Getting started': ['doc1'],
|
||||||
|
Docusaurus: ['doc2', 'doc3'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Using multiple sidebars {#using-multiple-sidebars}
|
## Using multiple sidebars {#using-multiple-sidebars}
|
||||||
|
|
||||||
You can have multiple sidebars for different Markdown files by adding more top-level keys to the exported object.
|
You can create a sidebar for each **set of markdown files** that you want to **group together**.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
The Docusaurus site is a good example of using multiple sidebars:
|
||||||
|
|
||||||
|
- [Docs](../../introduction.md)
|
||||||
|
- [API](../../cli.md)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```js title="sidebars.js"
|
```js title="sidebars.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
firstSidebar: {
|
tutorialSidebar: {
|
||||||
'Category A': ['doc1'],
|
'Category A': ['doc1', 'doc2'],
|
||||||
},
|
|
||||||
secondSidebar: {
|
|
||||||
'Category A': ['doc2'],
|
|
||||||
'Category B': ['doc3'],
|
|
||||||
},
|
},
|
||||||
|
apiSidebar: ['doc3', 'doc4'],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the doc page the user is reading will display the sidebar that it is part of. This can be customized with the [sidebar type](#understanding-sidebar-items).
|
:::note
|
||||||
|
|
||||||
For example, with the above example, when displaying the `doc2` page, the sidebar will contain the items of `secondSidebar` only. Another example of multiple sidebars is the `API` and `Docs` sections on the Docusaurus website.
|
The keys `tutorialSidebar` and `apiSidebar` are sidebar **technical ids** and do not matter much.
|
||||||
|
|
||||||
For more information about sidebars and how they relate to doc pages, see [Navbar doc link](../../api/themes/theme-configuration.md#navbar-doc-link).
|
:::
|
||||||
|
|
||||||
|
When browsing:
|
||||||
|
|
||||||
|
- `doc1` or `doc2`: the `tutorialSidebar` will be displayed
|
||||||
|
- `doc3` or `doc4`: the `apiSidebar` will be displayed
|
||||||
|
|
||||||
|
A **paginated navigation** link documents inside the same sidebar with **next and previous buttons**.
|
||||||
|
|
||||||
## Understanding sidebar items {#understanding-sidebar-items}
|
## Understanding sidebar items {#understanding-sidebar-items}
|
||||||
|
|
||||||
As the name implies, `SidebarItem` is an item defined in a Sidebar. A SidebarItem as a `type` that defines what the item links to.
|
`SidebarItem` is an item defined in a Sidebar tree.
|
||||||
|
|
||||||
`type` supports the following values
|
There are different types of sidebar items:
|
||||||
|
|
||||||
- [Doc](#linking-to-a-doc-page)
|
- **[Doc](#sidebar-item-doc)**: link to a doc page, assigning it to the sidebar
|
||||||
- [Link](#creating-a-generic-link)
|
- **[Ref](#sidebar-item-ref)**: link to a doc page, without assigning it to the sidebar
|
||||||
- [Ref](#creating-a-link-to-page-without-sidebar)
|
- **[Link](#sidebar-item-link)**: link to any internal or external page
|
||||||
- [Category](#creating-a-hierarchy)
|
- **[Category](#sidebar-item-category)**: create a hierarchy of sidebar items
|
||||||
|
- **[Autogenerated](#sidebar-item-autogenerated)**: generate a sidebar slice automatically
|
||||||
|
|
||||||
### Linking to a doc page {#linking-to-a-doc-page}
|
### Doc: link to a doc {#sidebar-item-doc}
|
||||||
|
|
||||||
Set `type` to `doc` to link to a documentation page. This is the default type.
|
Use the `doc` type to link to a doc page and assign that doc to a sidebar:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
type SidebarItemDoc =
|
type SidebarItemDoc =
|
||||||
| string
|
// Normal syntax
|
||||||
| {
|
| {
|
||||||
type: 'doc';
|
type: 'doc';
|
||||||
id: string;
|
id: string;
|
||||||
label: string; // Sidebar label text
|
label: string; // Sidebar label text
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// Shorthand syntax
|
||||||
|
| string; // docId shortcut
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
type: 'doc',
|
|
||||||
id: 'doc1', // string - document id
|
|
||||||
label: 'Getting started' // Sidebar label text
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `sidebar_label` in the markdown frontmatter has a higher precedence over the `label` key in `SidebarItemDoc`. Using just the [Document ID](#document-id) is also valid, the following is equivalent to the above:
|
|
||||||
|
|
||||||
```js
|
|
||||||
'doc1'; // string - document id
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Using this type will bind the linked doc to current sidebar. This means that if you access the `doc1` page, the sidebar displayed will be the sidebar that contains this doc page.
|
|
||||||
|
|
||||||
In the example below, `doc1` is bound to `firstSidebar`.
|
|
||||||
|
|
||||||
```js title="sidebars.js"
|
```js title="sidebars.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
firstSidebar: {
|
mySidebar: [
|
||||||
'Category A': ['doc1'],
|
// Normal syntax:
|
||||||
},
|
// highlight-start
|
||||||
secondSidebar: {
|
{
|
||||||
'Category A': ['doc2'],
|
type: 'doc',
|
||||||
'Category B': ['doc3'],
|
id: 'doc1', // document id
|
||||||
},
|
label: 'Getting started', // sidebar label
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
|
// Shorthand syntax:
|
||||||
|
// highlight-start
|
||||||
|
'doc2', // document id
|
||||||
|
// highlight-end
|
||||||
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating a generic link {#creating-a-generic-link}
|
The `sidebar_label` markdown frontmatter has a higher precedence over the `label` key in `SidebarItemDoc`.
|
||||||
|
|
||||||
Set `type` to `link` to link to a non-documentation page. For example, to create an external link.
|
:::note
|
||||||
|
|
||||||
|
Don't assign the same doc to multiple sidebars: use a [ref](#sidebar-item-ref) instead.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Ref: link to a doc, without sidebar {#sidebar-item-ref}
|
||||||
|
|
||||||
|
Use the `ref` type to link to a doc page without assigning it to a sidebar.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type SidebarItemRef = {
|
||||||
|
type: 'ref';
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js title="sidebars.js"
|
||||||
|
module.exports = {
|
||||||
|
mySidebar: [
|
||||||
|
{
|
||||||
|
type: 'ref',
|
||||||
|
id: 'doc1', // Document id (string).
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
When browsing `doc1`, Docusaurus **will not display** the `mySidebar` sidebar.
|
||||||
|
|
||||||
|
### Link: link to any page {#sidebar-item-link}
|
||||||
|
|
||||||
|
Use the `link` type to link to any page (internal or external) that is not a doc.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
type SidebarItemLink = {
|
type SidebarItemLink = {
|
||||||
|
@ -179,43 +250,41 @@ type SidebarItemLink = {
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```js
|
```js title="sidebars.js"
|
||||||
{
|
module.exports = {
|
||||||
type: 'link',
|
myLinksSidebar: [
|
||||||
label: 'Custom Label', // The label that should be displayed (string).
|
// highlight-start
|
||||||
href: 'https://example.com' // The target URL (string).
|
// External link
|
||||||
}
|
{
|
||||||
```
|
type: 'link',
|
||||||
|
label: 'Facebook', // The link label
|
||||||
|
href: 'https://facebook.com', // The external URL
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
### Creating a link to page without sidebar {#creating-a-link-to-page-without-sidebar}
|
// highlight-start
|
||||||
|
// Internal link
|
||||||
Set `type` to `ref` to link to a documentation page without binding it to a sidebar. This means the sidebar disappears when the user displays the linked page.
|
{
|
||||||
|
type: 'link',
|
||||||
```typescript
|
label: 'Home', // The link label
|
||||||
type SidebarItemRef = {
|
href: '/', // The internal path
|
||||||
type: 'ref';
|
},
|
||||||
id: string;
|
// highlight-end
|
||||||
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
### Category: create a hierarchy {#sidebar-item-category}
|
||||||
|
|
||||||
```js
|
Use the `category` type to create a hierarchy of sidebar items.
|
||||||
{
|
|
||||||
type: 'ref',
|
|
||||||
id: 'doc1', // Document id (string).
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating a hierarchy {#creating-a-hierarchy}
|
|
||||||
|
|
||||||
The Sidebar item type that creates a hierarchy in the sidebar. Set `type` to `category`.
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
type SidebarItemCategory = {
|
type SidebarItemCategory = {
|
||||||
type: 'category';
|
type: 'category';
|
||||||
label: string; // Sidebar label text.
|
label: string; // Sidebar label text.
|
||||||
items: SidebarItem[]; // Array of sidebar items.
|
items: SidebarItem[]; // Array of sidebar items.
|
||||||
|
|
||||||
|
// Category options:
|
||||||
collapsed: boolean; // Set the category to be collapsed or open by default
|
collapsed: boolean; // Set the category to be collapsed or open by default
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -225,16 +294,16 @@ Example:
|
||||||
```js title="sidebars.js"
|
```js title="sidebars.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
docs: [
|
docs: [
|
||||||
{
|
|
||||||
...
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
label: 'Guides',
|
label: 'Guides',
|
||||||
|
collapsed: false,
|
||||||
items: [
|
items: [
|
||||||
'guides/creating-pages',
|
'creating-pages',
|
||||||
{
|
{
|
||||||
Docs: ['docs-introduction', 'docs-sidebar', 'markdown-features', 'versioning'],
|
type: 'category',
|
||||||
|
label: 'Docs',
|
||||||
|
items: ['introduction', 'sidebar', 'markdown-features', 'versioning'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -242,7 +311,9 @@ module.exports = {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: it's possible to use the shorthand syntax to create nested categories:
|
:::tip
|
||||||
|
|
||||||
|
Use the **shorthand syntax** when you don't need **category options**:
|
||||||
|
|
||||||
```js title="sidebars.js"
|
```js title="sidebars.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -250,28 +321,25 @@ module.exports = {
|
||||||
Guides: [
|
Guides: [
|
||||||
'creating-pages',
|
'creating-pages',
|
||||||
{
|
{
|
||||||
Docs: [
|
Docs: ['introduction', 'sidebar', 'markdown-features', 'versioning'],
|
||||||
'docs-introduction',
|
|
||||||
'docs-sidebar',
|
|
||||||
'markdown-features',
|
|
||||||
'versioning',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
#### Collapsible categories {#collapsible-categories}
|
#### Collapsible categories {#collapsible-categories}
|
||||||
|
|
||||||
For sites with a sizable amount of content, we support the option to expand/collapse a category to toggle the display of its contents. Categories are collapsible by default. If you want them to be always expanded, set `themeConfig.sidebarCollapsible` to `false`:
|
For sites with a sizable amount of content, we support the option to expand/collapse a category to toggle the display of its contents. Categories are collapsible by default. If you want them to be always expanded, set `themeConfig.sidebarCollapsible` to `false`:
|
||||||
|
|
||||||
```js {4} title="docusaurus.config.js"
|
```js title="docusaurus.config.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// ...
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
// highlight-start
|
||||||
sidebarCollapsible: false,
|
sidebarCollapsible: false,
|
||||||
// ...
|
// highlight-end
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -296,16 +364,189 @@ module.exports = {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Autogenerated: generate a sidebar {#sidebar-item-autogenerated}
|
||||||
|
|
||||||
|
Docusaurus can **create a sidebar automatically** from your **filesystem structure**: each folder creates a sidebar category.
|
||||||
|
|
||||||
|
An `autogenerated` item is converted by Docusaurus to a **sidebar slice**: a list of items of type `doc` and `category`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type SidebarItemAutogenerated = {
|
||||||
|
type: 'autogenerated';
|
||||||
|
dirName: string; // Source folder to generate the sidebar slice from (relative to docs)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Docusaurus can generate a sidebar from your docs folder:
|
||||||
|
|
||||||
|
```js title="sidebars.js"
|
||||||
|
module.exports = {
|
||||||
|
myAutogeneratedSidebar: [
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: '.', // '.' means the current docs folder
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use **multiple `autogenerated` items** in a sidebar, and interleave them with regular sidebar items:
|
||||||
|
|
||||||
|
```js title="sidebars.js"
|
||||||
|
module.exports = {
|
||||||
|
mySidebar: [
|
||||||
|
'intro',
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Tutorials',
|
||||||
|
items: [
|
||||||
|
'tutorial-intro',
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: 'tutorials/easy', // Generate sidebar slice from docs/tutorials/easy
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
'tutorial-medium',
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: 'tutorials/advanced', // Generate sidebar slice from docs/tutorials/hard
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
'tutorial-end',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
type: 'autogenerated',
|
||||||
|
dirName: 'guides', // Generate sidebar slice from docs/guides
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Community',
|
||||||
|
items: ['team', 'chat'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Autogenerated sidebar metadatas {#autogenerated-sidebar-metadatas}
|
||||||
|
|
||||||
|
By default, the sidebar slice will be generated in **alphabetical order** (using files and folders names).
|
||||||
|
|
||||||
|
If the generated sidebar does not look good, you can assign additional metadatas to docs and categories.
|
||||||
|
|
||||||
|
**For docs**: use additional frontmatter:
|
||||||
|
|
||||||
|
```diff title="docs/tutorials/tutorial-easy.md"
|
||||||
|
+ ---
|
||||||
|
+ sidebar_label: Easy
|
||||||
|
+ sidebar_position: 2
|
||||||
|
+ ---
|
||||||
|
|
||||||
|
|
||||||
|
# Easy Tutorial
|
||||||
|
|
||||||
|
This is the easy tutorial!
|
||||||
|
```
|
||||||
|
|
||||||
|
**For categories**: add a `_category_.json` or `_category_.yml` file in the appropriate folder:
|
||||||
|
|
||||||
|
```json title="docs/tutorials/_category_.json"
|
||||||
|
{
|
||||||
|
"label": "Tutorial",
|
||||||
|
"position": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml title="docs/tutorials/_category_.yml"
|
||||||
|
label: 'Tutorial'
|
||||||
|
position: 2.5 # float position is supported
|
||||||
|
collapsed: false # keep the category open by default
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
The position metadata is only used **inside a sidebar slice**: Docusaurus does not re-order other items of your sidebar.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
#### Using number prefixes
|
||||||
|
|
||||||
|
A simple way to order an autogenerated sidebar is to prefix docs and folders by number prefixes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docs
|
||||||
|
├── 01-Intro.md
|
||||||
|
├── 02-Tutorial Easy
|
||||||
|
│ ├── 01-First Part.md
|
||||||
|
│ ├── 02-Second Part.md
|
||||||
|
│ └── 03-End.md
|
||||||
|
├── 03-Tutorial Hard
|
||||||
|
│ ├── 01-First Part.md
|
||||||
|
│ ├── 02-Second Part.md
|
||||||
|
│ ├── 03-Third Part.md
|
||||||
|
│ └── 04-End.md
|
||||||
|
└── 04-End.md
|
||||||
|
```
|
||||||
|
|
||||||
|
To make it **easier to adopt**, Docusaurus supports **multiple number prefix patterns**.
|
||||||
|
|
||||||
|
By default, Docusaurus will **remove the number prefix** from the doc id, title, label and url paths.
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
**Prefer using [additional metadatas](#autogenerated-sidebar-metadatas)**.
|
||||||
|
|
||||||
|
Updating a number prefix can be annoying, as it can require **updating multiple existing markdown links**:
|
||||||
|
|
||||||
|
```diff title="docs/02-Tutorial Easy/01-First Part.md"
|
||||||
|
- Check the [Tutorial End](../04-End.md);
|
||||||
|
+ Check the [Tutorial End](../05-End.md);
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
#### Customize the sidebar items generator
|
||||||
|
|
||||||
|
You can provide a custom `sidebarItemsGenerator` function in the docs plugin (or preset) config:
|
||||||
|
|
||||||
|
```js title="docusaurus.config.js"
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
'@docusaurus/plugin-content-docs',
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Function used to replace the sidebar items of type "autogenerated"
|
||||||
|
* by real sidebar items (docs, categories, links...)
|
||||||
|
*/
|
||||||
|
// highlight-start
|
||||||
|
sidebarItemsGenerator: function ({item, version, docs}) {
|
||||||
|
// Use the provided data to create a custom "sidebar slice"
|
||||||
|
return [{type: 'doc', id: 'doc1'}];
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## Hideable sidebar {#hideable-sidebar}
|
## Hideable sidebar {#hideable-sidebar}
|
||||||
|
|
||||||
Using the enabled `themeConfig.hideableSidebar` option, you can make the entire sidebar hidden, allowing you to better focus your users on the content. This is especially useful when content consumption on medium screens (e.g. on tablets).
|
Using the enabled `themeConfig.hideableSidebar` option, you can make the entire sidebar hidden, allowing you to better focus your users on the content. This is especially useful when content consumption on medium screens (e.g. on tablets).
|
||||||
|
|
||||||
```js {4} title="docusaurus.config.js"
|
```js title="docusaurus.config.js"
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// ...
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
// highlight-starrt
|
||||||
hideableSidebar: true,
|
hideableSidebar: true,
|
||||||
// ...
|
// highlight-end
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -323,3 +564,21 @@ To pass in custom props to a swizzled sidebar item, add the optional `customProp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Complex sidebars example {#complex-sidebars-example}
|
||||||
|
|
||||||
|
Real-world example from the Docusaurus site:
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
|
import CodeBlock from '@theme/CodeBlock';
|
||||||
|
|
||||||
|
<CodeBlock className="language-js" title="sidebars.js">
|
||||||
|
{require('!!raw-loader!@site/sidebars.js')
|
||||||
|
.default
|
||||||
|
.split('\n')
|
||||||
|
// remove comments
|
||||||
|
.map((line) => !['#','/*','*'].some(commentPattern => line.trim().startsWith(commentPattern)) && line)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n')}
|
||||||
|
</CodeBlock>
|
||||||
|
```
|
||||||
|
|
|
@ -514,13 +514,15 @@ It is currently **not possible to link to a specific file** in Crowdin.
|
||||||
|
|
||||||
The **Docusaurus v2 configuration file** is a good example of using versioning and multi-instance:
|
The **Docusaurus v2 configuration file** is a good example of using versioning and multi-instance:
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
import CrowdinConfigV2 from '!!raw-loader!@site/../crowdin-v2.yaml';
|
import CrowdinConfigV2 from '!!raw-loader!@site/../crowdin-v2.yaml';
|
||||||
import CodeBlock from '@theme/CodeBlock';
|
import CodeBlock from '@theme/CodeBlock';
|
||||||
|
|
||||||
<CodeBlock className="language-yaml" title="test">
|
<CodeBlock className="language-yaml" title="crowdin.yml">
|
||||||
{CrowdinConfigV2.split('\n')
|
{CrowdinConfigV2.split('\n')
|
||||||
// remove comments
|
// remove comments
|
||||||
.map((line) => !line.startsWith('#') && line)
|
.map((line) => !line.startsWith('#') && line)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join('\n')}
|
.join('\n')}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
|
```
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
community: [
|
community: [
|
||||||
'support',
|
{
|
||||||
'team',
|
type: 'autogenerated',
|
||||||
'resources',
|
dirName: '.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
href: '/showcase',
|
href: '/showcase',
|
||||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -3480,6 +3480,11 @@
|
||||||
jest-diff "^26.0.0"
|
jest-diff "^26.0.0"
|
||||||
pretty-format "^26.0.0"
|
pretty-format "^26.0.0"
|
||||||
|
|
||||||
|
"@types/js-yaml@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.0.tgz#d1a11688112091f2c711674df3a65ea2f47b5dfb"
|
||||||
|
integrity sha512-4vlpCM5KPCL5CfGmTbpjwVKbISRYhduEJvvUWsH5EB7QInhEj94XPZ3ts/9FPiLZFqYO0xoW4ZL8z2AabTGgJA==
|
||||||
|
|
||||||
"@types/jscodeshift@^0.7.1":
|
"@types/jscodeshift@^0.7.1":
|
||||||
version "0.7.1"
|
version "0.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jscodeshift/-/jscodeshift-0.7.1.tgz#8afcda6c8ca2ce828c3b192f8a1ba0245987ac12"
|
resolved "https://registry.yarnpkg.com/@types/jscodeshift/-/jscodeshift-0.7.1.tgz#8afcda6c8ca2ce828c3b192f8a1ba0245987ac12"
|
||||||
|
@ -4504,6 +4509,11 @@ argparse@^1.0.10, argparse@^1.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
sprintf-js "~1.0.2"
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
|
argparse@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||||
|
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||||
|
|
||||||
aria-query@^4.2.2:
|
aria-query@^4.2.2:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
|
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
|
||||||
|
@ -6292,6 +6302,11 @@ columnify@^1.5.4:
|
||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
wcwidth "^1.0.0"
|
wcwidth "^1.0.0"
|
||||||
|
|
||||||
|
combine-promises@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71"
|
||||||
|
integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==
|
||||||
|
|
||||||
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||||
|
@ -12017,6 +12032,13 @@ js-yaml@^3.11.0, js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.8.1:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
|
|
||||||
|
js-yaml@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f"
|
||||||
|
integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==
|
||||||
|
dependencies:
|
||||||
|
argparse "^2.0.1"
|
||||||
|
|
||||||
jsbn@~0.1.0:
|
jsbn@~0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||||
|
|
Loading…
Add table
Reference in a new issue