flotilla/src/app/components/ChatMessage.svelte

111 lines
4.3 KiB
Svelte
Raw Normal View History

2024-10-08 18:39:16 +00:00
<script lang="ts">
import cx from "classnames"
import {formatTimestampAsTime} from "@welshman/lib"
2026-08-29 17:20:39 +00:00
import type {Maybe} from "@welshman/lib"
2025-05-12 22:10:24 +00:00
import type {TrustedEvent, EventContent} from "@welshman/util"
2026-07-28 16:16:26 +00:00
import {Thunks} from "@welshman/app"
2024-10-24 23:28:56 +00:00
import {isMobile} from "@lib/html"
2025-09-10 22:26:39 +00:00
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
2024-10-08 18:39:16 +00:00
import Icon from "@lib/components/Icon.svelte"
2024-12-16 20:54:17 +00:00
import Button from "@lib/components/Button.svelte"
2024-10-15 21:00:15 +00:00
import Tippy from "@lib/components/Tippy.svelte"
2025-03-03 21:59:38 +00:00
import TapTarget from "@lib/components/TapTarget.svelte"
2026-08-29 17:20:39 +00:00
import type {TippyController} from "@lib/components/Tippy.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
2026-07-28 16:16:26 +00:00
import {publishWrappedReaction, retractWrappedReaction} from "@app/reactions"
2024-10-08 18:39:16 +00:00
import Content from "@app/components/Content.svelte"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import type {FeedContext} from "@app/feeds"
import ThunkFailure from "@app/components/ThunkFailure.svelte"
2024-12-16 20:54:17 +00:00
import ProfileDetail from "@app/components/ProfileDetail.svelte"
2024-10-15 21:00:15 +00:00
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
2024-10-24 23:28:56 +00:00
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
2026-07-28 16:16:26 +00:00
import {app, profiles, user} from "@app/core"
import {colorFor} from "@app/theme"
2026-06-08 21:14:40 +00:00
import {pushModal} from "@app/modal"
2024-10-08 18:39:16 +00:00
2025-02-04 00:37:14 +00:00
interface Props {
event: TrustedEvent
2025-03-04 18:20:06 +00:00
replyTo: (event: TrustedEvent) => void
2026-03-10 22:13:33 +00:00
canEdit?: (event: TrustedEvent) => boolean
onEdit?: (event: TrustedEvent) => void
2025-02-04 00:37:14 +00:00
pubkeys: string[]
showPubkey?: boolean
context: FeedContext
2025-02-04 00:37:14 +00:00
}
const {event, replyTo, canEdit, onEdit, pubkeys, showPubkey = false, context}: Props = $props()
2024-10-08 18:39:16 +00:00
2026-07-28 16:16:26 +00:00
const isOwn = event.pubkey === $user.pubkey
const profileDisplay = $profiles.display(event.pubkey).$
const thunks = $app.use(Thunks).history
const thunk = $app.use(Thunks).merge($thunks.filter(t => t.event.id === event.id))
const colorValue = colorFor(event.pubkey)
2024-10-08 18:39:16 +00:00
2025-03-04 18:20:06 +00:00
const reply = () => replyTo(event)
2026-03-10 22:13:33 +00:00
const edit = canEdit?.(event) ? () => onEdit?.(event) : undefined
2025-03-04 18:20:06 +00:00
2026-07-28 16:16:26 +00:00
const deleteReaction = (reaction: TrustedEvent) => retractWrappedReaction(reaction, pubkeys)
2024-10-08 18:39:16 +00:00
2026-07-28 16:16:26 +00:00
const createReaction = (values: EventContent) => publishWrappedReaction(event, values, pubkeys)
2024-10-08 18:39:16 +00:00
2024-12-16 20:54:17 +00:00
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
2026-03-10 22:13:33 +00:00
const showMobileMenu = () => pushModal(ChatMessageMenuMobile, {event, pubkeys, reply, edit})
2024-10-24 23:28:56 +00:00
2026-08-29 17:20:39 +00:00
const togglePopover = () => tippy?.toggle()
2024-10-15 21:00:15 +00:00
2026-08-29 17:20:39 +00:00
let tippy: Maybe<TippyController> = $state()
2024-10-08 18:39:16 +00:00
</script>
2026-07-28 16:16:26 +00:00
<ThunkFailure showToastOnRetry {thunk} class="mt-1" />
2024-10-17 18:27:17 +00:00
<div
data-event={event.id}
class={cx("group flex items-center justify-end gap-1 px-2", {"flex-row-reverse": !isOwn})}>
2025-03-04 18:20:06 +00:00
{#if !isMobile}
<Tippy
2026-08-29 17:20:39 +00:00
bind:controller={tippy}
2025-03-04 18:20:06 +00:00
component={ChatMessageMenu}
2026-08-29 17:20:39 +00:00
props={{event, pubkeys, tippy, replyTo, edit}}
params={{interactive: true, trigger: "manual"}}>
2025-03-04 18:20:06 +00:00
<button
type="button"
class="opacity-0 transition-all"
class:group-hover:opacity-100={!isMobile}
onclick={togglePopover}>
2025-09-10 22:26:39 +00:00
<Icon icon={MenuDots} size={4} />
2025-03-04 18:20:06 +00:00
</button>
</Tippy>
{/if}
<div class="flex min-w-0 flex-col" class:items-end={isOwn}>
<TapTarget class={cx("chat-bubble", {"chat-bubble--user": isOwn})} onTap={showMobileMenu}>
{#if showPubkey}
2024-10-30 20:52:35 +00:00
<div class="flex items-center gap-2">
{#if !isOwn}
<Button onclick={openProfile} class="flex items-center gap-1">
<ProfileCircle
pubkey={event.pubkey}
class="border border-solid"
style="border-color: var(--line)"
size={4} />
<div class="flex items-center gap-2">
<Button onclick={openProfile} class="text-sm font-bold" style="color: {colorValue}">
2025-04-15 22:45:48 +00:00
{$profileDisplay}
2024-12-16 20:54:17 +00:00
</Button>
</div>
2024-12-16 20:54:17 +00:00
</Button>
{/if}
2025-01-17 23:01:05 +00:00
<span class="whitespace-nowrap text-xs opacity-50"
>{formatTimestampAsTime(event.created_at)}</span>
2024-10-08 20:20:55 +00:00
</div>
2024-10-30 20:52:35 +00:00
{/if}
<div class="text-sm">
<Content showEntire {event} />
2024-10-08 20:20:55 +00:00
</div>
2025-03-03 21:59:38 +00:00
</TapTarget>
<div class="flex gap-2 z-feature -mt-4 ml-4">
<ReactionSummary {event} {context} {deleteReaction} {createReaction} noTooltip />
</div>
2024-10-08 20:20:55 +00:00
</div>
2024-10-17 18:27:17 +00:00
</div>