diff --git a/Frontend/bun.lockb b/Frontend/bun.lockb index cafcd5c..f936f11 100644 Binary files a/Frontend/bun.lockb and b/Frontend/bun.lockb differ diff --git a/Frontend/docs/home.md b/Frontend/docs/home.md index cd15e51..0d680ad 100644 --- a/Frontend/docs/home.md +++ b/Frontend/docs/home.md @@ -4,17 +4,131 @@ published: '04-19-2024' summary: 'Get started with RESTfulMC! duis numquam himenaeos lectus quisque assueverit aperiri' --- -# Get started with RESTfulMC! -Welcome to the RESTfulMC documentation! feugait pertinax duis laudem vix integer tempus conubia graece interpretaris +[//]: # (# Get started with RESTfulMC!) -## Table of Contents -- [Get started with RESTfulMC!](#get-started-with-restfulmc) - - [Features](#features) +[//]: # (Welcome to the RESTfulMC documentation! feugait pertinax duis laudem vix integer tempus conubia graece interpretaris) -## Features -Some of the core features of RESTfulMC include: +[//]: # () +[//]: # (## Table of Contents) -| Feature | Description | -|-------------------------:|------------------------------------------------------------------:| -| [Player Lookup](/player) | praesent omittam mollis maximus has pretium mediocritatem eripuit | -| [Server Lookup](/server) | sapien faucibus numquam ponderum utamur himenaeos menandri tation | \ No newline at end of file +[//]: # (- [Get started with RESTfulMC!](#get-started-with-restfulmc)) + +[//]: # ( - [Features](#features)) + +[//]: # () +[//]: # (## Features) + +[//]: # (Some of the core features of RESTfulMC include:) + +[//]: # () +[//]: # (| Feature | Description |) + +[//]: # (|-------------------------:|------------------------------------------------------------------:|) + +[//]: # (| [Player Lookup](/player) | praesent omittam mollis maximus has pretium mediocritatem eripuit |) + +[//]: # (| [Server Lookup](/server) | sapien faucibus numquam ponderum utamur himenaeos menandri tation |) +# Heading level 1 +I really like using Markdown. + +```typescript +"use client"; + +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { type ThemeProviderProps } from "next-themes/dist/types"; + +/** + * The provider of themes!! + */ +const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => ( + {children} +); +export default ThemeProvider; +``` + + +I think I'll use it to format all of my documents from now on. + +## Heading level 2 +### Heading level 3 +#### Heading level 4 +##### Heading level 5 +###### Heading level 6 + +Heading level 1 +=============== + +Heading level 2 +--------------- + +I just love **bold text**. +I just love __bold text__. + +This text is ***really important***. +This text is ___really important___. +This text is __*really important*__. +This text is **_really important_**. +This is really***very***important text. + +> Dorothy followed her through many of the beautiful rooms in her castle. +> +> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. + +> Dorothy followed her through many of the beautiful rooms in her castle. +> +>> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. + +> #### The quarterly results look great! +> +> - Revenue was off the chart. +> - Profits were higher than ever. + > + > *Everything* is going according to **plan**. + +1. First item +2. Second item +3. Third item +4. Fourth item + +1. First item +2. Second item +3. Third item + 1. Indented item + 2. Indented item +4. Fourth item + +- First item +- Second item +- Third item +- Fourth item + +* First item +* Second item +* Third item +* Fourth item + ++ First item ++ Second item ++ Third item ++ Fourth item + +- First item +- Second item +- Third item + - Indented item + - Indented item +- Fourth item + +At the command prompt, type `nano`. + +``Use `code` in your Markdown file.`` + + + +*** + +--- + +_________________ + +My favorite search engine is [Duck Duck Go](https://duckduckgo.com). \ No newline at end of file diff --git a/Frontend/docs/server/query.md b/Frontend/docs/server/query.md new file mode 100644 index 0000000..122a865 --- /dev/null +++ b/Frontend/docs/server/query.md @@ -0,0 +1,8 @@ +--- +title: 'Query a server' +published: '04-19-2024' +summary: 'utinam delicata nominavi ornare eirmod pharetra decore interesset necessitatibus.' +--- + +# bob +HELLO JOHN \ No newline at end of file diff --git a/Frontend/package.json b/Frontend/package.json index b177c09..57beb3f 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -24,6 +24,7 @@ "@mdx-js/react": "^3.0.1", "@next/mdx": "^14.2.2", "@radix-ui/react-context-menu": "^2.1.5", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-scroll-area": "^1.0.5", @@ -36,6 +37,7 @@ "class-variance-authority": "^0.7.0", "clipboard-copy": "^4.0.1", "clsx": "^2.1.0", + "fuse.js": "^7.0.0", "lucide-react": "^0.372.0", "moment": "^2.30.1", "next": "14.2.2", diff --git a/Frontend/src/app/(pages)/docs/[[...slug]]/page.tsx b/Frontend/src/app/(pages)/docs/[[...slug]]/page.tsx index f3ebee7..de220b2 100644 --- a/Frontend/src/app/(pages)/docs/[[...slug]]/page.tsx +++ b/Frontend/src/app/(pages)/docs/[[...slug]]/page.tsx @@ -71,7 +71,7 @@ const ContentPage = ({ params }: PageProps): ReactElement => { {/* Publish Date */} -

+

Published on{" "} {moment(content.published, "MM-DD-YYYY").format( "MMMM Do YYYY" diff --git a/Frontend/src/app/(pages)/mojang/page.tsx b/Frontend/src/app/(pages)/mojang/page.tsx index dca65c2..773fe6d 100644 --- a/Frontend/src/app/(pages)/mojang/page.tsx +++ b/Frontend/src/app/(pages)/mojang/page.tsx @@ -38,7 +38,7 @@ const MojangStatusPage = async (): Promise => { {/* Header */}

diff --git a/Frontend/src/app/(pages)/player/[[...slug]]/page.tsx b/Frontend/src/app/(pages)/player/[[...slug]]/page.tsx index 4c76079..2facfcb 100644 --- a/Frontend/src/app/(pages)/player/[[...slug]]/page.tsx +++ b/Frontend/src/app/(pages)/player/[[...slug]]/page.tsx @@ -33,7 +33,7 @@ const PlayerPage = async ({ params }: PageProps): Promise => {

diff --git a/Frontend/src/app/(pages)/server/[[...slug]]/page.tsx b/Frontend/src/app/(pages)/server/[[...slug]]/page.tsx index bcdf55e..292f67a 100644 --- a/Frontend/src/app/(pages)/server/[[...slug]]/page.tsx +++ b/Frontend/src/app/(pages)/server/[[...slug]]/page.tsx @@ -47,7 +47,7 @@ const ServerPage = async ({ params }: PageProps): Promise => {

diff --git a/Frontend/src/app/api/docs/search/route.ts b/Frontend/src/app/api/docs/search/route.ts new file mode 100644 index 0000000..f33b9a4 --- /dev/null +++ b/Frontend/src/app/api/docs/search/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from "next/server"; +import { DOCS_SEARCH_INDEX } from "@/lib/search"; +import { FuseResult } from "fuse.js"; + +export const GET = async (request: NextRequest): Promise => { + const query: string | null = request.nextUrl.searchParams.get("query"); // The query to search for + + // Ensure the query is valid + if (!query || query.length < 3 || query.length > 64) { + return new NextResponse( + JSON.stringify({ error: "Invalid query given" }), + { status: 400 } + ); + } + + // Return the results of the search + return new NextResponse( + JSON.stringify( + DOCS_SEARCH_INDEX.search(query, { limit: 5 }).map( + (result: FuseResult) => { + return { + slug: result.item.slug, + title: result.item.title, + summary: result.item.summary, + }; + } + ) + ) + ); +}; diff --git a/Frontend/src/app/common/search.ts b/Frontend/src/app/common/search.ts new file mode 100644 index 0000000..180abec --- /dev/null +++ b/Frontend/src/app/common/search.ts @@ -0,0 +1,14 @@ +import Fuse from "fuse.js"; +import { getDocsContent } from "@/lib/mdxUtils"; + +/** + * The fuse index for searching the docs. + */ +export const DOCS_SEARCH_INDEX: Fuse = new Fuse( + getDocsContent(), + { + keys: ["title", "summary"], + includeScore: true, + threshold: 0.5, + } +); diff --git a/Frontend/src/app/components/counter.tsx b/Frontend/src/app/components/counter.tsx index 8914f49..d14751a 100644 --- a/Frontend/src/app/components/counter.tsx +++ b/Frontend/src/app/components/counter.tsx @@ -31,6 +31,7 @@ type CounterProps = { /** * A counter component. * + * @param name the name of the counter * @param amount the amount to count up to * @param duration the optional duration of the count up * @returns the counter jsx diff --git a/Frontend/src/app/components/docs/search/search-dialog.tsx b/Frontend/src/app/components/docs/search/search-dialog.tsx new file mode 100644 index 0000000..1fe3c58 --- /dev/null +++ b/Frontend/src/app/components/docs/search/search-dialog.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { + AnchorHTMLAttributes, + ChangeEvent, + HTMLAttributes, + ReactElement, + useState, +} from "react"; +import { + DialogClose, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import Link from "next/link"; + +/** + * Content for the search dialog. + * + * @return the content jsx + */ +const SearchDialogContent = (): ReactElement => { + const [label, setLabel] = useState(undefined); + const [results, setResults] = useState( + undefined + ); // The search results + + /** + * Search the docs with the given query. + */ + const search = async ( + event: ChangeEvent + ): Promise => { + const query: string = event.target.value; // Get the query to search for + const tooShort: boolean = query.length < 3; + + // No query or too short + if (!query || tooShort || query.length > 64) { + // Display warning + if (query) { + setLabel( + tooShort + ? "Please enter at least 3 characters" + : "Your input is too long" + ); + } + setResults(undefined); + return; + } + const response: Response = await fetch( + `/api/docs/search?query=${query}` + ); // Search the docs + setLabel(undefined); // Clear the label + setResults((await response.json()) as DocsContentMetadata[]); + }; + + // Render the contents + return ( + <> + {/* Header */} + + Quick Search + + Quickly find the documentation you're looking for by + typing in a few terms. + + + {/* Query Input */} +
+ + +
+
+ + {/* Results */} +
+ {results?.length === 0 && ( +

No Results

+ )} + {results?.map( + ( + result: DocsContentMetadata, + index: number + ): ReactElement => ( + + ) + )} +
+ + {/* Footer */} + + + + + + + ); +}; + +/** + * The props for a search result entry. + */ +type SearchResultEntryProps = { + /** + * The search result to display. + */ + result: DocsContentMetadata; +}; + +/** + * A search result entry. + * + * @param result the result to display + * @param props the additional props + * @return the result jsx + */ +const SearchResultEntry = ({ + result, + ...props +}: AnchorHTMLAttributes & + SearchResultEntryProps): ReactElement => ( + +

{result.title}

+

{result.summary}

+ +); + +export default SearchDialogContent; diff --git a/Frontend/src/app/components/docs/search/search-input.tsx b/Frontend/src/app/components/docs/search/search-input.tsx new file mode 100644 index 0000000..9257cff --- /dev/null +++ b/Frontend/src/app/components/docs/search/search-input.tsx @@ -0,0 +1,37 @@ +import { ReactElement } from "react"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import SearchDialogContent from "@/components/docs/search/search-dialog"; + +/** + * The quick search component. + * + * @return the search jsx + */ +const QuickSearch = (): ReactElement => ( + + + {/* Button to open search */} +
+ +
+ + +
+ + + +
+); +export default QuickSearch; diff --git a/Frontend/src/app/components/docs/sidebar.tsx b/Frontend/src/app/components/docs/sidebar.tsx index dbdef87..88a194f 100644 --- a/Frontend/src/app/components/docs/sidebar.tsx +++ b/Frontend/src/app/components/docs/sidebar.tsx @@ -1,10 +1,10 @@ import { ReactElement } from "react"; -import { Input } from "@/components/ui/input"; -import { getDocsContent } from "@/lib/mdxUtils"; import Link from "next/link"; import { cn } from "@/lib/utils"; import { capitalize } from "@/lib/stringUtils"; import { minecrafter } from "@/font/fonts"; +import { getDocsContent } from "@/lib/mdxUtils"; +import QuickSearch from "@/components/docs/search/search-input"; /** * The sidebar for the docs page. @@ -23,13 +23,8 @@ const Sidebar = ({ activeSlug }: { activeSlug: string }): ReactElement => { return (
- {/* Search */} - + {/* Quick Search */} + {/* Links */}
@@ -45,7 +40,7 @@ const Sidebar = ({ activeSlug }: { activeSlug: string }): ReactElement => { {/* Category */}

diff --git a/Frontend/src/app/components/footer.tsx b/Frontend/src/app/components/footer.tsx index d7aec28..7c21505 100644 --- a/Frontend/src/app/components/footer.tsx +++ b/Frontend/src/app/components/footer.tsx @@ -21,7 +21,7 @@ const Footer = (): ReactElement => ( >
{/* Branding */} -
+
{/* Logo & Site Name */}
( {/* Header */}

diff --git a/Frontend/src/app/components/landing/hero.tsx b/Frontend/src/app/components/landing/hero.tsx index a2af413..1521b4d 100644 --- a/Frontend/src/app/components/landing/hero.tsx +++ b/Frontend/src/app/components/landing/hero.tsx @@ -13,7 +13,7 @@ import { ReactElement } from "react"; */ const Hero = (): ReactElement => (
-
+
{/* Title */}

(
-
+
diff --git a/Frontend/src/app/components/ui/dialog.tsx b/Frontend/src/app/components/ui/dialog.tsx new file mode 100644 index 0000000..51abff2 --- /dev/null +++ b/Frontend/src/app/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/Frontend/src/app/not-found.tsx b/Frontend/src/app/not-found.tsx index e5b3efc..1c71da9 100644 --- a/Frontend/src/app/not-found.tsx +++ b/Frontend/src/app/not-found.tsx @@ -9,7 +9,7 @@ import { ReactElement } from "react"; * @returns the page jsx */ const NotFoundPage = (): ReactElement => ( -
+
{/* Creeper */}