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 &&
}
+
+ );
+ }
+}
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 &&
}
-
- );
- }
-}
+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