Add option to be notified when a followed user posts (#13546)
* Add bell button Fix #4890 * Remove duplicate type from post-deployment migration * Fix legacy class type mappings * Improve query performance with better index * Fix validation * Remove redundant index from notifications
This commit is contained in:
		
					parent
					
						
							
								75e4bd9413
							
						
					
				
			
			
				commit
				
					
						974b1b79ce
					
				
			
		
					 42 changed files with 330 additions and 112 deletions
				
			
		|  | @ -7,6 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
| import { autoPlayGif, me, isStaff } from 'mastodon/initial_state'; | ||||
| import classNames from 'classnames'; | ||||
| import Icon from 'mastodon/components/icon'; | ||||
| import IconButton from 'mastodon/components/icon_button'; | ||||
| import Avatar from 'mastodon/components/avatar'; | ||||
| import { counterRenderer } from 'mastodon/components/common_counter'; | ||||
| import ShortNumber from 'mastodon/components/short_number'; | ||||
|  | @ -35,6 +36,8 @@ const messages = defineMessages({ | |||
|   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, | ||||
|   hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' }, | ||||
|   showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' }, | ||||
|   enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' }, | ||||
|   disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' }, | ||||
|   pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, | ||||
|   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, | ||||
|   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, | ||||
|  | @ -68,8 +71,9 @@ class Header extends ImmutablePureComponent { | |||
|     onBlock: PropTypes.func.isRequired, | ||||
|     onMention: PropTypes.func.isRequired, | ||||
|     onDirect: PropTypes.func.isRequired, | ||||
|     onReport: PropTypes.func.isRequired, | ||||
|     onReblogToggle: PropTypes.func.isRequired, | ||||
|     onNotifyToggle: PropTypes.func.isRequired, | ||||
|     onReport: PropTypes.func.isRequired, | ||||
|     onMute: PropTypes.func.isRequired, | ||||
|     onBlockDomain: PropTypes.func.isRequired, | ||||
|     onUnblockDomain: PropTypes.func.isRequired, | ||||
|  | @ -144,6 +148,7 @@ class Header extends ImmutablePureComponent { | |||
| 
 | ||||
|     let info        = []; | ||||
|     let actionBtn   = ''; | ||||
|     let bellBtn     = ''; | ||||
|     let lockedIcon  = ''; | ||||
|     let menu        = []; | ||||
| 
 | ||||
|  | @ -173,6 +178,10 @@ class Header extends ImmutablePureComponent { | |||
|       actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />; | ||||
|     } | ||||
| 
 | ||||
|     if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) { | ||||
|       bellBtn = <IconButton icon='bell-o' size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />; | ||||
|     } | ||||
| 
 | ||||
|     if (account.get('moved') && !account.getIn(['relationship', 'following'])) { | ||||
|       actionBtn = ''; | ||||
|     } | ||||
|  | @ -287,6 +296,7 @@ class Header extends ImmutablePureComponent { | |||
|             {!suspended && ( | ||||
|               <div className='account__header__tabs__buttons'> | ||||
|                 {actionBtn} | ||||
|                 {bellBtn} | ||||
| 
 | ||||
|                 <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' /> | ||||
|               </div> | ||||
|  |  | |||
|  | @ -55,6 +55,10 @@ export default class Header extends ImmutablePureComponent { | |||
|     this.props.onReblogToggle(this.props.account); | ||||
|   } | ||||
| 
 | ||||
|   handleNotifyToggle = () => { | ||||
|     this.props.onNotifyToggle(this.props.account); | ||||
|   } | ||||
| 
 | ||||
|   handleMute = () => { | ||||
|     this.props.onMute(this.props.account); | ||||
|   } | ||||
|  | @ -106,6 +110,7 @@ export default class Header extends ImmutablePureComponent { | |||
|           onMention={this.handleMention} | ||||
|           onDirect={this.handleDirect} | ||||
|           onReblogToggle={this.handleReblogToggle} | ||||
|           onNotifyToggle={this.handleNotifyToggle} | ||||
|           onReport={this.handleReport} | ||||
|           onMute={this.handleMute} | ||||
|           onBlockDomain={this.handleBlockDomain} | ||||
|  |  | |||
|  | @ -76,9 +76,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
| 
 | ||||
|   onReblogToggle (account) { | ||||
|     if (account.getIn(['relationship', 'showing_reblogs'])) { | ||||
|       dispatch(followAccount(account.get('id'), false)); | ||||
|       dispatch(followAccount(account.get('id'), { reblogs: false })); | ||||
|     } else { | ||||
|       dispatch(followAccount(account.get('id'), true)); | ||||
|       dispatch(followAccount(account.get('id'), { reblogs: true })); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|  | @ -90,6 +90,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   onNotifyToggle (account) { | ||||
|     if (account.getIn(['relationship', 'notifying'])) { | ||||
|       dispatch(followAccount(account.get('id'), { notify: false })); | ||||
|     } else { | ||||
|       dispatch(followAccount(account.get('id'), { notify: true })); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   onReport (account) { | ||||
|     dispatch(initReport(account)); | ||||
|   }, | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ const tooltips = defineMessages({ | |||
|   boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' }, | ||||
|   polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' }, | ||||
|   follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, | ||||
|   statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' }, | ||||
| }); | ||||
| 
 | ||||
| export default @injectIntl | ||||
|  | @ -87,6 +88,13 @@ class FilterBar extends React.PureComponent { | |||
|         > | ||||
|           <Icon id='tasks' fixedWidth /> | ||||
|         </button> | ||||
|         <button | ||||
|           className={selectedFilter === 'status' ? 'active' : ''} | ||||
|           onClick={this.onClick('status')} | ||||
|           title={intl.formatMessage(tooltips.statuses)} | ||||
|         > | ||||
|           <Icon id='home' fixedWidth /> | ||||
|         </button> | ||||
|         <button | ||||
|           className={selectedFilter === 'follow' ? 'active' : ''} | ||||
|           onClick={this.onClick('follow')} | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ const messages = defineMessages({ | |||
|   ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' }, | ||||
|   poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }, | ||||
|   reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' }, | ||||
|   status: { id: 'notification.status', defaultMessage: '{name} just posted' }, | ||||
| }); | ||||
| 
 | ||||
| const notificationForScreenReader = (intl, message, timestamp) => { | ||||
|  | @ -237,6 +238,38 @@ class Notification extends ImmutablePureComponent { | |||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderStatus (notification, link) { | ||||
|     const { intl } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <HotKeys handlers={this.getHandlers()}> | ||||
|         <div className='notification notification-status focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> | ||||
|           <div className='notification__message'> | ||||
|             <div className='notification__favourite-icon-wrapper'> | ||||
|               <Icon id='home' fixedWidth /> | ||||
|             </div> | ||||
| 
 | ||||
|             <span title={notification.get('created_at')}> | ||||
|               <FormattedMessage id='notification.status' defaultMessage='{name} just posted' values={{ name: link }} /> | ||||
|             </span> | ||||
|           </div> | ||||
| 
 | ||||
|           <StatusContainer | ||||
|             id={notification.get('status')} | ||||
|             account={notification.get('account')} | ||||
|             muted | ||||
|             withDismiss | ||||
|             hidden={this.props.hidden} | ||||
|             getScrollPosition={this.props.getScrollPosition} | ||||
|             updateScrollBottom={this.props.updateScrollBottom} | ||||
|             cachedMediaWidth={this.props.cachedMediaWidth} | ||||
|             cacheMediaWidth={this.props.cacheMediaWidth} | ||||
|           /> | ||||
|         </div> | ||||
|       </HotKeys> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderPoll (notification, account) { | ||||
|     const { intl } = this.props; | ||||
|     const ownPoll  = me === account.get('id'); | ||||
|  | @ -292,6 +325,8 @@ class Notification extends ImmutablePureComponent { | |||
|       return this.renderFavourite(notification, link); | ||||
|     case 'reblog': | ||||
|       return this.renderReblog(notification, link); | ||||
|     case 'status': | ||||
|       return this.renderStatus(notification, link); | ||||
|     case 'poll': | ||||
|       return this.renderPoll(notification, account); | ||||
|     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue