ba199f24bd
- Defer quiet-hours dispatches into new deferred_dispatch table; drain job + periodic catch-up scan re-fire at window end with coalescing on (link, event_type, collection_id). - Add ON DELETE SET NULL migration on event_log_id and partial unique index on (link_id, collection_id, event_type) WHERE status='pending'. - Add release-check provider abstraction (Gitea/GitHub) with SSRF-safe URL validation, settings UI cassette, and scheduled polling. - Replace importlib-only version lookup with version.py helper that prefers the higher of installed metadata vs source pyproject so stale editable dev installs stop misreporting. - Aurora frontend polish: MetaStrip component, ReleaseCassette, EventDetailModal expansion, and i18n additions.
188 lines
5.1 KiB
Svelte
188 lines
5.1 KiB
Svelte
<script lang="ts">
|
|
import MdiIcon from './MdiIcon.svelte';
|
|
|
|
export type MetaTone = 'default' | 'mint' | 'sky' | 'coral' | 'citrus' | 'orchid' | 'lavender';
|
|
|
|
export interface MetaTile {
|
|
icon?: string;
|
|
label: string;
|
|
value?: string;
|
|
hint?: string;
|
|
tone?: MetaTone;
|
|
mono?: boolean;
|
|
href?: string;
|
|
onclick?: (e: MouseEvent) => void;
|
|
copyValue?: string;
|
|
}
|
|
|
|
let { tiles, align = 'start' }: {
|
|
tiles: MetaTile[];
|
|
align?: 'start' | 'end';
|
|
} = $props();
|
|
|
|
function handleClick(e: MouseEvent, tile: MetaTile) {
|
|
if (tile.onclick) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
tile.onclick(e);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="meta-strip" style="justify-content: {align === 'end' ? 'flex-end' : 'flex-start'};">
|
|
{#each tiles as tile, i (i)}
|
|
{#if tile.href}
|
|
<a
|
|
class="meta-tile meta-tone-{tile.tone || 'default'} meta-tile--interactive"
|
|
class:meta-tile--mono={tile.mono}
|
|
title={tile.hint}
|
|
href={tile.href}
|
|
target="_blank"
|
|
rel="noopener"
|
|
>
|
|
{#if tile.icon}
|
|
<span class="meta-tile__icon"><MdiIcon name={tile.icon} size={14} /></span>
|
|
{/if}
|
|
<span class="meta-tile__text">
|
|
{#if tile.value}<span class="meta-tile__value">{tile.value}</span>{/if}
|
|
<span class="meta-tile__label">{tile.label}</span>
|
|
</span>
|
|
</a>
|
|
{:else if tile.onclick}
|
|
<button
|
|
type="button"
|
|
class="meta-tile meta-tone-{tile.tone || 'default'} meta-tile--interactive"
|
|
class:meta-tile--mono={tile.mono}
|
|
title={tile.hint}
|
|
onclick={(e: MouseEvent) => handleClick(e, tile)}
|
|
>
|
|
{#if tile.icon}
|
|
<span class="meta-tile__icon"><MdiIcon name={tile.icon} size={14} /></span>
|
|
{/if}
|
|
<span class="meta-tile__text">
|
|
{#if tile.value}<span class="meta-tile__value">{tile.value}</span>{/if}
|
|
<span class="meta-tile__label">{tile.label}</span>
|
|
</span>
|
|
</button>
|
|
{:else}
|
|
<div
|
|
class="meta-tile meta-tone-{tile.tone || 'default'}"
|
|
class:meta-tile--mono={tile.mono}
|
|
title={tile.hint}
|
|
>
|
|
{#if tile.icon}
|
|
<span class="meta-tile__icon"><MdiIcon name={tile.icon} size={14} /></span>
|
|
{/if}
|
|
<span class="meta-tile__text">
|
|
{#if tile.value}<span class="meta-tile__value">{tile.value}</span>{/if}
|
|
<span class="meta-tile__label">{tile.label}</span>
|
|
</span>
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
|
|
<style>
|
|
.meta-strip {
|
|
display: none;
|
|
min-width: 0;
|
|
flex: 1 1 auto;
|
|
gap: 0.45rem;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);
|
|
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 24px, #000 calc(100% - 24px), transparent 100%);
|
|
padding: 2px 18px;
|
|
}
|
|
@media (min-width: 1024px) {
|
|
.meta-strip {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.meta-tile {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.3rem 0.7rem;
|
|
border-radius: 999px;
|
|
background: var(--color-glass);
|
|
backdrop-filter: blur(14px) saturate(140%);
|
|
-webkit-backdrop-filter: blur(14px) saturate(140%);
|
|
border: 1px solid var(--color-border);
|
|
font-size: 0.72rem;
|
|
line-height: 1.1;
|
|
color: var(--color-muted-foreground);
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
max-width: 22rem;
|
|
min-width: 0;
|
|
text-decoration: none;
|
|
font-family: inherit;
|
|
transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease, transform 0.2s ease;
|
|
}
|
|
|
|
.meta-tile__icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
color: currentColor;
|
|
opacity: 0.9;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.meta-tile__text {
|
|
display: inline-flex;
|
|
align-items: baseline;
|
|
gap: 0.4rem;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.meta-tile__value {
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--color-foreground);
|
|
letter-spacing: -0.01em;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.meta-tile__label {
|
|
font-size: 0.72rem;
|
|
color: var(--color-muted-foreground);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
min-width: 0;
|
|
}
|
|
|
|
.meta-tile--mono .meta-tile__label,
|
|
.meta-tile--mono .meta-tile__value {
|
|
font-family: var(--font-mono);
|
|
letter-spacing: -0.02em;
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
.meta-tile--interactive {
|
|
cursor: pointer;
|
|
}
|
|
.meta-tile--interactive:hover {
|
|
border-color: var(--color-rule-strong);
|
|
background: var(--color-glass-strong);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Tone variants — applied to the dot/icon and accent border on hover */
|
|
.meta-tone-mint { box-shadow: inset 2px 0 0 var(--color-mint); }
|
|
.meta-tone-sky { box-shadow: inset 2px 0 0 var(--color-sky); }
|
|
.meta-tone-coral { box-shadow: inset 2px 0 0 var(--color-coral); }
|
|
.meta-tone-citrus { box-shadow: inset 2px 0 0 var(--color-citrus); }
|
|
.meta-tone-orchid { box-shadow: inset 2px 0 0 var(--color-orchid); }
|
|
.meta-tone-lavender { box-shadow: inset 2px 0 0 var(--color-primary); }
|
|
|
|
.meta-tone-mint .meta-tile__icon { color: var(--color-mint); }
|
|
.meta-tone-sky .meta-tile__icon { color: var(--color-sky); }
|
|
.meta-tone-coral .meta-tile__icon { color: var(--color-coral); }
|
|
.meta-tone-citrus .meta-tile__icon { color: var(--color-citrus); }
|
|
.meta-tone-orchid .meta-tile__icon { color: var(--color-orchid); }
|
|
.meta-tone-lavender .meta-tile__icon { color: var(--color-primary); }
|
|
</style>
|