diff --git a/components/docsearch.tsx b/components/docsearch.tsx index 668281c..b02490c 100644 --- a/components/docsearch.tsx +++ b/components/docsearch.tsx @@ -2,6 +2,7 @@ // sample code from https://docsearch.algolia.com/docs/docsearch import { DocSearch } from '@docsearch/react'; +import { useThemeConfig } from 'nextra-theme-docs' import '@docsearch/css'; @@ -11,6 +12,7 @@ function AlgoliaSearch() { appId={process.env.NEXT_SEARCH_ALGOLIA_APP_ID || 'NKGLZZZUBC'} indexName={process.env.NEXT_SEARCH_ALGOLIA_INDEX_NAME || 'notenextra_trance_0'} apiKey={process.env.NEXT_SEARCH_ALGOLIA_API_KEY || '727b389a61e862e590dfab9ce9df31a2'} + theme={useThemeConfig().darkMode ? 'dark' : 'light'} /> ); } diff --git a/components/navbar.client.tsx b/components/navbar.client.tsx new file mode 100644 index 0000000..926f0b7 --- /dev/null +++ b/components/navbar.client.tsx @@ -0,0 +1,188 @@ +'use client' + +import { + MenuItem as _MenuItem, + Menu, + MenuButton, + MenuItems +} from '@headlessui/react' +import cn from 'clsx' +import { Anchor, Button } from 'nextra/components' +import { useFSRoute } from 'nextra/hooks' +import { ArrowRightIcon, MenuIcon } from 'nextra/icons' +import type { MenuItem } from 'nextra/normalize-pages' +import type { FC, ReactNode } from 'react' +import { setMenu, useConfig, useMenu, useThemeConfig } from 'nextra-theme-docs' +import { usePathname } from 'next/navigation' +import { normalizePages } from 'nextra/normalize-pages' +import { PageMapItem } from 'nextra' + +const classes = { + link: cn( + 'x:text-sm x:contrast-more:text-gray-700 x:contrast-more:dark:text-gray-100 x:whitespace-nowrap', + 'x:text-gray-600 x:hover:text-black x:dark:text-gray-400 x:dark:hover:text-gray-200', + 'x:ring-inset x:transition-colors' + ) +} + +const NavbarMenu: FC<{ + menu: MenuItem + children: ReactNode +}> = ({ menu, children }) => { + const routes = Object.fromEntries( + (menu.children || []).map(route => [route.name, route]) + ) + return ( + + + cn( + classes.link, + 'x:items-center x:flex x:gap-1.5 x:cursor-pointer', + focus && 'x:nextra-focus' + ) + } + > + {children} + + + + {Object.entries( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- fixme + (menu.items as Record) || {} + ).map(([key, item]) => ( + <_MenuItem + key={key} + as={Anchor} + href={item.href || routes[key]?.route} + className={({ focus }) => + cn( + 'x:block x:py-1.5 x:transition-colors x:ps-3 x:pe-9', + focus + ? 'x:text-gray-900 x:dark:text-gray-100' + : 'x:text-gray-600 x:dark:text-gray-400' + ) + } + > + {item.title} + + ))} + + + ) +} + +const isMenu = (page: any): page is MenuItem => page.type === 'menu' + +export const ClientNavbar: FC<{ + pageMap: PageMapItem[] + children: ReactNode + className?: string +}> = ({ pageMap, children, className }) => { + + const { topLevelNavbarItems } = normalizePages({ + list: pageMap, + route: usePathname() + }) + + // filter out titles for elements in topLevelNavbarItems with non empty route + const existingCourseNames = new Set( + topLevelNavbarItems.filter( + item => !('href' in item) + ).map(item => item.title) + ) + // filter out elements in topLevelNavbarItems with url but have title in existingCourseNames + const filteredTopLevelNavbarItems = topLevelNavbarItems.filter(item => !('href' in item && existingCourseNames.has(item.title))) + + // or even better, remove all old doc page generated links + // this don't make sense because it will destroy the navbar structure highlight for children pages + // const filteredTopLevelNavbarItems = topLevelNavbarItems.filter(item => !('firstChildRoute' in item)) + + // const items = topLevelNavbarItems + // use filteredTopLevelNavbarItems to generate items + const items = filteredTopLevelNavbarItems + + const themeConfig = useThemeConfig() + + const pathname = useFSRoute() + const menu = useMenu() + + return ( + <> +
+ {items.map((page, _index, arr) => { + if ('display' in page && page.display === 'hidden') return + if (isMenu(page)) { + return ( + + {page.title} + + ) + } + const href = + // If it's a directory + ('frontMatter' in page ? page.route : page.firstChildRoute) || + page.href || + page.route + + const isCurrentPage = + href === pathname || + (pathname.startsWith(page.route + '/') && + arr.every(item => !('href' in item) || item.href !== pathname)) || + undefined + + return ( + + {page.title} + + ) + })} +
+ {themeConfig.search && ( +
{themeConfig.search}
+ )} + + {children} + + + + ) +} \ No newline at end of file diff --git a/components/navbar.tsx b/components/navbar.tsx new file mode 100644 index 0000000..d63424b --- /dev/null +++ b/components/navbar.tsx @@ -0,0 +1,170 @@ +// customized navbar component, modified from https://github.com/shuding/nextra/blob/c8238813e1ba425cdd72783d57707b0ff3ca52ea/examples/custom-theme/app/_components/navbar.tsx#L9 + +// Rebuild from source code https://github.com/shuding/nextra/tree/c8238813e1ba425cdd72783d57707b0ff3ca52ea/packages/nextra-theme-docs/src/components/navbar + +'use client' + +import { usePathname } from 'next/navigation' +import type { PageMapItem } from 'nextra' +import { Anchor } from 'nextra/components' +import { normalizePages } from 'nextra/normalize-pages' +import type { FC, ReactNode } from 'react' + +import cn from 'clsx' +// eslint-disable-next-line no-restricted-imports -- since we don't need `newWindow` prop +import NextLink from 'next/link' +import { DiscordIcon, GitHubIcon } from 'nextra/icons' +import { ClientNavbar } from './navbar.client' + +// export const Navbar: FC<{ pageMap: PageMapItem[] }> = ({ pageMap }) => { +// const pathname = usePathname() +// const { topLevelNavbarItems } = normalizePages({ +// list: pageMap, +// route: pathname +// }) +// return ( +// +// ) +// } + + +/* TODO: eslint typescript-sort-keys/interface: error */ + +interface NavbarProps { + /** + * Page map. + */ + pageMap: PageMapItem[] + /** + * Extra content after the last icon. + */ + children?: ReactNode + /** + * Specifies whether the logo should have a link or provides the URL for the logo's link. + * @default true + */ + logoLink?: string | boolean + /** + * Logo of the website. + */ + logo: ReactNode + /** + * URL of the project homepage. + */ + projectLink?: string + /** + * Icon of the project link. + * @default + */ + projectIcon?: ReactNode + /** + * URL of the chat link. + */ + chatLink?: string + /** + * Icon of the chat link. + * @default + */ + chatIcon?: ReactNode + /** + * CSS class name. + */ + className?: string + /** + * Aligns navigation links to the specified side. + * @default 'right' + */ + align?: 'left' | 'right' +} + +// Fix compiler error +// Expression type `JSXElement` cannot be safely reordered +const defaultGitHubIcon = ( + +) +const defaultChatIcon = + +export const Navbar: FC = ({ + pageMap, + children, + logoLink = true, + logo, + projectLink, + projectIcon = defaultGitHubIcon, + chatLink, + chatIcon = defaultChatIcon, + className, + align = 'right' +}) => { + const logoClass = cn( + 'x:flex x:items-center', + align === 'left' ? 'x:max-md:me-auto' : 'x:me-auto' + ) + return ( +
+
+ +
+ ) +} \ No newline at end of file diff --git a/content/_meta.js b/content/_meta.js index adbdd38..b5638da 100644 --- a/content/_meta.js +++ b/content/_meta.js @@ -26,180 +26,173 @@ export default { }, /* Load link with relative path */ Math3200_link: { - title: 'Math 3200', + title: 'Math3200', + type: 'page', href: '/Math3200' }, Math429_link: { - title: 'Math 429', + title: 'Math429', + type: 'page', href: '/Math429' }, Math4111_link: { - title: 'Math 4111', + title: 'Math4111', + type: 'page', href: '/Math4111' }, Math4121_link: { - title: 'Math 4121', + title: 'Math4121', + type: 'page', href: '/Math4121' }, Math4201_link: { - title: 'Math 4201', + title: 'Math4201', + type: 'page', href: '/Math4201' }, Math416_link: { - title: 'Math 416', + title: 'Math416', + type: 'page', href: '/Math416' }, Math401_link: { - title: 'Math 401', + title: 'Math401', + type: 'page', href: '/Math401' }, CSE332S_link: { - title: 'CSE 332S', + title: 'CSE332S', + type: 'page', href: '/CSE332S' }, CSE347_link: { - title: 'CSE 347', + title: 'CSE347', + type: 'page', href: '/CSE347' }, CSE442T_link: { - title: 'CSE 442T', + title: 'CSE442T', + type: 'page', href: '/CSE442T' }, CSE5313_link: { - title: 'CSE 5313', + title: 'CSE5313', + type: 'page', href: '/CSE5313' }, CSE510_link: { - title: 'CSE 510', + title: 'CSE510', + type: 'page', href: '/CSE510' }, CSE559A_link: { - title: 'CSE 559A', + title: 'CSE559A', + type: 'page', href: '/CSE559A' }, CSE5519_link: { - title: 'CSE 5519', + title: 'CSE5519', + type: 'page', href: '/CSE5519' }, /* Math Courses Start */ Math3200: { - display: 'hidden', + title: 'Math3200', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, Math429:{ - display: 'hidden', + title: 'Math429', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, Math4111: { - display: 'hidden', + title: 'Math4111', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, Math4121: { - display: 'hidden', + title: 'Math4121', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, Math4201: { - display: 'hidden', + title: 'Math4201', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, Math416: { - display: 'hidden', + title: 'Math416', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, Math401: { - display: 'hidden', + title: 'Math401', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, /* Math Courses End */ /* CSE Courses Start */ CSE332S: { - display: 'hidden', title: 'CSE332S', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, CSE347: { - display: 'hidden', title: 'CSE347', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, CSE442T: { - display: 'hidden', title: 'CSE442T', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, CSE5313: { - display: 'hidden', title: 'CSE5313', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, CSE510: { - display: 'hidden', title: 'CSE510', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, CSE559A: { - display: 'hidden', title: 'CSE559A', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, CSE5519: { - display: 'hidden', title: 'CSE5519', type: 'page', theme:{ - sidebar: false, timestamp: true, } }, diff --git a/content/layout.tsx b/content/layout.tsx index dad73d2..504aad1 100644 --- a/content/layout.tsx +++ b/content/layout.tsx @@ -1,10 +1,11 @@ /* eslint-env node */ -import { Footer, Layout, Navbar } from 'nextra-theme-docs' +import { Footer, Layout} from 'nextra-theme-docs' import { Banner, Head } from 'nextra/components' import { getPageMap } from 'nextra/page-map' import 'nextra-theme-docs/style.css' import { SpeedInsights } from "@vercel/speed-insights/next" import { Analytics } from "@vercel/analytics/react" +import { Navbar } from '../components/navbar' export const metadata = { metadataBase: new URL('https://notenextra.trance-0.com'), @@ -27,8 +28,10 @@ export const metadata = { } export default async function RootLayout({ children }) { + const pageMap = await getPageMap() const navbar = ( @@ -42,7 +45,6 @@ export default async function RootLayout({ children }) { projectLink="https://github.com/Trance-0/NoteNextra" /> ) - const pageMap = await getPageMap() return (