From c30eaf295dcdd37f66e455a1391357f660f4e48c Mon Sep 17 00:00:00 2001 From: gitfromwildan <> Date: Sat, 14 Jun 2025 20:33:39 +0700 Subject: [PATCH] bump docubook version 1.13.6 --- README.md | 2 +- components/context-popover.tsx | 133 + components/docs-menu.tsx | 60 +- components/footer.tsx | 6 +- components/leftbar.tsx | 18 +- components/markdown/CardGroupMdx.tsx | 2 +- components/markdown/CardMdx.tsx | 9 +- components/markdown/FileTreeMdx.tsx | 138 + components/markdown/KeyboardMdx.tsx | 2 +- components/markdown/NoteMdx.tsx | 2 +- components/markdown/StepperMdx.tsx | 4 +- components/markdown/TooltipsMdx.tsx | 9 +- components/navbar.tsx | 6 +- components/pagination.tsx | 2 +- components/search.tsx | 44 +- components/sublink.tsx | 13 +- components/theme-toggle.tsx | 20 +- components/toc-observer.tsx | 18 +- .../tutor-duitku/index.mdx | 0 .../tutor-moota/index.mdx | 0 .../tutor-tripay/index.mdx | 2 +- .../mendaftar-akun-tripay/index.mdx | 0 .../tutor-tripay/merchant/index.mdx | 0 .../tutor-tripay/sandbox/index.mdx | 0 docu.json | 59 +- lib/changelog.ts | 76 - lib/markdown.ts | 67 +- lib/routes-config.ts | 9 +- package-lock.json | 8628 ----------------- package.json | 4 +- public/hire-me.html | 14 - public/images/img-playground.png | Bin 30192 -> 0 bytes public/images/new-editor.png | Bin 32138 -> 0 bytes public/images/release-note.png | Bin 61454 -> 0 bytes public/images/snippet.png | Bin 87795 -> 0 bytes styles/editor.css | 57 - styles/globals.css | 117 +- styles/syntax.css | 64 +- tailwind.config.ts | 1 - 39 files changed, 551 insertions(+), 9035 deletions(-) create mode 100644 components/context-popover.tsx create mode 100644 components/markdown/FileTreeMdx.tsx rename contents/docs/{getting-started => plugins}/tutor-duitku/index.mdx (100%) rename contents/docs/{getting-started => plugins}/tutor-moota/index.mdx (100%) rename contents/docs/{getting-started => plugins}/tutor-tripay/index.mdx (70%) rename contents/docs/{getting-started => plugins}/tutor-tripay/mendaftar-akun-tripay/index.mdx (100%) rename contents/docs/{getting-started => plugins}/tutor-tripay/merchant/index.mdx (100%) rename contents/docs/{getting-started => plugins}/tutor-tripay/sandbox/index.mdx (100%) delete mode 100644 lib/changelog.ts delete mode 100644 package-lock.json delete mode 100644 public/hire-me.html delete mode 100644 public/images/img-playground.png delete mode 100644 public/images/new-editor.png delete mode 100644 public/images/release-note.png delete mode 100644 public/images/snippet.png delete mode 100644 styles/editor.css diff --git a/README.md b/README.md index 68f4d02..67ddb8e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **DocuBook** is a documentation web project designed to provide a simple and user-friendly interface for accessing various types of documentation. This site is crafted for developers and teams who need quick access to references, guides, and essential documents. [![Deploy with -Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/gitfromwildan/docubook) +Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/DocuBook/docubook) ## Features diff --git a/components/context-popover.tsx b/components/context-popover.tsx new file mode 100644 index 0000000..bbc454a --- /dev/null +++ b/components/context-popover.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { usePathname, useRouter } from "next/navigation"; +import { useState, useEffect } from "react"; +import { ROUTES, EachRoute } from "@/lib/routes-config"; +import { cn } from "@/lib/utils"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Button } from "@/components/ui/button"; +import * as LucideIcons from "lucide-react"; +import { ChevronsUpDown, Check, type LucideIcon } from "lucide-react"; + +interface ContextPopoverProps { + className?: string; +} + +// Get all root-level routes with context +function getContextRoutes(): EachRoute[] { + return ROUTES.filter(route => route.context); +} + +// Get the first item's href from a route +function getFirstItemHref(route: EachRoute): string { + return route.items?.[0]?.href ? `${route.href}${route.items[0].href}` : route.href; +} + +// Get the active context route from the current path +function getActiveContextRoute(path: string): EachRoute | undefined { + if (!path.startsWith('/docs')) return undefined; + const docPath = path.replace(/^\/docs/, ''); + return getContextRoutes().find(route => docPath.startsWith(route.href)); +} + +// Get icon component by name +function getIcon(name: string) { + const Icon = LucideIcons[name as keyof typeof LucideIcons] as LucideIcon | undefined; + if (!Icon) return ; + return ; +} + +export default function ContextPopover({ className }: ContextPopoverProps) { + const pathname = usePathname(); + const router = useRouter(); + const [activeRoute, setActiveRoute] = useState(); + const contextRoutes = getContextRoutes(); + + useEffect(() => { + if (pathname.startsWith("/docs")) { + setActiveRoute(getActiveContextRoute(pathname)); + } else { + setActiveRoute(undefined); + } + }, [pathname]); + + if (!pathname.startsWith("/docs") || contextRoutes.length === 0) { + return null; + } + + return ( + + + + + +
+ {contextRoutes.map((route) => { + const isActive = activeRoute?.href === route.href; + const firstItemPath = getFirstItemHref(route); + const contextPath = `/docs${firstItemPath}`; + + return ( + + ); + })} +
+
+
+ ); +} diff --git a/components/docs-menu.tsx b/components/docs-menu.tsx index 2ef1d9a..4df580c 100644 --- a/components/docs-menu.tsx +++ b/components/docs-menu.tsx @@ -1,44 +1,62 @@ "use client"; -import { ROUTES } from "@/lib/routes-config"; +import { ROUTES, EachRoute } from "@/lib/routes-config"; import SubLink from "./sublink"; import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; interface DocsMenuProps { isSheet?: boolean; className?: string; } +// Get the current context from the path +function getCurrentContext(path: string): string | undefined { + if (!path.startsWith('/docs')) return undefined; + + // Extract the first segment after /docs/ + const match = path.match(/^\/docs\/([^\/]+)/); + return match ? match[1] : undefined; +} + +// Get the route that matches the current context +function getContextRoute(contextPath: string): EachRoute | undefined { + return ROUTES.find(route => { + const normalizedHref = route.href.replace(/^\/+|\/+$/g, ''); + return normalizedHref === contextPath; + }); +} + export default function DocsMenu({ isSheet = false, className = "" }: DocsMenuProps) { const pathname = usePathname(); // Skip rendering if not on a docs page if (!pathname.startsWith("/docs")) return null; + // Get the current context + const currentContext = getCurrentContext(pathname); + + // Get the route for the current context + const contextRoute = currentContext ? getContextRoute(currentContext) : undefined; + + // If no context route is found, don't render anything + if (!contextRoute) return null; + return ( ); diff --git a/components/footer.tsx b/components/footer.tsx index fe71929..953cb48 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -63,8 +63,9 @@ export function FooterButtons() { target="_blank" rel="noopener noreferrer" aria-label={item.name} + className="text-muted-foreground hover:text-foreground transition-colors" > - + ); })} @@ -79,7 +80,8 @@ export function MadeWith() { DocuBook - + + ); } diff --git a/components/leftbar.tsx b/components/leftbar.tsx index e0de122..2264751 100644 --- a/components/leftbar.tsx +++ b/components/leftbar.tsx @@ -9,12 +9,12 @@ import { } from "@/components/ui/sheet"; import { Logo, NavMenu } from "@/components/navbar"; import { Button } from "@/components/ui/button"; -import { AlignLeftIcon, PanelLeftClose, PanelLeftOpen } from "lucide-react"; -import { FooterButtons } from "@/components/footer"; +import { LayoutGrid, PanelLeftClose, PanelLeftOpen } from "lucide-react"; import { DialogTitle, DialogDescription } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; import DocsMenu from "@/components/docs-menu"; import { ModeToggle } from "@/components/theme-toggle"; +import ContextPopover from "@/components/context-popover"; // Toggle Button Component export function ToggleButton({ @@ -52,9 +52,14 @@ export function Leftbar() { ${collapsed ? "w-[24px]" : "w-[280px]"} flex flex-col pr-2`} > - {/* Scrollable DocsMenu */} + {/* Scrollable Content */} - {!collapsed && } + {!collapsed && ( +
+ + +
+ )}
); @@ -65,7 +70,7 @@ export function SheetLeftbar() { @@ -82,7 +87,8 @@ export function SheetLeftbar() {
-
+
+
diff --git a/components/markdown/CardGroupMdx.tsx b/components/markdown/CardGroupMdx.tsx index f1f6771..a1423b8 100644 --- a/components/markdown/CardGroupMdx.tsx +++ b/components/markdown/CardGroupMdx.tsx @@ -13,7 +13,7 @@ const CardGroup: React.FC = ({ children, cols = 2, className }) return (
= ({ title, icon, href, horizontal, children, cl const content = (
= ({ title, icon, href, horizontal, children, cl > {Icon && }
- {title} -
{children}
+ {title} +
{children}
); diff --git a/components/markdown/FileTreeMdx.tsx b/components/markdown/FileTreeMdx.tsx new file mode 100644 index 0000000..6abdd3b --- /dev/null +++ b/components/markdown/FileTreeMdx.tsx @@ -0,0 +1,138 @@ +'use client'; + +import React, { useState, ReactNode, Children, isValidElement, cloneElement } from 'react'; +import { ChevronRight, File as FileIcon, Folder as FolderIcon, FolderOpen } from 'lucide-react'; + +interface FileProps { + name: string; + children?: ReactNode; +} + +const FileComponent = ({ name }: FileProps) => { + const [isHovered, setIsHovered] = useState(false); + const fileExtension = name.split('.').pop()?.toUpperCase(); + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + tabIndex={-1} + > + + {name} + {isHovered && fileExtension && ( + + {fileExtension} + + )} +
+ ); +}; + +const FolderComponent = ({ name, children }: FileProps) => { + const [isOpen, setIsOpen] = useState(true); // Set to true by default + const [isHovered, setIsHovered] = useState(false); + const hasChildren = React.Children.count(children) > 0; + + return ( +
+
hasChildren && setIsOpen(!isOpen)} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + tabIndex={-1} + onKeyDown={(e) => e.preventDefault()} + > + {hasChildren ? ( + + ) : ( +
+ )} + {isOpen ? ( + + ) : ( + + )} + + {name} + +
+ {isOpen && hasChildren && ( +
+ {children} +
+ )} +
+ ); +}; + +export const Files = ({ children }: { children: ReactNode }) => { + return ( +
e.preventDefault()} + > +
+ {Children.map(children, (child, index) => { + if (isValidElement(child)) { + return cloneElement(child, { key: index }); + } + return null; + })} +
+
+ ); +}; + +export const Folder = ({ name, children }: FileProps) => { + return {children}; +}; + +export const File = ({ name }: FileProps) => { + return ; +}; + +// MDX Components +export const FileTreeMdx = { + Files, + File, + Folder, +}; + +export default FileTreeMdx; diff --git a/components/markdown/KeyboardMdx.tsx b/components/markdown/KeyboardMdx.tsx index aede1a5..18c9625 100644 --- a/components/markdown/KeyboardMdx.tsx +++ b/components/markdown/KeyboardMdx.tsx @@ -88,7 +88,7 @@ const KbdComponent: React.FC = ({ return ( {renderContent()} diff --git a/components/markdown/NoteMdx.tsx b/components/markdown/NoteMdx.tsx index 49ee7b9..2627737 100644 --- a/components/markdown/NoteMdx.tsx +++ b/components/markdown/NoteMdx.tsx @@ -29,7 +29,7 @@ export default function Note({ "dark:bg-stone-950/25 bg-stone-50": type === "note", "dark:bg-red-950 bg-red-100 border-red-200 dark:border-red-900": type === "danger", - "dark:bg-orange-950 bg-orange-100 border-orange-200 dark:border-orange-900": + "bg-orange-50 border-orange-200 dark:border-orange-900 dark:bg-orange-900/50": type === "warning", "dark:bg-green-950 bg-green-100 border-green-200 dark:border-green-900": type === "success", diff --git a/components/markdown/StepperMdx.tsx b/components/markdown/StepperMdx.tsx index 297bfad..c63f0ee 100644 --- a/components/markdown/StepperMdx.tsx +++ b/components/markdown/StepperMdx.tsx @@ -11,13 +11,13 @@ export function Stepper({ children }: PropsWithChildren) { return (
-
+
{index + 1}
{child} diff --git a/components/markdown/TooltipsMdx.tsx b/components/markdown/TooltipsMdx.tsx index 6459348..43c6ab4 100644 --- a/components/markdown/TooltipsMdx.tsx +++ b/components/markdown/TooltipsMdx.tsx @@ -11,14 +11,17 @@ const Tooltip: React.FC = ({ text, tip }) => { return ( setVisible(true)} onMouseLeave={() => setVisible(false)} > - {text} + + {text} + {visible && ( - + {tip} + )} diff --git a/components/navbar.tsx b/components/navbar.tsx index cc00b89..1613c31 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -63,15 +63,15 @@ export function NavMenu({ isSheet = false }) { const Comp = ( {item.title} - {isExternal && } + {isExternal && } ); return isSheet ? ( diff --git a/components/pagination.tsx b/components/pagination.tsx index f73a831..9da486d 100644 --- a/components/pagination.tsx +++ b/components/pagination.tsx @@ -1,7 +1,7 @@ import { getPreviousNext } from "@/lib/markdown"; import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; import Link from "next/link"; -import { buttonVariants } from "./ui/button"; +import { buttonVariants } from "@/components/ui/button"; export default function Pagination({ pathname }: { pathname: string }) { const res = getPreviousNext(pathname); diff --git a/components/search.tsx b/components/search.tsx index 49bd70b..7509d88 100644 --- a/components/search.tsx +++ b/components/search.tsx @@ -17,6 +17,23 @@ import { import Anchor from "./anchor"; import { advanceSearch, cn } from "@/lib/utils"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { page_routes } from "@/lib/routes-config"; + +// Define the ContextInfo type to match the one in routes-config +type ContextInfo = { + icon: string; + description: string; + title?: string; +}; + +type SearchResult = { + title: string; + href: string; + noLink?: boolean; + items?: undefined; + score?: number; + context?: ContextInfo; +}; export default function Search() { const router = useRouter(); @@ -39,10 +56,25 @@ export default function Search() { }; }, []); - const filteredResults = useMemo( - () => advanceSearch(searchedInput.trim()), - [searchedInput] - ); + const filteredResults = useMemo(() => { + const trimmedInput = searchedInput.trim(); + + // If search input is empty or less than 3 characters, show initial suggestions + if (trimmedInput.length < 3) { + return page_routes + .filter((route: { href: string }) => !route.href.endsWith('/')) // Filter out directory routes + .slice(0, 6) // Limit to 6 posts + .map((route: { title: string; href: string; noLink?: boolean; context?: ContextInfo }) => ({ + title: route.title, + href: route.href, + noLink: route.noLink, + context: route.context + })); + } + + // For search with 3 or more characters, use the advance search + return advanceSearch(trimmedInput) as unknown as SearchResult[]; + }, [searchedInput]); useEffect(() => { setSelectedIndex(0); @@ -100,10 +132,10 @@ export default function Search() {
- +
- + ( @@ -72,8 +73,8 @@ export default function SubLink({ ) ) : (

{title}

@@ -93,7 +94,7 @@ export default function SubLink({ >
{titleOrLink} - + {!isOpen ? (