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
239 lines
5.8 KiB
TypeScript
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>
|
|
`;
|
|
}
|
|
}
|