From 55a03e8094863be7ccb9f95009b8733857d0465f Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Sun, 6 Sep 2026 05:32:51 -0400 Subject: [PATCH] feat(wiki): add editor undo/redo, template dialog, highlighting, and formatting - useEditorHistory: undo/redo stack over the textarea body - WikiTemplateDialog: insert {{Template}} transclusions from the toolbar, with per-template {{param}} placeholder prompts - WikiEditorHighlightLayer: mirrored overlay highlighting headings, directives, and template placeholders over the textarea - editorHighlight.ts: tokenizer driving the overlay - formatMarkdown.ts + createFormatUpdate: auto-format the document - editorFormatting.ts: Tab/Shift+Tab indent/outdent, list continuation on Enter, inline bold/italic/code formatting, infobox snippet - WikiEditor: editor and live preview side by side, preview renders template CSS/HTML via WikiPreparedBody --- src/components/frontend/wiki/WikiEditor.tsx | 78 ++++---- .../frontend/wiki/WikiEditorBody.tsx | 186 ++++++++++++++++-- .../wiki/WikiEditorHighlightLayer.tsx | 49 +++++ .../frontend/wiki/WikiTemplateDialog.tsx | 164 +++++++++++++++ .../frontend/wiki/useEditorHistory.ts | 91 +++++++++ src/lib/wiki/editorFormatting.ts | 116 +++++++++++ src/lib/wiki/editorHighlight.ts | 109 ++++++++++ src/lib/wiki/formatMarkdown.ts | 56 ++++++ 8 files changed, 802 insertions(+), 47 deletions(-) create mode 100644 src/components/frontend/wiki/WikiEditorHighlightLayer.tsx create mode 100644 src/components/frontend/wiki/WikiTemplateDialog.tsx create mode 100644 src/components/frontend/wiki/useEditorHistory.ts create mode 100644 src/lib/wiki/editorHighlight.ts create mode 100644 src/lib/wiki/formatMarkdown.ts diff --git a/src/components/frontend/wiki/WikiEditor.tsx b/src/components/frontend/wiki/WikiEditor.tsx index 891ab9c..b93ed0a 100644 --- a/src/components/frontend/wiki/WikiEditor.tsx +++ b/src/components/frontend/wiki/WikiEditor.tsx @@ -1,10 +1,10 @@ "use client"; -import { useRef, useState } from "react"; +import { useMemo, useRef, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import Markdown from "react-markdown"; 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"; @@ -19,14 +19,11 @@ import { import { createWikiPage, updateWikiPage } from "@/app/(frontend)/wiki/actions"; import { WIKI_CATEGORIES, type WikiCategory } from "@/lib/wiki/categories"; import { prepareMarkdown } from "@/lib/wiki/prepare"; -import { - wikiMarkdownComponents, - wikiRemarkPlugins, - wikiRemarkRehypeOptions, -} from "@/lib/wiki/markdown"; +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"; @@ -43,6 +40,8 @@ interface WikiEditorProps { readonly initial?: WikiEditorInitial; readonly initialTitle?: string; readonly templates: Record; + readonly templateCssMap?: Record; + readonly templateHtmlMap?: Record; readonly existingPages: readonly { title: string }[]; readonly isFirstEdit?: boolean; } @@ -67,6 +66,8 @@ export function WikiEditor({ initial, initialTitle, templates, + templateCssMap = {}, + templateHtmlMap = {}, existingPages, isFirstEdit = false, }: WikiEditorProps) { @@ -83,7 +84,19 @@ export function WikiEditor({ if (!isFirstEdit || typeof window === "undefined") return false; return window.localStorage.getItem(STANDARDS_ACCEPTED_KEY) !== "1"; }); - const preparedPreview = prepareMarkdown(body, templates); + 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"); @@ -133,6 +146,7 @@ export function WikiEditor({ return (
void handleSave(event)}> + {previewCss &&