Give a comment the same send delay a chat message gets (#562)

This commit is contained in:
Coracle-Bot 2026-09-17 17:46:55 +00:00 committed by hodlbod
parent caab696d36
commit f30ef6d344
7 changed files with 36 additions and 17 deletions

View file

@ -360,9 +360,10 @@ Plugin mutators already return a `Command`: `roomLists.get().addRelay(url).then(
`forceLoad` before writing. A replaceable event you build yourself needs the same, as in `forceLoad` before writing. A replaceable event you build yourself needs the same, as in
`publishSettings`. `publishSettings`.
Some call sites call `thunks.get().publish({event, relays, delay})` directly. Room chat Some call sites call `thunks.get().publish({event, relays, delay})` directly. Anything that
(`RoomChat.svelte`) does, because `Command` cannot carry the `send_delay` window, and so do honours the `send_delay` window does, because `Command` cannot carry it: room chat
`publishRoomQuote` in `rooms.ts`, the push adapters and `ProfileDelete.svelte`. DMs go through (`RoomChat.svelte`), the comment composers (`CommentCompose.svelte` and `EventReply.svelte`) and
`publishRoomQuote` in `rooms.ts`. So do the push adapters and `ProfileDelete.svelte`. DMs go through
`wraps.get().publish({event, recipients})`, which returns a merged thunk (see `reactions.ts`). `wraps.get().publish({event, recipients})`, which returns a merged thunk (see `reactions.ts`).
NIP-86 calls (`relayManagement.get().forUrl(url)`) are not thunks. They return NIP-86 calls (`relayManagement.get().forUrl(url)`) are not thunks. They return
`{result, error}`, and the caller handles `error`. `{result, error}`, and the caller handles `error`.
@ -373,7 +374,8 @@ NIP-86 calls (`relayManagement.get().forUrl(url)`) are not thunks. They return
relays when it is enqueued, so every derived store sees it immediately. Signing then swaps the relays when it is enqueued, so every derived store sees it immediately. Signing then swaps the
unsigned event for the signed one. unsigned event for the signed one.
- **Undo.** `thunk.abort()` during the `delay` removes the event from the repository and from - **Undo.** `thunk.abort()` during the `delay` removes the event from the repository and from
`history`. When `send_delay` is set, room chat shows a `ThunkToast` whose Cancel button aborts. `history`. When `send_delay` is set, room chat shows a `ThunkToast` whose Cancel button aborts,
and a comment carries the same Cancel in the `ThunkPending` row under it.
- **Editing.** Editing a message deletes it and republishes with the same `created_at` (see - **Editing.** Editing a message deletes it and republishes with the same `created_at` (see
`RoomChat.svelte`). `RoomChat.svelte`).
- **Status in rows.** Rows look up `$thunksByEventId.get(event.id) ?? noThunks` and pass - **Status in rows.** Rows look up `$thunksByEventId.get(event.id) ?? noThunks` and pass

View file

@ -1129,8 +1129,8 @@ Acceptance:
confirms. confirms.
- On confirmation the status disappears silently and the normal reaction and - On confirmation the status disappears silently and the normal reaction and
menu actions take its place. menu actions take its place.
- Cancelling removes the post entirely from the list or thread it was posted - Cancelling during the send delay removes the post entirely from the list or
into, and bob never sees it. thread it was posted into.
- A failed post shows "Failed to send!" in the same row, opening the same - A failed post shows "Failed to send!" in the same row, opening the same
per-relay detail popover. per-relay detail popover.

View file

@ -87,7 +87,11 @@ const setSendDelay = async (page: Page, seconds: number) => {
await page.goto("/settings/content") await page.goto("/settings/content")
await slider.fill(String(seconds * 1000)) await slider.fill(String(seconds * 1000))
await expect(page.getByText(`Delay sending chat messages for ${seconds} seconds.`)).toBeVisible() const unit = seconds === 1 ? "second" : "seconds"
await expect(
page.getByText(`Delay sending messages and comments for ${seconds} ${unit}.`),
).toBeVisible()
await page.getByRole("button", {name: "Save Changes"}).click() await page.getByRole("button", {name: "Save Changes"}).click()
@ -337,6 +341,11 @@ test("US-071 content posts show delivery status in place", async ({seed, as}) =>
env: {VITE_BLOCKED_RELAYS: quiet}, env: {VITE_BLOCKED_RELAYS: quiet},
}) })
// A comment leaves after the send delay the way a chat message does, so the window in which its
// Cancel link is live is hers to set.
await setSendDelay(alice, 1)
await alice.goto(`${spacePath(url)}/articles`)
await writeArticle(alice, "Signals in the Noise", "Everything worth hearing is quiet.") await writeArticle(alice, "Signals in the Noise", "Everything worth hearing is quiet.")
// Publishing lands on the article's own page, where its status sits under the article itself. // Publishing lands on the article's own page, where its status sits under the article itself.

View file

@ -177,11 +177,11 @@ test("US-088 adjust send delay and media servers", async ({seed, as}) => {
const sendDelay = page.locator('input[type="range"]') const sendDelay = page.locator('input[type="range"]')
const servers = page.getByRole("listitem") const servers = page.getByRole("listitem")
await expect(page.getByText("Delay sending chat messages for 0 seconds.")).toBeVisible() await expect(page.getByText("Delay sending messages and comments for 0 seconds.")).toBeVisible()
await sendDelay.fill("3000") await sendDelay.fill("3000")
await expect(page.getByText("Delay sending chat messages for 3 seconds.")).toBeVisible() await expect(page.getByText("Delay sending messages and comments for 3 seconds.")).toBeVisible()
await expect(servers).toHaveCount(0) await expect(servers).toHaveCount(0)
@ -208,7 +208,7 @@ test("US-088 adjust send delay and media servers", async ({seed, as}) => {
await waitForToastToClear(page) await waitForToastToClear(page)
await page.reload() await page.reload()
await expect(page.getByText("Delay sending chat messages for 3 seconds.")).toBeVisible() await expect(page.getByText("Delay sending messages and comments for 3 seconds.")).toBeVisible()
await expect(page.locator('input[type="range"]')).toHaveValue("3000") await expect(page.locator('input[type="range"]')).toHaveValue("3000")
}) })

View file

@ -2,7 +2,6 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {tagSpec, tagValue} from "@welshman/util" import {tagSpec, tagValue} from "@welshman/util"
import {publishToRelays} from "@welshman/app"
import {Comment} from "@welshman/domain" import {Comment} from "@welshman/domain"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl" import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
@ -10,9 +9,10 @@
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import EditorContent from "@app/editor/EditorContent.svelte" import EditorContent from "@app/editor/EditorContent.svelte"
import {command, relays, writer} from "@app/core" import {relays, thunks, writer} from "@app/core"
import {DraftKey} from "@app/drafts" import {DraftKey} from "@app/drafts"
import {makeEditor} from "@app/editor" import {makeEditor} from "@app/editor"
import {getSetting} from "@app/settings"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
type Values = { type Values = {
@ -63,7 +63,11 @@
eventWriter.setRoom(url, h) eventWriter.setRoom(url, h)
} }
const thunk = await command(eventWriter).then(publishToRelays([url])) const thunk = $thunks.publish({
relays: [url],
event: await eventWriter.renderTemplate(),
delay: getSetting("send_delay"),
})
const error = await thunk.waitForError() const error = await thunk.waitForError()
if (error) { if (error) {

View file

@ -3,7 +3,6 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {tagSpec, tagValue} from "@welshman/util" import {tagSpec, tagValue} from "@welshman/util"
import {publishToRelays} from "@welshman/app"
import {Comment} from "@welshman/domain" import {Comment} from "@welshman/domain"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
@ -17,7 +16,8 @@
import {makeEditor} from "@app/editor" import {makeEditor} from "@app/editor"
import {DraftKey} from "@app/drafts" import {DraftKey} from "@app/drafts"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {command, relays, writer} from "@app/core" import {relays, thunks, writer} from "@app/core"
import {getSetting} from "@app/settings"
type Values = { type Values = {
content?: string | object content?: string | object
@ -72,7 +72,11 @@
eventWriter.setRoom(url, h) eventWriter.setRoom(url, h)
} }
const thunk = await command(eventWriter).then(publishToRelays([url])) const thunk = $thunks.publish({
relays: [url],
event: await eventWriter.renderTemplate(),
delay: getSetting("send_delay"),
})
draftKey.clear() draftKey.clear()
onSubmit(thunk) onSubmit(thunk)

View file

@ -135,7 +135,7 @@
{/snippet} {/snippet}
{#snippet info()} {#snippet info()}
<p> <p>
Delay sending chat messages for {$settings.send_delay / 1000} Delay sending messages and comments for {$settings.send_delay / 1000}
{$settings.send_delay === 1000 ? "second" : "seconds"}. {$settings.send_delay === 1000 ? "second" : "seconds"}.
</p> </p>
{/snippet} {/snippet}