Added follow requests to notifications page
• Added: - follow requests to notifications • Updated: - ui.js will mount to listen for path for follow_requests to set filter - notification action expandNotifications to not request if filter is follow_request - order of notification_filters - AccountAuthorize - FollowRequest page
This commit is contained in:
parent
3bea8213ce
commit
66fc8269cc
@ -153,7 +153,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
|
|||||||
const notifications = getState().get('notifications')
|
const notifications = getState().get('notifications')
|
||||||
const isLoadingMore = !!maxId
|
const isLoadingMore = !!maxId
|
||||||
|
|
||||||
if (notifications.get('isLoading')) {
|
if (notifications.get('isLoading') || activeFilter === 'follow_requests') {
|
||||||
done();
|
done();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
61
app/javascript/gabsocial/components/account_authorize.js
Normal file
61
app/javascript/gabsocial/components/account_authorize.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import { authorizeFollowRequest, rejectFollowRequest } from '../actions/accounts'
|
||||||
|
import { makeGetAccount } from '../selectors'
|
||||||
|
import Account from './account'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
|
||||||
|
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const makeMapStateToProps = (state, props) => ({
|
||||||
|
account: makeGetAccount()(state, props.id),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { id }) => ({
|
||||||
|
onAuthorize() {
|
||||||
|
dispatch(authorizeFollowRequest(id))
|
||||||
|
},
|
||||||
|
onReject() {
|
||||||
|
dispatch(rejectFollowRequest(id))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default
|
||||||
|
@connect(makeMapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class AccountAuthorize extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
onAuthorize: PropTypes.func.isRequired,
|
||||||
|
onReject: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
intl,
|
||||||
|
account,
|
||||||
|
onAuthorize,
|
||||||
|
onReject,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
// <Button title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} />
|
||||||
|
// <Button title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Account
|
||||||
|
id={account.get('id')}
|
||||||
|
dismissAction={onReject}
|
||||||
|
onActionClick={onAuthorize}
|
||||||
|
actionIcon='add'
|
||||||
|
showDismiss
|
||||||
|
withBio
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -97,6 +97,7 @@ export const NOTIFICATION_FILTERS = [
|
|||||||
'mention',
|
'mention',
|
||||||
'favourite',
|
'favourite',
|
||||||
'reblog',
|
'reblog',
|
||||||
'poll',
|
|
||||||
'follow',
|
'follow',
|
||||||
|
'poll',
|
||||||
|
'follow_requests',
|
||||||
]
|
]
|
90
app/javascript/gabsocial/features/follow_requests.js
Normal file
90
app/javascript/gabsocial/features/follow_requests.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { FormattedMessage } from 'react-intl'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
|
import { me } from '../initial_state'
|
||||||
|
import { fetchFollowRequests, expandFollowRequests } from '../actions/accounts'
|
||||||
|
import AccountAuthorize from '../components/account_authorize'
|
||||||
|
import Block from '../components/block'
|
||||||
|
import ScrollableList from '../components/scrollable_list'
|
||||||
|
import Text from '../components/text'
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
|
||||||
|
isLoading: state.getIn(['user_lists', 'follow_requests', 'isLoading'], true),
|
||||||
|
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
|
||||||
|
locked: !!state.getIn(['accounts', me, 'locked']),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
onFetchFollowRequests() {
|
||||||
|
dispatch(fetchFollowRequests())
|
||||||
|
},
|
||||||
|
onExpandFollowRequests() {
|
||||||
|
dispatch(expandFollowRequests())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default
|
||||||
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
class FollowRequests extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
accountIds: ImmutablePropTypes.list,
|
||||||
|
hasMore: PropTypes.bool,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
locked: PropTypes.bool,
|
||||||
|
onFetchFollowRequests: PropTypes.func.isRequired,
|
||||||
|
onExpandFollowRequests: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.props.onFetchFollowRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLoadMore = debounce(() => {
|
||||||
|
this.props.onExpandFollowRequests()
|
||||||
|
}, 300, { leading: true })
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
accountIds,
|
||||||
|
hasMore,
|
||||||
|
isLoading,
|
||||||
|
locked,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const unlockedPrependMessage = locked ? null : (
|
||||||
|
<div className={[_s.default, _s.px15, _s.py15].join(' ')}>
|
||||||
|
<Text>
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_requests.unlocked_explanation'
|
||||||
|
defaultMessage='Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.'
|
||||||
|
values={{ domain: 'Gab.com' }}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Block>
|
||||||
|
{unlockedPrependMessage}
|
||||||
|
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey='follow_requests'
|
||||||
|
onLoadMore={this.handleLoadMore}
|
||||||
|
hasMore={hasMore}
|
||||||
|
isLoading={isLoading}
|
||||||
|
emptyMessage={<FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
!!accountIds && accountIds.map(id =>
|
||||||
|
<AccountAuthorize key={id} id={id} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ScrollableList>
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,70 +0,0 @@
|
|||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { authorizeFollowRequest, rejectFollowRequest } from '../../../../actions/accounts';
|
|
||||||
import { makeGetAccount } from '../../../../selectors';
|
|
||||||
import Button from '../../../../components/button'
|
|
||||||
import Avatar from '../../../../components/avatar';
|
|
||||||
import DisplayName from '../../../../components/display_name';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
|
|
||||||
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => ({
|
|
||||||
account: makeGetAccount()(state, props.id),
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { id }) => ({
|
|
||||||
onAuthorize() {
|
|
||||||
dispatch(authorizeFollowRequest(id));
|
|
||||||
},
|
|
||||||
|
|
||||||
onReject() {
|
|
||||||
dispatch(rejectFollowRequest(id));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default
|
|
||||||
@connect(makeMapStateToProps, mapDispatchToProps)
|
|
||||||
@injectIntl
|
|
||||||
class AccountAuthorize extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
|
||||||
onAuthorize: PropTypes.func.isRequired,
|
|
||||||
onReject: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl, account, onAuthorize, onReject } = this.props;
|
|
||||||
const content = { __html: account.get('note_emojified') };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account-authorize__wrapper'>
|
|
||||||
<div className='account-authorize'>
|
|
||||||
<Button href={`/${account.get('acct')}`} to={`/${account.get('acct')}`} className='account-authorize__display-name'>
|
|
||||||
<div className='account-authorize__avatar'>
|
|
||||||
<Avatar account={account} size={48} />
|
|
||||||
</div>
|
|
||||||
<DisplayName account={account} />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div className='account__header__content' dangerouslySetInnerHTML={content} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account--panel'>
|
|
||||||
<div className='account--panel__button'>
|
|
||||||
<Button title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} />
|
|
||||||
</div>
|
|
||||||
<div className='account--panel__button'>
|
|
||||||
<Button title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
.account-authorize {
|
|
||||||
padding: 14px 10px;
|
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
float: left;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__display-name {
|
|
||||||
display: block;
|
|
||||||
color: $secondary-text-color;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
line-height: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
strong,
|
|
||||||
span {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
@include text-overflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-size: 16px;
|
|
||||||
color: $primary-text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover strong {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__header__content {
|
|
||||||
color: $darker-text-color;
|
|
||||||
overflow: hidden;
|
|
||||||
word-break: normal;
|
|
||||||
word-wrap: break-word;
|
|
||||||
|
|
||||||
@include text-sizing(14px, 400);
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account--panel {
|
|
||||||
background: lighten($ui-base-color, 4%);
|
|
||||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
|
||||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account--panel__button {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './account_authorize'
|
|
@ -1,77 +0,0 @@
|
|||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
|
||||||
import debounce from 'lodash.debounce'
|
|
||||||
import { me } from '../../initial_state'
|
|
||||||
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'
|
|
||||||
import AccountAuthorize from './components/account_authorize'
|
|
||||||
import ScrollableList from '../../components/scrollable_list'
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
|
|
||||||
isLoading: state.getIn(['user_lists', 'follow_requests', 'isLoading'], true),
|
|
||||||
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
|
|
||||||
locked: !!state.getIn(['accounts', me, 'locked']),
|
|
||||||
domain: state.getIn(['meta', 'domain']),
|
|
||||||
})
|
|
||||||
|
|
||||||
export default
|
|
||||||
@connect(mapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class FollowRequests extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
params: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
hasMore: PropTypes.bool,
|
|
||||||
locked: PropTypes.bool,
|
|
||||||
domain: PropTypes.string,
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
accountIds: ImmutablePropTypes.list,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
this.props.dispatch(fetchFollowRequests())
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoadMore = debounce(() => {
|
|
||||||
this.props.dispatch(expandFollowRequests())
|
|
||||||
}, 300, { leading: true })
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl, accountIds, hasMore, locked, domain, isLoading } = this.props
|
|
||||||
|
|
||||||
// : todo :
|
|
||||||
const unlockedPrependMessage = locked ? null : (
|
|
||||||
<div className='follow_requests-unlocked_explanation'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='follow_requests.unlocked_explanation'
|
|
||||||
defaultMessage='Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.'
|
|
||||||
values={{ domain: domain }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollableList
|
|
||||||
scrollKey='follow_requests'
|
|
||||||
onLoadMore={this.handleLoadMore}
|
|
||||||
hasMore={hasMore}
|
|
||||||
isLoading={isLoading}
|
|
||||||
emptyMessage={<FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
!!accountIds && accountIds.map(id =>
|
|
||||||
<AccountAuthorize key={id} id={id} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ScrollableList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './follow_requests'
|
|
@ -1,6 +1,7 @@
|
|||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
import { defineMessages, injectIntl } from 'react-intl'
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import { setFilter } from '../actions/notifications'
|
import { setFilter } from '../actions/notifications'
|
||||||
|
import { me } from '../initial_state'
|
||||||
import { NOTIFICATION_FILTERS } from '../constants'
|
import { NOTIFICATION_FILTERS } from '../constants'
|
||||||
import PageTitle from '../features/ui/util/page_title'
|
import PageTitle from '../features/ui/util/page_title'
|
||||||
import LinkFooter from '../components/link_footer'
|
import LinkFooter from '../components/link_footer'
|
||||||
@ -23,6 +24,7 @@ const messages = defineMessages({
|
|||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
selectedFilter: state.getIn(['notifications', 'filter', 'active']),
|
selectedFilter: state.getIn(['notifications', 'filter', 'active']),
|
||||||
notificationCount: state.getIn(['notifications', 'unread']),
|
notificationCount: state.getIn(['notifications', 'unread']),
|
||||||
|
locked: !!state.getIn(['accounts', me, 'locked']),
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@ -46,6 +48,7 @@ class NotificationsPage extends PureComponent {
|
|||||||
notificationCount: PropTypes.number.isRequired,
|
notificationCount: PropTypes.number.isRequired,
|
||||||
setFilter: PropTypes.func.isRequired,
|
setFilter: PropTypes.func.isRequired,
|
||||||
selectedFilter: PropTypes.string.isRequired,
|
selectedFilter: PropTypes.string.isRequired,
|
||||||
|
locked: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeActiveFilter(notificationType) {
|
onChangeActiveFilter(notificationType) {
|
||||||
@ -53,6 +56,8 @@ class NotificationsPage extends PureComponent {
|
|||||||
|
|
||||||
if (notificationType === 'all') {
|
if (notificationType === 'all') {
|
||||||
this.context.router.history.push('/notifications')
|
this.context.router.history.push('/notifications')
|
||||||
|
} else if (notificationType === 'follow_requests') {
|
||||||
|
this.context.router.history.push(`/notifications/follow_requests`)
|
||||||
} else {
|
} else {
|
||||||
this.context.router.history.push(`/notifications?view=${notificationType}`)
|
this.context.router.history.push(`/notifications?view=${notificationType}`)
|
||||||
}
|
}
|
||||||
@ -64,9 +69,15 @@ class NotificationsPage extends PureComponent {
|
|||||||
intl,
|
intl,
|
||||||
notificationCount,
|
notificationCount,
|
||||||
selectedFilter,
|
selectedFilter,
|
||||||
|
locked
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const tabs = NOTIFICATION_FILTERS.map((filter) => ({
|
let filters = NOTIFICATION_FILTERS
|
||||||
|
if (!locked && filters.indexOf('follow_requests') > -1) {
|
||||||
|
filters.splice(filters.indexOf('follow_requests'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = filters.map((filter) => ({
|
||||||
title: intl.formatMessage(messages[filter]),
|
title: intl.formatMessage(messages[filter]),
|
||||||
onClick: () => this.onChangeActiveFilter(filter),
|
onClick: () => this.onChangeActiveFilter(filter),
|
||||||
active: selectedFilter.toLowerCase() === filter.toLowerCase(),
|
active: selectedFilter.toLowerCase() === filter.toLowerCase(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user