mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-26 17:52:18 +01:00
Show host emojis when writing stream chat message
This commit is contained in:
parent
c10a17eee7
commit
27abb20fd7
5
.changeset/nervous-ladybugs-drop.md
Normal file
5
.changeset/nervous-ladybugs-drop.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Show host emojis when writing stream chat message
|
@ -23,9 +23,9 @@ export function DefaultEmojiProvider({ children }: PropsWithChildren) {
|
||||
return <EmojiProvider emojis={defaultEmojis}>{children}</EmojiProvider>;
|
||||
}
|
||||
|
||||
export function UserEmojiProvider({ children }: PropsWithChildren) {
|
||||
export function UserEmojiProvider({ children, pubkey }: PropsWithChildren & { pubkey?: string }) {
|
||||
const account = useCurrentAccount();
|
||||
const userPacks = useUserEmojiPacks(account?.pubkey);
|
||||
const userPacks = useUserEmojiPacks(pubkey || account?.pubkey);
|
||||
const events = useReplaceableEvents(userPacks?.packs);
|
||||
|
||||
const emojis = events
|
||||
@ -34,8 +34,6 @@ export function UserEmojiProvider({ children }: PropsWithChildren) {
|
||||
)
|
||||
.flat();
|
||||
|
||||
console.log(userPacks, emojis);
|
||||
|
||||
return <EmojiProvider emojis={emojis}>{children}</EmojiProvider>;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ import RelaySelectionProvider from "../../../providers/relay-selection-provider"
|
||||
import StreamerCards from "../components/streamer-cards";
|
||||
import { useAppTitle } from "../../../hooks/use-app-title";
|
||||
import StreamSatsPerMinute from "../components/stream-sats-per-minute";
|
||||
import { UserEmojiProvider } from "../../../providers/emoji-provider";
|
||||
|
||||
function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode?: ChatDisplayMode }) {
|
||||
useAppTitle(stream.title);
|
||||
@ -194,7 +195,9 @@ export default function StreamView() {
|
||||
return (
|
||||
// add snort and damus relays so zap.stream will always see zaps
|
||||
<RelaySelectionProvider additionalDefaults={streamRelays}>
|
||||
<StreamPage stream={stream} displayMode={(params.get("displayMode") as ChatDisplayMode) ?? undefined} />
|
||||
<UserEmojiProvider pubkey={stream.host}>
|
||||
<StreamPage stream={stream} displayMode={(params.get("displayMode") as ChatDisplayMode) ?? undefined} />
|
||||
</UserEmojiProvider>
|
||||
</RelaySelectionProvider>
|
||||
);
|
||||
}
|
||||
|
95
src/views/streams/stream/stream-chat/chat-message-form.tsx
Normal file
95
src/views/streams/stream/stream-chat/chat-message-form.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { useMemo } from "react";
|
||||
import { Box, Button, IconButton, useDisclosure, useToast } from "@chakra-ui/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { ParsedStream, buildChatMessage } from "../../../../helpers/nostr/stream";
|
||||
import { useRelaySelectionRelays } from "../../../../providers/relay-selection-provider";
|
||||
import { useUserRelays } from "../../../../hooks/use-user-relays";
|
||||
import { RelayMode } from "../../../../classes/relay";
|
||||
import { unique } from "../../../../helpers/array";
|
||||
import { LightningIcon } from "../../../../components/icons";
|
||||
import useUserLNURLMetadata from "../../../../hooks/use-user-lnurl-metadata";
|
||||
import ZapModal from "../../../../components/zap-modal";
|
||||
import { useInvoiceModalContext } from "../../../../providers/invoice-modal";
|
||||
import { useSigningContext } from "../../../../providers/signing-provider";
|
||||
import NostrPublishAction from "../../../../classes/nostr-publish-action";
|
||||
import { createEmojiTags, ensureNotifyContentMentions } from "../../../../helpers/nostr/post";
|
||||
import { useContextEmojis } from "../../../../providers/emoji-provider";
|
||||
import MagicTextArea from "../../../../components/magic-textarea";
|
||||
|
||||
export default function ChatMessageForm({ stream }: { stream: ParsedStream }) {
|
||||
const toast = useToast();
|
||||
const emojis = useContextEmojis();
|
||||
const streamRelays = useRelaySelectionRelays();
|
||||
const hostReadRelays = useUserRelays(stream.host)
|
||||
.filter((r) => r.mode & RelayMode.READ)
|
||||
.map((r) => r.url);
|
||||
|
||||
const relays = useMemo(() => unique([...streamRelays, ...hostReadRelays]), [hostReadRelays, streamRelays]);
|
||||
|
||||
const { requestSignature } = useSigningContext();
|
||||
const { setValue, handleSubmit, formState, reset, getValues, watch } = useForm({
|
||||
defaultValues: { content: "" },
|
||||
});
|
||||
const sendMessage = handleSubmit(async (values) => {
|
||||
try {
|
||||
let draft = buildChatMessage(stream, values.content);
|
||||
draft = ensureNotifyContentMentions(draft);
|
||||
draft = createEmojiTags(draft, emojis);
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Send Chat", relays, signed);
|
||||
reset();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
}
|
||||
});
|
||||
|
||||
const { requestPay } = useInvoiceModalContext();
|
||||
const zapModal = useDisclosure();
|
||||
const zapMetadata = useUserLNURLMetadata(stream.host);
|
||||
|
||||
watch("content");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box as="form" borderRadius="md" flexShrink={0} display="flex" gap="2" px="2" pb="2" onSubmit={sendMessage}>
|
||||
<MagicTextArea
|
||||
placeholder="Message"
|
||||
autoComplete="off"
|
||||
isRequired
|
||||
value={getValues().content}
|
||||
onChange={(e) => setValue("content", e.target.value)}
|
||||
rows={1}
|
||||
/>
|
||||
<Button colorScheme="brand" type="submit" isLoading={formState.isSubmitting}>
|
||||
Send
|
||||
</Button>
|
||||
{zapMetadata.metadata?.allowsNostr && (
|
||||
<IconButton
|
||||
icon={<LightningIcon color="yellow.400" />}
|
||||
aria-label="Zap stream"
|
||||
borderColor="yellow.400"
|
||||
variant="outline"
|
||||
onClick={zapModal.onOpen}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{zapModal.isOpen && (
|
||||
<ZapModal
|
||||
isOpen
|
||||
stream={stream}
|
||||
pubkey={stream.host}
|
||||
onInvoice={async (invoice) => {
|
||||
reset();
|
||||
zapModal.onClose();
|
||||
await requestPay(invoice);
|
||||
}}
|
||||
onClose={zapModal.onClose}
|
||||
initialComment={getValues().content}
|
||||
additionalRelays={relays}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,47 +1,24 @@
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CardProps,
|
||||
Flex,
|
||||
Heading,
|
||||
IconButton,
|
||||
Input,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { Card, CardBody, CardHeader, CardProps, Flex, Heading } from "@chakra-ui/react";
|
||||
import { css } from "@emotion/react";
|
||||
import { Kind } from "nostr-tools";
|
||||
|
||||
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, buildChatMessage, getATag } from "../../../../helpers/nostr/stream";
|
||||
import { useUserRelays } from "../../../../hooks/use-user-relays";
|
||||
import { RelayMode } from "../../../../classes/relay";
|
||||
import ZapModal from "../../../../components/zap-modal";
|
||||
import { LightningIcon } from "../../../../components/icons";
|
||||
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, getATag } from "../../../../helpers/nostr/stream";
|
||||
import ChatMessage from "./chat-message";
|
||||
import ZapMessage from "./zap-message";
|
||||
import { LightboxProvider } from "../../../../components/lightbox-provider";
|
||||
import IntersectionObserverProvider from "../../../../providers/intersection-observer";
|
||||
import useUserLNURLMetadata from "../../../../hooks/use-user-lnurl-metadata";
|
||||
import { useInvoiceModalContext } from "../../../../providers/invoice-modal";
|
||||
import { unique } from "../../../../helpers/array";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useSigningContext } from "../../../../providers/signing-provider";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
|
||||
import { truncatedId } from "../../../../helpers/nostr/events";
|
||||
import { css } from "@emotion/react";
|
||||
import TopZappers from "./top-zappers";
|
||||
import { parseZapEvent } from "../../../../helpers/zaps";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { useRelaySelectionRelays } from "../../../../providers/relay-selection-provider";
|
||||
import useUserMuteList from "../../../../hooks/use-user-mute-list";
|
||||
import { NostrEvent, isPTag } from "../../../../types/nostr-event";
|
||||
import { useCurrentAccount } from "../../../../hooks/use-current-account";
|
||||
import NostrPublishAction from "../../../../classes/nostr-publish-action";
|
||||
import { ensureNotifyContentMentions } from "../../../../helpers/nostr/post";
|
||||
import ChatMessageForm from "./chat-message-form";
|
||||
|
||||
const hideScrollbar = css`
|
||||
scrollbar-width: 0;
|
||||
@ -59,14 +36,8 @@ export default function StreamChat({
|
||||
displayMode,
|
||||
...props
|
||||
}: CardProps & { stream: ParsedStream; actions?: React.ReactNode; displayMode?: ChatDisplayMode }) {
|
||||
const toast = useToast();
|
||||
const account = useCurrentAccount();
|
||||
const streamRelays = useRelaySelectionRelays();
|
||||
const hostReadRelays = useUserRelays(stream.host)
|
||||
.filter((r) => r.mode & RelayMode.READ)
|
||||
.map((r) => r.url);
|
||||
|
||||
const relays = useMemo(() => unique([...streamRelays, ...hostReadRelays]), [hostReadRelays, streamRelays]);
|
||||
|
||||
const hostMuteList = useUserMuteList(stream.host);
|
||||
const muteList = useUserMuteList(account?.pubkey);
|
||||
@ -101,106 +72,45 @@ export default function StreamChat({
|
||||
const scrollBox = useRef<HTMLDivElement | null>(null);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
const { requestSignature } = useSigningContext();
|
||||
const { register, handleSubmit, formState, reset, getValues } = useForm({
|
||||
defaultValues: { content: "" },
|
||||
});
|
||||
const sendMessage = handleSubmit(async (values) => {
|
||||
try {
|
||||
const draft = buildChatMessage(stream, values.content);
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Send Chat", relays, signed);
|
||||
reset();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
}
|
||||
});
|
||||
|
||||
const zapModal = useDisclosure();
|
||||
const { requestPay } = useInvoiceModalContext();
|
||||
const zapMetadata = useUserLNURLMetadata(stream.host);
|
||||
|
||||
const isPopup = !!displayMode;
|
||||
const isChatLog = displayMode === "log";
|
||||
|
||||
return (
|
||||
<>
|
||||
<IntersectionObserverProvider callback={callback} root={scrollBox}>
|
||||
<LightboxProvider>
|
||||
<Card {...props} overflow="hidden" background={isChatLog ? "transparent" : undefined}>
|
||||
{!isPopup && (
|
||||
<CardHeader py="3" display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Heading size="md">Stream Chat</Heading>
|
||||
{actions}
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardBody display="flex" flexDirection="column" overflow="hidden" p={0}>
|
||||
<TopZappers zaps={zaps} pt={!isPopup ? 0 : undefined} />
|
||||
<Flex
|
||||
overflowY="scroll"
|
||||
overflowX="hidden"
|
||||
ref={scrollBox}
|
||||
direction="column-reverse"
|
||||
flex={1}
|
||||
px="4"
|
||||
py="2"
|
||||
mb="2"
|
||||
gap="2"
|
||||
css={isChatLog && hideScrollbar}
|
||||
>
|
||||
{events.map((event) =>
|
||||
event.kind === STREAM_CHAT_MESSAGE_KIND ? (
|
||||
<ChatMessage key={event.id} event={event} stream={stream} />
|
||||
) : (
|
||||
<ZapMessage key={event.id} zap={event} stream={stream} />
|
||||
),
|
||||
)}
|
||||
</Flex>
|
||||
{!isChatLog && (
|
||||
<Box
|
||||
as="form"
|
||||
borderRadius="md"
|
||||
flexShrink={0}
|
||||
display="flex"
|
||||
gap="2"
|
||||
px="2"
|
||||
pb="2"
|
||||
onSubmit={sendMessage}
|
||||
>
|
||||
<Input placeholder="Message" {...register("content", { required: true })} autoComplete="off" />
|
||||
<Button colorScheme="brand" type="submit" isLoading={formState.isSubmitting}>
|
||||
Send
|
||||
</Button>
|
||||
{zapMetadata.metadata?.allowsNostr && (
|
||||
<IconButton
|
||||
icon={<LightningIcon color="yellow.400" />}
|
||||
aria-label="Zap stream"
|
||||
borderColor="yellow.400"
|
||||
variant="outline"
|
||||
onClick={zapModal.onOpen}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<IntersectionObserverProvider callback={callback} root={scrollBox}>
|
||||
<LightboxProvider>
|
||||
<Card {...props} overflow="hidden" background={isChatLog ? "transparent" : undefined}>
|
||||
{!isPopup && (
|
||||
<CardHeader py="3" display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Heading size="md">Stream Chat</Heading>
|
||||
{actions}
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardBody display="flex" flexDirection="column" overflow="hidden" p={0}>
|
||||
<TopZappers zaps={zaps} pt={!isPopup ? 0 : undefined} />
|
||||
<Flex
|
||||
overflowY="scroll"
|
||||
overflowX="hidden"
|
||||
ref={scrollBox}
|
||||
direction="column-reverse"
|
||||
flex={1}
|
||||
px="4"
|
||||
py="2"
|
||||
mb="2"
|
||||
gap="2"
|
||||
css={isChatLog && hideScrollbar}
|
||||
>
|
||||
{events.map((event) =>
|
||||
event.kind === STREAM_CHAT_MESSAGE_KIND ? (
|
||||
<ChatMessage key={event.id} event={event} stream={stream} />
|
||||
) : (
|
||||
<ZapMessage key={event.id} zap={event} stream={stream} />
|
||||
),
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</LightboxProvider>
|
||||
</IntersectionObserverProvider>
|
||||
{zapModal.isOpen && (
|
||||
<ZapModal
|
||||
isOpen
|
||||
stream={stream}
|
||||
pubkey={stream.host}
|
||||
onInvoice={async (invoice) => {
|
||||
reset();
|
||||
zapModal.onClose();
|
||||
await requestPay(invoice);
|
||||
}}
|
||||
onClose={zapModal.onClose}
|
||||
initialComment={getValues().content}
|
||||
additionalRelays={relays}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Flex>
|
||||
{!isChatLog && <ChatMessageForm stream={stream} />}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</LightboxProvider>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user