From a63147aa0b7809aecbdc2dadbb9d13911997f3ce Mon Sep 17 00:00:00 2001 From: noellabo Date: Sat, 18 Sep 2021 15:11:36 +0900 Subject: [PATCH] Add blocking and muting process for emoji reactions --- .../mastodon/components/account_popup.js | 83 ++++++++++++++ .../components/emoji_reactions_bar.js | 105 ++++++------------ app/lib/activitypub/activity/emoji_react.rb | 1 + app/lib/activitypub/activity/like.rb | 1 + app/models/status.rb | 4 +- app/validators/emoji_reaction_validator.rb | 9 -- 6 files changed, 120 insertions(+), 83 deletions(-) create mode 100644 app/javascript/mastodon/components/account_popup.js diff --git a/app/javascript/mastodon/components/account_popup.js b/app/javascript/mastodon/components/account_popup.js new file mode 100644 index 000000000..8796dd0e3 --- /dev/null +++ b/app/javascript/mastodon/components/account_popup.js @@ -0,0 +1,83 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { makeGetAccount } from 'mastodon/selectors'; +import ImmutablePropTypes, { list } from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Avatar from 'mastodon/components/avatar'; +import { FormattedMessage, injectIntl } from 'react-intl'; + +const makeMapStateToProps = () => { + const getAccount = makeGetAccount(); + + const mapStateToProps = (state, { accountId }) => ({ + account: getAccount(state, accountId), + }); + + return mapStateToProps; +}; + +@connect(makeMapStateToProps) +class Account extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map, + }; + + render () { + const { account } = this.props; + + if ( !account ) { + return ; + } + + return ( +
+
+ +
+ ); + } +} + +const ACCOUNT_POPUP_ROWS_MAX = 10; + +@injectIntl +export default class AccountPopup extends ImmutablePureComponent { + + static propTypes = { + accountIds: ImmutablePropTypes.list.isRequired, + style: PropTypes.object, + placement: PropTypes.string, + arrowOffsetLeft: PropTypes.string, + arrowOffsetTop: PropTypes.string, + }; + + render () { + const { accountIds, placement } = this.props; + var { arrowOffsetLeft, arrowOffsetTop, style } = this.props; + const OFFSET = 6; + + if (placement === 'top') { + arrowOffsetTop = String(parseInt(arrowOffsetTop ?? '0') - OFFSET); + style = { ...style, top: style.top - OFFSET }; + } else if (placement === 'bottom') { + arrowOffsetTop = String(parseInt(arrowOffsetTop ?? '0') + OFFSET); + style = { ...style, top: style.top + OFFSET }; + } else if (placement === 'left') { + arrowOffsetLeft = String(parseInt(arrowOffsetLeft ?? '0') - OFFSET); + style = { ...style, left: style.left - OFFSET }; + } else if (placement === 'right') { + arrowOffsetLeft = String(parseInt(arrowOffsetLeft ?? '0') + OFFSET); + style = { ...style, left: style.left + OFFSET }; + } + + return ( +
+
+ {accountIds.take(ACCOUNT_POPUP_ROWS_MAX).map(accountId => )} + {accountIds.size > ACCOUNT_POPUP_ROWS_MAX &&
<>{msg}} />
} +
+ ); + } +} diff --git a/app/javascript/mastodon/components/emoji_reactions_bar.js b/app/javascript/mastodon/components/emoji_reactions_bar.js index 2374af399..0799b2051 100644 --- a/app/javascript/mastodon/components/emoji_reactions_bar.js +++ b/app/javascript/mastodon/components/emoji_reactions_bar.js @@ -1,8 +1,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { makeGetAccount } from 'mastodon/selectors'; -import ImmutablePropTypes, { list } from 'react-immutable-proptypes'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { List } from 'immutable'; import classNames from 'classnames'; @@ -12,91 +11,47 @@ import TransitionMotion from 'react-motion/lib/TransitionMotion'; import AnimatedNumber from 'mastodon/components/animated_number'; import { reduceMotion, me } from 'mastodon/initial_state'; import spring from 'react-motion/lib/spring'; -import Avatar from 'mastodon/components/avatar'; import Overlay from 'react-overlays/lib/Overlay'; -import { FormattedMessage, injectIntl } from 'react-intl'; import { isUserTouching } from 'mastodon/is_mobile'; +import AccountPopup from 'mastodon/components/account_popup'; -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); +const getFilteredEmojiReaction = (emojiReaction, relationships) => { + let filteredEmojiReaction = emojiReaction.update('account_ids', accountIds => accountIds.filterNot( accountId => { + const relationship = relationships.get(accountId); + return relationship?.get('blocking') || relationship?.get('blocked_by') || relationship?.get('domain_blocking') || relationship?.get('muting') + })); - const mapStateToProps = (state, { accountId }) => ({ - account: getAccount(state, accountId), - }); + const count = filteredEmojiReaction.get('account_ids').size; - return mapStateToProps; + if (count > 0) { + return filteredEmojiReaction.set('count', count); + } else { + return null; + } }; -@connect(makeMapStateToProps) -class Account extends ImmutablePureComponent { +const mapStateToProps = (state, { emojiReaction }) => { + const relationship = new Map(); + emojiReaction.get('account_ids').forEach(accountId => relationship.set(accountId, state.getIn(['relationships', accountId]))); - static propTypes = { - account: ImmutablePropTypes.map, + return { + emojiReaction: emojiReaction, + relationships: relationship, }; +}; - render () { - const { account } = this.props; - - if ( !account ) { - return ; - } - - return ( -
-
- -
- ); - } -} - -const ACCOUNT_POPUP_ROWS_MAX = 10; - -@injectIntl -class AccountPopup extends ImmutablePureComponent { - - static propTypes = { - accountIds: ImmutablePropTypes.list.isRequired, - style: PropTypes.object, - placement: PropTypes.string, - arrowOffsetLeft: PropTypes.string, - arrowOffsetTop: PropTypes.string, - }; - - render () { - const { accountIds, placement } = this.props; - var { arrowOffsetLeft, arrowOffsetTop, style } = this.props; - const OFFSET = 6; - - if (placement === 'top') { - arrowOffsetTop = String(parseInt(arrowOffsetTop ?? '0') - OFFSET); - style = { ...style, top: style.top - OFFSET }; - } else if (placement === 'bottom') { - arrowOffsetTop = String(parseInt(arrowOffsetTop ?? '0') + OFFSET); - style = { ...style, top: style.top + OFFSET }; - } else if (placement === 'left') { - arrowOffsetLeft = String(parseInt(arrowOffsetLeft ?? '0') - OFFSET); - style = { ...style, left: style.left - OFFSET }; - } else if (placement === 'right') { - arrowOffsetLeft = String(parseInt(arrowOffsetLeft ?? '0') + OFFSET); - style = { ...style, left: style.left + OFFSET }; - } - - return ( -
-
- {accountIds.take(ACCOUNT_POPUP_ROWS_MAX).map(accountId => )} - {accountIds.size > ACCOUNT_POPUP_ROWS_MAX &&
<>{msg}} />
} -
- ); - } -} +const mergeProps = ({ emojiReaction, relationships }, dispatchProps, ownProps) => ({ + ...ownProps, + ...dispatchProps, + emojiReaction: getFilteredEmojiReaction(emojiReaction, relationships), +}); +@connect(mapStateToProps, null, mergeProps) class EmojiReaction extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, - emojiReaction: ImmutablePropTypes.map.isRequired, + emojiReaction: ImmutablePropTypes.map, myReaction: PropTypes.bool.isRequired, addEmojiReaction: PropTypes.func.isRequired, removeEmojiReaction: PropTypes.func.isRequired, @@ -154,6 +109,10 @@ class EmojiReaction extends ImmutablePureComponent { render () { const { emojiReaction, status, myReaction } = this.props; + if (!emojiReaction) { + return ; + } + let shortCode = emojiReaction.get('name'); if (unicodeMapping[shortCode]) { @@ -198,7 +157,7 @@ export default class EmojiReactionsBar extends ImmutablePureComponent { render () { const { status } = this.props; - const emoji_reactions = status.get("emoji_reactions") + const emoji_reactions = status.get("emoji_reactions"); const visibleReactions = emoji_reactions.filter(x => x.get('count') > 0); if (visibleReactions.isEmpty() ) { diff --git a/app/lib/activitypub/activity/emoji_react.rb b/app/lib/activitypub/activity/emoji_react.rb index 5e9425a6d..f2a499374 100644 --- a/app/lib/activitypub/activity/emoji_react.rb +++ b/app/lib/activitypub/activity/emoji_react.rb @@ -6,6 +6,7 @@ class ActivityPub::Activity::EmojiReact < ActivityPub::Activity shortcode = @json['content'] return if original_status.nil? || !original_status.account.local? || delete_arrived_first?(@json['id']) || @account.reacted?(original_status, shortcode) + return if original_status.account.blocking?(@account) || @account.blocking?(original_status.account) || original_status.account.domain_blocking?(@account.domain) reaction = original_status.emoji_reactions.create!(account: @account, name: shortcode, uri: @json['id']) diff --git a/app/lib/activitypub/activity/like.rb b/app/lib/activitypub/activity/like.rb index c72834a86..516714bc7 100644 --- a/app/lib/activitypub/activity/like.rb +++ b/app/lib/activitypub/activity/like.rb @@ -5,6 +5,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity @original_status = status_from_uri(object_uri) return if @original_status.nil? || delete_arrived_first?(@json['id']) + return if @original_status.account.local? && (@original_status.account.blocking?(@account) || @account.blocking?(@original_status.account) || @original_status.account.domain_blocking?(@account.domain)) lock_or_fail("like:#{object_uri}") do if shortcode.nil? diff --git a/app/models/status.rb b/app/models/status.rb index 646a117a9..81aaa810f 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -304,13 +304,15 @@ class Status < ApplicationRecord if account.present? emoji_reactions.each do |emoji_reaction| emoji_reaction['me'] = emoji_reaction['account_ids'].include?(account.id.to_s) + emoji_reaction['account_ids'] -= account.excluded_from_timeline_account_ids.map(&:to_s) + emoji_reaction['count'] = emoji_reaction['account_ids'].size end end end end def generate_grouped_emoji_reactions - records = emoji_reactions.group(:name).order(Arel.sql('MIN(created_at) ASC')).select('name, min(custom_emoji_id) as custom_emoji_id, count(*) as count, array_agg(account_id::text order by created_at) as account_ids') + records = emoji_reactions.group(:name).order(Arel.sql('MIN(created_at) ASC')).select('name, min(custom_emoji_id) as custom_emoji_id, count(*) as count, array_agg(account_id::text order by created_at) as account_ids').limit(EmojiReactionValidator::LIMIT) Oj.dump(ActiveModelSerializers::SerializableResource.new(records, each_serializer: REST::GroupedEmojiReactionSerializer, scope: nil, scope_name: :current_user)) end diff --git a/app/validators/emoji_reaction_validator.rb b/app/validators/emoji_reaction_validator.rb index 9619f23d6..a7860b9b2 100644 --- a/app/validators/emoji_reaction_validator.rb +++ b/app/validators/emoji_reaction_validator.rb @@ -8,7 +8,6 @@ class EmojiReactionValidator < ActiveModel::Validator return if reaction.name.blank? reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) if reaction.custom_emoji_id.blank? && !unicode_emoji?(reaction.name) - reaction.errors.add(:base, I18n.t('reactions.errors.limit_reached')) if new_reaction?(reaction) && limit_reached?(reaction) end @@ -17,12 +16,4 @@ class EmojiReactionValidator < ActiveModel::Validator def unicode_emoji?(name) SUPPORTED_EMOJIS.include?(name) end - - def new_reaction?(reaction) - !reaction.status.emoji_reactions.where(name: reaction.name).exists? - end - - def limit_reached?(reaction) - reaction.status.emoji_reactions.where.not(name: reaction.name).count('distinct name') >= LIMIT - end end