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.
86 lines
2.5 KiB
86 lines
2.5 KiB
<script lang="ts"> |
|
import ProfileBadge from '../../components/layout/ProfileBadge.svelte'; |
|
import type { NostrEvent } from '../../types/nostr.js'; |
|
|
|
interface Props { |
|
thread: NostrEvent; |
|
} |
|
|
|
let { thread }: Props = $props(); |
|
|
|
function getTitle(): string { |
|
const titleTag = thread.tags.find((t) => t[0] === 'title'); |
|
return titleTag?.[1] || 'Untitled'; |
|
} |
|
|
|
function getTopics(): string[] { |
|
return thread.tags.filter((t) => t[0] === 't').map((t) => t[1]).slice(0, 3); |
|
} |
|
|
|
function getPreview(): string { |
|
// First 250 chars, plaintext (no markdown/images) |
|
const plaintext = thread.content.replace(/[#*_`\[\]()]/g, '').replace(/\n/g, ' '); |
|
return plaintext.slice(0, 250) + (plaintext.length > 250 ? '...' : ''); |
|
} |
|
|
|
function getRelativeTime(): string { |
|
const now = Math.floor(Date.now() / 1000); |
|
const diff = now - thread.created_at; |
|
const hours = Math.floor(diff / 3600); |
|
const days = Math.floor(diff / 86400); |
|
|
|
if (days > 0) return `${days}d ago`; |
|
if (hours > 0) return `${hours}h ago`; |
|
return 'just now'; |
|
} |
|
|
|
function getClientName(): string | null { |
|
const clientTag = thread.tags.find((t) => t[0] === 'client'); |
|
return clientTag?.[1] || null; |
|
} |
|
</script> |
|
|
|
<article class="thread-card bg-fog-post dark:bg-fog-dark-post border border-fog-border dark:border-fog-dark-border p-4 mb-4 shadow-sm dark:shadow-lg"> |
|
<div class="flex justify-between items-start mb-2"> |
|
<h3 class="text-lg font-semibold"> |
|
<a href="/thread/{thread.id}">{getTitle()}</a> |
|
</h3> |
|
<span class="text-sm text-fog-text-light dark:text-fog-dark-text-light">{getRelativeTime()}</span> |
|
</div> |
|
|
|
<div class="mb-2 flex items-center gap-2"> |
|
<ProfileBadge pubkey={thread.pubkey} /> |
|
{#if getClientName()} |
|
<span class="text-xs text-fog-text-light dark:text-fog-dark-text-light">via {getClientName()}</span> |
|
{/if} |
|
</div> |
|
|
|
<p class="text-sm mb-2">{getPreview()}</p> |
|
|
|
{#if getTopics().length > 0} |
|
<div class="flex gap-2 mb-2"> |
|
{#each getTopics() as topic} |
|
<span class="text-xs bg-fog-highlight dark:bg-fog-dark-highlight text-fog-text dark:text-fog-dark-text px-2 py-1 rounded">{topic}</span> |
|
{/each} |
|
</div> |
|
{/if} |
|
|
|
<div class="text-xs text-fog-text-light dark:text-fog-dark-text-light"> |
|
<a href="/thread/{thread.id}">View thread →</a> |
|
</div> |
|
</article> |
|
|
|
<style> |
|
.thread-card { |
|
max-width: var(--content-width); |
|
} |
|
|
|
.thread-card a { |
|
color: inherit; |
|
text-decoration: none; |
|
} |
|
|
|
.thread-card a:hover { |
|
text-decoration: underline; |
|
} |
|
</style>
|
|
|