From c3aab44b5f4be375235e25146f6cbc7ff9a3adad Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Fri, 31 Jan 2025 15:14:10 +0530
Subject: [PATCH 1/2] feat: Add unread badge to sidebar for inbox view (#10803)
# Pull Request Template
## Description
This PR adds a badge to the icon in the sidebar group header and an
unread highlight for the inbox view.
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
**Loom video**
https://www.loom.com/share/1a880aa16de249a1836b8d014325c067?sid=87cc7b64-667b-4066-8993-d33abccf7b67
## 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
- [ ] 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
---
.../components-next/sidebar/Sidebar.vue | 3 +++
.../components-next/sidebar/SidebarGroup.vue | 2 ++
.../sidebar/SidebarGroupHeader.vue | 14 +++++++++--
.../routes/dashboard/inbox/InboxList.vue | 18 ++++++++++-----
.../store/modules/notifications/getters.js | 3 +++
.../store/modules/notifications/index.js | 2 +-
.../specs/notifications/getters.spec.js | 23 +++++++++++++++++++
7 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
index dcf4e9740..ca5f8b17e 100644
--- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
+++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
@@ -151,6 +151,9 @@ const menuItems = computed(() => {
icon: 'i-lucide-inbox',
to: accountScopedRoute('inbox_view'),
activeOn: ['inbox_view', 'inbox_view_conversation'],
+ getterKeys: {
+ badge: 'notifications/getHasUnreadNotifications',
+ },
},
{
name: 'Conversation',
diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue b/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue
index 305171d10..64345c7de 100644
--- a/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue
+++ b/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue
@@ -15,6 +15,7 @@ const props = defineProps({
to: { type: Object, default: null },
activeOn: { type: Array, default: () => [] },
children: { type: Array, default: undefined },
+ getterKeys: { type: Object, default: () => ({}) },
});
const {
@@ -143,6 +144,7 @@ onMounted(async () => {
:name
:label
:to
+ :getter-keys="getterKeys"
:is-active="isActive"
:has-active-child="hasActiveChild"
:expandable="hasChildren"
diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue b/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue
index 160483a6a..1fd1786a0 100644
--- a/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue
+++ b/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue
@@ -1,7 +1,8 @@
@@ -28,7 +32,13 @@ const emit = defineEmits(['toggle']);
}"
@click.stop="emit('toggle')"
>
-
+
+
+
+
{{ label }}
diff --git a/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue b/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue
index 660147cf3..50771d538 100644
--- a/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue
+++ b/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue
@@ -121,6 +121,7 @@ export default {
})
.then(() => {
useAlert(this.$t('INBOX.ALERTS.MARK_AS_READ'));
+ this.$store.dispatch('notifications/unReadCount'); // to update the unread count in the store real time
});
},
markNotificationAsUnRead(notification) {
@@ -133,6 +134,7 @@ export default {
})
.then(() => {
useAlert(this.$t('INBOX.ALERTS.MARK_AS_UNREAD'));
+ this.$store.dispatch('notifications/unReadCount'); // to update the unread count in the store real time
});
},
deleteNotification(notification) {
@@ -186,12 +188,16 @@ export default {
notificationType,
});
- this.$store.dispatch('notifications/read', {
- id,
- primaryActorId,
- primaryActorType,
- unreadCount: this.meta.unreadCount,
- });
+ this.$store
+ .dispatch('notifications/read', {
+ id,
+ primaryActorId,
+ primaryActorType,
+ unreadCount: this.meta.unreadCount,
+ })
+ .then(() => {
+ this.$store.dispatch('notifications/unReadCount'); // to update the unread count in the store real time
+ });
this.$router.push({
name: 'inbox_view_conversation',
diff --git a/app/javascript/dashboard/store/modules/notifications/getters.js b/app/javascript/dashboard/store/modules/notifications/getters.js
index 18e78595c..4f4cafc9a 100644
--- a/app/javascript/dashboard/store/modules/notifications/getters.js
+++ b/app/javascript/dashboard/store/modules/notifications/getters.js
@@ -35,4 +35,7 @@ export const getters = {
getNotificationFilters($state) {
return $state.notificationFilters;
},
+ getHasUnreadNotifications: $state => {
+ return $state.meta.unreadCount > 0;
+ },
};
diff --git a/app/javascript/dashboard/store/modules/notifications/index.js b/app/javascript/dashboard/store/modules/notifications/index.js
index ca4f155ff..f4d45bec2 100644
--- a/app/javascript/dashboard/store/modules/notifications/index.js
+++ b/app/javascript/dashboard/store/modules/notifications/index.js
@@ -6,7 +6,7 @@ const state = {
meta: {
count: 0,
currentPage: 1,
- unReadCount: 0,
+ unreadCount: 0,
},
records: {},
uiFlags: {
diff --git a/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js b/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js
index 8721d1c70..580a3df00 100644
--- a/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js
@@ -95,4 +95,27 @@ describe('#getters', () => {
state.notificationFilters
);
});
+
+ describe('getHasUnreadNotifications', () => {
+ it('should return true when there are unread notifications', () => {
+ const state = {
+ meta: { unreadCount: 5 },
+ };
+ expect(getters.getHasUnreadNotifications(state)).toBe(true);
+ });
+
+ it('should return false when there are no unread notifications', () => {
+ const state = {
+ meta: { unreadCount: 0 },
+ };
+ expect(getters.getHasUnreadNotifications(state)).toBe(false);
+ });
+
+ it('should return false when meta is empty', () => {
+ const state = {
+ meta: {},
+ };
+ expect(getters.getHasUnreadNotifications(state)).toBe(false);
+ });
+ });
});
From 24257f977137c239598eedcec08e86104fdb191f Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Fri, 31 Jan 2025 18:28:28 +0530
Subject: [PATCH 2/2] chore: Always display the extension of an attached file
(#10806)
---
.../components-next/message/chips/File.vue | 50 +++++++++++++++----
1 file changed, 40 insertions(+), 10 deletions(-)
diff --git a/app/javascript/dashboard/components-next/message/chips/File.vue b/app/javascript/dashboard/components-next/message/chips/File.vue
index 07ca522cf..8638fd15d 100644
--- a/app/javascript/dashboard/components-next/message/chips/File.vue
+++ b/app/javascript/dashboard/components-next/message/chips/File.vue
@@ -16,15 +16,41 @@ const { t } = useI18n();
const fileName = computed(() => {
const url = attachment.dataUrl;
- if (url) {
- const filename = url.substring(url.lastIndexOf('/') + 1);
- return filename || t('CONVERSATION.UNKNOWN_FILE_TYPE');
+ if (!url) return t('CONVERSATION.UNKNOWN_FILE_TYPE');
+
+ try {
+ const encodedFilename = url.substring(url.lastIndexOf('/') + 1);
+ return decodeURIComponent(encodedFilename);
+ } catch {
+ return t('CONVERSATION.UNKNOWN_FILE_TYPE');
}
- return t('CONVERSATION.UNKNOWN_FILE_TYPE');
});
-const fileType = computed(() => {
- return fileName.value.split('.').pop();
+const fileType = computed(() => fileName.value.split('.').pop()?.toLowerCase());
+
+const fileNameWithoutExt = computed(() => {
+ const parts = fileName.value.split('.');
+
+ // If there's no extension (no dots in filename)
+ if (parts.length === 1) {
+ return fileName.value.trim();
+ }
+
+ // Take all parts except the last one (extension)
+ const nameWithoutExt = parts.slice(0, -1).join('.');
+ return nameWithoutExt.trim();
+});
+
+const displayFileName = computed(() => {
+ const name = fileNameWithoutExt.value;
+ const truncatedName = (str, maxLength, hasExt) =>
+ str.length > maxLength
+ ? `${str.substring(0, maxLength).trimEnd()}${hasExt ? '..' : '...'}`
+ : str;
+
+ return fileType.value
+ ? `${truncatedName(name, 14, true)}.${fileType.value}`
+ : truncatedName(name, 16, false);
});
const textColorClass = computed(() => {
@@ -53,15 +79,19 @@ const textColorClass = computed(() => {
-
- {{ fileName }}
+
+ {{ displayFileName }}