mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-22 22:44:08 +02: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>;
|
return <EmojiProvider emojis={defaultEmojis}>{children}</EmojiProvider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserEmojiProvider({ children }: PropsWithChildren) {
|
export function UserEmojiProvider({ children, pubkey }: PropsWithChildren & { pubkey?: string }) {
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
const userPacks = useUserEmojiPacks(account?.pubkey);
|
const userPacks = useUserEmojiPacks(pubkey || account?.pubkey);
|
||||||
const events = useReplaceableEvents(userPacks?.packs);
|
const events = useReplaceableEvents(userPacks?.packs);
|
||||||
|
|
||||||
const emojis = events
|
const emojis = events
|
||||||
@ -34,8 +34,6 @@ export function UserEmojiProvider({ children }: PropsWithChildren) {
|
|||||||
)
|
)
|
||||||
.flat();
|
.flat();
|
||||||
|
|
||||||
console.log(userPacks, emojis);
|
|
||||||
|
|
||||||
return <EmojiProvider emojis={emojis}>{children}</EmojiProvider>;
|
return <EmojiProvider emojis={emojis}>{children}</EmojiProvider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import RelaySelectionProvider from "../../../providers/relay-selection-provider"
|
|||||||
import StreamerCards from "../components/streamer-cards";
|
import StreamerCards from "../components/streamer-cards";
|
||||||
import { useAppTitle } from "../../../hooks/use-app-title";
|
import { useAppTitle } from "../../../hooks/use-app-title";
|
||||||
import StreamSatsPerMinute from "../components/stream-sats-per-minute";
|
import StreamSatsPerMinute from "../components/stream-sats-per-minute";
|
||||||
|
import { UserEmojiProvider } from "../../../providers/emoji-provider";
|
||||||
|
|
||||||
function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode?: ChatDisplayMode }) {
|
function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode?: ChatDisplayMode }) {
|
||||||
useAppTitle(stream.title);
|
useAppTitle(stream.title);
|
||||||
@ -194,7 +195,9 @@ export default function StreamView() {
|
|||||||
return (
|
return (
|
||||||
// add snort and damus relays so zap.stream will always see zaps
|
// add snort and damus relays so zap.stream will always see zaps
|
||||||
<RelaySelectionProvider additionalDefaults={streamRelays}>
|
<RelaySelectionProvider additionalDefaults={streamRelays}>
|
||||||
|
<UserEmojiProvider pubkey={stream.host}>
|
||||||
<StreamPage stream={stream} displayMode={(params.get("displayMode") as ChatDisplayMode) ?? undefined} />
|
<StreamPage stream={stream} displayMode={(params.get("displayMode") as ChatDisplayMode) ?? undefined} />
|
||||||
|
</UserEmojiProvider>
|
||||||
</RelaySelectionProvider>
|
</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 { useCallback, useMemo, useRef } from "react";
|
||||||
import {
|
import { Card, CardBody, CardHeader, CardProps, Flex, Heading } from "@chakra-ui/react";
|
||||||
Box,
|
import { css } from "@emotion/react";
|
||||||
Button,
|
import { Kind } from "nostr-tools";
|
||||||
Card,
|
|
||||||
CardBody,
|
|
||||||
CardHeader,
|
|
||||||
CardProps,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
IconButton,
|
|
||||||
Input,
|
|
||||||
useDisclosure,
|
|
||||||
useToast,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
|
|
||||||
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, buildChatMessage, getATag } from "../../../../helpers/nostr/stream";
|
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, 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 ChatMessage from "./chat-message";
|
import ChatMessage from "./chat-message";
|
||||||
import ZapMessage from "./zap-message";
|
import ZapMessage from "./zap-message";
|
||||||
import { LightboxProvider } from "../../../../components/lightbox-provider";
|
import { LightboxProvider } from "../../../../components/lightbox-provider";
|
||||||
import IntersectionObserverProvider from "../../../../providers/intersection-observer";
|
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 { useTimelineCurserIntersectionCallback } from "../../../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
import useSubject from "../../../../hooks/use-subject";
|
import useSubject from "../../../../hooks/use-subject";
|
||||||
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
|
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
|
||||||
import { truncatedId } from "../../../../helpers/nostr/events";
|
import { truncatedId } from "../../../../helpers/nostr/events";
|
||||||
import { css } from "@emotion/react";
|
|
||||||
import TopZappers from "./top-zappers";
|
import TopZappers from "./top-zappers";
|
||||||
import { parseZapEvent } from "../../../../helpers/zaps";
|
import { parseZapEvent } from "../../../../helpers/zaps";
|
||||||
import { Kind } from "nostr-tools";
|
|
||||||
import { useRelaySelectionRelays } from "../../../../providers/relay-selection-provider";
|
import { useRelaySelectionRelays } from "../../../../providers/relay-selection-provider";
|
||||||
import useUserMuteList from "../../../../hooks/use-user-mute-list";
|
import useUserMuteList from "../../../../hooks/use-user-mute-list";
|
||||||
import { NostrEvent, isPTag } from "../../../../types/nostr-event";
|
import { NostrEvent, isPTag } from "../../../../types/nostr-event";
|
||||||
import { useCurrentAccount } from "../../../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../../../hooks/use-current-account";
|
||||||
import NostrPublishAction from "../../../../classes/nostr-publish-action";
|
import ChatMessageForm from "./chat-message-form";
|
||||||
import { ensureNotifyContentMentions } from "../../../../helpers/nostr/post";
|
|
||||||
|
|
||||||
const hideScrollbar = css`
|
const hideScrollbar = css`
|
||||||
scrollbar-width: 0;
|
scrollbar-width: 0;
|
||||||
@ -59,14 +36,8 @@ export default function StreamChat({
|
|||||||
displayMode,
|
displayMode,
|
||||||
...props
|
...props
|
||||||
}: CardProps & { stream: ParsedStream; actions?: React.ReactNode; displayMode?: ChatDisplayMode }) {
|
}: CardProps & { stream: ParsedStream; actions?: React.ReactNode; displayMode?: ChatDisplayMode }) {
|
||||||
const toast = useToast();
|
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
const streamRelays = useRelaySelectionRelays();
|
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 hostMuteList = useUserMuteList(stream.host);
|
||||||
const muteList = useUserMuteList(account?.pubkey);
|
const muteList = useUserMuteList(account?.pubkey);
|
||||||
@ -101,30 +72,10 @@ export default function StreamChat({
|
|||||||
const scrollBox = useRef<HTMLDivElement | null>(null);
|
const scrollBox = useRef<HTMLDivElement | null>(null);
|
||||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
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 isPopup = !!displayMode;
|
||||||
const isChatLog = displayMode === "log";
|
const isChatLog = displayMode === "log";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<IntersectionObserverProvider callback={callback} root={scrollBox}>
|
<IntersectionObserverProvider callback={callback} root={scrollBox}>
|
||||||
<LightboxProvider>
|
<LightboxProvider>
|
||||||
<Card {...props} overflow="hidden" background={isChatLog ? "transparent" : undefined}>
|
<Card {...props} overflow="hidden" background={isChatLog ? "transparent" : undefined}>
|
||||||
@ -156,51 +107,10 @@ export default function StreamChat({
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
{!isChatLog && (
|
{!isChatLog && <ChatMessageForm stream={stream} />}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</LightboxProvider>
|
</LightboxProvider>
|
||||||
</IntersectionObserverProvider>
|
</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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user