diff options
Diffstat (limited to 'app/javascript/glitch/components/notification')
5 files changed, 369 insertions, 0 deletions
diff --git a/app/javascript/glitch/components/notification/container.js b/app/javascript/glitch/components/notification/container.js new file mode 100644 index 000000000..7d2590684 --- /dev/null +++ b/app/javascript/glitch/components/notification/container.js @@ -0,0 +1,55 @@ +/* + +`<NotificationContainer>` +========================= + +This container connects `<Notification>`s to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + +// Package imports // +import { connect } from 'react-redux'; + +// Mastodon imports // +import { makeGetNotification } from '../../../mastodon/selectors'; + +// Our imports // +import Notification from '.'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +State mapping: +-------------- + +The `mapStateToProps()` function maps various state properties to the +props of our component. We wrap this in `makeMapStateToProps()` so that +we only have to call `makeGetNotification()` once instead of every +time. + +*/ + +const makeMapStateToProps = () => { + const getNotification = makeGetNotification(); + + const mapStateToProps = (state, props) => ({ + notification: getNotification(state, props.notification, props.accountId), + settings: state.get('local_settings'), + }); + + return mapStateToProps; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +export default connect(makeMapStateToProps)(Notification); diff --git a/app/javascript/glitch/components/notification/follow.js b/app/javascript/glitch/components/notification/follow.js new file mode 100644 index 000000000..d340e83c8 --- /dev/null +++ b/app/javascript/glitch/components/notification/follow.js @@ -0,0 +1,124 @@ +/* + +`<NotificationFollow>` +====================== + +This component renders a follow notification. + +__Props:__ + + - __`id` (`PropTypes.number.isRequired`) :__ + This is the id of the notification. + + - __`onDeleteNotification` (`PropTypes.func.isRequired`) :__ + The function to call when a notification should be + dismissed/deleted. + + - __`account` (`PropTypes.object.isRequired`) :__ + The account associated with the follow notification, ie the account + which followed the user. + + - __`intl` (`PropTypes.object.isRequired`) :__ + Our internationalization object, inserted by `@injectIntl`. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + +// Package imports // +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import escapeTextContentForBrowser from 'escape-html'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Mastodon imports // +import emojify from '../../../mastodon/emoji'; +import Permalink from '../../../mastodon/components/permalink'; +import AccountContainer from '../../../mastodon/containers/account_container'; + +// Our imports // +import NotificationOverlayContainer from '../notification/overlay/container'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Implementation: +--------------- + +*/ + +export default class NotificationFollow extends ImmutablePureComponent { + + static propTypes = { + id : PropTypes.number.isRequired, + account : ImmutablePropTypes.map.isRequired, + notification : ImmutablePropTypes.map.isRequired, + }; + +/* + +### `render()` + +This actually renders the component. + +*/ + + render () { + const { account, notification } = this.props; + +/* + +`link` is a container for the account's `displayName`, which links to +the account timeline using a `<Permalink>`. + +*/ + + const displayName = account.get('display_name') || account.get('username'); + const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; + const link = ( + <Permalink + className='notification__display-name' + href={account.get('url')} + title={account.get('acct')} + to={`/accounts/${account.get('id')}`} + dangerouslySetInnerHTML={displayNameHTML} + /> + ); + +/* + +We can now render our component. + +*/ + + return ( + <div className='notification notification-follow'> + <div className='notification__message'> + <div className='notification__favourite-icon-wrapper'> + <i className='fa fa-fw fa-user-plus' /> + </div> + + <FormattedMessage + id='notification.follow' + defaultMessage='{name} followed you' + values={{ name: link }} + /> + </div> + + <AccountContainer id={account.get('id')} withNote={false} /> + <NotificationOverlayContainer notification={notification} /> + </div> + ); + } + +} diff --git a/app/javascript/glitch/components/notification/index.js b/app/javascript/glitch/components/notification/index.js new file mode 100644 index 000000000..b2e55aad5 --- /dev/null +++ b/app/javascript/glitch/components/notification/index.js @@ -0,0 +1,82 @@ +// Package imports // +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Mastodon imports // + +// Our imports // +import StatusContainer from '../status/container'; +import NotificationFollow from './follow'; + +export default class Notification extends ImmutablePureComponent { + + static propTypes = { + notification: ImmutablePropTypes.map.isRequired, + settings: ImmutablePropTypes.map.isRequired, + }; + + renderFollow (notification) { + return ( + <NotificationFollow + id={notification.get('id')} + account={notification.get('account')} + notification={notification} + /> + ); + } + + renderMention (notification) { + return ( + <StatusContainer + id={notification.get('status')} + notification={notification} + withDismiss + /> + ); + } + + renderFavourite (notification) { + return ( + <StatusContainer + id={notification.get('status')} + account={notification.get('account')} + prepend='favourite' + muted + notification={notification} + withDismiss + /> + ); + } + + renderReblog (notification) { + return ( + <StatusContainer + id={notification.get('status')} + account={notification.get('account')} + prepend='reblog' + muted + notification={notification} + withDismiss + /> + ); + } + + render () { + const { notification } = this.props; + + switch(notification.get('type')) { + case 'follow': + return this.renderFollow(notification); + case 'mention': + return this.renderMention(notification); + case 'favourite': + return this.renderFavourite(notification); + case 'reblog': + return this.renderReblog(notification); + } + + return null; + } + +} diff --git a/app/javascript/glitch/components/notification/overlay/container.js b/app/javascript/glitch/components/notification/overlay/container.js new file mode 100644 index 000000000..019b78d0b --- /dev/null +++ b/app/javascript/glitch/components/notification/overlay/container.js @@ -0,0 +1,49 @@ +/* + +`<NotificationOverlayContainer>` +========================= + +This container connects `<NotificationOverlay>`s to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + +// Package imports // +import { connect } from 'react-redux'; + +// Our imports // +import NotificationOverlay from './notification_overlay'; +import { markNotificationForDelete } from '../../../../mastodon/actions/notifications'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Dispatch mapping: +----------------- + +The `mapDispatchToProps()` function maps dispatches to our store to the +various props of our component. We only need to provide a dispatch for +deleting notifications. + +*/ + +const mapDispatchToProps = dispatch => ({ + onMarkForDelete(id, yes) { + dispatch(markNotificationForDelete(id, yes)); + }, +}); + +const mapStateToProps = state => ({ + revealed: state.getIn(['notifications', 'cleaningMode']), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(NotificationOverlay); diff --git a/app/javascript/glitch/components/notification/overlay/notification_overlay.js b/app/javascript/glitch/components/notification/overlay/notification_overlay.js new file mode 100644 index 000000000..73eda718f --- /dev/null +++ b/app/javascript/glitch/components/notification/overlay/notification_overlay.js @@ -0,0 +1,59 @@ +/** + * Notification overlay + */ + + +// Package imports // +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, injectIntl } from 'react-intl'; + +// Mastodon imports // + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +const messages = defineMessages({ + markForDeletion: { id: 'notification.markForDeletion', defaultMessage: 'Mark for deletion' }, +}); + +@injectIntl +export default class NotificationOverlay extends ImmutablePureComponent { + + static propTypes = { + notification : ImmutablePropTypes.map.isRequired, + onMarkForDelete : PropTypes.func.isRequired, + revealed : PropTypes.bool.isRequired, + intl : PropTypes.object.isRequired, + }; + + onToggleMark = () => { + const mark = !this.props.notification.get('markedForDelete'); + const id = this.props.notification.get('id'); + this.props.onMarkForDelete(id, mark); + } + + render () { + const { notification, revealed, intl } = this.props; + + const active = notification.get('markedForDelete'); + const label = intl.formatMessage(messages.markForDeletion); + + return ( + <div + aria-label={label} + role='checkbox' + aria-checked={active} + tabIndex={0} + className={`notification__dismiss-overlay ${active ? 'active' : ''} ${revealed ? 'show' : ''}`} + onClick={this.onToggleMark} + > + <div className='notification__dismiss-overlay__ckbox' aria-hidden='true' title={label}> + {active ? (<i className='fa fa-check' />) : ''} + </div> + </div> + ); + } + +} |