mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
notifications v2 table
This commit is contained in:
+66
-4
@@ -1,4 +1,5 @@
|
|||||||
import { useGalleryStore, useNotificationStore } from '../stores';
|
import { useGalleryStore, useNotificationStore } from '../stores';
|
||||||
|
import { notificationRequest } from '.';
|
||||||
import { request } from '../service/request';
|
import { request } from '../service/request';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,6 +9,10 @@ function getGalleryStore() {
|
|||||||
return useGalleryStore();
|
return useGalleryStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNotificationStore() {
|
||||||
|
return useNotificationStore();
|
||||||
|
}
|
||||||
|
|
||||||
const notificationReq = {
|
const notificationReq = {
|
||||||
/** @typedef {{
|
/** @typedef {{
|
||||||
* n: number,
|
* n: number,
|
||||||
@@ -201,14 +206,20 @@ const notificationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
useNotificationStore().handleNotificationAccept(args);
|
getNotificationStore().handleNotificationAccept(args);
|
||||||
return args;
|
return args;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// if friend request could not be found, delete it
|
// if friend request could not be found, delete it
|
||||||
if (err && err.message && err.message.includes('404')) {
|
if (err && err.message && err.message.includes('404')) {
|
||||||
useNotificationStore().handleNotificationHide({ params });
|
getNotificationStore().handleNotificationHide(
|
||||||
|
params.notificationId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
json: null,
|
||||||
|
params
|
||||||
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -227,7 +238,41 @@ const notificationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
useNotificationStore().handleNotificationHide(args);
|
getNotificationStore().handleNotificationHide(
|
||||||
|
params.notificationId
|
||||||
|
);
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ notificationId: string }} params
|
||||||
|
* @return { Promise<{json: any, params}> }
|
||||||
|
*/
|
||||||
|
seeNotification(params) {
|
||||||
|
return request(`auth/user/notifications/${params.notificationId}/see`, {
|
||||||
|
method: 'PUT'
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ notificationId: string }} params
|
||||||
|
* @return { Promise<{json: any, params}> }
|
||||||
|
*/
|
||||||
|
seeNotificationV2(params) {
|
||||||
|
return request(`notifications/${params.notificationId}/see`, {
|
||||||
|
method: 'POST'
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -244,7 +289,24 @@ const notificationReq = {
|
|||||||
return request(`notifications/${params.notificationId}/respond`, {
|
return request(`notifications/${params.notificationId}/respond`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
});
|
})
|
||||||
|
.then((json) => {
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return args;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
getNotificationStore().handleNotificationV2Hide(
|
||||||
|
params.notificationId
|
||||||
|
);
|
||||||
|
notificationRequest.hideNotificationV2(params.notificationId);
|
||||||
|
return {
|
||||||
|
json: null,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
hideNotificationV2(notificationId) {
|
hideNotificationV2(notificationId) {
|
||||||
|
|||||||
@@ -1,43 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
:open="changeAvatarImageDialogVisible"
|
:open="changeAvatarImageDialogVisible"
|
||||||
@update:open="(open) => {
|
@update:open="
|
||||||
if (!open) closeDialog();
|
(open) => {
|
||||||
}">
|
if (!open) closeDialog();
|
||||||
|
}
|
||||||
|
">
|
||||||
<DialogContent class="x-dialog sm:max-w-212.5">
|
<DialogContent class="x-dialog sm:max-w-212.5">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{{ t('dialog.change_content_image.avatar') }}</DialogTitle>
|
<DialogTitle>{{ t('dialog.change_content_image.avatar') }}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
id="AvatarImageUploadButton"
|
id="AvatarImageUploadButton"
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
style="display: none"
|
style="display: none"
|
||||||
@change="onFileChangeAvatarImage" />
|
@change="onFileChangeAvatarImage" />
|
||||||
<span>{{ t('dialog.change_content_image.description') }}</span>
|
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||||
<br />
|
<br />
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon-sm"
|
size="sm"
|
||||||
:disabled="changeAvatarImageDialogLoading"
|
:disabled="changeAvatarImageDialogLoading"
|
||||||
@click="uploadAvatarImage">
|
@click="uploadAvatarImage">
|
||||||
<Upload />
|
<Upload />
|
||||||
{{ t('dialog.change_content_image.upload') }}
|
{{ t('dialog.change_content_image.upload') }}
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<div class="inline-block p-1 pb-0 hover:rounded-sm">
|
<div class="inline-block p-1 pb-0 hover:rounded-sm">
|
||||||
<img :src="previousImageUrl" class="img-size" loading="lazy" />
|
<img :src="previousImageUrl" class="img-size" loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import { Upload } from 'lucide-vue-next';
|
import { Upload } from 'lucide-vue-next';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
const { showGalleryPage, refreshEmojiTable } = useGalleryStore();
|
const { showGalleryPage, refreshEmojiTable } = useGalleryStore();
|
||||||
const { emojiTable } = storeToRefs(useGalleryStore());
|
const { emojiTable } = storeToRefs(useGalleryStore());
|
||||||
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
||||||
|
const { isNotificationExpired, handleNotificationV2Hide } = useNotificationStore();
|
||||||
|
|
||||||
const fileId = ref('');
|
const fileId = ref('');
|
||||||
const displayName = ref('');
|
const displayName = ref('');
|
||||||
@@ -161,14 +162,12 @@
|
|||||||
const array = notificationTable.value.data;
|
const array = notificationTable.value.data;
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
for (let i = array.length - 1; i >= 0; i--) {
|
||||||
const ref = array[i];
|
const ref = array[i];
|
||||||
if (ref.type !== 'boop' || ref.$isExpired || ref.senderUserId !== userId) {
|
if (ref.type !== 'boop' || isNotificationExpired(ref) || ref.link !== `user:${userId}`) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
notificationRequest.sendNotificationResponse({
|
console.log('Dismissing boop notification with id', ref.id);
|
||||||
notificationId: ref.id,
|
handleNotificationV2Hide(ref.id);
|
||||||
responseType: 'delete',
|
notificationRequest.hideNotificationV2(ref.id);
|
||||||
responseData: ''
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
"tab_group": "Group",
|
"tab_group": "Group",
|
||||||
"tab_other": "Other",
|
"tab_other": "Other",
|
||||||
"past_notifications": "Past",
|
"past_notifications": "Past",
|
||||||
"no_notifications": "No notifications"
|
"no_new_notifications": "No new notifications"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
@@ -334,7 +334,8 @@
|
|||||||
},
|
},
|
||||||
"report": {
|
"report": {
|
||||||
"closed": "Moderation Report Closed"
|
"closed": "Moderation Report Closed"
|
||||||
}
|
},
|
||||||
|
"contentrestriction": "Moderation Content Restriction"
|
||||||
},
|
},
|
||||||
"instance": {
|
"instance": {
|
||||||
"closed": "Instance Closed"
|
"closed": "Instance Closed"
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ const database = {
|
|||||||
await sqliteService.executeNonQuery(
|
await sqliteService.executeNonQuery(
|
||||||
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_notifications (id TEXT PRIMARY KEY, created_at TEXT, type TEXT, sender_user_id TEXT, sender_username TEXT, receiver_user_id TEXT, message TEXT, world_id TEXT, world_name TEXT, image_url TEXT, invite_message TEXT, request_message TEXT, response_message TEXT, expired INTEGER)`
|
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_notifications (id TEXT PRIMARY KEY, created_at TEXT, type TEXT, sender_user_id TEXT, sender_username TEXT, receiver_user_id TEXT, message TEXT, world_id TEXT, world_name TEXT, image_url TEXT, invite_message TEXT, request_message TEXT, response_message TEXT, expired INTEGER)`
|
||||||
);
|
);
|
||||||
|
await sqliteService.executeNonQuery(
|
||||||
|
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_notifications_v2 (id TEXT PRIMARY KEY, created_at TEXT, updated_at TEXT, expires_at TEXT, type TEXT, link TEXT, link_text TEXT, message TEXT, title TEXT, image_url TEXT, seen INTEGER, sender_user_id TEXT, sender_username TEXT, data TEXT, responses TEXT, details TEXT)`
|
||||||
|
);
|
||||||
await sqliteService.executeNonQuery(
|
await sqliteService.executeNonQuery(
|
||||||
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_moderation (user_id TEXT PRIMARY KEY, updated_at TEXT, display_name TEXT, block INTEGER, mute INTEGER)`
|
`CREATE TABLE IF NOT EXISTS ${dbVars.userPrefix}_moderation (user_id TEXT PRIMARY KEY, updated_at TEXT, display_name TEXT, block INTEGER, mute INTEGER)`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -151,6 +151,89 @@ const notifications = {
|
|||||||
'@expired': expired
|
'@expired': expired
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// notifications v2
|
||||||
|
|
||||||
|
async getNotificationsV2() {
|
||||||
|
const notifications = [];
|
||||||
|
await sqliteService.execute((dbRow) => {
|
||||||
|
const row = {
|
||||||
|
id: dbRow[0],
|
||||||
|
createdAt: dbRow[1],
|
||||||
|
updatedAt: dbRow[2],
|
||||||
|
expiresAt: dbRow[3],
|
||||||
|
type: dbRow[4],
|
||||||
|
link: dbRow[5],
|
||||||
|
linkText: dbRow[6],
|
||||||
|
message: dbRow[7],
|
||||||
|
title: dbRow[8],
|
||||||
|
imageUrl: dbRow[9],
|
||||||
|
seen: dbRow[10] === 1,
|
||||||
|
senderUserId: dbRow[11],
|
||||||
|
senderUsername: dbRow[12],
|
||||||
|
data: JSON.parse(dbRow[13] || '{}'),
|
||||||
|
responses: JSON.parse(dbRow[14] || '[]'),
|
||||||
|
details: JSON.parse(dbRow[15] || '{}')
|
||||||
|
};
|
||||||
|
// for UI table
|
||||||
|
row.created_at = row.createdAt;
|
||||||
|
row.version = 2;
|
||||||
|
notifications.unshift(row);
|
||||||
|
}, `SELECT * FROM ${dbVars.userPrefix}_notifications_v2 ORDER BY created_at DESC LIMIT ${dbVars.maxTableSize}`);
|
||||||
|
return notifications;
|
||||||
|
},
|
||||||
|
|
||||||
|
addNotificationV2ToDatabase(entry) {
|
||||||
|
sqliteService.executeNonQuery(
|
||||||
|
`INSERT OR REPLACE INTO ${dbVars.userPrefix}_notifications_v2 (id, created_at, updated_at, expires_at, type, link, link_text, message, title, image_url, seen, sender_user_id, sender_username, data, responses, details) VALUES (@id, @created_at, @updated_at, @expires_at, @type, @link, @link_text, @message, @title, @image_url, @seen, @sender_user_id, @sender_username, @data, @responses, @details)`,
|
||||||
|
{
|
||||||
|
'@id': entry.id,
|
||||||
|
'@created_at': entry.createdAt,
|
||||||
|
'@updated_at': entry.updatedAt,
|
||||||
|
'@expires_at': entry.expiresAt,
|
||||||
|
'@type': entry.type,
|
||||||
|
'@link': entry.link,
|
||||||
|
'@link_text': entry.linkText,
|
||||||
|
'@message': entry.message,
|
||||||
|
'@title': entry.title,
|
||||||
|
'@image_url': entry.imageUrl,
|
||||||
|
'@seen': entry.seen ? 1 : 0,
|
||||||
|
'@sender_user_id': entry.senderUserId,
|
||||||
|
'@sender_username': entry.senderUsername,
|
||||||
|
'@data': JSON.stringify(entry.data || {}),
|
||||||
|
'@responses': JSON.stringify(entry.responses || []),
|
||||||
|
'@details': JSON.stringify(entry.details || {})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
expireNotificationV2(id) {
|
||||||
|
sqliteService.executeNonQuery(
|
||||||
|
`UPDATE ${dbVars.userPrefix}_notifications_v2 SET expires_at = @expires_at, seen = 1 WHERE id = @id`,
|
||||||
|
{
|
||||||
|
'@id': id,
|
||||||
|
'@expires_at': new Date().toJSON()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
seenNotificationV2(id) {
|
||||||
|
sqliteService.executeNonQuery(
|
||||||
|
`UPDATE ${dbVars.userPrefix}_notifications_v2 SET seen = 1 WHERE id = @id`,
|
||||||
|
{
|
||||||
|
'@id': id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteNotificationV2(id) {
|
||||||
|
sqliteService.executeNonQuery(
|
||||||
|
`DELETE FROM ${dbVars.userPrefix}_notifications_v2 WHERE id = @id`,
|
||||||
|
{
|
||||||
|
'@id': id
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,13 @@ export function request(endpoint, options) {
|
|||||||
if (AppDebug.debugWebRequests) {
|
if (AppDebug.debugWebRequests) {
|
||||||
console.log(init, 'parsed data', response.data);
|
console.log(init, 'parsed data', response.data);
|
||||||
}
|
}
|
||||||
|
if (response.data.error) {
|
||||||
|
$throw(
|
||||||
|
response.data.error.status_code || 0,
|
||||||
|
response.data.error.message,
|
||||||
|
endpoint
|
||||||
|
);
|
||||||
|
}
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -215,16 +215,8 @@ function handlePipeline(args) {
|
|||||||
case 'notification-v2-delete':
|
case 'notification-v2-delete':
|
||||||
console.log('notification-v2-delete', content);
|
console.log('notification-v2-delete', content);
|
||||||
for (var id of content.ids) {
|
for (var id of content.ids) {
|
||||||
notificationStore.handleNotificationHide({
|
notificationStore.handleNotificationV2Hide(id);
|
||||||
params: {
|
notificationStore.handleNotificationSee(id);
|
||||||
notificationId: id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
notificationStore.handleNotificationSee({
|
|
||||||
params: {
|
|
||||||
notificationId: id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -239,37 +231,17 @@ function handlePipeline(args) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'see-notification':
|
case 'see-notification':
|
||||||
notificationStore.handleNotificationSee({
|
notificationStore.handleNotificationSee(content);
|
||||||
params: {
|
|
||||||
notificationId: content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'hide-notification':
|
case 'hide-notification':
|
||||||
notificationStore.handleNotificationHide({
|
notificationStore.handleNotificationHide(content);
|
||||||
params: {
|
notificationStore.handleNotificationSee(content);
|
||||||
notificationId: content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
notificationStore.handleNotificationSee({
|
|
||||||
params: {
|
|
||||||
notificationId: content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'response-notification':
|
case 'response-notification':
|
||||||
notificationStore.handleNotificationHide({
|
notificationStore.handleNotificationHide(content.notificationId);
|
||||||
params: {
|
notificationStore.handleNotificationSee(content.notificationId);
|
||||||
notificationId: content.notificationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
notificationStore.handleNotificationSee({
|
|
||||||
params: {
|
|
||||||
notificationId: content.notificationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'friend-add':
|
case 'friend-add':
|
||||||
|
|||||||
+194
-99
@@ -4,6 +4,7 @@ import { toast } from 'vue-sonner';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import Noty from 'noty';
|
import Noty from 'noty';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
checkCanInvite,
|
checkCanInvite,
|
||||||
@@ -23,8 +24,8 @@ import {
|
|||||||
userRequest,
|
userRequest,
|
||||||
worldRequest
|
worldRequest
|
||||||
} from '../api';
|
} from '../api';
|
||||||
|
import { database, dbVars } from '../service/database';
|
||||||
import { AppDebug } from '../service/appConfig';
|
import { AppDebug } from '../service/appConfig';
|
||||||
import { database } from '../service/database';
|
|
||||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||||
import { useAppearanceSettingsStore } from './settings/appearance';
|
import { useAppearanceSettingsStore } from './settings/appearance';
|
||||||
import { useFavoriteStore } from './favorite';
|
import { useFavoriteStore } from './favorite';
|
||||||
@@ -155,6 +156,11 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
const { ref } = args;
|
const { ref } = args;
|
||||||
const array = notificationTable.value.data;
|
const array = notificationTable.value.data;
|
||||||
const { length } = array;
|
const { length } = array;
|
||||||
|
if (ref.seen) {
|
||||||
|
removeFromArray(unseenNotifications.value, ref.id);
|
||||||
|
} else if (!unseenNotifications.value.includes(ref.id)) {
|
||||||
|
unseenNotifications.value.push(ref.id);
|
||||||
|
}
|
||||||
for (let i = 0; i < length; ++i) {
|
for (let i = 0; i < length; ++i) {
|
||||||
if (array[i].id === ref.id) {
|
if (array[i].id === ref.id) {
|
||||||
array[i] = ref;
|
array[i] = ref;
|
||||||
@@ -189,7 +195,6 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
) {
|
) {
|
||||||
uiStore.notifyMenu('notification');
|
uiStore.notifyMenu('notification');
|
||||||
}
|
}
|
||||||
unseenNotifications.value.push(ref.id);
|
|
||||||
queueNotificationNoty(ref);
|
queueNotificationNoty(ref);
|
||||||
sharedFeedStore.addEntry(ref);
|
sharedFeedStore.addEntry(ref);
|
||||||
}
|
}
|
||||||
@@ -206,30 +211,19 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
D.incomingRequest = true;
|
D.incomingRequest = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNotificationHide(args) {
|
function handleNotificationHide(notificationId) {
|
||||||
let ref;
|
const ref = notificationTable.value.data.find(
|
||||||
const array = notificationTable.value.data;
|
(n) => n.id === notificationId
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
);
|
||||||
if (array[i].id === args.params.notificationId) {
|
|
||||||
ref = array[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof ref === 'undefined') {
|
if (typeof ref === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
args.ref = ref;
|
|
||||||
if (
|
if (
|
||||||
ref.type === 'friendRequest' ||
|
ref.type === 'friendRequest' ||
|
||||||
ref.type === 'ignoredFriendRequest' ||
|
ref.type === 'ignoredFriendRequest' ||
|
||||||
ref.type.includes('.')
|
ref.type.includes('.')
|
||||||
) {
|
) {
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
removeFromArray(notificationTable.value.data, ref);
|
||||||
if (array[i].id === ref.id) {
|
|
||||||
array.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ref.$isExpired = true;
|
ref.$isExpired = true;
|
||||||
database.updateNotificationExpired(ref);
|
database.updateNotificationExpired(ref);
|
||||||
@@ -242,28 +236,6 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNotificationV2Update(args) {
|
|
||||||
const notificationId = args.params.notificationId;
|
|
||||||
const json = args.json;
|
|
||||||
if (!json) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
json.id = notificationId;
|
|
||||||
handleNotification({
|
|
||||||
json,
|
|
||||||
params: {
|
|
||||||
notificationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (json.seen) {
|
|
||||||
handleNotificationSee({
|
|
||||||
params: {
|
|
||||||
notificationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePipelineNotification(args) {
|
function handlePipelineNotification(args) {
|
||||||
const ref = args.json;
|
const ref = args.json;
|
||||||
if (
|
if (
|
||||||
@@ -374,12 +346,18 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNotificationSee(args) {
|
function handleNotificationSee(notificationId) {
|
||||||
const { notificationId } = args.params;
|
|
||||||
removeFromArray(unseenNotifications.value, notificationId);
|
removeFromArray(unseenNotifications.value, notificationId);
|
||||||
if (unseenNotifications.value.length === 0) {
|
if (unseenNotifications.value.length === 0) {
|
||||||
uiStore.removeNotify('notification');
|
uiStore.removeNotify('notification');
|
||||||
}
|
}
|
||||||
|
const ref = notificationTable.value.data.find(
|
||||||
|
(n) => n.id === notificationId
|
||||||
|
);
|
||||||
|
if (ref) {
|
||||||
|
ref.seen = true;
|
||||||
|
}
|
||||||
|
database.seenNotificationV2(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNotificationAccept(args) {
|
function handleNotificationAccept(args) {
|
||||||
@@ -435,10 +413,11 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} json
|
* @param {object} data
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
function applyNotification(json) {
|
function applyNotification(data) {
|
||||||
|
const json = { ...data };
|
||||||
if (json.message) {
|
if (json.message) {
|
||||||
json.message = replaceBioSymbols(json.message);
|
json.message = replaceBioSymbols(json.message);
|
||||||
}
|
}
|
||||||
@@ -489,25 +468,117 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
}
|
}
|
||||||
ref.details = details;
|
ref.details = details;
|
||||||
}
|
}
|
||||||
if (ref.type === 'boop') {
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyNotificationV2(data) {
|
||||||
|
const json = { ...data };
|
||||||
|
// delete any null in json
|
||||||
|
for (const key in json) {
|
||||||
|
if (json[key] === null || typeof json[key] === 'undefined') {
|
||||||
|
delete json[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ref = notificationTable.value.data.find((n) => n.id === json.id);
|
||||||
|
if (typeof ref === 'undefined') {
|
||||||
|
ref = {
|
||||||
|
id: '',
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
expiresAt: '',
|
||||||
|
type: '',
|
||||||
|
link: '',
|
||||||
|
linkText: '',
|
||||||
|
message: '',
|
||||||
|
title: '',
|
||||||
|
imageUrl: '',
|
||||||
|
seen: false,
|
||||||
|
data: {},
|
||||||
|
responses: [],
|
||||||
|
version: 2,
|
||||||
|
...json
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
Object.assign(ref, json);
|
||||||
|
}
|
||||||
|
ref.created_at = ref.createdAt; // for table
|
||||||
|
// legacy handling of boops
|
||||||
|
if (ref.type === 'boop' && ref.title) {
|
||||||
ref.message = ref.title;
|
ref.message = ref.title;
|
||||||
|
ref.title = '';
|
||||||
if (ref.details?.emojiId?.startsWith('default_')) {
|
if (ref.details?.emojiId?.startsWith('default_')) {
|
||||||
ref.details.imageUrl = ref.details.emojiId;
|
ref.imageUrl = ref.details.emojiId;
|
||||||
ref.message += ` ${ref.details.emojiId.replace('default_', '')}`;
|
ref.message += ` ${ref.details.emojiId.replace('default_', '')}`;
|
||||||
} else {
|
} else {
|
||||||
ref.details.imageUrl = `${AppDebug.endpointDomain}/file/${ref.details.emojiId}/${ref.details.emojiVersion}`;
|
ref.imageUrl = `${AppDebug.endpointDomain}/file/${ref.details.emojiId}/${ref.details.emojiVersion}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleNotificationV2(args) {
|
||||||
|
const ref = applyNotificationV2(args.json);
|
||||||
|
if (ref.seen) {
|
||||||
|
removeFromArray(unseenNotifications.value, ref.id);
|
||||||
|
} else if (!unseenNotifications.value.includes(ref.id)) {
|
||||||
|
unseenNotifications.value.push(ref.id);
|
||||||
|
}
|
||||||
|
const existingNotification = notificationTable.value.data.find(
|
||||||
|
(n) => n.id === ref.id
|
||||||
|
);
|
||||||
|
if (existingNotification) {
|
||||||
|
Object.assign(existingNotification, ref);
|
||||||
|
database.addNotificationV2ToDatabase(existingNotification); // update
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
notificationTable.value.filters[0].value.length === 0 ||
|
||||||
|
notificationTable.value.filters[0].value.includes(ref.type)
|
||||||
|
) {
|
||||||
|
uiStore.notifyMenu('notification');
|
||||||
|
}
|
||||||
|
database.addNotificationV2ToDatabase(ref);
|
||||||
|
notificationTable.value.data.push(ref);
|
||||||
|
queueNotificationNoty(ref);
|
||||||
|
sharedFeedStore.addEntry(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNotificationV2Update(args) {
|
||||||
|
const notificationId = args.params.notificationId;
|
||||||
|
const json = { ...args.json };
|
||||||
|
if (!json) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
json.id = notificationId;
|
||||||
|
handleNotificationV2({
|
||||||
|
json,
|
||||||
|
params: {
|
||||||
|
notificationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (json.seen) {
|
||||||
|
handleNotificationSee(notificationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNotificationV2Hide(notificationId) {
|
||||||
|
database.expireNotificationV2(notificationId);
|
||||||
|
const ref = notificationTable.value.data.find(
|
||||||
|
(n) => n.id === notificationId
|
||||||
|
);
|
||||||
|
if (ref) {
|
||||||
|
ref.expiresAt = new Date().toJSON();
|
||||||
|
ref.seen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function expireFriendRequestNotifications() {
|
function expireFriendRequestNotifications() {
|
||||||
const array = notificationTable.value.data;
|
const array = notificationTable.value.data;
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
for (let i = array.length - 1; i >= 0; i--) {
|
||||||
if (
|
if (
|
||||||
array[i].type === 'friendRequest' ||
|
array[i].type === 'friendRequest' ||
|
||||||
array[i].type === 'ignoredFriendRequest' ||
|
array[i].type === 'ignoredFriendRequest'
|
||||||
array[i].type.includes('.')
|
|
||||||
) {
|
) {
|
||||||
array.splice(i, 1);
|
array.splice(i, 1);
|
||||||
}
|
}
|
||||||
@@ -540,22 +611,6 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNotificationV2(args) {
|
|
||||||
const json = args.json;
|
|
||||||
json.created_at = json.createdAt;
|
|
||||||
if (json.title && json.message) {
|
|
||||||
json.message = `${json.title}, ${json.message}`;
|
|
||||||
} else if (json.title) {
|
|
||||||
json.message = json.title;
|
|
||||||
}
|
|
||||||
handleNotification({
|
|
||||||
json,
|
|
||||||
params: {
|
|
||||||
notificationId: json.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
@@ -581,7 +636,6 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
unseenNotifications.value = [];
|
|
||||||
params.offset += 100;
|
params.offset += 100;
|
||||||
if (args.json.length < 100) {
|
if (args.json.length < 100) {
|
||||||
break;
|
break;
|
||||||
@@ -595,23 +649,14 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const args =
|
const args =
|
||||||
await notificationRequest.getNotificationsV2(params);
|
await notificationRequest.getNotificationsV2(params);
|
||||||
|
|
||||||
for (const json of args.json) {
|
for (const json of args.json) {
|
||||||
json.created_at = json.createdAt;
|
handleNotificationV2({
|
||||||
if (json.title && json.message) {
|
|
||||||
json.message = `${json.title}, ${json.message}`;
|
|
||||||
} else if (json.title) {
|
|
||||||
json.message = json.title;
|
|
||||||
}
|
|
||||||
handleNotification({
|
|
||||||
json,
|
json,
|
||||||
params: {
|
params: {
|
||||||
notificationId: json.id
|
notificationId: json.id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unseenNotifications.value = [];
|
|
||||||
params.offset += 100;
|
params.offset += 100;
|
||||||
if (args.json.length < 100) {
|
if (args.json.length < 100) {
|
||||||
break;
|
break;
|
||||||
@@ -634,7 +679,6 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
unseenNotifications.value = [];
|
|
||||||
params.offset += 100;
|
params.offset += 100;
|
||||||
if (args.json.length < 100) {
|
if (args.json.length < 100) {
|
||||||
break;
|
break;
|
||||||
@@ -2412,7 +2456,16 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
|
|
||||||
async function initNotifications() {
|
async function initNotifications() {
|
||||||
notificationInitStatus.value = false;
|
notificationInitStatus.value = false;
|
||||||
notificationTable.value.data = await database.getNotifications();
|
let tableData = await database.getNotificationsV2();
|
||||||
|
let notifications = await database.getNotifications();
|
||||||
|
tableData = tableData.concat(
|
||||||
|
notifications.filter((n) => !tableData.some((t) => t.id === n.id))
|
||||||
|
);
|
||||||
|
tableData.sort(
|
||||||
|
(a, b) => Date.parse(b.created_at) - Date.parse(a.created_at)
|
||||||
|
);
|
||||||
|
tableData.splice(dbVars.maxTableSize);
|
||||||
|
notificationTable.value.data = tableData;
|
||||||
refreshNotifications();
|
refreshNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2441,11 +2494,11 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
|
|
||||||
async function hideNotification(row) {
|
async function hideNotification(row) {
|
||||||
if (row.type === 'ignoredFriendRequest') {
|
if (row.type === 'ignoredFriendRequest') {
|
||||||
const args = await friendRequest.deleteHiddenFriendRequest(
|
await friendRequest.deleteHiddenFriendRequest(
|
||||||
{ notificationId: row.id },
|
{ notificationId: row.id },
|
||||||
row.senderUserId
|
row.senderUserId
|
||||||
);
|
);
|
||||||
handleNotificationHide(args);
|
handleNotificationHide(row.id);
|
||||||
} else {
|
} else {
|
||||||
notificationRequest.hideNotification({
|
notificationRequest.hideNotification({
|
||||||
notificationId: row.id
|
notificationId: row.id
|
||||||
@@ -2516,23 +2569,15 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const params = { notificationId, responseType, responseData };
|
const params = { notificationId, responseType, responseData };
|
||||||
notificationRequest
|
notificationRequest.sendNotificationResponse(params).then((args) => {
|
||||||
.sendNotificationResponse(params)
|
console.log('Notification response', args);
|
||||||
.then((json) => {
|
if (!args.json) return;
|
||||||
if (!json) return;
|
handleNotificationV2Hide(notificationId);
|
||||||
const args = { json, params };
|
new Noty({
|
||||||
handleNotificationHide(args);
|
type: 'success',
|
||||||
new Noty({
|
text: escapeTag(args.json)
|
||||||
type: 'success',
|
}).show();
|
||||||
text: escapeTag(args.json)
|
});
|
||||||
}).show();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
handleNotificationHide({ params });
|
|
||||||
notificationRequest.hideNotificationV2(params.notificationId);
|
|
||||||
console.error('Notification response failed', err);
|
|
||||||
toast.error(t('message.error'));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteNotificationLog(row) {
|
function deleteNotificationLog(row) {
|
||||||
@@ -2546,7 +2591,11 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
row.type !== 'friendRequest' &&
|
row.type !== 'friendRequest' &&
|
||||||
row.type !== 'ignoredFriendRequest'
|
row.type !== 'ignoredFriendRequest'
|
||||||
) {
|
) {
|
||||||
database.deleteNotification(row.id);
|
if (!row.version || row.version < 2) {
|
||||||
|
database.deleteNotification(row.id);
|
||||||
|
} else {
|
||||||
|
database.deleteNotificationV2(row.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2562,6 +2611,49 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNotificationExpired(notification) {
|
||||||
|
if (notification.$isExpired !== undefined) {
|
||||||
|
return notification.$isExpired;
|
||||||
|
}
|
||||||
|
if (!notification.expiresAt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const expiresAt = dayjs(notification.expiresAt);
|
||||||
|
return expiresAt.isValid() && dayjs().isSameOrAfter(expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openNotificationLink(link) {
|
||||||
|
if (!link) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = link.split(':');
|
||||||
|
if (!data.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (data[0]) {
|
||||||
|
case 'group':
|
||||||
|
groupStore.showGroupDialog(data[1]);
|
||||||
|
break;
|
||||||
|
case 'user':
|
||||||
|
userStore.showUserDialog(data[1]);
|
||||||
|
break;
|
||||||
|
case 'event':
|
||||||
|
const ids = data[1].split(',');
|
||||||
|
if (ids.length < 2) {
|
||||||
|
console.error('Invalid event notification link:', data[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupStore.showGroupDialog(ids[0]);
|
||||||
|
// ids[1] cal_ is the event id
|
||||||
|
break;
|
||||||
|
case 'openNotificationLink':
|
||||||
|
default:
|
||||||
|
toast.error('Unsupported notification link type');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
notificationInitStatus,
|
notificationInitStatus,
|
||||||
notificationTable,
|
notificationTable,
|
||||||
@@ -2582,6 +2674,7 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
handlePipelineNotification,
|
handlePipelineNotification,
|
||||||
handleNotificationV2Update,
|
handleNotificationV2Update,
|
||||||
handleNotificationHide,
|
handleNotificationHide,
|
||||||
|
handleNotificationV2Hide,
|
||||||
handleNotification,
|
handleNotification,
|
||||||
handleNotificationV2,
|
handleNotificationV2,
|
||||||
testNotification,
|
testNotification,
|
||||||
@@ -2600,6 +2693,8 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
groupNotifications,
|
groupNotifications,
|
||||||
otherNotifications,
|
otherNotifications,
|
||||||
hasUnseenNotifications,
|
hasUnseenNotifications,
|
||||||
getNotificationCategory
|
getNotificationCategory,
|
||||||
|
isNotificationExpired,
|
||||||
|
openNotificationLink
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ export const useSharedFeedStore = defineStore('SharedFeed', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => watchState.isLoggedIn,
|
() => [watchState.isFriendsLoaded, watchState.isFavoritesLoaded],
|
||||||
(isLoggedIn) => {
|
([isFriendsLoaded, isFavoritesLoaded]) => {
|
||||||
if (isLoggedIn) {
|
if (isFriendsLoaded && isFavoritesLoaded) {
|
||||||
sharedFeedData.value = [];
|
sharedFeedData.value = [];
|
||||||
loadSharedFeed();
|
loadSharedFeed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@
|
|||||||
'group.queueReady',
|
'group.queueReady',
|
||||||
'moderation.warning.group',
|
'moderation.warning.group',
|
||||||
'moderation.report.closed',
|
'moderation.report.closed',
|
||||||
'instance.closed'
|
'moderation.contentrestriction',
|
||||||
|
'instance.closed',
|
||||||
|
'economy.alert'
|
||||||
]"
|
]"
|
||||||
:key="type"
|
:key="type"
|
||||||
:value="type">
|
:value="type">
|
||||||
@@ -89,7 +91,6 @@
|
|||||||
import { RefreshCw } from 'lucide-vue-next';
|
import { RefreshCw } from 'lucide-vue-next';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { toast } from 'vue-sonner';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -97,10 +98,8 @@
|
|||||||
import {
|
import {
|
||||||
useAppearanceSettingsStore,
|
useAppearanceSettingsStore,
|
||||||
useGalleryStore,
|
useGalleryStore,
|
||||||
useGroupStore,
|
|
||||||
useInviteStore,
|
useInviteStore,
|
||||||
useNotificationStore,
|
useNotificationStore,
|
||||||
useUserStore,
|
|
||||||
useVrcxStore
|
useVrcxStore
|
||||||
} from '../../stores';
|
} from '../../stores';
|
||||||
import { DataTableLayout } from '../../components/ui/data-table';
|
import { DataTableLayout } from '../../components/ui/data-table';
|
||||||
@@ -113,8 +112,6 @@
|
|||||||
import SendInviteResponseDialog from './dialogs/SendInviteResponseDialog.vue';
|
import SendInviteResponseDialog from './dialogs/SendInviteResponseDialog.vue';
|
||||||
import configRepository from '../../service/config';
|
import configRepository from '../../service/config';
|
||||||
|
|
||||||
const { showUserDialog } = useUserStore();
|
|
||||||
const { showGroupDialog } = useGroupStore();
|
|
||||||
const { refreshInviteMessageTableData } = useInviteStore();
|
const { refreshInviteMessageTableData } = useInviteStore();
|
||||||
const { clearInviteImageUpload } = useGalleryStore();
|
const { clearInviteImageUpload } = useGalleryStore();
|
||||||
const { notificationTable, isNotificationsLoading } = storeToRefs(useNotificationStore());
|
const { notificationTable, isNotificationsLoading } = storeToRefs(useNotificationStore());
|
||||||
@@ -126,7 +123,8 @@
|
|||||||
acceptRequestInvite,
|
acceptRequestInvite,
|
||||||
sendNotificationResponse,
|
sendNotificationResponse,
|
||||||
deleteNotificationLog,
|
deleteNotificationLog,
|
||||||
deleteNotificationLogPrompt
|
deleteNotificationLogPrompt,
|
||||||
|
openNotificationLink
|
||||||
} = useNotificationStore();
|
} = useNotificationStore();
|
||||||
const { showFullscreenImageDialog } = useGalleryStore();
|
const { showFullscreenImageDialog } = useGalleryStore();
|
||||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
@@ -294,38 +292,6 @@
|
|||||||
saveTableFilters();
|
saveTableFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openNotificationLink(link) {
|
|
||||||
if (!link) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = link.split(':');
|
|
||||||
if (!data.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (data[0]) {
|
|
||||||
case 'group':
|
|
||||||
showGroupDialog(data[1]);
|
|
||||||
break;
|
|
||||||
case 'user':
|
|
||||||
showUserDialog(data[1]);
|
|
||||||
break;
|
|
||||||
case 'event':
|
|
||||||
const ids = data[1].split(',');
|
|
||||||
if (ids.length < 2) {
|
|
||||||
console.error('Invalid event notification link:', data[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showGroupDialog(ids[0]);
|
|
||||||
// ids[1] cal_ is the event id
|
|
||||||
break;
|
|
||||||
case 'openNotificationLink':
|
|
||||||
default:
|
|
||||||
toast.error('Unsupported notification link type');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSmallThumbnailUrl(url) {
|
function getSmallThumbnailUrl(url) {
|
||||||
return convertFileUrlToImageUrl(url);
|
return convertFileUrlToImageUrl(url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import {
|
|||||||
useLocationStore,
|
useLocationStore,
|
||||||
useUiStore,
|
useUiStore,
|
||||||
useUserStore,
|
useUserStore,
|
||||||
useWorldStore
|
useWorldStore,
|
||||||
|
useNotificationStore
|
||||||
} from '../../stores';
|
} from '../../stores';
|
||||||
|
|
||||||
import Emoji from '../../components/Emoji.vue';
|
import Emoji from '../../components/Emoji.vue';
|
||||||
@@ -61,6 +62,7 @@ export const createColumns = ({
|
|||||||
const { currentUser } = storeToRefs(useUserStore());
|
const { currentUser } = storeToRefs(useUserStore());
|
||||||
const { lastLocation } = storeToRefs(useLocationStore());
|
const { lastLocation } = storeToRefs(useLocationStore());
|
||||||
const { isGameRunning } = storeToRefs(useGameStore());
|
const { isGameRunning } = storeToRefs(useGameStore());
|
||||||
|
const { isNotificationExpired } = useNotificationStore();
|
||||||
|
|
||||||
const canInvite = () => {
|
const canInvite = () => {
|
||||||
const location = lastLocation.value?.location;
|
const location = lastLocation.value?.location;
|
||||||
@@ -385,7 +387,8 @@ export const createColumns = ({
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
if (original.type === 'boop') {
|
if (original.type === 'boop') {
|
||||||
const imageUrl = original.details?.imageUrl;
|
const imageUrl =
|
||||||
|
original.details?.imageUrl || original.imageUrl;
|
||||||
if (!imageUrl || imageUrl.startsWith('default_')) {
|
if (!imageUrl || imageUrl.startsWith('default_')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -455,7 +458,28 @@ export const createColumns = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{original.message && original.title ? (
|
||||||
|
<TooltipWrapper
|
||||||
|
content={`${original.title}, ${original.message}`}
|
||||||
|
delayDuration={500}
|
||||||
|
>
|
||||||
|
<span class="block w-full min-w-0 truncate">
|
||||||
|
{`${original.title}, ${original.message}`}
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
|
) : null}
|
||||||
|
{!original.message && original.title ? (
|
||||||
|
<TooltipWrapper
|
||||||
|
content={original.title}
|
||||||
|
delayDuration={500}
|
||||||
|
>
|
||||||
|
<span class="block w-full min-w-0 truncate">
|
||||||
|
{original.title}
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
|
) : null}
|
||||||
{original.message &&
|
{original.message &&
|
||||||
|
!original.title &&
|
||||||
original.message !==
|
original.message !==
|
||||||
`This is a generated invite to ${original.details?.worldName}` ? (
|
`This is a generated invite to ${original.details?.worldName}` ? (
|
||||||
<TooltipWrapper
|
<TooltipWrapper
|
||||||
@@ -529,16 +553,12 @@ export const createColumns = ({
|
|||||||
!original.link?.startsWith('economy.');
|
!original.link?.startsWith('economy.');
|
||||||
const showDeleteLog =
|
const showDeleteLog =
|
||||||
original.type !== 'friendRequest' &&
|
original.type !== 'friendRequest' &&
|
||||||
original.type !== 'ignoredFriendRequest' &&
|
original.type !== 'ignoredFriendRequest';
|
||||||
!original.type?.includes('group.') &&
|
|
||||||
!original.type?.includes('moderation.') &&
|
|
||||||
!original.type?.includes('instance.') &&
|
|
||||||
!original.link?.startsWith('economy.');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center justify-end gap-2">
|
<div class="flex items-center justify-end gap-2">
|
||||||
{original.senderUserId !== currentUser.value?.id &&
|
{original.senderUserId !== currentUser.value?.id &&
|
||||||
!original.$isExpired ? (
|
!isNotificationExpired(original) ? (
|
||||||
<span class="inline-flex items-center gap-2">
|
<span class="inline-flex items-center gap-2">
|
||||||
{original.type === 'friendRequest' ? (
|
{original.type === 'friendRequest' ? (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -92,9 +92,9 @@
|
|||||||
const activeTab = ref('friend');
|
const activeTab = ref('friend');
|
||||||
|
|
||||||
const activeCount = computed(() => ({
|
const activeCount = computed(() => ({
|
||||||
friend: friendNotifications.value.filter((n) => !n.$isExpired).length,
|
friend: friendNotifications.value.length,
|
||||||
group: groupNotifications.value.filter((n) => !n.$isExpired).length,
|
group: groupNotifications.value.length,
|
||||||
other: otherNotifications.value.filter((n) => !n.$isExpired).length
|
other: otherNotifications.value.length
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Dialog state
|
// Dialog state
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Item
|
<Item size="sm" variant="muted" class="mb-1.5">
|
||||||
size="sm"
|
|
||||||
:variant="notification.$isExpired ? 'default' : 'muted'"
|
|
||||||
:class="[{ 'opacity-50': notification.$isExpired }, 'mb-1.5']">
|
|
||||||
<ItemMedia variant="image" class="cursor-pointer" @click.stop="openSender">
|
<ItemMedia variant="image" class="cursor-pointer" @click.stop="openSender">
|
||||||
<Avatar class="size-full">
|
<Avatar class="size-full">
|
||||||
<AvatarImage v-if="avatarUrl" :src="avatarUrl" />
|
<AvatarImage v-if="avatarUrl" :src="avatarUrl" />
|
||||||
@@ -18,7 +15,7 @@
|
|||||||
{{ typeLabel }}
|
{{ typeLabel }}
|
||||||
</Badge>
|
</Badge>
|
||||||
<span
|
<span
|
||||||
v-if="!notification.$isExpired && isUnseen"
|
v-if="!isNotificationExpired(notification) && !isSeen"
|
||||||
class="ml-auto size-2 shrink-0 rounded-full bg-blue-500" />
|
class="ml-auto size-2 shrink-0 rounded-full bg-blue-500" />
|
||||||
</ItemTitle>
|
</ItemTitle>
|
||||||
<TooltipWrapper v-if="displayMessage" side="top" :content="displayMessage" :delay-duration="600">
|
<TooltipWrapper v-if="displayMessage" side="top" :content="displayMessage" :delay-duration="600">
|
||||||
@@ -33,7 +30,7 @@
|
|||||||
{{ relativeTime }}
|
{{ relativeTime }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<template v-if="!notification.$isExpired">
|
<template v-if="!isNotificationExpired(notification)">
|
||||||
<TooltipWrapper
|
<TooltipWrapper
|
||||||
v-if="notification.type === 'friendRequest'"
|
v-if="notification.type === 'friendRequest'"
|
||||||
side="top"
|
side="top"
|
||||||
@@ -134,9 +131,10 @@
|
|||||||
} from 'lucide-vue-next';
|
} from 'lucide-vue-next';
|
||||||
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from '@/components/ui/item';
|
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from '@/components/ui/item';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
|
import { computed, onMounted } from 'vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { TooltipWrapper } from '@/components/ui/tooltip';
|
import { TooltipWrapper } from '@/components/ui/tooltip';
|
||||||
import { computed } from 'vue';
|
import { notificationRequest } from '@/api';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -158,6 +156,7 @@
|
|||||||
const notificationStore = useNotificationStore();
|
const notificationStore = useNotificationStore();
|
||||||
const { lastLocation } = storeToRefs(useLocationStore());
|
const { lastLocation } = storeToRefs(useLocationStore());
|
||||||
const { isGameRunning } = storeToRefs(useGameStore());
|
const { isGameRunning } = storeToRefs(useGameStore());
|
||||||
|
const { openNotificationLink, isNotificationExpired, handleNotificationV2Hide } = useNotificationStore();
|
||||||
|
|
||||||
const senderName = computed(() => {
|
const senderName = computed(() => {
|
||||||
const n = props.notification;
|
const n = props.notification;
|
||||||
@@ -224,6 +223,7 @@
|
|||||||
|
|
||||||
const showDecline = computed(() => {
|
const showDecline = computed(() => {
|
||||||
const type = props.notification.type;
|
const type = props.notification.type;
|
||||||
|
const link = props.notification.link;
|
||||||
return (
|
return (
|
||||||
type !== 'requestInviteResponse' &&
|
type !== 'requestInviteResponse' &&
|
||||||
type !== 'inviteResponse' &&
|
type !== 'inviteResponse' &&
|
||||||
@@ -232,7 +232,8 @@
|
|||||||
type !== 'groupChange' &&
|
type !== 'groupChange' &&
|
||||||
!type?.includes('group.') &&
|
!type?.includes('group.') &&
|
||||||
!type?.includes('moderation.') &&
|
!type?.includes('moderation.') &&
|
||||||
!type?.includes('instance.')
|
!type?.includes('instance.') &&
|
||||||
|
!link?.startsWith('economy.')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,13 +243,18 @@
|
|||||||
const n = props.notification;
|
const n = props.notification;
|
||||||
const type = n.type;
|
const type = n.type;
|
||||||
if (type === 'friendRequest' || type === 'ignoredFriendRequest') return false;
|
if (type === 'friendRequest' || type === 'ignoredFriendRequest') return false;
|
||||||
if (type?.includes('group.') || type?.includes('moderation.') || type?.includes('instance.')) return false;
|
|
||||||
if (n.link?.startsWith('economy.')) return false;
|
|
||||||
// For active notifications, group.queueReady is handled separately
|
|
||||||
if (!n.$isExpired && type === 'group.queueReady') return false;
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isSeen = computed(() => {
|
||||||
|
const n = props.notification;
|
||||||
|
if (typeof n.seen === 'boolean') {
|
||||||
|
return n.seen;
|
||||||
|
}
|
||||||
|
// Fallback for v1 notifications without seen property
|
||||||
|
return !props.isUnseen;
|
||||||
|
});
|
||||||
|
|
||||||
const canInvite = computed(() => {
|
const canInvite = computed(() => {
|
||||||
const location = lastLocation.value?.location;
|
const location = lastLocation.value?.location;
|
||||||
return Boolean(location) && isGameRunning.value && checkCanInvite(location);
|
return Boolean(location) && isGameRunning.value && checkCanInvite(location);
|
||||||
@@ -284,27 +290,6 @@
|
|||||||
notificationStore.sendNotificationResponse(props.notification.id, props.notification.responses, response.type);
|
notificationStore.sendNotificationResponse(props.notification.id, props.notification.responses, response.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openNotificationLink(link) {
|
|
||||||
if (!link) return;
|
|
||||||
const data = link.split(':');
|
|
||||||
if (!data.length) return;
|
|
||||||
switch (data[0]) {
|
|
||||||
case 'group':
|
|
||||||
groupStore.showGroupDialog(data[1]);
|
|
||||||
break;
|
|
||||||
case 'user':
|
|
||||||
userStore.showUserDialog(data[1]);
|
|
||||||
break;
|
|
||||||
case 'event': {
|
|
||||||
const ids = data[1].split(',');
|
|
||||||
if (ids.length >= 2) {
|
|
||||||
groupStore.showGroupDialog(ids[0]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSender() {
|
function openSender() {
|
||||||
const userId = props.notification.senderUserId;
|
const userId = props.notification.senderUserId;
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
@@ -314,4 +299,36 @@
|
|||||||
userStore.showUserDialog(userId);
|
userStore.showUserDialog(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Mark as seen
|
||||||
|
if (isNotificationExpired(props.notification) || isSeen.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = { notificationId: props.notification.id };
|
||||||
|
if (!props.notification.version || props.notification.version < 2) {
|
||||||
|
notificationRequest.seeNotification({ notificationId: props.notification.id }).then((args) => {
|
||||||
|
console.log('Marked notification-v1 as seen:', args.json);
|
||||||
|
notificationStore.handleNotificationSee(props.notification.id);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notificationRequest
|
||||||
|
.seeNotificationV2(params)
|
||||||
|
.then((args) => {
|
||||||
|
console.log('Marked notification-v2 as seen:', args.json);
|
||||||
|
const newArgs = {
|
||||||
|
params,
|
||||||
|
json: {
|
||||||
|
...args.json,
|
||||||
|
seen: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
notificationStore.handleNotificationV2Update(newArgs);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Failed to mark notification-v2 as seen:', err);
|
||||||
|
handleNotificationV2Hide(props.notification.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
@show-invite-request-response="$emit('show-invite-request-response', $event)" />
|
@show-invite-request-response="$emit('show-invite-request-response', $event)" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex items-center justify-center p-8 text-sm text-muted-foreground">
|
<div v-else class="flex items-center justify-center p-8 text-sm text-muted-foreground">
|
||||||
{{ t('side_panel.notification_center.no_notifications') }}
|
{{ t('side_panel.notification_center.no_new_notifications') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="expiredNotifications.length">
|
<template v-if="expiredNotifications.length">
|
||||||
@@ -73,11 +73,13 @@
|
|||||||
|
|
||||||
const sortedNotifications = computed(() => [...props.notifications].sort((a, b) => getTs(b) - getTs(a)));
|
const sortedNotifications = computed(() => [...props.notifications].sort((a, b) => getTs(b) - getTs(a)));
|
||||||
|
|
||||||
const activeNotifications = computed(() => sortedNotifications.value.filter((n) => !n.$isExpired));
|
const activeNotifications = computed(() =>
|
||||||
|
sortedNotifications.value.filter((n) => getTs(n) > dayjs().subtract(1, 'week').valueOf())
|
||||||
|
);
|
||||||
|
|
||||||
const MAX_EXPIRED = 20;
|
const MAX_EXPIRED = 20;
|
||||||
|
|
||||||
const expiredNotifications = computed(() =>
|
const expiredNotifications = computed(() =>
|
||||||
sortedNotifications.value.filter((n) => n.$isExpired).slice(0, MAX_EXPIRED)
|
sortedNotifications.value.filter((n) => getTs(n) <= dayjs().subtract(1, 'week').valueOf()).slice(0, MAX_EXPIRED)
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user