diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index 7b80663f3ddb52..3266df5a59d907 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -1,21 +1,13 @@
import { IntlMessageFormat } from 'intl-messageformat';
import { defineMessages } from 'react-intl';
-import { List as ImmutableList } from 'immutable';
-
-import { compareId } from 'flavours/glitch/compare_id';
-import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state';
-
-import api, { getLinks } from '../api';
import { unescapeHTML } from '../utils/html';
import { requestNotificationPermission } from '../utils/notifications';
import { fetchFollowRequests } from './accounts';
import {
importFetchedAccount,
- importFetchedAccounts,
importFetchedStatus,
- importFetchedStatuses,
} from './importer';
import { submitMarkers } from './markers';
import { notificationsUpdate } from "./notifications_typed";
@@ -26,50 +18,18 @@ export * from "./notifications_typed";
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
-// tracking the notif cleaning request
-export const NOTIFICATIONS_DELETE_MARKED_REQUEST = 'NOTIFICATIONS_DELETE_MARKED_REQUEST';
-export const NOTIFICATIONS_DELETE_MARKED_SUCCESS = 'NOTIFICATIONS_DELETE_MARKED_SUCCESS';
-export const NOTIFICATIONS_DELETE_MARKED_FAIL = 'NOTIFICATIONS_DELETE_MARKED_FAIL';
-export const NOTIFICATIONS_MARK_ALL_FOR_DELETE = 'NOTIFICATIONS_MARK_ALL_FOR_DELETE';
-export const NOTIFICATIONS_ENTER_CLEARING_MODE = 'NOTIFICATIONS_ENTER_CLEARING_MODE'; // arg: yes
-// Unmark notifications (when the cleaning mode is left)
-export const NOTIFICATIONS_UNMARK_ALL_FOR_DELETE = 'NOTIFICATIONS_UNMARK_ALL_FOR_DELETE';
-// Mark one for delete
-export const NOTIFICATION_MARK_FOR_DELETE = 'NOTIFICATION_MARK_FOR_DELETE';
-
-export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
-export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
-export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
-
export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
-export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
-export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
-
-export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT';
-export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
-
-export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY';
-
-export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
-
export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
-export const NOTIFICATION_REQUESTS_ACCEPT_REQUEST = 'NOTIFICATION_REQUESTS_ACCEPT_REQUEST';
-export const NOTIFICATION_REQUESTS_ACCEPT_SUCCESS = 'NOTIFICATION_REQUESTS_ACCEPT_SUCCESS';
-export const NOTIFICATION_REQUESTS_ACCEPT_FAIL = 'NOTIFICATION_REQUESTS_ACCEPT_FAIL';
-
export const NOTIFICATION_REQUESTS_DISMISS_REQUEST = 'NOTIFICATION_REQUESTS_DISMISS_REQUEST';
export const NOTIFICATION_REQUESTS_DISMISS_SUCCESS = 'NOTIFICATION_REQUESTS_DISMISS_SUCCESS';
export const NOTIFICATION_REQUESTS_DISMISS_FAIL = 'NOTIFICATION_REQUESTS_DISMISS_FAIL';
defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
-});
-
-export const loadPending = () => ({
- type: NOTIFICATIONS_LOAD_PENDING,
+ group: { id: 'notifications.group', defaultMessage: '{count} notifications' },
});
export function updateNotifications(notification, intlMessages, intlLocale) {
@@ -108,8 +68,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
dispatch(importFetchedAccount(notification.report.target_account));
}
-
- dispatch(notificationsUpdate({ notification, preferPendingItems, playSound: playSound && !filtered}));
+ dispatch(notificationsUpdate({ notification, playSound: playSound && !filtered}));
} else if (playSound && !filtered) {
dispatch({
type: NOTIFICATIONS_UPDATE_NOOP,
@@ -132,199 +91,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
};
}
-const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
-
-const excludeTypesFromFilter = filter => {
- const allTypes = ImmutableList([
- 'follow',
- 'follow_request',
- 'favourite',
- 'reblog',
- 'mention',
- 'poll',
- 'status',
- 'update',
- 'admin.sign_up',
- 'admin.report',
- ]);
-
- return allTypes.filterNot(item => item === filter).toJS();
-};
-
const noOp = () => {};
-let expandNotificationsController = new AbortController();
-
-export function expandNotifications({ maxId = undefined, forceLoad = false }) {
- return async (dispatch, getState) => {
- const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
- const notifications = getState().get('notifications');
- const isLoadingMore = !!maxId;
-
- if (notifications.get('isLoading')) {
- if (forceLoad) {
- expandNotificationsController.abort();
- expandNotificationsController = new AbortController();
- } else {
- return;
- }
- }
-
- const params = {
- max_id: maxId,
- exclude_types: activeFilter === 'all'
- ? excludeTypesFromSettings(getState())
- : excludeTypesFromFilter(activeFilter),
- };
-
- if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
- const a = notifications.getIn(['pendingItems', 0, 'id']);
- const b = notifications.getIn(['items', 0, 'id']);
-
- if (a && b && compareId(a, b) > 0) {
- params.since_id = a;
- } else {
- params.since_id = b || a;
- }
- }
-
- const isLoadingRecent = !!params.since_id;
-
- dispatch(expandNotificationsRequest(isLoadingMore));
-
- try {
- const response = await api().get('/api/v1/notifications', { params, signal: expandNotificationsController.signal });
- const next = getLinks(response).refs.find(link => link.rel === 'next');
-
- dispatch(importFetchedAccounts(response.data.map(item => item.account)));
- dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
- dispatch(importFetchedAccounts(response.data.filter(item => item.report).map(item => item.report.target_account)));
-
- dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
- dispatch(submitMarkers());
- } catch(error) {
- dispatch(expandNotificationsFail(error, isLoadingMore));
- }
- };
-}
-
-export function expandNotificationsRequest(isLoadingMore) {
- return {
- type: NOTIFICATIONS_EXPAND_REQUEST,
- skipLoading: !isLoadingMore,
- };
-}
-
-export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) {
- return {
- type: NOTIFICATIONS_EXPAND_SUCCESS,
- notifications,
- next,
- isLoadingRecent: isLoadingRecent,
- usePendingItems,
- skipLoading: !isLoadingMore,
- };
-}
-
-export function expandNotificationsFail(error, isLoadingMore) {
- return {
- type: NOTIFICATIONS_EXPAND_FAIL,
- error,
- skipLoading: !isLoadingMore,
- skipAlert: !isLoadingMore || error.name === 'AbortError',
- };
-}
-
-export function scrollTopNotifications(top) {
- return {
- type: NOTIFICATIONS_SCROLL_TOP,
- top,
- };
-}
-
-export function deleteMarkedNotifications() {
- return (dispatch, getState) => {
- dispatch(deleteMarkedNotificationsRequest());
-
- let ids = [];
- getState().getIn(['notifications', 'items']).forEach((n) => {
- if (n.get('markedForDelete')) {
- ids.push(n.get('id'));
- }
- });
-
- if (ids.length === 0) {
- return;
- }
-
- api().delete(`/api/v1/notifications/destroy_multiple?ids[]=${ids.join('&ids[]=')}`).then(() => {
- dispatch(deleteMarkedNotificationsSuccess());
- }).catch(error => {
- console.error(error);
- dispatch(deleteMarkedNotificationsFail(error));
- });
- };
-}
-
-export function enterNotificationClearingMode(yes) {
- return {
- type: NOTIFICATIONS_ENTER_CLEARING_MODE,
- yes: yes,
- };
-}
-
-export function markAllNotifications(yes) {
- return {
- type: NOTIFICATIONS_MARK_ALL_FOR_DELETE,
- yes: yes, // true, false or null. null = invert
- };
-}
-
-export function deleteMarkedNotificationsRequest() {
- return {
- type: NOTIFICATIONS_DELETE_MARKED_REQUEST,
- };
-}
-
-export function deleteMarkedNotificationsFail() {
- return {
- type: NOTIFICATIONS_DELETE_MARKED_FAIL,
- };
-}
-
-export function markNotificationForDelete(id, yes) {
- return {
- type: NOTIFICATION_MARK_FOR_DELETE,
- id: id,
- yes: yes,
- };
-}
-
-export function deleteMarkedNotificationsSuccess() {
- return {
- type: NOTIFICATIONS_DELETE_MARKED_SUCCESS,
- };
-}
-
-export function mountNotifications() {
- return {
- type: NOTIFICATIONS_MOUNT,
- };
-}
-
-export function unmountNotifications() {
- return {
- type: NOTIFICATIONS_UNMOUNT,
- };
-}
-
-export function notificationsSetVisibility(visibility) {
- return {
- type: NOTIFICATIONS_SET_VISIBILITY,
- visibility: visibility,
- };
-}
-
export function setFilter (filterType) {
return dispatch => {
dispatch({
@@ -332,17 +100,10 @@ export function setFilter (filterType) {
path: ['notifications', 'quickFilter', 'active'],
value: filterType,
});
- dispatch(expandNotifications({ forceLoad: true }));
dispatch(saveSettings());
};
}
-export function markNotificationsAsRead() {
- return {
- type: NOTIFICATIONS_MARK_AS_READ,
- };
-}
-
// Browser support
export function setupBrowserNotifications() {
return dispatch => {
diff --git a/app/javascript/flavours/glitch/actions/notifications_migration.tsx b/app/javascript/flavours/glitch/actions/notifications_migration.tsx
deleted file mode 100644
index 6f2d38b9e94677..00000000000000
--- a/app/javascript/flavours/glitch/actions/notifications_migration.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { createAppAsyncThunk } from 'flavours/glitch/store';
-
-import { fetchNotifications } from './notification_groups';
-
-export const initializeNotifications = createAppAsyncThunk(
- 'notifications/initialize',
- (_, { dispatch }) => {
- void dispatch(fetchNotifications());
- },
-);
diff --git a/app/javascript/flavours/glitch/actions/notifications_typed.ts b/app/javascript/flavours/glitch/actions/notifications_typed.ts
index 55896f4c378f49..a9abfa0e60d7ec 100644
--- a/app/javascript/flavours/glitch/actions/notifications_typed.ts
+++ b/app/javascript/flavours/glitch/actions/notifications_typed.ts
@@ -9,7 +9,6 @@ export const notificationsUpdate = createAction(
...args
}: {
notification: ApiNotificationJSON;
- usePendingItems: boolean;
playSound: boolean;
}) => ({
payload: args,
diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js
index fa7af7055e6180..93bd7dc5f44098 100644
--- a/app/javascript/flavours/glitch/actions/streaming.js
+++ b/app/javascript/flavours/glitch/actions/streaming.js
@@ -11,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,
@@ -107,9 +107,6 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
break;
}
case 'notifications_merged': {
- const state = getState();
- if (state.notifications.top || !state.notifications.mounted)
- dispatch(expandNotifications({ forceLoad: true, maxId: undefined }));
dispatch(refreshStaleNotificationGroups());
break;
}
diff --git a/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx b/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx
deleted file mode 100644
index b17173832277ab..00000000000000
--- a/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Buttons widget for controlling the notification clearing mode.
- * In idle state, the cleaning mode button is shown. When the mode is active,
- * a Confirm and Abort buttons are shown in its place.
- */
-
-import PropTypes from 'prop-types';
-
-import { defineMessages, injectIntl } from 'react-intl';
-
-import classNames from 'classnames';
-
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
-import { Icon } from 'flavours/glitch/components/icon';
-
-
-const messages = defineMessages({
- btnAll : { id: 'notification_purge.btn_all', defaultMessage: 'Select\nall' },
- btnNone : { id: 'notification_purge.btn_none', defaultMessage: 'Select\nnone' },
- btnInvert : { id: 'notification_purge.btn_invert', defaultMessage: 'Invert\nselection' },
- btnApply : { id: 'notification_purge.btn_apply', defaultMessage: 'Clear\nselected' },
-});
-
-class NotificationPurgeButtons extends ImmutablePureComponent {
-
- static propTypes = {
- onDeleteMarked : PropTypes.func.isRequired,
- onMarkAll : PropTypes.func.isRequired,
- onMarkNone : PropTypes.func.isRequired,
- onInvert : PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- markNewForDelete: PropTypes.bool,
- };
-
- render () {
- const { intl, markNewForDelete } = this.props;
-
- //className='active'
- return (
-
-
-
-
-
-
-
-
-
- );
- }
-
-}
-
-export default injectIntl(NotificationPurgeButtons);
diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx
index 4e24a3e2889d71..b38fac46fbd708 100644
--- a/app/javascript/flavours/glitch/components/status.jsx
+++ b/app/javascript/flavours/glitch/components/status.jsx
@@ -11,7 +11,6 @@ import { HotKeys } from 'react-hotkeys';
import { ContentWarning } from 'flavours/glitch/components/content_warning';
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
-import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container';
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router';
@@ -729,12 +728,6 @@ class Status extends ImmutablePureComponent {
onFilter={matchedFilters ? this.handleFilterClick : null}
{...other}
/>
-
- {notification && (
-
- )}
diff --git a/app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js b/app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js
deleted file mode 100644
index 144d77f135bbdb..00000000000000
--- a/app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js
+++ /dev/null
@@ -1,53 +0,0 @@
-// Package imports.
-import { defineMessages, injectIntl } from 'react-intl';
-
-import { connect } from 'react-redux';
-
-// Our imports.
-import { openModal } from 'flavours/glitch/actions/modal';
-import {
- deleteMarkedNotifications,
- enterNotificationClearingMode,
- markAllNotifications,
-} from 'flavours/glitch/actions/notifications';
-import NotificationPurgeButtons from 'flavours/glitch/components/notification_purge_buttons';
-
-const messages = defineMessages({
- clearMessage: { id: 'notifications.marked_clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all selected notifications?' },
- clearConfirm: { id: 'notifications.marked_clear', defaultMessage: 'Clear selected notifications' },
-});
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
- onEnterCleaningMode(yes) {
- dispatch(enterNotificationClearingMode(yes));
- },
-
- onDeleteMarked() {
- dispatch(openModal({
- modalType: 'CONFIRM',
- modalProps: {
- message: intl.formatMessage(messages.clearMessage),
- confirm: intl.formatMessage(messages.clearConfirm),
- onConfirm: () => dispatch(deleteMarkedNotifications()),
- },
- }));
- },
-
- onMarkAll() {
- dispatch(markAllNotifications(true));
- },
-
- onMarkNone() {
- dispatch(markAllNotifications(false));
- },
-
- onInvert() {
- dispatch(markAllNotifications(null));
- },
-});
-
-const mapStateToProps = state => ({
- markNewForDelete: state.getIn(['notifications', 'markNewForDelete']),
-});
-
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationPurgeButtons));
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
index 8518ddb2ca6f36..6ec613e2094b79 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
@@ -20,7 +20,6 @@ import StatusContainer from 'flavours/glitch/containers/status_container';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import FollowRequestContainer from '../containers/follow_request_container';
-import NotificationOverlayContainer from '../containers/overlay_container';
import { ModerationWarning } from './moderation_warning';
import { RelationshipsSeveranceEvent } from './relationships_severance_event';
@@ -133,7 +132,6 @@ class Notification extends ImmutablePureComponent {
-
);
@@ -154,7 +152,6 @@ class Notification extends ImmutablePureComponent {
-
);
@@ -366,7 +363,6 @@ class Notification extends ImmutablePureComponent {
-
);
@@ -406,7 +402,6 @@ class Notification extends ImmutablePureComponent {
-
);
diff --git a/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx b/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx
deleted file mode 100644
index faf7e42e71b57a..00000000000000
--- a/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * Notification overlay
- */
-
-import PropTypes from 'prop-types';
-
-import { defineMessages, injectIntl } from 'react-intl';
-
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-import CheckIcon from '@/material-icons/400-24px/check.svg?react';
-import { Icon } from 'flavours/glitch/components/icon';
-
-
-const messages = defineMessages({
- markForDeletion: { id: 'notification.markForDeletion', defaultMessage: 'Mark for deletion' },
-});
-
-class NotificationOverlay extends ImmutablePureComponent {
-
- static propTypes = {
- notification : ImmutablePropTypes.map.isRequired,
- onMarkForDelete : PropTypes.func.isRequired,
- show : PropTypes.bool.isRequired,
- intl : PropTypes.object.isRequired,
- };
-
- onToggleMark = () => {
- const mark = !this.props.notification.get('markedForDelete');
- const id = this.props.notification.get('id');
- this.props.onMarkForDelete(id, mark);
- };
-
- render () {
- const { notification, show, intl } = this.props;
-
- const active = notification.get('markedForDelete');
- const label = intl.formatMessage(messages.markForDeletion);
-
- return show ? (
-
-
-
- {active ? () : ''}
-
-
-
- ) : null;
- }
-
-}
-
-export default injectIntl(NotificationOverlay);
diff --git a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
index 3e0da03e33f40f..6a933237f9344f 100644
--- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
@@ -3,7 +3,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal';
-import { initializeNotifications } from 'flavours/glitch/actions/notifications_migration';
+import { fetchNotifications } from 'flavours/glitch/actions/notification_groups';
import { showAlert } from '../../../actions/alerts';
import { setFilter, requestBrowserPermission } from '../../../actions/notifications';
@@ -60,7 +60,7 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(changeSetting(['notifications', ...path], checked));
if(path[0] === 'group' && path[1] === 'follow') {
- dispatch(initializeNotifications());
+ dispatch(fetchNotifications());
}
}
},
diff --git a/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js b/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js
index 8ecea4bc735731..269e83b8a06099 100644
--- a/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js
+++ b/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js
@@ -2,8 +2,8 @@ import { connect } from 'react-redux';
import { mentionCompose } from '../../../actions/compose';
import {
- toggleReblog,
toggleFavourite,
+ toggleReblog,
} from '../../../actions/interactions';
import { makeGetNotification, makeGetStatus, makeGetReport } from '../../../selectors';
import Notification from '../components/notification';
@@ -19,7 +19,6 @@ const makeMapStateToProps = () => {
notification: notification,
status: notification.get('status') ? getStatus(state, { id: notification.get('status'), contextType: 'notifications' }) : null,
report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null,
- notifCleaning: state.getIn(['notifications', 'cleaningMode']),
};
};
diff --git a/app/javascript/flavours/glitch/features/notifications/containers/overlay_container.js b/app/javascript/flavours/glitch/features/notifications/containers/overlay_container.js
deleted file mode 100644
index e4b8902a8164cc..00000000000000
--- a/app/javascript/flavours/glitch/features/notifications/containers/overlay_container.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Package imports.
-import { connect } from 'react-redux';
-
-// Our imports.
-import { markNotificationForDelete } from 'flavours/glitch/actions/notifications';
-
-import NotificationOverlay from '../components/overlay';
-
-const mapDispatchToProps = dispatch => ({
- onMarkForDelete(id, yes) {
- dispatch(markNotificationForDelete(id, yes));
- },
-});
-
-const mapStateToProps = state => ({
- show: state.getIn(['notifications', 'cleaningMode']),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(NotificationOverlay);
diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx
index 96a8fe89a3b660..db6d6a1fcaa42b 100644
--- a/app/javascript/flavours/glitch/features/ui/index.jsx
+++ b/app/javascript/flavours/glitch/features/ui/index.jsx
@@ -14,7 +14,7 @@ import { HotKeys } from 'react-hotkeys';
import { focusApp, unfocusApp, changeLayout } from 'flavours/glitch/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
-import { initializeNotifications } from 'flavours/glitch/actions/notifications_migration';
+import { fetchNotifications } from 'flavours/glitch/actions/notification_groups';
import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
import { HoverCardController } from 'flavours/glitch/components/hover_card_controller';
import { Permalink } from 'flavours/glitch/components/permalink';
@@ -26,7 +26,6 @@ import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
import { clearHeight } from '../../actions/height_cache';
-import { notificationsSetVisibility } from '../../actions/notifications';
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
import { expandHomeTimeline } from '../../actions/timelines';
import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards } from '../../initial_state';
@@ -316,7 +315,6 @@ class UI extends PureComponent {
handleVisibilityChange = () => {
const visibility = !document[this.visibilityHiddenProp];
- this.props.dispatch(notificationsSetVisibility(visibility));
if (visibility) {
this.props.dispatch(focusApp());
this.props.dispatch(submitMarkers({ immediate: true }));
@@ -436,7 +434,7 @@ class UI extends PureComponent {
if (signedIn) {
this.props.dispatch(fetchMarkers());
this.props.dispatch(expandHomeTimeline());
- this.props.dispatch(initializeNotifications());
+ this.props.dispatch(fetchNotifications());
this.props.dispatch(fetchServerTranslationLanguages());
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
diff --git a/app/javascript/flavours/glitch/locales/en.json b/app/javascript/flavours/glitch/locales/en.json
index 6384a971877806..567bd724596df0 100644
--- a/app/javascript/flavours/glitch/locales/en.json
+++ b/app/javascript/flavours/glitch/locales/en.json
@@ -51,14 +51,7 @@
"navigation_bar.app_settings": "App settings",
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
"navigation_bar.misc": "Misc",
- "notification.markForDeletion": "Mark for deletion",
- "notification_purge.btn_all": "Select\nall",
- "notification_purge.btn_apply": "Clear\nselected",
- "notification_purge.btn_invert": "Invert\nselection",
- "notification_purge.btn_none": "Select\nnone",
"notifications.column_settings.filter_bar.show_bar": "Show filter bar",
- "notifications.marked_clear": "Clear selected notifications",
- "notifications.marked_clear_confirmation": "Are you sure you want to permanently clear all selected notifications?",
"settings.always_show_spoilers_field": "Always enable the Content Warning field",
"settings.close": "Close",
"settings.compose_box_opts": "Compose box",
diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js
index 8453dd70385423..b47e24e3979c77 100644
--- a/app/javascript/flavours/glitch/reducers/notifications.js
+++ b/app/javascript/flavours/glitch/reducers/notifications.js
@@ -1,379 +1,32 @@
-import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import { fromJS, Map as ImmutableMap } from 'immutable';
-import { blockDomainSuccess } from 'flavours/glitch/actions/domain_blocks';
-import { timelineDelete } from 'flavours/glitch/actions/timelines_typed';
-
-import {
- authorizeFollowRequestSuccess,
- blockAccountSuccess,
- muteAccountSuccess,
- rejectFollowRequestSuccess,
-} from '../actions/accounts';
import {
- fetchMarkers,
-} from '../actions/markers';
-import { clearNotifications } from '../actions/notification_groups';
-import {
- NOTIFICATIONS_MOUNT,
- NOTIFICATIONS_UNMOUNT,
- NOTIFICATIONS_SET_VISIBILITY,
- notificationsUpdate,
- NOTIFICATIONS_EXPAND_SUCCESS,
- NOTIFICATIONS_EXPAND_REQUEST,
- NOTIFICATIONS_EXPAND_FAIL,
- NOTIFICATIONS_FILTER_SET,
- NOTIFICATIONS_SCROLL_TOP,
- NOTIFICATIONS_LOAD_PENDING,
- NOTIFICATIONS_DELETE_MARKED_REQUEST,
- NOTIFICATIONS_DELETE_MARKED_SUCCESS,
- NOTIFICATION_MARK_FOR_DELETE,
- NOTIFICATIONS_DELETE_MARKED_FAIL,
- NOTIFICATIONS_ENTER_CLEARING_MODE,
- NOTIFICATIONS_MARK_ALL_FOR_DELETE,
- NOTIFICATIONS_MARK_AS_READ,
NOTIFICATIONS_SET_BROWSER_SUPPORT,
NOTIFICATIONS_SET_BROWSER_PERMISSION,
} from '../actions/notifications';
-import { disconnectTimeline } from '../actions/timelines';
-import { compareId } from '../compare_id';
const initialState = ImmutableMap({
- pendingItems: ImmutableList(),
- items: ImmutableList(),
- hasMore: true,
- top: false,
- mounted: 0,
- unread: 0,
- lastReadId: '0',
- readMarkerId: '0',
- isLoading: 0,
- cleaningMode: false,
- isTabVisible: true,
browserSupport: false,
browserPermission: 'default',
- // notification removal mark of new notifs loaded whilst cleaningMode is true.
- markNewForDelete: false,
});
-export const notificationToMap = (notification) => ImmutableMap({
+export const notificationToMap = notification => ImmutableMap({
id: notification.id,
type: notification.type,
account: notification.account.id,
- markedForDelete: false,
+ created_at: notification.created_at,
status: notification.status ? notification.status.id : null,
report: notification.report ? fromJS(notification.report) : null,
event: notification.event ? fromJS(notification.event) : null,
moderation_warning: notification.moderation_warning ? fromJS(notification.moderation_warning) : null,
});
-const normalizeNotification = (state, notification, usePendingItems) => {
- const markNewForDelete = state.get('markNewForDelete');
- const top = state.get('top');
-
- // Under currently unknown conditions, the client may receive duplicates from the server
- if (state.get('pendingItems').some((item) => item?.get('id') === notification.id) || state.get('items').some((item) => item?.get('id') === notification.id)) {
- return state;
- }
-
- if (usePendingItems || !state.get('pendingItems').isEmpty()) {
- return state.update('pendingItems', list => list.unshift(notificationToMap(notification).set('markForDelete', markNewForDelete))).update('unread', unread => unread + 1);
- }
-
- if (shouldCountUnreadNotifications(state)) {
- state = state.update('unread', unread => unread + 1);
- } else {
- state = state.set('lastReadId', notification.id);
- }
-
- return state.update('items', list => {
- if (top && list.size > 40) {
- list = list.take(20);
- }
-
- return list.unshift(notificationToMap(notification).set('markForDelete', markNewForDelete));
- });
-};
-
-const expandNormalizedNotifications = (state, notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) => {
- // This method is pretty tricky because:
- // - existing notifications might be out of order
- // - the existing notifications may have gaps, most often explicitly noted with a `null` item
- // - ideally, we don't want it to reorder existing items
- // - `notifications` may include items that are already included
- // - this function can be called either to fill in a gap, or load newer items
-
- const markNewForDelete = state.get('markNewForDelete');
- const lastReadId = state.get('lastReadId');
- const newItems = ImmutableList(notifications.map((notification) => notificationToMap(notification).set('markForDelete', markNewForDelete)));
-
- return state.withMutations(mutable => {
- if (!newItems.isEmpty()) {
- usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('pendingItems').isEmpty());
-
- mutable.update(usePendingItems ? 'pendingItems' : 'items', oldItems => {
- // If called to poll *new* notifications, we just need to add them on top without duplicates
- if (isLoadingRecent) {
- const idsToCheck = oldItems.map(item => item?.get('id')).toSet();
- const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
- return insertedItems.concat(oldItems);
- }
-
- // If called to expand more (presumably older than any known to the WebUI), we just have to
- // add them to the bottom without duplicates
- if (isLoadingMore) {
- const idsToCheck = oldItems.map(item => item?.get('id')).toSet();
- const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
- return oldItems.concat(insertedItems);
- }
-
- // Now this gets tricky, as we don't necessarily know for sure where the gap to fill is,
- // and some items in the timeline may not be properly ordered.
-
- // However, we know that `newItems.last()` is the oldest item that was requested and that
- // there is no “hole” between `newItems.last()` and `newItems.first()`.
-
- // First, find the furthest (if properly sorted, oldest) item in the notifications that is
- // newer than the oldest fetched one, as it's most likely that it delimits the gap.
- // Start the gap *after* that item.
- const lastIndex = oldItems.findLastIndex(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) >= 0) + 1;
-
- // Then, try to find the furthest (if properly sorted, oldest) item in the notifications that
- // is newer than the most recent fetched one, as it delimits a section comprised of only
- // items older or within `newItems` (or that were deleted from the server, so should be removed
- // anyway).
- // Stop the gap *after* that item.
- const firstIndex = oldItems.take(lastIndex).findLastIndex(item => item !== null && compareId(item.get('id'), newItems.first().get('id')) > 0) + 1;
-
- // At this point:
- // - no `oldItems` after `firstIndex` is newer than any of the `newItems`
- // - all `oldItems` after `lastIndex` are older than every of the `newItems`
- // - it is possible for items in the replaced slice to be older than every `newItems`
- // - it is possible for items before `firstIndex` to be in the `newItems` range
- // Therefore:
- // - to avoid losing items, items from the replaced slice that are older than `newItems`
- // should be added in the back.
- // - to avoid duplicates, `newItems` should be checked the first `firstIndex` items of
- // `oldItems`
- const idsToCheck = oldItems.take(firstIndex).map(item => item?.get('id')).toSet();
- const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id')));
- const olderItems = oldItems.slice(firstIndex, lastIndex).filter(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) < 0);
-
- return oldItems.take(firstIndex).concat(
- insertedItems,
- olderItems,
- oldItems.skip(lastIndex),
- );
- });
- }
-
- if (!next) {
- mutable.set('hasMore', false);
- }
-
- if (shouldCountUnreadNotifications(state)) {
- mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), lastReadId) > 0));
- } else {
- const mostRecent = newItems.find(item => item !== null);
- if (mostRecent && compareId(lastReadId, mostRecent.get('id')) < 0) {
- mutable.set('lastReadId', mostRecent.get('id'));
- }
- }
-
- mutable.update('isLoading', (nbLoading) => nbLoading - 1);
- });
-};
-
-const filterNotifications = (state, accountIds, type) => {
- const helper = list => list.filterNot(item => item !== null && accountIds.includes(item.get('account')) && (type === undefined || type === item.get('type')));
- return state.update('items', helper).update('pendingItems', helper);
-};
-
-const clearUnread = (state) => {
- state = state.set('unread', state.get('pendingItems').size);
- const lastNotification = state.get('items').find(item => item !== null);
- return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0');
-};
-
-const updateTop = (state, top) => {
- state = state.set('top', top);
-
- if (!shouldCountUnreadNotifications(state)) {
- state = clearUnread(state);
- }
-
- return state;
-};
-
-const deleteByStatus = (state, statusId) => {
- const lastReadId = state.get('lastReadId');
-
- if (shouldCountUnreadNotifications(state)) {
- const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
- state = state.update('unread', unread => unread - deletedUnread.size);
- }
-
- const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId);
- const deletedUnread = state.get('pendingItems').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
- state = state.update('unread', unread => unread - deletedUnread.size);
- return state.update('items', helper).update('pendingItems', helper);
-};
-
-const markForDelete = (state, notificationId, yes) => {
- return state.update('items', list => list.map(item => {
- if (item === null) {
- return null;
- } else if(item.get('id') === notificationId) {
- return item.set('markedForDelete', yes);
- } else {
- return item;
- }
- }));
-};
-
-const markAllForDelete = (state, yes) => {
- return state.update('items', list => list.map(item => {
- if (item === null) {
- return null;
- } else if(yes !== null) {
- return item.set('markedForDelete', yes);
- } else {
- return item.set('markedForDelete', !item.get('markedForDelete'));
- }
- }));
-};
-
-const unmarkAllForDelete = (state) => {
- return state.update('items', list => list.map(item => item === null ? item : item.set('markedForDelete', false)));
-};
-
-const deleteMarkedNotifs = (state) => {
- return state.update('items', list => list.filterNot(item => item === null ? item : item.get('markedForDelete')));
-};
-
-const updateMounted = (state) => {
- state = state.update('mounted', count => count + 1);
- if (!shouldCountUnreadNotifications(state, state.get('mounted') === 1)) {
- state = state.set('readMarkerId', state.get('lastReadId'));
- state = clearUnread(state);
- }
- return state;
-};
-
-const updateVisibility = (state, visibility) => {
- state = state.set('isTabVisible', visibility);
- if (!shouldCountUnreadNotifications(state)) {
- state = state.set('readMarkerId', state.get('lastReadId'));
- state = clearUnread(state);
- }
- return state;
-};
-
-const shouldCountUnreadNotifications = (state, ignoreScroll = false) => {
- const isTabVisible = state.get('isTabVisible');
- const isOnTop = state.get('top');
- const isMounted = state.get('mounted') > 0;
- const lastReadId = state.get('lastReadId');
- const lastItem = state.get('items').findLast(item => item !== null);
- const lastItemReached = !state.get('hasMore') || lastReadId === '0' || (lastItem && compareId(lastItem.get('id'), lastReadId) <= 0);
-
- return !(isTabVisible && (ignoreScroll || isOnTop) && isMounted && lastItemReached);
-};
-
-const recountUnread = (state, last_read_id) => {
- return state.withMutations(mutable => {
- if (compareId(last_read_id, mutable.get('lastReadId')) > 0) {
- mutable.set('lastReadId', last_read_id);
- }
-
- if (compareId(last_read_id, mutable.get('readMarkerId')) > 0) {
- mutable.set('readMarkerId', last_read_id);
- }
-
- if (state.get('unread') > 0 || shouldCountUnreadNotifications(state)) {
- mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), last_read_id) > 0));
- }
- });
-};
-
export default function notifications(state = initialState, action) {
- let st;
-
switch(action.type) {
- case fetchMarkers.fulfilled.type:
- return action.payload.markers.notifications ? recountUnread(state, action.payload.markers.notifications.last_read_id) : state;
- case NOTIFICATIONS_MOUNT:
- return updateMounted(state);
- case NOTIFICATIONS_UNMOUNT:
- return state.update('mounted', count => count - 1);
- case NOTIFICATIONS_SET_VISIBILITY:
- return updateVisibility(state, action.visibility);
- case NOTIFICATIONS_LOAD_PENDING:
- return state.update('items', list => state.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0);
- case NOTIFICATIONS_EXPAND_REQUEST:
- case NOTIFICATIONS_DELETE_MARKED_REQUEST:
- return state.update('isLoading', (nbLoading) => nbLoading + 1);
- case NOTIFICATIONS_DELETE_MARKED_FAIL:
- case NOTIFICATIONS_EXPAND_FAIL:
- return state.update('isLoading', (nbLoading) => nbLoading - 1);
- case NOTIFICATIONS_FILTER_SET:
- return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', true);
- case NOTIFICATIONS_SCROLL_TOP:
- return updateTop(state, action.top);
- case notificationsUpdate.type:
- return normalizeNotification(state, action.payload.notification, action.payload.usePendingItems);
- case NOTIFICATIONS_EXPAND_SUCCESS:
- return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingMore, action.isLoadingRecent, action.usePendingItems);
- case blockAccountSuccess.type:
- return filterNotifications(state, [action.payload.relationship.id]);
- case muteAccountSuccess.type:
- return action.payload.relationship.muting_notifications ? filterNotifications(state, [action.payload.relationship.id]) : state;
- case blockDomainSuccess.type:
- return filterNotifications(state, action.payload.accounts);
- case authorizeFollowRequestSuccess.type:
- case rejectFollowRequestSuccess.type:
- return filterNotifications(state, [action.payload.id], 'follow_request');
- case clearNotifications.pending.type:
- return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false);
- case timelineDelete.type:
- return deleteByStatus(state, action.payload.statusId);
- case disconnectTimeline.type:
- return action.payload.timeline === 'home' ?
- state.update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
- state;
case NOTIFICATIONS_SET_BROWSER_SUPPORT:
return state.set('browserSupport', action.value);
case NOTIFICATIONS_SET_BROWSER_PERMISSION:
return state.set('browserPermission', action.value);
-
- case NOTIFICATION_MARK_FOR_DELETE:
- return markForDelete(state, action.id, action.yes);
-
- case NOTIFICATIONS_DELETE_MARKED_SUCCESS:
- return deleteMarkedNotifs(state).update('isLoading', (nbLoading) => nbLoading - 1);
-
- case NOTIFICATIONS_ENTER_CLEARING_MODE:
- st = state.set('cleaningMode', action.yes);
- if (!action.yes) {
- return unmarkAllForDelete(st).set('markNewForDelete', false);
- } else {
- return st;
- }
-
- case NOTIFICATIONS_MARK_ALL_FOR_DELETE:
- st = state;
- if (action.yes === null) {
- // Toggle - this is a bit confusing, as it toggles the all-none mode
- //st = st.set('markNewForDelete', !st.get('markNewForDelete'));
- } else {
- st = st.set('markNewForDelete', action.yes);
- }
- return markAllForDelete(st, action.yes);
-
- case NOTIFICATIONS_MARK_AS_READ: {
- const lastNotification = state.get('items').find(item => item !== null);
- return lastNotification ? recountUnread(state, lastNotification.get('id')) : state;
- }
-
default:
return state;
}