Eve/src/routes/Arbor/ThreadView.ts
Danny Morabito 9893945f55
Enhance app with multi-CCN support and improved UX
Features:

 Add support for multiple CCNs
🔍 Implement sidebar hiding functionality
🎨 Revamp navigation system for better flow
🖌️ Replace icons with custom-designed assets and improved naming
🚀 Streamline initial setup process
📝 Refine terminology (e.g., "forum thread" → "topic")
🛠️ Enhance forum usability and interaction
2025-04-11 22:26:00 +02:00

239 lines
5.8 KiB
TypeScript

import { getSigner, ndk } from '@/ndk';
import type { NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import '@components/Arbor/ForumPost';
import '@components/Breadcrumbs';
import '@components/General/Button';
import { map } from 'lit/directives/map.js';
import { when } from 'lit/directives/when.js';
interface ForumPost {
id: string;
npub: string;
date: Date;
content: string;
}
@customElement('arx-arbor-thread-view')
export class ArborThreadView extends LitElement {
@property({ type: String })
threadId = '';
@state()
override title = '';
@state()
private posts: ForumPost[] = [];
private subscription: NDKSubscription | undefined;
static override styles = css`
:host {
display: block;
margin: 0 auto;
}
.thread-container {
background: var(--color-base-100);
border-radius: var(--radius-box);
border: var(--border) solid var(--color-base-300);
overflow: hidden;
margin-top: 1.5rem;
box-shadow: calc(var(--depth) * 2px) calc(var(--depth) * 2px)
calc(var(--depth) * 4px)
oklch(from var(--color-base-content) l c h / 0.1),
calc(var(--depth) * -1px) calc(var(--depth) * -1px)
calc(var(--depth) * 3px) oklch(from var(--color-base-100) l c h / 0.4);
}
.header {
background: var(--color-secondary);
color: var(--color-secondary-content);
padding: 1.5rem 2rem;
font-weight: 600;
font-size: 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
text-shadow: 0 1px 2px oklch(from var(--color-base-content) l c h / 0.2);
letter-spacing: 0.01em;
border-bottom: var(--border) solid var(--color-base-300);
}
.posts-container {
display: flex;
flex-direction: column;
gap: 0;
background: var(--color-base-200);
padding: 1.5rem;
min-height: 300px;
}
.actions {
background: var(--color-base-200);
padding: 1.25rem 1.5rem;
display: flex;
justify-content: flex-end;
border-top: var(--border) solid var(--color-base-300);
}
.empty-state {
padding: 3rem;
text-align: center;
color: var(--color-secondary);
font-style: italic;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
background: var(--color-base-100);
border-radius: var(--radius-field);
margin: 1rem 0;
border: var(--border) dashed var(--color-base-300);
}
arx-breadcrumbs {
margin-bottom: 1rem;
}
arx-forum-post {
border-bottom: var(--border) solid var(--color-base-300);
}
arx-forum-post:last-child {
border-bottom: none;
}
@media (max-width: 768px) {
.header {
padding: 1.25rem 1.5rem;
font-size: 1.1rem;
}
.posts-container {
padding: 1rem;
}
.actions {
padding: 1rem;
}
}
@media (max-width: 480px) {
:host {
padding: 0.5rem;
}
.header {
padding: 1rem;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.empty-state {
padding: 2rem 1rem;
}
}
`;
override async connectedCallback() {
super.connectedCallback();
await this.loadThread();
}
override disconnectedCallback() {
super.disconnectedCallback();
if (this.subscription) {
this.subscription.stop();
}
}
private async loadThread() {
try {
await getSigner();
const event = await ndk.fetchEvent(this.threadId);
if (!event) {
throw new Error('Could not load thread');
}
this.title = event.tags.find((tag) => tag[0] === 'name')?.[1] || '';
this.posts = [
{
id: event.id,
npub: event.pubkey,
date: new Date((event.created_at || 0) * 1000),
content: event.content,
},
];
// Subscribe to new posts
this.subscription = ndk
.subscribe({
kinds: [893 as NDKKind],
'#E': [this.threadId],
})
.on('event', (event) => {
this.posts = [
...this.posts,
{
id: event.id,
npub: event.pubkey,
date: new Date((event.created_at || 0) * 1000),
content: event.content,
},
];
});
} catch (error) {
console.error('Failed to load thread:', error);
alert('Could not load thread');
}
}
override render() {
const breadcrumbItems = [{ text: 'Home', href: '/' }, { text: 'Arbor', href: '/arbor' }, { text: this.title }];
return html`
<arx-breadcrumbs .items=${breadcrumbItems}></arx-breadcrumbs>
<div class="thread-container">
<div class="header">
<span>${this.title || 'Loading thread...'}</span>
</div>
<div class="posts-container">
${when(
this.posts.length === 0,
() => html`<div class="empty-state">No posts available</div>`,
() =>
map(
this.posts,
(post) => html`
<arx-forum-post
id=${post.id}
.threadId=${this.threadId}
.npub=${post.npub}
.date=${post.date}
.content=${post.content}
></arx-forum-post>
`,
),
)}
</div>
<div class="actions">
<arx-button label="New Post" href="/arbor/new-post/${this.threadId}">
New Post
</arx-button>
</div>
</div>
`;
}
}