notifications v2 table

This commit is contained in:
Natsumi
2026-02-20 17:19:06 +11:00
parent aa6ae21033
commit 5fe2f8ddf5
15 changed files with 488 additions and 259 deletions
+194 -99
View File
@@ -4,6 +4,7 @@ import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import Noty from 'noty';
import dayjs from 'dayjs';
import {
checkCanInvite,
@@ -23,8 +24,8 @@ import {
userRequest,
worldRequest
} from '../api';
import { database, dbVars } from '../service/database';
import { AppDebug } from '../service/appConfig';
import { database } from '../service/database';
import { useAdvancedSettingsStore } from './settings/advanced';
import { useAppearanceSettingsStore } from './settings/appearance';
import { useFavoriteStore } from './favorite';
@@ -155,6 +156,11 @@ export const useNotificationStore = defineStore('Notification', () => {
const { ref } = args;
const array = notificationTable.value.data;
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) {
if (array[i].id === ref.id) {
array[i] = ref;
@@ -189,7 +195,6 @@ export const useNotificationStore = defineStore('Notification', () => {
) {
uiStore.notifyMenu('notification');
}
unseenNotifications.value.push(ref.id);
queueNotificationNoty(ref);
sharedFeedStore.addEntry(ref);
}
@@ -206,30 +211,19 @@ export const useNotificationStore = defineStore('Notification', () => {
D.incomingRequest = true;
}
function handleNotificationHide(args) {
let ref;
const array = notificationTable.value.data;
for (let i = array.length - 1; i >= 0; i--) {
if (array[i].id === args.params.notificationId) {
ref = array[i];
break;
}
}
function handleNotificationHide(notificationId) {
const ref = notificationTable.value.data.find(
(n) => n.id === notificationId
);
if (typeof ref === 'undefined') {
return;
}
args.ref = ref;
if (
ref.type === 'friendRequest' ||
ref.type === 'ignoredFriendRequest' ||
ref.type.includes('.')
) {
for (let i = array.length - 1; i >= 0; i--) {
if (array[i].id === ref.id) {
array.splice(i, 1);
break;
}
}
removeFromArray(notificationTable.value.data, ref);
} else {
ref.$isExpired = true;
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) {
const ref = args.json;
if (
@@ -374,12 +346,18 @@ export const useNotificationStore = defineStore('Notification', () => {
});
}
function handleNotificationSee(args) {
const { notificationId } = args.params;
function handleNotificationSee(notificationId) {
removeFromArray(unseenNotifications.value, notificationId);
if (unseenNotifications.value.length === 0) {
uiStore.removeNotify('notification');
}
const ref = notificationTable.value.data.find(
(n) => n.id === notificationId
);
if (ref) {
ref.seen = true;
}
database.seenNotificationV2(ref);
}
function handleNotificationAccept(args) {
@@ -435,10 +413,11 @@ export const useNotificationStore = defineStore('Notification', () => {
/**
*
* @param {object} json
* @param {object} data
* @returns {object}
*/
function applyNotification(json) {
function applyNotification(data) {
const json = { ...data };
if (json.message) {
json.message = replaceBioSymbols(json.message);
}
@@ -489,25 +468,117 @@ export const useNotificationStore = defineStore('Notification', () => {
}
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.title = '';
if (ref.details?.emojiId?.startsWith('default_')) {
ref.details.imageUrl = ref.details.emojiId;
ref.imageUrl = ref.details.emojiId;
ref.message += ` ${ref.details.emojiId.replace('default_', '')}`;
} 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;
}
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() {
const array = notificationTable.value.data;
for (let i = array.length - 1; i >= 0; i--) {
if (
array[i].type === 'friendRequest' ||
array[i].type === 'ignoredFriendRequest' ||
array[i].type.includes('.')
array[i].type === 'ignoredFriendRequest'
) {
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>}
@@ -581,7 +636,6 @@ export const useNotificationStore = defineStore('Notification', () => {
}
});
}
unseenNotifications.value = [];
params.offset += 100;
if (args.json.length < 100) {
break;
@@ -595,23 +649,14 @@ export const useNotificationStore = defineStore('Notification', () => {
for (let i = 0; i < count; i++) {
const args =
await notificationRequest.getNotificationsV2(params);
for (const json of 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({
handleNotificationV2({
json,
params: {
notificationId: json.id
}
});
}
unseenNotifications.value = [];
params.offset += 100;
if (args.json.length < 100) {
break;
@@ -634,7 +679,6 @@ export const useNotificationStore = defineStore('Notification', () => {
}
});
}
unseenNotifications.value = [];
params.offset += 100;
if (args.json.length < 100) {
break;
@@ -2412,7 +2456,16 @@ export const useNotificationStore = defineStore('Notification', () => {
async function initNotifications() {
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();
}
@@ -2441,11 +2494,11 @@ export const useNotificationStore = defineStore('Notification', () => {
async function hideNotification(row) {
if (row.type === 'ignoredFriendRequest') {
const args = await friendRequest.deleteHiddenFriendRequest(
await friendRequest.deleteHiddenFriendRequest(
{ notificationId: row.id },
row.senderUserId
);
handleNotificationHide(args);
handleNotificationHide(row.id);
} else {
notificationRequest.hideNotification({
notificationId: row.id
@@ -2516,23 +2569,15 @@ export const useNotificationStore = defineStore('Notification', () => {
}
}
const params = { notificationId, responseType, responseData };
notificationRequest
.sendNotificationResponse(params)
.then((json) => {
if (!json) return;
const args = { json, params };
handleNotificationHide(args);
new Noty({
type: 'success',
text: escapeTag(args.json)
}).show();
})
.catch((err) => {
handleNotificationHide({ params });
notificationRequest.hideNotificationV2(params.notificationId);
console.error('Notification response failed', err);
toast.error(t('message.error'));
});
notificationRequest.sendNotificationResponse(params).then((args) => {
console.log('Notification response', args);
if (!args.json) return;
handleNotificationV2Hide(notificationId);
new Noty({
type: 'success',
text: escapeTag(args.json)
}).show();
});
}
function deleteNotificationLog(row) {
@@ -2546,7 +2591,11 @@ export const useNotificationStore = defineStore('Notification', () => {
row.type !== 'friendRequest' &&
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(() => {});
}
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 {
notificationInitStatus,
notificationTable,
@@ -2582,6 +2674,7 @@ export const useNotificationStore = defineStore('Notification', () => {
handlePipelineNotification,
handleNotificationV2Update,
handleNotificationHide,
handleNotificationV2Hide,
handleNotification,
handleNotificationV2,
testNotification,
@@ -2600,6 +2693,8 @@ export const useNotificationStore = defineStore('Notification', () => {
groupNotifications,
otherNotifications,
hasUnseenNotifications,
getNotificationCategory
getNotificationCategory,
isNotificationExpired,
openNotificationLink
};
});