From 990722679a5c89fa9ed8279fbdafe8eaef5867df Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Fri, 9 Feb 2024 20:42:37 +0530 Subject: [PATCH] feat: use copied replied message for showing reply block --- .../MessageActions/MessageActionModal.tsx | 1 - .../chat-space/chat-view/MessageBlock.tsx | 37 ++++++-------- .../feature/chat/ChatMessage/MessageItem.tsx | 11 +++-- .../TiptapRenderer/TiptapRenderer.tsx | 9 ---- .../ReplyMessageBox/ReplyMessageBox.tsx | 49 ++++--------------- raven/api/chat.py | 29 ----------- raven/api/raven_message.py | 4 +- raven/patches.txt | 3 +- ...ages_to_include_replied_message_content.py | 24 +++++++++ .../doctype/raven_message/raven_message.json | 25 +++++++--- .../doctype/raven_message/raven_message.py | 22 ++++++++- types/Messaging/Message.ts | 7 ++- 12 files changed, 103 insertions(+), 118 deletions(-) create mode 100644 raven/patches/v1_3/update_all_messages_to_include_replied_message_content.py diff --git a/mobile/src/components/features/chat-space/MessageActions/MessageActionModal.tsx b/mobile/src/components/features/chat-space/MessageActions/MessageActionModal.tsx index f6332d115..65e29cc56 100644 --- a/mobile/src/components/features/chat-space/MessageActions/MessageActionModal.tsx +++ b/mobile/src/components/features/chat-space/MessageActions/MessageActionModal.tsx @@ -5,7 +5,6 @@ import { IonContent, IonList, } from '@ionic/react'; -import { EmojiAction } from './EmojiAction'; import './messageActionModal.styles.css' import { DeleteAction } from './DeleteAction'; import { UserContext } from '@/utils/auth/UserProvider'; diff --git a/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx b/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx index bc27dc0fe..8edd72730 100644 --- a/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx +++ b/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx @@ -80,7 +80,7 @@ const ContinuationMessageBlock = ({ message }: { message: MessageBlock }) => { const MessageContent = ({ message }: { message: MessageBlock }) => { return
- {message.data.is_reply === 1 && message.data.linked_message && } + {message.data.is_reply === 1 && message.data.linked_message && message.data.replied_message_details && } {message.data.message_type === 'Text' &&
} {message.data.message_type === 'Image' && } {message.data.message_type === 'File' && } @@ -159,47 +159,38 @@ const FileMessageBlock = ({ message }: { message: FileMessage }) => {
} -const ReplyBlock = ({ linked_message }: { linked_message: string }) => { +const ReplyBlock = ({ message }: { message: Message }) => { const members = useContext(ChannelMembersContext) - const { data, isLoading } = useFrappeGetCall('raven.api.chat.get_reply_message_content', { - message_id: linked_message - }, `reply_message_${linked_message}`, { - revalidateIfStale: false, - revalidateOnFocus: false, - shouldRetryOnError: false, - revalidateOnReconnect: false - }) - const user = useMemo(() => { - if (data && data.message) { - return members[data?.message.owner] + if (message) { + return members[message?.owner] } else { return undefined } - }, [data]) + }, [message]) const scrollToMessage = () => { Haptics.impact({ style: ImpactStyle.Light }) - document.getElementById(`message-${linked_message}`)?.scrollIntoView({ behavior: 'smooth' }) + document.getElementById(`message-${message.name}`)?.scrollIntoView({ behavior: 'smooth' }) } - const date = data ? new Date(data?.message?.creation) : null + const date = message ? new Date(message?.creation) : null return
- {data && data.message &&
+ {message &&
- {user?.full_name ?? data.message.owner} + {user?.full_name ?? message.owner} {date && on {DateObjectToFormattedDateStringWithoutYear(date)} at {DateObjectToTimeString(date)}}
- {data.message.message_type === 'Text' &&
} - {data.message.message_type === 'Image' &&
- {`Image`} + {message.message_type === 'Text' &&
{message.content}
} + {message.message_type === 'Image' &&
+ {`Image`}

📸  Image

} - {data.message.message_type === 'File' &&

📎  {data.message.file?.split('/')[3]}

} + {message.message_type === 'File' &&

📎  {message.file?.split('/')[3]}

}
}
diff --git a/raven-app/src/components/feature/chat/ChatMessage/MessageItem.tsx b/raven-app/src/components/feature/chat/ChatMessage/MessageItem.tsx index bf7c70fef..d0e0472ef 100644 --- a/raven-app/src/components/feature/chat/ChatMessage/MessageItem.tsx +++ b/raven-app/src/components/feature/chat/ChatMessage/MessageItem.tsx @@ -16,7 +16,7 @@ import { TiptapRenderer } from './Renderers/TiptapRenderer/TiptapRenderer' import { QuickActions } from './MessageActions/QuickActions/QuickActions' import { memo, useContext } from 'react' import { UserContext } from '@/utils/auth/UserProvider' -import { ReplyMessage } from './ReplyMessageBox/ReplyMessageBox' +import { ReplyMessageBox } from './ReplyMessageBox/ReplyMessageBox' import { generateAvatarColor } from '../../select-member/GenerateAvatarColor' import { Skeleton } from '@/components/common/Skeleton' import { DoctypeLinkRenderer } from './Renderers/DoctypeLinkRenderer' @@ -33,7 +33,7 @@ interface MessageBlockProps { export const MessageItem = ({ message, setDeleteMessage, onReplyMessageClick, setEditMessage, isScrolling, replyToMessage, updateMessages }: MessageBlockProps) => { - const { name, owner: userID, creation: timestamp, message_reactions, is_continuation, is_reply, linked_message } = message + const { name, owner: userID, creation: timestamp, message_reactions, is_continuation, is_reply, linked_message, replied_message_details } = message const { user, isActive } = useGetUserDetails(userID) @@ -78,12 +78,13 @@ export const MessageItem = ({ message, setDeleteMessage, onReplyMessageClick, se {/* Message content goes here */} {/* If it's a reply, then show the linked message */} - {linked_message && onReplyMessageClick(linked_message)} - messageID={linked_message} />} - {/* Show message according to type */} + message={JSON.parse(replied_message_details)} /> + } + { /* Show message according to type */} ) -} - -export const TruncatedTiptapRenderer = ({ message, user, showLinkPreview = false, ...props }: TiptapRendererProps) => { - - - return - - - } \ No newline at end of file diff --git a/raven-app/src/components/feature/chat/ChatMessage/ReplyMessageBox/ReplyMessageBox.tsx b/raven-app/src/components/feature/chat/ChatMessage/ReplyMessageBox/ReplyMessageBox.tsx index cd24b542a..b16f5cd39 100644 --- a/raven-app/src/components/feature/chat/ChatMessage/ReplyMessageBox/ReplyMessageBox.tsx +++ b/raven-app/src/components/feature/chat/ChatMessage/ReplyMessageBox/ReplyMessageBox.tsx @@ -6,12 +6,9 @@ import { FileExtensionIcon } from "@/utils/layout/FileExtIcon" import { getFileExtension, getFileName } from "@/utils/operations" import { FlexProps } from "@radix-ui/themes/dist/cjs/components/flex" import { clsx } from "clsx" -import { TruncatedTiptapRenderer } from "../Renderers/TiptapRenderer/TiptapRenderer" -import { useFrappeGetCall } from "frappe-react-sdk" -import { Loader } from "@/components/common/Loader" interface ReplyMessageBoxProps extends FlexProps { - message: Message + message: Partial } /** * UI component to show the message being replied to @@ -22,53 +19,27 @@ export const ReplyMessageBox = ({ message, children, className, ...props }: Repl const user = useGetUser(message.owner) return ( - + - {user?.full_name ?? message.owner} + {user?.full_name ?? message.owner} - + {message.creation && } - {['File', 'Image'].includes(message.message_type) ? - - {message.message_type === 'File' && } - {message.message_type === 'Image' && {`Image} - {getFileName((message as FileMessage).file)} + {['File', 'Image'].includes(message.message_type ?? 'Text') ? + + {message.message_type === 'File' && message.file && } + {message.message_type === 'Image' && {`Image} + {getFileName((message as FileMessage).file)} - : + : {(message as TextMessage).content} } {children} ) -} - -interface ReplyMessageProps extends FlexProps { - messageID: string -} -/** - * Component to fetch the message being replied to and show it in the UI - */ -export const ReplyMessage = ({ messageID, ...props }: ReplyMessageProps) => { - - const { data, isLoading } = useFrappeGetCall('raven.api.chat.get_reply_message_content', { - message_id: messageID - }, `reply_message_${messageID}`, { - revalidateIfStale: false, - revalidateOnFocus: false, - shouldRetryOnError: false, - revalidateOnReconnect: false - }) - - //TODO: Replace with a skeleton loader - if (isLoading) return - - if (data) return - - return null - } \ No newline at end of file diff --git a/raven/api/chat.py b/raven/api/chat.py index 4475e0918..bf535fe39 100644 --- a/raven/api/chat.py +++ b/raven/api/chat.py @@ -36,32 +36,3 @@ def get_channel_members(channel_id): else: frappe.throw(_("You do not have permission to view this channel"), frappe.PermissionError) - - -@frappe.whitelist(methods=['GET']) -def get_reply_message_content(message_id): - # Check if the user has permission to view the message - # fetch all channel members - # get member details from user table, such as name, full_name, user_image, first_name - - if frappe.db.exists("Raven Message", message_id): - - channel_id = frappe.db.get_value("Raven Message", message_id, "channel_id") - channel_type = frappe.db.get_value("Raven Channel", channel_id, "type") - has_permission = False - - if channel_type == 'Public' or channel_type == 'Open': - has_permission = True - else: - if frappe.db.exists("Raven Channel Member", {"user_id": frappe.session.user, "channel_id": channel_id}): - has_permission = True - - if has_permission: - return frappe.db.sql(""" - SELECT owner, creation, message_type, file, text, channel_id, name - FROM `tabRaven Message` - WHERE name = %s - """, message_id, as_dict=True)[0] - else: - frappe.throw(_("Message {} does not exist".format(message_id)), frappe.DoesNotExistError) - # return frappe.db.get_value("Raven Message", message_id, ['owner', 'creation', 'message_type', 'file', 'text', 'channel_id', 'name'], ignore_permissions=True) diff --git a/raven/api/raven_message.py b/raven/api/raven_message.py index acdbef9bc..3bb7c8a20 100644 --- a/raven/api/raven_message.py +++ b/raven/api/raven_message.py @@ -96,7 +96,9 @@ def get_messages(channel_id): messages = frappe.db.get_all('Raven Message', filters={'channel_id': channel_id}, fields=['name', 'owner', 'creation', 'modified', 'text', - 'file', 'message_type', 'message_reactions', 'is_reply', 'linked_message', '_liked_by', 'channel_id', 'thumbnail_width', 'thumbnail_height', 'file_thumbnail', 'link_doctype', 'link_document'], + 'file', 'message_type', 'message_reactions', 'is_reply', 'linked_message', '_liked_by', 'channel_id', + 'thumbnail_width', 'thumbnail_height', 'file_thumbnail', 'link_doctype', 'link_document', + 'replied_message_details', 'content'], order_by='creation asc' ) diff --git a/raven/patches.txt b/raven/patches.txt index ad1b343a6..ea9426b23 100644 --- a/raven/patches.txt +++ b/raven/patches.txt @@ -3,4 +3,5 @@ [post_model_sync] raven.patches.v1_2.create_raven_users raven.patches.v1_3.create_raven_message_indexes #23 -raven.patches.v1_3.update_all_messages_to_include_message_content #2 \ No newline at end of file +raven.patches.v1_3.update_all_messages_to_include_message_content #2 +raven.patches.v1_3.update_all_messages_to_include_replied_message_content #2 \ No newline at end of file diff --git a/raven/patches/v1_3/update_all_messages_to_include_replied_message_content.py b/raven/patches/v1_3/update_all_messages_to_include_replied_message_content.py new file mode 100644 index 000000000..30ceeb27c --- /dev/null +++ b/raven/patches/v1_3/update_all_messages_to_include_replied_message_content.py @@ -0,0 +1,24 @@ +import frappe +import datetime +import json + +def execute(): + ''' + Update all old messages to include it's replied message content + This is a one-time operation, not required for new messages + ''' + messages = frappe.db.get_all('Raven Message', fields=[ + 'name', 'linked_message'], filters={'is_reply': 1}) + for message in messages: + if message.linked_message: + details = frappe.db.get_value( + "Raven Message", message.linked_message, ["text", "content", "file", "message_type", "owner", "creation"], as_dict=True) + frappe.db.set_value("Raven Message", message.name, "replied_message_details", json.dumps({ + "text": details.text, + "content": details.content, + "file": details.file, + "message_type": details.message_type, + "owner": details.owner, + "creation": datetime.datetime.strftime(details.creation, "%Y-%m-%d %H:%M:%S") + }, indent=1), update_modified=False) + frappe.db.commit() diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.json b/raven/raven_messaging/doctype/raven_message/raven_message.json index ff7d32024..50c3929cd 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.json +++ b/raven/raven_messaging/doctype/raven_message/raven_message.json @@ -11,19 +11,21 @@ "channel_id", "text", "json", + "message_reactions", + "is_reply", + "linked_message", + "replied_message_details", + "column_break_wvje", "message_type", + "content", "file", "image_width", "image_height", "file_thumbnail", "thumbnail_width", "thumbnail_height", - "message_reactions", - "is_reply", - "linked_message", "link_doctype", - "link_document", - "content" + "link_document" ], "fields": [ { @@ -71,7 +73,7 @@ { "fieldname": "linked_message", "fieldtype": "Link", - "label": "Linked Message", + "label": "Replied Message ID", "options": "Raven Message" }, { @@ -116,11 +118,20 @@ "fieldtype": "Long Text", "label": "Content", "read_only": 1 + }, + { + "fieldname": "column_break_wvje", + "fieldtype": "Column Break" + }, + { + "fieldname": "replied_message_details", + "fieldtype": "JSON", + "label": "Replied Message Details" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-01-19 14:56:44.534433", + "modified": "2024-02-09 18:15:42.424300", "modified_by": "Administrator", "module": "Raven Messaging", "name": "Raven Message", diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.py b/raven/raven_messaging/doctype/raven_message/raven_message.py index 833f6da5c..96012ae17 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.py +++ b/raven/raven_messaging/doctype/raven_message/raven_message.py @@ -5,6 +5,7 @@ from frappe.model.document import Document from raven.api.raven_message import track_visit from frappe.core.utils import html2text +import datetime class RavenMessage(Document): # begin: auto-generated types @@ -28,6 +29,7 @@ class RavenMessage(Document): linked_message: DF.Link | None message_reactions: DF.JSON | None message_type: DF.Literal["Text", "Image", "File"] + replied_message_details: DF.JSON | None text: DF.LongText | None thumbnail_height: DF.Data | None thumbnail_width: DF.Data | None @@ -36,7 +38,9 @@ class RavenMessage(Document): def before_validate(self): try: if self.text: - self.content = html2text(self.text) + content = html2text(self.text) + # Remove trailing new line characters and white spaces + self.content = content.rstrip() except Exception: pass def validate(self): @@ -66,6 +70,21 @@ def validate_linked_message(self): if frappe.db.get_value("Raven Message", self.linked_message, "channel_id") != self.channel_id: frappe.throw(_("Linked message should be in the same channel")) + def before_insert(self): + ''' + If the message is a reply, update the replied_message_details field + ''' + if self.is_reply and self.linked_message: + details = frappe.db.get_value( + "Raven Message", self.linked_message, ["text", "content", "file", "message_type", "owner", "creation"], as_dict=True) + self.replied_message_details = { + "text": details.text, + "content": details.content, + "file": details.file, + "message_type": details.message_type, + "owner": details.owner, + "creation": datetime.datetime.strftime(details.creation, "%Y-%m-%d %H:%M:%S") + } def after_insert(self): frappe.publish_realtime( 'raven:unread_channel_count_updated', { @@ -98,6 +117,7 @@ def on_trash(self): frappe.db.delete("Raven Message Reaction", {"message": self.name}) def before_save(self): + #TODO: Remove this if frappe.db.get_value('Raven Channel', self.channel_id, 'type') != 'Private' or frappe.db.exists("Raven Channel Member", {"channel_id": self.channel_id, "user_id": frappe.session.user}): track_visit(self.channel_id) diff --git a/types/Messaging/Message.ts b/types/Messaging/Message.ts index dd516b009..e1bbd6382 100644 --- a/types/Messaging/Message.ts +++ b/types/Messaging/Message.ts @@ -13,7 +13,9 @@ export interface BaseMessage { is_reply: 1 | 0 linked_message?: string | null link_doctype?: string - link_document?: string + link_document?: string, + /** JSON as string */ + replied_message_details?: string } export interface FileMessage extends BaseMessage { @@ -33,7 +35,8 @@ export interface ImageMessage extends BaseMessage { export interface TextMessage extends BaseMessage { text: string, - message_type: 'Text' + message_type: 'Text', + content?: string } export type DateBlock = {