Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chats: added message forwarding between contacts and channels #1578

Merged
merged 4 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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