import Link from "next/link"; import { notFound } from "next/navigation"; import { cookies } from "next/headers"; import { getDocument, getRelated, listRevisions, listDocuments } from "@mcpedia/core"; import { WEBHOOK_SECRET } from "@mcpedia/config"; import Markdown from "@/components/Markdown"; import DocForm from "@/components/DocForm"; import TOC from "@/components/TOC"; import DocActions from "@/components/DocActions"; import { classifyPath, extractFoldersForSection } from "@mcpedia/core"; import { getSectionMeta } from "@mcpedia/config"; import type { DocumentMeta } from "@mcpedia/core"; export const dynamic = "force-dynamic"; interface DocPageProps { params: Promise<{ section: string; slug: string[] }>; searchParams: Promise<{ edit?: string }>; } function getSectionInfo(section: string) { return getSectionMeta(section); } function calculateReadingTime(text: string): string { const words = text.trim().split(/\s+/).length; const minutes = Math.ceil(words / 200); return `${minutes} min read`; } function CustomFieldBadges({ doc, standardKeys, }: { doc: Record; standardKeys: Set; }) { const docRecord: Record = { ...doc } as Record; const customEntries = Object.entries(docRecord).filter( ([k, v]) => !standardKeys.has(k) && v !== undefined && v !== null && v !== "" && !k.startsWith("_"), ); if (customEntries.length === 0) return null; return (
{customEntries.map(([key, value]) => { const label = key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " "); let colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-secondary)]"; let icon = "๐Ÿท๏ธ"; let displayValue: string; if (typeof value === "number") { colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; icon = "๐ŸŽฏ"; displayValue = `${value} pts`; } else if (typeof value === "boolean") { colorClass = value ? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400" : "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400"; icon = value ? "โœ“" : "โœ•"; displayValue = value ? "Solved" : "Pending"; } else if (Array.isArray(value)) { colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; icon = "๐Ÿ—‚๏ธ"; displayValue = value.join(", "); } else { const v = String(value).toLowerCase(); if (/^(easy|simple|beginner)/i.test(v)) { colorClass = "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400"; icon = "๐ŸŸข"; displayValue = "Easy"; } else if (/^(medium|intermediate)/i.test(v)) { colorClass = "bg-amber-500/10 border-amber-500/30 text-amber-600 dark:text-amber-400"; icon = "๐ŸŸก"; displayValue = "Medium"; } else if (/^(hard|expert|advanced)/i.test(v)) { colorClass = "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400"; icon = "๐Ÿ”ด"; displayValue = "Hard"; } else if (/^(pwn|web|crypto|misc|forensic|reverse|binary)/i.test(v)) { colorClass = "bg-indigo-500/10 border-indigo-500/30 text-indigo-600 dark:text-indigo-300"; icon = "โšก"; displayValue = v.toUpperCase(); } else if (/ctf|def.?con|event/i.test(v)) { colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; icon = "๐Ÿ†"; displayValue = String(value); } else { displayValue = String(value); } } return ( {icon} {label}: {displayValue} ); })}
); } function FolderIndexPage({ section, slug, allDocs, }: { section: string; slug: string; allDocs: DocumentMeta[]; }) { const prefix = `${section}/${slug}/`; const folderDocPaths = allDocs .filter((d) => d.slug.startsWith(prefix)) .map((d) => ({ rel: d.slug.slice(prefix.length), doc: d, })) .sort((a, b) => a.rel.localeCompare(b.rel)); const immediateFolders = new Map(); const immediateDocs: DocumentMeta[] = []; for (const { rel, doc } of folderDocPaths) { const parts = rel.split("/"); if (parts.length === 1) { immediateDocs.push(doc); } else { const folder = parts[0]; const existing = immediateFolders.get(folder) || { docCount: 0 }; existing.docCount += 1; immediateFolders.set(folder, existing); } } const folders = [...immediateFolders.entries()].sort(([a], [b]) => a.localeCompare(b)); const sectionInfo = getSectionInfo(section); const folderName = slug.split("/").pop()!.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); return (
{/* Breadcrumb */} {/* Header card */}
๐Ÿ“

{folderName}

Directory path: {section}/{slug}

{immediateDocs.length + folders.length} item{folders.length + immediateDocs.length !== 1 ? "s" : ""} ๐Ÿ“„ Export PDF Report
{/* Subfolders */} {folders.length > 0 && (

Subfolders ({folders.length})

{folders.map(([folder, info]) => ( ๐Ÿ“
{folder.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")}
{info.docCount} document{info.docCount !== 1 ? "s" : ""}
))}
)} {/* Documents */} {immediateDocs.length > 0 && (

Documents ({immediateDocs.length})

{immediateDocs.map((doc) => (
๐Ÿ“„
{doc.title}
{doc.tags.length > 0 && (
{doc.tags.slice(0, 4).map((t) => ( #{t} ))}
)}
))}
)} {folders.length === 0 && immediateDocs.length === 0 && (

No documents found in this directory.

)}
); } export default async function DocPage({ params, searchParams }: DocPageProps) { const { section, slug } = await params; const { edit } = await searchParams; const fullSlug = `${section}/${slug.join("/")}`; const allDocs = await listDocuments(); const docSlugs = allDocs.map((d) => d.slug); const classification = classifyPath(docSlugs, fullSlug); if (classification === "folder") { return ; } const doc = await getDocument(fullSlug); if (!doc) notFound(); const cookieStore = await cookies(); const canEdit = cookieStore.get("mcpedia_admin")?.value != null; const STANDARD_KEYS = new Set([ "id", "slug", "title", "type", "section", "status", "author", "tags", "path", "createdAt", "updatedAt", "extraFields", "body", ]); if (edit === "1" && canEdit) { const existingFolders = extractFoldersForSection(docSlugs, doc.section); return (
โ† Back to document

Edit: {doc.title}

d.section)))} existingFolders={{ [doc.section]: existingFolders, }} initial={{ title: doc.title, body: doc.body, section: doc.section, type: doc.type, status: doc.status, tags: doc.tags, author: doc.author, extraFields: doc.extraFields, }} />
); } const related = await getRelated(fullSlug, 4); const revisions = await listRevisions(fullSlug, 10); const sectionInfo = getSectionInfo(doc.section); const readingTime = calculateReadingTime(doc.body); return (
{/* Main Content Column */}
{/* Breadcrumb */} {/* Hero Metadata Card */}
{sectionInfo.icon} {sectionInfo.label} ยท {doc.type.charAt(0).toUpperCase() + doc.type.slice(1)} ยท {readingTime}

{doc.title}

{/* Quick Actions */}
{/* Author + Date + Tags */}
{doc.author && (
{doc.author.charAt(0).toUpperCase()}
{doc.author} ยท
)} Updated {new Date(doc.updatedAt).toLocaleDateString(undefined, { month: "long", day: "numeric", year: "numeric", })} {doc.tags.length > 0 && ( <> ยท
{doc.tags.map((t) => ( #{t} ))}
)}
{/* Dynamic Badges */}
{/* Mobile TOC (rendered inline on smaller screens) */}
{/* Markdown content */}
{/* Related Documents */} {related.length > 0 && ( )} {/* Revision History */} {revisions.length > 0 && ( )}
{/* Desktop Sticky Table of Contents Column */}
); }