commit d6e3946296b88b271e64dc5bd1b2a66f912e3f4f Author: Wildan Nursahidan Date: Sun Feb 23 10:43:08 2025 +0700 initial to gitea diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd3dbb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.vscode/accordion.code-snippets b/.vscode/accordion.code-snippets new file mode 100644 index 0000000..efb3a05 --- /dev/null +++ b/.vscode/accordion.code-snippets @@ -0,0 +1,14 @@ +{ + "DocuAccordion": { + "prefix": "accordion", + "body": [ + "", + " this is an example of plain text content from the accordion component and below is markdown ;", + " 1. number one", + " 2. number two", + " 3. number three", + "" + ], + "description": "Create a DocuAccordion component with markdown list." + } + } diff --git a/.vscode/button.code-snippets b/.vscode/button.code-snippets new file mode 100644 index 0000000..3142994 --- /dev/null +++ b/.vscode/button.code-snippets @@ -0,0 +1,16 @@ +{ + "DocuButton": { + "prefix": "button", + "body": [ + "" + ], + "description": "Create a DocuButton component on markdown." + } + } diff --git a/.vscode/card.code-snippets b/.vscode/card.code-snippets new file mode 100644 index 0000000..54dbc34 --- /dev/null +++ b/.vscode/card.code-snippets @@ -0,0 +1,26 @@ +{ + "DocuCards": { + "prefix": "card", + "body": [ + "
", + "", + " ", + " ", + "", + "", + " ", + " ", + "", + "", + " ", + " ", + "", + "", + " ", + " ", + "", + "
" + ], + "description": "Create a DocuCards component on markdown." + } + } diff --git a/.vscode/codeblock.code-snippets b/.vscode/codeblock.code-snippets new file mode 100644 index 0000000..ec5d1a1 --- /dev/null +++ b/.vscode/codeblock.code-snippets @@ -0,0 +1,16 @@ +{ + "DocuCodeBlock": { + "prefix": "codeblock", + "description": "Checks if the rocket is stable and prevents a crash if it's not.", + "body": [ + "```${1:javascript:main.js} showLineNumbers {${2:3-4}}", + "function isRocketAboutToCrash() {", + " // Check if the rocket is stable", + " if (!isStable()) {", + " NoCrash(); // Prevent the crash", + " }", + "}", + "```", + ], + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4ef01b0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": [] +} diff --git a/.vscode/image-link.code-snippets b/.vscode/image-link.code-snippets new file mode 100644 index 0000000..b4a86c5 --- /dev/null +++ b/.vscode/image-link.code-snippets @@ -0,0 +1,16 @@ +{ + "DocuImage": { + "prefix": "image", + "body": [ + "![${1:Alt text for the image}](${2:https://via.placeholder.com/150})" + ], + "description": "Snippet untuk menampilkan image komponen." + }, + "DocuLink": { + "prefix": "link", + "body": [ + "[${1:Text Link}](${2:https://www.openai.com})" + ], + "description": "Snippet untuk menampilkan link komponen." + } +} diff --git a/.vscode/metadata.code-snippets b/.vscode/metadata.code-snippets new file mode 100644 index 0000000..5cd7ce9 --- /dev/null +++ b/.vscode/metadata.code-snippets @@ -0,0 +1,13 @@ +{ + "DocuMetadata": { + "prefix": "metadata", + "body": [ + "---", + "title : ${1:judul post}", + "description : ${2:deskripsi singkat dari post}", + "date : ${3:10-12-2024}", + "---" + ], + "description": "Snippet untuk membuat metadata." + } +} diff --git a/.vscode/note.code-snippets b/.vscode/note.code-snippets new file mode 100644 index 0000000..0126504 --- /dev/null +++ b/.vscode/note.code-snippets @@ -0,0 +1,38 @@ +{ + "DocuNote - General Note": { + "prefix": "note", + "body": [ + "", + " ${1:This is a general note to convey information to the user.}", + "" + ], + "description": "Insert a general note" + }, + "DocuNote - Danger Note": { + "prefix": "danger", + "body": [ + "", + " ${1:This is a danger alert to notify the user of a critical issue.}", + "" + ], + "description": "Insert a danger note" + }, + "DocuNote - Warning Note": { + "prefix": "warning", + "body": [ + "", + " ${1:This is a warning alert for issues that require attention.}", + "" + ], + "description": "Insert a warning note" + }, + "DocuNote - Success Note": { + "prefix": "success", + "body": [ + "", + " ${1:This is a success message to inform the user of successful actions.}", + "" + ], + "description": "Insert a success note" + } +} diff --git a/.vscode/stepper.code-snippets b/.vscode/stepper.code-snippets new file mode 100644 index 0000000..704d492 --- /dev/null +++ b/.vscode/stepper.code-snippets @@ -0,0 +1,24 @@ +{ + "DocuStepper": { + "prefix": "stepper", + "body": [ + "", + " ", + " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec interdum,", + " felis sed efficitur tincidunt, justo nulla viverra enim, et maximus nunc", + " dolor in lorem.", + " ${2:}", + "" + ], + "description": "Snippet untuk menampilkan stepper komponen." + }, + "DocuStepperItem": { + "prefix": "item", + "body": [ + "", + " ${2:Your step description here.}", + "${3:}" + ], + "description": "Snippet untuk menambahkan item baru ke dalam Stepper." + } +} diff --git a/.vscode/table.code-snippets b/.vscode/table.code-snippets new file mode 100644 index 0000000..202f150 --- /dev/null +++ b/.vscode/table.code-snippets @@ -0,0 +1,20 @@ +{ + "DocuTable": { + "prefix": "table", + "body": [ + "| **${1:Feature}** | **${2:Description}** |", + "| ------------------------------- | ----------------------------------------------------- |", + "| ${3:MDX Support} | ${4:Write interactive documentation with MDX.} |", + "| Nested Pages | Organize content in a nested, hierarchical structure. |", + "| Blog Section | Include a dedicated blog section. |", + "| Pagination | Split content across multiple pages. |", + "| Syntax Highlighting | Highlight code for better readability. |", + "| Code Line Highlighting & Titles | Highlight specific lines with descriptive titles. |", + "| Interactive Code Blocks | Language-specific and interactive code display. |", + "| Custom Markdown Components | Embed custom, reusable components in your docs. |", + "| Static Site Generation | Generate a static, high-performance site. |", + "| SEO-Optimized | Structured for optimal search engine indexing. |" + ], + "description": "Create a DocuTable component on markdown." + } + } diff --git a/.vscode/tabs.code-snippets b/.vscode/tabs.code-snippets new file mode 100644 index 0000000..02e2f36 --- /dev/null +++ b/.vscode/tabs.code-snippets @@ -0,0 +1,33 @@ +{ + "DocuTabs": { + "prefix": "tabs", + "body": [ + "", + " ", + " Java", + " TypeScript", + " ", + " ", + " ```java", + " // HelloWorld.java", + " public class HelloWorld {", + " public static void main(String[] args) {", + " System.out.println(\"Hello, World!\");", + " }", + " }", + " ```", + " ", + " ", + " ```typescript", + " // helloWorld.ts", + " function helloWorld(): void {", + " console.log(\"Hello, World!\");", + " }", + " helloWorld();", + " ```", + " ", + "" + ], + "description": "Create a DocuTabs component with Java and TypeScript examples." + } + } diff --git a/.vscode/tooltips.code-snippets b/.vscode/tooltips.code-snippets new file mode 100644 index 0000000..06728d1 --- /dev/null +++ b/.vscode/tooltips.code-snippets @@ -0,0 +1,16 @@ +{ + "DocuTooltips": { + "prefix": "tooltips", + "body": [ + "
", + " ", + " ", + " ${2:Hover over me}", + " ", + " ", + " ${3:and this is some regular text.}", + "
" + ], + "description": "Create a DocuTooltips component with version examples." + } + } diff --git a/.vscode/typography.code-snippets b/.vscode/typography.code-snippets new file mode 100644 index 0000000..d534da3 --- /dev/null +++ b/.vscode/typography.code-snippets @@ -0,0 +1,33 @@ +{ + "DocuH2": { + "prefix": "h2", + "body": [ + "## Heading 2" + ], + "description": "Tag Heading 2 for markdown." + }, + "DocuH3": { + "prefix": "h3", + "body": [ + "### Heading 3" + ], + "description": "Tag Heading 3 for markdown." + }, + "DocuText": { + "prefix": "text", + "body": [ + "DocuBook is proudly **open-source**! 🎉 We believe in creating an accessible, collaborative platform that thrives on community contributions." + ], + "description": "Tag Paragraph for markdown." + }, + "Docu-UndorderList": { + "prefix": "unorderlist", + "body": [ + "- ${1:**Next.js 14** - The powerful React framework optimized for production.}", + "- **Tailwind CSS** - Utility-first styling for quick, clean designs.", + "- **Shadcn-UI** - Elegant, accessible components for a polished look.", + "- **next-mdx-remote** - Enables MDX support for dynamic, interactive Markdown content." + ], + "description": "Tag Undorderlist for markdown." + }, +} diff --git a/.vscode/youtube.code-snippets b/.vscode/youtube.code-snippets new file mode 100644 index 0000000..6ec60a4 --- /dev/null +++ b/.vscode/youtube.code-snippets @@ -0,0 +1,9 @@ +{ + "DocuVideo": { + "prefix": "youtube", + "body": [ + "" + ], + "description": "Snippet untuk menampilkan komponen video Youtube." + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a6ac76c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,132 @@ +## [1.3.0] - 2024-12-31 + +> Release Note Feature to Make it Easier to Write Changelogs + +![version 1.3.0 - release note image](https://docubook.pro/images/release-note.png) + +### Added + +- New Release Note Feature +- New Layout for Changelog page +- New Changelog page +- Add Release Note Component +- Easily write release notes directly from the CHANGELOG.md file +- TOC for versioning +- Write with the markdown tag +- Add lib / changelog.ts + +### Improved + +- Improvement Responsive feature image for Version Entry +- Improvement Layout for changelog page +- Improvement Padding on mobile devices +- Only use containers of md size +- Improvement syntax.css for ul>li classes + +### Fixed + +- Fix og:image not showing on Page.tsx +- Fix text-indent on class li + +### Removed + +- Remove excessive padding +- Remove Logo on Footer + +## [1.2.0] - 2024-12-22 + +> New Accordion Component : Support content plain text, html and all markdown component + +### Added + +- add New Accordion + +### Improved + +- Props Improvement +- Support Dynamic Content for Accordion + +## [1.1.0] - 2024-12-15 + +> Major Update : Easily manage set up with docu.json + +### Added + +- add docu.json file +- add openGraph (title, description, image) +- add Dynamic metadata +- Generate metadata as openGraph +- openGraph support for .mdx + +### Improved + +- routes-config from json +- Frontmatter improvement +- Edit the content of footer.tsx simply via the docu.json file +- Edit the content of navbar.tsx simply via the docu.json file + +## [1.0.7] - 2024-12-14 + +> Easily updates your DocuBook Version with CLI npx update_docu + +### Added + +- CLI npx update_docu (update features into docubook existing directory) +- Playground (easily to written content) +- New Button component +- Navbar external link conditions +- CLI npx create_docu + +### Improved + +- Searchbar Improvement +- Navigation Improvement +- Edit on Github Improvement + +### Removed +- Remove CLI npx create-docu (on this version not usage dash `-`) + +## [1.0.6] - 2024-11-24 + +> New Components, Fix and Improvement + +### Added + +- New Card component +- New Tooltips component + +### Fixed + +- change root folder + +### Improved + +- logo on navbar & footer +- easily change logo + +## [1.0.5] - 2024-11-16 + +> Add New Features and Improvement for this version + +### Added + +- New Youtube component +- edit this page - easily manage directory content via the github repo +- support installation via cli commant npx create-docu + +### Improved + +- keyboard shortcut command + k or ctrl + k to open search dialog + +## [1.0.0] - 2024-11-10 + +> Initial release of DocuBook to create interactive nested docs with MDX + +### Added + +- Initial release of DocuBook +- Basic documentation structure +- Markdown support with MDX +- Responsive design +- Search functionality +- Dark mode support diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab7a1da --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Mohd. Nisab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a368f7 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# DocuBook + +**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. + +> **Note**: This application is a fork of [AriaDocs](https://github.com/nisabmohd/Aria-Docs), created by [Nisab Mohd](https://github.com/nisabmohd). DocuBook provides an alternative to the documentation solution found on [Mintlify](https://mintlify.com/), utilizing `.mdx` (Markdown + JSX) for content creation and management. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/mywildancloud/docubook) + +## Features + +- **Easy Navigation**: Simple layout for quick navigation between pages. +- **Quick Search**: Easily find documentation using a search function. +- **Responsive Theme**: Responsive design optimized for devices ranging from desktops to mobile. +- **Markdown Content**: Support for markdown-based documents. +- **SEO Friendly**: Optimized structure for search visibility, enhancing accessibility on search engines. + +## Installation + +```bash +npx @docubook/create@latest +``` + +#### command output + +```bash +? Enter a name for your project directory: (docubook) + +Creating a new Docubook project in /path/your/docubook from the main branch... +✔ Docubook project successfully created in /path/your/docubook! + +Next steps: +1. Navigate to your project directory: + cd docubook +2. Install dependencies: + npm install +3. Start the development server: + npm run dev +``` + +## Update +### How to Update DocuBook? +- **Open a New Terminal**: Please open a new terminal on the desktop that has DocuBook installed. +- **Move Directory**: for example, if the directory name is docubook, then write `cd docubook` and press enter. + +```bash +npx @docubook/update@latest +``` + +#### command output + +```bash +📂 Updating Docubook project in /Users/wildan/Public/docubook... + +ℹ ⚡ Skipped public +ℹ ⚡ Skipped contents +ℹ ⚡ Skipped app/page.tsx +ℹ ⚡ Skipped docu.json +ℹ ⚡ Skipped CHANGELOG.md +✨ Replacing styles folder... +✨ Replaced all CSS files in styles folder +✔ ✅ Docubook v1.4.2 successfully updated in /Users/wildan/Public/docubook! + +🎯 Next steps: +1. Verify your changes in the current directory. +2. Run the install script to check for package updates: + npm install +3. Run the development server: + npm run dev +``` + +Access the app on => http://localhost:3000 diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..42951bc --- /dev/null +++ b/app/blog/[slug]/page.tsx @@ -0,0 +1,92 @@ +import { Typography } from "@/components/typography"; +import { buttonVariants } from "@/components/ui/button"; +import { Author, getAllBlogStaticPaths, getBlogForSlug } from "@/lib/markdown"; +import { ArrowLeftIcon } from "lucide-react"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { formatDate } from "@/lib/utils"; +import { ScrollToTop } from "@/components/scroll-to-top"; + +type PageProps = { + params: { slug: string }; +}; + +export async function generateMetadata({ params: { slug } }: PageProps) { + const res = await getBlogForSlug(slug); + if (!res) return null; + const { frontmatter } = res; + return { + title: frontmatter.title, + description: frontmatter.description, + }; +} + +export async function generateStaticParams() { + const val = await getAllBlogStaticPaths(); + if (!val) return []; + return val.map((it) => ({ slug: it })); +} + +export default async function BlogPage({ params: { slug } }: PageProps) { + const res = await getBlogForSlug(slug); + if (!res) notFound(); + return ( +
+ + Back to blog + +
+

+ {formatDate(res.frontmatter.date)} +

+

+ {res.frontmatter.title} +

+
+

Posted by

+ +
+
+
+ {res.content} +
+ +
+ ); +} + +function Authors({ authors }: { authors: Author[] }) { + return ( +
+ {authors.map((author) => { + return ( + + + + + {author.username.slice(0, 2).toUpperCase()} + + +
+

{author.username}

+

+ @{author.handle} +

+
+ + ); + })} +
+ ); +} diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx new file mode 100644 index 0000000..6211155 --- /dev/null +++ b/app/blog/layout.tsx @@ -0,0 +1,9 @@ +import { PropsWithChildren } from "react"; + +export default function BlogLayout({ children }: PropsWithChildren) { + return ( +
+ {children} +
+ ); +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000..dd5eaec --- /dev/null +++ b/app/blog/page.tsx @@ -0,0 +1,98 @@ +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Author, BlogMdxFrontmatter, getAllBlogs } from "@/lib/markdown"; +import { formatDate2, stringToDate } from "@/lib/utils"; +import { getMetadata } from "@/app/layout"; +import Image from "next/image"; +import Link from "next/link"; +import docuConfig from "@/docu.json"; + +export const metadata = getMetadata({ + title: "Blog", + description: "Discover the latest updates, tutorials, and insights on DocuBook.", +}); +const { meta } = docuConfig; +export default async function BlogIndexPage() { + const blogs = (await getAllBlogs()).sort( + (a, b) => stringToDate(b.date).getTime() - stringToDate(a.date).getTime() + ); + return ( +
+
+

+ Blog Posts +

+

+ Discover the latest updates, tutorials, and insights on {meta.title}. +

+
+
+ {blogs.map((blog) => ( + + ))} +
+
+ ); +} + +function BlogCard({ + date, + title, + description, + slug, + cover, + authors, +}: BlogMdxFrontmatter & { slug: string }) { + return ( + +

{title}

+
+ {title} +
+

{description}

+
+

+ Published on {formatDate2(date)} +

+ +
+ + ); +} + +function AvatarGroup({ users, max = 4 }: { users: Author[]; max?: number }) { + const displayUsers = users.slice(0, max); + const remainingUsers = Math.max(users.length - max, 0); + + return ( +
+ {displayUsers.map((user, index) => ( + + + + {user.username.slice(0, 2).toUpperCase()} + + + ))} + {remainingUsers > 0 && ( + + +{remainingUsers} + + )} +
+ ); +} diff --git a/app/changelog/layout.tsx b/app/changelog/layout.tsx new file mode 100644 index 0000000..b77f34a --- /dev/null +++ b/app/changelog/layout.tsx @@ -0,0 +1,11 @@ +export default function ChangelogLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/app/changelog/page.tsx b/app/changelog/page.tsx new file mode 100644 index 0000000..397a039 --- /dev/null +++ b/app/changelog/page.tsx @@ -0,0 +1,63 @@ +import { Suspense } from "react"; +import { getChangelogEntries } from "@/lib/changelog"; +import { VersionEntry } from "@/components/changelog/version-entry"; +import { VersionToc } from "@/components/changelog/version-toc"; +import { getMetadata } from "@/app/layout"; +import docuConfig from "@/docu.json"; +import { FloatingVersionToc } from "@/components/changelog/floating-version"; + +export const metadata = getMetadata({ + title: "Changelog", + description: "Latest updates and improvements to DocuBook", + image: "release-note.png", +}); + +export default async function ChangelogPage() { + const entries = await getChangelogEntries(); + const { meta } = docuConfig; + return ( +
+
+
+

Changelog

+

+ Latest updates and improvements to {meta.title} +

+
+
+ +
+
+ }> + ({ version, date }))} + /> + + +
+
+
+
+ {entries.map((entry, index) => ( +
+ +
+ ))} +
+
+
+
+
+ {/* Floating TOC for smaller screens */} + {entries.length > 0 && ( + ({ version, date }))} + /> + )} +
+ ); +} diff --git a/app/docs/[[...slug]]/page.tsx b/app/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..aa1a9da --- /dev/null +++ b/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,105 @@ +import { notFound } from "next/navigation"; +import { getDocsForSlug, getDocsTocs } from "@/lib/markdown"; +import DocsBreadcrumb from "@/components/docs-breadcrumb"; +import Pagination from "@/components/pagination"; +import Toc from "@/components/toc"; +import { Typography } from "@/components/typography"; +import EditThisPage from "@/components/edit-on-github"; +import { formatDate2 } from "@/lib/utils"; +import docuConfig from "@/docu.json"; +import MobToc from "@/components/mob-toc"; +import { ScrollToTop } from "@/components/scroll-to-top"; + +const { meta } = docuConfig; + +type PageProps = { + params: { + slug: string[]; + }; +}; + +// Function to generate metadata dynamically +export async function generateMetadata({ params: { slug = [] } }: PageProps) { + const pathName = slug.join("/"); + const res = await getDocsForSlug(pathName); + + if (!res) { + return { + title: "Page Not Found", + description: "The requested page was not found.", + }; + } + + const { title, description, image } = res.frontmatter; + + // Absolute URL for og:image + const ogImage = image + ? `${meta.baseURL}/images/${image}` + : `${meta.baseURL}/images/og-image.png`; + + return { + title: `${title}`, + description, + openGraph: { + title, + description, + url: `${meta.baseURL}/docs/${pathName}`, + type: "article", + images: [ + { + url: ogImage, + width: 1200, + height: 630, + alt: title, + }, + ], + }, + twitter: { + card: "summary_large_image", + title, + description, + images: [ogImage], + }, + }; +} + +export default async function DocsPage({ params: { slug = [] } }: PageProps) { + const pathName = slug.join("/"); + const res = await getDocsForSlug(pathName); + + if (!res) notFound(); + + const { title, description, image, date } = res.frontmatter; + + // File path for edit link + const filePath = `contents/docs/${slug.join("/") || ""}/index.mdx`; + + const tocs = await getDocsTocs(pathName); + + return ( +
+
+ +
+ +
+ +

{title}

+

{description}

+
{res.content}
+
+ {date && ( +

+ Published on {formatDate2(date)} +

+ )} + {/* */} +
+ +
+ +
+ +
+ ); +} diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx new file mode 100644 index 0000000..83f9bba --- /dev/null +++ b/app/docs/layout.tsx @@ -0,0 +1,14 @@ +import { Leftbar } from "@/components/leftbar"; + +export default function DocsLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+ +
{children}
+
+ ); +} \ No newline at end of file diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000..8a8ae0b --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,44 @@ +"use client"; // Error components must be Client Components + +import { Button, buttonVariants } from "@/components/ui/button"; +import Link from "next/link"; +import { useEffect } from "react"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error(error); + }, [error]); + + return ( +
+
+

Oops!

+

+ Something went wrong {":`("} +

+

+ We're sorry, but an error occurred while processing your request. +

+
+
+ + + Back to homepage + +
+
+ ); +} diff --git a/app/hire-me/page.tsx b/app/hire-me/page.tsx new file mode 100644 index 0000000..327d889 --- /dev/null +++ b/app/hire-me/page.tsx @@ -0,0 +1,18 @@ +import { getMetadata } from "@/app/layout"; + +export const metadata = getMetadata({ + title: "Hire Me", + description: "Hire me to start a documentation project with DocuBook", +}); + +export default function EmbeddedHTML() { + return ( +
+ +
+ ); +}; + +export default Youtube; diff --git a/components/mob-toc.tsx b/components/mob-toc.tsx new file mode 100644 index 0000000..7b81480 --- /dev/null +++ b/components/mob-toc.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { ListIcon } from "lucide-react"; +import TocObserver from "./toc-observer"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; + +interface MobTocProps { + tocs: { + level: number; + text: string; + href: string; + }[]; +} + +export default function MobToc({ tocs }: MobTocProps) { + return ( +
+ + + +
+ + On this page +
+
+ + + +
+
+
+ ); +} diff --git a/components/navbar.tsx b/components/navbar.tsx new file mode 100644 index 0000000..1744b5e --- /dev/null +++ b/components/navbar.tsx @@ -0,0 +1,99 @@ +import { ModeToggle } from "@/components/theme-toggle"; +import { ArrowUpRight } from "lucide-react"; +import Link from "next/link"; +import Image from "next/image"; +import { buttonVariants } from "./ui/button"; +import Search from "./search"; +import Anchor from "./anchor"; +import { SheetLeftbar } from "./leftbar"; +import { SheetClose } from "@/components/ui/sheet"; +import docuConfig from "@/docu.json"; // Import JSON + +export function Navbar() { + const { social } = docuConfig; // Extract navbar and social from JSON + + return ( + + ); +} + +export function Logo() { + const { navbar } = docuConfig; // Extract navbar from JSON + + return ( + + {navbar.logo.alt} +

{navbar.logoText}

+ + ); +} + +export function NavMenu({ isSheet = false }) { + const { navbar } = docuConfig; // Extract navbar from JSON + + return ( + <> + {navbar.menu.map((item) => { + const isExternal = item.href.startsWith("http"); + + const Comp = ( + + {item.title} + {isExternal && } + + ); + return isSheet ? ( + + {Comp} + + ) : ( + Comp + ); + })} + + ); +} diff --git a/components/pagination.tsx b/components/pagination.tsx new file mode 100644 index 0000000..4f0ba7e --- /dev/null +++ b/components/pagination.tsx @@ -0,0 +1,49 @@ +import { getPreviousNext } from "@/lib/markdown"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import Link from "next/link"; +import { buttonVariants } from "./ui/button"; + +export default function Pagination({ pathname }: { pathname: string }) { + const res = getPreviousNext(pathname); + + return ( +
+
+ {res.prev && ( + + + + Previous + + {res.prev.title} + + )} +
+
+ {res.next && ( + + + Next + + + {res.next.title} + + )} +
+
+ ); +} \ No newline at end of file diff --git a/components/scroll-to-top.tsx b/components/scroll-to-top.tsx new file mode 100644 index 0000000..220ebe3 --- /dev/null +++ b/components/scroll-to-top.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { ArrowUpIcon } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Button } from "./ui/button"; +import { cn } from "@/lib/utils"; + +export function ScrollToTop() { + const [show, setShow] = useState(false); + + useEffect(() => { + const handleScroll = () => { + // Check if user has scrolled to bottom + const scrolledToBottom = + window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100; + + if (scrolledToBottom) { + setShow(true); + } else { + setShow(false); + } + }; + + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + const scrollToTop = () => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }; + + return ( +
+
+ +
+
+ ); +} diff --git a/components/search.tsx b/components/search.tsx new file mode 100644 index 0000000..9e61ca3 --- /dev/null +++ b/components/search.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { ArrowUpIcon, ArrowDownIcon, CommandIcon, FileIcon, SearchIcon, CornerDownLeftIcon } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogFooter, + DialogTrigger, + DialogClose, + DialogTitle, +} from "@/components/ui/dialog"; +import { useEffect, useMemo, useState } from "react"; +import Anchor from "./anchor"; +import { advanceSearch, cn } from "@/lib/utils"; +import { ScrollArea } from "@/components/ui/scroll-area"; + +export default function Search() { + const [searchedInput, setSearchedInput] = useState(""); + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ((event.ctrlKey || event.metaKey) && event.key === "k") { + event.preventDefault(); + setIsOpen(true); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + const filteredResults = useMemo( + () => advanceSearch(searchedInput.trim()), + [searchedInput] + ); + + return ( +
+ { + if (!open) setSearchedInput(""); + setIsOpen(open); + }} + > + +
+ + +
+ + K +
+
+
+ + Search + + setSearchedInput(e.target.value)} + placeholder="Type something to search..." + autoFocus + className="h-14 px-6 bg-transparent border-b text-[14px] outline-none" + /> + + {filteredResults.length == 0 && searchedInput && ( +

+ No results found for{" "} + {`"${searchedInput}"`} +

+ )} + +
+ {filteredResults.map((item) => { + const level = (item.href.split("/").slice(1).length - + 1) as keyof typeof paddingMap; + const paddingClass = paddingMap[level]; + + return ( + + +
1 && "border-l pl-4" + )} + > + {" "} + {item.title} +
+
+
+ ); + })} +
+
+ +
+ + + + + + +

to navigate

+ + + +

to select

+ + esc + +

to close

+
+
+
+
+
+ ); +} + +const paddingMap = { + 1: "pl-2", + 2: "pl-4", + 3: "pl-10", + // Add more levels if needed +} as const; diff --git a/components/sublink.tsx b/components/sublink.tsx new file mode 100644 index 0000000..b81c684 --- /dev/null +++ b/components/sublink.tsx @@ -0,0 +1,85 @@ +import { EachRoute } from "@/lib/routes-config"; +import Anchor from "./anchor"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { cn } from "@/lib/utils"; +import { SheetClose } from "@/components/ui/sheet"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; + +export default function SubLink({ + title, + href, + items, + noLink, + level, + isSheet, +}: EachRoute & { level: number; isSheet: boolean }) { + const path = usePathname(); + const [isOpen, setIsOpen] = useState(level == 0); + + useEffect(() => { + if (path == href || path.includes(href)) setIsOpen(true); + }, [href, path]); + + const Comp = ( + + {title} + + ); + + const titleOrLink = !noLink ? ( + isSheet ? ( + {Comp} + ) : ( + Comp + ) + ) : ( +

{title}

+ ); + + if (!items) { + return
{titleOrLink}
; + } + + return ( +
+ + +
+ {titleOrLink} + + {!isOpen ? ( + + ) : ( + + )} + +
+
+ +
0 && "pl-4 border-l ml-1.5" + )} + > + {items?.map((innerLink) => { + const modifiedItems = { + ...innerLink, + href: `${href + innerLink.href}`, + level: level + 1, + isSheet, + }; + return ; + })} +
+
+
+
+ ); +} diff --git a/components/terminal.tsx b/components/terminal.tsx new file mode 100644 index 0000000..e3c2408 --- /dev/null +++ b/components/terminal.tsx @@ -0,0 +1,48 @@ +import { + AnimatedSpan, + Terminal, + TypingAnimation, + } from "@/components/ui/terminal"; + + export function NpxTerminal() { + return ( + + > npx @docubook/create@latest + + + Need to install the following packages: + + + + @docubook/create@1.4.0 + + + + Ok to proceed? (y) + + + + ✔ ? Enter a name for your project directory: (docubook) + + + + Creating a new Docubook project in /path/your/docubook from the starter branch... + + + + ✔ Docubook project successfully created in /path/your/docubook! + + + + Next Step + 1. Navigate to your project directory: cd docubook + 2. Install dependencies: npm install + 3. Start the development server: npm run dev + + + + Open the apps via browser http://localhost:3000. + + + ); + } diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx new file mode 100644 index 0000000..3079558 --- /dev/null +++ b/components/theme-toggle.tsx @@ -0,0 +1,40 @@ +"use client"; + +import * as React from "react"; +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export function ModeToggle() { + const { setTheme } = useTheme(); + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ); +} diff --git a/components/toc-observer.tsx b/components/toc-observer.tsx new file mode 100644 index 0000000..0709a81 --- /dev/null +++ b/components/toc-observer.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { getDocsTocs } from "@/lib/markdown"; +import clsx from "clsx"; +import Link from "next/link"; +import { useState, useRef, useEffect } from "react"; + +type Props = { data: Awaited> }; + +export default function TocObserver({ data }: Props) { + const [activeId, setActiveId] = useState(null); + const observer = useRef(null); + + useEffect(() => { + const handleIntersect = (entries: IntersectionObserverEntry[]) => { + const visibleEntry = entries.find((entry) => entry.isIntersecting); + if (visibleEntry) { + setActiveId(visibleEntry.target.id); + } + }; + + observer.current = new IntersectionObserver(handleIntersect, { + root: null, + rootMargin: "-20px 0px 0px 0px", + threshold: 0.1, + }); + + const elements = data.map((item) => + document.getElementById(item.href.slice(1)) + ); + + elements.forEach((el) => { + if (el && observer.current) { + observer.current.observe(el); + } + }); + + return () => { + if (observer.current) { + elements.forEach((el) => { + if (el) { + observer.current!.unobserve(el); + } + }); + } + }; + }, [data]); + + return ( +
+ {data.map(({ href, level, text }) => { + return ( + + {text} + + ); + })} +
+ ); +} diff --git a/components/toc.tsx b/components/toc.tsx new file mode 100644 index 0000000..099291a --- /dev/null +++ b/components/toc.tsx @@ -0,0 +1,21 @@ +import { getDocsTocs } from "@/lib/markdown"; +import TocObserver from "./toc-observer"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { ListIcon } from "lucide-react"; + +export default async function Toc({ path }: { path: string }) { + const tocs = await getDocsTocs(path); + + return ( +
+
+
+

On this page

+
+ + + +
+
+ ); +} diff --git a/components/typography.tsx b/components/typography.tsx new file mode 100644 index 0000000..86676ff --- /dev/null +++ b/components/typography.tsx @@ -0,0 +1,9 @@ +import { PropsWithChildren } from "react"; + +export function Typography({ children }: PropsWithChildren) { + return ( +
+ {children} +
+ ); +} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..24c788c --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/animated-shiny-text.tsx b/components/ui/animated-shiny-text.tsx new file mode 100644 index 0000000..f55fd20 --- /dev/null +++ b/components/ui/animated-shiny-text.tsx @@ -0,0 +1,40 @@ +import { CSSProperties, FC, ReactNode } from "react"; + +import { cn } from "@/lib/utils"; + +interface AnimatedShinyTextProps { + children: ReactNode; + className?: string; + shimmerWidth?: number; +} + +const AnimatedShinyText: FC = ({ + children, + className, + shimmerWidth = 100, +}) => { + return ( +

+ {children} +

+ ); +}; + +export default AnimatedShinyText; diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..760ec38 --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,37 @@ +"use client"; + +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; \ No newline at end of file diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..71a5c32 --- /dev/null +++ b/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>