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 isLoadingMore = !!maxId
|
||||
|
||||
if (notifications.get('isLoading')) {
|
||||
if (notifications.get('isLoading') || activeFilter === 'follow_requests') {
|
||||
done();
|
||||
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',
|
||||
'favourite',
|
||||
'reblog',
|
||||
'poll',
|
||||
'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 { defineMessages, injectIntl } from 'react-intl'
|
||||
import { setFilter } from '../actions/notifications'
|
||||
import { me } from '../initial_state'
|
||||
import { NOTIFICATION_FILTERS } from '../constants'
|
||||
import PageTitle from '../features/ui/util/page_title'
|
||||
import LinkFooter from '../components/link_footer'
|
||||
@ -23,6 +24,7 @@ const messages = defineMessages({
|
||||
const mapStateToProps = (state) => ({
|
||||
selectedFilter: state.getIn(['notifications', 'filter', 'active']),
|
||||
notificationCount: state.getIn(['notifications', 'unread']),
|
||||
locked: !!state.getIn(['accounts', me, 'locked']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@ -46,6 +48,7 @@ class NotificationsPage extends PureComponent {
|
||||
notificationCount: PropTypes.number.isRequired,
|
||||
setFilter: PropTypes.func.isRequired,
|
||||
selectedFilter: PropTypes.string.isRequired,
|
||||
locked: PropTypes.bool,
|
||||
}
|
||||
|
||||
onChangeActiveFilter(notificationType) {
|
||||
@ -53,6 +56,8 @@ class NotificationsPage extends PureComponent {
|
||||
|
||||
if (notificationType === 'all') {
|
||||
this.context.router.history.push('/notifications')
|
||||
} else if (notificationType === 'follow_requests') {
|
||||
this.context.router.history.push(`/notifications/follow_requests`)
|
||||
} else {
|
||||
this.context.router.history.push(`/notifications?view=${notificationType}`)
|
||||
}
|
||||
@ -64,9 +69,15 @@ class NotificationsPage extends PureComponent {
|
||||
intl,
|
||||
notificationCount,
|
||||
selectedFilter,
|
||||
locked
|
||||
} = 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]),
|
||||
onClick: () => this.onChangeActiveFilter(filter),
|
||||
active: selectedFilter.toLowerCase() === filter.toLowerCase(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user