/** * 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 { useAllDocsData, useActivePlugin, } from '@docusaurus/plugin-content-docs/client'; import type { PropSidebar, PropSidebarItem, PropSidebarItemCategory, PropVersionDoc, PropSidebarBreadcrumbsItem, } from '@docusaurus/plugin-content-docs'; import {useDocsVersion} from '../contexts/docsVersion'; import {useDocsSidebar} from '../contexts/docsSidebar'; import {isSamePath} from './routesUtils'; import {useLocation} from '@docusaurus/router'; // TODO not ideal, see also "useDocs" export const isDocsPluginEnabled: boolean = !!useAllDocsData; /** * A null-safe way to access a doc's data by ID in the active version. */ export function useDocById(id: string): PropVersionDoc; /** * A null-safe way to access a doc's data by ID in the active version. */ export function useDocById(id: string | undefined): PropVersionDoc | undefined; export function useDocById(id: string | undefined): PropVersionDoc | undefined { const version = useDocsVersion(); if (!id) { return undefined; } const doc = version.docs[id]; if (!doc) { throw new Error(`no version doc found by id=${id}`); } return doc; } /** * Pure function, similar to `Array#find`, but works on the sidebar tree. */ export function findSidebarCategory( sidebar: PropSidebar, predicate: (category: PropSidebarItemCategory) => boolean, ): PropSidebarItemCategory | undefined { for (const item of sidebar) { if (item.type === 'category') { if (predicate(item)) { return item; } const subItem = findSidebarCategory(item.items, predicate); if (subItem) { return subItem; } } } return undefined; } /** * 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( item: PropSidebarItemCategory, ): string | undefined { if (item.href) { 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; } } else if (subItem.type === 'html') { // skip } else { throw new Error( `Unexpected category item type for ${JSON.stringify(subItem)}`, ); } } return undefined; } /** * Gets the category associated with the current location. Should only be used * on category index pages. */ export function useCurrentSidebarCategory(): PropSidebarItemCategory { const {pathname} = useLocation(); const sidebar = useDocsSidebar(); if (!sidebar) { throw new Error('Unexpected: cant find current sidebar in context'); } const category = findSidebarCategory(sidebar.items, (item) => isSamePath(item.href, pathname), ); if (!category) { throw new Error( `${pathname} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`, ); } return category; } const isActive = (testedPath: string | undefined, activePath: string) => typeof testedPath !== 'undefined' && isSamePath(testedPath, activePath); const containsActiveSidebarItem = ( items: PropSidebarItem[], activePath: string, ) => items.some((subItem) => isActiveSidebarItem(subItem, activePath)); /** * Checks if a sidebar item should be active, based on the active path. */ export function isActiveSidebarItem( item: PropSidebarItem, activePath: string, ): boolean { if (item.type === 'link') { return isActive(item.href, activePath); } if (item.type === 'category') { return ( isActive(item.href, activePath) || containsActiveSidebarItem(item.items, activePath) ); } return false; } /** * Gets the breadcrumbs of the current doc page, based on its sidebar location. * Returns `null` if there's no sidebar or breadcrumbs are disabled. */ export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null { const sidebar = useDocsSidebar(); const {pathname} = useLocation(); const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs; if (breadcrumbsOption === false || !sidebar) { return null; } const breadcrumbs: PropSidebarBreadcrumbsItem[] = []; function extract(items: PropSidebar) { for (const item of items) { if ( (item.type === 'category' && (isSamePath(item.href, pathname) || extract(item.items))) || (item.type === 'link' && isSamePath(item.href, pathname)) ) { breadcrumbs.push(item); return true; } } return false; } extract(sidebar.items); return breadcrumbs.reverse(); }