You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

369 lines
8.9 KiB

<script lang="ts">
import ProfileBadge from './ProfileBadge.svelte';
import type { Snippet } from 'svelte';
interface Props {
pubkey: string;
relativeTime?: string;
clientName?: string | null;
topics?: string[];
inline?: boolean; // If true, use inline ProfileBadge (no picture)
showDivider?: boolean; // If true, show divider line below header
kindLabel?: string; // Optional kind label to show on the right
badges?: Snippet;
left?: Snippet;
actions?: Snippet;
}
let {
pubkey,
relativeTime,
clientName,
topics = [],
inline = false,
showDivider = false,
kindLabel,
badges,
left,
actions
}: Props = $props();
// Load profile to get NIP-05 for separate display
let profile = $state<{ name?: string; nip05?: string[] } | null>(null);
let lastLoadedPubkey = $state<string | null>(null);
// Always show NIP-05 if it exists (it's a verified identifier, different from display name)
let shouldShowNip05 = $derived.by(() => {
return !!(profile?.nip05 && profile.nip05.length > 0);
});
$effect(() => {
if (pubkey && pubkey !== lastLoadedPubkey) {
profile = null;
loadProfile();
}
});
async function loadProfile() {
if (!pubkey || lastLoadedPubkey === pubkey) return;
const currentPubkey = pubkey;
try {
const { fetchProfile } = await import('../../services/user-data.js');
const p = await fetchProfile(currentPubkey);
if (pubkey === currentPubkey) {
profile = p;
lastLoadedPubkey = currentPubkey;
}
} catch (error) {
// Silently fail - profile loading is non-critical
}
}
</script>
<div class="card-header" class:with-divider={showDivider}>
<div class="card-header-left">
<div class="profile-badge-wrapper">
<ProfileBadge pubkey={pubkey} inline={inline} hideNip05={true} />
</div>
{#if shouldShowNip05 && profile?.nip05}
<span class="nip05-text text-fog-text-light dark:text-fog-dark-text-light nip05-separate">
{profile.nip05[0]}
</span>
{/if}
{#if badges}
{@render badges()}
{/if}
{#if relativeTime}
<span class="time-text">{relativeTime}</span>
{/if}
{#if clientName}
<span class="client-text">via {clientName}</span>
{/if}
{#if topics && topics.length > 0}
{#each topics.slice(0, 3) as topic}
<a href="/topics/{topic}" class="topic-badge">{topic}</a>
{/each}
{/if}
{#if left}
{@render left()}
{/if}
</div>
<div class="card-header-right">
{#if actions}
{@render actions()}
{/if}
{#if kindLabel}
<span class="kind-label">{kindLabel}</span>
{/if}
</div>
{#if showDivider}
<hr class="card-header-divider" />
{/if}
</div>
<style>
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.75rem;
position: relative;
min-width: 0;
flex-wrap: wrap;
width: 100%;
max-width: 100%;
box-sizing: border-box;
overflow: hidden;
word-break: break-word;
overflow-wrap: anywhere;
line-height: 1.5;
}
.card-header.with-divider {
margin-bottom: 1rem;
}
.card-header-left {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
flex: 1;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
word-break: break-word;
overflow-wrap: anywhere;
}
.profile-badge-wrapper {
min-width: 0 !important;
max-width: 100% !important;
flex-shrink: 1 !important;
overflow: visible !important;
}
/* Keep profile badge and NIP-05 together, allow time to wrap */
.profile-badge-wrapper {
flex-shrink: 1;
min-width: 0;
}
.nip05-separate {
flex-shrink: 1;
min-width: 0;
}
/* Time can wrap to next line if needed */
.time-text {
flex-shrink: 0;
}
.card-header-left :global(.profile-badge) {
max-width: 100% !important;
width: auto !important;
min-width: 0 !important;
word-break: break-word !important;
overflow-wrap: anywhere !important;
box-sizing: border-box !important;
flex-shrink: 1 !important;
flex-wrap: wrap !important;
}
.nip05-separate {
font-size: 0.875em;
white-space: nowrap;
flex-shrink: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.nip05-separate {
font-size: 0.875em;
white-space: nowrap;
flex-shrink: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
:global(.dark) .nip05-separate {
color: var(--fog-dark-text-light, #a8b8d0);
}
.time-text,
.client-text {
font-size: 0.75em;
color: var(--fog-text-light, #52667a);
white-space: nowrap;
flex-shrink: 0;
}
:global(.dark) .time-text,
:global(.dark) .client-text {
color: var(--fog-dark-text-light, #a8b8d0);
}
.topic-badge {
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
background: var(--fog-border, #e5e7eb);
color: var(--fog-text-light, #52667a);
text-decoration: none;
font-size: 0.75em;
transition: opacity 0.2s;
flex-shrink: 0;
}
.topic-badge:hover {
text-decoration: underline;
opacity: 0.8;
}
:global(.dark) .topic-badge {
background: var(--fog-dark-border, #374151);
color: var(--fog-dark-text-light, #a8b8d0);
}
.card-header-right {
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
flex-wrap: wrap;
}
.kind-label {
font-size: 0.75rem;
color: var(--fog-text-light, #52667a);
padding: 0.25rem 0.5rem;
background: var(--fog-highlight, #f3f4f6);
border-radius: 0.25rem;
white-space: nowrap;
}
:global(.dark) .kind-label {
color: var(--fog-dark-text-light, #a8b8d0);
background: var(--fog-dark-highlight, #374151);
}
.card-header-divider {
position: absolute;
bottom: -0.5rem;
left: 0;
right: 0;
margin: 0;
border: none;
border-top: 1px solid var(--fog-border, #e5e7eb);
}
:global(.dark) .card-header-divider {
border-top-color: var(--fog-dark-border, #374151);
}
/* On wider screens, keep on one line but allow truncation if needed */
@media (min-width: 641px) {
.card-header-left :global(.nip05-container) {
flex-wrap: nowrap !important;
overflow: hidden !important;
}
.card-header-left :global(.profile-name) {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
flex-shrink: 1 !important;
min-width: 0 !important;
}
.card-header-left :global(.nip05-text),
.card-header-left :global(.break-nip05) {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
flex-shrink: 1 !important;
min-width: 0 !important;
word-break: normal !important;
overflow-wrap: normal !important;
word-wrap: normal !important;
max-width: 100% !important;
box-sizing: border-box !important;
}
}
@media (max-width: 640px) {
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.card-header-left {
width: 100%;
flex-wrap: wrap;
gap: 0.5rem;
}
.card-header-left > span {
white-space: normal !important;
word-break: break-word !important;
overflow-wrap: anywhere !important;
max-width: 100%;
flex-shrink: 1;
min-width: 0;
}
.card-header-right {
width: 100%;
justify-content: flex-start;
flex-wrap: wrap;
}
.profile-badge-wrapper {
max-width: 100% !important;
width: 100% !important;
flex-shrink: 1 !important;
min-width: 0 !important;
}
.card-header-left :global(.profile-badge) {
max-width: 100% !important;
width: 100% !important;
min-width: 0 !important;
flex-shrink: 1 !important;
}
.card-header-left :global(.nip05-container) {
flex-direction: column !important;
align-items: flex-start !important;
width: 100% !important;
}
/* On narrow screens, allow wrapping instead of truncating */
.card-header-left :global(.nip05-text),
.card-header-left :global(.break-nip05),
.card-header-left :global(.nip05-text.break-all),
.card-header-left :global(.break-nip05.break-all),
.card-header-left :global(span.nip05-text),
.card-header-left :global(span.break-nip05),
.card-header-left :global(span.nip05-text.break-all),
.card-header-left :global(span.break-nip05.break-all) {
max-width: none !important;
overflow: visible !important;
text-overflow: clip !important;
white-space: normal !important;
word-break: break-word !important;
overflow-wrap: anywhere !important;
word-break: normal !important;
overflow-wrap: normal !important;
word-wrap: normal !important;
display: inline-block !important;
width: auto !important;
box-sizing: border-box !important;
}
}
</style>