From 7e31cce96db080bb26200021d7a5679469335819 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 29 Jul 2019 20:34:28 +0200 Subject: [PATCH] Change adjacent notifications about the same status to display as one Fix #7698 --- .../notifications/components/notification.js | 10 ++++++++ .../containers/notification_container.js | 1 + .../mastodon/features/notifications/index.js | 23 ++++++++++++++++--- app/javascript/mastodon/selectors/index.js | 5 ++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 41e9324e6..fc50b5c4a 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -17,6 +17,8 @@ const notificationForScreenReader = (intl, message, timestamp) => { return output.join(', '); }; +const appendDisplayNames = (base, accounts) => {base} `@${a.get('acct')}`).join(', ')}>; + export default @injectIntl class Notification extends ImmutablePureComponent { @@ -144,6 +146,10 @@ class Notification extends ImmutablePureComponent { renderFavourite (notification, link) { const { intl } = this.props; + if (notification.get('collapsed_accounts')) { + link = appendDisplayNames(link, notification.get('collapsed_accounts')); + } + return (
@@ -176,6 +182,10 @@ class Notification extends ImmutablePureComponent { renderReblog (notification, link) { const { intl } = this.props; + if (notification.get('collapsed_accounts')) { + link = appendDisplayNames(link, notification.get('collapsed_accounts')); + } + return (
diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js index 78576c760..81c5872f4 100644 --- a/app/javascript/mastodon/features/notifications/containers/notification_container.js +++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js @@ -21,6 +21,7 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, props) => { const notification = getNotification(state, props.notification, props.accountId); + return { notification: notification, status: notification.get('status') ? getStatus(state, { id: notification.get('status') }) : null, diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index e708c4fcf..7e80d6ff4 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -20,6 +20,22 @@ const messages = defineMessages({ title: { id: 'column.notifications', defaultMessage: 'Notifications' }, }); +const collapsibleNotifications = (a, b) => { + if (!['reblog', 'favourite'].includes(a.get('type'))) { + return false; + } + + return a.get('type') === b.get('type') && a.get('status') === b.get('status'); +}; + +const reduceNotifications = (list, notification) => { + if (!list.isEmpty() && collapsibleNotifications(list.last(), notification)) { + return list.update(list.size - 1, item => item.update('collapsed_account_ids', ImmutableList(), collapsed_account_ids => collapsed_account_ids.push(notification.get('account')))); + } else { + return list.push(notification); + } +}; + const getNotifications = createSelector([ state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']), state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']), @@ -27,12 +43,13 @@ const getNotifications = createSelector([ state => state.getIn(['notifications', 'items']), ], (showFilterBar, allowedType, excludedTypes, notifications) => { if (!showFilterBar || allowedType === 'all') { - // used if user changed the notification settings after loading the notifications from the server + // Used if user changed the notification settings after loading the notifications from the server // otherwise a list of notifications will come pre-filtered from the backend // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category - return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type'))); + return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type'))).reduce(reduceNotifications, ImmutableList()); } - return notifications.filter(item => item !== null && allowedType === item.get('type')); + + return notifications.filter(item => item !== null && allowedType === item.get('type')).reduce(reduceNotifications, ImmutableList()); }); const mapStateToProps = state => ({ diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index c87654547..fc837f771 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -144,8 +144,9 @@ export const makeGetNotification = () => { return createSelector([ (_, base) => base, (state, _, accountId) => state.getIn(['accounts', accountId]), - ], (base, account) => { - return base.set('account', account); + (state, base) => base.get('collapsed_account_ids') && base.get('collapsed_account_ids').map(id => state.getIn(['accounts', id])), + ], (base, account, collapsedAccounts) => { + return base.set('account', account).set('collapsed_accounts', collapsedAccounts); }); };