@@ -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);
});
};