feat(wiki): add rendered template preview to template dialog
Template dialog gains a rendered preview (markdown pipeline + scoped template CSS + HTML templates) alongside the existing source preview, with Rendered/Markdown toggle; editor threads the template maps through. Ultraworked with [Sisyphus](https://github.com/code-yeongju/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
44b78ba7d9
commit
28250550ab
3 changed files with 84 additions and 6 deletions
|
|
@ -215,6 +215,8 @@ export function WikiEditor({
|
|||
onLinkDialogChange={setLinkDialogOpen}
|
||||
pending={pending}
|
||||
templates={templates}
|
||||
templateCssMap={templateCssMap}
|
||||
templateHtmlMap={templateHtmlMap}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ interface WikiEditorBodyProps {
|
|||
readonly onLinkDialogChange: (open: boolean) => void;
|
||||
readonly pending: boolean;
|
||||
readonly templates: TemplateMap;
|
||||
readonly templateCssMap?: Record<string, string>;
|
||||
readonly templateHtmlMap?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function WikiEditorBody({
|
||||
|
|
@ -74,6 +76,8 @@ export function WikiEditorBody({
|
|||
onLinkDialogChange,
|
||||
pending,
|
||||
templates,
|
||||
templateCssMap,
|
||||
templateHtmlMap,
|
||||
}: WikiEditorBodyProps) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const mirrorRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -429,6 +433,8 @@ export function WikiEditorBody({
|
|||
onOpenChange={setTemplateDialogOpen}
|
||||
disabled={pending}
|
||||
onInsert={insertTemplateBody}
|
||||
templateCssMap={templateCssMap}
|
||||
templateHtmlMap={templateHtmlMap}
|
||||
/>
|
||||
<WikiEditorToolbar groups={groups} disabled={pending || uploading} />
|
||||
<div className="relative min-h-[23.5rem] w-full rounded-md border border-input bg-transparent shadow-sm transition-[box-shadow] focus-within:border-ring focus-within:ring-1 focus-within:ring-ring lg:h-[calc(100vh-10rem)]">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ 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;
|
||||
|
|
@ -21,6 +24,8 @@ interface WikiTemplateDialogProps {
|
|||
readonly onOpenChange: (open: boolean) => void;
|
||||
readonly disabled?: boolean;
|
||||
readonly onInsert: (body: string) => void;
|
||||
readonly templateCssMap?: Record<string, string>;
|
||||
readonly templateHtmlMap?: Record<string, string>;
|
||||
}
|
||||
|
||||
function escapeRegExp(text: string): string {
|
||||
|
|
@ -48,9 +53,14 @@ export function WikiTemplateDialog({
|
|||
onOpenChange,
|
||||
disabled,
|
||||
onInsert,
|
||||
templateCssMap,
|
||||
templateHtmlMap,
|
||||
}: WikiTemplateDialogProps) {
|
||||
const [selectedName, setSelectedName] = useState<string | null>(null);
|
||||
const [values, setValues] = useState<Record<string, string>>({});
|
||||
const [previewMode, setPreviewMode] = useState<"rendered" | "source">(
|
||||
"rendered",
|
||||
);
|
||||
|
||||
const names = useMemo(() => Object.keys(templates).sort(), [templates]);
|
||||
const selectedBody = selectedName !== null ? (templates[selectedName] ?? "") : "";
|
||||
|
|
@ -62,11 +72,35 @@ export function WikiTemplateDialog({
|
|||
() => 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);
|
||||
}
|
||||
|
|
@ -143,12 +177,48 @@ export function WikiTemplateDialog({
|
|||
</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
Preview
|
||||
</span>
|
||||
<pre className="max-h-40 overflow-auto rounded-md border bg-muted/20 p-3 font-mono text-xs whitespace-pre-wrap">
|
||||
{filled}
|
||||
</pre>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
Preview
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={previewMode === "rendered" ? "secondary" : "ghost"}
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => setPreviewMode("rendered")}
|
||||
>
|
||||
Rendered
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={previewMode === "source" ? "secondary" : "ghost"}
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => setPreviewMode("source")}
|
||||
>
|
||||
Markdown
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{previewMode === "rendered" ? (
|
||||
<div className="max-h-72 min-h-24 overflow-auto rounded-md border bg-muted/20 p-3">
|
||||
{previewCss ? (
|
||||
<style dangerouslySetInnerHTML={{ __html: previewCss }} />
|
||||
) : null}
|
||||
<div className={cn("wiki-markdown", previewScopeClasses)}>
|
||||
<WikiPreparedBody
|
||||
prepared={preparedPreview}
|
||||
htmlTemplates={templateHtmlMap}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<pre className="max-h-40 overflow-auto rounded-md border bg-muted/20 p-3 font-mono text-xs whitespace-pre-wrap">
|
||||
{filled}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn("flex justify-end gap-2")}>
|
||||
<Button variant="outline" onClick={() => handleOpenChange(false)}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue