diff --git a/e2e/USER_STORIES.md b/e2e/USER_STORIES.md index 2c9078db..2ef3d86e 100644 --- a/e2e/USER_STORIES.md +++ b/e2e/USER_STORIES.md @@ -682,8 +682,10 @@ Acceptance: - Creating a thread from a room's compose menu files it under that room's board and posts a quote of it into the room's chat. -- Creating a thread from the top-level Threads page files it under a "General" - board, or under whichever board is picked from its board selector. +- Each board on the Threads page has its own create button, and a thread started + from it is filed under that board. +- The "General" board is always present, so a thread belonging to no room can be + started from the Threads page. - Each board row shows the topic title, author, reply count, and last-post time. ### US-043 — Reply to a thread and to a specific post diff --git a/e2e/specs/articles-threads.spec.ts b/e2e/specs/articles-threads.spec.ts index 0ce99535..839508c6 100644 --- a/e2e/specs/articles-threads.spec.ts +++ b/e2e/specs/articles-threads.spec.ts @@ -676,7 +676,13 @@ test("US-042 start a thread and see it filed under its room", async ({seed, as}) await shortDate(page, at(4, HOUR) + 60), ) - await pageBar(page).getByRole("button", {name: "Create", exact: true}).click() + const general = page + .locator("section") + .filter({has: page.getByRole("heading", {name: "General", exact: true})}) + + // Each board creates its own threads, so the room comes from the button that was clicked rather + // than from a picker. + await general.getByRole("button", {name: "Create", exact: true}).click() const fromThreads = modal(page, "Create a Thread") @@ -686,18 +692,12 @@ test("US-042 start a thread and see it filed under its room", async ({seed, as}) await expect(page.getByRole("heading", {name: "Create a Thread"})).toHaveCount(0) - const general = page - .locator("section") - .filter({has: page.getByRole("heading", {name: "General", exact: true})}) - await expect(general.getByRole("row").filter({hasText: "Open floor"})).toBeVisible() - // The threads page has no room of its own, so the form offers one. - await pageBar(page).getByRole("button", {name: "Create", exact: true}).click() + await lounge.getByRole("button", {name: "Create", exact: true}).click() const toLounge = modal(page, "Create a Thread") - await toLounge.getByRole("combobox").selectOption({label: "Lounge"}) await toLounge.getByPlaceholder("What is this thread about?").fill("Carpet swatches") await editorOf(toLounge).pressSequentially("Beige is a choice.") await toLounge.getByRole("button", {name: "Create Thread"}).click() diff --git a/src/app/components/ThreadBoard.svelte b/src/app/components/ThreadBoard.svelte index 610e88aa..43b2e3a1 100644 --- a/src/app/components/ThreadBoard.svelte +++ b/src/app/components/ThreadBoard.svelte @@ -1,9 +1,14 @@
@@ -25,12 +32,20 @@ General {/if} - - {threads.length} - {threads.length === 1 ? "Topic" : "Topics"} - +
+ + {threads.length} + {threads.length === 1 ? "Topic" : "Topics"} + + +
- {#if $matchMd} + {#if threads.length === 0} +

No topics yet.

+ {:else if $matchMd}
import {writable} from "svelte/store" - import {sortBy} from "@welshman/lib" import type {TrustedEvent} from "@welshman/util" import {relay} from "@welshman/util" import {Thread} from "@welshman/domain" @@ -22,13 +21,7 @@ import {command, relays, writer} from "@app/core" import {DraftKey} from "@app/drafts" import {makeEditor} from "@app/editor" - import { - deriveOtherRooms, - deriveUserRooms, - displayRoom, - publishRoomQuote, - roomComparator, - } from "@app/rooms" + import {displayRoom, publishRoomQuote} from "@app/rooms" import {pushToast} from "@app/toast" type Values = { @@ -40,19 +33,16 @@ type Props = { url: string h?: string - selectRoom?: boolean shareToChat?: boolean quote?: TrustedEvent initialValues?: Values } - const {url, h, selectRoom = false, shareToChat = false, quote, initialValues}: Props = $props() + const {url, h, shareToChat = false, quote, initialValues}: Props = $props() const draftKey = new DraftKey(`thread:${url}:${h ?? ""}`) const draft = draftKey.get() const shouldProtect = $relays.hasNip(url, 70) - const userRooms = deriveUserRooms(url) - const otherRooms = deriveOtherRooms(url) - const roomOptions = $derived(sortBy(roomComparator(url), [...$userRooms, ...$otherRooms])) + const room = h ?? initialValues?.h ?? draft?.h ?? "" const uploading = writable(false) @@ -121,7 +111,6 @@ let title = $state(initialValues?.title ?? draft?.title ?? "") let content = $state(initialValues?.content ?? draft?.content ?? "") - let room = $state(h ?? initialValues?.h ?? draft?.h ?? "") const onChange = (json: object) => { content = json @@ -145,7 +134,9 @@ Create a Thread - Share a link, or start a discussion. + + Share a link, or start a discussion in {room ? displayRoom(url, room) : "General"}. +
@@ -164,21 +155,6 @@ {/snippet} - {#if selectRoom && roomOptions.length > 0} - - {#snippet label()} -

Board

- {/snippet} - {#snippet input()} - - {/snippet} -
- {/if} {#snippet label()}

Message*

diff --git a/src/routes/spaces/[relay]/threads/+page.svelte b/src/routes/spaces/[relay]/threads/+page.svelte index d5d54f49..ef8b1ef4 100644 --- a/src/routes/spaces/[relay]/threads/+page.svelte +++ b/src/routes/spaces/[relay]/threads/+page.svelte @@ -8,19 +8,15 @@ import type {TrustedEvent} from "@welshman/util" import {THREAD, tagValue, tagSpec} from "@welshman/util" import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl" - import Add from "@assets/icons/add.svg?dataurl" import Icon from "@lib/components/Icon.svelte" - import Button from "@lib/components/Button.svelte" import PageContent from "@lib/components/PageContent.svelte" import Spinner from "@lib/components/Spinner.svelte" import SpaceBar from "@app/components/SpaceBar.svelte" import ThreadBoard from "@app/components/ThreadBoard.svelte" - import ThreadCreate from "@app/components/ThreadCreate.svelte" import {decodeRelay} from "@app/relays" import {displayRoom} from "@app/rooms" import {makeCommentFilter} from "@app/content" import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds" - import {pushModal} from "@app/modal" const url = decodeRelay($page.params.relay!) const context = makeFeedContext({relays: [url]}) @@ -34,8 +30,6 @@ let element: HTMLElement | undefined = $state() let events: Readable = $state(readable([])) - const createThread = () => pushModal(ThreadCreate, {url, selectRoom: true}) - const threadFeed = $derived.by(() => { const scores = new Map() const [threads, comments] = partition(spec({kind: THREAD}), $events) @@ -50,7 +44,8 @@ const items = sortBy(e => -max([...(scores.get(e.id) || []), e.created_at]), threads) - const byRoom = groupBy(e => tagValue(tagSpec("h"), e.tags) || "", items) + const grouped = groupBy(e => tagValue(tagSpec("h"), e.tags) || "", items) + const byRoom = new Map([["", []], ...grouped]) const roomName = (h: string) => (h ? displayRoom(url, h) : "general").toLowerCase() const boards = sortBy(([h]) => roomName(h), Array.from(byRoom.entries())) @@ -84,12 +79,6 @@ {#snippet title()} Threads {/snippet} - {#snippet action()} - - {/snippet}