mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
fix
This commit is contained in:
@@ -102,8 +102,8 @@
|
||||
"tab_friend": "Friend",
|
||||
"tab_group": "Group",
|
||||
"tab_other": "Other",
|
||||
"past_notifications": "Past",
|
||||
"no_new_notifications": "No new notifications"
|
||||
"past_notifications": "Last 24 hours",
|
||||
"no_new_notifications": "No new notifications in the last 24 hours"
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
|
||||
@@ -134,6 +134,34 @@ export const useNotificationStore = defineStore('Notification', () => {
|
||||
const unseenOtherNotifications = computed(() =>
|
||||
otherNotifications.value.filter((n) => unseenSet.value.has(n.id))
|
||||
);
|
||||
const recentCutoff = computed(() => dayjs().subtract(24, 'hour').valueOf());
|
||||
function getNotificationTs(n) {
|
||||
const raw = n.created_at ?? n.createdAt;
|
||||
if (typeof raw === 'number') return raw > 1e12 ? raw : raw * 1000;
|
||||
const ts = dayjs(raw).valueOf();
|
||||
return Number.isFinite(ts) ? ts : 0;
|
||||
}
|
||||
const recentFriendNotifications = computed(() =>
|
||||
friendNotifications.value.filter(
|
||||
(n) =>
|
||||
!unseenSet.value.has(n.id) &&
|
||||
getNotificationTs(n) > recentCutoff.value
|
||||
)
|
||||
);
|
||||
const recentGroupNotifications = computed(() =>
|
||||
groupNotifications.value.filter(
|
||||
(n) =>
|
||||
!unseenSet.value.has(n.id) &&
|
||||
getNotificationTs(n) > recentCutoff.value
|
||||
)
|
||||
);
|
||||
const recentOtherNotifications = computed(() =>
|
||||
otherNotifications.value.filter(
|
||||
(n) =>
|
||||
!unseenSet.value.has(n.id) &&
|
||||
getNotificationTs(n) > recentCutoff.value
|
||||
)
|
||||
);
|
||||
const hasUnseenNotifications = computed(
|
||||
() => unseenNotifications.value.length > 0
|
||||
);
|
||||
@@ -2716,6 +2744,9 @@ export const useNotificationStore = defineStore('Notification', () => {
|
||||
unseenFriendNotifications,
|
||||
unseenGroupNotifications,
|
||||
unseenOtherNotifications,
|
||||
recentFriendNotifications,
|
||||
recentGroupNotifications,
|
||||
recentOtherNotifications,
|
||||
hasUnseenNotifications,
|
||||
getNotificationCategory,
|
||||
isNotificationExpired,
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<TabsContent value="friend" class="mt-0 min-h-0 flex-1 overflow-hidden">
|
||||
<NotificationList
|
||||
:notifications="unseenFriendNotifications"
|
||||
:recent-notifications="recentFriendNotifications"
|
||||
@show-invite-response="showSendInviteResponseDialog"
|
||||
@show-invite-request-response="showSendInviteRequestResponseDialog"
|
||||
@navigate-to-table="navigateToTable" />
|
||||
@@ -37,6 +38,7 @@
|
||||
<TabsContent value="group" class="mt-0 min-h-0 flex-1 overflow-hidden">
|
||||
<NotificationList
|
||||
:notifications="unseenGroupNotifications"
|
||||
:recent-notifications="recentGroupNotifications"
|
||||
@show-invite-response="showSendInviteResponseDialog"
|
||||
@show-invite-request-response="showSendInviteRequestResponseDialog"
|
||||
@navigate-to-table="navigateToTable" />
|
||||
@@ -44,6 +46,7 @@
|
||||
<TabsContent value="other" class="mt-0 min-h-0 flex-1 overflow-hidden">
|
||||
<NotificationList
|
||||
:notifications="unseenOtherNotifications"
|
||||
:recent-notifications="recentOtherNotifications"
|
||||
@show-invite-response="showSendInviteResponseDialog"
|
||||
@show-invite-request-response="showSendInviteRequestResponseDialog"
|
||||
@navigate-to-table="navigateToTable" />
|
||||
@@ -78,8 +81,15 @@
|
||||
const { refreshInviteMessageTableData } = useInviteStore();
|
||||
const { clearInviteImageUpload } = useGalleryStore();
|
||||
|
||||
const { isNotificationCenterOpen, unseenFriendNotifications, unseenGroupNotifications, unseenOtherNotifications } =
|
||||
storeToRefs(useNotificationStore());
|
||||
const {
|
||||
isNotificationCenterOpen,
|
||||
unseenFriendNotifications,
|
||||
unseenGroupNotifications,
|
||||
unseenOtherNotifications,
|
||||
recentFriendNotifications,
|
||||
recentGroupNotifications,
|
||||
recentOtherNotifications
|
||||
} = storeToRefs(useNotificationStore());
|
||||
|
||||
const activeTab = ref('friend');
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</Avatar>
|
||||
</ItemMedia>
|
||||
<ItemContent class="min-w-0">
|
||||
<ItemTitle class="min-w-0">
|
||||
<ItemTitle class="min-w-0 w-full">
|
||||
<span class="truncate cursor-pointer" @click.stop="openSender">{{ senderName }}</span>
|
||||
<Badge variant="secondary" class="shrink-0 text-muted-foreground text-[10px]">
|
||||
{{ typeLabel }}
|
||||
@@ -19,16 +19,18 @@
|
||||
class="ml-auto size-2 shrink-0 rounded-full bg-blue-500" />
|
||||
</ItemTitle>
|
||||
<TooltipWrapper v-if="displayMessage" side="top" :content="displayMessage" :delay-duration="600">
|
||||
<ItemDescription class="text-xs select-none line-clamp-3">
|
||||
<ItemDescription class="text-xs select-none">
|
||||
{{ displayMessage }}
|
||||
</ItemDescription>
|
||||
</TooltipWrapper>
|
||||
</ItemContent>
|
||||
|
||||
<div class="flex shrink-0 flex-col items-end gap-1">
|
||||
<span class="text-[10px] text-muted-foreground whitespace-nowrap">
|
||||
{{ relativeTime }}
|
||||
</span>
|
||||
<TooltipWrapper v-if="relativeTime" side="top" :content="absoluteTime">
|
||||
<span class="text-[10px] text-muted-foreground whitespace-nowrap">
|
||||
{{ relativeTime }}
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
<div class="flex items-center gap-1">
|
||||
<template v-if="!isNotificationExpired(notification)">
|
||||
<TooltipWrapper
|
||||
@@ -160,15 +162,18 @@
|
||||
|
||||
const senderName = computed(() => {
|
||||
const n = props.notification;
|
||||
if (n.senderUsername && n.senderUsername?.Value === null) {
|
||||
return n.title || n.data?.groupName || n.groupName || n.details?.groupName || '';
|
||||
}
|
||||
return n.senderUsername || n.data?.groupName || n.groupName || n.details?.groupName || '';
|
||||
});
|
||||
|
||||
const avatarUrl = computed(() => {
|
||||
const n = props.notification;
|
||||
const userId = n.senderUserId;
|
||||
const userId = typeof n.senderUserId === 'string' ? n.senderUserId : '';
|
||||
|
||||
// Group notifications: use details.imageUrl or imageUrl
|
||||
if (userId?.startsWith('grp_') || n.type?.startsWith('group.') || n.type === 'groupChange') {
|
||||
if (userId.startsWith('grp_') || n.type?.startsWith('group.') || n.type === 'groupChange') {
|
||||
return n.details?.imageUrl || n.imageUrl || n.senderUserIcon || null;
|
||||
}
|
||||
|
||||
@@ -215,10 +220,16 @@
|
||||
return '';
|
||||
});
|
||||
|
||||
const createdAtValue = computed(() => props.notification.created_at || props.notification.createdAt);
|
||||
|
||||
const relativeTime = computed(() => {
|
||||
const createdAt = props.notification.created_at || props.notification.createdAt;
|
||||
if (!createdAt) return '';
|
||||
return dayjs(createdAt).fromNow(true);
|
||||
if (!createdAtValue.value) return '';
|
||||
return dayjs(createdAtValue.value).fromNow(true);
|
||||
});
|
||||
|
||||
const absoluteTime = computed(() => {
|
||||
if (!createdAtValue.value) return '';
|
||||
return dayjs(createdAtValue.value).format('YYYY-MM-DD HH:mm:ss');
|
||||
});
|
||||
|
||||
const showDecline = computed(() => {
|
||||
@@ -291,16 +302,24 @@
|
||||
}
|
||||
|
||||
function openSender() {
|
||||
const userId = props.notification.senderUserId;
|
||||
if (userId) {
|
||||
if (userId.startsWith('grp_')) {
|
||||
groupStore.showGroupDialog(userId);
|
||||
} else {
|
||||
userStore.showUserDialog(userId);
|
||||
const n = props.notification;
|
||||
const userId = typeof n.senderUserId === 'string' ? n.senderUserId : '';
|
||||
|
||||
// Group notifications: try to find a group ID
|
||||
if (userId.startsWith('grp_') || n.type?.startsWith('group.') || n.type === 'groupChange') {
|
||||
const groupId = userId.startsWith('grp_') ? userId : n.data?.groupId || n.details?.groupId || '';
|
||||
if (groupId) {
|
||||
groupStore.showGroupDialog(groupId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
userStore.showUserDialog(userId);
|
||||
return;
|
||||
}
|
||||
const link = props.notification.link;
|
||||
|
||||
const link = n.link;
|
||||
if (link) {
|
||||
openNotificationLink(link);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
<template>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div v-if="activeNotifications.length" class="flex flex-col gap-0.5 p-2">
|
||||
<div v-if="sortedUnseenNotifications.length" class="flex flex-col gap-0.5 p-2">
|
||||
<NotificationItem
|
||||
v-for="n in activeNotifications"
|
||||
:key="n.id || n.type + n.created_at"
|
||||
v-for="n in sortedUnseenNotifications"
|
||||
:key="n.id + n.created_at"
|
||||
:notification="n"
|
||||
:is-unseen="true"
|
||||
@show-invite-response="$emit('show-invite-response', $event)"
|
||||
@show-invite-request-response="$emit('show-invite-request-response', $event)" />
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center p-8 text-sm text-muted-foreground">
|
||||
|
||||
<div v-if="sortedRecentNotifications.length">
|
||||
<div class="flex items-center gap-2 px-4 py-2">
|
||||
<Separator class="flex-1" />
|
||||
<span class="shrink-0 text-[10px] text-muted-foreground uppercase tracking-wider">
|
||||
{{ t('side_panel.notification_center.past_notifications') }}
|
||||
</span>
|
||||
<Separator class="flex-1" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5 px-2 pb-2">
|
||||
<NotificationItem
|
||||
v-for="n in sortedRecentNotifications"
|
||||
:key="n.id + n.created_at"
|
||||
:notification="n"
|
||||
:is-unseen="false" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!sortedUnseenNotifications.length && !sortedRecentNotifications.length"
|
||||
class="flex items-center justify-center p-8 text-sm text-muted-foreground">
|
||||
{{ t('side_panel.notification_center.no_new_notifications') }}
|
||||
</div>
|
||||
|
||||
@@ -29,6 +49,7 @@
|
||||
|
||||
<script setup>
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -37,7 +58,8 @@
|
||||
import NotificationItem from './NotificationItem.vue';
|
||||
|
||||
const props = defineProps({
|
||||
notifications: { type: Array, required: true }
|
||||
notifications: { type: Array, required: true },
|
||||
recentNotifications: { type: Array, default: () => [] }
|
||||
});
|
||||
|
||||
defineEmits(['show-invite-response', 'show-invite-request-response', 'navigate-to-table']);
|
||||
@@ -46,12 +68,13 @@
|
||||
|
||||
function getTs(n) {
|
||||
const raw = n?.created_at ?? n?.createdAt;
|
||||
if (typeof raw === 'number') {
|
||||
return raw > 1_000_000_000_000 ? raw : raw * 1000;
|
||||
}
|
||||
const ts = dayjs(raw).valueOf();
|
||||
return Number.isFinite(ts) ? ts : 0;
|
||||
}
|
||||
|
||||
const activeNotifications = computed(() => [...props.notifications].sort((a, b) => getTs(b) - getTs(a)));
|
||||
const sortedUnseenNotifications = computed(() => [...props.notifications].sort((a, b) => getTs(b) - getTs(a)));
|
||||
|
||||
const sortedRecentNotifications = computed(() =>
|
||||
[...props.recentNotifications].sort((a, b) => getTs(b) - getTs(a))
|
||||
);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user