Let the threads page choose which board a new thread goes to

This commit is contained in:
Coracle-Bot 2026-09-09 15:22:44 +00:00
parent 5cf598d2ac
commit 7e82429a1a
4 changed files with 50 additions and 8 deletions

View file

@ -669,7 +669,7 @@ 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.
board, or under whichever board is picked from its board selector.
- 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

View file

@ -691,6 +691,20 @@ test("US-042 start a thread and see it filed under its room", async ({seed, as})
.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()
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()
await expect(page.getByRole("heading", {name: "Create a Thread"})).toHaveCount(0)
await expect(lounge.getByRole("row").filter({hasText: "Carpet swatches"})).toBeVisible()
})
test("US-043 reply to a thread and to a specific post", async ({seed, as}) => {

View file

@ -1,5 +1,6 @@
<script lang="ts">
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"
@ -21,26 +22,37 @@
import {command, relays, writer} from "@app/core"
import {DraftKey} from "@app/drafts"
import {makeEditor} from "@app/editor"
import {publishRoomQuote} from "@app/rooms"
import {
deriveOtherRooms,
deriveUserRooms,
displayRoom,
publishRoomQuote,
roomComparator,
} from "@app/rooms"
import {pushToast} from "@app/toast"
type Values = {
content?: string | object
title?: string
h?: string
}
type Props = {
url: string
h?: string
selectRoom?: boolean
shareToChat?: boolean
quote?: TrustedEvent
initialValues?: Values
}
const {url, h, shareToChat = false, quote, initialValues}: Props = $props()
const {url, h, selectRoom = false, shareToChat = false, quote, initialValues}: Props = $props()
const draftKey = new DraftKey<Values>(`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 uploading = writable(false)
@ -79,8 +91,8 @@
.addTags(...ed.storage.nostr.getEditorTags())
.forceRoutes(relay(url))
if (h) {
eventWriter.setRoom(url, h)
if (room) {
eventWriter.setRoom(url, room)
}
if (quote) {
@ -98,7 +110,7 @@
history.back()
if (shareToChat) {
publishRoomQuote({url, h, parent: thunk.event, protect})
publishRoomQuote({url, h: room, parent: thunk.event, protect})
}
} finally {
loading = false
@ -109,6 +121,7 @@
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
@ -124,7 +137,7 @@
})
$effect(() => {
draftKey.update({title, content})
draftKey.update({title, content, h: room})
})
</script>
@ -151,6 +164,21 @@
</label>
{/snippet}
</Field>
{#if selectRoom && roomOptions.length > 0}
<Field>
{#snippet label()}
<p>Board</p>
{/snippet}
{#snippet input()}
<select class="select input w-full" bind:value={room}>
<option value="">General</option>
{#each roomOptions as option (option)}
<option value={option}>{displayRoom(url, option)}</option>
{/each}
</select>
{/snippet}
</Field>
{/if}
<Field>
{#snippet label()}
<p>Message*</p>

View file

@ -34,7 +34,7 @@
let element: HTMLElement | undefined = $state()
let events: Readable<TrustedEvent[]> = $state(readable([]))
const createThread = () => pushModal(ThreadCreate, {url})
const createThread = () => pushModal(ThreadCreate, {url, selectRoom: true})
const threadFeed = $derived.by(() => {
const scores = new Map<string, number[]>()