From 9c3d77a2f216d8f8ae0d915b7732c8c175208903 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Fri, 25 Sep 2026 21:37:45 -0700 Subject: [PATCH] Send emoji reactions through the shared reaction helpers and toggle room pins through toggleRoomPin --- .agents/skills/flotilla-model/SKILL.md | 3 +- .agents/skills/flotilla-model/kinds.md | 2 +- .../components/ChatMessageEmojiButton.svelte | 9 ++---- .../components/ChatMessageMenuMobile.svelte | 9 ++---- src/app/components/RoomItemEmojiButton.svelte | 20 ++---------- src/app/components/RoomItemMenu.svelte | 21 ++---------- src/app/components/RoomItemMenuMobile.svelte | 32 ++++--------------- .../components/RoomPinnedMessagesAll.svelte | 22 ++----------- src/app/roomPins.ts | 17 ++++++++++ 9 files changed, 40 insertions(+), 95 deletions(-) diff --git a/.agents/skills/flotilla-model/SKILL.md b/.agents/skills/flotilla-model/SKILL.md index 886a53a8..25b91b8c 100644 --- a/.agents/skills/flotilla-model/SKILL.md +++ b/.agents/skills/flotilla-model/SKILL.md @@ -119,7 +119,8 @@ user holds a method for — reports under `banevent`, join requests under `allow - **Invite** (`publishRoomInvite`): a 9009 with a random `code` tag. The link carries `h` and `code`, and `joinRoom(url, h, code)` sends the code as the join's `claim`. - **Pins**: `roomPinLists.setPins(url, h, pins)` sends 9010 and the relay republishes 39005. - `deriveRoomPinnedEvents` (`src/app/roomPins.ts`) loads the pinned events from the room's relay. + `toggleRoomPin(url, h, id)` (`src/app/roomPins.ts`) wraps it for the message menus and toasts + the result. `deriveRoomPinnedEvents` loads the pinned events from the room's relay. The relay enforces the `RoomMetaReader` flags (`isClosed`, `isHidden`, `isPrivate`, `isRestricted`); the UI only reflects them. Until membership is `Granted`, `RoomChat` hides a diff --git a/.agents/skills/flotilla-model/kinds.md b/.agents/skills/flotilla-model/kinds.md index f7ee13b4..d8589748 100644 --- a/.agents/skills/flotilla-model/kinds.md +++ b/.agents/skills/flotilla-model/kinds.md @@ -17,7 +17,7 @@ and NIP-43 tables in [SKILL.md](SKILL.md). Routes are under `src/routes/`. | Goals | `ZAP_GOAL` (9041) | `ZapGoal` | none | `spaces/[relay]/goals`, `goals/[id]` (`GoalCreate`) | | Polls | `POLL` (1068), `POLL_RESPONSE` (1018) | `Poll`, `PollResponse` | none | `spaces/[relay]/polls`, `polls/[id]` (`PollCreate`, `PollVotes`) | | Library | `PINBOARD` (30067), `PIN` (39067) | `Pinboard`, `Pin` | `src/app/pinboards.ts` | `spaces/[relay]/library` (`PinboardEdit`, `PinAdd`); published by any member | -| Room pins | `ROOM_PINS` (39005), `ROOM_UPDATE_PINS` (9010) | `RoomPins`, `RoomUpdatePins` | `src/app/roomPins.ts` | `RoomItemMenu`, `RoomPinnedMessagesAll` | +| Room pins | `ROOM_PINS` (39005), `ROOM_UPDATE_PINS` (9010) | `RoomPins`, `RoomUpdatePins` | `src/app/roomPins.ts` | `RoomItemMenu`, `RoomItemMenuMobile`, `RoomPinnedMessagesAll` | | Featured content | `APP_DATA` (30078), `d` = `flotilla/featured-content` | `AppData` | `src/app/featured.ts` | `SpaceFeaturedContent`; published as the relay | | Bot commands (NIP-CD) | `COMMAND` (31992) | `Command` | `src/app/commands.ts` | `RoomCompose`, `ContentCommand` | | Voice room participants | `LIVEKIT_PARTICIPANTS` (39004, defined in `src/app/call.ts`) | none | `src/app/call.ts` | rooms where `meta.hasLivekit()` | diff --git a/src/app/components/ChatMessageEmojiButton.svelte b/src/app/components/ChatMessageEmojiButton.svelte index 8e6fc555..05ed9584 100644 --- a/src/app/components/ChatMessageEmojiButton.svelte +++ b/src/app/components/ChatMessageEmojiButton.svelte @@ -3,7 +3,7 @@ import SmileCircle from "@assets/icons/smile-circle.svg?dataurl" import Icon from "@lib/components/Icon.svelte" import EmojiButton from "@lib/components/EmojiButton.svelte" - import {reactions, wraps} from "@app/core" + import {publishWrappedReaction} from "@app/reactions" type Props = { event: TrustedEvent @@ -12,11 +12,8 @@ const {event, pubkeys}: Props = $props() - const onEmoji = async (emoji: string) => { - const reaction = await $reactions.react(event, emoji) - - return $wraps.publish({event: reaction.event, recipients: pubkeys, pow: 16}) - } + const onEmoji = (emoji: string) => + publishWrappedReaction(event, {content: emoji, tags: []}, pubkeys) { + const onEmoji = (emoji: string) => { history.back() - - const reaction = await $reactions.react(event, emoji) - - return $wraps.publish({event: reaction.event, recipients: pubkeys, pow: 16}) + publishWrappedReaction(event, {content: emoji, tags: []}, pubkeys) } const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true}) diff --git a/src/app/components/RoomItemEmojiButton.svelte b/src/app/components/RoomItemEmojiButton.svelte index 7a47a793..0e501ee5 100644 --- a/src/app/components/RoomItemEmojiButton.svelte +++ b/src/app/components/RoomItemEmojiButton.svelte @@ -1,10 +1,9 @@ { + const togglePin = () => { onClick() - - if (!h) { - return - } - - // The optimistic write flips isPinned while the publish is in flight, so read it before it goes out. - const wasPinned = isPinned - const pins = wasPinned ? $pinIds.filter(pin => pin !== event.id) : [...$pinIds, event.id] - const command = await $roomPinLists.setPins(url, h, pins) - const error = await command.publishToRelays([url]).waitForError() - - if (error) { - pushToast({theme: "error", message: error}) - } else { - pushToast({message: wasPinned ? "Message unpinned" : "Message pinned"}) - } + toggleRoomPin(url, h, event.id) } diff --git a/src/app/components/RoomItemMenuMobile.svelte b/src/app/components/RoomItemMenuMobile.svelte index 47c3de53..9cd6d747 100644 --- a/src/app/components/RoomItemMenuMobile.svelte +++ b/src/app/components/RoomItemMenuMobile.svelte @@ -31,14 +31,15 @@ import EventAdminDeleteConfirm from "@app/components/EventAdminDeleteConfirm.svelte" import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte" import ThreadCreate from "@app/components/ThreadCreate.svelte" - import {reactions, relays, roomPinLists, user} from "@app/core" + import {roomPinLists, user} from "@app/core" import {deriveUserAdminDelete, deriveUserIsRoomAdmin} from "@app/rooms" + import {toggleRoomPin} from "@app/roomPins" + import {publishReaction} from "@app/reactions" import {ENABLE_ZAPS} from "@app/env" import {makeContentPath} from "@app/routes" import {shareEvent} from "@app/share" import {readAloud} from "@app/speech" import {pushModal} from "@app/modal" - import {pushToast} from "@app/toast" type Props = { url: string @@ -57,13 +58,9 @@ const isPinned = $derived($pinIds.includes(event.id)) const tile = "button h-auto flex-col gap-1.5 py-4 text-xs" - const onEmoji = async (emoji: string) => { + const onEmoji = (emoji: string) => { history.back() - - const protect = await $relays.hasNip(url, 70) - const command = await $reactions.react(event, emoji, w => w.setProtected(protect)) - - command.publishToRelays([url]) + publishReaction(event, {content: emoji, tags: []}, {url}) } const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true}) @@ -113,24 +110,9 @@ showMore = !showMore } - const togglePin = async () => { - if (!h) { - return - } - + const togglePin = () => { history.back() - - // The optimistic write flips isPinned while the publish is in flight, so read it before it goes out. - const wasPinned = isPinned - const pins = wasPinned ? $pinIds.filter(pin => pin !== event.id) : [...$pinIds, event.id] - const command = await $roomPinLists.setPins(url, h, pins) - const error = await command.publishToRelays([url]).waitForError() - - if (error) { - pushToast({theme: "error", message: error}) - } else { - pushToast({message: wasPinned ? "Message unpinned" : "Message pinned"}) - } + toggleRoomPin(url, h, event.id) } let showMore = $state(false) diff --git a/src/app/components/RoomPinnedMessagesAll.svelte b/src/app/components/RoomPinnedMessagesAll.svelte index 7f0cf641..8a4acd98 100644 --- a/src/app/components/RoomPinnedMessagesAll.svelte +++ b/src/app/components/RoomPinnedMessagesAll.svelte @@ -10,15 +10,13 @@ import ModalTitle from "@lib/components/ModalTitle.svelte" import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte" - import {roomPinLists} from "@app/core" import ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileName from "@app/components/ProfileName.svelte" import RoomItemContent from "@app/components/RoomItemContent.svelte" import RoomName from "@app/components/RoomName.svelte" import {deriveUserIsRoomAdmin} from "@app/rooms" - import {deriveRoomPinnedEvents} from "@app/roomPins" + import {deriveRoomPinnedEvents, toggleRoomPin} from "@app/roomPins" import {goToEvent} from "@app/routes" - import {pushToast} from "@app/toast" type Props = { url: string @@ -27,7 +25,6 @@ const {url, h}: Props = $props() - const pinIds = $roomPinLists.pins(url, h).$ const pinnedEvents = deriveRoomPinnedEvents(url, h) const userIsAdmin = deriveUserIsRoomAdmin(url, h) @@ -37,21 +34,6 @@ back() goToEvent(event) } - - const unpin = async (event: TrustedEvent) => { - const command = await $roomPinLists.setPins( - url, - h, - $pinIds.filter(pin => pin !== event.id), - ) - const error = await command.publishToRelays([url]).waitForError() - - if (error) { - pushToast({theme: "error", message: error}) - } else { - pushToast({message: "Message unpinned"}) - } - } @@ -64,7 +46,7 @@
{#each $pinnedEvents as event (event.id)} - {@const onUnpin = () => unpin(event)} + {@const onUnpin = () => toggleRoomPin(url, h, event.id)} {@const onJump = () => jumpToMessage(event)}
diff --git a/src/app/roomPins.ts b/src/app/roomPins.ts index 57ea5617..6223672a 100644 --- a/src/app/roomPins.ts +++ b/src/app/roomPins.ts @@ -3,6 +3,7 @@ import type {Readable} from "svelte/store" import {getIdFilters, getIdOrAddress} from "@welshman/util" import type {TrustedEvent} from "@welshman/util" import {events, network, roomPinLists} from "@app/core" +import {pushToast} from "@app/toast" export const deriveRoomPinnedEvents = (url: string, h: string): Readable => readable([], set => { @@ -50,3 +51,19 @@ export const deriveRoomPinnedEvents = (url: string, h: string): Readable { + const pins = roomPinLists.get().pins(url, h).get() + // The optimistic write flips the pin while the publish is in flight, so read it before it goes out. + const wasPinned = pins.includes(id) + const command = await roomPinLists + .get() + .setPins(url, h, wasPinned ? pins.filter(pin => pin !== id) : [...pins, id]) + const error = await command.publishToRelays([url]).waitForError() + + if (error) { + pushToast({theme: "error", message: error}) + } else { + pushToast({message: wasPinned ? "Message unpinned" : "Message pinned"}) + } +}