Jinja2 syntax validation with debounced API check
All checks were successful
Validate / Hassfest (push) Successful in 3s
All checks were successful
Validate / Hassfest (push) Successful in 3s
Two-pass validation in preview-raw endpoint:
1. Syntax check (catches {% if %}, unclosed tags)
2. StrictUndefined render (catches {{ asset.a }}, {{ bad_var }})
Frontend shows:
- Red error for syntax errors with line number
- Amber warning for undefined variables
- Error line highlighted in editor
Sample context now uses proper structured data (lists of dicts
for assets/albums) so valid field access like {{ asset.filename }}
renders correctly during preview.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -360,6 +360,7 @@
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"syntaxError": "Syntax error",
|
||||
"undefinedVar": "Unknown variable",
|
||||
"line": "line"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +360,7 @@
|
||||
"expand": "Развернуть",
|
||||
"collapse": "Свернуть",
|
||||
"syntaxError": "Ошибка синтаксиса",
|
||||
"undefinedVar": "Неизвестная переменная",
|
||||
"line": "строка"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
let slotPreview = $state<Record<string, string>>({});
|
||||
let slotErrors = $state<Record<string, string>>({});
|
||||
let slotErrorLines = $state<Record<string, number | null>>({});
|
||||
let slotErrorTypes = $state<Record<string, string>>({});
|
||||
let validateTimers: Record<string, ReturnType<typeof setTimeout>> = {};
|
||||
|
||||
function validateSlot(slotKey: string, template: string) {
|
||||
@@ -33,6 +34,7 @@
|
||||
if (!template) {
|
||||
slotErrors = { ...slotErrors, [slotKey]: '' };
|
||||
slotErrorLines = { ...slotErrorLines, [slotKey]: null };
|
||||
slotErrorTypes = { ...slotErrorTypes, [slotKey]: '' };
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,10 +44,12 @@
|
||||
const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template }) });
|
||||
slotErrors = { ...slotErrors, [slotKey]: res.error || '' };
|
||||
slotErrorLines = { ...slotErrorLines, [slotKey]: res.error_line || null };
|
||||
slotErrorTypes = { ...slotErrorTypes, [slotKey]: res.error_type || '' };
|
||||
} catch {
|
||||
// Network error, don't show as template error
|
||||
slotErrors = { ...slotErrors, [slotKey]: '' };
|
||||
slotErrorLines = { ...slotErrorLines, [slotKey]: null };
|
||||
slotErrorTypes = { ...slotErrorTypes, [slotKey]: '' };
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
@@ -189,7 +193,11 @@
|
||||
{#if (slot.rows || 2) > 2}
|
||||
<JinjaEditor value={(form as any)[slot.key] || ''} onchange={(v) => { (form as any)[slot.key] = v; validateSlot(slot.key, v); }} rows={slot.rows || 6} errorLine={slotErrorLines[slot.key] || null} />
|
||||
{#if slotErrors[slot.key]}
|
||||
<p class="mt-1 text-xs" style="color: var(--color-error-fg);">{t('common.syntaxError')}: {slotErrors[slot.key]}{slotErrorLines[slot.key] ? ` (${t('common.line')} ${slotErrorLines[slot.key]})` : ''}</p>
|
||||
{#if slotErrorTypes[slot.key] === 'undefined'}
|
||||
<p class="mt-1 text-xs" style="color: #d97706;">⚠ {t('common.undefinedVar')}: {slotErrors[slot.key]}</p>
|
||||
{:else}
|
||||
<p class="mt-1 text-xs" style="color: var(--color-error-fg);">✕ {t('common.syntaxError')}: {slotErrors[slot.key]}{slotErrorLines[slot.key] ? ` (${t('common.line')} ${slotErrorLines[slot.key]})` : ''}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if slotPreview[slot.key]}
|
||||
<div class="mt-1 p-2 bg-[var(--color-muted)] rounded text-sm">
|
||||
|
||||
Reference in New Issue
Block a user