diff --git a/src/app/components/EventReply.svelte b/src/app/components/EventReply.svelte index 765887be..43d192e8 100644 --- a/src/app/components/EventReply.svelte +++ b/src/app/components/EventReply.svelte @@ -1,15 +1,17 @@ + +
+
+

+ {#if h} + # + {:else} + General + {/if} +

+ + {threads.length} + {threads.length === 1 ? "topic" : "topics"} + +
+ + {#each threads as event (event.id)} + + {/each} +
diff --git a/src/app/components/ThreadBoardItem.svelte b/src/app/components/ThreadBoardItem.svelte new file mode 100644 index 00000000..6f2dc129 --- /dev/null +++ b/src/app/components/ThreadBoardItem.svelte @@ -0,0 +1,48 @@ + + + +
+

{title || "Untitled thread"}

+

+ by +

+
+ +

+ Replies · + {replyCount} +

+

+ Last · + {formatTimestamp(lastActive)} +

+ diff --git a/src/app/components/ThreadPagination.svelte b/src/app/components/ThreadPagination.svelte new file mode 100644 index 00000000..25e07adf --- /dev/null +++ b/src/app/components/ThreadPagination.svelte @@ -0,0 +1,66 @@ + + +
+

Page {page} of {pageCount}

+
+ + + {#each pages as p, i (p)} + {#if i > 0 && p - pages[i - 1] > 1} + + {/if} + + {/each} + + +
+
diff --git a/src/app/components/ThreadPost.svelte b/src/app/components/ThreadPost.svelte new file mode 100644 index 00000000..c4785458 --- /dev/null +++ b/src/app/components/ThreadPost.svelte @@ -0,0 +1,93 @@ + + +
+
+ +
+
+ {formatTimestamp(event.created_at)} + +
+
+ {#if isComment} + + {:else} + + {/if} +
+
+ + {#if isComment} + + {:else} + + {/if} +
+
+
+
diff --git a/src/app/routes.ts b/src/app/routes.ts index 04e5de78..95ab4a42 100644 --- a/src/app/routes.ts +++ b/src/app/routes.ts @@ -20,7 +20,7 @@ import { getRelaysFromList, } from "@welshman/util" import {makeChatId} from "@app/chats" -import {entityLink} from "@app/env" +import {entityLink, PLATFORM_URL} from "@app/env" import {encodeRelay, hasNip29} from "@app/relays" import {DM_KINDS} from "@app/content" import {ROOM} from "@app/groups" @@ -211,6 +211,17 @@ export const getEventPath = (event: TrustedEvent, urls: string[]) => { return entityLink(nip19.neventEncode({id: event.id, relays: urls})) } +export const makeEventPermalink = (event: TrustedEvent, url?: string) => { + const urls = url ? [url] : Array.from(tracker.getRelays(event.id)) + const path = getEventPath(event, urls) + + if (path.includes("://")) { + return path + } + + return `${PLATFORM_URL}${path}#${nip19.neventEncode({id: event.id, relays: urls})}` +} + export const getRoomItemPath = (url: string, event: TrustedEvent) => { switch (event.kind) { case THREAD: diff --git a/src/routes/spaces/[relay]/threads/+page.svelte b/src/routes/spaces/[relay]/threads/+page.svelte index c36c3542..720a54b7 100644 --- a/src/routes/spaces/[relay]/threads/+page.svelte +++ b/src/routes/spaces/[relay]/threads/+page.svelte @@ -3,10 +3,9 @@ import {readable} from "svelte/store" import type {Readable} from "svelte/store" import {page} from "$app/stores" - import {sortBy, partition, spec, max, pushToMapKey} from "@welshman/lib" + import {sortBy, partition, spec, max, pushToMapKey, groupBy} from "@welshman/lib" import type {TrustedEvent} from "@welshman/util" import {THREAD, getTagValue} from "@welshman/util" - import {fly} from "@lib/transition" import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl" import Add from "@assets/icons/add.svg?dataurl" import Icon from "@lib/components/Icon.svelte" @@ -14,9 +13,10 @@ import PageContent from "@lib/components/PageContent.svelte" import Spinner from "@lib/components/Spinner.svelte" import SpaceBar from "@app/components/SpaceBar.svelte" - import ThreadItem from "@app/components/ThreadItem.svelte" + import ThreadBoard from "@app/components/ThreadBoard.svelte" import ThreadCreate from "@app/components/ThreadCreate.svelte" import {decodeRelay} from "@app/relays" + import {displayRoom} from "@app/groups" import {makeCommentFilter} from "@app/content" import {makeFeed} from "@app/feeds" import {pushModal} from "@app/modal" @@ -29,9 +29,9 @@ const createThread = () => pushModal(ThreadCreate, {url}) - const items = $derived.by(() => { + const threadFeed = $derived.by(() => { const scores = new Map() - const [goals, comments] = partition(spec({kind: THREAD}), $events) + const [threads, comments] = partition(spec({kind: THREAD}), $events) for (const comment of comments) { const id = getTagValue("E", comment.tags) @@ -41,7 +41,13 @@ } } - return sortBy(e => -max([...(scores.get(e.id) || []), e.created_at]), goals) + const items = sortBy(e => -max([...(scores.get(e.id) || []), e.created_at]), threads) + + const byRoom = groupBy(e => getTagValue("h", e.tags) || "", items) + const roomName = (h: string) => (h ? displayRoom(url, h) : "general").toLowerCase() + const boards = sortBy(([h]) => roomName(h), Array.from(byRoom.entries())) + + return {items, boards} }) onMount(() => { @@ -77,17 +83,15 @@ {/snippet} - - {#each items as event (event.id)} -
- -
+ + {#each threadFeed.boards as [h, threads] (h || "general")} + {/each}

{#if loading} Looking for threads... - {:else if items.length === 0} + {:else if threadFeed.items.length === 0} No threads found. {:else} That's all! diff --git a/src/routes/spaces/[relay]/threads/[id]/+page.svelte b/src/routes/spaces/[relay]/threads/[id]/+page.svelte index 5196ed9b..dc9d60dd 100644 --- a/src/routes/spaces/[relay]/threads/[id]/+page.svelte +++ b/src/routes/spaces/[relay]/threads/[id]/+page.svelte @@ -1,26 +1,31 @@ - + {#snippet title()} -

{getTagValue("title", $event?.tags || []) || ""}

+
+

+ {getTagValue("title", $event?.tags || []) || ""} +

+

+ {replyCount} + {replyCount === 1 ? "reply" : "replies"} + {#if h} + · # + {/if} +

+
{/snippet} - + {#if $event} -
- -
- - -
-
- {#if !showAll && $replies.length > 4} -
- -
- {/if} - {#each $replies.slice(0, showAll ? undefined : 4) as reply (reply.id)} - -
- - -
-
+
+ {#each pagePosts as post (post.id)} + {/each}
- {#if showReply} - + {#if pageCount > 1} + + {/if} + {#if showReply && replyTo && $event} + {:else} -
-