Name the message share menu item and offer a link from the share dialog

This commit is contained in:
Coracle-Bot 2026-09-18 18:22:51 +00:00
parent ec835f7cfb
commit f2c9a02756
5 changed files with 42 additions and 8 deletions

View file

@ -439,7 +439,10 @@ that I can pass it along without retyping it.
Acceptance:
- "Share" on a message inside a space opens a picker of that space's rooms.
- "Share Message" on a message inside a space opens a picker of that space's
rooms.
- The picker also offers a link to the message, which copies to the clipboard
and opens the room scrolled to it.
- Choosing a destination navigates there with the composer pre-filled with a
quote of the shared message.
- Sending posts the quoted message in the destination, visible to bob there.

View file

@ -1,4 +1,4 @@
import {npubEncode} from "nostr-tools/nip19"
import {neventEncode, npubEncode} from "nostr-tools/nip19"
import {DAY, HOUR, MINUTE, WEEK, bech32ToHex} from "@welshman/lib"
import {getLnUrl} from "@welshman/util"
import {Profile, displayPubkey} from "@welshman/domain"
@ -871,6 +871,8 @@ test("US-027a a permalink near the newest end lands at the bottom", async ({seed
})
test("US-028 share a message somewhere else", async ({seed, as}) => {
let shared!: SeededEvent
const scenario = await seed(({relay, user, at}) => {
const space = relay("space")
@ -880,7 +882,7 @@ test("US-028 share a message somewhere else", async ({seed, as}) => {
space.join(user.bob, "general", "random")
space.profile(user.alice, {name: "Alice Anchor"})
space.profile(user.bob, {name: "Bob Barnacle"})
space.message(user.bob, "general", "the dock is closed on sunday", at(2, HOUR))
shared = space.message(user.bob, "general", "the dock is closed on sunday", at(2, HOUR))
seedChatter(space, user.alice)
seedChatter(space, user.bob)
@ -888,13 +890,15 @@ test("US-028 share a message somewhere else", async ({seed, as}) => {
const {url} = scenario.space("space")
const alice = await as(users.alice, roomPath(url, "general"))
const alice = await as(users.alice, roomPath(url, "general"), {
context: {permissions: ["clipboard-read", "clipboard-write"]},
})
const bob = await as(users.bob, roomPath(url, "random"))
await expect(message(alice, "the dock is closed on sunday")).toBeVisible()
await openMessageMenu(alice, "the dock is closed on sunday")
await alice.getByRole("button", {name: "Share"}).click()
await alice.getByRole("button", {name: "Share Message"}).click()
// ShareEvent is titled after the noun it was opened with, so its subtitle names it instead.
const picker = alice
@ -902,6 +906,16 @@ test("US-028 share a message somewhere else", async ({seed, as}) => {
.filter({hasText: "Which room would you like to share this event to?"})
.last()
// The same dialog hands out a link to the message, for anywhere flotilla cannot reach.
await picker.getByRole("button", {name: "Copy link"}).click()
await expect(alice.getByRole("alert")).toContainText("Copied to clipboard!")
const permalink = new URL(await alice.evaluate(() => navigator.clipboard.readText()))
expect(permalink.pathname).toBe(roomPath(url, "general"))
expect(permalink.search).toBe(`?at=${shared.event.created_at}`)
expect(permalink.hash).toBe(`#${neventEncode({id: shared.id, relays: [url]})}`)
await picker.getByRole("button", {name: "Random"}).click()
await picker.getByRole("button", {name: /^Share/}).click()

View file

@ -127,7 +127,7 @@
<li>
<Button onclick={share}>
<Icon size={4} icon={ShareCircle} />
Share
Share Message
</Button>
</li>
<li>

View file

@ -157,7 +157,7 @@
{/if}
<Button class="button button-neutral w-full" onclick={share}>
<Icon size={4} icon={ShareCircle} />
Share
Share Message
</Button>
<Button class="button button-neutral w-full" onclick={read}>
<Icon size={4} icon={VolumeLoud} />

View file

@ -3,6 +3,8 @@
import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
@ -12,8 +14,9 @@
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
import {makeRoomPath} from "@app/routes"
import {makeEventPermalink, makeRoomPath} from "@app/routes"
import {shareTo} from "@app/share"
import {clip} from "@app/toast"
import {rooms} from "@app/core"
type Props = {
@ -36,6 +39,10 @@
selection = h === selection ? "" : h
}
const permalink = makeEventPermalink(event, url)
const copyPermalink = () => clip(permalink)
let selection = $state("")
</script>
@ -57,6 +64,16 @@
</Button>
{/each}
</div>
<div class="flex flex-col gap-2">
<p class="text-xs uppercase tracking-wide opacity-60">Or copy a link</p>
<label class="input flex min-w-0 items-center gap-2">
<Icon icon={LinkRound} class="shrink-0" />
<input value={permalink} class="min-w-0 flex-1 truncate" type="text" readonly />
<Button class="shrink-0" aria-label="Copy link" onclick={copyPermalink}>
<Icon icon={Copy} />
</Button>
</label>
</div>
</ModalBody>
<ModalFooter>
<Button class="button button-link" onclick={back}>