const excludeTypesFromFilter = filter => {
- const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']);
+ const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll']);
return allTypes.filterNot(item => item === filter).toJS();
};
</div>
</div>
+ <div role='group' aria-labelledby='notifications-follow-request'>
+ <span id='notifications-follow-request' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow_request' defaultMessage='New follow requests:' /></span>
+
+ <div className='column-settings__row'>
+ <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow_request']} onChange={onChange} label={alertStr} />
+ {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow_request']} onChange={this.onPushChange} label={pushStr} />}
+ <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow_request']} onChange={onChange} label={showStr} />
+ <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow_request']} onChange={onChange} label={soundStr} />
+ </div>
+ </div>
+
<div role='group' aria-labelledby='notifications-favourite'>
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
--- /dev/null
+import React, { Fragment } from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import Avatar from 'flavours/glitch/components/avatar';
+import DisplayName from 'flavours/glitch/components/display_name';
+import Permalink from 'flavours/glitch/components/permalink';
+import IconButton from 'flavours/glitch/components/icon_button';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import NotificationOverlayContainer from '../containers/overlay_container';
+import { HotKeys } from 'react-hotkeys';
+import Icon from 'flavours/glitch/components/icon';
+
+const messages = defineMessages({
+ authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
+ reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
+});
+
+export default @injectIntl
+class FollowRequest extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ onAuthorize: PropTypes.func.isRequired,
+ onReject: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ notification: ImmutablePropTypes.map.isRequired,
+ };
+
+ handleMoveUp = () => {
+ const { notification, onMoveUp } = this.props;
+ onMoveUp(notification.get('id'));
+ }
+
+ handleMoveDown = () => {
+ const { notification, onMoveDown } = this.props;
+ onMoveDown(notification.get('id'));
+ }
+
+ handleOpen = () => {
+ this.handleOpenProfile();
+ }
+
+ handleOpenProfile = () => {
+ const { notification } = this.props;
+ this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`);
+ }
+
+ handleMention = e => {
+ e.preventDefault();
+
+ const { notification, onMention } = this.props;
+ onMention(notification.get('account'), this.context.router.history);
+ }
+
+ getHandlers () {
+ return {
+ moveUp: this.handleMoveUp,
+ moveDown: this.handleMoveDown,
+ open: this.handleOpen,
+ openProfile: this.handleOpenProfile,
+ mention: this.handleMention,
+ reply: this.handleMention,
+ };
+ }
+
+ render () {
+ const { intl, hidden, account, onAuthorize, onReject, notification } = this.props;
+
+ if (!account) {
+ return <div />;
+ }
+
+ if (hidden) {
+ return (
+ <Fragment>
+ {account.get('display_name')}
+ {account.get('username')}
+ </Fragment>
+ );
+ }
+
+ // Links to the display name.
+ const displayName = account.get('display_name_html') || account.get('username');
+ const link = (
+ <bdi><Permalink
+ className='notification__display-name'
+ href={account.get('url')}
+ title={account.get('acct')}
+ to={`/accounts/${account.get('id')}`}
+ dangerouslySetInnerHTML={{ __html: displayName }}
+ /></bdi>
+ );
+
+ return (
+ <HotKeys handlers={this.getHandlers()}>
+ <div className='notification notification-follow-request focusable' tabIndex='0'>
+ <div className='notification__message'>
+ <div className='notification__favourite-icon-wrapper'>
+ <Icon id='user' fixedWidth />
+ </div>
+
+ <FormattedMessage
+ id='notification.follow_request'
+ defaultMessage='{name} has requested to follow you'
+ values={{ name: link }}
+ />
+ </div>
+
+ <div className='account'>
+ <div className='account__wrapper'>
+ <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
+ <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
+ <DisplayName account={account} />
+ </Permalink>
+
+ <div className='account__relationship'>
+ <IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} />
+ <IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} />
+ </div>
+ </div>
+ </div>
+
+ <NotificationOverlayContainer notification={notification} />
+ </div>
+ </HotKeys>
+ );
+ }
+
+}
// Our imports,
import StatusContainer from 'flavours/glitch/containers/status_container';
import NotificationFollow from './follow';
+import NotificationFollowRequestContainer from '../containers/follow_request_container';
export default class Notification extends ImmutablePureComponent {
onMention={onMention}
/>
);
+ case 'follow_request':
+ return (
+ <NotificationFollowRequestContainer
+ hidden={hidden}
+ id={notification.get('id')}
+ account={notification.get('account')}
+ notification={notification}
+ onMoveDown={onMoveDown}
+ onMoveUp={onMoveUp}
+ onMention={onMention}
+ />
+ );
case 'mention':
return (
<StatusContainer
--- /dev/null
+import { connect } from 'react-redux';
+import { makeGetAccount } from 'flavours/glitch/selectors';
+import FollowRequest from '../components/follow_request';
+import { authorizeFollowRequest, rejectFollowRequest } from 'flavours/glitch/actions/accounts';
+
+const mapDispatchToProps = (dispatch, { account }) => ({
+ onAuthorize () {
+ dispatch(authorizeFollowRequest(account.get('id')));
+ },
+
+ onReject () {
+ dispatch(rejectFollowRequest(account.get('id')));
+ },
+});
+
+export default connect(null, mapDispatchToProps)(FollowRequest);
import { connect } from 'react-redux';
import { NavLink, withRouter } from 'react-router-dom';
import IconWithBadge from 'flavours/glitch/components/icon_with_badge';
-import { me } from 'flavours/glitch/util/initial_state';
import { List as ImmutableList } from 'immutable';
import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
- locked: state.getIn(['accounts', me, 'locked']),
count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
});
static propTypes = {
dispatch: PropTypes.func.isRequired,
- locked: PropTypes.bool,
count: PropTypes.number.isRequired,
};
componentDidMount () {
- const { dispatch, locked } = this.props;
+ const { dispatch } = this.props;
- if (locked) {
- dispatch(fetchFollowRequests());
- }
+ dispatch(fetchFollowRequests());
}
render () {
- const { locked, count } = this.props;
+ const { count } = this.props;
- if (!locked || count === 0) {
+ if (count === 0) {
return null;
}
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
+ FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
+ FOLLOW_REQUEST_REJECT_SUCCESS,
} from 'flavours/glitch/actions/accounts';
import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks';
import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines';
});
};
-const filterNotifications = (state, accountIds) => {
- const helper = list => list.filterNot(item => item !== null && accountIds.includes(item.get('account')));
+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);
};
return action.relationship.muting_notifications ? filterNotifications(state, [action.relationship.id]) : state;
case DOMAIN_BLOCK_SUCCESS:
return filterNotifications(state, action.accounts);
+ case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
+ case FOLLOW_REQUEST_REJECT_SUCCESS:
+ return filterNotifications(state, [action.id], 'follow_request');
+ case ACCOUNT_MUTE_SUCCESS:
+ return action.relationship.muting_notifications ? filterNotifications(state, [action.relationship.id]) : state;
case NOTIFICATIONS_CLEAR:
return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false);
case TIMELINE_DELETE:
subscription: null,
alerts: new Immutable.Map({
follow: false,
+ follow_request: false,
favourite: false,
reblog: false,
mention: false,
notifications: ImmutableMap({
alerts: ImmutableMap({
follow: true,
+ follow_request: false,
favourite: true,
reblog: true,
mention: true,
shows: ImmutableMap({
follow: true,
+ follow_request: false,
favourite: true,
reblog: true,
mention: true,
sounds: ImmutableMap({
follow: true,
+ follow_request: false,
favourite: true,
reblog: true,
mention: true,
+import {
+ NOTIFICATIONS_UPDATE,
+} from '../actions/notifications';
import {
FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS,
});
};
+const normalizeFollowRequest = (state, notification) => {
+ return state.updateIn(['follow_requests', 'items'], list => {
+ return list.filterNot(item => item === notification.account.id).unshift(notification.account.id);
+ });
+};
+
export default function userLists(state = initialState, action) {
switch(action.type) {
case FOLLOWERS_FETCH_SUCCESS:
return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
case FAVOURITES_FETCH_SUCCESS:
return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
+ case NOTIFICATIONS_UPDATE:
+ return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS:
return state.setIn(['follow_requests', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
}
.notif-cleaning {
- .status, .notification-follow {
+ .status,
+ .notification-follow,
+ .notification-follow-request {
padding-right: ($dismiss-overlay-width + 0.5rem);
}
}
position: absolute;
}
-.notification-follow {
+.notification-follow,
+.notification-follow-request {
position: relative;
// same like Status