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 { Fragment } from 'preact';
import { memo } from 'preact/compat'; import { memo } from 'preact/compat';
import { api } from '../utils/api';
import shortenNumber from '../utils/shorten-number'; import shortenNumber from '../utils/shorten-number';
import states, { statusKey } from '../utils/states'; import states, { statusKey } from '../utils/states';
import { getCurrentAccountID } from '../utils/store-utils'; import { getCurrentAccountID } from '../utils/store-utils';
@ -302,6 +303,7 @@ function Notification({
disableContextMenu, disableContextMenu,
}) { }) {
const { _ } = useLingui(); const { _ } = useLingui();
const { masto } = api();
const { const {
id, id,
status, status,
@ -313,9 +315,11 @@ function Notification({
_ids, _ids,
_accounts, _accounts,
_statuses, _statuses,
_groupKeys,
// Server-side grouped notification // Server-side grouped notification
sampleAccounts, sampleAccounts,
notificationsCount, notificationsCount,
groupKey,
} = notification; } = notification;
let { type } = notification; let { type } = notification;
@ -443,10 +447,15 @@ function Notification({
console.debug('RENDER Notification', notification.id); console.debug('RENDER Notification', notification.id);
const sameCount =
notificationsCount > 0 && notificationsCount <= sampleAccounts?.length;
const expandAccounts = sameCount ? 'local' : 'remote';
return ( return (
<div <div
class={`notification notification-${type}`} class={`notification notification-${type}`}
data-notification-id={_ids || id} data-notification-id={_ids || id}
data-group-key={_groupKeys?.join(' ') || groupKey}
tabIndex="0" tabIndex="0"
> >
<div <div
@ -550,15 +559,67 @@ function Notification({
</a>{' '} </a>{' '}
</Fragment> </Fragment>
))} ))}
<button {type === 'favourite+reblog' && expandAccounts === 'remote' ? (
type="button" <button
class="small plain" type="button"
onClick={handleOpenGenericAccounts} class="small plain"
> data-group-keys={_groupKeys.join(' ')}
{_accounts.length > AVATARS_LIMIT && onClick={() => {
`+${_accounts.length - AVATARS_LIMIT}`} states.showGenericAccounts = {
<Icon icon="chevron-down" /> heading: genericAccountsHeading,
</button> 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> </p>
)} )}
{!_accounts?.length && sampleAccounts?.length > 1 && ( {!_accounts?.length && sampleAccounts?.length > 1 && (

View file

@ -69,7 +69,7 @@ export function mastoFetchNotifications(opts = {}) {
memSupportsGroupedNotifications() memSupportsGroupedNotifications()
) { ) {
// https://github.com/mastodon/mastodon/pull/29889 // https://github.com/mastodon/mastodon/pull/29889
return masto.v2_alpha.notifications.list({ return masto.v2.notifications.list({
limit: NOTIFICATIONS_GROUPED_LIMIT, limit: NOTIFICATIONS_GROUPED_LIMIT,
...opts, ...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 // Merge favourited and reblogged of same status into a single notification
// - new type: "favourite+reblog" // - new type: "favourite+reblog"
// - sum numbers for `notificationsCount` and `sampleAccounts` // - sum numbers for `notificationsCount` and `sampleAccounts`
// const mappedNotifications = {}; const notificationsMap = {};
// const newNewGroupNotifications = []; const newGroupNotifications1 = [];
// for (let i = 0; i < newGroupNotifications.length; i++) { for (let i = 0; i < newGroupNotifications.length; i++) {
// const gn = newGroupNotifications[i]; const gn = newGroupNotifications[i];
// const { type, status, createdAt, notificationsCount, sampleAccounts } = gn; const {
// const date = createdAt ? new Date(createdAt).toLocaleDateString() : ''; type,
// let virtualType = type; status,
// if (type === 'favourite' || type === 'reblog') { createdAt,
// virtualType = 'favourite+reblog'; notificationsCount,
// } sampleAccounts,
// const key = `${status?.id}-${virtualType}-${date}`; groupKey,
// const mappedNotification = mappedNotifications[key]; } = gn;
// if (mappedNotification) { const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
// const accountIDs = mappedNotification.sampleAccounts.map((a) => a.id); let virtualType = type;
// sampleAccounts.forEach((a) => { const sameCount =
// if (!accountIDs.includes(a.id)) { notificationsCount > 0 && notificationsCount === sampleAccounts?.length;
// mappedNotification.sampleAccounts.push(a); // if (sameCount && (type === 'favourite' || type === 'reblog')) {
// } if (type === 'favourite' || type === 'reblog') {
// }); virtualType = 'favourite+reblog';
// mappedNotification.notificationsCount = Math.max( }
// mappedNotification.notificationsCount, // const key = `${status?.id}-${virtualType}-${date}-${sameCount ? 1 : 0}`;
// notificationsCount, const key = `${status?.id}-${virtualType}-${date}`;
// mappedNotification.sampleAccounts.length, const mappedNotification = notificationsMap[key];
// ); if (mappedNotification) {
// } else { // Merge sampleAccounts + merge _types
// mappedNotifications[key] = { sampleAccounts.forEach((a) => {
// ...gn, const mappedAccount = mappedNotification.sampleAccounts.find(
// type: virtualType, (ma) => ma.id === a.id,
// }; );
// newNewGroupNotifications.push(mappedNotifications[key]); 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. // 2nd pass.
// - Group 1 account favourte/reblog multiple posts // - Group 1 account favourte/reblog multiple posts
// - _statuses: [status, status, ...] // - _statuses: [status, status, ...]
const notificationsMap2 = {}; const notificationsMap2 = {};
const newGroupNotifications2 = []; const newGroupNotifications2 = [];
for (let i = 0; i < newGroupNotifications.length; i++) { for (let i = 0; i < newGroupNotifications1.length; i++) {
const gn = newGroupNotifications[i]; const gn = newGroupNotifications1[i];
const { type, account, _accounts, sampleAccounts, createdAt } = gn; const { type, account, _accounts, sampleAccounts, createdAt, groupKey } =
gn;
const date = createdAt ? new Date(createdAt).toLocaleDateString() : ''; const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
const hasOneAccount = const hasOneAccount =
sampleAccounts?.length === 1 || _accounts?.length === 1; 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 key = `${account?.id}-${type}-${date}`;
const mappedNotification = notificationsMap2[key]; const mappedNotification = notificationsMap2[key];
if (mappedNotification) { if (mappedNotification) {
mappedNotification._statuses.push(gn.status); mappedNotification._statuses.push(gn.status);
mappedNotification._ids += `-${gn.id}`; mappedNotification._ids += `-${gn.id}`;
mappedNotification._groupKeys.push(groupKey);
} else { } else {
let n = (notificationsMap2[key] = { let n = (notificationsMap2[key] = {
...gn, ...gn,
type, type,
_ids: gn.id, _ids: gn.id,
_statuses: [gn.status], _statuses: [gn.status],
_groupKeys: groupKey ? [groupKey] : [],
}); });
newGroupNotifications2.push(n); newGroupNotifications2.push(n);
} }
@ -136,6 +175,8 @@ export function groupNotifications2(groupNotifications) {
} }
} }
console.log('newGroupNotifications2', newGroupNotifications2);
return newGroupNotifications2; return newGroupNotifications2;
} }