mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 11:08:04 +00:00 
			
		
		
		
	 d45527b851
			
		
	
	d45527b851
	
	
	
		
			
			Fixes [CW-5540](https://linear.app/chatwoot/issue/CW-5540/feat-allow-retry-a-failed-message-sendout), https://github.com/chatwoot/chatwoot/issues/12333 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Screenshot <img width="196" height="113" alt="image" src="https://github.com/user-attachments/assets/8b4a1aa9-c75c-4169-b4a2-e89148647e91" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
		
			
				
	
	
		
			124 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <script setup>
 | |
| import { defineProps, computed } from 'vue';
 | |
| import Message from './Message.vue';
 | |
| import { MESSAGE_TYPES } from './constants.js';
 | |
| import { useCamelCase } from 'dashboard/composables/useTransformKeys';
 | |
| 
 | |
| /**
 | |
|  * Props definition for the component
 | |
|  * @typedef {Object} Props
 | |
|  * @property {Array} readMessages - Array of read messages
 | |
|  * @property {Array} unReadMessages - Array of unread messages
 | |
|  * @property {Number} currentUserId - ID of the current user
 | |
|  * @property {Boolean} isAnEmailChannel - Whether this is an email channel
 | |
|  * @property {Object} inboxSupportsReplyTo - Inbox reply support configuration
 | |
|  * @property {Array} messages - Array of all messages [These are not in camelcase]
 | |
|  */
 | |
| const props = defineProps({
 | |
|   currentUserId: {
 | |
|     type: Number,
 | |
|     required: true,
 | |
|   },
 | |
|   firstUnreadId: {
 | |
|     type: Number,
 | |
|     default: null,
 | |
|   },
 | |
|   isAnEmailChannel: {
 | |
|     type: Boolean,
 | |
|     default: false,
 | |
|   },
 | |
|   inboxSupportsReplyTo: {
 | |
|     type: Object,
 | |
|     default: () => ({ incoming: false, outgoing: false }),
 | |
|   },
 | |
|   messages: {
 | |
|     type: Array,
 | |
|     default: () => [],
 | |
|   },
 | |
| });
 | |
| 
 | |
| const emit = defineEmits(['retry']);
 | |
| 
 | |
| const allMessages = computed(() => {
 | |
|   return useCamelCase(props.messages, { deep: true });
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Determines if a message should be grouped with the next message
 | |
|  * @param {Number} index - Index of the current message
 | |
|  * @param {Array} searchList - Array of messages to check
 | |
|  * @returns {Boolean} - Whether the message should be grouped with next
 | |
|  */
 | |
| const shouldGroupWithNext = (index, searchList) => {
 | |
|   if (index === searchList.length - 1) return false;
 | |
| 
 | |
|   const current = searchList[index];
 | |
|   const next = searchList[index + 1];
 | |
| 
 | |
|   if (next.status === 'failed') return false;
 | |
| 
 | |
|   const nextSenderId = next.senderId ?? next.sender?.id;
 | |
|   const currentSenderId = current.senderId ?? current.sender?.id;
 | |
|   const hasSameSender = nextSenderId === currentSenderId;
 | |
| 
 | |
|   const nextMessageType = next.messageType;
 | |
|   const currentMessageType = current.messageType;
 | |
| 
 | |
|   const areBothTemplates =
 | |
|     nextMessageType === MESSAGE_TYPES.TEMPLATE &&
 | |
|     currentMessageType === MESSAGE_TYPES.TEMPLATE;
 | |
| 
 | |
|   if (!hasSameSender || areBothTemplates) return false;
 | |
| 
 | |
|   if (currentMessageType !== nextMessageType) return false;
 | |
| 
 | |
|   // Check if messages are in the same minute by rounding down to nearest minute
 | |
|   return Math.floor(next.createdAt / 60) === Math.floor(current.createdAt / 60);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Gets the message that was replied to
 | |
|  * @param {Object} parentMessage - The message containing the reply reference
 | |
|  * @returns {Object|null} - The message being replied to, or null if not found
 | |
|  */
 | |
| const getInReplyToMessage = parentMessage => {
 | |
|   if (!parentMessage) return null;
 | |
| 
 | |
|   const inReplyToMessageId =
 | |
|     parentMessage.contentAttributes?.inReplyTo ??
 | |
|     parentMessage.content_attributes?.in_reply_to;
 | |
| 
 | |
|   if (!inReplyToMessageId) return null;
 | |
| 
 | |
|   // Find in-reply-to message in the messages prop
 | |
|   const replyMessage = props.messages?.find(
 | |
|     message => message.id === inReplyToMessageId
 | |
|   );
 | |
| 
 | |
|   return replyMessage ? useCamelCase(replyMessage) : null;
 | |
| };
 | |
| </script>
 | |
| 
 | |
| <template>
 | |
|   <ul class="px-4 bg-n-background">
 | |
|     <slot name="beforeAll" />
 | |
|     <template v-for="(message, index) in allMessages" :key="message.id">
 | |
|       <slot
 | |
|         v-if="firstUnreadId && message.id === firstUnreadId"
 | |
|         name="unreadBadge"
 | |
|       />
 | |
|       <Message
 | |
|         v-bind="message"
 | |
|         :is-email-inbox="isAnEmailChannel"
 | |
|         :in-reply-to="getInReplyToMessage(message)"
 | |
|         :group-with-next="shouldGroupWithNext(index, allMessages)"
 | |
|         :inbox-supports-reply-to="inboxSupportsReplyTo"
 | |
|         :current-user-id="currentUserId"
 | |
|         data-clarity-mask="True"
 | |
|         @retry="emit('retry', message)"
 | |
|       />
 | |
|     </template>
 | |
|     <slot name="after" />
 | |
|   </ul>
 | |
| </template>
 |