mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-28 00:17:14 +02:00
feat(content-docs): displayed_sidebar front matter (#5782)
This commit is contained in:
parent
fdf59f30f0
commit
45f1b819b5
11 changed files with 128 additions and 32 deletions
|
@ -30,6 +30,7 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
|
|||
sidebar_label: Joi.string(),
|
||||
sidebar_position: Joi.number(),
|
||||
sidebar_class_name: Joi.string(),
|
||||
displayed_sidebar: Joi.string().allow(null),
|
||||
tags: FrontMatterTagsSchema,
|
||||
pagination_label: Joi.string(),
|
||||
custom_edit_url: URISchema.allow('', null),
|
||||
|
|
|
@ -295,6 +295,7 @@ export function addDocNavigation(
|
|||
const navigation = sidebarsUtils.getDocNavigation(
|
||||
doc.unversionedId,
|
||||
doc.id,
|
||||
doc.frontMatter.displayed_sidebar,
|
||||
);
|
||||
|
||||
const toNavigationLinkByDocId = (
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
collectSidebarLinks,
|
||||
transformSidebarItems,
|
||||
collectSidebarsDocIds,
|
||||
type SidebarNavigation,
|
||||
toDocNavigationLink,
|
||||
toNavigationLink,
|
||||
} from '../utils';
|
||||
|
@ -148,32 +147,32 @@ describe('createSidebarsUtils', () => {
|
|||
});
|
||||
|
||||
test('getDocNavigation', async () => {
|
||||
expect(getDocNavigation('doc1', 'doc1')).toEqual({
|
||||
expect(getDocNavigation('doc1', 'doc1', undefined)).toEqual({
|
||||
sidebarName: 'sidebar1',
|
||||
previous: undefined,
|
||||
next: {
|
||||
type: 'doc',
|
||||
id: 'doc2',
|
||||
},
|
||||
} as SidebarNavigation);
|
||||
expect(getDocNavigation('doc2', 'doc2')).toEqual({
|
||||
});
|
||||
expect(getDocNavigation('doc2', 'doc2', undefined)).toEqual({
|
||||
sidebarName: 'sidebar1',
|
||||
previous: {
|
||||
type: 'doc',
|
||||
id: 'doc1',
|
||||
},
|
||||
next: undefined,
|
||||
} as SidebarNavigation);
|
||||
});
|
||||
|
||||
expect(getDocNavigation('doc3', 'doc3')).toEqual({
|
||||
expect(getDocNavigation('doc3', 'doc3', undefined)).toEqual({
|
||||
sidebarName: 'sidebar2',
|
||||
previous: undefined,
|
||||
next: {
|
||||
type: 'doc',
|
||||
id: 'doc4',
|
||||
},
|
||||
} as SidebarNavigation);
|
||||
expect(getDocNavigation('doc4', 'doc4')).toEqual({
|
||||
});
|
||||
expect(getDocNavigation('doc4', 'doc4', undefined)).toEqual({
|
||||
sidebarName: 'sidebar2',
|
||||
previous: {
|
||||
type: 'doc',
|
||||
|
@ -181,17 +180,17 @@ describe('createSidebarsUtils', () => {
|
|||
label: 'Doc 3',
|
||||
},
|
||||
next: undefined,
|
||||
} as SidebarNavigation);
|
||||
});
|
||||
|
||||
expect(getDocNavigation('doc5', 'doc5')).toMatchObject({
|
||||
expect(getDocNavigation('doc5', 'doc5', undefined)).toMatchObject({
|
||||
sidebarName: 'sidebar3',
|
||||
previous: undefined,
|
||||
next: {
|
||||
type: 'category',
|
||||
label: 'S3 SubCategory',
|
||||
},
|
||||
} as SidebarNavigation);
|
||||
expect(getDocNavigation('doc6', 'doc6')).toMatchObject({
|
||||
});
|
||||
expect(getDocNavigation('doc6', 'doc6', undefined)).toMatchObject({
|
||||
sidebarName: 'sidebar3',
|
||||
previous: {
|
||||
type: 'category',
|
||||
|
@ -201,15 +200,30 @@ describe('createSidebarsUtils', () => {
|
|||
type: 'doc',
|
||||
id: 'doc7',
|
||||
},
|
||||
} as SidebarNavigation);
|
||||
expect(getDocNavigation('doc7', 'doc7')).toMatchObject({
|
||||
});
|
||||
expect(getDocNavigation('doc7', 'doc7', undefined)).toEqual({
|
||||
sidebarName: 'sidebar3',
|
||||
previous: {
|
||||
type: 'doc',
|
||||
id: 'doc6',
|
||||
},
|
||||
next: undefined,
|
||||
} as SidebarNavigation);
|
||||
});
|
||||
expect(getDocNavigation('doc3', 'doc3', null)).toEqual({
|
||||
sidebarName: undefined,
|
||||
previous: undefined,
|
||||
next: undefined,
|
||||
});
|
||||
expect(() =>
|
||||
getDocNavigation('doc3', 'doc3', 'foo'),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Doc with ID doc3 wants to display sidebar foo but a sidebar with this name doesn't exist"`,
|
||||
);
|
||||
expect(getDocNavigation('doc3', 'doc3', 'sidebar1')).toEqual({
|
||||
sidebarName: 'sidebar1',
|
||||
previous: undefined,
|
||||
next: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('getCategoryGeneratedIndexNavigation', async () => {
|
||||
|
@ -225,7 +239,7 @@ describe('createSidebarsUtils', () => {
|
|||
type: 'category',
|
||||
label: 'S3 SubSubCategory',
|
||||
},
|
||||
} as SidebarNavigation);
|
||||
});
|
||||
|
||||
expect(
|
||||
getCategoryGeneratedIndexNavigation('/s3-subsubcategory-index-permalink'),
|
||||
|
@ -239,7 +253,7 @@ describe('createSidebarsUtils', () => {
|
|||
type: 'doc',
|
||||
id: 'doc6',
|
||||
},
|
||||
} as SidebarNavigation);
|
||||
});
|
||||
});
|
||||
|
||||
test('getCategoryGeneratedIndexList', async () => {
|
||||
|
|
|
@ -131,6 +131,7 @@ export type SidebarsUtils = {
|
|||
getDocNavigation: (
|
||||
unversionedId: string,
|
||||
versionedId: string,
|
||||
displayedSidebar: string | null | undefined,
|
||||
) => SidebarNavigation;
|
||||
getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
|
||||
getCategoryGeneratedIndexNavigation: (
|
||||
|
@ -182,16 +183,25 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils {
|
|||
function getDocNavigation(
|
||||
unversionedId: string,
|
||||
versionedId: string,
|
||||
displayedSidebar: string | null | undefined,
|
||||
): SidebarNavigation {
|
||||
// TODO legacy id retro-compatibility!
|
||||
let docId = unversionedId;
|
||||
let sidebarName = getSidebarNameByDocId(docId);
|
||||
if (!sidebarName) {
|
||||
let sidebarName =
|
||||
displayedSidebar === undefined
|
||||
? getSidebarNameByDocId(docId)
|
||||
: displayedSidebar;
|
||||
if (sidebarName === undefined) {
|
||||
docId = versionedId;
|
||||
sidebarName = getSidebarNameByDocId(docId);
|
||||
}
|
||||
|
||||
if (sidebarName) {
|
||||
if (!sidebarNameToNavigationItems[sidebarName]) {
|
||||
throw new Error(
|
||||
`Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`,
|
||||
);
|
||||
}
|
||||
const navigationItems = sidebarNameToNavigationItems[sidebarName];
|
||||
const currentItemIndex = navigationItems.findIndex((item) => {
|
||||
if (item.type === 'doc') {
|
||||
|
@ -202,6 +212,9 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils {
|
|||
}
|
||||
return false;
|
||||
});
|
||||
if (currentItemIndex === -1) {
|
||||
return {sidebarName, next: undefined, previous: undefined};
|
||||
}
|
||||
|
||||
const {previous, next} = getElementsAround(
|
||||
navigationItems,
|
||||
|
|
|
@ -22,6 +22,9 @@ import type {
|
|||
import {isCategoriesShorthand} from './utils';
|
||||
import type {CategoryMetadataFile} from './generator';
|
||||
|
||||
// NOTE: we don't add any default values during validation on purpose!
|
||||
// Config types are exposed to users for typechecking and we use the same type in normalization
|
||||
|
||||
const sidebarItemBaseSchema = Joi.object<SidebarItemBase>({
|
||||
className: Joi.string(),
|
||||
customProps: Joi.object().unknown(),
|
||||
|
|
|
@ -66,6 +66,7 @@ export type DocFrontMatter = {
|
|||
sidebar_label?: string;
|
||||
sidebar_position?: number;
|
||||
sidebar_class_name?: string;
|
||||
displayed_sidebar?: string | null;
|
||||
pagination_label?: string;
|
||||
custom_edit_url?: string | null;
|
||||
parse_number_prefixes?: boolean;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
displayed_sidebar: anotherSidebar
|
||||
---
|
||||
|
||||
# Doc with another sidebar
|
||||
|
||||
My link appears in a sidebar, but I want to display another sidebar...
|
7
website/_dogfooding/_docs tests/doc-without-sidebar.md
Normal file
7
website/_dogfooding/_docs tests/doc-without-sidebar.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
displayed_sidebar: null
|
||||
---
|
||||
|
||||
# Doc without sidebar
|
||||
|
||||
My link appears in a sidebar, but I don't want to display that...
|
1
website/_dogfooding/_docs tests/dummy.md
Normal file
1
website/_dogfooding/_docs tests/dummy.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Just a dummy page
|
|
@ -14,6 +14,8 @@ const sidebars = {
|
|||
className: 'red',
|
||||
label: 'Index',
|
||||
},
|
||||
'doc-without-sidebar',
|
||||
'doc-with-another-sidebar',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tests',
|
||||
|
@ -70,6 +72,7 @@ const sidebars = {
|
|||
],
|
||||
},
|
||||
],
|
||||
anotherSidebar: ['dummy'],
|
||||
};
|
||||
module.exports = sidebars;
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ We have introduced three types of item types in the above example: `doc`, `categ
|
|||
- **[Link](#sidebar-item-link)**: link to any internal or external page
|
||||
- **[Category](#sidebar-item-category)**: creates a dropdown of sidebar items
|
||||
- **[Autogenerated](#sidebar-item-autogenerated)**: generate a sidebar slice automatically
|
||||
- **[\*Ref](#sidebar-association)**: link to a doc page, without associating it with the sidebar
|
||||
- **[\*Ref](#sidebar-item-ref)**: link to a doc page, without making the item take part in navigation generation
|
||||
|
||||
### Doc: link to a doc {#sidebar-item-doc}
|
||||
|
||||
|
@ -966,31 +966,49 @@ module.exports = {
|
|||
};
|
||||
```
|
||||
|
||||
How does Docusaurus know which sidebar to display when browsing `commonDoc`? Answer: it doesn't, and we don't guarantee which sidebar it will pick. In this case, in order to remove the ambiguity, you can use the special `ref` sidebar item type.
|
||||
How does Docusaurus know which sidebar to display when browsing `commonDoc`? Answer: it doesn't, and we don't guarantee which sidebar it will pick.
|
||||
|
||||
The `ref` type is identical to the [`doc` type](#sidebar-item-doc) in every way, except that it doesn't set the association. It only registers itself as a link but doesn't take part in generating navigation metadata. When [generating pagination](#generating-pagination) and displaying sidebar, `ref` items are completely ignored.
|
||||
When you add doc Y to sidebar X, it creates a two-way binding: sidebar X contains a link to doc Y, and when browsing doc Y, sidebar X will be displayed. But sometimes, we want to break either implicit binding:
|
||||
|
||||
So you can turn the sidebars above into:
|
||||
1. _How do I generate a link to doc Y in sidebar X without making sidebar X displayed on Y?_ For example, when I include doc Y in multiple sidebars as in the example above, and I want to explicitly tell Docusaurus to display one sidebar?
|
||||
2. _How do I make sidebar X displayed when browsing doc Y, but sidebar X shouldn't contain the link to Y?_ For example, when Y is a "doc home page" and the sidebar is purely used for navigation?
|
||||
|
||||
Front matter option `displayed_sidebar` will forcibly set the sidebar association. For the same example, you can still use doc shorthands without any special configuration:
|
||||
|
||||
```js title="sidebars.js"
|
||||
module.exports = {
|
||||
tutorialSidebar: {
|
||||
'Category A': [
|
||||
'doc1',
|
||||
'doc2',
|
||||
// highlight-next-line
|
||||
{type: 'ref', id: 'commonDoc'},
|
||||
],
|
||||
'Category A': ['doc1', 'doc2'],
|
||||
},
|
||||
apiSidebar: ['doc3', 'doc4', 'commonDoc'],
|
||||
apiSidebar: ['doc3', 'doc4'],
|
||||
};
|
||||
```
|
||||
|
||||
Now, although the link to `commonDoc` is still included in the `tutorialSidebar` sidebar, when browsing `commonDoc`, only `apiSidebar` can be possibly displayed.
|
||||
And then add a front matter:
|
||||
|
||||
```md title="commonDoc.md"
|
||||
---
|
||||
displayed_sidebar: apiSidebar
|
||||
---
|
||||
```
|
||||
|
||||
Which explicitly tells Docusaurus to display `apiSidebar` when browsing `commonDoc`. Using the same method, you can make sidebar X which doesn't contain doc Y appear on doc Y:
|
||||
|
||||
```md title="home.md"
|
||||
---
|
||||
displayed_sidebar: tutorialSidebar
|
||||
---
|
||||
```
|
||||
|
||||
Even when `tutorialSidebar` doesn't contain a link to `home`, it will still be displayed when viewing `home`.
|
||||
|
||||
If you set `displayed_sidebar: null`, no sidebar will be displayed whatsoever on this page, and subsequently, no pagination either.
|
||||
|
||||
### Generating pagination {#generating-pagination}
|
||||
|
||||
Docusaurus uses the sidebar to generate the "next" and "previous" pagination links at the bottom of each doc page. It strictly uses the sidebar that is displayed: if no sidebar is associated, no pagination is generated either.
|
||||
Docusaurus uses the sidebar to generate the "next" and "previous" pagination links at the bottom of each doc page. It strictly uses the sidebar that is displayed: if no sidebar is associated, it doesn't generate pagination either. However, the docs linked as "next" and "previous" are not guaranteed to display the same sidebar: they are included in this sidebar, but in their front matter, they may have a different `displayed_sidebar`.
|
||||
|
||||
If a sidebar is displayed by setting `displayed_sidebar` front matter, and this sidebar doesn't contain the doc itself, no pagination is displayed.
|
||||
|
||||
You can customize pagination with front matter `pagination_next` and `pagination_prev`. Consider this sidebar:
|
||||
|
||||
|
@ -1021,6 +1039,33 @@ You can also disable displaying a pagination link with `pagination_next: null` o
|
|||
|
||||
The pagination label by default is the sidebar label. You can use the front matter `pagination_label` to customize how this doc appears in the pagination.
|
||||
|
||||
### The `ref` item {sidebar-item-ref}
|
||||
|
||||
The `ref` type is identical to the [`doc` type](#sidebar-item-doc) in every way, except that it doesn't participate in generating navigation metadata. It only registers itself as a link. When [generating pagination](#generating-pagination) and [displaying sidebar](#sidebar-association), `ref` items are completely ignored.
|
||||
|
||||
Consider this example:
|
||||
|
||||
```js title="sidebars.js"
|
||||
module.exports = {
|
||||
tutorialSidebar: {
|
||||
'Category A': [
|
||||
'doc1',
|
||||
'doc2',
|
||||
// highlight-next-line
|
||||
{type: 'ref', id: 'commonDoc'},
|
||||
'doc5',
|
||||
],
|
||||
},
|
||||
apiSidebar: ['doc3', 'doc4', 'commonDoc'],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
You can think of the `ref` type as the equivalent to doing the following:
|
||||
|
||||
- Setting `displayed_sidebar: tutorialSidebar` for `commonDoc` (`ref` is ignored in sidebar association)
|
||||
- Setting `pagination_next: doc5` for `doc2` and setting `pagination_prev: doc2` for `doc5` (`ref` is ignored in pagination generation)
|
||||
|
||||
## Passing custom props {#passing-custom-props}
|
||||
|
||||
To pass in custom props to a swizzled sidebar item, add the optional `customProps` object to any of the items:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue