Add blocking and muting process for emoji reactions
This commit is contained in:
parent
56fa09dbd8
commit
a63147aa0b
6 changed files with 120 additions and 83 deletions
83
app/javascript/mastodon/components/account_popup.js
Normal file
83
app/javascript/mastodon/components/account_popup.js
Normal file
|
@ -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 <Fragment></Fragment>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account-popup__wapper'>
|
||||
<div className='account-popup__avatar-wrapper'><Avatar account={account} size={14} /></div>
|
||||
<bdi><strong className='account-popup__display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={`dropdown-menu account-popup ${placement}`} style={{ ...style}}>
|
||||
<div className={`dropdown-menu__arrow account-popup__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
||||
{accountIds.take(ACCOUNT_POPUP_ROWS_MAX).map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||
{accountIds.size > ACCOUNT_POPUP_ROWS_MAX && <div className='account-popup__wapper'><bdi><strong className='account-popup__display-name__html'><FormattedMessage id='account_popup.more_users' defaultMessage='({number, plural, one {# other user} other {# other users}})' values={{ number: accountIds.size - ACCOUNT_POPUP_ROWS_MAX}} children={msg=> <>{msg}</>} /></strong></bdi></div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
if (count > 0) {
|
||||
return filteredEmojiReaction.set('count', count);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { emojiReaction }) => {
|
||||
const relationship = new Map();
|
||||
emojiReaction.get('account_ids').forEach(accountId => relationship.set(accountId, state.getIn(['relationships', accountId])));
|
||||
|
||||
return {
|
||||
emojiReaction: emojiReaction,
|
||||
relationships: relationship,
|
||||
};
|
||||
};
|
||||
|
||||
const mergeProps = ({ emojiReaction, relationships }, dispatchProps, ownProps) => ({
|
||||
...ownProps,
|
||||
...dispatchProps,
|
||||
emojiReaction: getFilteredEmojiReaction(emojiReaction, relationships),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
@connect(makeMapStateToProps)
|
||||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
|
||||
if ( !account ) {
|
||||
return <Fragment></Fragment>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account-popup__wapper'>
|
||||
<div className='account-popup__avatar-wrapper'><Avatar account={account} size={14} /></div>
|
||||
<bdi><strong className='account-popup__display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={`dropdown-menu account-popup ${placement}`} style={{ ...style}}>
|
||||
<div className={`dropdown-menu__arrow account-popup__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
||||
{accountIds.take(ACCOUNT_POPUP_ROWS_MAX).map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||
{accountIds.size > ACCOUNT_POPUP_ROWS_MAX && <div className='account-popup__wapper'><bdi><strong className='account-popup__display-name__html'><FormattedMessage id='account_popup.more_users' defaultMessage='({number, plural, one {# other user} other {# other users}})' values={{ number: accountIds.size - ACCOUNT_POPUP_ROWS_MAX}} children={msg=> <>{msg}</>} /></strong></bdi></div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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 <Fragment></Fragment>;
|
||||
}
|
||||
|
||||
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() ) {
|
||||
|
|
|
@ -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'])
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue