Replace video_warning with target_type + has_videos/has_photos
All checks were successful
Validate / Hassfest (push) Successful in 2s

Major template system improvements:
- Remove video_warning field from TemplateConfig model
- Add target_type, has_videos, has_photos to template context
- Templates use {% if target_type == "telegram" and has_videos %}
  for conditional Telegram warnings instead of a separate field
- date_format moved from "Telegram" to "Settings" group
- Add target type selector (Telegram/Webhook) in template editor
  to preview how templates render for each target type
- All template slots now use JinjaEditor (not plain <input>)
- Preview endpoint accepts target_type parameter
- Clean up TemplateConfigCreate schema (remove stale fields)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 21:11:38 +03:00
parent 510463cba6
commit 4babaddd87
9 changed files with 66 additions and 49 deletions

View File

@@ -263,8 +263,8 @@
"periodicAlbum": "Per-album item",
"scheduledAssets": "Scheduled assets",
"memoryMode": "Memory mode",
"telegramSettings": "Telegram",
"videoWarning": "Video warning",
"settings": "Settings",
"previewAs": "Preview as",
"preview": "Preview",
"variables": "Variables",
"assetFields": "Asset fields (in {% for asset in added_assets %})",
@@ -289,7 +289,9 @@
"added_assets": "List of asset dicts (use {% for asset in added_assets %})",
"removed_assets": "List of removed asset IDs (strings)",
"shared": "Whether album is shared (boolean)",
"video_warning": "Video size warning (from template config, only if videos present)",
"target_type": "Target type: 'telegram' or 'webhook'",
"has_videos": "Whether added assets contain videos (boolean)",
"has_photos": "Whether added assets contain photos (boolean)",
"old_name": "Previous album name (rename events)",
"new_name": "New album name (rename events)",
"old_shared": "Was album shared before rename (boolean)",

View File

@@ -263,8 +263,8 @@
"periodicAlbum": "Элемент альбома",
"scheduledAssets": "Запланированные фото",
"memoryMode": "Воспоминания",
"telegramSettings": "Telegram",
"videoWarning": "Предупреждение о видео",
"settings": "Настройки",
"previewAs": "Предпросмотр как",
"preview": "Предпросмотр",
"variables": "Переменные",
"assetFields": "Поля файла (в {% for asset in added_assets %})",
@@ -289,7 +289,9 @@
"added_assets": "Список файлов ({% for asset in added_assets %})",
"removed_assets": "Список ID удалённых файлов (строки)",
"shared": "Общий альбом (boolean)",
"video_warning": "Предупреждение о видео (из конфига шаблона, если есть видео)",
"target_type": "Тип получателя: 'telegram' или 'webhook'",
"has_videos": "Содержат ли добавленные файлы видео (boolean)",
"has_photos": "Содержат ли добавленные файлы фото (boolean)",
"old_name": "Прежнее название альбома (при переименовании)",
"new_name": "Новое название альбома (при переименовании)",
"old_shared": "Был ли общим до переименования (boolean)",

View File

@@ -43,7 +43,7 @@
// Debounce 800ms
validateTimers[slotKey] = setTimeout(async () => {
try {
const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template }) });
const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template, target_type: previewTargetType }) });
slotErrors = { ...slotErrors, [slotKey]: res.error || '' };
slotErrorLines = { ...slotErrorLines, [slotKey]: res.error_line || null };
slotErrorTypes = { ...slotErrorTypes, [slotKey]: res.error_type || '' };
@@ -73,9 +73,9 @@
scheduled_assets_message: '',
memory_mode_message: '',
date_format: '%d.%m.%Y, %H:%M UTC',
video_warning: '\n\n⚠ Note: Videos may not be sent due to Telegram\'s 50 MB file size limit.',
});
let form = $state(defaultForm());
let previewTargetType = $state('telegram');
const templateSlots = [
{ group: 'eventMessages', slots: [
@@ -89,9 +89,8 @@
{ key: 'scheduled_assets_message', label: 'scheduledAssets', rows: 6 },
{ key: 'memory_mode_message', label: 'memoryMode', rows: 6 },
]},
{ group: 'telegramSettings', slots: [
{ group: 'settings', slots: [
{ key: 'date_format', label: 'dateFormat', rows: 1 },
{ key: 'video_warning', label: 'videoWarning', rows: 2 },
]},
];
@@ -124,7 +123,7 @@
const template = (form as any)[slotKey] || '';
if (!template) { slotPreview = { ...slotPreview, [slotKey]: '(empty)' }; return; }
try {
const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template }) });
const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template, target_type: previewTargetType }) });
slotPreview = { ...slotPreview, [slotKey]: res.error ? `Error: ${res.error}` : res.rendered };
} catch (err: any) { slotPreview = { ...slotPreview, [slotKey]: `Error: ${err.message}` }; }
}
@@ -135,7 +134,7 @@
const template = config[slotKey] || '';
if (!template) return;
try {
const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template }) });
const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template, target_type: previewTargetType }) });
slotPreview[slotKey + '_' + configId] = res.error ? `Error: ${res.error}` : res.rendered;
} catch (err: any) { slotPreview[slotKey + '_' + configId] = `Error: ${err.message}`; }
}
@@ -180,6 +179,21 @@
class="w-full px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
</div>
<!-- Target type selector for preview -->
<div class="flex items-center gap-2">
<label class="text-sm font-medium">{t('templateConfig.previewAs')}:</label>
<div class="flex gap-1">
<button type="button" onclick={() => previewTargetType = 'telegram'}
class="px-3 py-1 text-xs rounded-md transition-colors {previewTargetType === 'telegram' ? 'bg-[var(--color-primary)] text-[var(--color-primary-foreground)]' : 'bg-[var(--color-muted)] text-[var(--color-muted-foreground)]'}">
Telegram
</button>
<button type="button" onclick={() => previewTargetType = 'webhook'}
class="px-3 py-1 text-xs rounded-md transition-colors {previewTargetType === 'webhook' ? 'bg-[var(--color-primary)] text-[var(--color-primary-foreground)]' : 'bg-[var(--color-muted)] text-[var(--color-muted-foreground)]'}">
Webhook
</button>
</div>
</div>
{#each templateSlots as group}
<fieldset class="border border-[var(--color-border)] rounded-md p-3">
<legend class="text-sm font-medium px-1">{t(`templateConfig.${group.group}`)}{#if group.group === 'eventMessages'}<Hint text={t('hints.eventMessages')} />{:else if group.group === 'assetFormatting'}<Hint text={t('hints.assetFormatting')} />{:else if group.group === 'dateLocation'}<Hint text={t('hints.dateLocation')} />{:else if group.group === 'scheduledMessages'}<Hint text={t('hints.scheduledMessages')} />{/if}</legend>
@@ -197,8 +211,11 @@
{/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} errorLine={slotErrorLines[slot.key] || null} />
{#if slot.key === 'date_format'}
<input bind:value={(form as any)[slot.key]}
class="w-full px-2 py-1 border border-[var(--color-border)] rounded text-sm bg-[var(--color-background)] font-mono" />
{:else}
<JinjaEditor value={(form as any)[slot.key] || ''} onchange={(v) => { (form as any)[slot.key] = v; validateSlot(slot.key, v); }} rows={slot.rows || 3} errorLine={slotErrorLines[slot.key] || null} />
{#if slotErrors[slot.key]}
{#if slotErrorTypes[slot.key] === 'undefined'}
<p class="mt-1 text-xs" style="color: #d97706;">⚠ {t('common.undefinedVar')}: {slotErrors[slot.key]}</p>
@@ -211,9 +228,6 @@
<pre class="whitespace-pre-wrap">{slotPreview[slot.key]}</pre>
</div>
{/if}
{:else}
<input bind:value={(form as any)[slot.key]}
class="w-full px-2 py-1 border border-[var(--color-border)] rounded text-sm bg-[var(--color-background)] font-mono" />
{/if}
</div>
{/each}