Jinja2 syntax highlighting + description field + preview toggle
All checks were successful
Validate / Hassfest (push) Successful in 32s
All checks were successful
Validate / Hassfest (push) Successful in 32s
- Error line highlighting in JinjaEditor (red background on error line)
- Backend returns error_line from TemplateSyntaxError
- Localized syntax error messages with line number
- Renamed {{ }} button to "Variables" (localized)
- Localized all template variable descriptions (EN/RU)
- Added t() fallback parameter for graceful degradation
- Page transition animation (fade) to prevent content stacking
- Added syntaxError/line i18n keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,21 +24,28 @@
|
||||
let confirmDelete = $state<any>(null);
|
||||
let slotPreview = $state<Record<string, string>>({});
|
||||
let slotErrors = $state<Record<string, string>>({});
|
||||
let slotErrorLines = $state<Record<string, number | null>>({});
|
||||
let validateTimers: Record<string, ReturnType<typeof setTimeout>> = {};
|
||||
|
||||
function validateSlot(slotKey: string, template: string) {
|
||||
// Clear previous timer
|
||||
if (validateTimers[slotKey]) clearTimeout(validateTimers[slotKey]);
|
||||
if (!template) { slotErrors = { ...slotErrors, [slotKey]: '' }; return; }
|
||||
if (!template) {
|
||||
slotErrors = { ...slotErrors, [slotKey]: '' };
|
||||
slotErrorLines = { ...slotErrorLines, [slotKey]: null };
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce 800ms
|
||||
validateTimers[slotKey] = setTimeout(async () => {
|
||||
try {
|
||||
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 };
|
||||
} catch {
|
||||
// Network error, don't show as template error
|
||||
slotErrors = { ...slotErrors, [slotKey]: '' };
|
||||
slotErrorLines = { ...slotErrorLines, [slotKey]: null };
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
@@ -175,14 +182,14 @@
|
||||
{/if}
|
||||
{#if varsRef[slot.key]}
|
||||
<button type="button" onclick={() => showVarsFor = slot.key}
|
||||
class="text-xs text-[var(--color-muted-foreground)] hover:underline">{'{{ }}'}</button>
|
||||
class="text-xs text-[var(--color-muted-foreground)] hover:underline">{t('templateConfig.variables')}</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#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} />
|
||||
<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);">Syntax error: {slotErrors[slot.key]}</p>
|
||||
<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 slotPreview[slot.key]}
|
||||
<div class="mt-1 p-2 bg-[var(--color-muted)] rounded text-sm">
|
||||
@@ -239,25 +246,25 @@
|
||||
onconfirm={() => confirmDelete?.onconfirm()} oncancel={() => confirmDelete = null} />
|
||||
|
||||
<!-- Variables reference modal -->
|
||||
<Modal open={showVarsFor !== null} title="Template Variables: {showVarsFor}" onclose={() => showVarsFor = null}>
|
||||
<Modal open={showVarsFor !== null} title="{t('templateConfig.variables')}: {showVarsFor ? t(`templateConfig.${templateSlots.flatMap(g => g.slots).find(s => s.key === showVarsFor)?.label || showVarsFor}`) : ''}" onclose={() => showVarsFor = null}>
|
||||
{#if showVarsFor && varsRef[showVarsFor]}
|
||||
<p class="text-sm text-[var(--color-muted-foreground)] mb-3">{varsRef[showVarsFor].description}</p>
|
||||
<p class="text-sm text-[var(--color-muted-foreground)] mb-3">{t(`templateVars.${showVarsFor}.description`, varsRef[showVarsFor].description)}</p>
|
||||
<div class="space-y-1">
|
||||
<p class="text-xs font-medium mb-1">Variables:</p>
|
||||
<p class="text-xs font-medium mb-1">{t('templateConfig.variables')}:</p>
|
||||
{#each Object.entries(varsRef[showVarsFor].variables || {}) as [name, desc]}
|
||||
<div class="flex items-start gap-2 text-sm">
|
||||
<code class="text-xs bg-[var(--color-muted)] px-1 py-0.5 rounded font-mono whitespace-nowrap">{'{{ ' + name + ' }}'}</code>
|
||||
<span class="text-xs text-[var(--color-muted-foreground)]">{desc}</span>
|
||||
<span class="text-xs text-[var(--color-muted-foreground)]">{t(`templateVars.${name}`, desc as string)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if varsRef[showVarsFor].asset_fields}
|
||||
<div class="mt-3 pt-3 border-t border-[var(--color-border)]">
|
||||
<p class="text-xs font-medium mb-1">Asset fields (in {'{'}% for asset in added_assets %{'}'}):</p>
|
||||
<p class="text-xs font-medium mb-1">{t('templateConfig.assetFields')}:</p>
|
||||
{#each Object.entries(varsRef[showVarsFor].asset_fields || {}) as [name, desc]}
|
||||
<div class="flex items-start gap-2 text-sm">
|
||||
<code class="text-xs bg-[var(--color-muted)] px-1 py-0.5 rounded font-mono whitespace-nowrap">{'{{ asset.' + name + ' }}'}</code>
|
||||
<span class="text-xs text-[var(--color-muted-foreground)]">{desc}</span>
|
||||
<span class="text-xs text-[var(--color-muted-foreground)]">{t(`templateVars.asset_${name}`, desc as string)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user