parent
da2ef4d676
commit
38dd85daab
@ -0,0 +1,24 @@ |
||||
export const ALERT_SHOW = 'ALERT_SHOW'; |
||||
export const ALERT_DISMISS = 'ALERT_DISMISS'; |
||||
export const ALERT_CLEAR = 'ALERT_CLEAR'; |
||||
|
||||
export function dismissAlert(alert) { |
||||
return { |
||||
type: ALERT_DISMISS, |
||||
alert |
||||
}; |
||||
}; |
||||
|
||||
export function clearAlert() { |
||||
return { |
||||
type: ALERT_CLEAR |
||||
}; |
||||
}; |
||||
|
||||
export function showAlert(title, message) { |
||||
return { |
||||
type: ALERT_SHOW, |
||||
title, |
||||
message |
||||
}; |
||||
}; |
@ -1,24 +1,124 @@ |
||||
export const NOTIFICATION_SHOW = 'NOTIFICATION_SHOW'; |
||||
export const NOTIFICATION_DISMISS = 'NOTIFICATION_DISMISS'; |
||||
export const NOTIFICATION_CLEAR = 'NOTIFICATION_CLEAR'; |
||||
import api, { getLinks } from '../api' |
||||
import Immutable from 'immutable'; |
||||
|
||||
export function dismissNotification(notification) { |
||||
import { fetchRelationships } from './accounts'; |
||||
|
||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; |
||||
|
||||
export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; |
||||
export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; |
||||
export const NOTIFICATIONS_REFRESH_FAIL = 'NOTIFICATIONS_REFRESH_FAIL'; |
||||
|
||||
export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; |
||||
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; |
||||
export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; |
||||
|
||||
const fetchRelatedRelationships = (dispatch, notifications) => { |
||||
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); |
||||
|
||||
if (accountIds > 0) { |
||||
dispatch(fetchRelationships(accountIds)); |
||||
} |
||||
}; |
||||
|
||||
export function updateNotifications(notification) { |
||||
return dispatch => { |
||||
dispatch({ |
||||
type: NOTIFICATIONS_UPDATE, |
||||
notification, |
||||
account: notification.account, |
||||
status: notification.status |
||||
}); |
||||
|
||||
fetchRelatedRelationships(dispatch, [notification]); |
||||
}; |
||||
}; |
||||
|
||||
export function refreshNotifications() { |
||||
return (dispatch, getState) => { |
||||
dispatch(refreshNotificationsRequest()); |
||||
|
||||
const params = {}; |
||||
const ids = getState().getIn(['notifications', 'items']); |
||||
|
||||
if (ids.size > 0) { |
||||
params.since_id = ids.first().get('id'); |
||||
} |
||||
|
||||
api(getState).get('/api/v1/notifications', { params }).then(response => { |
||||
const next = getLinks(response).refs.find(link => link.rel === 'next'); |
||||
|
||||
dispatch(refreshNotificationsSuccess(response.data, next ? next.uri : null)); |
||||
fetchRelatedRelationships(dispatch, response.data); |
||||
}).catch(error => { |
||||
dispatch(refreshNotificationsFail(error)); |
||||
}); |
||||
}; |
||||
}; |
||||
|
||||
export function refreshNotificationsRequest() { |
||||
return { |
||||
type: NOTIFICATIONS_REFRESH_REQUEST |
||||
}; |
||||
}; |
||||
|
||||
export function refreshNotificationsSuccess(notifications, next) { |
||||
return { |
||||
type: NOTIFICATIONS_REFRESH_SUCCESS, |
||||
notifications, |
||||
accounts: notifications.map(item => item.account), |
||||
statuses: notifications.map(item => item.status), |
||||
next |
||||
}; |
||||
}; |
||||
|
||||
export function refreshNotificationsFail(error) { |
||||
return { |
||||
type: NOTIFICATIONS_REFRESH_FAIL, |
||||
error |
||||
}; |
||||
}; |
||||
|
||||
export function expandNotifications() { |
||||
return (dispatch, getState) => { |
||||
const url = getState().getIn(['notifications', 'next'], null); |
||||
|
||||
if (url === null) { |
||||
return; |
||||
} |
||||
|
||||
dispatch(expandNotificationsRequest()); |
||||
|
||||
api(getState).get(url).then(response => { |
||||
const next = getLinks(response).refs.find(link => link.rel === 'next'); |
||||
|
||||
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); |
||||
fetchRelatedRelationships(dispatch, response.data); |
||||
}).catch(error => { |
||||
dispatch(expandNotificationsFail(error)); |
||||
}); |
||||
}; |
||||
}; |
||||
|
||||
export function expandNotificationsRequest() { |
||||
return { |
||||
type: NOTIFICATION_DISMISS, |
||||
notification: notification |
||||
type: NOTIFICATIONS_EXPAND_REQUEST |
||||
}; |
||||
}; |
||||
|
||||
export function clearNotifications() { |
||||
export function expandNotificationsSuccess(notifications, next) { |
||||
return { |
||||
type: NOTIFICATION_CLEAR |
||||
type: NOTIFICATIONS_EXPAND_SUCCESS, |
||||
notifications, |
||||
accounts: notifications.map(item => item.account), |
||||
statuses: notifications.map(item => item.status), |
||||
next |
||||
}; |
||||
}; |
||||
|
||||
export function showNotification(title, message) { |
||||
export function expandNotificationsFail(error) { |
||||
return { |
||||
type: NOTIFICATION_SHOW, |
||||
title: title, |
||||
message: message |
||||
type: NOTIFICATIONS_EXPAND_FAIL, |
||||
error |
||||
}; |
||||
}; |
||||
|
@ -1,9 +1,9 @@ |
||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import Avatar from '../../../components/avatar'; |
||||
import DisplayName from '../../../components/display_name'; |
||||
import Avatar from './avatar'; |
||||
import DisplayName from './display_name'; |
||||
import { Link } from 'react-router'; |
||||
import IconButton from '../../../components/icon_button'; |
||||
import IconButton from './icon_button'; |
||||
import { defineMessages, injectIntl } from 'react-intl'; |
||||
|
||||
const messages = defineMessages({ |
@ -1,10 +1,10 @@ |
||||
import { connect } from 'react-redux'; |
||||
import { makeGetAccount } from '../../../selectors'; |
||||
import Account from '../components/account'; |
||||
import { connect } from 'react-redux'; |
||||
import { makeGetAccount } from '../selectors'; |
||||
import Account from '../components/account'; |
||||
import { |
||||
followAccount, |
||||
unfollowAccount |
||||
} from '../../../actions/accounts'; |
||||
} from '../actions/accounts'; |
||||
|
||||
const makeMapStateToProps = () => { |
||||
const getAccount = makeGetAccount(); |
@ -0,0 +1,79 @@ |
||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import StatusContainer from '../../../containers/status_container'; |
||||
import AccountContainer from '../../../containers/account_container'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import { Link } from 'react-router'; |
||||
|
||||
const messageStyle = { |
||||
padding: '8px 10px', |
||||
paddingBottom: '0', |
||||
cursor: 'default', |
||||
color: '#d9e1e8', |
||||
fontSize: '15px' |
||||
}; |
||||
|
||||
const linkStyle = { |
||||
fontWeight: '500' |
||||
}; |
||||
|
||||
const Notification = React.createClass({ |
||||
|
||||
propTypes: { |
||||
notification: ImmutablePropTypes.map.isRequired |
||||
}, |
||||
|
||||
mixins: [PureRenderMixin], |
||||
|
||||
renderFollow (account, link) { |
||||
return ( |
||||
<div className='notification'> |
||||
<div style={messageStyle}><i className='fa fa-fw fa-user-plus' style={{ color: '#2b90d9' }} /> <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} /></div> |
||||
<AccountContainer id={account.get('id')} withNote={false} /> |
||||
</div> |
||||
); |
||||
}, |
||||
|
||||
renderMention (notification) { |
||||
return <StatusContainer id={notification.get('status')} />; |
||||
}, |
||||
|
||||
renderFavourite (notification, link) { |
||||
return ( |
||||
<div className='notification'> |
||||
<div style={messageStyle}><i className='fa fa-fw fa-star' style={{ color: '#ca8f04' }} /> <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} /></div> |
||||
<StatusContainer id={notification.get('status')} muted={true} /> |
||||
</div> |
||||
); |
||||
}, |
||||
|
||||
renderReblog (notification, link) { |
||||
return ( |
||||
<div className='notification'> |
||||
<div style={messageStyle}><i className='fa fa-fw fa-retweet' style={{ color: '#2b90d9' }} /> <FormattedMessage id='notification.reblog' defaultMessage='{name} reblogged your status' values={{ name: link }} /></div> |
||||
<StatusContainer id={notification.get('status')} muted={true} /> |
||||
</div> |
||||
); |
||||
}, |
||||
|
||||
render () { |
||||
const { notification } = this.props; |
||||
const account = notification.get('account'); |
||||
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); |
||||
const link = <Link className='notification__display-name' style={linkStyle} to={`/accounts/${account.get('id')}`}>{displayName}</Link>; |
||||
|
||||
switch(notification.get('type')) { |
||||
case 'follow': |
||||
return this.renderFollow(account, link); |
||||
case 'mention': |
||||
return this.renderMention(notification); |
||||
case 'favourite': |
||||
return this.renderFavourite(notification, link); |
||||
case 'reblog': |
||||
return this.renderReblog(notification, link); |
||||
} |
||||
} |
||||
|
||||
}); |
||||
|
||||
export default Notification; |
@ -0,0 +1,15 @@ |
||||
import { connect } from 'react-redux'; |
||||
import { makeGetNotification } from '../../../selectors'; |
||||
import Notification from '../components/notification'; |
||||
|
||||
const makeMapStateToProps = () => { |
||||
const getNotification = makeGetNotification(); |
||||
|
||||
const mapStateToProps = (state, props) => ({ |
||||
notification: getNotification(state, props.notification, props.accountId) |
||||
}); |
||||
|
||||
return mapStateToProps; |
||||
}; |
||||
|
||||
export default connect(makeMapStateToProps)(Notification); |
@ -0,0 +1,61 @@ |
||||
import { connect } from 'react-redux'; |
||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import Column from '../ui/components/column'; |
||||
import { |
||||
refreshNotifications, |
||||
expandNotifications |
||||
} from '../../actions/notifications'; |
||||
import NotificationContainer from './containers/notification_container'; |
||||
import { ScrollContainer } from 'react-router-scroll'; |
||||
import { defineMessages, injectIntl } from 'react-intl'; |
||||
|
||||
const messages = defineMessages({ |
||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' } |
||||
}); |
||||
|
||||
const mapStateToProps = state => ({ |
||||
notifications: state.getIn(['notifications', 'items']) |
||||
}); |
||||
|
||||
const Notifications = React.createClass({ |
||||
|
||||
propTypes: { |
||||
notifications: ImmutablePropTypes.list.isRequired, |
||||
dispatch: React.PropTypes.func.isRequired |
||||
}, |
||||
|
||||
mixins: [PureRenderMixin], |
||||
|
||||
componentWillMount () { |
||||
const { dispatch } = this.props; |
||||
dispatch(refreshNotifications()); |
||||
}, |
||||
|
||||
handleScroll (e) { |
||||
const { scrollTop, scrollHeight, clientHeight } = e.target; |
||||
|
||||
if (scrollTop === scrollHeight - clientHeight) { |
||||
this.props.dispatch(expandNotifications()); |
||||
} |
||||
}, |
||||
|
||||
render () { |
||||
const { intl, notifications } = this.props; |
||||
|
||||
return ( |
||||
<Column icon='bell' heading={intl.formatMessage(messages.title)}> |
||||
<ScrollContainer scrollKey='notifications'> |
||||
<div className='scrollable' onScroll={this.handleScroll}> |
||||
<div> |
||||
{notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)} |
||||
</div> |
||||
</div> |
||||
</ScrollContainer> |
||||
</Column> |
||||
); |
||||
} |
||||
|
||||
}); |
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Notifications)); |
@ -0,0 +1,25 @@ |
||||
import { |
||||
ALERT_SHOW, |
||||
ALERT_DISMISS, |
||||
ALERT_CLEAR |
||||
} from '../actions/alerts'; |
||||
import Immutable from 'immutable'; |
||||
|
||||
const initialState = Immutable.List([]); |
||||
|
||||
export default function alerts(state = initialState, action) { |
||||
switch(action.type) { |
||||
case ALERT_SHOW: |
||||
return state.push(Immutable.Map({ |
||||
key: state.size > 0 ? state.last().get('key') + 1 : 0, |
||||
title: action.title, |
||||
message: action.message |
||||
})); |
||||
case ALERT_DISMISS: |
||||
return state.filterNot(item => item.get('key') === action.alert.key); |
||||
case ALERT_CLEAR: |
||||
return state.clear(); |
||||
default: |
||||
return state; |
||||
} |
||||
}; |
@ -1,26 +1,28 @@ |
||||
import { combineReducers } from 'redux-immutable'; |
||||
import timelines from './timelines'; |
||||
import meta from './meta'; |
||||
import compose from './compose'; |
||||
import notifications from './notifications'; |
||||
import { combineReducers } from 'redux-immutable'; |
||||
import timelines from './timelines'; |
||||
import meta from './meta'; |
||||
import compose from './compose'; |
||||
import alerts from './alerts'; |
||||
import { loadingBarReducer } from 'react-redux-loading-bar'; |
||||
import modal from './modal'; |
||||
import user_lists from './user_lists'; |
||||
import accounts from './accounts'; |
||||
import statuses from './statuses'; |
||||
import relationships from './relationships'; |
||||
import search from './search'; |
||||
import modal from './modal'; |
||||
import user_lists from './user_lists'; |
||||
import accounts from './accounts'; |
||||
import statuses from './statuses'; |
||||
import relationships from './relationships'; |
||||
import search from './search'; |
||||
import notifications from './notifications'; |
||||
|
||||
export default combineReducers({ |
||||
timelines, |
||||
meta, |
||||
compose, |
||||
notifications, |
||||
alerts, |
||||
loadingBar: loadingBarReducer, |
||||
modal, |
||||
user_lists, |
||||
accounts, |
||||
statuses, |
||||
relationships, |
||||
search |
||||
search, |
||||
notifications |
||||
}); |
||||
|
Loading…
Reference in new issue