diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js
index bee3cd23b..94cc81354 100644
--- a/app/javascript/dashboard/api/inbox/conversation.js
+++ b/app/javascript/dashboard/api/inbox/conversation.js
@@ -127,6 +127,10 @@ class ConversationApi extends ApiClient {
user_ids: userIds,
});
}
+
+ getAllAttachments(conversationId) {
+ return axios.get(`${this.url}/${conversationId}/attachments`);
+ }
}
export default new ConversationApi();
diff --git a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js
index d276b8053..08343b69c 100644
--- a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js
+++ b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js
@@ -210,5 +210,12 @@ describe('#ConversationAPI', () => {
{ params: { page: payload.page } }
);
});
+
+ it('#getAllAttachments', () => {
+ conversationAPI.getAllAttachments(1);
+ expect(context.axiosMock.get).toHaveBeenCalledWith(
+ '/api/v1/conversations/1/attachments'
+ );
+ });
});
});
diff --git a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss
index 6c37e5677..f95ee8a65 100644
--- a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss
+++ b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss
@@ -29,13 +29,13 @@
}
.modal-image {
- max-height: 90%;
- max-width: 90%;
+ max-height: 80vh;
+ max-width: 80vw;
}
.modal-video {
- max-height: 75vh;
- max-width: 100%;
+ max-height: 80vh;
+ max-width: 80vw;
}
&::before {
@@ -53,16 +53,6 @@
width: 100%;
}
}
-
- .video {
- .modal-container {
- width: auto;
-
- .modal--close {
- z-index: var(--z-index-low);
- }
- }
- }
}
.conversations-list-wrap {
@@ -400,4 +390,3 @@
margin-bottom: 0;
}
}
-
diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue
index dd51ea838..4df910cde 100644
--- a/app/javascript/dashboard/components/widgets/conversation/Message.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue
@@ -53,22 +53,11 @@
-
-
-
0) {
const { attachments = [{}] } = this.data;
diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue
index 23ebdc23e..5bac4a631 100644
--- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue
@@ -279,6 +279,7 @@ export default {
if (newChat.id === oldChat.id) {
return;
}
+ this.fetchAllAttachmentsFromCurrentChat();
this.selectedTweetId = null;
},
},
@@ -290,6 +291,7 @@ export default {
mounted() {
this.addScrollListener();
+ this.fetchAllAttachmentsFromCurrentChat();
},
beforeDestroy() {
@@ -298,6 +300,9 @@ export default {
},
methods: {
+ fetchAllAttachmentsFromCurrentChat() {
+ this.$store.dispatch('fetchAllAttachments', this.currentChat.id);
+ },
removeBusListeners() {
bus.$off(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
bus.$off(BUS_EVENTS.SET_TWEET_REPLY, this.setSelectedTweet);
diff --git a/app/javascript/dashboard/components/widgets/conversation/bubble/ImageAudioVideo.vue b/app/javascript/dashboard/components/widgets/conversation/bubble/ImageAudioVideo.vue
new file mode 100644
index 000000000..f703fefe2
--- /dev/null
+++ b/app/javascript/dashboard/components/widgets/conversation/bubble/ImageAudioVideo.vue
@@ -0,0 +1,106 @@
+
+
+
![]()
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components/widgets/conversation/components/GalleryView.vue b/app/javascript/dashboard/components/widgets/conversation/components/GalleryView.vue
new file mode 100644
index 000000000..0f04c1d07
--- /dev/null
+++ b/app/javascript/dashboard/components/widgets/conversation/components/GalleryView.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/store/modules/conversations/actions.js b/app/javascript/dashboard/store/modules/conversations/actions.js
index a5d4a25df..8db97fc88 100644
--- a/app/javascript/dashboard/store/modules/conversations/actions.js
+++ b/app/javascript/dashboard/store/modules/conversations/actions.js
@@ -86,6 +86,18 @@ const actions = {
}
},
+ fetchAllAttachments: async ({ commit }, conversationId) => {
+ try {
+ const { data } = await ConversationApi.getAllAttachments(conversationId);
+ commit(types.SET_ALL_ATTACHMENTS, {
+ id: conversationId,
+ data: data.payload,
+ });
+ } catch (error) {
+ // Handle error
+ }
+ },
+
syncActiveConversationMessages: async (
{ commit, state, dispatch },
{ conversationId }
@@ -247,6 +259,10 @@ const actions = {
...response.data,
status: MESSAGE_STATUS.SENT,
});
+ commit(types.ADD_CONVERSATION_ATTACHMENTS, {
+ ...response.data,
+ status: MESSAGE_STATUS.SENT,
+ });
} catch (error) {
const errorMessage = error.response
? error.response.data.error
@@ -269,6 +285,7 @@ const actions = {
conversationId: message.conversation_id,
canReply: true,
});
+ commit(types.ADD_CONVERSATION_ATTACHMENTS, message);
}
},
@@ -283,6 +300,7 @@ const actions = {
try {
const { data } = await MessageApi.delete(conversationId, messageId);
commit(types.ADD_MESSAGE, data);
+ commit(types.DELETE_CONVERSATION_ATTACHMENTS, data);
} catch (error) {
throw new Error(error);
}
diff --git a/app/javascript/dashboard/store/modules/conversations/getters.js b/app/javascript/dashboard/store/modules/conversations/getters.js
index b5b38da0f..0b1ca73d3 100644
--- a/app/javascript/dashboard/store/modules/conversations/getters.js
+++ b/app/javascript/dashboard/store/modules/conversations/getters.js
@@ -32,6 +32,11 @@ const getters = {
);
return selectedChat || {};
},
+ getSelectedChatAttachments: (_state, _getters) => {
+ const selectedChat = _getters.getSelectedChat;
+ const { attachments } = selectedChat;
+ return attachments;
+ },
getLastEmailInSelectedChat: (stage, _getters) => {
const selectedChat = _getters.getSelectedChat;
const { messages = [] } = selectedChat;
diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js
index 04db1fddf..2062b07da 100644
--- a/app/javascript/dashboard/store/modules/conversations/index.js
+++ b/app/javascript/dashboard/store/modules/conversations/index.js
@@ -3,6 +3,7 @@ import types from '../../mutation-types';
import getters, { getSelectedChatConversation } from './getters';
import actions from './actions';
import { findPendingMessageIndex } from './helpers';
+import { MESSAGE_STATUS } from 'shared/constants/messages';
import wootConstants from 'dashboard/constants/globals';
import { BUS_EVENTS } from '../../../../shared/constants/busEvents';
@@ -56,6 +57,13 @@ export const mutations = {
chat.messages.unshift(...data);
}
},
+ [types.SET_ALL_ATTACHMENTS](_state, { id, data }) {
+ if (data.length) {
+ const [chat] = _state.allConversations.filter(c => c.id === id);
+ Vue.set(chat, 'attachments', []);
+ chat.attachments.push(...data);
+ }
+ },
[types.SET_MISSING_MESSAGES](_state, { id, data }) {
const [chat] = _state.allConversations.filter(c => c.id === id);
if (!chat) return;
@@ -115,6 +123,44 @@ export const mutations = {
Vue.set(chat, 'muted', false);
},
+ [types.ADD_CONVERSATION_ATTACHMENTS]({ allConversations }, message) {
+ const { conversation_id: conversationId } = message;
+ const [chat] = getSelectedChatConversation({
+ allConversations,
+ selectedChatId: conversationId,
+ });
+
+ if (!chat) return;
+
+ const isMessageSent =
+ message.status === MESSAGE_STATUS.SENT && message.attachments;
+ if (isMessageSent) {
+ message.attachments.forEach(attachment => {
+ if (!chat.attachments.some(a => a.id === attachment.id)) {
+ chat.attachments.push(attachment);
+ }
+ });
+ }
+ },
+
+ [types.DELETE_CONVERSATION_ATTACHMENTS]({ allConversations }, message) {
+ const { conversation_id: conversationId } = message;
+ const [chat] = getSelectedChatConversation({
+ allConversations,
+ selectedChatId: conversationId,
+ });
+
+ if (!chat) return;
+
+ const isMessageSent = message.status === MESSAGE_STATUS.SENT;
+ if (isMessageSent) {
+ const attachmentIndex = chat.attachments.findIndex(
+ a => a.message_id === message.id
+ );
+ if (attachmentIndex !== -1) chat.attachments.splice(attachmentIndex, 1);
+ }
+ },
+
[types.ADD_MESSAGE]({ allConversations, selectedChatId }, message) {
const { conversation_id: conversationId } = message;
const [chat] = getSelectedChatConversation({
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
index b2d68c94f..5ff27c03e 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
@@ -204,6 +204,7 @@ describe('#actions', () => {
]);
});
});
+
describe('#addMessage', () => {
it('sends correct mutations if message is incoming', () => {
const message = {
@@ -218,6 +219,7 @@ describe('#actions', () => {
types.SET_CONVERSATION_CAN_REPLY,
{ conversationId: 1, canReply: true },
],
+ [types.ADD_CONVERSATION_ATTACHMENTS, message],
]);
});
it('sends correct mutations if message is not an incoming message', () => {
@@ -436,10 +438,13 @@ describe('#actions', () => {
describe('#deleteMessage', () => {
it('sends correct actions if API is success', async () => {
const [conversationId, messageId] = [1, 1];
- axios.delete.mockResolvedValue({ data: { id: 1, content: 'deleted' } });
+ axios.delete.mockResolvedValue({
+ data: { id: 1, content: 'deleted' },
+ });
await actions.deleteMessage({ commit }, { conversationId, messageId });
expect(commit.mock.calls).toEqual([
[types.ADD_MESSAGE, { id: 1, content: 'deleted' }],
+ [types.DELETE_CONVERSATION_ATTACHMENTS, { id: 1, content: 'deleted' }],
]);
});
it('sends no actions if API is error', async () => {
@@ -554,4 +559,40 @@ describe('#addMentions', () => {
],
]);
});
+
+ describe('#fetchAllAttachments', () => {
+ it('fetches all attachments', async () => {
+ axios.get.mockResolvedValue({
+ data: {
+ payload: [
+ {
+ id: 1,
+ message_id: 1,
+ file_type: 'image',
+ data_url: '',
+ thumb_url: '',
+ },
+ ],
+ },
+ });
+ await actions.fetchAllAttachments({ commit }, 1);
+ expect(commit.mock.calls).toEqual([
+ [
+ types.SET_ALL_ATTACHMENTS,
+ {
+ id: 1,
+ data: [
+ {
+ id: 1,
+ message_id: 1,
+ file_type: 'image',
+ data_url: '',
+ thumb_url: '',
+ },
+ ],
+ },
+ ],
+ ]);
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js
index 2a012fd53..e874b05ec 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js
@@ -305,4 +305,34 @@ describe('#getters', () => {
});
});
});
+
+ describe('#getSelectedChatAttachments', () => {
+ it('Returns attachments in selected chat', () => {
+ const state = {};
+ const getSelectedChat = {
+ attachments: [
+ {
+ id: 1,
+ file_name: 'test1',
+ },
+ {
+ id: 2,
+ file_name: 'test2',
+ },
+ ],
+ };
+ expect(
+ getters.getSelectedChatAttachments(state, { getSelectedChat })
+ ).toEqual([
+ {
+ id: 1,
+ file_name: 'test1',
+ },
+ {
+ id: 2,
+ file_name: 'test2',
+ },
+ ]);
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
index 2c4bdb2cc..c7280c6f9 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
@@ -278,4 +278,121 @@ describe('#mutations', () => {
expect(state.appliedFilters).toEqual([]);
});
});
+
+ describe('#SET_ALL_ATTACHMENTS', () => {
+ it('set all attachments', () => {
+ const state = {
+ allConversations: [{ id: 1 }],
+ };
+ const data = [{ id: 1, name: 'test' }];
+ mutations[types.SET_ALL_ATTACHMENTS](state, { id: 1, data });
+ expect(state.allConversations[0].attachments).toEqual(data);
+ });
+ });
+
+ describe('#ADD_CONVERSATION_ATTACHMENTS', () => {
+ it('add conversation attachments', () => {
+ const state = {
+ allConversations: [{ id: 1, attachments: [] }],
+ };
+ const message = {
+ conversation_id: 1,
+ status: 'sent',
+ attachments: [{ id: 1, name: 'test' }],
+ };
+
+ mutations[types.ADD_CONVERSATION_ATTACHMENTS](state, message);
+ expect(state.allConversations[0].attachments).toEqual(
+ message.attachments
+ );
+ });
+
+ it('should not add duplicate attachments', () => {
+ const state = {
+ allConversations: [
+ {
+ id: 1,
+ attachments: [{ id: 1, name: 'existing' }],
+ },
+ ],
+ };
+ const message = {
+ conversation_id: 1,
+ status: 'sent',
+ attachments: [
+ { id: 1, name: 'existing' },
+ { id: 2, name: 'new' },
+ ],
+ };
+
+ mutations[types.ADD_CONVERSATION_ATTACHMENTS](state, message);
+ expect(state.allConversations[0].attachments).toHaveLength(2);
+ expect(state.allConversations[0].attachments).toContainEqual({
+ id: 1,
+ name: 'existing',
+ });
+ expect(state.allConversations[0].attachments).toContainEqual({
+ id: 2,
+ name: 'new',
+ });
+ });
+
+ it('should not add attachments if chat not found', () => {
+ const state = {
+ allConversations: [{ id: 1, attachments: [] }],
+ };
+ const message = {
+ conversation_id: 2,
+ status: 'sent',
+ attachments: [{ id: 1, name: 'test' }],
+ };
+
+ mutations[types.ADD_CONVERSATION_ATTACHMENTS](state, message);
+ expect(state.allConversations[0].attachments).toHaveLength(0);
+ });
+ });
+
+ describe('#DELETE_CONVERSATION_ATTACHMENTS', () => {
+ it('delete conversation attachments', () => {
+ const state = {
+ allConversations: [{ id: 1, attachments: [{ id: 1, message_id: 1 }] }],
+ };
+ const message = {
+ conversation_id: 1,
+ status: 'sent',
+ id: 1,
+ };
+
+ mutations[types.DELETE_CONVERSATION_ATTACHMENTS](state, message);
+ expect(state.allConversations[0].attachments).toHaveLength(0);
+ });
+
+ it('should not delete attachments for non-matching message id', () => {
+ const state = {
+ allConversations: [{ id: 1, attachments: [{ id: 1, message_id: 1 }] }],
+ };
+ const message = {
+ conversation_id: 1,
+ status: 'sent',
+ id: 2,
+ };
+
+ mutations[types.DELETE_CONVERSATION_ATTACHMENTS](state, message);
+ expect(state.allConversations[0].attachments).toHaveLength(1);
+ });
+
+ it('should not delete attachments if chat not found', () => {
+ const state = {
+ allConversations: [{ id: 1, attachments: [{ id: 1, message_id: 1 }] }],
+ };
+ const message = {
+ conversation_id: 2,
+ status: 'sent',
+ id: 1,
+ };
+
+ mutations[types.DELETE_CONVERSATION_ATTACHMENTS](state, message);
+ expect(state.allConversations[0].attachments).toHaveLength(1);
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index ae15ff572..2f73080d3 100644
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -49,6 +49,10 @@ export default {
UPDATE_CONVERSATION_LAST_ACTIVITY: 'UPDATE_CONVERSATION_LAST_ACTIVITY',
SET_MISSING_MESSAGES: 'SET_MISSING_MESSAGES',
+ SET_ALL_ATTACHMENTS: 'SET_ALL_ATTACHMENTS',
+ ADD_CONVERSATION_ATTACHMENTS: 'ADD_CONVERSATION_ATTACHMENTS',
+ DELETE_CONVERSATION_ATTACHMENTS: 'DELETE_CONVERSATION_ATTACHMENTS',
+
SET_CONVERSATION_CAN_REPLY: 'SET_CONVERSATION_CAN_REPLY',
// Inboxes
diff --git a/app/javascript/shared/helpers/KeyboardHelpers.js b/app/javascript/shared/helpers/KeyboardHelpers.js
index 5ef9d9f6a..11e5af598 100644
--- a/app/javascript/shared/helpers/KeyboardHelpers.js
+++ b/app/javascript/shared/helpers/KeyboardHelpers.js
@@ -94,6 +94,14 @@ export const hasPressedArrowDownKey = e => {
return e.keyCode === 40;
};
+export const hasPressedArrowLeftKey = e => {
+ return e.keyCode === 37;
+};
+
+export const hasPressedArrowRightKey = e => {
+ return e.keyCode === 39;
+};
+
export const hasPressedCommandPlusKKey = e => {
return e.metaKey && e.keyCode === 75;
};