Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(content-docs): displayed_sidebar front matter #5782

Merged
merged 27 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-plugin-content-docs/src/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ export function addDocNavigation(
const navigation = sidebarsUtils.getDocNavigation(
doc.unversionedId,
doc.id,
doc.frontMatter.displayed_sidebar,
);

const toNavigationLinkByDocId = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
collectSidebarLinks,
transformSidebarItems,
collectSidebarsDocIds,
type SidebarNavigation,
toDocNavigationLink,
toNavigationLink,
} from '../utils';
Expand Down Expand Up @@ -140,50 +139,50 @@ 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',
id: 'doc3',
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',
Expand All @@ -193,15 +192,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 () => {
Expand All @@ -217,7 +231,7 @@ describe('createSidebarsUtils', () => {
type: 'category',
label: 'S3 SubSubCategory',
},
} as SidebarNavigation);
});

expect(
getCategoryGeneratedIndexNavigation('/s3-subsubcategory-index-permalink'),
Expand All @@ -231,7 +245,7 @@ describe('createSidebarsUtils', () => {
type: 'doc',
id: 'doc6',
},
} as SidebarNavigation);
});
});

test('getCategoryGeneratedIndexList', async () => {
Expand Down
17 changes: 15 additions & 2 deletions packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export type SidebarsUtils = {
getDocNavigation: (
unversionedId: string,
versionedId: string,
displayedSidebar: string | null | undefined,
) => SidebarNavigation;
getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
getCategoryGeneratedIndexNavigation: (
Expand Down Expand Up @@ -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') {
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-plugin-content-docs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions website/_dogfooding/_docs tests/doc-with-another-sidebar.md
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions website/_dogfooding/_docs tests/doc-without-sidebar.md
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions website/_dogfooding/_docs tests/dummy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Just a dummy page
3 changes: 3 additions & 0 deletions website/_dogfooding/docs-tests-sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const sidebars = {
className: 'red',
label: 'Index',
},
'doc-without-sidebar',
'doc-with-another-sidebar',
{
type: 'category',
label: 'Tests',
Expand Down Expand Up @@ -70,6 +72,7 @@ const sidebars = {
],
},
],
anotherSidebar: ['dummy'],
};
module.exports = sidebars;

Expand Down
71 changes: 58 additions & 13 deletions website/docs/guides/docs/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

break either implicit binding?

I'm not native but that sounds weird to me

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break the forward binding, or backward binding 😄 I think it makes sense

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, seems good enough, we can improve later if anyone complains :D


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:

Expand Down Expand Up @@ -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:
Expand Down