mirror of
https://github.com/mastodon/mastodon.git
synced 2025-01-01 16:05:08 +01:00
[WiP] Remove old notifications code
This commit is contained in:
parent
cdcd834f3c
commit
e79ed3dcab
@ -2,7 +2,6 @@ import { debounce } from 'lodash';
|
|||||||
|
|
||||||
import type { MarkerJSON } from 'mastodon/api_types/markers';
|
import type { MarkerJSON } from 'mastodon/api_types/markers';
|
||||||
import { getAccessToken } from 'mastodon/initial_state';
|
import { getAccessToken } from 'mastodon/initial_state';
|
||||||
import { selectUseGroupedNotifications } from 'mastodon/selectors/settings';
|
|
||||||
import type { AppDispatch, RootState } from 'mastodon/store';
|
import type { AppDispatch, RootState } from 'mastodon/store';
|
||||||
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
|
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
@ -76,12 +75,7 @@ interface MarkerParam {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLastNotificationId(state: RootState): string | undefined {
|
function getLastNotificationId(state: RootState): string | undefined {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
return state.notificationGroups.lastReadId;
|
||||||
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']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildPostMarkersParams = (state: RootState) => {
|
const buildPostMarkersParams = (state: RootState) => {
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import { selectUseGroupedNotifications } from 'mastodon/selectors/settings';
|
|
||||||
import { createAppAsyncThunk } from 'mastodon/store';
|
import { createAppAsyncThunk } from 'mastodon/store';
|
||||||
|
|
||||||
import { fetchNotifications } from './notification_groups';
|
import { fetchNotifications } from './notification_groups';
|
||||||
import { expandNotifications } from './notifications';
|
|
||||||
|
|
||||||
export const initializeNotifications = createAppAsyncThunk(
|
export const initializeNotifications = createAppAsyncThunk(
|
||||||
'notifications/initialize',
|
'notifications/initialize',
|
||||||
(_, { dispatch, getState }) => {
|
(_, { dispatch }) => {
|
||||||
if (selectUseGroupedNotifications(getState()))
|
void dispatch(fetchNotifications());
|
||||||
void dispatch(fetchNotifications());
|
|
||||||
else void dispatch(expandNotifications({}));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import { selectUseGroupedNotifications } from 'mastodon/selectors/settings';
|
|
||||||
|
|
||||||
import { getLocale } from '../locales';
|
import { getLocale } from '../locales';
|
||||||
import { connectStream } from '../stream';
|
import { connectStream } from '../stream';
|
||||||
|
|
||||||
@ -13,7 +11,7 @@ import {
|
|||||||
} from './announcements';
|
} from './announcements';
|
||||||
import { updateConversations } from './conversations';
|
import { updateConversations } from './conversations';
|
||||||
import { processNewNotificationForGroups, refreshStaleNotificationGroups, pollRecentNotifications as pollRecentGroupNotifications } from './notification_groups';
|
import { processNewNotificationForGroups, refreshStaleNotificationGroups, pollRecentNotifications as pollRecentGroupNotifications } from './notification_groups';
|
||||||
import { updateNotifications, expandNotifications } from './notifications';
|
import { updateNotifications } from './notifications';
|
||||||
import { updateStatus } from './statuses';
|
import { updateStatus } from './statuses';
|
||||||
import {
|
import {
|
||||||
updateTimeline,
|
updateTimeline,
|
||||||
@ -39,7 +37,7 @@ const randomUpTo = max =>
|
|||||||
* @param {string} channelName
|
* @param {string} channelName
|
||||||
* @param {Object.<string, string>} params
|
* @param {Object.<string, string>} params
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {function(Function, Function): Promise<void>} [options.fallback]
|
* @param {function(Function): Promise<void>} [options.fallback]
|
||||||
* @param {function(): void} [options.fillGaps]
|
* @param {function(): void} [options.fillGaps]
|
||||||
* @param {function(object): boolean} [options.accept]
|
* @param {function(object): boolean} [options.accept]
|
||||||
* @returns {function(): void}
|
* @returns {function(): void}
|
||||||
@ -54,11 +52,11 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||||||
let pollingId;
|
let pollingId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {function(Function, Function): Promise<void>} fallback
|
* @param {function(Function): Promise<void>} fallback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const useFallback = async 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
|
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
|
||||||
pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000));
|
pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000));
|
||||||
};
|
};
|
||||||
@ -103,20 +101,13 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||||||
case 'notification': {
|
case 'notification': {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const notificationJSON = JSON.parse(data.payload);
|
const notificationJSON = JSON.parse(data.payload);
|
||||||
dispatch(updateNotifications(notificationJSON, messages, locale));
|
|
||||||
// TODO: remove this once the groups feature replaces the previous one
|
// TODO: remove this once the groups feature replaces the previous one
|
||||||
if(selectUseGroupedNotifications(getState())) {
|
dispatch(updateNotifications(notificationJSON, messages, locale));
|
||||||
dispatch(processNewNotificationForGroups(notificationJSON));
|
dispatch(processNewNotificationForGroups(notificationJSON));
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'notifications_merged':
|
case 'notifications_merged':
|
||||||
const state = getState();
|
dispatch(refreshStaleNotificationGroups());
|
||||||
if (state.notifications.top || !state.notifications.mounted)
|
|
||||||
dispatch(expandNotifications({ forceLoad: true, maxId: undefined }));
|
|
||||||
if (selectUseGroupedNotifications(state)) {
|
|
||||||
dispatch(refreshStaleNotificationGroups());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'conversation':
|
case 'conversation':
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@ -141,21 +132,14 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Function} dispatch
|
* @param {Function} dispatch
|
||||||
* @param {Function} getState
|
|
||||||
*/
|
*/
|
||||||
async function refreshHomeTimelineAndNotification(dispatch, getState) {
|
async function refreshHomeTimelineAndNotification(dispatch) {
|
||||||
await dispatch(expandHomeTimeline({ maxId: undefined }));
|
await dispatch(expandHomeTimeline({ maxId: undefined }));
|
||||||
|
|
||||||
// TODO: remove this once the groups feature replaces the previous one
|
try {
|
||||||
if(selectUseGroupedNotifications(getState())) {
|
await dispatch(pollRecentGroupNotifications());
|
||||||
// TODO: polling for merged notifications
|
} catch (error) {
|
||||||
try {
|
// TODO
|
||||||
await dispatch(pollRecentGroupNotifications());
|
|
||||||
} catch (error) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await dispatch(expandNotifications({}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await dispatch(fetchAnnouncements());
|
await dispatch(fetchAnnouncements());
|
||||||
|
@ -6,7 +6,6 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||||
import { forceGroupedNotifications } from 'mastodon/initial_state';
|
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
|
||||||
|
|
||||||
import ClearColumnButton from './clear_column_button';
|
import ClearColumnButton from './clear_column_button';
|
||||||
@ -36,7 +35,6 @@ class ColumnSettings extends PureComponent {
|
|||||||
|
|
||||||
const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
|
const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
|
||||||
const unreadMarkersShowStr = <FormattedMessage id='notifications.column_settings.unread_notifications.highlight' defaultMessage='Highlight unread notifications' />;
|
const unreadMarkersShowStr = <FormattedMessage id='notifications.column_settings.unread_notifications.highlight' defaultMessage='Highlight unread notifications' />;
|
||||||
const groupingShowStr = <FormattedMessage id='notifications.column_settings.beta.grouping' defaultMessage='Group notifications' />;
|
|
||||||
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' />;
|
||||||
@ -68,18 +66,6 @@ class ColumnSettings extends PureComponent {
|
|||||||
|
|
||||||
<PolicyControls />
|
<PolicyControls />
|
||||||
|
|
||||||
{!forceGroupedNotifications && (
|
|
||||||
<section role='group' aria-labelledby='notifications-beta'>
|
|
||||||
<h3 id='notifications-beta'>
|
|
||||||
<FormattedMessage id='notifications.column_settings.beta.category' defaultMessage='Experimental features' />
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className='column-settings__row'>
|
|
||||||
<SettingToggle id='unread-notification-markers' prefix='notifications' settings={settings} settingPath={['groupingBeta']} onChange={onChange} label={groupingShowStr} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<section role='group' aria-labelledby='notifications-unread-markers'>
|
<section role='group' aria-labelledby='notifications-unread-markers'>
|
||||||
<h3 id='notifications-unread-markers'>
|
<h3 id='notifications-unread-markers'>
|
||||||
<FormattedMessage id='notifications.column_settings.unread_notifications.category' defaultMessage='Unread notifications' />
|
<FormattedMessage id='notifications.column_settings.unread_notifications.category' defaultMessage='Unread notifications' />
|
||||||
|
@ -3,7 +3,6 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
import { initializeNotifications } from 'mastodon/actions/notifications_migration';
|
|
||||||
|
|
||||||
import { showAlert } from '../../../actions/alerts';
|
import { showAlert } from '../../../actions/alerts';
|
||||||
import { setFilter, requestBrowserPermission } from '../../../actions/notifications';
|
import { setFilter, requestBrowserPermission } from '../../../actions/notifications';
|
||||||
@ -56,9 +55,6 @@ 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));
|
||||||
}
|
}
|
||||||
|
@ -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 = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
|
|
||||||
const { signedIn } = this.props.identity;
|
|
||||||
|
|
||||||
let scrollableContent = null;
|
|
||||||
|
|
||||||
const filterBarContainer = signedIn
|
|
||||||
? (<FilterBarContainer />)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (isLoading && this.scrollableContent) {
|
|
||||||
scrollableContent = this.scrollableContent;
|
|
||||||
} else if (notifications.size > 0 || hasMore) {
|
|
||||||
scrollableContent = notifications.map((item, index) => item === null ? (
|
|
||||||
<LoadGap
|
|
||||||
key={'gap:' + notifications.getIn([index + 1, 'id'])}
|
|
||||||
disabled={isLoading}
|
|
||||||
param={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
|
|
||||||
onClick={this.handleLoadGap}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<NotificationContainer
|
|
||||||
key={item.get('id')}
|
|
||||||
notification={item}
|
|
||||||
accountId={item.get('account')}
|
|
||||||
onMoveUp={this.handleMoveUp}
|
|
||||||
onMoveDown={this.handleMoveDown}
|
|
||||||
unread={lastReadId !== '0' && compareId(item.get('id'), lastReadId) > 0}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
scrollableContent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollableContent = scrollableContent;
|
|
||||||
|
|
||||||
let scrollContainer;
|
|
||||||
|
|
||||||
const prepend = (
|
|
||||||
<>
|
|
||||||
{needsNotificationPermission && <NotificationsPermissionBanner />}
|
|
||||||
<FilteredNotificationsBanner />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (signedIn) {
|
|
||||||
scrollContainer = (
|
|
||||||
<ScrollableList
|
|
||||||
scrollKey={`notifications-${columnId}`}
|
|
||||||
trackScroll={!pinned}
|
|
||||||
isLoading={isLoading}
|
|
||||||
showLoading={isLoading && notifications.size === 0}
|
|
||||||
hasMore={hasMore}
|
|
||||||
numPending={numPending}
|
|
||||||
prepend={prepend}
|
|
||||||
alwaysPrepend
|
|
||||||
emptyMessage={emptyMessage}
|
|
||||||
onLoadMore={this.handleLoadOlder}
|
|
||||||
onLoadPending={this.handleLoadPending}
|
|
||||||
onScrollToTop={this.handleScrollToTop}
|
|
||||||
onScroll={this.handleScroll}
|
|
||||||
bindToDocument={!multiColumn}
|
|
||||||
>
|
|
||||||
{scrollableContent}
|
|
||||||
</ScrollableList>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
scrollContainer = <NotSignedInIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extraButton = (
|
|
||||||
<>
|
|
||||||
<FilteredNotificationsIconButton className='column-header__button' />
|
|
||||||
{canMarkAsRead && (
|
|
||||||
<button
|
|
||||||
aria-label={intl.formatMessage(messages.markAsRead)}
|
|
||||||
title={intl.formatMessage(messages.markAsRead)}
|
|
||||||
onClick={this.handleMarkAsRead}
|
|
||||||
className='column-header__button'
|
|
||||||
>
|
|
||||||
<Icon id='done-all' icon={DoneAllIcon} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
|
|
||||||
<ColumnHeader
|
|
||||||
icon='bell'
|
|
||||||
iconComponent={NotificationsIcon}
|
|
||||||
active={isUnread}
|
|
||||||
title={intl.formatMessage(messages.title)}
|
|
||||||
onPin={this.handlePin}
|
|
||||||
onMove={this.handleMove}
|
|
||||||
onClick={this.handleHeaderClick}
|
|
||||||
pinned={pinned}
|
|
||||||
multiColumn={multiColumn}
|
|
||||||
extraButton={extraButton}
|
|
||||||
>
|
|
||||||
<ColumnSettingsContainer />
|
|
||||||
</ColumnHeader>
|
|
||||||
|
|
||||||
{filterBarContainer}
|
|
||||||
|
|
||||||
{scrollContainer}
|
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<title>{intl.formatMessage(messages.title)}</title>
|
|
||||||
<meta name='robots' content='noindex' />
|
|
||||||
</Helmet>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));
|
|
@ -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 ? <Notifications_v2 {...props} /> : <Notifications {...props} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NotificationsWrapper;
|
|
@ -10,7 +10,7 @@ import { scrollRight } from '../../../scroll';
|
|||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
import {
|
import {
|
||||||
Compose,
|
Compose,
|
||||||
NotificationsWrapper,
|
Notifications,
|
||||||
HomeTimeline,
|
HomeTimeline,
|
||||||
CommunityTimeline,
|
CommunityTimeline,
|
||||||
PublicTimeline,
|
PublicTimeline,
|
||||||
@ -32,7 +32,7 @@ import NavigationPanel from './navigation_panel';
|
|||||||
const componentMap = {
|
const componentMap = {
|
||||||
'COMPOSE': Compose,
|
'COMPOSE': Compose,
|
||||||
'HOME': HomeTimeline,
|
'HOME': HomeTimeline,
|
||||||
'NOTIFICATIONS': NotificationsWrapper,
|
'NOTIFICATIONS': Notifications,
|
||||||
'PUBLIC': PublicTimeline,
|
'PUBLIC': PublicTimeline,
|
||||||
'REMOTE': PublicTimeline,
|
'REMOTE': PublicTimeline,
|
||||||
'COMMUNITY': CommunityTimeline,
|
'COMMUNITY': CommunityTimeline,
|
||||||
|
@ -37,7 +37,6 @@ import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
|
|||||||
import { transientSingleColumn } from 'mastodon/is_mobile';
|
import { transientSingleColumn } from 'mastodon/is_mobile';
|
||||||
import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
|
import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
|
||||||
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
||||||
import { selectUseGroupedNotifications } from 'mastodon/selectors/settings';
|
|
||||||
|
|
||||||
import ColumnLink from './column_link';
|
import ColumnLink from './column_link';
|
||||||
import DisabledAccountBanner from './disabled_account_banner';
|
import DisabledAccountBanner from './disabled_account_banner';
|
||||||
@ -65,19 +64,16 @@ const messages = defineMessages({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const NotificationsLink = () => {
|
const NotificationsLink = () => {
|
||||||
const optedInGroupedNotifications = useSelector(selectUseGroupedNotifications);
|
const count = useSelector(selectUnreadNotificationGroupsCount);
|
||||||
const count = useSelector(state => state.getIn(['notifications', 'unread']));
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const newCount = useSelector(selectUnreadNotificationGroupsCount);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColumnLink
|
<ColumnLink
|
||||||
key='notifications'
|
key='notifications'
|
||||||
transparent
|
transparent
|
||||||
to='/notifications'
|
to='/notifications'
|
||||||
icon={<IconWithBadge id='bell' icon={NotificationsIcon} count={optedInGroupedNotifications ? newCount : count} className='column-link__icon' />}
|
icon={<IconWithBadge id='bell' icon={NotificationsIcon} count={count} className='column-link__icon' />}
|
||||||
activeIcon={<IconWithBadge id='bell' icon={NotificationsActiveIcon} count={optedInGroupedNotifications ? newCount : count} className='column-link__icon' />}
|
activeIcon={<IconWithBadge id='bell' icon={NotificationsActiveIcon} count={count} className='column-link__icon' />}
|
||||||
text={intl.formatMessage(messages.notifications)}
|
text={intl.formatMessage(messages.notifications)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -49,7 +49,7 @@ import {
|
|||||||
Favourites,
|
Favourites,
|
||||||
DirectTimeline,
|
DirectTimeline,
|
||||||
HashtagTimeline,
|
HashtagTimeline,
|
||||||
NotificationsWrapper,
|
Notifications,
|
||||||
NotificationRequests,
|
NotificationRequests,
|
||||||
NotificationRequest,
|
NotificationRequest,
|
||||||
FollowRequests,
|
FollowRequests,
|
||||||
@ -206,7 +206,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||||
<WrappedRoute path='/links/:url' component={LinkTimeline} content={children} />
|
<WrappedRoute path='/links/:url' component={LinkTimeline} content={children} />
|
||||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
||||||
<WrappedRoute path='/notifications' component={NotificationsWrapper} content={children} exact />
|
<WrappedRoute path='/notifications' component={Notifications} content={children} exact />
|
||||||
<WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
|
<WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
|
||||||
<WrappedRoute path='/notifications/requests/:id' component={NotificationRequest} content={children} exact />
|
<WrappedRoute path='/notifications/requests/:id' component={NotificationRequest} content={children} exact />
|
||||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||||
|
@ -7,15 +7,7 @@ export function Compose () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Notifications () {
|
export function Notifications () {
|
||||||
return import(/* webpackChunkName: "features/notifications_v1" */'../../notifications');
|
return import(/* webpackChunkName: "features/notifications" */'../../notifications_v2');
|
||||||
}
|
|
||||||
|
|
||||||
export function Notifications_v2 () {
|
|
||||||
return import(/* webpackChunkName: "features/notifications_v2" */'../../notifications_v2');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NotificationsWrapper () {
|
|
||||||
return import(/* webpackChunkName: "features/notifications" */'../../notifications_wrapper');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HomeTimeline () {
|
export function HomeTimeline () {
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
* @property {boolean=} use_pending_items
|
* @property {boolean=} use_pending_items
|
||||||
* @property {string} version
|
* @property {string} version
|
||||||
* @property {string} sso_redirect
|
* @property {string} sso_redirect
|
||||||
* @property {boolean} force_grouped_notifications
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,7 +118,6 @@ export const criticalUpdatesPending = initialState?.critical_updates_pending;
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
export const statusPageUrl = getMeta('status_page_url');
|
export const statusPageUrl = getMeta('status_page_url');
|
||||||
export const sso_redirect = getMeta('sso_redirect');
|
export const sso_redirect = getMeta('sso_redirect');
|
||||||
export const forceGroupedNotifications = getMeta('force_grouped_notifications');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string | undefined}
|
* @returns {string | undefined}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { forceGroupedNotifications } from 'mastodon/initial_state';
|
|
||||||
import type { RootState } from 'mastodon/store';
|
import type { RootState } from 'mastodon/store';
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
/* 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;
|
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) =>
|
export const selectSettingsNotificationsShowUnread = (state: RootState) =>
|
||||||
state.settings.getIn(['notifications', 'showUnread']) as boolean;
|
state.settings.getIn(['notifications', 'showUnread']) as boolean;
|
||||||
|
|
||||||
|
@ -109,7 +109,6 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||||||
trends_as_landing_page: Setting.trends_as_landing_page,
|
trends_as_landing_page: Setting.trends_as_landing_page,
|
||||||
trends_enabled: Setting.trends,
|
trends_enabled: Setting.trends,
|
||||||
version: instance_presenter.version,
|
version: instance_presenter.version,
|
||||||
force_grouped_notifications: ENV['FORCE_GROUPED_NOTIFICATIONS'] == 'true',
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user