diff --git a/components/docs/eternalcore/commands/commands-table-rsc.tsx b/components/docs/eternalcore/commands/commands-table-rsc.tsx
new file mode 100644
index 00000000..521159ba
--- /dev/null
+++ b/components/docs/eternalcore/commands/commands-table-rsc.tsx
@@ -0,0 +1,7 @@
+import { fetchEternalCoreData } from "@/lib/docs/eternalcore-data";
+import DynamicCommandsTable from "./dynamic-commands-table";
+
+export default async function CommandsTableRSC() {
+ const { commands } = await fetchEternalCoreData();
+ return ;
+}
diff --git a/components/docs/eternalcore/commands/dynamic-commands-table.tsx b/components/docs/eternalcore/commands/dynamic-commands-table.tsx
index a6fbad70..3b815707 100644
--- a/components/docs/eternalcore/commands/dynamic-commands-table.tsx
+++ b/components/docs/eternalcore/commands/dynamic-commands-table.tsx
@@ -1,12 +1,17 @@
"use client";
import { useCommands } from "@/components/docs/eternalcore/commands/hooks/use-commands";
+import type { CommandData } from "@/components/docs/eternalcore/commands/types";
import { CommandsSearchBar } from "./commands-search-bar";
import { CommandsTable } from "./commands-table";
-export default function DynamicCommandsTable() {
+interface DynamicCommandsTableProps {
+ initialData: CommandData[];
+}
+
+export default function DynamicCommandsTable({ initialData }: DynamicCommandsTableProps) {
const { commands, filteredCommands, searchQuery, loading, error, handleSearchChange } =
- useCommands();
+ useCommands(initialData);
if (error) {
return
Error: {error}
;
diff --git a/components/docs/eternalcore/commands/hooks/use-commands.ts b/components/docs/eternalcore/commands/hooks/use-commands.ts
index 6b45a621..1cb485a9 100644
--- a/components/docs/eternalcore/commands/hooks/use-commands.ts
+++ b/components/docs/eternalcore/commands/hooks/use-commands.ts
@@ -2,8 +2,7 @@
import { create, insert, type Orama, search } from "@orama/orama";
import { type ChangeEvent, useEffect, useState } from "react";
-
-import type { CommandData, EternalCoreData } from "@/components/docs/eternalcore/commands/types";
+import type { CommandData } from "@/components/docs/eternalcore/commands/types";
const commandSchema = {
name: "string",
@@ -14,41 +13,34 @@ const commandSchema = {
type CommandDB = Orama;
-const REGEX_LEADING_SLASH = /^\//;
-
-export function useCommands() {
- const [commands, setCommands] = useState([]);
- const [filteredCommands, setFilteredCommands] = useState([]);
+export function useCommands(initialData: CommandData[]) {
+ const [commands] = useState(initialData);
+ const [filteredCommands, setFilteredCommands] = useState(initialData);
const [searchQuery, setSearchQuery] = useState("");
const [db, setDb] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
- const load = async (): Promise => {
+ const initDb = async () => {
try {
setLoading(true);
- const all = await fetchAndParseCommands();
-
- setCommands(all);
- setFilteredCommands(all);
-
const orama = create({
schema: commandSchema,
});
- await insertCommands(orama, all);
+ await insertCommands(orama, initialData);
setDb(orama);
} catch (err) {
- const message = err instanceof Error ? err.message : "Unknown error";
+ const message = err instanceof Error ? err.message : "Failed to initialize search";
setError(message);
} finally {
setLoading(false);
}
};
- load();
- }, []);
+ initDb();
+ }, [initialData]);
const handleSearch = async (q: string): Promise => {
setSearchQuery(q);
@@ -84,42 +76,6 @@ export function useCommands() {
};
}
-async function fetchAndParseCommands(): Promise {
- const res = await fetch(
- "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_documentation.json"
- );
-
- if (!res.ok) {
- throw new Error("Failed to fetch data");
- }
-
- const data = (await res.json()) as unknown as EternalCoreData;
-
- const commandsList =
- data.commands?.map((c) => ({
- name: `/${c.name.trim()}`,
- permission: c.permissions?.[0] ?? "-",
- description: c.descriptions?.[0] ?? "-",
- arguments: c.arguments?.join(", ") ?? "-",
- })) ?? [];
-
- const permsList =
- data.permissions?.map((p) => ({
- name: p.name || "Unknown",
- permission: p.permissions?.[0] ?? "-",
- description: p.descriptions?.[0] ?? "-",
- arguments: "-",
- })) ?? [];
-
- return [...commandsList, ...permsList].sort((a, b) =>
- a.name
- .replace(REGEX_LEADING_SLASH, "")
- .localeCompare(b.name.replace(REGEX_LEADING_SLASH, ""), "pl", {
- sensitivity: "base",
- })
- );
-}
-
async function insertCommands(orama: CommandDB, commands: CommandData[]) {
for (const c of commands) {
await insert(orama, c);
diff --git a/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx b/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx
index fe34e119..136e1757 100644
--- a/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx
+++ b/components/docs/eternalcore/placeholder/dynamic-placeholders-table.tsx
@@ -1,12 +1,17 @@
"use client";
import { usePlaceholders } from "@/components/docs/eternalcore/placeholder/hooks/use-placeholders";
+import type { Placeholder } from "@/components/docs/eternalcore/placeholder/types";
import { DynamicNoPlaceholderMessage } from "./dynamic-no-placeholder-message";
import { PlaceholderCategoryButtons } from "./placeholder-category-buttons";
import { PlaceholderSearchBar } from "./placeholder-search-bar";
import { PlaceholderTable } from "./placeholder-table";
-export default function DynamicPlaceholdersTable() {
+interface DynamicPlaceholdersTableProps {
+ initialData: Placeholder[];
+}
+
+export default function DynamicPlaceholdersTable({ initialData }: DynamicPlaceholdersTableProps) {
const {
allPlaceholders,
viewablePlaceholders,
@@ -17,7 +22,7 @@ export default function DynamicPlaceholdersTable() {
loading,
handleSearchChange,
handleCategoryClick,
- } = usePlaceholders();
+ } = usePlaceholders(initialData);
if (error) {
return Error: {error}
;
diff --git a/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts b/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts
index ada2f83c..d24f2ded 100644
--- a/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts
+++ b/components/docs/eternalcore/placeholder/hooks/use-placeholders.ts
@@ -16,9 +16,9 @@ const placeholderSchema = {
type PlaceholderDB = Orama;
-export function usePlaceholders() {
- const [allPlaceholders, setAllPlaceholders] = useState([]);
- const [viewablePlaceholders, setViewablePlaceholders] = useState([]);
+export function usePlaceholders(initialData: Placeholder[]) {
+ const [allPlaceholders] = useState(initialData);
+ const [viewablePlaceholders, setViewablePlaceholders] = useState(initialData);
const [categories, setCategories] = useState([]);
const [activeCategory, setActiveCategory] = useState("All");
const [searchQuery, setSearchQuery] = useState("");
@@ -31,26 +31,11 @@ export function usePlaceholders() {
try {
setLoading(true);
- const response = await fetch(
- "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_placeholders.json"
- );
-
- if (!response.ok) {
- setError(`Failed to fetch data: ${response.statusText}`);
- return;
- }
-
- const data = (await response.json()) as Placeholder[];
- const sortedData = [...data].sort((a, b) => a.name.localeCompare(b.name, "pl"));
-
- setAllPlaceholders(sortedData);
- setViewablePlaceholders(sortedData);
-
- const uniqueCategories = Array.from(new Set(sortedData.map((p) => p.category))).sort();
+ const uniqueCategories = Array.from(new Set(initialData.map((p) => p.category))).sort();
setCategories(["All", ...uniqueCategories]);
const oramaDb = create({ schema: placeholderSchema });
- await Promise.all(sortedData.map((p) => insert(oramaDb, p)));
+ await Promise.all(initialData.map((p) => insert(oramaDb, p)));
setDb(oramaDb);
} catch (exception) {
@@ -61,7 +46,7 @@ export function usePlaceholders() {
};
initializeData();
- }, []);
+ }, [initialData]);
const filterPlaceholders = useCallback(
async (query = searchQuery, category = activeCategory) => {
diff --git a/components/docs/eternalcore/placeholder/placeholders-table-rsc.tsx b/components/docs/eternalcore/placeholder/placeholders-table-rsc.tsx
new file mode 100644
index 00000000..036e3b7b
--- /dev/null
+++ b/components/docs/eternalcore/placeholder/placeholders-table-rsc.tsx
@@ -0,0 +1,7 @@
+import { fetchEternalCoreData } from "@/lib/docs/eternalcore-data";
+import DynamicPlaceholdersTable from "./dynamic-placeholders-table";
+
+export default async function PlaceholdersTableRSC() {
+ const { placeholders } = await fetchEternalCoreData();
+ return ;
+}
diff --git a/components/ui/mdx/mdx-components.tsx b/components/ui/mdx/mdx-components.tsx
index 599601e6..ec883f24 100644
--- a/components/ui/mdx/mdx-components.tsx
+++ b/components/ui/mdx/mdx-components.tsx
@@ -1,8 +1,9 @@
import type { MDXComponents } from "mdx/types";
+import dynamic from "next/dynamic";
import type { ComponentProps, HTMLAttributes } from "react";
-import DynamicCommandsTable from "@/components/docs/eternalcore/commands/dynamic-commands-table";
-import DynamicPlaceholdersTable from "@/components/docs/eternalcore/placeholder/dynamic-placeholders-table";
+import CommandsTableRSC from "@/components/docs/eternalcore/commands/commands-table-rsc";
+import PlaceholdersTableRSC from "@/components/docs/eternalcore/placeholder/placeholders-table-rsc";
import { AlertBox } from "@/components/ui/alert-box";
import { Badge } from "@/components/ui/mdx/badge";
import { BeforeAfter, BeforeAfterItem } from "@/components/ui/mdx/before-after";
@@ -15,11 +16,14 @@ import { Divider } from "@/components/ui/mdx/divider";
import { FileTree, FileTreeItem } from "@/components/ui/mdx/file-tree";
import { Heading } from "@/components/ui/mdx/heading";
import { Inline } from "@/components/ui/mdx/inline";
-import { LinkPreview } from "@/components/ui/mdx/link-preview";
import { MdxImage } from "@/components/ui/mdx/mdx-image";
import { MdxLink } from "@/components/ui/mdx/mdx-link";
import { Step, Steps } from "@/components/ui/mdx/steps";
+const LinkPreview = dynamic(() =>
+ import("@/components/ui/mdx/link-preview").then((mod) => mod.LinkPreview)
+);
+
type HeadingProps = HTMLAttributes;
export const components: MDXComponents = {
@@ -75,8 +79,8 @@ export const components: MDXComponents = {
FileTree,
FileTreeItem,
LinkPreview,
- DynamicCommandsTable,
- DynamicPlaceholdersTable,
+ DynamicCommandsTable: CommandsTableRSC,
+ DynamicPlaceholdersTable: PlaceholdersTableRSC,
code: ({ children, ...props }: ComponentProps<"code">) => {
if (typeof children !== "string") {
diff --git a/lib/docs/eternalcore-data.ts b/lib/docs/eternalcore-data.ts
new file mode 100644
index 00000000..f3cf5806
--- /dev/null
+++ b/lib/docs/eternalcore-data.ts
@@ -0,0 +1,116 @@
+import "server-only";
+
+export interface CommandData {
+ name: string;
+ permission: string;
+ description: string;
+ arguments: string;
+}
+
+export interface PlaceholderData {
+ name: string;
+ description: string;
+ category: string;
+ example: string;
+ returnType: string;
+ requiresPlayer: boolean;
+}
+
+interface RawEternalCoreData {
+ commands?: Array<{
+ name: string;
+ permissions?: string[];
+ descriptions?: string[];
+ arguments?: string[];
+ }>;
+ permissions?: Array<{
+ name: string;
+ permissions?: string[];
+ descriptions?: string[];
+ }>;
+ placeholders?: Array<{
+ name: string;
+ description: string;
+ category?: string;
+ }>;
+}
+
+const REGEX_LEADING_SLASH = /^\//;
+
+export async function fetchEternalCoreData() {
+ const [commandsRes, placeholdersRes] = await Promise.all([
+ fetch(
+ "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_documentation.json",
+ {
+ next: {
+ revalidate: 3600,
+ tags: ["eternalcore-data"],
+ },
+ }
+ ),
+ fetch(
+ "https://raw.githubusercontent.com/EternalCodeTeam/EternalCore/refs/heads/master/raw_eternalcore_placeholders.json",
+ {
+ next: {
+ revalidate: 3600,
+ tags: ["eternalcore-placeholders"],
+ },
+ }
+ ),
+ ]);
+
+ if (!commandsRes.ok) {
+ throw new Error("Failed to fetch EternalCore commands data");
+ }
+
+ if (!placeholdersRes.ok) {
+ throw new Error("Failed to fetch EternalCore placeholders data");
+ }
+
+ const commandsData = (await commandsRes.json()) as RawEternalCoreData;
+ // biome-ignore lint/suspicious/noExplicitAny: External data source
+ const placeholdersData = (await placeholdersRes.json()) as any[];
+
+ const commandsList =
+ commandsData.commands?.map((c) => ({
+ name: `/${c.name.trim()}`,
+ permission: c.permissions?.[0] ?? "-",
+ description: c.descriptions?.[0] ?? "-",
+ arguments: c.arguments?.join(", ") ?? "-",
+ })) ?? [];
+
+ const permsList =
+ commandsData.permissions?.map((p) => ({
+ name: p.name || "Unknown",
+ permission: p.permissions?.[0] ?? "-",
+ description: p.descriptions?.[0] ?? "-",
+ arguments: "-",
+ })) ?? [];
+
+ const sortedCommands = [...commandsList, ...permsList].sort((a, b) =>
+ a.name
+ .replace(REGEX_LEADING_SLASH, "")
+ .localeCompare(b.name.replace(REGEX_LEADING_SLASH, ""), "pl", {
+ sensitivity: "base",
+ })
+ );
+
+ const placeholders =
+ placeholdersData?.map((p) => ({
+ name: p.name,
+ description: p.description,
+ category: p.category ?? "General",
+ example: p.example ?? "",
+ returnType: p.returnType ?? "String",
+ requiresPlayer: p.requiresPlayer ?? false,
+ })) ?? [];
+
+ const sortedPlaceholders = placeholders.sort((a, b: { name: string }) =>
+ a.name.localeCompare(b.name)
+ );
+
+ return {
+ commands: sortedCommands,
+ placeholders: sortedPlaceholders,
+ };
+}