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.
256 lines
6.7 KiB
256 lines
6.7 KiB
<script lang="ts"> |
|
import { Button, Modal, Popover } from "flowbite-svelte"; |
|
import { |
|
DotsVerticalOutline, |
|
EyeOutline, |
|
ClipboardCleanOutline, |
|
} from "flowbite-svelte-icons"; |
|
import CopyToClipboard from "$components/util/CopyToClipboard.svelte"; |
|
import { userBadge } from "$lib/snippets/UserSnippets.svelte"; |
|
import { neventEncode, naddrEncode } from "$lib/utils"; |
|
import { standardRelays, fallbackRelays } from "$lib/consts"; |
|
import { ndkSignedIn, inboxRelays } from "$lib/ndk"; |
|
import { feedType } from "$lib/stores"; |
|
import { FeedType } from "$lib/consts"; |
|
import { goto } from "$app/navigation"; |
|
import type { NDKEvent } from "$lib/utils/nostrUtils"; |
|
|
|
const { |
|
event, |
|
title, |
|
author, |
|
originalAuthor, |
|
summary, |
|
image, |
|
version, |
|
source, |
|
type, |
|
language, |
|
publisher, |
|
identifier, |
|
} = $props<{ |
|
event: NDKEvent; |
|
title?: string; |
|
author?: string; |
|
originalAuthor?: string; |
|
summary?: string; |
|
image?: string; |
|
version?: string; |
|
source?: string; |
|
type?: string; |
|
language?: string; |
|
publisher?: string; |
|
identifier?: string; |
|
}>(); |
|
|
|
// UI state |
|
let detailsModalOpen: boolean = $state(false); |
|
let isOpen: boolean = $state(false); |
|
|
|
/** |
|
* Selects the appropriate relay set based on user state and feed type |
|
* - Uses user's inbox relays when signed in and viewing personal feed |
|
* - Falls back to standard relays for anonymous users or standard feed |
|
*/ |
|
let activeRelays = $derived( |
|
(() => { |
|
const isUserFeed = $ndkSignedIn && $feedType === FeedType.UserRelays; |
|
const relays = isUserFeed ? $inboxRelays : standardRelays; |
|
|
|
console.debug("[CardActions] Selected relays:", { |
|
eventId: event.id, |
|
isSignedIn: $ndkSignedIn, |
|
feedType: $feedType, |
|
isUserFeed, |
|
relayCount: relays.length, |
|
relayUrls: relays, |
|
}); |
|
|
|
return relays; |
|
})(), |
|
); |
|
|
|
/** |
|
* Opens the actions popover menu |
|
*/ |
|
function openPopover() { |
|
isOpen = true; |
|
} |
|
|
|
/** |
|
* Closes the actions popover menu and removes focus |
|
*/ |
|
function closePopover() { |
|
isOpen = false; |
|
const menu = document.getElementById("dots-" + event.id); |
|
if (menu) menu.blur(); |
|
} |
|
|
|
/** |
|
* Gets the appropriate identifier (nevent or naddr) for copying |
|
* @param type - The type of identifier to get ('nevent' or 'naddr') |
|
* @returns The encoded identifier string |
|
*/ |
|
function getIdentifier(type: "nevent" | "naddr"): string { |
|
const encodeFn = type === "nevent" ? neventEncode : naddrEncode; |
|
const identifier = encodeFn(event, activeRelays); |
|
return identifier; |
|
} |
|
|
|
/** |
|
* Opens the event details modal |
|
*/ |
|
function viewDetails() { |
|
detailsModalOpen = true; |
|
} |
|
|
|
/** |
|
* Navigates to the event details page |
|
*/ |
|
function viewEventDetails() { |
|
const nevent = getIdentifier('nevent'); |
|
goto(`/events?id=${encodeURIComponent(nevent)}`); |
|
} |
|
|
|
</script> |
|
|
|
<div |
|
class="group bg-highlight dark:bg-primary-1000 rounded" |
|
role="group" |
|
onmouseenter={openPopover} |
|
> |
|
<!-- Main button --> |
|
<Button |
|
type="button" |
|
id="dots-{event.id}" |
|
class=" hover:bg-primary-0 dark:text-highlight dark:hover:bg-primary-800 p-1 dots" |
|
color="none" |
|
data-popover-target="popover-actions" |
|
> |
|
<DotsVerticalOutline class="h-6 w-6" /> |
|
<span class="sr-only">Open actions menu</span> |
|
</Button> |
|
|
|
{#if isOpen} |
|
<Popover |
|
id="popover-actions" |
|
placement="bottom" |
|
trigger="click" |
|
class="popover-leather w-fit z-10" |
|
onmouseleave={closePopover} |
|
> |
|
<div class="flex flex-row justify-between space-x-4"> |
|
<div class="flex flex-col text-nowrap"> |
|
<ul class="space-y-2"> |
|
<li> |
|
<button |
|
class="btn-leather w-full text-left" |
|
onclick={viewDetails} |
|
> |
|
<EyeOutline class="inline mr-2" /> View details |
|
</button> |
|
</li> |
|
<li> |
|
<CopyToClipboard |
|
displayText="Copy naddr address" |
|
copyText={getIdentifier("naddr")} |
|
icon={ClipboardCleanOutline} |
|
/> |
|
</li> |
|
<li> |
|
<CopyToClipboard |
|
displayText="Copy nevent address" |
|
copyText={getIdentifier("nevent")} |
|
icon={ClipboardCleanOutline} |
|
/> |
|
</li> |
|
</ul> |
|
</div> |
|
</div> |
|
</Popover> |
|
{/if} |
|
<!-- Event details --> |
|
<Modal |
|
class="modal-leather" |
|
title="Publication details" |
|
bind:open={detailsModalOpen} |
|
autoclose |
|
outsideclose |
|
size="sm" |
|
> |
|
<div class="flex flex-row space-x-4"> |
|
{#if image} |
|
<div |
|
class="flex col justify-center align-middle h-32 w-24 min-w-20 max-w-24 overflow-hidden" |
|
> |
|
<img |
|
src={image} |
|
alt="Publication cover" |
|
class="rounded w-full h-full object-cover" |
|
/> |
|
</div> |
|
{/if} |
|
<div class="flex flex-col col space-y-5 justify-center align-middle"> |
|
<h1 class="text-3xl font-bold mt-5">{title || "Untitled"}</h1> |
|
<h2 class="text-base font-bold"> |
|
by |
|
{#if originalAuthor} |
|
{@render userBadge(originalAuthor, author)} |
|
{:else} |
|
{author || "Unknown"} |
|
{/if} |
|
</h2> |
|
{#if version} |
|
<h4 |
|
class="text-base font-medium text-primary-700 dark:text-primary-300 mt-2" |
|
> |
|
Version: {version} |
|
</h4> |
|
{/if} |
|
</div> |
|
</div> |
|
|
|
{#if summary} |
|
<div class="flex flex-row"> |
|
<p class="text-base text-primary-900 dark:text-highlight">{summary}</p> |
|
</div> |
|
{/if} |
|
|
|
<div class="flex flex-row"> |
|
<h4 class="text-base font-normal mt-2"> |
|
Index author: {@render userBadge(event.pubkey, author)} |
|
</h4> |
|
</div> |
|
|
|
<div class="flex flex-col pb-4 space-y-1"> |
|
{#if source} |
|
<h5 class="text-sm"> |
|
Source: <a |
|
class="underline" |
|
href={source} |
|
target="_blank" |
|
rel="noopener noreferrer">{source}</a |
|
> |
|
</h5> |
|
{/if} |
|
{#if type} |
|
<h5 class="text-sm">Publication type: {type}</h5> |
|
{/if} |
|
{#if language} |
|
<h5 class="text-sm">Language: {language}</h5> |
|
{/if} |
|
{#if publisher} |
|
<h5 class="text-sm">Published by: {publisher}</h5> |
|
{/if} |
|
{#if identifier} |
|
<h5 class="text-sm">Identifier: {identifier}</h5> |
|
{/if} |
|
<button |
|
class="mt-4 btn-leather text-center text-primary-700 hover:text-primary-800 dark:text-primary-400 dark:hover:text-primary-300 font-semibold" |
|
onclick={viewEventDetails} |
|
> |
|
View Event Details |
|
</button> |
|
</div> |
|
</Modal> |
|
</div>
|
|
|