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

feat: use copied replied message for showing reply block #663

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
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const ContinuationMessageBlock = ({ message }: { message: MessageBlock }) => {
const MessageContent = ({ message }: { message: MessageBlock }) => {

return <div className='min-w-[100px] max-w-[280px]'>
{message.data.is_reply === 1 && message.data.linked_message && <ReplyBlock linked_message={message.data.linked_message} />}
{message.data.is_reply === 1 && message.data.linked_message && message.data.replied_message_details && <ReplyBlock message={JSON.parse(message.data.replied_message_details)} />}
{message.data.message_type === 'Text' && <div className='text-zinc-300'><TextMessageBlock message={message.data} /></div>}
{message.data.message_type === 'Image' && <ImageMessageBlock message={message.data} />}
{message.data.message_type === 'File' && <FileMessageBlock message={message.data} />}
Expand Down Expand Up @@ -159,47 +159,38 @@ const FileMessageBlock = ({ message }: { message: FileMessage }) => {
</div>
}

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 <div onClick={scrollToMessage} className='px-2 py-1.5 my-2 rounded-e-sm bg-neutral-900 border-l-4 border-l-neutral-500'>
{data && data.message && <div>
{message && <div>
<div className='flex items-end pb-1'>
<IonText className='font-bold text-sm'>{user?.full_name ?? data.message.owner}</IonText>
<IonText className='font-bold text-sm'>{user?.full_name ?? message.owner}</IonText>
{date && <IonText className='font-normal text-xs pl-2' color='medium'>on {DateObjectToFormattedDateStringWithoutYear(date)} at {DateObjectToTimeString(date)}</IonText>}
</div>
{data.message.message_type === 'Text' && <div className='text-sm text-neutral-400'><TextMessageBlock message={data.message} truncate /></div>}
{data.message.message_type === 'Image' && <div className='flex items-center space-x-2'>
<img src={data.message.file} alt={`Image`} className='inline-block w-10 h-10 rounded-md' />
{message.message_type === 'Text' && <div className='text-sm text-neutral-400'>{message.content}</div>}
{message.message_type === 'Image' && <div className='flex items-center space-x-2'>
<img src={message.file} alt={`Image`} className='inline-block w-10 h-10 rounded-md' />
<p className='text-sm font-semibold'>📸 &nbsp;Image</p>
</div>}
{data.message.message_type === 'File' && <p
className='text-sm font-semibold'>📎 &nbsp;{data.message.file?.split('/')[3]}</p>}
{message.message_type === 'File' && <p
className='text-sm font-semibold'>📎 &nbsp;{message.file?.split('/')[3]}</p>}
</div>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)

Expand Down Expand Up @@ -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 && <ReplyMessage
{linked_message && replied_message_details && <ReplyMessageBox
className='min-w-[32rem] cursor-pointer mb-1'
role='button'
onClick={() => onReplyMessageClick(linked_message)}
messageID={linked_message} />}
{/* Show message according to type */}
message={JSON.parse(replied_message_details)} />
}
{ /* Show message according to type */}
<MessageContent
message={message}
user={user}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,4 @@ export const TiptapRenderer = ({ message, user, isScrolling = false, isTruncated
</EditorContext.Provider>
</Box>
)
}

export const TruncatedTiptapRenderer = ({ message, user, showLinkPreview = false, ...props }: TiptapRendererProps) => {


return <Box className='text-ellipsis overflow-hidden line-clamp-3'>
<TiptapRenderer message={message} user={user} showLinkPreview={showLinkPreview} isTruncated {...props} />
</Box>

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Message>
}
/**
* UI component to show the message being replied to
Expand All @@ -22,53 +19,27 @@ export const ReplyMessageBox = ({ message, children, className, ...props }: Repl

const user = useGetUser(message.owner)
return (
<Flex className={clsx('p-2 items-start bg-white border border-gray-5 shadow-sm hover:border-iris-10 dark:bg-gray-1 dark:border-gray-7 dark:hover:border-iris-8 rounded-md', className)} {...props}>
<Flex className={clsx('p-2 items-start bg-white border border-gray-5 shadow-sm dark:bg-gray-1 dark:border-gray-7 rounded-md', className)} {...props}>
<Flex gap='1' direction='column' className="border-l-2 pl-2 border-gray-8">
<Flex gap='2' align='center'>
<Text as='span' size='1' weight='bold'>{user?.full_name ?? message.owner}</Text>
<Text as='span' size='1' weight='medium'>{user?.full_name ?? message.owner}</Text>
<Separator orientation='vertical' />
<Text as='span' size='1' color='gray'>
<DateMonthAtHourMinuteAmPm date={message.creation} />
{message.creation && <DateMonthAtHourMinuteAmPm date={message.creation} />}
</Text>
</Flex>
<Box className="max-w-3xl">
{['File', 'Image'].includes(message.message_type) ?
<Flex gap='1'>
{message.message_type === 'File' && <FileExtensionIcon ext={getFileExtension(message.file)} />}
{message.message_type === 'Image' && <img src={message.file} alt={`Image sent by ${message.owner}`} height='30' width='30' className="object-cover" />}
<Text as='span'>{getFileName((message as FileMessage).file)}</Text>
{['File', 'Image'].includes(message.message_type ?? 'Text') ?
<Flex gap='2' align='center'>
{message.message_type === 'File' && message.file && <FileExtensionIcon ext={getFileExtension(message.file)} size='18' />}
{message.message_type === 'Image' && <img src={message.file} alt={`Image sent by ${message.owner}`} height='30' width='30' className="object-cover rounded-md" />}
<Text as='span' size='2'>{getFileName((message as FileMessage).file)}</Text>
</Flex>
: <TruncatedTiptapRenderer message={message as TextMessage} />
: <Text as='span' size='2' className="line-clamp-2">{(message as TextMessage).content}</Text>
}
</Box>
</Flex>
{children}
</Flex>
)
}

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 <Loader />

if (data) return <ReplyMessageBox message={data.message} {...props} />

return null

}
29 changes: 0 additions & 29 deletions raven/api/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 3 additions & 1 deletion raven/api/raven_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
)

Expand Down
3 changes: 2 additions & 1 deletion raven/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
Original file line number Diff line number Diff line change
@@ -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()
25 changes: 18 additions & 7 deletions raven/raven_messaging/doctype/raven_message/raven_message.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down Expand Up @@ -71,7 +73,7 @@
{
"fieldname": "linked_message",
"fieldtype": "Link",
"label": "Linked Message",
"label": "Replied Message ID",
"options": "Raven Message"
},
{
Expand Down Expand Up @@ -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",
Expand Down
22 changes: 21 additions & 1 deletion raven/raven_messaging/doctype/raven_message/raven_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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', {
Expand Down Expand Up @@ -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)

Expand Down
Loading
Loading