feat: IconGridSelect, CrossLink, SearchPalette components + entity crosslinks

New components:
- IconGridSelect: Visual grid selector replacing <select> dropdowns,
  with icon + label cells, fixed-position popup, smart placement
- CrossLink: Inline clickable badge for cross-entity navigation,
  hover highlights primary, used on entity cards
- SearchPalette: Ctrl+K global command palette, searches all entity
  types via cached data, grouped results, keyboard navigation

Integration:
- Targets: type selector uses IconGridSelect (4-column grid with icons)
- Targets: bot crosslink on telegram/email/matrix target cards
- Command Trackers: provider and config badges → CrossLinks
- Command Trackers: listener bot name → CrossLink
- Command Configs: template config shown as CrossLink on card
- Notification Trackers: provider CrossLink added to card
- Layout: SearchPalette mounted globally

Infrastructure:
- Added CommandConfig, CommandTemplateConfig, CommandTracker types
- Added notificationTrackersCache, commandTrackersCache to caches
- Added allCaches map and fetchAllCaches() for search palette
- Added searchPalette i18n keys (EN/RU)
This commit is contained in:
2026-03-21 23:44:12 +03:00
parent 563716fa76
commit 06b24638cb
12 changed files with 764 additions and 26 deletions
@@ -14,6 +14,7 @@
import ConfirmModal from '$lib/components/ConfirmModal.svelte';
import Hint from '$lib/components/Hint.svelte';
import IconButton from '$lib/components/IconButton.svelte';
import CrossLink from '$lib/components/CrossLink.svelte';
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
import type { Tracker, ServiceProvider, NotificationTarget, TrackingConfig, TemplateConfig } from '$lib/types';
@@ -234,6 +235,11 @@
return p?.type || '';
}
function getProviderName(id: number): string {
const p = providers.find(p => p.id === id);
return p?.name || `#${id}`;
}
function configsForTracker(tracker: any, configs: (TrackingConfig | TemplateConfig)[]): any[] {
const pt = getProviderType(tracker);
return pt ? configs.filter((c: any) => c.provider_type === pt) : configs;
@@ -373,6 +379,7 @@
<span class="text-xs px-1.5 py-0.5 rounded {tracker.enabled ? 'bg-[var(--color-success-bg)] text-[var(--color-success-fg)]' : 'bg-[var(--color-muted)] text-[var(--color-muted-foreground)]'}">
{tracker.enabled ? t('notificationTracker.active') : t('notificationTracker.paused')}
</span>
t <CrossLink href="/providers" icon="mdiServer" label={getProviderName(tracker.provider_id)} />
</div>
<p class="text-sm text-[var(--color-muted-foreground)]">
{(tracker.collection_ids || []).length} {t('notificationTracker.albums_count')} · {t('notificationTracker.every')} {tracker.scan_interval}s · {(tracker.tracker_targets || []).length} {t('notificationTracker.linkedTargets')}