"use client"; import { useActionState, useEffect, useId, useState } from "react"; import CodeMirror, { EditorView } from "@uiw/react-codemirror"; import { Led, Spinner, Switch, TabList, TabPanel, Tabs } from "@unlocalhosted/metalui"; import type { FormState } from "@/app/actions/types"; import { ConfirmButton } from "../confirm-button"; import { vectrumDark, vectrumLight } from "../editor-theme"; import { useLanguage } from "../file-editor"; import { BookIcon, CodeIcon, EyeIcon, TrashIcon } from "../icons"; import { MarkdownPreview } from "../markdown-preview"; import { TextField } from "../settings-fields"; import { useDarkMode } from "../theme-toggle"; import { Alert, Box, Button, ButtonLink, Kbd } from "../ui"; type Tab = "write" | "preview"; /** * Writing a wiki page, like GitHub's: its title, the Markdown with a preview, and an optional edit * message; saving commits it to the wiki. Editing an existing page also offers to delete it. */ export function WikiEditor({ action, deleteAction, wiki, originalPath, baseSha, initialTitle, initialContent, cancelHref, }: { action: (prev: FormState, formData: FormData) => Promise; deleteAction?: () => Promise; /** The wiki's base URL, for links in the preview. */ wiki: string; /** The page's file, or "" for a new page. */ originalPath: string; baseSha: string; initialTitle: string; initialContent: string; cancelHref: string; }) { const formId = useId(); const [state, formAction, pending] = useActionState(action, undefined); const [title, setTitle] = useState(initialTitle); const [content, setContent] = useState(initialContent); const [tab, setTab] = useState("write"); const [wrap, setWrap] = useState(true); const dark = useDarkMode(); const lang = useLanguage("page.md"); const dirty = content !== initialContent || title !== initialTitle; const extensions = [...(lang ? [lang.ext] : []), ...(wrap ? [EditorView.lineWrapping] : [])]; // Ctrl/Cmd+S saves; warn before leaving with unsaved edits. useEffect(() => { const onKey = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); (document.getElementById(formId) as HTMLFormElement | null)?.requestSubmit(); } }; const onBeforeUnload = (e: BeforeUnloadEvent) => { if (dirty && !pending) e.preventDefault(); }; window.addEventListener("keydown", onKey); window.addEventListener("beforeunload", onBeforeUnload); return () => { window.removeEventListener("keydown", onKey); window.removeEventListener("beforeunload", onBeforeUnload); }; }, [dirty, pending, formId]); return (
} required maxLength={200} value={title} onChange={(e) => setTitle(e.target.value)} autoFocus={!initialTitle} placeholder="Getting Started" /> value={tab} onValueChange={setTab}>
aria-label="Editor view" size="compact" items={[ { value: "write", label: "Write", icon: }, { value: "preview", label: "Preview", icon: }, ]} /> Markdown. Link pages with [[Page Name]] or [[Text|Page Name]].
value="write" keepMounted> value="preview">
{content}
{state?.error && {state.error}}
{deleteAction && ( Delete Page )} Ctrl S saves. {dirty && ( Unsaved changes )}
Cancel
); }