diff --git a/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx index db0bda743570..90a5a3121da0 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx @@ -9,7 +9,7 @@ import React, {type ReactNode} from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; import { - findFirstCategoryLink, + findFirstSidebarItemLink, useDocById, } from '@docusaurus/theme-common/internal'; import isInternalUrl from '@docusaurus/isInternalUrl'; @@ -71,7 +71,7 @@ function CardCategory({ }: { item: PropSidebarItemCategory; }): JSX.Element | null { - const href = findFirstCategoryLink(item); + const href = findFirstSidebarItemLink(item); // Unexpected: categories that don't have a link have been filtered upfront if (!href) { diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category/index.tsx index 996720e70941..a3bb2f05d355 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category/index.tsx @@ -16,7 +16,7 @@ import { } from '@docusaurus/theme-common'; import { isActiveSidebarItem, - findFirstCategoryLink, + findFirstSidebarItemLink, useDocSidebarItemsExpandedState, isSamePath, } from '@docusaurus/theme-common/internal'; @@ -67,7 +67,7 @@ function useCategoryHrefWithSSRFallback( if (isBrowser || !item.collapsible) { return undefined; } - return findFirstCategoryLink(item); + return findFirstSidebarItemLink(item); }, [item, isBrowser]); } diff --git a/packages/docusaurus-theme-common/src/internal.ts b/packages/docusaurus-theme-common/src/internal.ts index 7e3c4a73b4f7..4eaf6eaa3300 100644 --- a/packages/docusaurus-theme-common/src/internal.ts +++ b/packages/docusaurus-theme-common/src/internal.ts @@ -67,7 +67,7 @@ export { isDocsPluginEnabled, useDocById, findSidebarCategory, - findFirstCategoryLink, + findFirstSidebarItemLink, isActiveSidebarItem, isVisibleSidebarItem, useVisibleSidebarItems, diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx index 30ff811d827d..88b10b6f8566 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx +++ b/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx @@ -10,7 +10,7 @@ import {renderHook} from '@testing-library/react-hooks'; import {StaticRouter} from 'react-router-dom'; import {Context} from '@docusaurus/core/src/client/docusaurusContext'; import { - findFirstCategoryLink, + findFirstSidebarItemLink, isActiveSidebarItem, useDocById, findSidebarCategory, @@ -64,6 +64,7 @@ function testVersion(data?: Partial): PropVersionMetadata { docsSidebars: {}, isLast: false, pluginId: 'default', + noIndex: false, ...data, }; } @@ -163,9 +164,31 @@ describe('findSidebarCategory', () => { }); describe('findFirstCategoryLink', () => { + it('works with html item', () => { + const htmlItem = {type: 'html', value: '
'} 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', () => { expect( - findFirstCategoryLink( + findFirstSidebarItemLink( testCategory({ href: undefined, }), @@ -175,7 +198,7 @@ describe('findFirstCategoryLink', () => { it('works with category with link', () => { expect( - findFirstCategoryLink( + findFirstSidebarItemLink( testCategory({ href: '/itemPath', }), @@ -183,9 +206,95 @@ describe('findFirstCategoryLink', () => { ).toBe('/itemPath'); }); - it('works with category with deeply nested category link', () => { + it('works with deeply nested category', () => { + expect( + findFirstSidebarItemLink( + testCategory({ + href: '/category1', + linkUnlisted: true, + items: [ + {type: 'html', value: '

test1

'}, + testCategory({ + href: '/category2', + linkUnlisted: true, + items: [ + {type: 'html', value: '

test2

'}, + testCategory({ + href: '/category3', + items: [ + {type: 'html', value: '

test2

'}, + testCategory({ + href: '/category4', + linkUnlisted: true, + }), + ], + }), + ], + }), + ], + }), + ), + ).toBe('/category3'); + }); + + it('works with deeply nested link', () => { expect( - findFirstCategoryLink( + 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({ href: undefined, items: [ @@ -196,29 +305,37 @@ describe('findFirstCategoryLink', () => { {type: 'html', value: '

test2

'}, testCategory({ 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( - findFirstCategoryLink( + findFirstSidebarItemLink( testCategory({ href: undefined, items: [ testCategory({ href: undefined, - items: [{type: 'link', href: '/itemPath', label: 'Label'}], + items: [ + { + type: 'link', + href: '/itemPath', + label: 'Label', + unlisted: true, + }, + ], }), ], }), ), - ).toBe('/itemPath'); + ).toBeUndefined(); }); }); diff --git a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx index 0a628b257ab8..67b27265969b 100644 --- a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx @@ -79,27 +79,38 @@ export function findSidebarCategory( * 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. */ -export function findFirstCategoryLink( +export function findFirstSidebarItemCategoryLink( item: PropSidebarItemCategory, ): string | undefined { - if (item.href) { + if (item.href && !item.linkUnlisted) { return item.href; } for (const subItem of item.items) { - if (subItem.type === 'link') { - return subItem.href; - } else if (subItem.type === 'category') { - const categoryLink = findFirstCategoryLink(subItem); - if (categoryLink) { - return categoryLink; - } + const link = findFirstSidebarItemLink(subItem); + if (link) { + return link; } - // Could be "html" items } 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 * 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 */ export function filterDocCardListItems( items: PropSidebarItem[], ): PropSidebarItem[] { return items.filter((item) => { - if (item.type === 'category') { - return !!findFirstCategoryLink(item); + const canHaveLink = item.type === 'category' || item.type === 'link'; + if (canHaveLink) { + return !!findFirstSidebarItemLink(item); } return true; }); diff --git a/website/_dogfooding/_docs tests/tests/visibility/index.md b/website/_dogfooding/_docs tests/tests/visibility/index.md index a0b92f20cc8b..2b48428b4c90 100644 --- a/website/_dogfooding/_docs tests/tests/visibility/index.md +++ b/website/_dogfooding/_docs tests/tests/visibility/index.md @@ -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/unlisted2.md](./some-unlisteds/unlisted2.md) - [./some-unlisteds/unlisted-subcategory/unlisted3.md](./some-unlisteds/unlisted-subcategory/unlisted3.md) + +--- + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; + + +``` diff --git a/website/_dogfooding/_docs tests/tests/visibility/only-drafts/draft-subcategory/index.md b/website/_dogfooding/_docs tests/tests/visibility/only-drafts/draft-subcategory/index.md index d22fe8149241..be3f16ee0a53 100644 --- a/website/_dogfooding/_docs tests/tests/visibility/only-drafts/draft-subcategory/index.md +++ b/website/_dogfooding/_docs tests/tests/visibility/only-drafts/draft-subcategory/index.md @@ -6,3 +6,9 @@ tags: [visibility, draft] # Only Drafts - Subcategory index draft Doc with draft front matter + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; + + +``` diff --git a/website/_dogfooding/_docs tests/tests/visibility/only-unlisteds/unlisted-subcategory/index.md b/website/_dogfooding/_docs tests/tests/visibility/only-unlisteds/unlisted-subcategory/index.md index f179ed324ada..fd1ebdea290d 100644 --- a/website/_dogfooding/_docs tests/tests/visibility/only-unlisteds/unlisted-subcategory/index.md +++ b/website/_dogfooding/_docs tests/tests/visibility/only-unlisteds/unlisted-subcategory/index.md @@ -6,3 +6,9 @@ tags: [visibility, unlisted] # Only Unlisteds - Subcategory index unlisted Doc with unlisted front matter + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; + + +``` diff --git a/website/_dogfooding/_docs tests/tests/visibility/some-drafts/draft-subcategory/index.md b/website/_dogfooding/_docs tests/tests/visibility/some-drafts/draft-subcategory/index.md index 8ead97125412..1ab23fe816a4 100644 --- a/website/_dogfooding/_docs tests/tests/visibility/some-drafts/draft-subcategory/index.md +++ b/website/_dogfooding/_docs tests/tests/visibility/some-drafts/draft-subcategory/index.md @@ -6,3 +6,9 @@ tags: [visibility, draft] # Some Drafts - Subcategory index draft Doc with draft front matter + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; + + +``` diff --git a/website/_dogfooding/_docs tests/tests/visibility/some-unlisteds/unlisted-subcategory/index.md b/website/_dogfooding/_docs tests/tests/visibility/some-unlisteds/unlisted-subcategory/index.md index ea122bdc09fd..31f24b59c698 100644 --- a/website/_dogfooding/_docs tests/tests/visibility/some-unlisteds/unlisted-subcategory/index.md +++ b/website/_dogfooding/_docs tests/tests/visibility/some-unlisteds/unlisted-subcategory/index.md @@ -6,3 +6,9 @@ tags: [visibility, unlisted] # Some Unlisteds - Subcategory index unlisted Doc with unlisted front matter + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; + + +```