From 19ab890d8b9bb08ecb512a5703be09b8485f0bcf Mon Sep 17 00:00:00 2001 From: SoonIter Date: Thu, 6 Feb 2025 17:31:48 +0800 Subject: [PATCH 1/2] chore: fix the bug --- .../src/components/Sidebar/SidebarItem.tsx | 3 +- .../src/components/Sidebar/index.tsx | 174 +++++++++-------- .../src/components/Sidebar/utils.ts | 25 ++- .../theme-default/src/components/index.tsx | 2 +- .../theme-default/src/logic/useSidebarData.ts | 178 +++++++----------- 5 files changed, 198 insertions(+), 184 deletions(-) diff --git a/packages/theme-default/src/components/Sidebar/SidebarItem.tsx b/packages/theme-default/src/components/Sidebar/SidebarItem.tsx index 7eea0183f..857ee5340 100644 --- a/packages/theme-default/src/components/Sidebar/SidebarItem.tsx +++ b/packages/theme-default/src/components/Sidebar/SidebarItem.tsx @@ -9,6 +9,7 @@ import { preloadLink } from './utils'; export function SidebarItem(props: SidebarItemProps) { const { item, depth = 0, activeMatcher, id, setSidebarData } = props; + const active = 'link' in item && item.link && activeMatcher(item.link); const ref = useRef(null); useEffect(() => { @@ -23,10 +24,10 @@ export function SidebarItem(props: SidebarItemProps) { return ( diff --git a/packages/theme-default/src/components/Sidebar/index.tsx b/packages/theme-default/src/components/Sidebar/index.tsx index 26b7bea97..384c8876a 100644 --- a/packages/theme-default/src/components/Sidebar/index.tsx +++ b/packages/theme-default/src/components/Sidebar/index.tsx @@ -1,15 +1,13 @@ -import { matchRoutes, removeBase, useLocation } from '@rspress/runtime'; +import { useLocation } from '@rspress/runtime'; import { type SidebarDivider as ISidebarDivider, type SidebarItem as ISidebarItem, type SidebarSectionHeader as ISidebarSectionHeader, type NormalizedSidebarGroup, inBrowser, - normalizeSlash, } from '@rspress/shared'; import { useEffect, useState } from 'react'; -import { routes } from 'virtual-routes'; -import { isActive, useLocaleSiteData, useSidebarData } from '../../logic'; +import { useSidebarData } from '../../logic'; import type { UISwitchResult } from '../../logic/useUISwitch'; import { NavBarTitle } from '../Nav/NavBarTitle'; @@ -22,6 +20,7 @@ import { isSideBarCustomLink, isSidebarDivider, isSidebarSectionHeader, + useActiveMatcher, } from './utils'; export interface SidebarItemProps { @@ -45,7 +44,11 @@ interface Props { navTitle?: React.ReactNode; } -type SidebarData = (ISidebarDivider | ISidebarItem | NormalizedSidebarGroup)[]; +export type SidebarData = ( + | ISidebarDivider + | ISidebarItem + | NormalizedSidebarGroup +)[]; export const highlightTitleStyle = { fontSize: '14px', @@ -72,9 +75,9 @@ export function Sidebar(props: Props) { return rawSidebarData.filter(Boolean).flat(); }); - const localesData = useLocaleSiteData(); const pathname = decodeURIComponent(rawPathname); - const langRoutePrefix = normalizeSlash(localesData.langRoutePrefix || ''); + + const activeMatcher = useActiveMatcher(); useEffect(() => { if (inBrowser()) { @@ -133,73 +136,6 @@ export function Sidebar(props: Props) { setSidebarData(newSidebarData); }, [rawSidebarData, pathname]); - const removeLangPrefix = (path: string) => { - return path.replace(langRoutePrefix, ''); - }; - const activeMatcher = (path: string) => { - return isActive( - removeBase(removeLangPrefix(pathname)), - removeLangPrefix(path), - true, - ); - }; - - const renderItem = ( - item: - | NormalizedSidebarGroup - | ISidebarItem - | ISidebarDivider - | ISidebarSectionHeader, - index: number, - ) => { - if (isSidebarDivider(item)) { - return ( - - ); - } - - if (isSidebarSectionHeader(item)) { - return ( - - ); - } - - if (isSideBarCustomLink(item)) { - return ( -
- -
- ); - } - - return ( - - ); - }; return ( ); } + +export function SidebarList({ + sidebarData, + setSidebarData, +}: { + sidebarData: SidebarData; + setSidebarData: React.Dispatch>; +}) { + const activeMatcher = useActiveMatcher(); + return ( + <> + {sidebarData.map((item, index) => { + return ( + + ); + })} + + ); +} + +function SidebarListItem(props: { + item: + | NormalizedSidebarGroup + | ISidebarItem + | ISidebarDivider + | ISidebarSectionHeader; + index: number; + setSidebarData: React.Dispatch>; + activeMatcher: (link: string) => boolean; +}) { + const { item, index, setSidebarData, activeMatcher } = props; + if (isSidebarDivider(item)) { + return ( + + ); + } + + if (isSidebarSectionHeader(item)) { + return ( + + ); + } + + if (isSideBarCustomLink(item)) { + return ( +
+ +
+ ); + } + + return ( + + ); +} diff --git a/packages/theme-default/src/components/Sidebar/utils.ts b/packages/theme-default/src/components/Sidebar/utils.ts index 719f4ad67..58d4e3470 100644 --- a/packages/theme-default/src/components/Sidebar/utils.ts +++ b/packages/theme-default/src/components/Sidebar/utils.ts @@ -1,12 +1,14 @@ -import { matchRoutes } from '@rspress/runtime'; +import { matchRoutes, removeBase, useLocation } from '@rspress/runtime'; import { type SidebarDivider as ISidebarDivider, type SidebarItem as ISidebarItem, type SidebarSectionHeader as ISidebarSectionHeader, type NormalizedSidebarGroup, isExternalUrl, + normalizeSlash, } from '@rspress/shared'; import { routes } from 'virtual-routes'; +import { isActive, useLocaleSiteData } from '../../logic'; export const isSidebarDivider = ( item: @@ -49,3 +51,24 @@ export const preloadLink = (link: string) => { route.preload(); } }; + +export const useActiveMatcher = () => { + const localesData = useLocaleSiteData(); + const langRoutePrefix = normalizeSlash(localesData.langRoutePrefix || ''); + + const { pathname: rawPathname } = useLocation(); + + const pathname = decodeURIComponent(rawPathname); + const removeLangPrefix = (path: string) => { + return path.replace(langRoutePrefix, ''); + }; + const activeMatcher = (link: string) => { + return isActive( + removeBase(removeLangPrefix(pathname)), + removeLangPrefix(link), + true, + ); + }; + + return activeMatcher; +}; diff --git a/packages/theme-default/src/components/index.tsx b/packages/theme-default/src/components/index.tsx index 3db1ea6d6..aab626660 100644 --- a/packages/theme-default/src/components/index.tsx +++ b/packages/theme-default/src/components/index.tsx @@ -16,7 +16,7 @@ export { PackageManagerTabs } from './PackageManagerTabs'; export { PrevNextPage } from './PrevNextPage'; export { ScrollToTop } from './ScrollToTop'; export { Search, SearchPanel } from './Search'; -export { Sidebar } from './Sidebar'; +export { Sidebar, SidebarList, type SidebarData } from './Sidebar'; export { SocialLinks } from './SocialLinks'; export { SourceCode } from './SourceCode'; export { Steps } from './Steps'; diff --git a/packages/theme-default/src/logic/useSidebarData.ts b/packages/theme-default/src/logic/useSidebarData.ts index 9d393254e..761a6531a 100644 --- a/packages/theme-default/src/logic/useSidebarData.ts +++ b/packages/theme-default/src/logic/useSidebarData.ts @@ -6,7 +6,7 @@ import { type SidebarItem, addTrailingSlash, } from '@rspress/shared'; -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; import { useLocaleSiteData } from './useLocaleSiteData'; interface SidebarData { @@ -15,111 +15,81 @@ interface SidebarData { items: (NormalizedSidebarGroup | SidebarItem | SidebarDivider)[]; } -export const getSidebarGroupData = ( - sidebar: NormalizedSidebar, +/** + * @param pattern /zh/guide + * @param currentPathname /base/zh/guide/getting-started + */ +export const matchPath = ( + pattern: string, currentPathname: string, -) => { - let detectedGroupName; - for (const name of Object.keys(sidebar)) { - if (detectedGroupName && detectedGroupName !== name) { - continue; - } - if (isEqualPath(withBase(name), currentPathname)) { - // Such as `/api/`,it will return all the sidebar group - return { - group: 'Documentation', - items: sidebar[name], - }; - } - // Such as `/guide/getting-started`, it will return the guide groups and the group name `Introduction` - // eslint-disable-next-line @typescript-eslint/no-loop-func - const result = sidebar[name].find(group => { - const match = ( - item: NormalizedSidebarGroup | SidebarItem | SidebarDivider, - ): boolean => { - // Fix https://github.com/web-infra-dev/rspress/issues/241 - // For example, there is the following sidebar: - // { - // '/guide/': [ - // { - // text: 'Introduction', - // link: '/misc/team', - // }, - // { - // text: 'Getting Started', - // link: '/xyz/getting-started', - // }, - // ], - // '/misc/': [ - // { - // ... - // } - // ] - // } - // The /misc/team will match the /misc/ group instead of the /guide/ group - // However, if the current path is /xyz/getting-started, it will match the /guide/ group because there isn't any other group that matches the current path - if (!currentPathname.startsWith(withBase(name))) { - for (const otherGroupName of Object.keys(sidebar)) { - if ( - otherGroupName !== name && - currentPathname.startsWith( - // https://github.com/web-infra-dev/rspress/issues/360 - // Ensure the other group name ends with `/` to avoid some unexpected results, for example, `/react-native` will match `/react`, that's not what we want - // FIXME: should parse url instead of add trailing slash - addTrailingSlash(withBase(otherGroupName)), - ) - ) { - // Performance optimization, once we find the other group name, we can skip the other group in the future loops - detectedGroupName = otherGroupName; - return false; - } - } - } - - const isLink = 'link' in item && item.link !== ''; - const isDir = 'items' in item; +): boolean => { + const prefix = withBase(pattern); + if (prefix === currentPathname) { + return true; + } + const prefixWithTrailingSlash = addTrailingSlash(prefix); + return currentPathname.startsWith(prefixWithTrailingSlash); +}; - // 0. divider or section headers others return false +const match = ( + item: NormalizedSidebarGroup | SidebarItem | SidebarDivider, + currentPathname: string, +): NormalizedSidebarGroup | SidebarItem | undefined => { + const isLink = 'link' in item && item.link !== ''; + const isDir = 'items' in item; - // 1. file link - if (!isDir && isLink) { - // 1.1 /api/config /api/config.html - if (isEqualPath(withBase(item.link), currentPathname)) { - return true; - } - // 1.2 /api/config/index /api/config/index.html - if ( - currentPathname.includes('index') && - isEqualPath(`${item.link}/index`, currentPathname) - ) { - return true; - } - } + // 0. divider or section headers others return false - // 2. dir - if (isDir) { - // 2.1 dir link (index convention) - if ( - isLink && - (isEqualPath(withBase(item.link), currentPathname) || - isEqualPath(withBase(`${item.link}/index`), currentPathname)) - ) { - return true; - } - // 2.2 dir recursive - return item.items.some(i => match(i)); - } + // 1. file link + if (!isDir && isLink) { + // 1.1 /api/config /api/config.html + if (isEqualPath(withBase(item.link), currentPathname)) { + return item; + } + // 1.2 /api/config/index /api/config/index.html + if ( + currentPathname.includes('index') && + isEqualPath(`${item.link}/index`, currentPathname) + ) { + return item; + } + } - return false; - }; + // 2. dir + if (isDir) { + // 2.1 dir link (index convention) + if ( + isLink && + (isEqualPath(withBase(item.link), currentPathname) || + isEqualPath(withBase(`${item.link}/index`), currentPathname)) + ) { + return item; + } + // 2.2 dir recursive + for (const childItem of item.items) { + const matched = match(childItem, currentPathname); + if (matched) { + return matched; + } + } + } - return match(group); - }); + return undefined; +}; - if (result) { +export const getSidebarGroupData = ( + sidebar: NormalizedSidebar, + currentPathname: string, +) => { + console.log(sidebar, 111111); + for (const name of Object.keys(sidebar)) { + const isMatch = matchPath(name, currentPathname); + console.log(isMatch, name, currentPathname); + if (isMatch) { const sidebarGroup = sidebar[name]; + const group = sidebarGroup.find(item => match(item, currentPathname)); return { - group: ('text' in result && result.text) || '', + group: group && 'text' in group ? group.text : '', items: sidebarGroup, }; } @@ -130,17 +100,13 @@ export const getSidebarGroupData = ( }; }; export function useSidebarData(): SidebarData { - const localeData = useLocaleSiteData(); - const sidebar = localeData.sidebar ?? {}; + const { sidebar } = useLocaleSiteData(); const { pathname: rawPathname } = useLocation(); const pathname = decodeURIComponent(rawPathname); - const [sidebarData, setSidebarData] = useState( - getSidebarGroupData(sidebar, pathname), - ); - useEffect(() => { - const newSidebarData = getSidebarGroupData(sidebar, pathname); - setSidebarData(newSidebarData); - }, [pathname, localeData.lang]); + + const sidebarData = useMemo(() => { + return getSidebarGroupData(sidebar, pathname); + }, [sidebar, pathname]); return sidebarData; } From dafb8df15b5404f0a598793a265409c58a3af2aa Mon Sep 17 00:00:00 2001 From: SoonIter Date: Thu, 6 Feb 2025 17:39:21 +0800 Subject: [PATCH 2/2] chore: update --- .../src/components/Search/SearchPanel.tsx | 4 ++-- packages/theme-default/src/logic/useFullTextSearch.ts | 4 ++-- packages/theme-default/src/logic/useSidebarData.ts | 11 ++++------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/theme-default/src/components/Search/SearchPanel.tsx b/packages/theme-default/src/components/Search/SearchPanel.tsx index bb94f3810..f7c07169d 100644 --- a/packages/theme-default/src/components/Search/SearchPanel.tsx +++ b/packages/theme-default/src/components/Search/SearchPanel.tsx @@ -8,7 +8,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import * as userSearchHooks from 'virtual-search-hooks'; import { useLocaleSiteData } from '../../logic/useLocaleSiteData'; -import { getSidebarGroupData } from '../../logic/useSidebarData'; +import { getSidebarData } from '../../logic/useSidebarData'; import { SvgWrapper } from '../SvgWrapper'; import { Tab, Tabs } from '../Tabs'; import { NoSearchResult } from './NoSearchResult'; @@ -123,7 +123,7 @@ export function SearchPanel({ focused, setFocused }: SearchPanelProps) { // We need to extract the group name by the link so that we can divide the search result into different groups. const extractGroupName = (link: string) => - getSidebarGroupData(sidebar, link).group; + getSidebarData(sidebar, link).group; async function initPageSearcher() { if (search === false) { diff --git a/packages/theme-default/src/logic/useFullTextSearch.ts b/packages/theme-default/src/logic/useFullTextSearch.ts index eb492b76c..18a8d6318 100644 --- a/packages/theme-default/src/logic/useFullTextSearch.ts +++ b/packages/theme-default/src/logic/useFullTextSearch.ts @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'; import type { MatchResult } from '..'; import { PageSearcher } from '../components/Search/logic/search'; import { useLocaleSiteData } from './useLocaleSiteData'; -import { getSidebarGroupData } from './useSidebarData'; +import { getSidebarData } from './useSidebarData'; export function useFullTextSearch(): { initialized: boolean; @@ -13,7 +13,7 @@ export function useFullTextSearch(): { const [initialized, setInitialized] = useState(false); const { sidebar } = useLocaleSiteData(); const extractGroupName = (link: string) => - getSidebarGroupData(sidebar, link).group; + getSidebarData(sidebar, link).group; const searchRef = useRef(null); useEffect(() => { diff --git a/packages/theme-default/src/logic/useSidebarData.ts b/packages/theme-default/src/logic/useSidebarData.ts index 761a6531a..57bced09a 100644 --- a/packages/theme-default/src/logic/useSidebarData.ts +++ b/packages/theme-default/src/logic/useSidebarData.ts @@ -77,15 +77,12 @@ const match = ( return undefined; }; -export const getSidebarGroupData = ( +export const getSidebarData = ( sidebar: NormalizedSidebar, currentPathname: string, -) => { - console.log(sidebar, 111111); +): SidebarData => { for (const name of Object.keys(sidebar)) { - const isMatch = matchPath(name, currentPathname); - console.log(isMatch, name, currentPathname); - if (isMatch) { + if (matchPath(name, currentPathname)) { const sidebarGroup = sidebar[name]; const group = sidebarGroup.find(item => match(item, currentPathname)); return { @@ -105,7 +102,7 @@ export function useSidebarData(): SidebarData { const pathname = decodeURIComponent(rawPathname); const sidebarData = useMemo(() => { - return getSidebarGroupData(sidebar, pathname); + return getSidebarData(sidebar, pathname); }, [sidebar, pathname]); return sidebarData;