Improve grouping for server-side grouping

Migrate from v2_alpha to v2
This commit is contained in:
Lim Chee Aun 2024-09-22 13:55:37 +08:00
parent 4cc6a6a12c
commit c8abb95e8e
3 changed files with 150 additions and 48 deletions

View file

@ -3,6 +3,7 @@ import { useLingui } from '@lingui/react';
import { Fragment } from 'preact';
import { memo } from 'preact/compat';
import { api } from '../utils/api';
import shortenNumber from '../utils/shorten-number';
import states, { statusKey } from '../utils/states';
import { getCurrentAccountID } from '../utils/store-utils';
@ -302,6 +303,7 @@ function Notification({
disableContextMenu,
}) {
const { _ } = useLingui();
const { masto } = api();
const {
id,
status,
@ -313,9 +315,11 @@ function Notification({
_ids,
_accounts,
_statuses,
_groupKeys,
// Server-side grouped notification
sampleAccounts,
notificationsCount,
groupKey,
} = notification;
let { type } = notification;
@ -443,10 +447,15 @@ function Notification({
console.debug('RENDER Notification', notification.id);
const sameCount =
notificationsCount > 0 && notificationsCount <= sampleAccounts?.length;
const expandAccounts = sameCount ? 'local' : 'remote';
return (
<div
class={`notification notification-${type}`}
data-notification-id={_ids || id}
data-group-key={_groupKeys?.join(' ') || groupKey}
tabIndex="0"
>
<div
@ -550,15 +559,67 @@ function Notification({
</a>{' '}
</Fragment>
))}
<button
type="button"
class="small plain"
onClick={handleOpenGenericAccounts}
>
{_accounts.length > AVATARS_LIMIT &&
`+${_accounts.length - AVATARS_LIMIT}`}
<Icon icon="chevron-down" />
</button>
{type === 'favourite+reblog' && expandAccounts === 'remote' ? (
<button
type="button"
class="small plain"
data-group-keys={_groupKeys.join(' ')}
onClick={() => {
states.showGenericAccounts = {
heading: genericAccountsHeading,
fetchAccounts: async () => {
const keyAccounts = await Promise.allSettled(
_groupKeys.map(async (gKey) => {
const iterator = masto.v2.notifications
.$select(gKey)
.accounts.list();
return [gKey, (await iterator.next()).value];
}),
);
const accounts = [];
for (const keyAccount of keyAccounts) {
const [key, _accounts] = keyAccount.value;
const type = /^favourite/.test(key)
? 'favourite'
: /^reblog/.test(key)
? 'reblog'
: null;
if (!type) continue;
for (const account of _accounts) {
const theAccount = accounts.find(
(a) => a.id === account.id,
);
if (theAccount) {
theAccount._types.push(type);
} else {
account._types = [type];
accounts.push(account);
}
}
}
return {
done: true,
value: accounts,
};
},
showReactions: true,
postID: statusKey(actualStatusID, instance),
};
}}
>
<Icon icon="chevron-down" />
</button>
) : (
<button
type="button"
class="small plain"
onClick={handleOpenGenericAccounts}
>
{_accounts.length > AVATARS_LIMIT &&
`+${_accounts.length - AVATARS_LIMIT}`}
<Icon icon="chevron-down" />
</button>
)}
</p>
)}
{!_accounts?.length && sampleAccounts?.length > 1 && (

View file

@ -69,7 +69,7 @@ export function mastoFetchNotifications(opts = {}) {
memSupportsGroupedNotifications()
) {
// https://github.com/mastodon/mastodon/pull/29889
return masto.v2_alpha.notifications.list({
return masto.v2.notifications.list({
limit: NOTIFICATIONS_GROUPED_LIMIT,
...opts,
});

View file

@ -68,66 +68,105 @@ export function groupNotifications2(groupNotifications) {
};
});
// DISABLED FOR NOW.
// Merge favourited and reblogged of same status into a single notification
// - new type: "favourite+reblog"
// - sum numbers for `notificationsCount` and `sampleAccounts`
// const mappedNotifications = {};
// const newNewGroupNotifications = [];
// for (let i = 0; i < newGroupNotifications.length; i++) {
// const gn = newGroupNotifications[i];
// const { type, status, createdAt, notificationsCount, sampleAccounts } = gn;
// const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
// let virtualType = type;
// if (type === 'favourite' || type === 'reblog') {
// virtualType = 'favourite+reblog';
// }
// const key = `${status?.id}-${virtualType}-${date}`;
// const mappedNotification = mappedNotifications[key];
// if (mappedNotification) {
// const accountIDs = mappedNotification.sampleAccounts.map((a) => a.id);
// sampleAccounts.forEach((a) => {
// if (!accountIDs.includes(a.id)) {
// mappedNotification.sampleAccounts.push(a);
// }
// });
// mappedNotification.notificationsCount = Math.max(
// mappedNotification.notificationsCount,
// notificationsCount,
// mappedNotification.sampleAccounts.length,
// );
// } else {
// mappedNotifications[key] = {
// ...gn,
// type: virtualType,
// };
// newNewGroupNotifications.push(mappedNotifications[key]);
// }
// }
const notificationsMap = {};
const newGroupNotifications1 = [];
for (let i = 0; i < newGroupNotifications.length; i++) {
const gn = newGroupNotifications[i];
const {
type,
status,
createdAt,
notificationsCount,
sampleAccounts,
groupKey,
} = gn;
const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
let virtualType = type;
const sameCount =
notificationsCount > 0 && notificationsCount === sampleAccounts?.length;
// if (sameCount && (type === 'favourite' || type === 'reblog')) {
if (type === 'favourite' || type === 'reblog') {
virtualType = 'favourite+reblog';
}
// const key = `${status?.id}-${virtualType}-${date}-${sameCount ? 1 : 0}`;
const key = `${status?.id}-${virtualType}-${date}`;
const mappedNotification = notificationsMap[key];
if (mappedNotification) {
// Merge sampleAccounts + merge _types
sampleAccounts.forEach((a) => {
const mappedAccount = mappedNotification.sampleAccounts.find(
(ma) => ma.id === a.id,
);
if (!mappedAccount) {
mappedNotification.sampleAccounts.push({
...a,
_types: [type],
});
} else {
mappedAccount._types.push(type);
mappedAccount._types.sort().reverse();
}
});
// mappedNotification.notificationsCount =
// mappedNotification.sampleAccounts.length;
mappedNotification.notificationsCount = Math.min(
mappedNotification.notificationsCount,
notificationsCount,
);
mappedNotification._notificationsCount.push(notificationsCount);
mappedNotification._accounts = mappedNotification.sampleAccounts;
mappedNotification._groupKeys.push(groupKey);
} else {
const accounts = sampleAccounts.map((a) => ({
...a,
_types: [type],
}));
notificationsMap[key] = {
...gn,
sampleAccounts: accounts,
type: virtualType,
_accounts: accounts,
_groupKeys: groupKey ? [groupKey] : [],
_notificationsCount: [notificationsCount],
};
newGroupNotifications1.push(notificationsMap[key]);
}
}
// 2nd pass.
// - Group 1 account favourte/reblog multiple posts
// - _statuses: [status, status, ...]
// - _statuses: [status, status, ...]
const notificationsMap2 = {};
const newGroupNotifications2 = [];
for (let i = 0; i < newGroupNotifications.length; i++) {
const gn = newGroupNotifications[i];
const { type, account, _accounts, sampleAccounts, createdAt } = gn;
for (let i = 0; i < newGroupNotifications1.length; i++) {
const gn = newGroupNotifications1[i];
const { type, account, _accounts, sampleAccounts, createdAt, groupKey } =
gn;
const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
const hasOneAccount =
sampleAccounts?.length === 1 || _accounts?.length === 1;
if ((type === 'favourite' || type === 'reblog') && hasOneAccount) {
if (
(type === 'favourite' ||
type === 'reblog' ||
type === 'favourite+reblog') &&
hasOneAccount
) {
const key = `${account?.id}-${type}-${date}`;
const mappedNotification = notificationsMap2[key];
if (mappedNotification) {
mappedNotification._statuses.push(gn.status);
mappedNotification._ids += `-${gn.id}`;
mappedNotification._groupKeys.push(groupKey);
} else {
let n = (notificationsMap2[key] = {
...gn,
type,
_ids: gn.id,
_statuses: [gn.status],
_groupKeys: groupKey ? [groupKey] : [],
});
newGroupNotifications2.push(n);
}
@ -136,6 +175,8 @@ export function groupNotifications2(groupNotifications) {
}
}
console.log('newGroupNotifications2', newGroupNotifications2);
return newGroupNotifications2;
}