"use client"; import { useMemo, useRef, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { createWikiPage, updateWikiPage } from "@/app/(frontend)/wiki/actions"; import { WIKI_CATEGORIES, type WikiCategory } from "@/lib/wiki/categories"; import { prepareMarkdown } from "@/lib/wiki/prepare"; import { buildTemplateStyles } from "@/lib/wiki/templateCss"; import { MarkdownGuide } from "@/components/frontend/wiki/MarkdownGuide"; import { WikiEditorBody } from "@/components/frontend/wiki/WikiEditorBody"; import { WikiStandardsDialog } from "@/components/frontend/wiki/WikiStandardsDialog"; import { WikiPreparedBody } from "@/components/frontend/wiki/WikiContent"; const STANDARDS_ACCEPTED_KEY = "ptf:wiki-standards-accepted"; export interface WikiEditorInitial { readonly id: number; readonly title: string; readonly category: WikiCategory; readonly tags: readonly string[]; readonly body: string; } interface WikiEditorProps { readonly mode: "create" | "edit"; readonly initial?: WikiEditorInitial; readonly initialTitle?: string; readonly templates: Record; readonly templateCssMap?: Record; readonly templateHtmlMap?: Record; readonly existingPages: readonly { title: string }[]; readonly isFirstEdit?: boolean; /** * Whether the current user may use the Documentation category (admin and * developer roles). The server enforces this too; this only shapes the * category picker. */ readonly canEditDocumentation?: boolean; } function isWikiCategory(value: string): value is WikiCategory { return WIKI_CATEGORIES.some((category) => category === value); } function parseTags(value: string): string[] { return Array.from( new Set( value .split(",") .map((tag) => tag.trim()) .filter(Boolean), ), ); } export function WikiEditor({ mode, initial, initialTitle, templates, templateCssMap = {}, templateHtmlMap = {}, existingPages, isFirstEdit = false, canEditDocumentation = false, }: WikiEditorProps) { const router = useRouter(); const bodyRef = useRef(null); const [title, setTitle] = useState(initial?.title ?? initialTitle ?? ""); const [category, setCategory] = useState(initial?.category ?? "Meta"); const [tags, setTags] = useState(initial?.tags.join(", ") ?? ""); const [summary, setSummary] = useState(""); const [body, setBody] = useState(initial?.body ?? ""); const [pending, setPending] = useState(false); const [linkDialogOpen, setLinkDialogOpen] = useState(false); const [showStandards, setShowStandards] = useState(() => { if (!isFirstEdit || typeof window === "undefined") return false; return window.localStorage.getItem(STANDARDS_ACCEPTED_KEY) !== "1"; }); const preparedPreview = prepareMarkdown(body, templates, templateHtmlMap); const { css: previewCss, scopeClasses: previewScopeClasses } = useMemo( () => buildTemplateStyles( body, Object.entries(templates).map(([name, templateBody]) => ({ name, body: templateBody, css: templateCssMap[name] ?? null, })), ), [body, templates, templateCssMap], ); function handleStandardsAccept() { window.localStorage.setItem(STANDARDS_ACCEPTED_KEY, "1"); setShowStandards(false); } async function handleSave(event: React.FormEvent) { event.preventDefault(); const trimmedTitle = title.trim(); if (!trimmedTitle) { toast.error("Add a title before saving the page."); return; } setPending(true); const parsedTags = parseTags(tags); const trimmedSummary = summary.trim(); const result = mode === "create" ? await createWikiPage({ title: trimmedTitle, category, tags: parsedTags, body, ...(trimmedSummary ? { summary: trimmedSummary } : {}), }) : initial ? await updateWikiPage({ id: initial.id, title: trimmedTitle, category, tags: parsedTags, body, ...(trimmedSummary ? { summary: trimmedSummary } : {}), }) : { success: false, error: "This page could not be loaded for editing." }; setPending(false); if (!result.success || !result.data) { toast.error(result.error ?? "Could not save the wiki page."); return; } toast.success(mode === "create" ? "Wiki page created" : "Wiki page updated"); router.push(`/wiki/${result.data.slug}`); } return (
void handleSave(event)}> {previewCss &&