Skip to content

Commit

Permalink
Merge pull request #1578 from ecency/feature/forward-message
Browse files Browse the repository at this point in the history
Chats: added message forwarding between contacts and channels
  • Loading branch information
feruzm authored Mar 22, 2024
2 parents 116e9f7 + 6b62f39 commit ddaf0b4
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 39 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"start:prod": "NODE_ENV=production node build/server.js"
},
"dependencies": {
"@ecency/ns-query": "^1.1.7",
"@ecency/render-helper": "^2.2.30",
"@ecency/ns-query": "^1.1.8",
"@ecency/render-helper": "^2.2.32",
"@ecency/render-helper-amp": "^1.1.0",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
Expand Down
10 changes: 5 additions & 5 deletions src/common/constants/contributors.json
Original file line number Diff line number Diff line change
Expand Up @@ -495,20 +495,20 @@
"name": "gamingumar",
"contributes": ["Developer (Mobile)"]
},
{
{
"name": "asgharali",
"contributes": ["Guest Curator"]
},
{
{
"name": "aliakbar2",
"contributes": ["Guest Curator"]
},
{
{
"name": "untilwelearn",
"contributes": ["Guest Curator"]
},
{
{
"name": "sirenahippie",
"contributes": ["Guest Curator"]
}
}
]
15 changes: 15 additions & 0 deletions src/common/features/chats/components/chat-channel-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Spinner } from "@ui/spinner";
import {
Channel,
checkContiguousMessage,
Message,
PublicMessage,
useHideMessageInChannel,
useJoinedCommunityTeamQuery,
Expand All @@ -21,6 +22,8 @@ import { differenceInCalendarDays } from "date-fns";
import useDebounce from "react-use/lib/useDebounce";
import { useCommunityCache } from "../../../core";
import { ROLES } from "../../../store/communities";
import { ForwardMessageDialog } from "./forward-message-dialog";
import { UilMessage } from "@iconscout/react-unicons";

interface Props {
publicMessages: PublicMessage[];
Expand All @@ -36,6 +39,7 @@ export function ChatsChannelMessages({ publicMessages, currentChannel, isPage }:
// Message where users interacted with context menu
const [currentInteractingMessageId, setCurrentInteractingMessageId] = useState<string>();
const [needFetchNextPage, setNeedFetchNextPage] = useState(false);
const [forwardingMessage, setForwardingMessage] = useState<Message>();

const { data: community } = useCommunityCache(currentChannel?.communityName);
const { data: joinedCommunityTeamKeys, isSuccess: isJoinedCommunityTeamKeysFetched } =
Expand Down Expand Up @@ -127,6 +131,11 @@ export function ChatsChannelMessages({ publicMessages, currentChannel, isPage }:
className="top-[70%]"
align={message.creator === publicKey ? "right" : "left"}
>
<DropdownItemWithIcon
icon={<UilMessage />}
label={_t("chat.forward")}
onClick={() => setForwardingMessage(message)}
/>
<DropdownItemWithIcon
icon={isHideMessageLoading ? <Spinner className="w-3.5 h-3.5" /> : hideSvg}
label={_t("chat.hide-message")}
Expand All @@ -147,6 +156,12 @@ export function ChatsChannelMessages({ publicMessages, currentChannel, isPage }:
))}
</React.Fragment>
))}

<ForwardMessageDialog
message={forwardingMessage!!}
show={!!forwardingMessage}
setShow={() => setForwardingMessage(undefined)}
/>
</div>
</>
);
Expand Down
4 changes: 2 additions & 2 deletions src/common/features/chats/components/chat-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default function ChatInput({ currentChannel, currentContact }: Props) {
if (isDisabled || isSendMessageLoading || isFilesUploading || !nextMessage) {
return;
}
await sendMessage(nextMessage);
await sendMessage({ message: nextMessage });
setFiles([]);
setUploadedFileLinks([]);
// Re-focus to input because when DOM changes and input position changes then
Expand Down Expand Up @@ -124,7 +124,7 @@ export default function ChatInput({ currentChannel, currentContact }: Props) {
gifImagesStyle={GifImagesStyle}
shGif={true}
changeState={(gifState) => setShowGifPicker(gifState!)}
fallback={(e) => sendMessage(e)}
fallback={(e) => sendMessage({ message: e })}
/>
)}
{(files.length > 0 || uploadedFileLinks.length > 0) && !showGifPicker && (
Expand Down
23 changes: 21 additions & 2 deletions src/common/features/chats/components/chat-message-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import {
import { format } from "date-fns";
import { useInViewport } from "react-in-viewport";
import "./_chat-message-item.scss";
import { makePath } from "../../../components/profile-link";
import ProfileLink, { makePath } from "../../../components/profile-link";
import { Link } from "react-router-dom";
import { history } from "../../../store";

interface Props {
type: "sender" | "receiver";
Expand Down Expand Up @@ -52,7 +53,7 @@ export function ChatMessageItem({
className = ""
}: Props) {
const ref = useRef<HTMLDivElement | null>(null);
const { global, activeUser } = useMappedStore();
const { global, activeUser, addAccount } = useMappedStore();
const { publicKey } = useKeysQuery();

const [holdStarted, setHoldStarted] = useState(false);
Expand All @@ -74,6 +75,7 @@ export function ChatMessageItem({
const { inViewport } = useInViewport(ref);

const { data: nostrUserProfiles } = useNostrGetUserProfileQuery(message.creator);
const { data: nostrForwardedUserProfiles } = useNostrGetUserProfileQuery(message.forwardedFrom);

const profile = useMemo(
() => nostrUserProfiles?.find((p) => p.creator === message.creator),
Expand Down Expand Up @@ -171,6 +173,23 @@ export function ChatMessageItem({
{profile!!.name}
</Link>
)}
{message.forwardedFrom && (
<div className="text-xs text-gray-300 dark:text-gray-700">
{_t("chat.forwarded-from")}
{nostrForwardedUserProfiles?.[0]?.name && (
<ProfileLink
target="_blank"
addAccount={addAccount}
history={history!!}
username={nostrForwardedUserProfiles?.[0]?.name}
>
<span className="text-gray-300 dark:text-gray-700">
({nostrForwardedUserProfiles[0].name})
</span>
</ProfileLink>
)}
</div>
)}
<div
className="sender-message-content [&>img]:rounded-xl"
dangerouslySetInnerHTML={{ __html: renderedPreview }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import {
checkContiguousMessage,
DirectContact,
DirectMessage,
Message,
useDirectMessagesQuery,
useKeysQuery
} from "@ecency/ns-query";
import { ChatFloatingDate } from "../chat-floating-date";
import { differenceInCalendarDays } from "date-fns";
import { groupMessages } from "../../utils";
import useDebounce from "react-use/lib/useDebounce";
import { Dropdown, DropdownItemWithIcon, DropdownMenu } from "@ui/dropdown";
import { UilMessage } from "@iconscout/react-unicons";
import { _t } from "../../../../i18n";
import { ForwardMessageDialog } from "../forward-message-dialog";

interface Props {
directMessages: DirectMessage[];
Expand All @@ -23,9 +28,14 @@ export default function ChatsDirectMessages(props: Props) {
const { directMessages } = props;

const [needFetchNextPage, setNeedFetchNextPage] = useState(false);
const [forwardingMessage, setForwardingMessage] = useState<Message>();

const { publicKey } = useKeysQuery();
const directMessagesQuery = useDirectMessagesQuery(props.currentContact);

// Message where users interacted with context menu
const [currentInteractingMessageId, setCurrentInteractingMessageId] = useState<string>();

const groupedDirectMessages = useMemo(() => groupMessages(directMessages), [directMessages]);

useDebounce(
Expand Down Expand Up @@ -53,32 +63,57 @@ export default function ChatsDirectMessages(props: Props) {
<React.Fragment key={date.getTime()}>
{diff > 0 && <ChatFloatingDate currentDate={date} isPage={props.isPage} />}
{messages.map((message, j) => (
<ChatMessageItem
showDate={j === messages.length - 1}
<Dropdown
key={message.id}
currentContact={props.currentContact}
type={message.creator !== publicKey ? "receiver" : "sender"}
message={message}
isSameUser={checkContiguousMessage(message, i, directMessages)}
onAppear={() =>
setTimeout(
() =>
groupedDirectMessages?.length - 1 === i && messages.length - 1 === j
? document
.querySelector(`[data-message-id="${message.id}"]`)
?.scrollIntoView({ block: "nearest" })
: {},
300
)
}
onInViewport={(inViewport) =>
i === 0 && j === 0 && setNeedFetchNextPage(inViewport)
closeOnClickOutside={true}
show={currentInteractingMessageId === message.id}
setShow={(v) =>
setCurrentInteractingMessageId(v ? currentInteractingMessageId : undefined)
}
/>
>
<ChatMessageItem
showDate={j === messages.length - 1}
key={message.id}
currentContact={props.currentContact}
type={message.creator !== publicKey ? "receiver" : "sender"}
message={message}
isSameUser={checkContiguousMessage(message, i, directMessages)}
onContextMenu={() => setCurrentInteractingMessageId(message.id)}
onAppear={() =>
setTimeout(
() =>
groupedDirectMessages?.length - 1 === i && messages.length - 1 === j
? document
.querySelector(`[data-message-id="${message.id}"]`)
?.scrollIntoView({ block: "nearest" })
: {},
300
)
}
onInViewport={(inViewport) =>
i === 0 && j === 0 && setNeedFetchNextPage(inViewport)
}
/>
<DropdownMenu
className="top-[70%]"
align={message.creator === publicKey ? "right" : "left"}
>
<DropdownItemWithIcon
icon={<UilMessage />}
label={_t("chat.forward")}
onClick={() => setForwardingMessage(message)}
/>
</DropdownMenu>
</Dropdown>
))}
</React.Fragment>
);
})}
<ForwardMessageDialog
message={forwardingMessage!!}
show={!!forwardingMessage}
setShow={() => setForwardingMessage(undefined)}
/>
</div>
</>
);
Expand Down
128 changes: 128 additions & 0 deletions src/common/features/chats/components/forward-message-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Modal, ModalBody, ModalHeader } from "@ui/modal";
import { List } from "@ui/list";
import { ChatSidebarDirectContact } from "./chats-sidebar/chat-sidebar-direct-contact";
import { ChatSidebarChannel } from "./chats-sidebar/chat-sidebar-channel";
import React, { useEffect, useState } from "react";
import {
Channel,
DirectContact,
Message,
useChannelsQuery,
useDirectContactsQuery,
useSendMessage
} from "@ecency/ns-query";
import { _t } from "../../../i18n";
import { Spinner } from "@ui/spinner";
import { error, success } from "../../../components/feedback";

interface Props {
show: boolean;
setShow: (v: boolean) => void;
message: Message;
}

export function ForwardMessageDialog({ show, setShow, message }: Props) {
const { data: contacts } = useDirectContactsQuery();
const { data: channels } = useChannelsQuery();

const [selectedChannel, setSelectedChannel] = useState<Channel>();
const [selectedContact, setSelectedContact] = useState<DirectContact>();

const {
mutateAsync: forwardMessage,
isLoading: isMessageForwarding,
isSuccess: isMessageForwardSuccess,
isError: isMessageForwardError
} = useSendMessage(selectedChannel, selectedContact);

useEffect(() => {
if (isMessageForwardError) {
error(_t("g.error"));
setShow(false);
}
}, [isMessageForwardError]);

useEffect(() => {
if (isMessageForwardSuccess) {
success(_t("g.success"));
setShow(false);
}
}, [isMessageForwardSuccess]);

useEffect(() => {
if (selectedContact) {
setTimeout(
() => forwardMessage({ message: message.content, forwardedFrom: message.creator }),
1
);
}
}, [selectedContact]);

useEffect(() => {
if (selectedChannel) {
setTimeout(
() => forwardMessage({ message: message.content, forwardedFrom: message.creator }),
1
);
}
}, [selectedChannel]);

return (
<Modal animation={false} centered={true} show={show} onHide={() => setShow(false)}>
<ModalHeader closeButton={true}>
<div>{_t("chat.message-forwarding")}</div>
</ModalHeader>
<ModalBody>
<div className="relative">
<div className="mb-4 text-blue-dark-sky">{_t("chat.select-contact-or-channel")}</div>
{(contacts?.length ?? 0) > 0 && (
<div>
<div className="mb-2 text-xs font-semibold text-gray-500 uppercase px-3">
{_t("chat.direct-messages")}
</div>
<List>
{contacts?.map((contact) => (
<div
className="border-b border-[--border-color] cursor-pointer"
key={contact.pubkey}
onClick={() => setSelectedContact(contact)}
>
<ChatSidebarDirectContact contact={contact} />
</div>
))}
</List>
</div>
)}
{channels.length > 0 && (
<div>
<div className="mb-2 text-xs font-semibold text-gray-500 uppercase px-3">
{_t("chat.communities")}
</div>
<List>
{channels.map((channel) => (
<div
className="border-b border-[--border-color] cursor-pointer"
key={channel.id}
onClick={() => setSelectedChannel(selectedChannel)}
>
<ChatSidebarChannel
username={channel.name}
channel={channel}
isChannel={true}
/>
</div>
))}
</List>
</div>
)}

{isMessageForwarding && (
<div className="absolute top-0 left-0 right-0 bottom-0 bg-white dark:bg-dark-200 bg-opacity-75 flex items-center justify-center">
<Spinner className="w-4 h-4" />
</div>
)}
</div>
</ModalBody>
</Modal>
);
}
Loading

0 comments on commit ddaf0b4

Please sign in to comment.