Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1670567bb2 | ||
|
|
682a931707 | ||
|
|
7aa3adc4a3 | ||
|
|
89576a6475 | ||
|
|
307dbc4202 | ||
|
|
f34a20e12a | ||
|
|
5e1680e840 | ||
|
|
7bbedf2b6a | ||
|
|
a7d70f418b | ||
|
|
38873fcfc2 | ||
|
|
6cf8a6b7f8 | ||
|
|
26ec0ebd09 | ||
|
|
d248f0bea2 | ||
|
|
1caec05517 |
@@ -1,10 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, computed, reactive } from 'vue';
|
import { defineProps, computed, ref } from 'vue';
|
||||||
import Message from './Message.vue';
|
import Message from './Message.vue';
|
||||||
import { MESSAGE_TYPES } from './constants.js';
|
import { MESSAGE_TYPES } from './constants.js';
|
||||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
import { useMapGetter, useStore } from 'dashboard/composables/store.js';
|
||||||
import MessageApi from 'dashboard/api/inbox/message.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props definition for the component
|
* Props definition for the component
|
||||||
@@ -40,53 +39,15 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['retry']);
|
const emit = defineEmits(['retry']);
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const fetchingConversations = ref(new Set());
|
||||||
const allMessages = computed(() => {
|
const allMessages = computed(() => {
|
||||||
return useCamelCase(props.messages, { deep: true });
|
return useCamelCase(props.messages, { deep: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentChat = useMapGetter('getSelectedChat');
|
const currentChat = useMapGetter('getSelectedChat');
|
||||||
|
|
||||||
// Cache for fetched reply messages to avoid duplicate API calls
|
|
||||||
const fetchedReplyMessages = reactive(new Map());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a specific message from the API by trying to get messages around it
|
|
||||||
* @param {number} messageId - The ID of the message to fetch
|
|
||||||
* @param {number} conversationId - The ID of the conversation
|
|
||||||
* @returns {Promise<Object|null>} - The fetched message or null if not found/error
|
|
||||||
*/
|
|
||||||
const fetchReplyMessage = async (messageId, conversationId) => {
|
|
||||||
// Return cached result if already fetched
|
|
||||||
if (fetchedReplyMessages.has(messageId)) {
|
|
||||||
return fetchedReplyMessages.get(messageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await MessageApi.getPreviousMessages({
|
|
||||||
conversationId,
|
|
||||||
before: messageId + 100,
|
|
||||||
after: messageId - 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
const messages = response.data?.payload || [];
|
|
||||||
const targetMessage = messages.find(msg => msg.id === messageId);
|
|
||||||
|
|
||||||
if (targetMessage) {
|
|
||||||
const camelCaseMessage = useCamelCase(targetMessage);
|
|
||||||
fetchedReplyMessages.set(messageId, camelCaseMessage);
|
|
||||||
return camelCaseMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache null result to avoid repeated API calls
|
|
||||||
fetchedReplyMessages.set(messageId, null);
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
fetchedReplyMessages.set(messageId, null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a message should be grouped with the next message
|
* Determines if a message should be grouped with the next message
|
||||||
* @param {Number} index - Index of the current message
|
* @param {Number} index - Index of the current message
|
||||||
@@ -126,36 +87,43 @@ const shouldGroupWithNext = (index, searchList) => {
|
|||||||
* @returns {Object|null} - The message being replied to, or null if not found
|
* @returns {Object|null} - The message being replied to, or null if not found
|
||||||
*/
|
*/
|
||||||
const getInReplyToMessage = parentMessage => {
|
const getInReplyToMessage = parentMessage => {
|
||||||
if (!parentMessage) return null;
|
|
||||||
|
|
||||||
const inReplyToMessageId =
|
const inReplyToMessageId =
|
||||||
parentMessage.contentAttributes?.inReplyTo ??
|
parentMessage?.contentAttributes?.inReplyTo ??
|
||||||
parentMessage.content_attributes?.in_reply_to;
|
parentMessage?.content_attributes?.in_reply_to;
|
||||||
|
|
||||||
if (!inReplyToMessageId) return null;
|
if (!inReplyToMessageId) return null;
|
||||||
|
|
||||||
// Try to find in current messages first
|
// 1. Check props messages (already camelCased via allMessages)
|
||||||
let replyMessage = props.messages?.find(msg => msg.id === inReplyToMessageId);
|
const foundInProps = allMessages.value?.find(
|
||||||
|
m => m.id === inReplyToMessageId
|
||||||
|
);
|
||||||
|
if (foundInProps) return foundInProps;
|
||||||
|
|
||||||
// Then try store messages
|
// 2. Check store messages
|
||||||
if (!replyMessage && currentChat.value?.messages) {
|
const foundInStore = currentChat.value?.messages?.find(
|
||||||
replyMessage = currentChat.value.messages.find(
|
m => m.id === inReplyToMessageId
|
||||||
msg => msg.id === inReplyToMessageId
|
);
|
||||||
);
|
if (foundInStore) return useCamelCase(foundInStore);
|
||||||
|
|
||||||
|
// 3. Fetch if not currently fetching for this conversation
|
||||||
|
const conversationId = currentChat.value?.id;
|
||||||
|
if (
|
||||||
|
conversationId &&
|
||||||
|
!currentChat.value.allMessagesLoaded &&
|
||||||
|
!fetchingConversations.value.has(conversationId)
|
||||||
|
) {
|
||||||
|
fetchingConversations.value.add(conversationId);
|
||||||
|
store
|
||||||
|
.dispatch('fetchPreviousMessages', {
|
||||||
|
conversationId,
|
||||||
|
before: currentChat.value.messages?.[0]?.id ?? null,
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
fetchingConversations.value.delete(conversationId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then check fetch cache
|
return null;
|
||||||
if (!replyMessage && fetchedReplyMessages.has(inReplyToMessageId)) {
|
|
||||||
replyMessage = fetchedReplyMessages.get(inReplyToMessageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If still not found and we have conversation context, fetch it
|
|
||||||
if (!replyMessage && currentChat.value?.id) {
|
|
||||||
fetchReplyMessage(inReplyToMessageId, currentChat.value.id);
|
|
||||||
return null; // Let UI handle loading state
|
|
||||||
}
|
|
||||||
|
|
||||||
return replyMessage ? useCamelCase(replyMessage) : null;
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -74,9 +74,17 @@ export const mutations = {
|
|||||||
[types.SET_PREVIOUS_CONVERSATIONS](_state, { id, data }) {
|
[types.SET_PREVIOUS_CONVERSATIONS](_state, { id, data }) {
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
const [chat] = _state.allConversations.filter(c => c.id === id);
|
const [chat] = _state.allConversations.filter(c => c.id === id);
|
||||||
chat.messages.unshift(...data);
|
if (chat) {
|
||||||
|
// Duplicate check: only add messages that don't already exist
|
||||||
|
const existingIds = new Set(chat.messages.map(m => m.id));
|
||||||
|
const newMessages = data.filter(m => !existingIds.has(m.id));
|
||||||
|
if (newMessages.length) {
|
||||||
|
chat.messages.unshift(...newMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.SET_ALL_ATTACHMENTS](_state, { id, data }) {
|
[types.SET_ALL_ATTACHMENTS](_state, { id, data }) {
|
||||||
_state.attachments[id] = [...data];
|
_state.attachments[id] = [...data];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -615,6 +615,29 @@ describe('#mutations', () => {
|
|||||||
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||||
expect(state.allConversations[0].messages).toEqual([{ id: 'msg2' }]);
|
expect(state.allConversations[0].messages).toEqual([{ id: 'msg2' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should filter out duplicate messages', () => {
|
||||||
|
const state = {
|
||||||
|
allConversations: [{ id: 1, messages: [{ id: 'msg2' }] }],
|
||||||
|
};
|
||||||
|
const payload = { id: 1, data: [{ id: 'msg1' }, { id: 'msg2' }] };
|
||||||
|
|
||||||
|
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||||
|
expect(state.allConversations[0].messages).toEqual([
|
||||||
|
{ id: 'msg1' },
|
||||||
|
{ id: 'msg2' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if conversation is not found', () => {
|
||||||
|
const state = {
|
||||||
|
allConversations: [{ id: 2, messages: [{ id: 'msg2' }] }],
|
||||||
|
};
|
||||||
|
const payload = { id: 1, data: [{ id: 'msg1' }] };
|
||||||
|
|
||||||
|
mutations[types.SET_PREVIOUS_CONVERSATIONS](state, payload);
|
||||||
|
expect(state.allConversations[0].messages).toEqual([{ id: 'msg2' }]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#SET_MISSING_MESSAGES', () => {
|
describe('#SET_MISSING_MESSAGES', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user