feat: card highlight system for cross-entity navigation
When clicking a CrossLink, the target entity ID is passed as ?highlight=<id> in the URL. The destination page: 1. Shows a semi-transparent dim overlay (z-index: 10) 2. Finds the card with data-entity-id matching the ID 3. Scrolls to it smoothly (block: center) 4. Applies a pulsing primary-color box-shadow animation (z-index: 11) 5. Cleans up overlay + animation after 2 seconds If the card isn't in DOM yet (data still loading), a MutationObserver waits up to 5 seconds for it to appear. Changes: - New highlight.ts utility with highlightFromUrl(), MutationObserver, dim overlay management - Card component accepts entityId prop → data-entity-id attribute - CrossLink accepts entityId prop → appends ?highlight=<id> to href - All 9 entity pages: Card elements have entityId, highlightFromUrl() called after data loads - CSS: cardHighlight keyframe animation + nav-dim-overlay styles
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
import CrossLink from '$lib/components/CrossLink.svelte';
|
||||
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import { highlightFromUrl } from '$lib/highlight';
|
||||
import type { NotificationTarget, TelegramBot, TelegramChat, EmailBot, MatrixBot } from '$lib/types';
|
||||
|
||||
function getBotName(target: any): string | null {
|
||||
@@ -42,6 +43,13 @@
|
||||
return '/telegram-bots';
|
||||
}
|
||||
|
||||
function getBotEntityId(target: any): number | null {
|
||||
if (target.type === 'telegram') return target.config?.bot_id || null;
|
||||
if (target.type === 'email') return target.config?.email_bot_id || null;
|
||||
if (target.type === 'matrix') return target.config?.matrix_bot_id || null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const ALL_TYPES = ['telegram', 'webhook', 'email', 'discord', 'slack', 'ntfy', 'matrix'] as const;
|
||||
type TargetType = typeof ALL_TYPES[number];
|
||||
const TYPE_ICONS: Record<string, string> = {
|
||||
@@ -106,7 +114,7 @@
|
||||
emailBotsCache.fetch(), matrixBotsCache.fetch(),
|
||||
]);
|
||||
loadError = '';
|
||||
} catch (err: any) { loadError = err.message || t('common.loadError'); snackError(loadError); } finally { loaded = true; }
|
||||
} catch (err: any) { loadError = err.message || t('common.loadError'); snackError(loadError); } finally { loaded = true; highlightFromUrl(); }
|
||||
}
|
||||
|
||||
async function loadBotChats() {
|
||||
@@ -398,7 +406,7 @@
|
||||
{:else}
|
||||
<div class="space-y-3 stagger-children">
|
||||
{#each targets as target}
|
||||
<Card hover>
|
||||
<Card hover entityId={target.id}>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -406,7 +414,7 @@
|
||||
<p class="font-medium">{target.name}</p>
|
||||
{#if !activeType}<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)]">{target.type}</span>{/if}
|
||||
{#if target.receiver_count}<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)]">{target.receiver_count} receiver(s)</span>{/if}
|
||||
{#if getBotName(target)}<CrossLink href={getBotHref(target)} icon="mdiRobot" label={getBotName(target)} />{/if}
|
||||
{#if getBotName(target)}<CrossLink href={getBotHref(target)} icon="mdiRobot" label={getBotName(target)} entityId={getBotEntityId(target)} />{/if}
|
||||
</div>
|
||||
<p class="text-sm text-[var(--color-muted-foreground)]">
|
||||
{#if target.type === 'telegram'}
|
||||
|
||||
Reference in New Issue
Block a user