diff --git a/app/javascript/dashboard/components-next/Conversation/Chips/UnreadIndicator.vue b/app/javascript/dashboard/components-next/Conversation/Chips/UnreadIndicator.vue
new file mode 100644
index 000000000..66d89f0cb
--- /dev/null
+++ b/app/javascript/dashboard/components-next/Conversation/Chips/UnreadIndicator.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue
index 00f2289ab..a8ccceeb8 100644
--- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue
@@ -11,6 +11,7 @@ import ReplyBox from './ReplyBox.vue';
import Message from './Message.vue';
import NextMessageList from 'next/message/MessageList.vue';
import TypingIndicator from 'next/Conversation/Chips/TypingIndicator.vue';
+import UnreadIndicator from 'next/Conversation/Chips/UnreadIndicator.vue';
import ConversationLabelSuggestion from './conversation/LabelSuggestion.vue';
import Banner from 'dashboard/components/ui/Banner.vue';
@@ -43,6 +44,7 @@ export default {
NextMessageList,
ReplyBox,
TypingIndicator,
+ UnreadIndicator,
Banner,
ConversationLabelSuggestion,
},
@@ -110,6 +112,9 @@ export default {
data() {
return {
isLoadingPrevious: true,
+ // this is only used to show a chip if the user has scrolled far enough
+ // we will hide this the moment the user reaches the start of the unread messages
+ showFloatingUnreadIndicator: false,
heightBeforeLoad: null,
conversationPanel: null,
hasUserScrolled: false,
@@ -230,9 +235,9 @@ export default {
this.unreadMessageCount > 9 ? '9+' : this.unreadMessageCount;
const label =
this.unreadMessageCount > 1
- ? 'CONVERSATION.UNREAD_MESSAGES'
- : 'CONVERSATION.UNREAD_MESSAGE';
- return `${count} ${this.$t(label)}`;
+ ? this.$t('CONVERSATION.UNREAD_MESSAGES')
+ : this.$t('CONVERSATION.UNREAD_MESSAGE');
+ return `${count} ${label}`;
},
isInstagramDM() {
return this.conversationType === 'instagram_direct_message';
@@ -344,7 +349,6 @@ export default {
this.scrollToBottomIfNotScrolled();
}
});
- this.makeMessagesRead();
},
addScrollListener() {
this.conversationPanel = this.$el.querySelector('.conversation-panel');
@@ -356,7 +360,7 @@ export default {
removeScrollListener() {
this.conversationPanel.removeEventListener('scroll', this.handleScroll);
},
- scrollToBottomIfNotScrolled() {
+ isNearBottom(offset = 200) {
// In case the user has already scrolled to a point in the message, we should
// not scroll to the bottom against the intent of the user.
const clientHeight = this.conversationPanel.clientHeight;
@@ -371,13 +375,20 @@ export default {
// if the user is at the bottom or close to the bottom, we can skip scrolling
// we add a 200px margin, this has enough space to accomodate new messages
// while also resetting position to the bottom if the user has scrolled but not significantly
- const isNearBottom = scrollTop > scrollHeight - clientHeight - 200;
+ return scrollTop > scrollHeight - clientHeight - offset;
+ },
+ scrollToBottomIfNotScrolled() {
+ const isNearBottom = this.isNearBottom();
- if (this.hasUserScrolled && !isNearBottom) return;
+ if (this.hasUserScrolled && !isNearBottom) {
+ this.showFloatingUnreadIndicator = true;
+ return;
+ }
this.scrollToBottom();
},
scrollToBottom() {
+ this.showFloatingUnreadIndicator = false;
this.isProgrammaticScroll = true;
let relevantMessages = [];
@@ -404,6 +415,10 @@ export default {
).slice(-1);
}
+ if (this.unreadMessageCount !== 0) {
+ this.makeMessagesRead();
+ }
+
this.conversationPanel.scrollTop = calculateScrollTop(
this.conversationPanel.scrollHeight,
this.$el.scrollHeight,
@@ -457,6 +472,14 @@ export default {
} else {
this.hasUserScrolled = true;
}
+
+ // in case the user has scrolled to the bottom manually
+ // we trigger the makeMessagesRead method if there are unread messages
+ // TODO: debounce this check to ensure this does not affect performance
+ if (this.isNearBottom(50) && this.unreadMessageCount !== 0) {
+ this.makeMessagesRead();
+ }
+
emitter.emit(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL);
this.fetchPreviousMessages(e.target.scrollTop);
},
@@ -464,6 +487,14 @@ export default {
makeMessagesRead() {
this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
},
+ // when the fixed unread badge is visible
+ // we trigger the makeMessagesRead method
+ onUnreadBadgeIntersect(visible) {
+ if (visible) {
+ this.showFloatingUnreadIndicator = false;
+ this.makeMessagesRead();
+ }
+ },
getInReplyToMessage(parentMessage) {
if (!parentMessage) return {};
const inReplyToMessageId = parentMessage.content_attributes?.in_reply_to;
@@ -520,9 +551,12 @@ export default {
-
- {{ unreadMessageLabel }}
-
+
@@ -598,6 +632,13 @@ export default {
class="flex items-center justify-center gap-1 absolute w-full -top-7 h-0"
>
+