import Link from "next/link"; import { notFound } from "next/navigation"; import { cookies } from "next/headers"; import { getDocument, getRelated, listRevisions, listDocuments, getExportDocuments } 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 PdfExportView from "@/components/PdfExportView"; import { classifyPath, extractFoldersForSection } from "@mcpedia/core"; import { getSectionMeta } from "@mcpedia/config"; import type { DocumentMeta } from "@mcpedia/core"; import { Folder, FileText, Clock, History, FileDown, ArrowLeft, Sparkles, CheckCircle2, XCircle, Target, Trophy, Zap, Tag, } from "lucide-react"; export const dynamic = "force-dynamic"; interface DocPageProps { params: Promise<{ section: string; slug: string[] }>; searchParams: Promise<{ edit?: string; path?: string; sort?: 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 displayValue: string; let IconComponent = Tag; if (typeof value === "number") { colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-primary)] font-semibold"; IconComponent = Target; 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 font-semibold" : "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400 font-semibold"; IconComponent = value ? CheckCircle2 : XCircle; displayValue = value ? "Solved" : "Pending"; } else if (Array.isArray(value)) { colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-secondary)]"; IconComponent = Folder; 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 font-semibold"; 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 font-semibold"; 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 font-semibold"; displayValue = "Hard"; } else if (/^(pwn|web|crypto|misc|forensic|reverse|binary)/i.test(v)) { colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-primary)] font-bold"; IconComponent = Zap; displayValue = v.toUpperCase(); } else if (/ctf|def.?con|event/i.test(v)) { colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-primary)]"; IconComponent = Trophy; displayValue = String(value); } else { displayValue = String(value); } } return ( {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 Navigation */} {/* Header Card */}

{folderName}

Directory: {section}/{slug}

{immediateDocs.length + folders.length} item{folders.length + immediateDocs.length !== 1 ? "s" : ""} Export PDF
{/* Subfolders Grid */} {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 List */} {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 in this directory.

)}
); } export default async function DocPage({ params, searchParams }: DocPageProps) { const { section, slug } = await params; const searchParamsObj = await searchParams; const { edit, path: queryPath, sort } = searchParamsObj; // Handle direct export views if (section === "export" || (slug && slug[slug.length - 1] === "export")) { const isTrailingExport = slug && slug[slug.length - 1] === "export"; const targetSlug = isTrailingExport ? slug.slice(0, -1) : slug; const targetPath = queryPath || (targetSlug.length > 0 ? `${section}/${targetSlug.join("/")}` : section); const exportData = await getExportDocuments({ path: targetPath === "export" ? queryPath : targetPath, section: targetPath === section ? section : undefined, sortBy: (sort as any) || "category_points", }); const backHref = targetPath && targetPath !== "export" ? `/${targetPath}` : section !== "export" ? `/${section}` : "/"; return ; } 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 */}
{/* Breadcrumbs Navigation */} {/* Hero Metadata Card */}
{sectionInfo.icon} {sectionInfo.label} · {doc.type} · {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: "short", day: "numeric", year: "numeric", })} {doc.tags.length > 0 && ( <> ·
{doc.tags.map((t) => ( #{t} ))}
)}
{/* Dynamic Badges */}
{/* Mobile TOC */}
{/* Markdown Content */}
{/* Related Documents Grid */} {related.length > 0 && ( )} {/* Revision History */} {revisions.length > 0 && ( )}
{/* Desktop Sticky Table of Contents Column */}
); }