import type { MenuProps } from 'antd'; import { Tag } from 'antd'; import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { Link, useFullSidebarData, useLocation, useSidebarData, useLocale } from 'dumi'; import type { ReactNode } from 'react'; import { useMemo } from 'react'; import type { ISidebarGroupModePathItem, SidebarEnhanceGroupType, SidebarEnhanceItems, SidebarEnhanceItemType, SidebarEnhanceSubType, SidebarEnhanceType } from '../types'; import { removeTitleCode, handleFullSidebarData } from '../utils'; import useAdditionalThemeConfig from './useAdditionalThemeConfig'; import pkgJSON from '../../package.json'; export type UseMenuOptions = { before?: ReactNode; after?: ReactNode; }; const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] => { const { pathname, search } = useLocation(); const { sidebarGroupModePath, sidebarEnhance = {} } = useAdditionalThemeConfig(); const { before, after } = options; const fullSidebarData = useFullSidebarData(); const navSecondSidebarData = handleFullSidebarData(fullSidebarData); const locale = useLocale(); // 提取一级导航下侧边栏数据 const currentNavKey = `/${pathname.split('/')?.[1]}`; const sidebarData = navSecondSidebarData[currentNavKey]; const linkTitleMap = useMemo(() => { return Object.values(fullSidebarData).reduce>((res, sidebar) => { const sidebarItems = sidebar.map((item) => item.children).flat(); sidebarItems.forEach((item) => { res[item.link] = item.title; }); return res; }, {}); }, [fullSidebarData]); const currentSidebarEnhanceData = useMemo(() => { const currentLink = Object.keys(sidebarEnhance).find((link) => pathname.startsWith(link)); if (!currentLink) return undefined; return sidebarEnhance[currentLink]; }, [pathname, sidebarEnhance]); const sidebarEnhanceMenuItems = useMemo(() => { const isItemMenu = (v: any): v is SidebarEnhanceItemType => { return v && typeof v === 'object' && 'link' in v && 'title' in v; }; const isGroupMenu = (v: any): v is SidebarEnhanceGroupType => { return v && typeof v === 'object' && v.type === 'group'; }; const isSubMenu = (v: any): v is SidebarEnhanceSubType => { return v && typeof v === 'object' && 'children' in v; }; function processMenu(menu: SidebarEnhanceType): ItemType { if (typeof menu === 'string') { // menu: '/introduction' return { key: menu, label: ( {before} {linkTitleMap[menu]} {after} ) }; } if (isGroupMenu(menu)) { // menu: { type: 'group', title: '组件', children: ['/components/button', { title: 'Installation', children: ['/aaa'] }] } return { type: 'group', label: menu.title, key: menu.title, children: menu.children.map(processMenu) }; } if (isSubMenu(menu)) { return { key: menu.title, label: {menu.title}, children: menu.children.map(processMenu) }; } if (isItemMenu(menu)) { // { title: 'aaa', link: '/xxx' } return { label: ( {menu.title} ), key: menu.link }; } return null; } if (!currentSidebarEnhanceData) return undefined; return currentSidebarEnhanceData.map(processMenu); }, [after, before, currentSidebarEnhanceData, linkTitleMap, search]); const menuItems = useMemo(() => { const suffixRegExp = new RegExp(`${(locale as any)?.suffix ?? ''}$`, 'g'); const sidebarItems = [...(sidebarData ?? [])]; const getItemTag = (tag: string | { color: string; title: string }, show = true) => tag && show && ( {(typeof tag === 'string' ? tag : tag.title).replace('VERSION', `v${pkgJSON.version}`)} ); return ( sidebarItems?.reduce>( (result, group) => { if (group?.title) { // sideBar menu group 模式, 默认以非 group 模式渲染 const isSideBarGroupMode = sidebarGroupModePath === true ? true : (sidebarGroupModePath ?? []).filter((rule: ISidebarGroupModePathItem) => { return pathname.startsWith(rule); }).length > 0; if (isSideBarGroupMode) { result.push({ type: 'group', label: group?.title, order: group?.order, key: group?.title, children: group.children?.map((item) => ({ label: ( {before} {removeTitleCode(item?.title)} {removeTitleCode(item.frontmatter?.subtitle)} {getItemTag(item.frontmatter?.tag, !before && !after)} {after} ), key: item.link.replace(suffixRegExp, '') })) }); } else { const childrenResultTypeOrder = {}; const childrenGroup = group.children.reduce< Record[number]['children']> >((childrenResult, child) => { const nextChildrenResult = childrenResult; const childType = child?.frontmatter?.type; // 兼容 type 为字符串 && object,object 支持排序 const type = typeof childType === 'string' ? childType : childType?.title ?? 'default'; if (!nextChildrenResult[type]) { nextChildrenResult[type] = []; } if (!childrenResultTypeOrder[type]) { childrenResultTypeOrder[type] = { title: type, order: childType?.order ?? -1 }; } else if (childType?.order) { childrenResultTypeOrder[type].order = childType.order; } nextChildrenResult[type].push(child); return nextChildrenResult; }, {}); const childrenGroupOrdered = Object.keys(childrenGroup) .sort((a, b) => childrenResultTypeOrder[a].order - childrenResultTypeOrder[b].order) .reduce[number]['children']>>( (obj, key) => { const _obj = obj; _obj[key] = childrenGroup[key]; return _obj; }, {} ); const childItems: any[] = []; childItems.push( ...(childrenGroupOrdered.default?.map((item) => ({ label: ( {before} {removeTitleCode(item?.title)} {getItemTag(item.frontmatter?.tag, !before && !after)} {after} ), key: item.link.replace(suffixRegExp, '') })) ?? []) ); Object.entries(childrenGroupOrdered).forEach(([type, children]) => { if (type !== 'default') { childItems.push({ type: 'group', label: type, key: type, children: children?.map((item) => ({ label: ( {before} {removeTitleCode(item?.title)} {getItemTag(item.frontmatter?.tag, !before && !after)} {after} ), key: item.link.replace(suffixRegExp, '') })) }); } }); result.push({ label: group?.title, key: group?.title, order: group?.order, children: childItems }); } } else { const list = group.children || []; // 如果有 date 字段,我们就对其进行排序 if (list.every((info) => info?.frontmatter?.date)) { list.sort((a, b) => (a?.frontmatter?.date > b?.frontmatter?.date ? -1 : 1)); } result.push( ...list.map((item) => ({ order: item?.order, label: ( {before} {removeTitleCode(item?.title)} {getItemTag(item.frontmatter?.tag, !before && !after)} {after} ), key: item.link.replace(suffixRegExp, '') })) ); } // group 模式与 single 模式混合排序 result.sort((a, b) => (a?.order < b?.order ? -1 : 1)); return result; }, [] ) ?? [] ); }, [sidebarData, sidebarGroupModePath, pathname, search, before, after, locale]); const selectedKey = pathname.replace(new RegExp(`${(locale as any)?.suffix ?? ''}$`, 'g'), ''); return [sidebarEnhanceMenuItems || menuItems, selectedKey]; }; export default useMenu;