Merge branch 'develop' into fix/next-message-bubbles

This commit is contained in:
Sivin Varghese
2025-01-31 21:51:33 +05:30
committed by GitHub
8 changed files with 89 additions and 23 deletions
@@ -16,20 +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('.');
return parts.slice(0, -1).join('.') || fileName.value;
// 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(() => {
@@ -62,17 +83,15 @@ const textColorClass = computed(() => {
>
<FileIcon class="flex-shrink-0" :file-type="fileType" />
<span
class="flex-1 min-w-0 text-sm max-w-36"
:title="fileName"
:class="textColorClass"
class="inline-flex items-center text-sm overflow-hidden"
>
<span class="truncate min-w-6">
{{ fileNameWithoutExt }}
</span>
<span class="flex-shrink-0 whitespace-nowrap">.{{ fileType }}</span>
{{ displayFileName }}
</span>
<a
v-tooltip="t('CONVERSATION.DOWNLOAD')"
class="flex-shrink-0 h-9 grid place-content-center cursor-pointer text-n-slate-11"
class="flex-shrink-0 size-9 grid place-content-center cursor-pointer text-n-slate-11 hover:text-n-slate-12 transition-colors"
:href="attachment.dataUrl"
rel="noreferrer noopener nofollow"
target="_blank"
@@ -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',
@@ -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"
@@ -1,7 +1,8 @@
<script setup>
import { useMapGetter } from 'dashboard/composables/store.js';
import Icon from 'next/icon/Icon.vue';
defineProps({
const props = defineProps({
to: { type: [Object, String], default: '' },
label: { type: String, default: '' },
icon: { type: [String, Object], default: '' },
@@ -9,9 +10,12 @@ defineProps({
isExpanded: { type: Boolean, default: false },
isActive: { type: Boolean, default: false },
hasActiveChild: { type: Boolean, default: false },
getterKeys: { type: Object, default: () => ({}) },
});
const emit = defineEmits(['toggle']);
const showBadge = useMapGetter(props.getterKeys.badge);
</script>
<template>
@@ -28,7 +32,13 @@ const emit = defineEmits(['toggle']);
}"
@click.stop="emit('toggle')"
>
<Icon v-if="icon" :icon="icon" class="size-4" />
<div v-if="icon" class="relative flex items-center gap-2">
<Icon v-if="icon" :icon="icon" class="size-4" />
<span
v-if="showBadge"
class="size-2 -top-px ltr:-right-px rtl:-left-px bg-n-brand absolute rounded-full border border-n-solid-2"
/>
</div>
<span class="text-sm font-medium leading-5 flex-grow">
{{ label }}
</span>
@@ -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',
@@ -35,4 +35,7 @@ export const getters = {
getNotificationFilters($state) {
return $state.notificationFilters;
},
getHasUnreadNotifications: $state => {
return $state.meta.unreadCount > 0;
},
};
@@ -6,7 +6,7 @@ const state = {
meta: {
count: 0,
currentPage: 1,
unReadCount: 0,
unreadCount: 0,
},
records: {},
uiFlags: {
@@ -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);
});
});
});