Add duration parameter to muting. (#13831)
* Adding duration to muting. * Remove useless checks
This commit is contained in:
		| @@ -42,7 +42,7 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def mute | ||||
|     MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications)) | ||||
|     MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration] || 0)) | ||||
|     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ class Api::V1::MutesController < Api::BaseController | ||||
|  | ||||
|   def index | ||||
|     @accounts = load_accounts | ||||
|     render json: @accounts, each_serializer: REST::AccountSerializer | ||||
|     render json: @accounts, each_serializer: REST::MutedAccountSerializer | ||||
|   end | ||||
|  | ||||
|   private | ||||
|   | ||||
| @@ -257,11 +257,11 @@ export function unblockAccountFail(error) { | ||||
| }; | ||||
|  | ||||
|  | ||||
| export function muteAccount(id, notifications) { | ||||
| export function muteAccount(id, notifications, duration=0) { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(muteAccountRequest(id)); | ||||
|  | ||||
|     api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then(response => { | ||||
|     api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => { | ||||
|       // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers | ||||
|       dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); | ||||
|     }).catch(error => { | ||||
|   | ||||
| @@ -13,6 +13,7 @@ export const MUTES_EXPAND_FAIL    = 'MUTES_EXPAND_FAIL'; | ||||
|  | ||||
| export const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL'; | ||||
| export const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS'; | ||||
| export const MUTES_CHANGE_DURATION = 'MUTES_CHANGE_DURATION'; | ||||
|  | ||||
| export function fetchMutes() { | ||||
|   return (dispatch, getState) => { | ||||
| @@ -104,3 +105,12 @@ export function toggleHideNotifications() { | ||||
|     dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS }); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export function changeMuteDuration(duration) { | ||||
|   return dispatch => { | ||||
|     dispatch({ | ||||
|       type: MUTES_CHANGE_DURATION, | ||||
|       duration, | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import IconButton from './icon_button'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { me } from '../initial_state'; | ||||
| import RelativeTimestamp from './relative_timestamp'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   follow: { id: 'account.follow', defaultMessage: 'Follow' }, | ||||
| @@ -107,11 +108,17 @@ class Account extends ImmutablePureComponent { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     let mute_expires_at; | ||||
|     if (account.get('mute_expires_at')) { | ||||
|       mute_expires_at =  <div><RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></div>; | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <div className='account'> | ||||
|         <div className='account__wrapper'> | ||||
|           <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}> | ||||
|             <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div> | ||||
|             {mute_expires_at} | ||||
|             <DisplayName account={account} /> | ||||
|           </Permalink> | ||||
|  | ||||
|   | ||||
| @@ -1,25 +1,31 @@ | ||||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import Toggle from 'react-toggle'; | ||||
| import Button from '../../../components/button'; | ||||
| import { closeModal } from '../../../actions/modal'; | ||||
| import { muteAccount } from '../../../actions/accounts'; | ||||
| import { toggleHideNotifications } from '../../../actions/mutes'; | ||||
| import { toggleHideNotifications, changeMuteDuration } from '../../../actions/mutes'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' }, | ||||
|   hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' }, | ||||
|   days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' }, | ||||
| }); | ||||
|  | ||||
| const mapStateToProps = state => { | ||||
|   return { | ||||
|     account: state.getIn(['mutes', 'new', 'account']), | ||||
|     notifications: state.getIn(['mutes', 'new', 'notifications']), | ||||
|     muteDuration: state.getIn(['mutes', 'new', 'duration']), | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| const mapDispatchToProps = dispatch => { | ||||
|   return { | ||||
|     onConfirm(account, notifications) { | ||||
|       dispatch(muteAccount(account.get('id'), notifications)); | ||||
|     onConfirm(account, notifications, muteDuration) { | ||||
|       dispatch(muteAccount(account.get('id'), notifications, muteDuration)); | ||||
|     }, | ||||
|  | ||||
|     onClose() { | ||||
| @@ -29,6 +35,10 @@ const mapDispatchToProps = dispatch => { | ||||
|     onToggleNotifications() { | ||||
|       dispatch(toggleHideNotifications()); | ||||
|     }, | ||||
|  | ||||
|     onChangeMuteDuration(e) { | ||||
|       dispatch(changeMuteDuration(e.target.value)); | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @@ -43,6 +53,8 @@ class MuteModal extends React.PureComponent { | ||||
|     onConfirm: PropTypes.func.isRequired, | ||||
|     onToggleNotifications: PropTypes.func.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     muteDuration: PropTypes.number.isRequired, | ||||
|     onChangeMuteDuration: PropTypes.func.isRequired, | ||||
|   }; | ||||
|  | ||||
|   componentDidMount() { | ||||
| @@ -51,7 +63,7 @@ class MuteModal extends React.PureComponent { | ||||
|  | ||||
|   handleClick = () => { | ||||
|     this.props.onClose(); | ||||
|     this.props.onConfirm(this.props.account, this.props.notifications); | ||||
|     this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration); | ||||
|   } | ||||
|  | ||||
|   handleCancel = () => { | ||||
| @@ -66,8 +78,12 @@ class MuteModal extends React.PureComponent { | ||||
|     this.props.onToggleNotifications(); | ||||
|   } | ||||
|  | ||||
|   changeMuteDuration = (e) => { | ||||
|     this.props.onChangeMuteDuration(e); | ||||
|   } | ||||
|  | ||||
|   render () { | ||||
|     const { account, notifications } = this.props; | ||||
|     const { account, notifications, muteDuration, intl } = this.props; | ||||
|  | ||||
|     return ( | ||||
|       <div className='modal-root__modal mute-modal'> | ||||
| @@ -91,6 +107,21 @@ class MuteModal extends React.PureComponent { | ||||
|               <FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' /> | ||||
|             </label> | ||||
|           </div> | ||||
|           <div> | ||||
|             <span><FormattedMessage id='mute_modal.duration' defaultMessage='Duration' />: </span> | ||||
|  | ||||
|             {/* eslint-disable-next-line jsx-a11y/no-onchange */} | ||||
|             <select value={muteDuration} onChange={this.changeMuteDuration}> | ||||
|               <option value={0}>{intl.formatMessage({ id: 'mute_modal.indefinite' })}</option> | ||||
|               <option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option> | ||||
|               <option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option> | ||||
|               <option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option> | ||||
|               <option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option> | ||||
|               <option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option> | ||||
|               <option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option> | ||||
|               <option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option> | ||||
|             </select> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div className='mute-modal__action-bar'> | ||||
|   | ||||
| @@ -276,6 +276,8 @@ | ||||
|   "missing_indicator.label": "Not found", | ||||
|   "missing_indicator.sublabel": "This resource could not be found", | ||||
|   "mute_modal.hide_notifications": "Hide notifications from this user?", | ||||
|   "mute_modal.duration": "Duration", | ||||
|   "mute_modal.indefinite": "Indefinite", | ||||
|   "navigation_bar.apps": "Mobile apps", | ||||
|   "navigation_bar.blocks": "Blocked users", | ||||
|   "navigation_bar.bookmarks": "Bookmarks", | ||||
|   | ||||
| @@ -268,6 +268,8 @@ | ||||
|   "missing_indicator.label": "見つかりません", | ||||
|   "missing_indicator.sublabel": "見つかりませんでした", | ||||
|   "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?", | ||||
|   "mute_modal.duration": "ミュートする期間", | ||||
|   "mute_modal.indefinite": "無期限", | ||||
|   "navigation_bar.apps": "アプリ", | ||||
|   "navigation_bar.blocks": "ブロックしたユーザー", | ||||
|   "navigation_bar.bookmarks": "ブックマーク", | ||||
|   | ||||
| @@ -3,12 +3,14 @@ import Immutable from 'immutable'; | ||||
| import { | ||||
|   MUTES_INIT_MODAL, | ||||
|   MUTES_TOGGLE_HIDE_NOTIFICATIONS, | ||||
|   MUTES_CHANGE_DURATION, | ||||
| } from '../actions/mutes'; | ||||
|  | ||||
| const initialState = Immutable.Map({ | ||||
|   new: Immutable.Map({ | ||||
|     account: null, | ||||
|     notifications: true, | ||||
|     duration: 0, | ||||
|   }), | ||||
| }); | ||||
|  | ||||
| @@ -21,6 +23,8 @@ export default function mutes(state = initialState, action) { | ||||
|     }); | ||||
|   case MUTES_TOGGLE_HIDE_NOTIFICATIONS: | ||||
|     return state.updateIn(['new', 'notifications'], (old) => !old); | ||||
|   case MUTES_CHANGE_DURATION: | ||||
|     return state.setIn(['new', 'duration'], Number(action.duration)); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
|   | ||||
| @@ -759,3 +759,8 @@ html { | ||||
| .compose-form .compose-form__warning { | ||||
|   box-shadow: none; | ||||
| } | ||||
|  | ||||
| .mute-modal select { | ||||
|   border: 1px solid lighten($ui-base-color, 8%); | ||||
|   background: $simple-background-color url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 8%))}'/></svg>") no-repeat right 8px center / auto 16px; | ||||
| } | ||||
|   | ||||
| @@ -5074,6 +5074,22 @@ a.status-card.compact:hover { | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   select { | ||||
|     appearance: none; | ||||
|     box-sizing: border-box; | ||||
|     font-size: 14px; | ||||
|     color: $inverted-text-color; | ||||
|     display: inline-block; | ||||
|     width: auto; | ||||
|     outline: 0; | ||||
|     font-family: inherit; | ||||
|     background: $simple-background-color url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(darken($simple-background-color, 14%))}'/></svg>") no-repeat right 8px center / auto 16px; | ||||
|     border: 1px solid darken($simple-background-color, 14%); | ||||
|     border-radius: 4px; | ||||
|     padding: 6px 10px; | ||||
|     padding-right: 30px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .confirmation-modal__container, | ||||
|   | ||||
| @@ -131,9 +131,12 @@ module AccountInteractions | ||||
|                        .find_or_create_by!(target_account: other_account) | ||||
|   end | ||||
|  | ||||
|   def mute!(other_account, notifications: nil) | ||||
|   def mute!(other_account, notifications: nil, duration: 0) | ||||
|     notifications = true if notifications.nil? | ||||
|     mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account) | ||||
|     mute = mute_relationships.create_with(hide_notifications: notifications).find_or_initialize_by(target_account: other_account) | ||||
|     mute.expires_in = duration.zero? ? nil : duration | ||||
|     mute.save! | ||||
|  | ||||
|     remove_potential_friendship(other_account) | ||||
|  | ||||
|     # When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't. | ||||
|   | ||||
| @@ -9,11 +9,13 @@ | ||||
| #  account_id         :bigint(8)        not null | ||||
| #  target_account_id  :bigint(8)        not null | ||||
| #  hide_notifications :boolean          default(TRUE), not null | ||||
| #  expires_at         :datetime | ||||
| # | ||||
|  | ||||
| class Mute < ApplicationRecord | ||||
|   include Paginable | ||||
|   include RelationshipCacheable | ||||
|   include Expireable | ||||
|  | ||||
|   belongs_to :account | ||||
|   belongs_to :target_account, class_name: 'Account' | ||||
|   | ||||
							
								
								
									
										10
									
								
								app/serializers/rest/muted_account_serializer.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/serializers/rest/muted_account_serializer.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class REST::MutedAccountSerializer < REST::AccountSerializer | ||||
|   attribute :mute_expires_at | ||||
|  | ||||
|   def mute_expires_at | ||||
|     mute = current_user.account.mute_relationships.find_by(target_account_id: object.id) | ||||
|     mute && !mute.expired? ? mute.expires_at : nil | ||||
|   end | ||||
| end | ||||
| @@ -1,10 +1,10 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class MuteService < BaseService | ||||
|   def call(account, target_account, notifications: nil) | ||||
|   def call(account, target_account, notifications: nil, duration: 0) | ||||
|     return if account.id == target_account.id | ||||
|  | ||||
|     mute = account.mute!(target_account, notifications: notifications) | ||||
|     mute = account.mute!(target_account, notifications: notifications, duration: duration) | ||||
|  | ||||
|     if mute.hide_notifications? | ||||
|       BlockWorker.perform_async(account.id, target_account.id) | ||||
| @@ -12,6 +12,8 @@ class MuteService < BaseService | ||||
|       MuteWorker.perform_async(account.id, target_account.id) | ||||
|     end | ||||
|  | ||||
|     DeleteMuteWorker.perform_at(duration.seconds, mute.id) if duration != 0 | ||||
|  | ||||
|     mute | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										10
									
								
								app/workers/delete_mute_worker.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/workers/delete_mute_worker.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class DeleteMuteWorker | ||||
|   include Sidekiq::Worker | ||||
|  | ||||
|   def perform(mute_id) | ||||
|     mute = Mute.find_by(id: mute_id) | ||||
|     UnmuteService.new.call(mute.account, mute.target_account) if mute&.expired? | ||||
|   end | ||||
| end | ||||
							
								
								
									
										5
									
								
								db/migrate/20200317021758_add_expires_at_to_mutes.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								db/migrate/20200317021758_add_expires_at_to_mutes.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| class AddExpiresAtToMutes < ActiveRecord::Migration[5.2] | ||||
|   def change | ||||
|     add_column :mutes, :expires_at, :datetime | ||||
|   end | ||||
| end | ||||
| @@ -545,6 +545,7 @@ ActiveRecord::Schema.define(version: 2020_10_08_220312) do | ||||
|     t.boolean "hide_notifications", default: true, null: false | ||||
|     t.bigint "account_id", null: false | ||||
|     t.bigint "target_account_id", null: false | ||||
|     t.datetime "expires_at" | ||||
|     t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true | ||||
|     t.index ["target_account_id"], name: "index_mutes_on_target_account_id" | ||||
|   end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user