This commit is contained in:
2026-03-07 15:20:48 +01:00
parent 72faf32011
commit 5aa01f4dec
3 changed files with 22 additions and 41 deletions

View File

@@ -106,14 +106,14 @@ export const ConfigNode = memo(function ConfigNode({ id, data, width, height }:
const insertVariableReference = useCallback(
(sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
insertAt(`\${${sourceNode.id}}`, mode)
insertAt(`{{ ${sourceNode.id} }}`, mode)
},
[insertAt]
)
const insertFunctionCall = useCallback(
(sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
insertAt(`\${${sourceNode.id}()}`, mode)
insertAt(`{{ ${sourceNode.id}() }}`, mode)
},
[insertAt]
)

View File

@@ -191,8 +191,8 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
const resolvedIncludes = resolveIncludes(plantumlText)
const varMap: Record<string, string> = {}
// Use null prototype so keys like "var", "constructor", "toString" don't collide with Object.prototype
// Use null prototype so keys like "var", "constructor", "toString" don't collide with Object.prototype.
// Only Nunjucks {{ var }} / {% if var %} are supported; context is built from connected variable/function nodes.
const nunjucksContext = Object.create(null) as Record<string, unknown>
for (const e of edges) {
@@ -201,7 +201,6 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
if (src?.type === 'variable') {
const v = src.data?.value
const str = v === undefined || v === null ? '' : String(v)
varMap[src.id] = str
// Keep booleans/numbers for {% if %} etc.; Nunjucks treats "false" as truthy
nunjucksContext[src.id] =
v === undefined || v === null ? '' : typeof v === 'boolean' || typeof v === 'number' ? v : str
@@ -229,33 +228,9 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
throw new Error(`Nunjucks: ${nunjucksErr?.message ?? String(nunjucksErr)}`)
}
const resolveFunctionCalls = (text: string): string => {
const fnCallRegex = /\$\{([\w-]+)\s*\(([^)]*)\)\}/g
return text.replace(fnCallRegex, (match, funcId: string, argsStr: string) => {
const fnNode = nodes.find((n: any) => n.id === funcId && n.type === 'function')
if (!fnNode) return match
const body = fnNode.data?.body ?? 'return args[0];'
const argIds = argsStr.split(',').map((s: string) => s.trim()).filter(Boolean)
const argValues = argIds.map((argId: string) => varMap[argId] ?? '')
try {
const fn = new Function('args', body)
const result = fn(argValues)
if (result === undefined || result === null) return ''
if (typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean') return String(result)
return String(result)
} catch {
return match
}
})
}
const resolveVariables = (text: string): string =>
text.replace(/\$\{([^}]+)\}/g, (_, varId: string) => varMap[varId.trim()] ?? '')
const afterFunctions = resolveFunctionCalls(afterNunjucks)
let resolvedWithVars = resolveVariables(afterFunctions)
let resolvedPlantuml = afterNunjucks
if (theme === 'dark') {
resolvedWithVars = resolvedWithVars.replace(
resolvedPlantuml = resolvedPlantuml.replace(
/^(\s*@startuml\s*\n)/i,
`$1${DARK_SKINPARAMS}\n`
)
@@ -264,7 +239,7 @@ export const RenderingNode = memo(function RenderingNode({ id, width, height }:
const res = await fetch(KROKI_PLANTUML_SVG, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: resolvedWithVars,
body: resolvedPlantuml,
})
if (cancelled || thisRunId !== runIdRef.current) return