Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcdc9d4388 | ||
|
|
4b12a8a51e | ||
|
|
f639d8ca51 | ||
|
|
3c78d25306 | ||
|
|
8faccba052 | ||
|
|
02000de905 | ||
|
|
d7c0507e33 | ||
|
|
a7f0476e19 | ||
|
|
51e0c1b9d9 | ||
|
|
a8c57f692e | ||
|
|
e97e68b1ba | ||
|
|
be1999e7f8 |
+3
-3
@@ -186,7 +186,7 @@ GEM
|
||||
database_cleaner-core (2.0.1)
|
||||
datadog-ci (0.8.3)
|
||||
msgpack
|
||||
date (3.3.4)
|
||||
date (3.4.1)
|
||||
ddtrace (1.23.2)
|
||||
datadog-ci (~> 0.8.1)
|
||||
debase-ruby_core_source (= 3.3.1)
|
||||
@@ -487,7 +487,7 @@ GEM
|
||||
uri
|
||||
net-http-persistent (4.0.2)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.4.17)
|
||||
net-imap (0.4.19)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -782,7 +782,7 @@ GEM
|
||||
time_diff (0.3.0)
|
||||
activesupport
|
||||
i18n
|
||||
timeout (0.4.1)
|
||||
timeout (0.4.3)
|
||||
trailblazer-option (0.1.2)
|
||||
twilio-ruby (5.77.0)
|
||||
faraday (>= 0.9, < 3.0)
|
||||
|
||||
+3
-2
@@ -77,7 +77,7 @@ const toggleMessageSignature = () => {
|
||||
setSignature();
|
||||
};
|
||||
|
||||
// Added this watch to dynamically set signature.
|
||||
// Added this watch to dynamically set signature on target inbox change.
|
||||
// Only targetInbox has value and is Advance Editor(used by isEmailOrWebWidgetInbox)
|
||||
// Set the signature only if the inbox based flag is true
|
||||
watch(
|
||||
@@ -86,7 +86,8 @@ watch(
|
||||
nextTick(() => {
|
||||
if (newValue && props.isEmailOrWebWidgetInbox) setSignature();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const onClickInsertEmoji = emoji => {
|
||||
|
||||
@@ -15,18 +15,14 @@ import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
* @property {Array} messages - Array of all messages [These are not in camelcase]
|
||||
*/
|
||||
const props = defineProps({
|
||||
readMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
unReadMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
currentUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
firstUnreadId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
isAnEmailChannel: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -41,12 +37,8 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const unread = computed(() => {
|
||||
return useCamelCase(props.unReadMessages, { deep: true });
|
||||
});
|
||||
|
||||
const read = computed(() => {
|
||||
return useCamelCase(props.readMessages, { deep: true });
|
||||
const allMessages = computed(() => {
|
||||
return useCamelCase(props.messages, { deep: true });
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -108,26 +100,18 @@ const getInReplyToMessage = parentMessage => {
|
||||
<template>
|
||||
<ul class="px-4 bg-n-background">
|
||||
<slot name="beforeAll" />
|
||||
<template v-for="(message, index) in read" :key="message.id">
|
||||
<Message
|
||||
v-bind="message"
|
||||
:is-email-inbox="isAnEmailChannel"
|
||||
:in-reply-to="getInReplyToMessage(message)"
|
||||
:group-with-next="shouldGroupWithNext(index, read)"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:current-user-id="currentUserId"
|
||||
data-clarity-mask="True"
|
||||
<template v-for="(message, index) in allMessages" :key="message.id">
|
||||
<slot
|
||||
v-if="firstUnreadId && message.id === firstUnreadId"
|
||||
name="unreadBadge"
|
||||
/>
|
||||
</template>
|
||||
<slot name="beforeUnread" />
|
||||
<template v-for="(message, index) in unread" :key="message.id">
|
||||
<Message
|
||||
v-bind="message"
|
||||
:is-email-inbox="isAnEmailChannel"
|
||||
:in-reply-to="getInReplyToMessage(message)"
|
||||
:group-with-next="shouldGroupWithNext(index, unread)"
|
||||
:group-with-next="shouldGroupWithNext(index, allMessages)"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:current-user-id="currentUserId"
|
||||
:is-email-inbox="isAnEmailChannel"
|
||||
data-clarity-mask="True"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -38,9 +38,10 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const formatTime = time => {
|
||||
if (!time || Number.isNaN(time)) return '00:00';
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
@@ -49,7 +50,7 @@ const toggleMute = () => {
|
||||
};
|
||||
|
||||
const onTimeUpdate = () => {
|
||||
currentTime.value = audioPlayer.value.currentTime;
|
||||
currentTime.value = audioPlayer.value?.currentTime;
|
||||
};
|
||||
|
||||
const seek = event => {
|
||||
|
||||
@@ -243,6 +243,15 @@ export default {
|
||||
unreadMessageCount() {
|
||||
return this.currentChat.unread_count || 0;
|
||||
},
|
||||
unreadMessageLabel() {
|
||||
const count =
|
||||
this.unreadMessageCount > 9 ? '9+' : this.unreadMessageCount;
|
||||
const label =
|
||||
this.unreadMessageCount > 1
|
||||
? 'CONVERSATION.UNREAD_MESSAGES'
|
||||
: 'CONVERSATION.UNREAD_MESSAGE';
|
||||
return `${count} ${this.$t(label)}`;
|
||||
},
|
||||
isInstagramDM() {
|
||||
return this.conversationType === 'instagram_direct_message';
|
||||
},
|
||||
@@ -492,12 +501,11 @@ export default {
|
||||
<NextMessageList
|
||||
v-if="showNextBubbles"
|
||||
class="conversation-panel"
|
||||
:read-messages="readMessages"
|
||||
:un-read-messages="unReadMessages"
|
||||
:current-user-id="currentUserId"
|
||||
:first-unread-id="unReadMessages[0]?.id"
|
||||
:is-an-email-channel="isAnEmailChannel"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:messages="currentChat ? currentChat.messages : []"
|
||||
:messages="getMessages"
|
||||
>
|
||||
<template #beforeAll>
|
||||
<transition name="slide-up">
|
||||
@@ -507,15 +515,10 @@ export default {
|
||||
</li>
|
||||
</transition>
|
||||
</template>
|
||||
<template #beforeUnread>
|
||||
<template #unreadBadge>
|
||||
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
||||
<span>
|
||||
{{ unreadMessageCount > 9 ? '9+' : unreadMessageCount }}
|
||||
{{
|
||||
unreadMessageCount > 1
|
||||
? $t('CONVERSATION.UNREAD_MESSAGES')
|
||||
: $t('CONVERSATION.UNREAD_MESSAGE')
|
||||
}}
|
||||
{{ unreadMessageLabel }}
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,44 @@
|
||||
class ActionCableBroadcastJob < ApplicationJob
|
||||
queue_as :critical
|
||||
include Events::Types
|
||||
|
||||
CONVERSATION_UPDATE_EVENTS = [
|
||||
CONVERSATION_READ,
|
||||
CONVERSATION_UPDATED,
|
||||
TEAM_CHANGED,
|
||||
ASSIGNEE_CHANGED,
|
||||
CONVERSATION_STATUS_CHANGED
|
||||
].freeze
|
||||
|
||||
def perform(members, event_name, data)
|
||||
return if members.blank?
|
||||
|
||||
broadcast_data = prepare_broadcast_data(event_name, data)
|
||||
broadcast_to_members(members, event_name, broadcast_data)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Ensures that only the latest available data is sent to prevent UI issues
|
||||
# caused by out-of-order events during high-traffic periods. This prevents
|
||||
# the conversation job from processing outdated data.
|
||||
def prepare_broadcast_data(event_name, data)
|
||||
return data unless CONVERSATION_UPDATE_EVENTS.include?(event_name)
|
||||
|
||||
account = Account.find(data[:account_id])
|
||||
conversation = account.conversations.find_by!(display_id: data[:id])
|
||||
conversation.push_event_data.merge(account_id: data[:account_id])
|
||||
end
|
||||
|
||||
def broadcast_to_members(members, event_name, broadcast_data)
|
||||
members.each do |member|
|
||||
ActionCable.server.broadcast(member, { event: event_name, data: data })
|
||||
ActionCable.server.broadcast(
|
||||
member,
|
||||
{
|
||||
event: event_name,
|
||||
data: broadcast_data
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,8 @@ class Conversations::EventDataPresenter < SimpleDelegator
|
||||
contact_last_seen_at: contact_last_seen_at.to_i,
|
||||
last_activity_at: last_activity_at.to_i,
|
||||
timestamp: last_activity_at.to_i,
|
||||
created_at: created_at.to_i
|
||||
created_at: created_at.to_i,
|
||||
updated_at: updated_at.to_f
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,6 +44,7 @@ json.muted conversation.muted?
|
||||
json.snoozed_until conversation.snoozed_until
|
||||
json.status conversation.status
|
||||
json.created_at conversation.created_at.to_i
|
||||
json.updated_at conversation.updated_at.to_f
|
||||
json.timestamp conversation.last_activity_at.to_i
|
||||
json.first_reply_created_at conversation.first_reply_created_at.to_i
|
||||
json.unread_count conversation.unread_incoming_messages.count
|
||||
|
||||
+22
-22
@@ -34,18 +34,17 @@
|
||||
"@chatwoot/ninja-keys": "1.2.3",
|
||||
"@chatwoot/prosemirror-schema": "1.1.1-next",
|
||||
"@chatwoot/utils": "^0.0.35",
|
||||
"@formkit/core": "^1.6.7",
|
||||
"@formkit/vue": "^1.6.7",
|
||||
"@formkit/core": "^1.6.9",
|
||||
"@formkit/vue": "^1.6.9",
|
||||
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
||||
"@highlightjs/vue-plugin": "^2.1.0",
|
||||
"@iconify-json/material-symbols": "^1.2.10",
|
||||
"@june-so/analytics-next": "^2.0.0",
|
||||
"@lk77/vue3-color": "^3.0.6",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@rails/actioncable": "6.1.3",
|
||||
"@rails/ujs": "^7.1.400",
|
||||
"@scmmishra/pico-search": "0.5.4",
|
||||
"@sentry/vue": "^8.31.0",
|
||||
"@sentry/vue": "^8.54.0",
|
||||
"@sindresorhus/slugify": "2.2.1",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tanstack/vue-table": "^8.20.5",
|
||||
@@ -53,25 +52,25 @@
|
||||
"@vue/compiler-sfc": "^3.5.8",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/components": "^12.0.0",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"@vueuse/components": "^12.5.0",
|
||||
"@vueuse/core": "^12.5.0",
|
||||
"activestorage": "^5.2.6",
|
||||
"axios": "^1.7.7",
|
||||
"axios": "^1.7.9",
|
||||
"camelcase-keys": "^9.1.3",
|
||||
"chart.js": "~4.4.4",
|
||||
"color2k": "^2.0.2",
|
||||
"company-email-validator": "^1.1.0",
|
||||
"core-js": "3.38.1",
|
||||
"countries-and-timezones": "^3.6.0",
|
||||
"countries-and-timezones": "^3.7.2",
|
||||
"date-fns": "2.21.1",
|
||||
"date-fns-tz": "^1.3.3",
|
||||
"dompurify": "3.1.6",
|
||||
"flag-icons": "^7.2.3",
|
||||
"flag-icons": "^7.3.2",
|
||||
"floating-vue": "^5.2.2",
|
||||
"highlight.js": "^11.10.0",
|
||||
"idb": "^8.0.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"idb": "^8.0.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"libphonenumber-js": "^1.11.9",
|
||||
"libphonenumber-js": "^1.11.19",
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-link-attributes": "^4.0.1",
|
||||
"md5": "^2.3.0",
|
||||
@@ -86,10 +85,10 @@
|
||||
"video.js": "7.18.1",
|
||||
"videojs-record": "4.5.0",
|
||||
"videojs-wavesurfer": "3.8.0",
|
||||
"vue": "^3.5.12",
|
||||
"vue": "^3.5.13",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-datepicker-next": "^1.0.3",
|
||||
"vue-dompurify-html": "^5.1.0",
|
||||
"vue-dompurify-html": "^5.2.0",
|
||||
"vue-i18n": "9.14.2",
|
||||
"vue-letter": "^0.2.0",
|
||||
"vue-multiselect": "3.1.0",
|
||||
@@ -103,18 +102,19 @@
|
||||
"wavesurfer.js": "7.8.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@egoist/tailwindcss-icons": "^1.8.1",
|
||||
"@egoist/tailwindcss-icons": "^1.9.0",
|
||||
"@histoire/plugin-vue": "0.17.15",
|
||||
"@iconify-json/logos": "^1.2.3",
|
||||
"@iconify-json/lucide": "^1.2.11",
|
||||
"@iconify-json/ph": "^1.2.1",
|
||||
"@iconify-json/ri": "^1.2.3",
|
||||
"@iconify-json/teenyicons": "^1.2.1",
|
||||
"@iconify-json/logos": "^1.2.4",
|
||||
"@iconify-json/lucide": "^1.2.26",
|
||||
"@iconify-json/material-symbols": "^1.2.14",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@iconify-json/ri": "^1.2.5",
|
||||
"@iconify-json/teenyicons": "^1.2.2",
|
||||
"@size-limit/file": "^8.2.4",
|
||||
"@vitest/coverage-v8": "3.0.5",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-interactive": "^11.1.0",
|
||||
@@ -133,7 +133,7 @@
|
||||
"prettier": "^3.3.3",
|
||||
"prosemirror-model": "^1.22.3",
|
||||
"size-limit": "^8.2.4",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^5.4.12",
|
||||
"vite-plugin-ruby": "^5.0.0",
|
||||
"vitest": "3.0.5"
|
||||
|
||||
Generated
+398
-487
File diff suppressed because it is too large
Load Diff
@@ -538,6 +538,7 @@ RSpec.describe Conversation do
|
||||
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
||||
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
||||
created_at: conversation.created_at.to_i,
|
||||
updated_at: conversation.updated_at.to_f,
|
||||
waiting_since: conversation.waiting_since.to_i,
|
||||
priority: nil,
|
||||
unread_count: 0
|
||||
|
||||
@@ -31,6 +31,7 @@ RSpec.describe Conversations::EventDataPresenter do
|
||||
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
||||
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
||||
created_at: conversation.created_at.to_i,
|
||||
updated_at: conversation.updated_at.to_f,
|
||||
waiting_since: conversation.waiting_since.to_i,
|
||||
priority: nil,
|
||||
unread_count: 0
|
||||
|
||||
Reference in New Issue
Block a user