mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 19:27:48 +02:00
fix(docs,theme): auto-generated category index should not display unlisted content (#8319)
This commit is contained in:
parent
1d5afbaaa5
commit
d8c72fb32d
10 changed files with 189 additions and 28 deletions
|
@ -9,7 +9,7 @@ import React, {type ReactNode} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import {
|
import {
|
||||||
findFirstCategoryLink,
|
findFirstSidebarItemLink,
|
||||||
useDocById,
|
useDocById,
|
||||||
} from '@docusaurus/theme-common/internal';
|
} from '@docusaurus/theme-common/internal';
|
||||||
import isInternalUrl from '@docusaurus/isInternalUrl';
|
import isInternalUrl from '@docusaurus/isInternalUrl';
|
||||||
|
@ -71,7 +71,7 @@ function CardCategory({
|
||||||
}: {
|
}: {
|
||||||
item: PropSidebarItemCategory;
|
item: PropSidebarItemCategory;
|
||||||
}): JSX.Element | null {
|
}): JSX.Element | null {
|
||||||
const href = findFirstCategoryLink(item);
|
const href = findFirstSidebarItemLink(item);
|
||||||
|
|
||||||
// Unexpected: categories that don't have a link have been filtered upfront
|
// Unexpected: categories that don't have a link have been filtered upfront
|
||||||
if (!href) {
|
if (!href) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import {
|
import {
|
||||||
isActiveSidebarItem,
|
isActiveSidebarItem,
|
||||||
findFirstCategoryLink,
|
findFirstSidebarItemLink,
|
||||||
useDocSidebarItemsExpandedState,
|
useDocSidebarItemsExpandedState,
|
||||||
isSamePath,
|
isSamePath,
|
||||||
} from '@docusaurus/theme-common/internal';
|
} from '@docusaurus/theme-common/internal';
|
||||||
|
@ -67,7 +67,7 @@ function useCategoryHrefWithSSRFallback(
|
||||||
if (isBrowser || !item.collapsible) {
|
if (isBrowser || !item.collapsible) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return findFirstCategoryLink(item);
|
return findFirstSidebarItemLink(item);
|
||||||
}, [item, isBrowser]);
|
}, [item, isBrowser]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ export {
|
||||||
isDocsPluginEnabled,
|
isDocsPluginEnabled,
|
||||||
useDocById,
|
useDocById,
|
||||||
findSidebarCategory,
|
findSidebarCategory,
|
||||||
findFirstCategoryLink,
|
findFirstSidebarItemLink,
|
||||||
isActiveSidebarItem,
|
isActiveSidebarItem,
|
||||||
isVisibleSidebarItem,
|
isVisibleSidebarItem,
|
||||||
useVisibleSidebarItems,
|
useVisibleSidebarItems,
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {renderHook} from '@testing-library/react-hooks';
|
||||||
import {StaticRouter} from 'react-router-dom';
|
import {StaticRouter} from 'react-router-dom';
|
||||||
import {Context} from '@docusaurus/core/src/client/docusaurusContext';
|
import {Context} from '@docusaurus/core/src/client/docusaurusContext';
|
||||||
import {
|
import {
|
||||||
findFirstCategoryLink,
|
findFirstSidebarItemLink,
|
||||||
isActiveSidebarItem,
|
isActiveSidebarItem,
|
||||||
useDocById,
|
useDocById,
|
||||||
findSidebarCategory,
|
findSidebarCategory,
|
||||||
|
@ -64,6 +64,7 @@ function testVersion(data?: Partial<PropVersionMetadata>): PropVersionMetadata {
|
||||||
docsSidebars: {},
|
docsSidebars: {},
|
||||||
isLast: false,
|
isLast: false,
|
||||||
pluginId: 'default',
|
pluginId: 'default',
|
||||||
|
noIndex: false,
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -163,9 +164,31 @@ describe('findSidebarCategory', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findFirstCategoryLink', () => {
|
describe('findFirstCategoryLink', () => {
|
||||||
|
it('works with html item', () => {
|
||||||
|
const htmlItem = {type: 'html', value: '<div/>'} as const;
|
||||||
|
expect(findFirstSidebarItemLink(htmlItem)).toBeUndefined();
|
||||||
|
expect(findFirstSidebarItemLink(htmlItem)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with link item', () => {
|
||||||
|
const linkItem = {
|
||||||
|
type: 'link',
|
||||||
|
href: '/linkHref',
|
||||||
|
label: 'Label',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
expect(findFirstSidebarItemLink(linkItem)).toBe('/linkHref');
|
||||||
|
expect(
|
||||||
|
findFirstSidebarItemLink({
|
||||||
|
...linkItem,
|
||||||
|
unlisted: true,
|
||||||
|
}),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('works with category without link nor child', () => {
|
it('works with category without link nor child', () => {
|
||||||
expect(
|
expect(
|
||||||
findFirstCategoryLink(
|
findFirstSidebarItemLink(
|
||||||
testCategory({
|
testCategory({
|
||||||
href: undefined,
|
href: undefined,
|
||||||
}),
|
}),
|
||||||
|
@ -175,7 +198,7 @@ describe('findFirstCategoryLink', () => {
|
||||||
|
|
||||||
it('works with category with link', () => {
|
it('works with category with link', () => {
|
||||||
expect(
|
expect(
|
||||||
findFirstCategoryLink(
|
findFirstSidebarItemLink(
|
||||||
testCategory({
|
testCategory({
|
||||||
href: '/itemPath',
|
href: '/itemPath',
|
||||||
}),
|
}),
|
||||||
|
@ -183,9 +206,95 @@ describe('findFirstCategoryLink', () => {
|
||||||
).toBe('/itemPath');
|
).toBe('/itemPath');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with category with deeply nested category link', () => {
|
it('works with deeply nested category', () => {
|
||||||
expect(
|
expect(
|
||||||
findFirstCategoryLink(
|
findFirstSidebarItemLink(
|
||||||
|
testCategory({
|
||||||
|
href: '/category1',
|
||||||
|
linkUnlisted: true,
|
||||||
|
items: [
|
||||||
|
{type: 'html', value: '<p>test1</p>'},
|
||||||
|
testCategory({
|
||||||
|
href: '/category2',
|
||||||
|
linkUnlisted: true,
|
||||||
|
items: [
|
||||||
|
{type: 'html', value: '<p>test2</p>'},
|
||||||
|
testCategory({
|
||||||
|
href: '/category3',
|
||||||
|
items: [
|
||||||
|
{type: 'html', value: '<p>test2</p>'},
|
||||||
|
testCategory({
|
||||||
|
href: '/category4',
|
||||||
|
linkUnlisted: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('/category3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with deeply nested link', () => {
|
||||||
|
expect(
|
||||||
|
findFirstSidebarItemLink(
|
||||||
|
testCategory({
|
||||||
|
href: '/category1',
|
||||||
|
linkUnlisted: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/itemPathUnlisted',
|
||||||
|
label: 'Label',
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
testCategory({
|
||||||
|
href: '/category2',
|
||||||
|
linkUnlisted: true,
|
||||||
|
items: [
|
||||||
|
testCategory({
|
||||||
|
href: '/category3',
|
||||||
|
linkUnlisted: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/itemPathUnlisted2',
|
||||||
|
label: 'Label',
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
testCategory({
|
||||||
|
href: '/category4',
|
||||||
|
linkUnlisted: true,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/itemPathListed1',
|
||||||
|
label: 'Label',
|
||||||
|
},
|
||||||
|
testCategory({
|
||||||
|
href: '/category5',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/itemPathListed2',
|
||||||
|
label: 'Label',
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('/itemPathListed1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with category with deeply nested category link unlisted', () => {
|
||||||
|
expect(
|
||||||
|
findFirstSidebarItemLink(
|
||||||
testCategory({
|
testCategory({
|
||||||
href: undefined,
|
href: undefined,
|
||||||
items: [
|
items: [
|
||||||
|
@ -196,29 +305,37 @@ describe('findFirstCategoryLink', () => {
|
||||||
{type: 'html', value: '<p>test2</p>'},
|
{type: 'html', value: '<p>test2</p>'},
|
||||||
testCategory({
|
testCategory({
|
||||||
href: '/itemPath',
|
href: '/itemPath',
|
||||||
|
linkUnlisted: true,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe('/itemPath');
|
).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with category with deeply nested link', () => {
|
it('works with category with deeply nested link unlisted', () => {
|
||||||
expect(
|
expect(
|
||||||
findFirstCategoryLink(
|
findFirstSidebarItemLink(
|
||||||
testCategory({
|
testCategory({
|
||||||
href: undefined,
|
href: undefined,
|
||||||
items: [
|
items: [
|
||||||
testCategory({
|
testCategory({
|
||||||
href: undefined,
|
href: undefined,
|
||||||
items: [{type: 'link', href: '/itemPath', label: 'Label'}],
|
items: [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: '/itemPath',
|
||||||
|
label: 'Label',
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBe('/itemPath');
|
).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -79,27 +79,38 @@ export function findSidebarCategory(
|
||||||
* Best effort to assign a link to a sidebar category. If the category doesn't
|
* Best effort to assign a link to a sidebar category. If the category doesn't
|
||||||
* have a link itself, we link to the first sub item with a link.
|
* have a link itself, we link to the first sub item with a link.
|
||||||
*/
|
*/
|
||||||
export function findFirstCategoryLink(
|
export function findFirstSidebarItemCategoryLink(
|
||||||
item: PropSidebarItemCategory,
|
item: PropSidebarItemCategory,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (item.href) {
|
if (item.href && !item.linkUnlisted) {
|
||||||
return item.href;
|
return item.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const subItem of item.items) {
|
for (const subItem of item.items) {
|
||||||
if (subItem.type === 'link') {
|
const link = findFirstSidebarItemLink(subItem);
|
||||||
return subItem.href;
|
if (link) {
|
||||||
} else if (subItem.type === 'category') {
|
return link;
|
||||||
const categoryLink = findFirstCategoryLink(subItem);
|
|
||||||
if (categoryLink) {
|
|
||||||
return categoryLink;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Could be "html" items
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Best effort to assign a link to a sidebar item.
|
||||||
|
*/
|
||||||
|
export function findFirstSidebarItemLink(
|
||||||
|
item: PropSidebarItem,
|
||||||
|
): string | undefined {
|
||||||
|
if (item.type === 'link' && !item.unlisted) {
|
||||||
|
return item.href;
|
||||||
|
}
|
||||||
|
if (item.type === 'category') {
|
||||||
|
return findFirstSidebarItemCategoryLink(item);
|
||||||
|
}
|
||||||
|
// Other items types, like "html"
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the category associated with the current location. Should only be used
|
* Gets the category associated with the current location. Should only be used
|
||||||
* on category index pages.
|
* on category index pages.
|
||||||
|
@ -391,15 +402,16 @@ export function useDocRootMetadata({route}: DocRootProps): null | {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter categories that don't have a link.
|
* Filter items we don't want to display on the doc card list view
|
||||||
* @param items
|
* @param items
|
||||||
*/
|
*/
|
||||||
export function filterDocCardListItems(
|
export function filterDocCardListItems(
|
||||||
items: PropSidebarItem[],
|
items: PropSidebarItem[],
|
||||||
): PropSidebarItem[] {
|
): PropSidebarItem[] {
|
||||||
return items.filter((item) => {
|
return items.filter((item) => {
|
||||||
if (item.type === 'category') {
|
const canHaveLink = item.type === 'category' || item.type === 'link';
|
||||||
return !!findFirstCategoryLink(item);
|
if (canHaveLink) {
|
||||||
|
return !!findFirstSidebarItemLink(item);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,3 +24,11 @@ In production, unlisted items should remain accessible, but be hidden in the sid
|
||||||
- [./some-unlisteds/unlisted1.md](./some-unlisteds/unlisted1.md)
|
- [./some-unlisteds/unlisted1.md](./some-unlisteds/unlisted1.md)
|
||||||
- [./some-unlisteds/unlisted2.md](./some-unlisteds/unlisted2.md)
|
- [./some-unlisteds/unlisted2.md](./some-unlisteds/unlisted2.md)
|
||||||
- [./some-unlisteds/unlisted-subcategory/unlisted3.md](./some-unlisteds/unlisted-subcategory/unlisted3.md)
|
- [./some-unlisteds/unlisted-subcategory/unlisted3.md](./some-unlisteds/unlisted-subcategory/unlisted3.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
|
import DocCardList from '@theme/DocCardList';
|
||||||
|
|
||||||
|
<DocCardList />
|
||||||
|
```
|
||||||
|
|
|
@ -6,3 +6,9 @@ tags: [visibility, draft]
|
||||||
# Only Drafts - Subcategory index draft
|
# Only Drafts - Subcategory index draft
|
||||||
|
|
||||||
Doc with draft front matter
|
Doc with draft front matter
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
|
import DocCardList from '@theme/DocCardList';
|
||||||
|
|
||||||
|
<DocCardList />
|
||||||
|
```
|
||||||
|
|
|
@ -6,3 +6,9 @@ tags: [visibility, unlisted]
|
||||||
# Only Unlisteds - Subcategory index unlisted
|
# Only Unlisteds - Subcategory index unlisted
|
||||||
|
|
||||||
Doc with unlisted front matter
|
Doc with unlisted front matter
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
|
import DocCardList from '@theme/DocCardList';
|
||||||
|
|
||||||
|
<DocCardList />
|
||||||
|
```
|
||||||
|
|
|
@ -6,3 +6,9 @@ tags: [visibility, draft]
|
||||||
# Some Drafts - Subcategory index draft
|
# Some Drafts - Subcategory index draft
|
||||||
|
|
||||||
Doc with draft front matter
|
Doc with draft front matter
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
|
import DocCardList from '@theme/DocCardList';
|
||||||
|
|
||||||
|
<DocCardList />
|
||||||
|
```
|
||||||
|
|
|
@ -6,3 +6,9 @@ tags: [visibility, unlisted]
|
||||||
# Some Unlisteds - Subcategory index unlisted
|
# Some Unlisteds - Subcategory index unlisted
|
||||||
|
|
||||||
Doc with unlisted front matter
|
Doc with unlisted front matter
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
|
import DocCardList from '@theme/DocCardList';
|
||||||
|
|
||||||
|
<DocCardList />
|
||||||
|
```
|
||||||
|
|
Loading…
Add table
Reference in a new issue