Browse Source

Search results, partial

master
Nuša Pukšič 7 months ago committed by buttercat1791
parent
commit
a348d080bd
  1. 189
      src/lib/a/cards/AEventPreview.svelte
  2. 10
      src/lib/snippets/UserSnippets.svelte
  3. 2
      src/routes/+layout.svelte

189
src/lib/a/cards/AEventPreview.svelte

@ -1,15 +1,13 @@
<script lang="ts"> <script lang="ts">
import { Card } from "flowbite-svelte"; import { Card, Button } from "flowbite-svelte";
import ViewPublicationLink from "$lib/components/util/ViewPublicationLink.svelte"; import ViewPublicationLink from "$lib/components/util/ViewPublicationLink.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte"; import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { toNpub, getMatchingTags } from "$lib/utils/nostrUtils"; import { toNpub, getMatchingTags } from "$lib/utils/nostrUtils";
import type { NDKEvent } from "$lib/utils/nostrUtils"; import type { NDKEvent } from "$lib/utils/nostrUtils";
import { preventDefault } from "svelte/legacy";
// AI-NOTE: 2025-08-16 - AEventPreview centralizes display logic for search result cards
// Used for primary search results (profiles or events). Extend cautiously for other contexts.
let { let {
event, event,
index,
label = "", label = "",
community = false, community = false,
truncateContentAt = 200, truncateContentAt = 200,
@ -17,32 +15,42 @@
showSummary = true, showSummary = true,
showDeferralNaddr = true, showDeferralNaddr = true,
showPublicationLink = true, showPublicationLink = true,
onSelect showContent = true,
actions,
onSelect,
onDeferralClick
}: { }: {
event: NDKEvent; event: NDKEvent;
index?: number;
label?: string; label?: string;
community?: boolean|string; community?: boolean;
truncateContentAt?: number; truncateContentAt?: number;
showKind?: boolean; showKind?: boolean;
showSummary?: boolean; showSummary?: boolean;
showDeferralNaddr?: boolean; showDeferralNaddr?: boolean;
showPublicationLink?: boolean; showPublicationLink?: boolean;
showContent?: boolean;
actions?: { label: string; onClick: (ev: NDKEvent) => void; variant?: "primary" | "light" | "alternative" }[];
onSelect?: (ev: NDKEvent) => void; onSelect?: (ev: NDKEvent) => void;
onDeferralClick?: (naddr: string, ev: NDKEvent) => void;
} = $props(); } = $props();
// Parse kind 0 profile JSON type ProfileData = {
function parseProfileContent(ev: NDKEvent): {
name?: string; name?: string;
display_name?: string; display_name?: string;
about?: string; about?: string;
picture?: string; picture?: string;
} | null { banner?: string;
website?: string;
lud16?: string;
nip05?: string;
};
function parseProfileContent(ev: NDKEvent): ProfileData | null {
if (ev.kind !== 0 || !ev.content) { if (ev.kind !== 0 || !ev.content) {
return null; return null;
} }
try { try {
return JSON.parse(ev.content); return JSON.parse(ev.content) as ProfileData;
} catch { } catch {
return null; return null;
} }
@ -60,6 +68,16 @@
const summary = showSummary ? getSummary(event) : undefined; const summary = showSummary ? getSummary(event) : undefined;
const deferralNaddr = showDeferralNaddr ? getDeferralNaddr(event) : undefined; const deferralNaddr = showDeferralNaddr ? getDeferralNaddr(event) : undefined;
function clippedContent(content: string): string {
if (!showContent) {
return "";
}
if (!truncateContentAt || content.length <= truncateContentAt) {
return content;
}
return content.slice(0, truncateContentAt) + "...";
}
function handleSelect(): void { function handleSelect(): void {
onSelect?.(event); onSelect?.(event);
} }
@ -71,121 +89,124 @@
} }
} }
function clippedContent(content: string): string { function handleDeferralClick(e: MouseEvent): void {
if (!truncateContentAt || content.length <= truncateContentAt) { e.stopPropagation();
return content; if (deferralNaddr) {
onDeferralClick?.(deferralNaddr, event);
} }
return content.slice(0, truncateContentAt) + "...";
} }
const displayName: string | undefined =
profileData?.display_name || profileData?.name;
const avatarFallback: string =
(displayName || event.pubkey || "?").slice(0, 1).toUpperCase();
const createdDate: string =
event.created_at
? new Date(event.created_at * 1000).toLocaleDateString()
: "Unknown date";
const computedActions =
actions && actions.length > 0
? actions
: [
{
label: "Open",
onClick: (ev: NDKEvent) => onSelect?.(ev),
variant: "light" as const
}
];
</script> </script>
<Card <Card
class="card" class="hover:bg-highlight dark:bg-primary-900/70 bg-primary-50 dark:hover:bg-primary-800 border-primary-400 border-s-4 transition-colors cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500 shadow-none"
role="button" role="group"
tabindex="0" tabindex="0"
aria-label="Event preview"
onclick={handleSelect} onclick={handleSelect}
onkeydown={handleKeydown} onkeydown={handleKeydown}
size="xl"
> >
<div class="flex flex-col gap-1 p-4"> <!-- Header -->
<div class="flex items-center gap-2 mb-1"> <div class="flex items-start w-full p-4">
<!-- Meta -->
<div class="flex flex-row w-full gap-3 items-center min-w-0">
{#if label} {#if label}
<span class="font-medium text-gray-800 dark:text-gray-100">{label}</span> <span class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
{label}
</span>
{/if} {/if}
{#if showKind} {#if showKind}
<span class="text-xs text-gray-600 dark:text-gray-400">Kind: {event.kind}</span> <span class="text-[10px] px-1.5 py-0.5 rounded bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
Kind {event.kind}
</span>
{/if} {/if}
{#if community} {#if community}
<div <span
class="flex-shrink-0 w-4 h-4 bg-yellow-100 dark:bg-yellow-900 rounded-full flex items-center justify-center" class="inline-flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300"
title="Has posted to the community" title="Has posted to the community"
> >
<svg <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
class="w-3 h-3 text-yellow-600 dark:text-yellow-400" <path
fill="currentColor" d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"
viewBox="0 0 24 24" />
>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg> </svg>
</div> Community
{:else}
<div class="flex-shrink-0 w-4 h-4"></div>
{/if}
<span class="text-xs text-gray-600 dark:text-gray-400">
{@render userBadge(
toNpub(event.pubkey) as string,
profileData?.display_name || profileData?.name
)}
</span> </span>
<span class="text-xs text-gray-500 dark:text-gray-400 ml-auto"> {/if}
{event.created_at <span class="text-xs ml-auto mb-4">
? new Date(event.created_at * 1000).toLocaleDateString() {createdDate}
: "Unknown date"}
</span> </span>
</div> </div>
{#if event.kind === 0 && profileData} <div class="flex flex-row">
<div class="flex items-center gap-3 mb-2"> {@render userBadge(toNpub(event.pubkey) as string, displayName)}
{#if profileData.picture}
<img
src={profileData.picture}
alt="Profile"
class="w-12 h-12 rounded-full object-cover border border-gray-200 dark:border-gray-600"
onerror={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
{:else}
<div
class="w-12 h-12 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center border border-gray-200 dark:border-gray-600"
>
<span class="text-lg font-medium text-gray-600 dark:text-gray-300">
{(profileData.display_name || profileData.name || event.pubkey.slice(0, 1)).toUpperCase()}
</span>
</div> </div>
{/if}
<div class="flex flex-col min-w-0 flex-1">
{#if profileData.display_name || profileData.name}
<span class="font-medium text-gray-900 dark:text-gray-100 truncate">
{profileData.display_name || profileData.name}
</span>
{/if}
{#if profileData.about}
<span class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
{profileData.about}
</span>
{/if}
</div> </div>
<!-- Body -->
<div class="px-4 pb-3 flex flex-col gap-2">
{#if event.kind === 0 && profileData?.about}
<div class="text-sm text-gray-700 dark:text-gray-300 line-clamp-3">
{clippedContent(profileData.about)}
</div> </div>
{:else} {:else}
{#if summary} {#if summary}
<div class="text-sm text-primary-900 dark:text-primary-200 mb-1 line-clamp-2"> <div class="text-sm text-primary-900 dark:text-primary-200 line-clamp-2">
{summary} {summary}
</div> </div>
{/if} {/if}
{#if deferralNaddr} {#if deferralNaddr}
<div class="text-xs text-primary-800 dark:text-primary-300 mb-1"> <div class="text-xs text-primary-800 dark:text-primary-300">
Read Read
<span <span
class="underline text-primary-700 dark:text-primary-400 hover:text-primary-900 dark:hover:text-primary-200 break-all cursor-pointer" class="underline text-primary-700 dark:text-primary-400 hover:text-primary-900 dark:hover:text-primary-200 break-all cursor-pointer"
onclick={(e) => { role="button"
e.stopPropagation(); tabindex="0"
// Parent should intercept navigation by listening onSelect and inspecting event tags if needed onclick={handleDeferralClick}
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleDeferralClick(e as unknown as MouseEvent);
}
}} }}
> >
{deferralNaddr} {deferralNaddr}
</span> </span>
</div> </div>
{/if} {/if}
{#if showPublicationLink}
<div class="text-xs text-blue-600 dark:text-blue-400 mb-1"> {#if showContent && event.content}
<ViewPublicationLink event={event} /> <div class="text-sm text-gray-800 dark:text-gray-200 line-clamp-3 break-words mb-4">
</div>
{/if}
{#if event.content}
<div class="text-sm text-gray-800 dark:text-gray-200 mt-1 line-clamp-2 break-words">
{clippedContent(event.content)} {clippedContent(event.content)}
</div> </div>
{/if} {/if}
{/if} {/if}
</div> </div>
<!-- Footer / Actions -->
{#if showPublicationLink && event.kind !== 0}
<div class="px-4 pt-2 pb-3 border-t border-primary-200 dark:border-primary-700 flex items-center gap-2 flex-wrap">
<ViewPublicationLink {event} />
</div>
{/if}
</Card> </Card>

10
src/lib/snippets/UserSnippets.svelte

@ -19,7 +19,7 @@
{@const p = profile as UserProfile} {@const p = profile as UserProfile}
<span class="inline-flex items-center gap-0.5"> <span class="inline-flex items-center gap-0.5">
<button <button
class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" class="npub-badge bg-transparent border-none !p-0 underline cursor-pointer"
onclick={() => goto(`/events?id=${npub}`)} onclick={() => goto(`/events?id=${npub}`)}
> >
@{p.displayName || @{p.displayName ||
@ -32,7 +32,7 @@
{@const debugError = console.error("Error fetching profile for", npub, ":", error)} {@const debugError = console.error("Error fetching profile for", npub, ":", error)}
<span class="inline-flex items-center gap-0.5"> <span class="inline-flex items-center gap-0.5">
<button <button
class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" class="npub-badge bg-transparent border-none !p-0 underline cursor-pointer"
onclick={() => goto(`/events?id=${npub}`)} onclick={() => goto(`/events?id=${npub}`)}
> >
@{npub.slice(0, 8) + "..." + npub.slice(-4)} @{npub.slice(0, 8) + "..." + npub.slice(-4)}
@ -43,7 +43,7 @@
{#await createProfileLinkWithVerification(npub as string, displayText, ndk)} {#await createProfileLinkWithVerification(npub as string, displayText, ndk)}
<span class="inline-flex items-center gap-0.5"> <span class="inline-flex items-center gap-0.5">
<button <button
class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" class="npub-badge bg-transparent border-none !p-0 underline cursor-pointer"
onclick={() => goto(`/events?id=${npub}`)} onclick={() => goto(`/events?id=${npub}`)}
> >
@{displayText} @{displayText}
@ -52,7 +52,7 @@
{:then html} {:then html}
<span class="inline-flex items-center gap-0.5"> <span class="inline-flex items-center gap-0.5">
<button <button
class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" class="npub-badge bg-transparent border-none !p-0 underline cursor-pointer"
onclick={() => goto(`/events?id=${npub}`)} onclick={() => goto(`/events?id=${npub}`)}
> >
@{displayText} @{displayText}
@ -62,7 +62,7 @@
{:catch} {:catch}
<span class="inline-flex items-center gap-0.5"> <span class="inline-flex items-center gap-0.5">
<button <button
class="npub-badge bg-transparent border-none p-0 underline cursor-pointer" class="npub-badge bg-transparent border-none !p-0 underline cursor-pointer"
onclick={() => goto(`/events?id=${npub}`)} onclick={() => goto(`/events?id=${npub}`)}
> >
@{displayText} @{displayText}

2
src/routes/+layout.svelte

@ -185,7 +185,7 @@
<div class="min-h-screen flex flex-col"> <div class="min-h-screen flex flex-col">
<ANavbar {currentPath} /> <ANavbar {currentPath} />
<main class="flex-1 w-full"> <main class="flex-1 flex-col w-full mt-[75px] self-center">
{@render children()} {@render children()}
</main> </main>

Loading…
Cancel
Save