Send emoji reactions through the shared reaction helpers and toggle room pins through toggleRoomPin
This commit is contained in:
parent
8d0c73b364
commit
9c3d77a2f2
9 changed files with 40 additions and 95 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()` |
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
</script>
|
||||
|
||||
<EmojiButton
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
import Button from "@lib/components/Button.svelte"
|
||||
import EmojiPicker from "@lib/components/EmojiPicker.svelte"
|
||||
import EventInfo from "@app/components/EventInfo.svelte"
|
||||
import {reactions, wraps} from "@app/core"
|
||||
import {publishWrappedReaction} from "@app/reactions"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {clip} from "@app/toast"
|
||||
|
||||
|
|
@ -29,12 +29,9 @@
|
|||
|
||||
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 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})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<script lang="ts">
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {tagSpec, tagValue} from "@welshman/util"
|
||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import {reactions, relays} from "@app/core"
|
||||
import {publishReaction} from "@app/reactions"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
|
|
@ -13,22 +12,7 @@
|
|||
|
||||
const {url, event}: Props = $props()
|
||||
|
||||
const shouldProtect = $relays.hasNip(url, 70)
|
||||
|
||||
const onEmoji = async (emoji: string) => {
|
||||
const protect = await shouldProtect
|
||||
const room = tagValue(tagSpec("h"), event.tags)
|
||||
|
||||
const command = await $reactions.react(event, emoji, writer => {
|
||||
writer.setProtected(protect)
|
||||
|
||||
if (room) {
|
||||
writer.setRoom(url, room)
|
||||
}
|
||||
})
|
||||
|
||||
return command.publishToRelays([url])
|
||||
}
|
||||
const onEmoji = (emoji: string) => publishReaction(event, {content: emoji, tags: []}, {url})
|
||||
</script>
|
||||
|
||||
<EmojiButton
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
import {deriveUserAdminDelete, deriveUserIsRoomAdmin} from "@app/rooms"
|
||||
import {shareEvent} from "@app/share"
|
||||
import {readAloud} from "@app/speech"
|
||||
import {toggleRoomPin} from "@app/roomPins"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
|
|
@ -83,24 +83,9 @@
|
|||
pushModal(EventAdminDeleteConfirm, {url, noun: "Message", event})
|
||||
}
|
||||
|
||||
const togglePin = async () => {
|
||||
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)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal>
|
||||
|
|
@ -64,7 +46,7 @@
|
|||
</ModalHeader>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each $pinnedEvents as event (event.id)}
|
||||
{@const onUnpin = () => unpin(event)}
|
||||
{@const onUnpin = () => toggleRoomPin(url, h, event.id)}
|
||||
{@const onJump = () => jumpToMessage(event)}
|
||||
<div class="card card-sm flex flex-col gap-3 p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
|
|
|
|||
|
|
@ -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<TrustedEvent[]> =>
|
||||
readable<TrustedEvent[]>([], set => {
|
||||
|
|
@ -50,3 +51,19 @@ export const deriveRoomPinnedEvents = (url: string, h: string): Readable<Trusted
|
|||
unsubscribe()
|
||||
}
|
||||
})
|
||||
|
||||
export const toggleRoomPin = async (url: string, h: string, id: string) => {
|
||||
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"})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue