[Glitch] Add ability to group follow notifications in WebUI
Port 6c87c76e18 to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
			
			
This commit is contained in:
		
							parent
							
								
									42336d810b
								
							
						
					
					
						commit
						829ff8633b
					
				| 
						 | 
					@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
 | 
				
			||||||
import type {
 | 
					import type {
 | 
				
			||||||
  ApiNotificationGroupJSON,
 | 
					  ApiNotificationGroupJSON,
 | 
				
			||||||
  ApiNotificationJSON,
 | 
					  ApiNotificationJSON,
 | 
				
			||||||
 | 
					  NotificationType,
 | 
				
			||||||
} from 'flavours/glitch/api_types/notifications';
 | 
					} from 'flavours/glitch/api_types/notifications';
 | 
				
			||||||
import { allNotificationTypes } from 'flavours/glitch/api_types/notifications';
 | 
					import { allNotificationTypes } from 'flavours/glitch/api_types/notifications';
 | 
				
			||||||
import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
 | 
					import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
 | 
				
			||||||
| 
						 | 
					@ -15,6 +16,7 @@ import { usePendingItems } from 'flavours/glitch/initial_state';
 | 
				
			||||||
import type { NotificationGap } from 'flavours/glitch/reducers/notification_groups';
 | 
					import type { NotificationGap } from 'flavours/glitch/reducers/notification_groups';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  selectSettingsNotificationsExcludedTypes,
 | 
					  selectSettingsNotificationsExcludedTypes,
 | 
				
			||||||
 | 
					  selectSettingsNotificationsGroupFollows,
 | 
				
			||||||
  selectSettingsNotificationsQuickFilterActive,
 | 
					  selectSettingsNotificationsQuickFilterActive,
 | 
				
			||||||
  selectSettingsNotificationsShows,
 | 
					  selectSettingsNotificationsShows,
 | 
				
			||||||
} from 'flavours/glitch/selectors/settings';
 | 
					} from 'flavours/glitch/selectors/settings';
 | 
				
			||||||
| 
						 | 
					@ -68,17 +70,19 @@ function dispatchAssociatedRecords(
 | 
				
			||||||
    dispatch(importFetchedStatuses(fetchedStatuses));
 | 
					    dispatch(importFetchedStatuses(fetchedStatuses));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const supportedGroupedNotificationTypes = ['favourite', 'reblog'];
 | 
					function selectNotificationGroupedTypes(state: RootState) {
 | 
				
			||||||
 | 
					  const types: NotificationType[] = ['favourite', 'reblog'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function shouldGroupNotificationType(type: string) {
 | 
					  if (selectSettingsNotificationsGroupFollows(state)) types.push('follow');
 | 
				
			||||||
  return supportedGroupedNotificationTypes.includes(type);
 | 
					
 | 
				
			||||||
 | 
					  return types;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const fetchNotifications = createDataLoadingThunk(
 | 
					export const fetchNotifications = createDataLoadingThunk(
 | 
				
			||||||
  'notificationGroups/fetch',
 | 
					  'notificationGroups/fetch',
 | 
				
			||||||
  async (_params, { getState }) =>
 | 
					  async (_params, { getState }) =>
 | 
				
			||||||
    apiFetchNotificationGroups({
 | 
					    apiFetchNotificationGroups({
 | 
				
			||||||
      grouped_types: supportedGroupedNotificationTypes,
 | 
					      grouped_types: selectNotificationGroupedTypes(getState()),
 | 
				
			||||||
      exclude_types: getExcludedTypes(getState()),
 | 
					      exclude_types: getExcludedTypes(getState()),
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
  ({ notifications, accounts, statuses }, { dispatch }) => {
 | 
					  ({ notifications, accounts, statuses }, { dispatch }) => {
 | 
				
			||||||
| 
						 | 
					@ -102,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
 | 
				
			||||||
  'notificationGroups/fetchGap',
 | 
					  'notificationGroups/fetchGap',
 | 
				
			||||||
  async (params: { gap: NotificationGap }, { getState }) =>
 | 
					  async (params: { gap: NotificationGap }, { getState }) =>
 | 
				
			||||||
    apiFetchNotificationGroups({
 | 
					    apiFetchNotificationGroups({
 | 
				
			||||||
      grouped_types: supportedGroupedNotificationTypes,
 | 
					      grouped_types: selectNotificationGroupedTypes(getState()),
 | 
				
			||||||
      max_id: params.gap.maxId,
 | 
					      max_id: params.gap.maxId,
 | 
				
			||||||
      exclude_types: getExcludedTypes(getState()),
 | 
					      exclude_types: getExcludedTypes(getState()),
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
| 
						 | 
					@ -119,7 +123,7 @@ export const pollRecentNotifications = createDataLoadingThunk(
 | 
				
			||||||
  'notificationGroups/pollRecentNotifications',
 | 
					  'notificationGroups/pollRecentNotifications',
 | 
				
			||||||
  async (_params, { getState }) => {
 | 
					  async (_params, { getState }) => {
 | 
				
			||||||
    return apiFetchNotificationGroups({
 | 
					    return apiFetchNotificationGroups({
 | 
				
			||||||
      grouped_types: supportedGroupedNotificationTypes,
 | 
					      grouped_types: selectNotificationGroupedTypes(getState()),
 | 
				
			||||||
      max_id: undefined,
 | 
					      max_id: undefined,
 | 
				
			||||||
      exclude_types: getExcludedTypes(getState()),
 | 
					      exclude_types: getExcludedTypes(getState()),
 | 
				
			||||||
      // In slow mode, we don't want to include notifications that duplicate the already-displayed ones
 | 
					      // In slow mode, we don't want to include notifications that duplicate the already-displayed ones
 | 
				
			||||||
| 
						 | 
					@ -168,7 +172,10 @@ export const processNewNotificationForGroups = createAppAsyncThunk(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dispatchAssociatedRecords(dispatch, [notification]);
 | 
					    dispatchAssociatedRecords(dispatch, [notification]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return notification;
 | 
					    return {
 | 
				
			||||||
 | 
					      notification,
 | 
				
			||||||
 | 
					      groupedTypes: selectNotificationGroupedTypes(state),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,7 @@ class ColumnSettings extends PureComponent {
 | 
				
			||||||
    const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
 | 
					    const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
 | 
				
			||||||
    const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
 | 
					    const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
 | 
				
			||||||
    const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
 | 
					    const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
 | 
				
			||||||
 | 
					    const groupStr = <FormattedMessage id='notifications.column_settings.group' defaultMessage='Group' />;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
 | 
					    const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
 | 
				
			||||||
    const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
 | 
					    const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
 | 
				
			||||||
| 
						 | 
					@ -96,6 +97,10 @@ class ColumnSettings extends PureComponent {
 | 
				
			||||||
            <PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
 | 
					            <PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
 | 
				
			||||||
            <PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
 | 
					            <PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='column-settings__row'>
 | 
				
			||||||
 | 
					            <SettingToggle prefix='notifications' settings={settings} settingPath={['group', 'follow']} onChange={onChange} label={groupStr} />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <section role='group' aria-labelledby='notifications-follow-request'>
 | 
					        <section role='group' aria-labelledby='notifications-follow-request'>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,11 +56,12 @@ const mapDispatchToProps = (dispatch) => ({
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        dispatch(changeSetting(['notifications', ...path], checked));
 | 
					        dispatch(changeSetting(['notifications', ...path], checked));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else if(path[0] === 'groupingBeta') {
 | 
					 | 
				
			||||||
      dispatch(changeSetting(['notifications', ...path], checked));
 | 
					 | 
				
			||||||
      dispatch(initializeNotifications());
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      dispatch(changeSetting(['notifications', ...path], checked));
 | 
					      dispatch(changeSetting(['notifications', ...path], checked));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(path[0] === 'group' && path[1] === 'follow') {
 | 
				
			||||||
 | 
					        dispatch(initializeNotifications());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,19 @@
 | 
				
			||||||
import { FormattedMessage } from 'react-intl';
 | 
					import { FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
 | 
					import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
 | 
				
			||||||
import { FollowersCounter } from 'flavours/glitch/components/counters';
 | 
					import { FollowersCounter } from 'flavours/glitch/components/counters';
 | 
				
			||||||
import { FollowButton } from 'flavours/glitch/components/follow_button';
 | 
					import { FollowButton } from 'flavours/glitch/components/follow_button';
 | 
				
			||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
 | 
					import { ShortNumber } from 'flavours/glitch/components/short_number';
 | 
				
			||||||
 | 
					import { me } from 'flavours/glitch/initial_state';
 | 
				
			||||||
import type { NotificationGroupFollow } from 'flavours/glitch/models/notification_group';
 | 
					import type { NotificationGroupFollow } from 'flavours/glitch/models/notification_group';
 | 
				
			||||||
import { useAppSelector } from 'flavours/glitch/store';
 | 
					import { useAppSelector } from 'flavours/glitch/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { LabelRenderer } from './notification_group_with_status';
 | 
					import type { LabelRenderer } from './notification_group_with_status';
 | 
				
			||||||
import { NotificationGroupWithStatus } from './notification_group_with_status';
 | 
					import { NotificationGroupWithStatus } from './notification_group_with_status';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const labelRenderer: LabelRenderer = (displayedName, total) => {
 | 
					const labelRenderer: LabelRenderer = (displayedName, total, seeMoreHref) => {
 | 
				
			||||||
  if (total === 1)
 | 
					  if (total === 1)
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <FormattedMessage
 | 
					      <FormattedMessage
 | 
				
			||||||
| 
						 | 
					@ -23,10 +26,12 @@ const labelRenderer: LabelRenderer = (displayedName, total) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <FormattedMessage
 | 
					    <FormattedMessage
 | 
				
			||||||
      id='notification.follow.name_and_others'
 | 
					      id='notification.follow.name_and_others'
 | 
				
			||||||
      defaultMessage='{name} and {count, plural, one {# other} other {# others}} followed you'
 | 
					      defaultMessage='{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you'
 | 
				
			||||||
      values={{
 | 
					      values={{
 | 
				
			||||||
        name: displayedName,
 | 
					        name: displayedName,
 | 
				
			||||||
        count: total - 1,
 | 
					        count: total - 1,
 | 
				
			||||||
 | 
					        a: (chunks) =>
 | 
				
			||||||
 | 
					          seeMoreHref ? <Link to={seeMoreHref}>{chunks}</Link> : chunks,
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					@ -46,6 +51,10 @@ export const NotificationFollow: React.FC<{
 | 
				
			||||||
  notification: NotificationGroupFollow;
 | 
					  notification: NotificationGroupFollow;
 | 
				
			||||||
  unread: boolean;
 | 
					  unread: boolean;
 | 
				
			||||||
}> = ({ notification, unread }) => {
 | 
					}> = ({ notification, unread }) => {
 | 
				
			||||||
 | 
					  const username = useAppSelector(
 | 
				
			||||||
 | 
					    (state) => state.accounts.getIn([me, 'username']) as string,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let actions: JSX.Element | undefined;
 | 
					  let actions: JSX.Element | undefined;
 | 
				
			||||||
  let additionalContent: JSX.Element | undefined;
 | 
					  let additionalContent: JSX.Element | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,6 +77,7 @@ export const NotificationFollow: React.FC<{
 | 
				
			||||||
      timestamp={notification.latest_page_notification_at}
 | 
					      timestamp={notification.latest_page_notification_at}
 | 
				
			||||||
      count={notification.notifications_count}
 | 
					      count={notification.notifications_count}
 | 
				
			||||||
      labelRenderer={labelRenderer}
 | 
					      labelRenderer={labelRenderer}
 | 
				
			||||||
 | 
					      labelSeeMoreHref={`/@${username}/followers`}
 | 
				
			||||||
      unread={unread}
 | 
					      unread={unread}
 | 
				
			||||||
      actions={actions}
 | 
					      actions={actions}
 | 
				
			||||||
      additionalContent={additionalContent}
 | 
					      additionalContent={additionalContent}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,6 @@ import {
 | 
				
			||||||
  unmountNotifications,
 | 
					  unmountNotifications,
 | 
				
			||||||
  refreshStaleNotificationGroups,
 | 
					  refreshStaleNotificationGroups,
 | 
				
			||||||
  pollRecentNotifications,
 | 
					  pollRecentNotifications,
 | 
				
			||||||
  shouldGroupNotificationType,
 | 
					 | 
				
			||||||
} from 'flavours/glitch/actions/notification_groups';
 | 
					} from 'flavours/glitch/actions/notification_groups';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  disconnectTimeline,
 | 
					  disconnectTimeline,
 | 
				
			||||||
| 
						 | 
					@ -30,6 +29,7 @@ import {
 | 
				
			||||||
import type {
 | 
					import type {
 | 
				
			||||||
  ApiNotificationJSON,
 | 
					  ApiNotificationJSON,
 | 
				
			||||||
  ApiNotificationGroupJSON,
 | 
					  ApiNotificationGroupJSON,
 | 
				
			||||||
 | 
					  NotificationType,
 | 
				
			||||||
} from 'flavours/glitch/api_types/notifications';
 | 
					} from 'flavours/glitch/api_types/notifications';
 | 
				
			||||||
import { compareId } from 'flavours/glitch/compare_id';
 | 
					import { compareId } from 'flavours/glitch/compare_id';
 | 
				
			||||||
import { usePendingItems } from 'flavours/glitch/initial_state';
 | 
					import { usePendingItems } from 'flavours/glitch/initial_state';
 | 
				
			||||||
| 
						 | 
					@ -205,8 +205,9 @@ function mergeGapsAround(
 | 
				
			||||||
function processNewNotification(
 | 
					function processNewNotification(
 | 
				
			||||||
  groups: NotificationGroupsState['groups'],
 | 
					  groups: NotificationGroupsState['groups'],
 | 
				
			||||||
  notification: ApiNotificationJSON,
 | 
					  notification: ApiNotificationJSON,
 | 
				
			||||||
 | 
					  groupedTypes: NotificationType[],
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  if (!shouldGroupNotificationType(notification.type)) {
 | 
					  if (!groupedTypes.includes(notification.type)) {
 | 
				
			||||||
    notification = {
 | 
					    notification = {
 | 
				
			||||||
      ...notification,
 | 
					      ...notification,
 | 
				
			||||||
      group_key: `ungrouped-${notification.id}`,
 | 
					      group_key: `ungrouped-${notification.id}`,
 | 
				
			||||||
| 
						 | 
					@ -476,11 +477,13 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
 | 
				
			||||||
        trimNotifications(state);
 | 
					        trimNotifications(state);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .addCase(processNewNotificationForGroups.fulfilled, (state, action) => {
 | 
					      .addCase(processNewNotificationForGroups.fulfilled, (state, action) => {
 | 
				
			||||||
        const notification = action.payload;
 | 
					        if (action.payload) {
 | 
				
			||||||
        if (notification) {
 | 
					          const { notification, groupedTypes } = action.payload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          processNewNotification(
 | 
					          processNewNotification(
 | 
				
			||||||
            usePendingItems ? state.pendingGroups : state.groups,
 | 
					            usePendingItems ? state.pendingGroups : state.groups,
 | 
				
			||||||
            notification,
 | 
					            notification,
 | 
				
			||||||
 | 
					            groupedTypes,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          updateLastReadId(state);
 | 
					          updateLastReadId(state);
 | 
				
			||||||
          trimNotifications(state);
 | 
					          trimNotifications(state);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,6 +79,10 @@ const initialState = ImmutableMap({
 | 
				
			||||||
      'admin.sign_up': true,
 | 
					      'admin.sign_up': true,
 | 
				
			||||||
      'admin.report': true,
 | 
					      'admin.report': true,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    group: ImmutableMap({
 | 
				
			||||||
 | 
					      follow: true
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  firehose: ImmutableMap({
 | 
					  firehose: ImmutableMap({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,4 +52,7 @@ export const selectSettingsNotificationsMinimizeFilteredBanner = (
 | 
				
			||||||
) =>
 | 
					) =>
 | 
				
			||||||
  state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean;
 | 
					  state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const selectSettingsNotificationsGroupFollows = (state: RootState) =>
 | 
				
			||||||
 | 
					  state.settings.getIn(['notifications', 'group', 'follow']) as boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
 | 
					/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue