fix: nunjuclk rendering

This commit is contained in:
2026-03-11 00:05:32 +01:00
parent 6014f34f76
commit 08d66b59c2

View File

@@ -480,12 +480,14 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
return
}
setResolvedContent(afterNunjucks)
// Collapse runs of newlines so {% for %} / {{ }} on their own lines don't leave blank lines
const resolved = afterNunjucks.replace(/(\r?\n)(\s*\r?\n)+/g, '$1').trim()
setResolvedContent(resolved)
const typeRenderer = getConfigType(configTypeId)
try {
const renderOptions = configTypeId === 'wireframe' ? { width: viewportWidth, height: viewportHeight } : undefined
const htmlOrSvg = await typeRenderer.render(afterNunjucks, renderOptions)
const htmlOrSvg = await typeRenderer.render(resolved, renderOptions)
if (cancelled || thisRunId !== runIdRef.current) return
setRenderedContent(htmlOrSvg)
setError(null)
@@ -539,6 +541,18 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
// Kroki and other SVG sources may prepend <?xml ... ?> so we detect by presence of <svg> tag
const isSvgOutput = Boolean(renderedContent?.trim() && /<svg[\s>]/i.test(renderedContent.trim()))
/** Strip remaining Nunjucks tags from resolved content for display in raw view (so tags don't show as literal lines). */
const rawDisplayContent = useMemo(() => {
if (resolvedContent == null) return ''
return resolvedContent
.replace(/\{%[\s\S]*?%\}/g, '')
.replace(/\{\{[\s\S]*?\}\}/g, '')
.replace(/\{#[\s\S]*?#\}/g, '')
.replace(/(\r?\n)\s*(\r?\n)/g, '$1$2')
.replace(/^\s*\n|\n\s*$/g, (m) => (m === '\n' ? '\n' : ''))
.trim()
}, [resolvedContent])
/** Process SVG HTML so it keeps aspect ratio and fills the viewport (used only for display, not download). */
const displayContent = useMemo(() => {
if (!renderedContent || !isSvgOutput) return renderedContent
@@ -650,16 +664,34 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
<ToggleGroup
type="single"
value={viewMode}
onValueChange={(v) => v && (v === 'preview' || v === 'raw') && setViewMode(v)}
onValueChange={(v) => {
if (v === 'preview' || v === 'raw') setViewMode(v)
}}
aria-label="View mode"
variant="outline"
size="sm"
className="gap-0 rounded-md p-0.5 [&>button]:rounded-none [&>button:first-child]:rounded-l-md [&>button:last-child]:rounded-r-md [&>button:not(:first-child)]:border-l-0"
>
<ToggleGroupItem value="preview" aria-label="Preview" className="gap-1.5 px-2.5 h-7">
<ToggleGroupItem
value="preview"
aria-label="Preview"
className="gap-1.5 px-2.5 h-7"
onClick={(e) => {
e.stopPropagation()
setViewMode('preview')
}}
>
Preview
</ToggleGroupItem>
<ToggleGroupItem value="raw" aria-label="Raw config" className="gap-1.5 px-2.5 h-7">
<ToggleGroupItem
value="raw"
aria-label="Raw config"
className="gap-1.5 px-2.5 h-7"
onClick={(e) => {
e.stopPropagation()
setViewMode('raw')
}}
>
Raw
</ToggleGroupItem>
</ToggleGroup>
@@ -800,18 +832,18 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
size="icon"
className="absolute top-2 right-2 z-10 h-7 w-7 shrink-0 rounded-md shadow-sm"
onClick={() => {
const text = resolvedContent ?? ''
const text = rawDisplayContent
if (text) {
navigator.clipboard?.writeText(text).then(() => toast.success('Copied to clipboard')).catch(() => {})
}
}}
disabled={!resolvedContent}
disabled={!rawDisplayContent}
title="Copy raw output"
>
<Copy className="size-3.5" />
</Button>
<CodeMirror
value={resolvedContent ?? ''}
value={rawDisplayContent}
height={`${rawEditorHeight}px`}
theme={theme}
extensions={rawExtensions}
@@ -917,7 +949,7 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
<BaseNodeFooter>
<NodeFooterEdgeIndicators nodeId={id} nodeType="render">
{viewMode === 'raw'
? (resolvedContent != null ? `Raw · ${resolvedContent.length} chars` : '—')
? (rawDisplayContent ? `Raw · ${rawDisplayContent.length} chars` : '—')
: renderedContent
? `${configType.label} · ${renderedContent.length} chars`
: error