"use client"; import { useMemo, useState } from "react"; import { LayoutTemplate } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; import { extractPlaceholders, type TemplateMap } from "@/lib/wiki/templates"; import { prepareMarkdown } from "@/lib/wiki/prepare"; import { buildTemplateStyles } from "@/lib/wiki/templateCss"; import { WikiPreparedBody } from "@/components/frontend/wiki/WikiContent"; interface WikiTemplateDialogProps { readonly templates: TemplateMap; readonly open: boolean; readonly onOpenChange: (open: boolean) => void; readonly disabled?: boolean; readonly onInsert: (body: string) => void; readonly templateCssMap?: Record; readonly templateHtmlMap?: Record; } function escapeRegExp(text: string): string { return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function fillTemplate( body: string, values: Readonly>, ): string { let filled = body; for (const [name, value] of Object.entries(values)) { if (value.length === 0) continue; filled = filled.replace( new RegExp(`\\{\\{\\s*${escapeRegExp(name)}\\s*\\}\\}`, "g"), value, ); } return filled; } export function WikiTemplateDialog({ templates, open, onOpenChange, disabled, onInsert, templateCssMap, templateHtmlMap, }: WikiTemplateDialogProps) { const [selectedName, setSelectedName] = useState(null); const [values, setValues] = useState>({}); const [previewMode, setPreviewMode] = useState<"rendered" | "source">( "rendered", ); const names = useMemo(() => Object.keys(templates).sort(), [templates]); const selectedBody = selectedName !== null ? (templates[selectedName] ?? "") : ""; const placeholders = useMemo( () => extractPlaceholders(selectedBody), [selectedBody], ); const filled = useMemo( () => fillTemplate(selectedBody, values), [selectedBody, values], ); const preparedPreview = useMemo( () => selectedName !== null ? prepareMarkdown(filled, templates, templateHtmlMap ?? {}) : "", [selectedName, filled, templates, templateHtmlMap], ); const { css: previewCss, scopeClasses: previewScopeClasses } = useMemo( () => buildTemplateStyles( filled, selectedName !== null ? [ { name: selectedName, body: templates[selectedName] ?? "", css: templateCssMap?.[selectedName] ?? null, }, ] : [], ), [filled, selectedName, templates, templateCssMap], ); function handleOpenChange(next: boolean) { if (!next) { setSelectedName(null); setValues({}); setPreviewMode("rendered"); } onOpenChange(next); } function handleInsert() { onInsert(filled); handleOpenChange(false); } return ( Insert template Pick a wiki template, fill in its fields, and insert it at the cursor. Leave a field blank to keep its {"{{marker}}"} in the article. {names.length === 0 ? (

No templates are available yet. Templates can be created in the admin panel.

) : selectedName === null ? (
{names.map((name) => ( ))}
) : (
{placeholders.length > 0 ? (
{placeholders.map((name) => (
setValues((previous) => ({ ...previous, [name]: event.target.value, })) } />
))}
) : (

This template has no fields to fill in.

)}
Preview
{previewMode === "rendered" ? (
{previewCss ? (