feat: telegram commands, app settings, bot polling, webhook handling, UI improvements

Adds telegram bot command system with 13 commands (search, latest, random, etc.),
webhook/polling handlers, rate limiting, app settings page, and various UI/UX
improvements across all entity pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 23:11:42 +03:00
parent 5015e378fe
commit 03ec9b3c86
64 changed files with 2585 additions and 648 deletions
+7 -8
View File
@@ -8,14 +8,16 @@
import Loading from '$lib/components/Loading.svelte';
import IconPicker from '$lib/components/IconPicker.svelte';
import MdiIcon from '$lib/components/MdiIcon.svelte';
import EmptyState from '$lib/components/EmptyState.svelte';
import ConfirmModal from '$lib/components/ConfirmModal.svelte';
import Hint from '$lib/components/Hint.svelte';
import IconButton from '$lib/components/IconButton.svelte';
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
import type { NotificationTarget, TelegramBot, TelegramChat } from '$lib/types';
let targets = $state<any[]>([]);
let bots = $state<any[]>([]);
let botChats = $state<Record<number, any[]>>({});
let targets = $state<NotificationTarget[]>([]);
let bots = $state<TelegramBot[]>([]);
let botChats = $state<Record<number, TelegramChat[]>>({});
let showForm = $state(false);
let editing = $state<number | null>(null);
let formType = $state<'telegram' | 'webhook'>('telegram');
@@ -29,7 +31,7 @@
let submitting = $state(false);
let loadError = $state('');
let showTelegramSettings = $state(false);
let confirmDelete = $state<any>(null);
let confirmDelete = $state<NotificationTarget | null>(null);
onMount(load);
async function load() {
@@ -223,10 +225,7 @@
{#if targets.length === 0 && !showForm}
<Card>
<div class="flex flex-col items-center py-8 gap-3" style="color: var(--color-muted-foreground);">
<div style="opacity: 0.4;"><MdiIcon name="mdiTarget" size={40} /></div>
<p class="text-sm">{t('targets.noTargets')}</p>
</div>
<EmptyState icon="mdiTarget" message={t('targets.noTargets')} />
</Card>
{:else}
<div class="space-y-3 stagger-children">