diff --git a/.gitignore b/.gitignore index 92b3722..7f768ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# vscode + +.vscode/settings.json + # jetbrains .idea diff --git a/bun.lock b/bun.lock index c33e363..01e7014 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@icons-pack/react-simple-icons": "^13.7.0", "@shikijs/types": "^3.11.0", + "feed": "^5.1.0", "fumadocs-core": "15.8.5", "fumadocs-mdx": "12.0.3", "fumadocs-ui": "15.8.5", @@ -832,6 +833,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "feed": ["feed@5.1.0", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-qGNhgYygnefSkAHHrNHqC7p3R8J0/xQDS/cYUud8er/qD9EFGWyCdUDfULHTJQN1d3H3WprzVwMc9MfB4J50Wg=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -1358,6 +1361,8 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], @@ -1524,6 +1529,8 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="], + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], diff --git a/content/blog/future-plans.mdx b/content/blog/future-plans.mdx new file mode 100644 index 0000000..59fcdbf --- /dev/null +++ b/content/blog/future-plans.mdx @@ -0,0 +1,69 @@ +--- +title: Future and current plans for our plugins +description: In this post we are discussing what plugins we are currently working on and what the plans for the future are +author: NonSwag +category: updates +keywords: + [ + "minecraft plugins", + "plugin development", + "Portals plugin", + "Holograms plugin", + "PerWorlds plugin", + "minecraft server tools", + "plugin news", + "plugin roadmap", + "minecraft development" + ] +--- + +# What are we currently working on? + +## Portals + +As some of you may already know, we started working on a new plugin called [Portals](https://github.com/TheNextLvl-net/portals). +The API already works but commands and actual functionality is yet to come. +Beta builds will probably arrive within the next couple of weeks, so stay tuned. + +## Holograms + +_Previously known as HologramAPI_ + +HologramAPI got a huge rebranding and is now called [Holograms](https://github.com/TheNextLvl-net/holograms). +Instead of just an API developers could use to create holograms, +it is now going to be a proper plugin, everyone can use and enjoy. + +There already is an alpha build out, but only to supersede the discontinued HologramAPI. +The plugin still lacks all commands and interaction points from within the game, +but hopefully we are able to ship the first usable release this year. + +## PerWorlds + +A long requested feature is currently in the making: **Data import from other plugins.** +_(e.g. Multiverse-Inventories, MultiInv…)_ + +--- + +# Our future plans and ideas + +## Broader version support + +A big critique point we are often facing is about the game versions we support. +To make development and support easier for ourselves we generally only support the most recent game version. +But with Mojang dropping more and more updates in a shorter period of time, the amount of users we leave behind is growing with every new version. +From a user standpoint I can see how you are not always able to be on the most recent version, especially because of plugins that still want to support Spigot. + +The idea now is to only support the last 3 most recent major versions of the game. +(At the time of writing 1.21.10 is the latest version, meaning 1.21.8 and 1.21.6 would also still be supported) + +This comes with a few technical challenges and downsides, we might not be able to immediately support new features as they come out. +But on the other hand when a feature finally gets introduced it will be better tested, developed and more stable. + +## Backwards compatibility + +This is a tough topic for many developers… +Personally I always said that no backwards compatibility will ever be provided – BUT, I saw that a lot of users of our plugins are still on 1.21.4 +(especially with the plugin Worlds) + +Due to that we are planning on **TEMPORARILY** bringing back compatibility for that specific version. +This does not inherently mean you are always getting new features, **only bug fixes**. \ No newline at end of file diff --git a/package.json b/package.json index a4c72ae..dcb7b7c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@icons-pack/react-simple-icons": "^13.7.0", "@shikijs/types": "^3.11.0", + "feed": "^5.1.0", "fumadocs-core": "15.8.5", "fumadocs-mdx": "12.0.3", "fumadocs-ui": "15.8.5", diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..976b765 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..4f087b3 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..05ea6bb Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..f84f95a Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..1b474de Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico index a4b9b4c..0357f0f 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/source.config.ts b/source.config.ts index 1a38692..99db49f 100644 --- a/source.config.ts +++ b/source.config.ts @@ -1,6 +1,13 @@ -import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from "fumadocs-mdx/config" +import { + defineConfig, + defineDocs, + defineCollections, + frontmatterSchema, + metaSchema, +} from "fumadocs-mdx/config" import { transformerCommandColor } from "./src/lib/command-transformer" import { remarkMdxMermaid } from "fumadocs-core/mdx-plugins" +import { z } from "zod" // You can customise Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.vercel.app/docs/mdx/collections#define-docs @@ -13,6 +20,16 @@ export const docs = defineDocs({ }, }) +export const blogPosts = defineCollections({ + type: "doc", + dir: "content/blog", + schema: frontmatterSchema.extend({ + author: z.string(), + category: z.enum(["devlog", "updates", "other"]), + keywords: z.string().array().optional(), + }), +}) + export default defineConfig({ mdxOptions: { rehypeCodeOptions: { diff --git a/src/app/(home)/blog/[slug]/page.tsx b/src/app/(home)/blog/[slug]/page.tsx new file mode 100644 index 0000000..79a527e --- /dev/null +++ b/src/app/(home)/blog/[slug]/page.tsx @@ -0,0 +1,104 @@ +import { lastEdit } from "@/lib/api" +import { blog } from "@/lib/source" +import { getMDXComponents } from "@/mdx-components" +import { PathUtils } from "fumadocs-core/source" +import type { Metadata } from "next" +import Link from "next/link" +import { notFound } from "next/navigation" + +export async function generateMetadata({ params }: PageProps<"/blog/[slug]">): Promise { + const { slug } = await params + const page = blog.getPage([slug]) + + return { + title: page?.data.title, + description: page?.data.description, + keywords: page?.data.keywords, + } +} + +export default async function Page(props: PageProps<"/blog/[slug]">) { + const params = await props.params + const page = blog.getPage([params.slug]) + if (!page) notFound() + const { body: Mdx, toc } = page.data + return ( + <> +
+
+
+
+
+ +

+ {page?.data.title} +

+ {page?.data.description && ( +

+ {page?.data.description} +

+ )} +
+ {page.data.author && ( +
+ By + {page.data.author} +
+ )} +
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+ {toc.length > 0 && ( + + )} +
+
+
+ + ) +} diff --git a/src/app/(home)/blog/page.tsx b/src/app/(home)/blog/page.tsx new file mode 100644 index 0000000..4d60058 --- /dev/null +++ b/src/app/(home)/blog/page.tsx @@ -0,0 +1,46 @@ +"use server" + +import { blog } from "@/lib/source" +import { PathUtils } from "fumadocs-core/source" +import { BlogSidebar } from "@/components/blog/blog-sidebar" +import { lastEdit } from "@/lib/api" + +function getName(path: string) { + return PathUtils.basename(path, PathUtils.extname(path)) +} + +export default async function Blog() { + const pages = blog.getPages() + + const postsWithDates = await Promise.all( + pages.map(async (page) => ({ + page, + lastEditDate: await lastEdit(page), + })), + ) + + const posts = postsWithDates.sort( + (a, b) => + new Date(b.lastEditDate ?? getName(b.page.path)).getTime() - + new Date(a.lastEditDate ?? getName(a.page.path)).getTime(), + ) + + const serializablePosts = await Promise.all( + posts.map(async (p) => ({ + path: p.page.path, + data: { + title: p.page.data.title, + description: p.page.data.description, + category: p.page.data.category, + url: p.page.url, + date: p.lastEditDate, + }, + })), + ) + + return ( + <> + + + ) +} diff --git a/src/app/(home)/layout.tsx b/src/app/(home)/layout.tsx index 6581027..193cd39 100644 --- a/src/app/(home)/layout.tsx +++ b/src/app/(home)/layout.tsx @@ -2,17 +2,24 @@ import type { ReactNode } from "react" import { HomeLayout } from "fumadocs-ui/layouts/home" import { baseOptions } from "@/app/layout.config" import { SiDiscord } from "@icons-pack/react-simple-icons" +import { RssIcon } from "lucide-react" export default function Layout({ children }: { children: ReactNode }) { return ( , + text: "Blog", + url: "/blog", + }, { type: "icon", icon: , text: "Discord", - url: "https://thenextlvl.net/discord", + url: "/discord", }, ]} > diff --git a/src/app/layout.config.tsx b/src/app/layout.config.tsx index 450ce56..2b64c3b 100644 --- a/src/app/layout.config.tsx +++ b/src/app/layout.config.tsx @@ -1,6 +1,7 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared" import Image from "next/image" import { SiDiscord } from "@icons-pack/react-simple-icons" +import { RssIcon } from "lucide-react" /** * Shared layout configurations * @@ -18,6 +19,13 @@ export const baseOptions: BaseLayoutProps = { ), }, links: [ + { + type: "icon", + label: "Blog", + icon: , + text: "Blog", + url: "/blog", + }, { type: "icon", label: "Discord", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a59637f..efebf25 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -14,7 +14,14 @@ export const metadata: Metadata = { description: "TheNextLvl is a community-driven Minecraft server that provides a safe and fun environment for players of all ages.", icons: { - icon: "/favicon.ico", + icon: [ + { url: "/favicon.ico" }, + { url: "/favicon-16x16.png", sizes: "16x16" }, + { url: "/favicon-32x32.png", sizes: "32x32" }, + { url: "/android-chrome-192x192.png", sizes: "192x192" }, + { url: "/android-chrome-512x512.png", sizes: "512x512" }, + ], + apple: [{ url: "/apple-touch-icon.png" }], }, } diff --git a/src/app/rss.xml/route.ts b/src/app/rss.xml/route.ts new file mode 100644 index 0000000..77ee1ae --- /dev/null +++ b/src/app/rss.xml/route.ts @@ -0,0 +1,7 @@ +import { getRSS } from "@/lib/rss" + +export const revalidate = false + +export async function GET() { + return new Response(await getRSS()) +} diff --git a/src/components/blog/blog-sidebar.tsx b/src/components/blog/blog-sidebar.tsx new file mode 100644 index 0000000..f368c85 --- /dev/null +++ b/src/components/blog/blog-sidebar.tsx @@ -0,0 +1,138 @@ +"use client" + +import Link from "next/link" +import { useState } from "react" +import { BookOpen, Code, RotateCcw, FileText, File } from "lucide-react" + +type BlogPost = { + path: string + data: { + title: string + description?: string + category?: "devlog" | "updates" | "other" + date?: string | Date + url: string + } +} + +type BlogSidebarProps = { + posts: BlogPost[] +} + +export function BlogSidebar({ posts }: BlogSidebarProps) { + const categories = [ + "All Posts", + ...[...new Set(posts.map((post) => post.data.category))].filter( + (category): category is "devlog" | "updates" | "other" => Boolean(category), + ), + ] + + const [selectedCategory, setSelectedCategory] = useState("All Posts") + + const getCategoryIcon = (category: string) => { + switch (category) { + case "All Posts": + return + case "devlog": + return + case "updates": + return + case "other": + return + default: + return + } + } + + const filteredPosts = + selectedCategory === "All Posts" + ? posts + : posts.filter((post) => post.data.category === selectedCategory) + + return ( + <> +
+
+
+

+ Blog +

+

+ Updates, devlogs, and notes from the team. +

+
+
+
+ +
+
+
+
+ {filteredPosts.map((post) => ( + +
+

+ {post.data.title} +

+ {post.data.description && ( +

+ {post.data.description} +

+ )} +
+ + {getCategoryIcon(post.data.category ?? "All Posts")}{" "} + {post.data.category ?? "other"} + +
+ +
+
+ + ))} +
+ + +
+
+
+ + ) +} diff --git a/src/lib/api.ts b/src/lib/api.ts index 2070ce3..1b1edaa 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -25,10 +25,10 @@ export async function lastEdit(page: Page): Promise { owner: "TheNextLvl-net", repo: "docs", token: process.env.GITHUB_TOKEN, - path: `content/docs/${page.file.path}`, + path: `content/docs/${page.path}`, }).catch((error) => { console.error("Error fetching last edit date:", error) - return new Date(0) + return new Date() }) return time ? time : new Date() } diff --git a/src/lib/command-transformer.ts b/src/lib/command-transformer.ts index 664dbbd..51ebbd6 100644 --- a/src/lib/command-transformer.ts +++ b/src/lib/command-transformer.ts @@ -24,7 +24,7 @@ function detectRanges(code: string): MetaRanges { const re = /<([A-Za-z_+-]+)>([\s\S]*?)<\/\1>/g let match: RegExpExecArray | null while ((match = re.exec(code)) !== null) { - const [full, colorName, inner] = match + const [full, colorName] = match const openStart = match.index const openEnd = openStart + `<${colorName}>`.length const closeEnd = openStart + full.length diff --git a/src/lib/rss.ts b/src/lib/rss.ts new file mode 100644 index 0000000..addcd3b --- /dev/null +++ b/src/lib/rss.ts @@ -0,0 +1,36 @@ +import { Feed } from "feed" +import { blog } from "@/lib/source" +import { lastEdit } from "@/lib/api" + +const baseUrl = "https://thenextlvl.net" + +export async function getRSS() { + const feed = new Feed({ + title: "TheNextLvl Blog", + id: `${baseUrl}/blog`, + link: `${baseUrl}/blog`, + language: "en", + description: + "Our blog where we post about more or less technical stuff regarding our plugins and minecraft in general", + image: `${baseUrl}/banner.png`, + favicon: `${baseUrl}/icon.png`, + copyright: "All rights reserved 2025, TheNextLvl", + }) + + for (const page of blog.getPages()) { + feed.addItem({ + id: page.url, + title: page.data.title, + description: page.data.description, + link: `${baseUrl}${page.url}`, + date: await lastEdit(page), + author: [ + { + name: page.data.author, + }, + ], + }) + } + + return feed.rss2() +} diff --git a/src/lib/source.ts b/src/lib/source.ts index fc86c25..5a5a974 100644 --- a/src/lib/source.ts +++ b/src/lib/source.ts @@ -1,14 +1,13 @@ -import { docs } from "@/.source" +import { docs, blogPosts } from "@/.source" import { loader } from "fumadocs-core/source" -import { createElement } from "react" -import { icons } from "lucide-react" +import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons" +import { createMDXSource } from "fumadocs-mdx/runtime/next" export const source = loader({ baseUrl: "/docs", source: docs.toFumadocsSource(), - icon(icon) { - if (!icon) { - return - } - if (icon in icons) return createElement(icons[icon as keyof typeof icons]) - }, + plugins: [lucideIconsPlugin()], +}) + +export const blog = loader(createMDXSource(blogPosts), { + baseUrl: "/blog", })