From e79ed3dcab50b59f01ad4c102f6284f46edcaca2 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 10 Sep 2024 16:51:21 +0200 Subject: [PATCH] [WiP] Remove old notifications code --- app/javascript/mastodon/actions/markers.ts | 8 +- .../actions/notifications_migration.tsx | 8 +- app/javascript/mastodon/actions/streaming.js | 40 +-- .../components/column_settings.jsx | 14 - .../containers/column_settings_container.js | 4 - .../mastodon/features/notifications/index.jsx | 308 ------------------ .../features/notifications_wrapper.jsx | 14 - .../features/ui/components/columns_area.jsx | 4 +- .../ui/components/navigation_panel.jsx | 10 +- app/javascript/mastodon/features/ui/index.jsx | 4 +- .../features/ui/util/async-components.js | 10 +- app/javascript/mastodon/initial_state.js | 2 - app/javascript/mastodon/selectors/settings.ts | 5 - app/serializers/initial_state_serializer.rb | 1 - 14 files changed, 23 insertions(+), 409 deletions(-) delete mode 100644 app/javascript/mastodon/features/notifications/index.jsx delete mode 100644 app/javascript/mastodon/features/notifications_wrapper.jsx diff --git a/app/javascript/mastodon/actions/markers.ts b/app/javascript/mastodon/actions/markers.ts index 521859f6c2..2625eca026 100644 --- a/app/javascript/mastodon/actions/markers.ts +++ b/app/javascript/mastodon/actions/markers.ts @@ -2,7 +2,6 @@ import { debounce } from 'lodash'; import type { MarkerJSON } from 'mastodon/api_types/markers'; import { getAccessToken } from 'mastodon/initial_state'; -import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; import type { AppDispatch, RootState } from 'mastodon/store'; import { createAppAsyncThunk } from 'mastodon/store/typed_functions'; @@ -76,12 +75,7 @@ interface MarkerParam { } function getLastNotificationId(state: RootState): string | undefined { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return selectUseGroupedNotifications(state) - ? state.notificationGroups.lastReadId - : // @ts-expect-error state.notifications is not yet typed - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - state.getIn(['notifications', 'lastReadId']); + return state.notificationGroups.lastReadId; } const buildPostMarkersParams = (state: RootState) => { diff --git a/app/javascript/mastodon/actions/notifications_migration.tsx b/app/javascript/mastodon/actions/notifications_migration.tsx index 0d4da765ec..cd9f5ca3d6 100644 --- a/app/javascript/mastodon/actions/notifications_migration.tsx +++ b/app/javascript/mastodon/actions/notifications_migration.tsx @@ -1,14 +1,10 @@ -import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; import { createAppAsyncThunk } from 'mastodon/store'; import { fetchNotifications } from './notification_groups'; -import { expandNotifications } from './notifications'; export const initializeNotifications = createAppAsyncThunk( 'notifications/initialize', - (_, { dispatch, getState }) => { - if (selectUseGroupedNotifications(getState())) - void dispatch(fetchNotifications()); - else void dispatch(expandNotifications({})); + (_, { dispatch }) => { + void dispatch(fetchNotifications()); }, ); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index bfdd894b81..6e0a005f8c 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -1,7 +1,5 @@ // @ts-check -import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; - import { getLocale } from '../locales'; import { connectStream } from '../stream'; @@ -13,7 +11,7 @@ import { } from './announcements'; import { updateConversations } from './conversations'; import { processNewNotificationForGroups, refreshStaleNotificationGroups, pollRecentNotifications as pollRecentGroupNotifications } from './notification_groups'; -import { updateNotifications, expandNotifications } from './notifications'; +import { updateNotifications } from './notifications'; import { updateStatus } from './statuses'; import { updateTimeline, @@ -39,7 +37,7 @@ const randomUpTo = max => * @param {string} channelName * @param {Object.} params * @param {Object} options - * @param {function(Function, Function): Promise} [options.fallback] + * @param {function(Function): Promise} [options.fallback] * @param {function(): void} [options.fillGaps] * @param {function(object): boolean} [options.accept] * @returns {function(): void} @@ -54,11 +52,11 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti let pollingId; /** - * @param {function(Function, Function): Promise} fallback + * @param {function(Function): Promise} fallback */ const useFallback = async fallback => { - await fallback(dispatch, getState); + await fallback(dispatch); // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000)); }; @@ -103,20 +101,13 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti case 'notification': { // @ts-expect-error const notificationJSON = JSON.parse(data.payload); - dispatch(updateNotifications(notificationJSON, messages, locale)); // TODO: remove this once the groups feature replaces the previous one - if(selectUseGroupedNotifications(getState())) { - dispatch(processNewNotificationForGroups(notificationJSON)); - } + dispatch(updateNotifications(notificationJSON, messages, locale)); + dispatch(processNewNotificationForGroups(notificationJSON)); break; } case 'notifications_merged': - const state = getState(); - if (state.notifications.top || !state.notifications.mounted) - dispatch(expandNotifications({ forceLoad: true, maxId: undefined })); - if (selectUseGroupedNotifications(state)) { - dispatch(refreshStaleNotificationGroups()); - } + dispatch(refreshStaleNotificationGroups()); break; case 'conversation': // @ts-expect-error @@ -141,21 +132,14 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti /** * @param {Function} dispatch - * @param {Function} getState */ -async function refreshHomeTimelineAndNotification(dispatch, getState) { +async function refreshHomeTimelineAndNotification(dispatch) { await dispatch(expandHomeTimeline({ maxId: undefined })); - // TODO: remove this once the groups feature replaces the previous one - if(selectUseGroupedNotifications(getState())) { - // TODO: polling for merged notifications - try { - await dispatch(pollRecentGroupNotifications()); - } catch (error) { - // TODO - } - } else { - await dispatch(expandNotifications({})); + try { + await dispatch(pollRecentGroupNotifications()); + } catch (error) { + // TODO } await dispatch(fetchAnnouncements()); diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index ed2f4ada3a..ed2947c175 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -6,7 +6,6 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; -import { forceGroupedNotifications } from 'mastodon/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions'; import ClearColumnButton from './clear_column_button'; @@ -36,7 +35,6 @@ class ColumnSettings extends PureComponent { const filterAdvancedStr = ; const unreadMarkersShowStr = ; - const groupingShowStr = ; const alertStr = ; const showStr = ; const soundStr = ; @@ -68,18 +66,6 @@ class ColumnSettings extends PureComponent { - {!forceGroupedNotifications && ( -
-

- -

- -
- -
-
- )} -

diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index 8bcc7ab4ef..1b2d5292e8 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -3,7 +3,6 @@ import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { openModal } from 'mastodon/actions/modal'; -import { initializeNotifications } from 'mastodon/actions/notifications_migration'; import { showAlert } from '../../../actions/alerts'; import { setFilter, requestBrowserPermission } from '../../../actions/notifications'; @@ -56,9 +55,6 @@ const mapDispatchToProps = (dispatch) => ({ } else { dispatch(changeSetting(['notifications', ...path], checked)); } - } else if(path[0] === 'groupingBeta') { - dispatch(changeSetting(['notifications', ...path], checked)); - dispatch(initializeNotifications()); } else { dispatch(changeSetting(['notifications', ...path], checked)); } diff --git a/app/javascript/mastodon/features/notifications/index.jsx b/app/javascript/mastodon/features/notifications/index.jsx deleted file mode 100644 index cefbd544b0..0000000000 --- a/app/javascript/mastodon/features/notifications/index.jsx +++ /dev/null @@ -1,308 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import { Helmet } from 'react-helmet'; - -import { createSelector } from '@reduxjs/toolkit'; -import { List as ImmutableList } from 'immutable'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; - -import { debounce } from 'lodash'; - -import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react'; -import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react'; -import { compareId } from 'mastodon/compare_id'; -import { Icon } from 'mastodon/components/icon'; -import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; -import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; - -import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; -import { submitMarkers } from '../../actions/markers'; -import { - expandNotifications, - scrollTopNotifications, - loadPending, - mountNotifications, - unmountNotifications, - markNotificationsAsRead, -} from '../../actions/notifications'; -import Column from '../../components/column'; -import ColumnHeader from '../../components/column_header'; -import { LoadGap } from '../../components/load_gap'; -import ScrollableList from '../../components/scrollable_list'; - -import { - FilteredNotificationsBanner, - FilteredNotificationsIconButton, -} from './components/filtered_notifications_banner'; -import NotificationsPermissionBanner from './components/notifications_permission_banner'; -import ColumnSettingsContainer from './containers/column_settings_container'; -import FilterBarContainer from './containers/filter_bar_container'; -import NotificationContainer from './containers/notification_container'; - -const messages = defineMessages({ - title: { id: 'column.notifications', defaultMessage: 'Notifications' }, - markAsRead : { id: 'notifications.mark_as_read', defaultMessage: 'Mark every notification as read' }, -}); - -const getExcludedTypes = createSelector([ - state => state.getIn(['settings', 'notifications', 'shows']), -], (shows) => { - return ImmutableList(shows.filter(item => !item).keys()); -}); - -const getNotifications = createSelector([ - state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']), - state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']), - getExcludedTypes, - 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 - // 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.filter(item => item === null || allowedType === item.get('type')); -}); - -const mapStateToProps = state => ({ - notifications: getNotifications(state), - isLoading: state.getIn(['notifications', 'isLoading'], 0) > 0, - isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0, - hasMore: state.getIn(['notifications', 'hasMore']), - numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, - lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0', - canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0), - needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']), -}); - -class Notifications extends PureComponent { - static propTypes = { - identity: identityContextPropShape, - columnId: PropTypes.string, - notifications: ImmutablePropTypes.list.isRequired, - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - isLoading: PropTypes.bool, - isUnread: PropTypes.bool, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - numPending: PropTypes.number, - lastReadId: PropTypes.string, - canMarkAsRead: PropTypes.bool, - needsNotificationPermission: PropTypes.bool, - }; - - static defaultProps = { - trackScroll: true, - }; - - UNSAFE_componentWillMount() { - this.props.dispatch(mountNotifications()); - } - - componentWillUnmount () { - this.handleLoadOlder.cancel(); - this.handleScrollToTop.cancel(); - this.handleScroll.cancel(); - this.props.dispatch(scrollTopNotifications(false)); - this.props.dispatch(unmountNotifications()); - } - - handleLoadGap = (maxId) => { - this.props.dispatch(expandNotifications({ maxId })); - }; - - handleLoadOlder = debounce(() => { - const last = this.props.notifications.last(); - this.props.dispatch(expandNotifications({ maxId: last && last.get('id') })); - }, 300, { leading: true }); - - handleLoadPending = () => { - this.props.dispatch(loadPending()); - }; - - handleScrollToTop = debounce(() => { - this.props.dispatch(scrollTopNotifications(true)); - }, 100); - - handleScroll = debounce(() => { - this.props.dispatch(scrollTopNotifications(false)); - }, 100); - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('NOTIFICATIONS', {})); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - setColumnRef = c => { - this.column = c; - }; - - handleMoveUp = id => { - const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1; - this._selectChild(elementIndex, true); - }; - - handleMoveDown = id => { - const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1; - this._selectChild(elementIndex, false); - }; - - _selectChild (index, align_top) { - const container = this.column.node; - const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); - - if (element) { - if (align_top && container.scrollTop > element.offsetTop) { - element.scrollIntoView(true); - } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { - element.scrollIntoView(false); - } - element.focus(); - } - } - - handleMarkAsRead = () => { - this.props.dispatch(markNotificationsAsRead()); - this.props.dispatch(submitMarkers({ immediate: true })); - }; - - render () { - const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props; - const pinned = !!columnId; - const emptyMessage = ; - const { signedIn } = this.props.identity; - - let scrollableContent = null; - - const filterBarContainer = signedIn - ? () - : null; - - if (isLoading && this.scrollableContent) { - scrollableContent = this.scrollableContent; - } else if (notifications.size > 0 || hasMore) { - scrollableContent = notifications.map((item, index) => item === null ? ( - 0 ? notifications.getIn([index - 1, 'id']) : null} - onClick={this.handleLoadGap} - /> - ) : ( - 0} - /> - )); - } else { - scrollableContent = null; - } - - this.scrollableContent = scrollableContent; - - let scrollContainer; - - const prepend = ( - <> - {needsNotificationPermission && } - - - ); - - if (signedIn) { - scrollContainer = ( - - {scrollableContent} - - ); - } else { - scrollContainer = ; - } - - const extraButton = ( - <> - - {canMarkAsRead && ( - - )} - - ); - - return ( - - - - - - {filterBarContainer} - - {scrollContainer} - - - {intl.formatMessage(messages.title)} - - - - ); - } - -} - -export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications))); diff --git a/app/javascript/mastodon/features/notifications_wrapper.jsx b/app/javascript/mastodon/features/notifications_wrapper.jsx deleted file mode 100644 index 4b3efeb54e..0000000000 --- a/app/javascript/mastodon/features/notifications_wrapper.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import Notifications from 'mastodon/features/notifications'; -import Notifications_v2 from 'mastodon/features/notifications_v2'; -import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; -import { useAppSelector } from 'mastodon/store'; - -export const NotificationsWrapper = (props) => { - const optedInGroupedNotifications = useAppSelector(selectUseGroupedNotifications); - - return ( - optedInGroupedNotifications ? : - ); -}; - -export default NotificationsWrapper; \ No newline at end of file diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 063ac28d96..19c2f40ac6 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -10,7 +10,7 @@ import { scrollRight } from '../../../scroll'; import BundleContainer from '../containers/bundle_container'; import { Compose, - NotificationsWrapper, + Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, @@ -32,7 +32,7 @@ import NavigationPanel from './navigation_panel'; const componentMap = { 'COMPOSE': Compose, 'HOME': HomeTimeline, - 'NOTIFICATIONS': NotificationsWrapper, + 'NOTIFICATIONS': Notifications, 'PUBLIC': PublicTimeline, 'REMOTE': PublicTimeline, 'COMMUNITY': CommunityTimeline, diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index 407276d126..6bd8ea8dd5 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -37,7 +37,6 @@ import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; import { transientSingleColumn } from 'mastodon/is_mobile'; import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions'; import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications'; -import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; import ColumnLink from './column_link'; import DisabledAccountBanner from './disabled_account_banner'; @@ -65,19 +64,16 @@ const messages = defineMessages({ }); const NotificationsLink = () => { - const optedInGroupedNotifications = useSelector(selectUseGroupedNotifications); - const count = useSelector(state => state.getIn(['notifications', 'unread'])); + const count = useSelector(selectUnreadNotificationGroupsCount); const intl = useIntl(); - const newCount = useSelector(selectUnreadNotificationGroupsCount); - return ( } - activeIcon={} + icon={} + activeIcon={} text={intl.formatMessage(messages.notifications)} /> ); diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 657c9e9e58..66110e8fb0 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -49,7 +49,7 @@ import { Favourites, DirectTimeline, HashtagTimeline, - NotificationsWrapper, + Notifications, NotificationRequests, NotificationRequest, FollowRequests, @@ -206,7 +206,7 @@ class SwitchingColumnsArea extends PureComponent { - + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 7e9a7af00a..b4ac362e8e 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -7,15 +7,7 @@ export function Compose () { } export function Notifications () { - return import(/* webpackChunkName: "features/notifications_v1" */'../../notifications'); -} - -export function Notifications_v2 () { - return import(/* webpackChunkName: "features/notifications_v2" */'../../notifications_v2'); -} - -export function NotificationsWrapper () { - return import(/* webpackChunkName: "features/notifications" */'../../notifications_wrapper'); + return import(/* webpackChunkName: "features/notifications" */'../../notifications_v2'); } export function HomeTimeline () { diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index cf33b12dd9..60b35cb31a 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -43,7 +43,6 @@ * @property {boolean=} use_pending_items * @property {string} version * @property {string} sso_redirect - * @property {boolean} force_grouped_notifications */ /** @@ -119,7 +118,6 @@ export const criticalUpdatesPending = initialState?.critical_updates_pending; // @ts-expect-error export const statusPageUrl = getMeta('status_page_url'); export const sso_redirect = getMeta('sso_redirect'); -export const forceGroupedNotifications = getMeta('force_grouped_notifications'); /** * @returns {string | undefined} diff --git a/app/javascript/mastodon/selectors/settings.ts b/app/javascript/mastodon/selectors/settings.ts index 22e7c13b93..93276c6692 100644 --- a/app/javascript/mastodon/selectors/settings.ts +++ b/app/javascript/mastodon/selectors/settings.ts @@ -1,4 +1,3 @@ -import { forceGroupedNotifications } from 'mastodon/initial_state'; import type { RootState } from 'mastodon/store'; /* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ @@ -26,10 +25,6 @@ export const selectSettingsNotificationsQuickFilterAdvanced = ( ) => state.settings.getIn(['notifications', 'quickFilter', 'advanced']) as boolean; -export const selectUseGroupedNotifications = (state: RootState) => - forceGroupedNotifications || - (state.settings.getIn(['notifications', 'groupingBeta']) as boolean); - export const selectSettingsNotificationsShowUnread = (state: RootState) => state.settings.getIn(['notifications', 'showUnread']) as boolean; diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 25a352806f..13f332c95c 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -109,7 +109,6 @@ class InitialStateSerializer < ActiveModel::Serializer trends_as_landing_page: Setting.trends_as_landing_page, trends_enabled: Setting.trends, version: instance_presenter.version, - force_grouped_notifications: ENV['FORCE_GROUPED_NOTIFICATIONS'] == 'true', } end