Add emoji reaction detail status
This commit is contained in:
parent
de951a0ef9
commit
a1485f242d
22 changed files with 393 additions and 16 deletions
|
@ -3,6 +3,7 @@
|
|||
class Api::BaseController < ApplicationController
|
||||
DEFAULT_STATUSES_LIMIT = 20
|
||||
DEFAULT_ACCOUNTS_LIMIT = 40
|
||||
DEFAULT_EMOJI_REACTION_LIMIT = 10
|
||||
|
||||
include RateLimitHeaders
|
||||
include AccessTokenTrackingConcern
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||
before_action :set_status
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render json: @accounts, each_serializer: REST::EmojiReactionAccountSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_accounts
|
||||
scope = default_accounts
|
||||
# scope = scope.where.not(account_id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
||||
scope.merge(paginated_emoji_reactions).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
EmojiReaction
|
||||
.where(status_id: @status.id)
|
||||
#.where(account: { suspended_at: nil })
|
||||
end
|
||||
|
||||
def paginated_emoji_reactions
|
||||
EmojiReaction.paginate_by_max_id(
|
||||
limit_param(1000), #limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_status_emoji_reactioned_by_index_url pagination_params(max_id: pagination_max_id) if records_continue?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_status_emoji_reactioned_by_index_url pagination_params(since_id: pagination_since_id) unless @accounts.empty?
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
|
@ -40,6 +40,13 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
private
|
||||
|
||||
def create_private(emoji)
|
||||
count = EmojiReaction.where(account: current_account, status: @status).count
|
||||
|
||||
if count >= DEFAULT_EMOJI_REACTION_LIMIT
|
||||
bad_request
|
||||
return
|
||||
end
|
||||
|
||||
EmojiReactService.new.call(current_account, @status, emoji)
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
|
|
@ -33,6 +33,10 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
|
|||
export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
|
||||
export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
|
||||
|
||||
export const EMOJI_REACTIONS_FETCH_REQUEST = 'EMOJI_REACTIONS_FETCH_REQUEST';
|
||||
export const EMOJI_REACTIONS_FETCH_SUCCESS = 'EMOJI_REACTIONS_FETCH_SUCCESS';
|
||||
export const EMOJI_REACTIONS_FETCH_FAIL = 'EMOJI_REACTIONS_FETCH_FAIL';
|
||||
|
||||
export const PIN_REQUEST = 'PIN_REQUEST';
|
||||
export const PIN_SUCCESS = 'PIN_SUCCESS';
|
||||
export const PIN_FAIL = 'PIN_FAIL';
|
||||
|
@ -427,6 +431,41 @@ export function fetchFavouritesFail(id, error) {
|
|||
};
|
||||
}
|
||||
|
||||
export function fetchEmojiReactions(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchEmojiReactionsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${id}/emoji_reactioned_by`).then(response => {
|
||||
dispatch(importFetchedAccounts(response.data.map((er) => er.account)));
|
||||
dispatch(fetchEmojiReactionsSuccess(id, response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchEmojiReactionsFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchEmojiReactionsRequest(id) {
|
||||
return {
|
||||
type: EMOJI_REACTIONS_FETCH_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchEmojiReactionsSuccess(id, accounts) {
|
||||
return {
|
||||
type: EMOJI_REACTIONS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchEmojiReactionsFail(id, error) {
|
||||
return {
|
||||
type: EMOJI_REACTIONS_FETCH_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function pin(status) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(pinRequest(status));
|
||||
|
|
|
@ -39,6 +39,7 @@ class Account extends ImmutablePureComponent {
|
|||
actionTitle: PropTypes.string,
|
||||
defaultAction: PropTypes.string,
|
||||
onActionClick: PropTypes.func,
|
||||
children: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -70,7 +71,7 @@ class Account extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size } = this.props;
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, children } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
|
@ -146,6 +147,10 @@ class Account extends ImmutablePureComponent {
|
|||
<DisplayName account={account} />
|
||||
</Link>
|
||||
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<div className='account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
|
|
33
app/javascript/mastodon/components/emoji_view.jsx
Normal file
33
app/javascript/mastodon/components/emoji_view.jsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import emojify from '../features/emoji/emoji';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class EmojiView extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
name: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
staticUrl: PropTypes.string,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { name, url, staticUrl } = this.props;
|
||||
|
||||
let emojiHtml = null;
|
||||
if (url) {
|
||||
let customEmojis = {};
|
||||
customEmojis[`:${name}:`] = { url, static_url: staticUrl };
|
||||
emojiHtml = emojify(`:${name}:`, customEmojis);
|
||||
} else {
|
||||
emojiHtml = emojify(name);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className='emoji' dangerouslySetInnerHTML={{ __html: emojiHtml }} />
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import { injectIntl } from 'react-intl';
|
||||
import emojify from '../features/emoji/emoji';
|
||||
import classNames from 'classnames';
|
||||
import EmojiView from './emoji_view';
|
||||
|
||||
class EmojiReactionButton extends React.PureComponent {
|
||||
|
||||
|
@ -32,15 +33,6 @@ class EmojiReactionButton extends React.PureComponent {
|
|||
render () {
|
||||
const { name, url, staticUrl, count, me } = this.props;
|
||||
|
||||
let emojiHtml = null;
|
||||
if (url) {
|
||||
let customEmojis = {};
|
||||
customEmojis[`:${name}:`] = { url, static_url: staticUrl };
|
||||
emojiHtml = emojify(`:${name}:`, customEmojis);
|
||||
} else {
|
||||
emojiHtml = emojify(name);
|
||||
}
|
||||
|
||||
const classList = {
|
||||
'emoji-reactions-bar__button': true,
|
||||
'toggled': me,
|
||||
|
@ -48,7 +40,9 @@ class EmojiReactionButton extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<button className={classNames(classList)} type='button' onClick={this.onClick}>
|
||||
<span className='emoji' dangerouslySetInnerHTML={{ __html: emojiHtml }} />
|
||||
<span className='emoji'>
|
||||
<EmojiView name={name} url={url} staticUrl={staticUrl} />
|
||||
</span>
|
||||
<span className='count'>{count}</span>
|
||||
</button>
|
||||
);
|
||||
|
|
107
app/javascript/mastodon/features/emoji_reactions/index.jsx
Normal file
107
app/javascript/mastodon/features/emoji_reactions/index.jsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { fetchEmojiReactions, fetchFavourites } from 'mastodon/actions/interactions';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import EmojiView from '../../components/emoji_view';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => { return {
|
||||
accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId]),
|
||||
} };
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class EmojiReactions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
if (!this.props.accountIds) {
|
||||
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||
this.props.dispatch(fetchEmojiReactions(nextProps.params.statusId));
|
||||
}
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, accountIds, multiColumn } = this.props;
|
||||
console.dir(accountIds);
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
let groups = {};
|
||||
for (const emoji_reaction of accountIds) {
|
||||
const key = emoji_reaction.account.id;
|
||||
const value = emoji_reaction;
|
||||
if (!groups[key]) groups[key] = [value];
|
||||
else groups[key].push(value);
|
||||
}
|
||||
console.dir(groups)
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.emoji_reactions' defaultMessage='No one has reacted with emoji this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn}>
|
||||
<ColumnHeader
|
||||
showBackButton
|
||||
multiColumn={multiColumn}
|
||||
extraButton={(
|
||||
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='emoji_reactions'
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{Object.keys(groups).map((key, index) =>(
|
||||
<AccountContainer key={index} id={key} withNote={false}>
|
||||
<div style={ { 'max-width': '100px' } }>
|
||||
{groups[key].map((value) => <EmojiView name={value.name} url={value.url} staticUrl={value.static_url} />)}
|
||||
</div>
|
||||
</AccountContainer>
|
||||
))}
|
||||
</ScrollableList>
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -49,6 +49,8 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
available: PropTypes.bool,
|
||||
}),
|
||||
onToggleMediaVisibility: PropTypes.func,
|
||||
onEmojiReact: PropTypes.func,
|
||||
onUnEmojiReact: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -191,7 +193,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
let emojiReactionsBar = null;
|
||||
if (status.get('emoji_reactions')) {
|
||||
const emojiReactions = status.get('emoji_reactions');
|
||||
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} status={status} onEmojiReaction={this.props.onEmojiReaction} OnUnEmojiReaction={this.props.OnUnEmojiReaction} />;
|
||||
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} status={status} onEmojiReact={this.props.onEmojiReact} onUnEmojiReact={this.props.onUnEmojiReact} />;
|
||||
}
|
||||
|
||||
if (status.get('application')) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
pin,
|
||||
unpin,
|
||||
emojiReact,
|
||||
unEmojiReact,
|
||||
} from '../../../actions/interactions';
|
||||
import {
|
||||
muteStatus,
|
||||
|
@ -98,6 +99,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
dispatch(emojiReact(status, emoji));
|
||||
},
|
||||
|
||||
onUnEmojiReact (status, emoji) {
|
||||
dispatch(unEmojiReact(status, emoji));
|
||||
},
|
||||
|
||||
onPin (status) {
|
||||
if (status.get('pinned')) {
|
||||
dispatch(unpin(status));
|
||||
|
|
|
@ -24,6 +24,8 @@ import Column from '../ui/components/column';
|
|||
import {
|
||||
favourite,
|
||||
unfavourite,
|
||||
emojiReact,
|
||||
unEmojiReact,
|
||||
bookmark,
|
||||
unbookmark,
|
||||
reblog,
|
||||
|
@ -254,6 +256,16 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleEmojiReact = (status, emoji) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(emojiReact(status, emoji));
|
||||
};
|
||||
|
||||
handleUnEmojiReact = (status, emoji) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(unEmojiReact(status, emoji));
|
||||
};
|
||||
|
||||
handlePin = (status) => {
|
||||
if (status.get('pinned')) {
|
||||
this.props.dispatch(unpin(status));
|
||||
|
@ -644,6 +656,8 @@ class Status extends ImmutablePureComponent {
|
|||
showMedia={this.state.showMedia}
|
||||
onToggleMediaVisibility={this.handleToggleMediaVisibility}
|
||||
pictureInPicture={pictureInPicture}
|
||||
onEmojiReact={this.handleEmojiReact}
|
||||
onUnEmojiReact={this.handleUnEmojiReact}
|
||||
/>
|
||||
|
||||
<ActionBar
|
||||
|
@ -651,6 +665,7 @@ class Status extends ImmutablePureComponent {
|
|||
status={status}
|
||||
onReply={this.handleReplyClick}
|
||||
onFavourite={this.handleFavouriteClick}
|
||||
onEmojiReact={this.handleEmojiReact}
|
||||
onReblog={this.handleReblogClick}
|
||||
onBookmark={this.handleBookmarkClick}
|
||||
onDelete={this.handleDeleteClick}
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
Following,
|
||||
Reblogs,
|
||||
Favourites,
|
||||
EmojiReactions,
|
||||
DirectTimeline,
|
||||
HashtagTimeline,
|
||||
Notifications,
|
||||
|
@ -206,6 +207,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/emoji_reactions' component={EmojiReactions} content={children} />
|
||||
|
||||
{/* Legacy routes, cannot be easily factored with other routes because they share a param name */}
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
|
@ -213,6 +215,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/emoji_reactions' component={EmojiReactions} content={children} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
||||
|
|
|
@ -78,6 +78,10 @@ export function Favourites () {
|
|||
return import(/* webpackChunkName: "features/favourites" */'../../favourites');
|
||||
}
|
||||
|
||||
export function EmojiReactions () {
|
||||
return import(/* webpackChunkName: "features/favourites" */'../../emoji_reactions');
|
||||
}
|
||||
|
||||
export function FollowRequests () {
|
||||
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests');
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
import {
|
||||
REBLOGS_FETCH_SUCCESS,
|
||||
FAVOURITES_FETCH_SUCCESS,
|
||||
EMOJI_REACTIONS_FETCH_SUCCESS,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
BLOCKS_FETCH_REQUEST,
|
||||
|
@ -69,6 +70,7 @@ const initialState = ImmutableMap({
|
|||
following: initialListState,
|
||||
reblogged_by: initialListState,
|
||||
favourited_by: initialListState,
|
||||
emoji_reactioned_by: initialListState,
|
||||
follow_requests: initialListState,
|
||||
blocks: initialListState,
|
||||
mutes: initialListState,
|
||||
|
@ -133,6 +135,10 @@ export default function userLists(state = initialState, action) {
|
|||
return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
case FAVOURITES_FETCH_SUCCESS:
|
||||
return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
case EMOJI_REACTIONS_FETCH_SUCCESS:
|
||||
console.log('===================')
|
||||
console.dir(state);
|
||||
return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts));
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
|
||||
case FOLLOW_REQUESTS_FETCH_SUCCESS:
|
||||
|
|
|
@ -42,8 +42,11 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
|
||||
return if @account.reacted?(@original_status, shortcode, emoji)
|
||||
|
||||
return if EmojiReaction.where(account: @account, status: @original_status).count >= BaseController::DEFAULT_EMOJI_REACTION_LIMIT
|
||||
|
||||
EmojiReaction.find_by(account: @account, status: @original_status)&.destroy
|
||||
reaction = @original_status.emoji_reactions.create!(account: @account, name: shortcode, custom_emoji: emoji, uri: @json['id'])
|
||||
write_stream(reaction)
|
||||
|
||||
if @original_status.account.local?
|
||||
NotifyService.new.call(@original_status.account, :emoji_reaction, reaction)
|
||||
|
@ -91,4 +94,15 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
|
||||
@emoji_tag = @json['tag'].is_a?(Array) ? @json['tag']&.first : @json['tag']
|
||||
end
|
||||
|
||||
def write_stream(emoji_reaction)
|
||||
emoji_group = @original_status.emoji_reactions_grouped_by_name
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.custom_emoji&.domain) }
|
||||
emoji_group['status_id'] = @original_status.id.to_s
|
||||
FeedAnyJsonWorker.perform_async(render_emoji_reaction(emoji_group), @original_status.id, emoji_reaction.account_id)
|
||||
end
|
||||
|
||||
def render_emoji_reaction(emoji_group)
|
||||
@render_emoji_reaction ||= Oj.dump(event: :emoji_reaction, payload: emoji_group.to_json)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -140,6 +140,23 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
def write_stream(emoji_reaction)
|
||||
emoji_group = @original_status.emoji_reactions_grouped_by_name
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.custom_emoji&.domain) }
|
||||
if emoji_group
|
||||
emoji_group['status_id'] = @original_status.id.to_s
|
||||
else
|
||||
# name: emoji_reaction.name, count: 0, domain: emoji_reaction.domain
|
||||
emoji_group = { 'name' => emoji_reaction.name, 'count' => 0, 'account_ids' => [], 'status_id' => @status.id.to_s }
|
||||
emoji_group['domain'] = emoji_reaction.custom_emoji.domain if emoji_reaction.custom_emoji
|
||||
end
|
||||
FeedAnyJsonWorker.perform_async(render_emoji_reaction(emoji_group), @original_status.id, emoji_reaction.account_id)
|
||||
end
|
||||
|
||||
def render_emoji_reaction(emoji_group)
|
||||
@render_emoji_reaction ||= Oj.dump(event: :emoji_reaction, payload: emoji_group.to_json)
|
||||
end
|
||||
|
||||
def forward_for_undo_emoji_reaction
|
||||
return unless @json['signature'].present?
|
||||
|
||||
|
|
|
@ -40,4 +40,11 @@ class EmojiReaction < ApplicationRecord
|
|||
|
||||
account.statuses_cleanup_policy&.invalidate_last_inspected(status, :unfav)
|
||||
end
|
||||
|
||||
def paginate_by_max_id(limit, max_id = nil, since_id = nil)
|
||||
query = order(arel_table[:id].desc).limit(limit)
|
||||
query = query.where(arel_table[:id].lt(max_id)) if max_id.present?
|
||||
query = query.where(arel_table[:id].gt(since_id)) if since_id.present?
|
||||
query
|
||||
end
|
||||
end
|
||||
|
|
34
app/serializers/rest/emoji_reaction_account_serializer.rb
Normal file
34
app/serializers/rest/emoji_reaction_account_serializer.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::EmojiReactionAccountSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
include FormattingHelper
|
||||
|
||||
attributes :id, :name
|
||||
|
||||
attribute :url, if: :custom_emoji?
|
||||
attribute :static_url, if: :custom_emoji?
|
||||
attribute :domain, if: :custom_emoji?
|
||||
|
||||
belongs_to :account, serializer: REST::AccountSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def url
|
||||
full_asset_url(object.custom_emoji.image.url)
|
||||
end
|
||||
|
||||
def static_url
|
||||
full_asset_url(object.custom_emoji.image.url(:static))
|
||||
end
|
||||
|
||||
def domain
|
||||
object.custom_emoji.domain
|
||||
end
|
||||
|
||||
def custom_emoji?
|
||||
object.custom_emoji.present?
|
||||
end
|
||||
end
|
|
@ -147,6 +147,7 @@ class DeleteAccountService < BaseService
|
|||
purge_polls!
|
||||
purge_generated_notifications!
|
||||
purge_favourites!
|
||||
purge_emoji_reactions!
|
||||
purge_bookmarks!
|
||||
purge_feeds!
|
||||
purge_other_associations!
|
||||
|
@ -193,6 +194,16 @@ class DeleteAccountService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
def purge_emoji_reactions!
|
||||
@account.emoji_reactions.in_batches do |reactions|
|
||||
reactions.each do |reaction|
|
||||
reaction.status.refresh_emoji_reactions_grouped_by_name
|
||||
end
|
||||
Chewy.strategy.current.update(StatusesIndex, reactions.pluck(:status_id)) if Chewy.enabled?
|
||||
reactions.delete_all
|
||||
end
|
||||
end
|
||||
|
||||
def purge_bookmarks!
|
||||
@account.bookmarks.in_batches do |bookmarks|
|
||||
Chewy.strategy.current.update(StatusesIndex, bookmarks.pluck(:status_id)) if Chewy.enabled?
|
||||
|
|
|
@ -55,7 +55,7 @@ class EmojiReactService < BaseService
|
|||
|
||||
def write_stream(emoji_reaction)
|
||||
emoji_group = emoji_reaction.status.emoji_reactions_grouped_by_name
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.domain) }
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.custom_emoji&.domain) }
|
||||
emoji_group['status_id'] = emoji_reaction.status_id.to_s
|
||||
FeedAnyJsonWorker.perform_async(render_emoji_reaction(emoji_group), emoji_reaction.status_id, emoji_reaction.account_id)
|
||||
end
|
||||
|
|
|
@ -40,7 +40,7 @@ class UnEmojiReactService < BaseService
|
|||
|
||||
def write_stream(emoji_reaction)
|
||||
emoji_group = @status.emoji_reactions_grouped_by_name
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.domain) }
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.custom_emoji&.domain) }
|
||||
if emoji_group
|
||||
emoji_group['status_id'] = @status.id.to_s
|
||||
else
|
||||
|
|
|
@ -16,6 +16,7 @@ Rails.application.routes.draw do
|
|||
/lists/(*any)
|
||||
/notifications
|
||||
/favourites
|
||||
/emoji_reactions
|
||||
/bookmarks
|
||||
/pinned
|
||||
/start
|
||||
|
@ -439,6 +440,7 @@ Rails.application.routes.draw do
|
|||
scope module: :statuses do
|
||||
resources :reblogged_by, controller: :reblogged_by_accounts, only: :index
|
||||
resources :favourited_by, controller: :favourited_by_accounts, only: :index
|
||||
resources :emoji_reactioned_by, controller: :emoji_reactioned_by_accounts, only: :index
|
||||
resource :reblog, only: :create
|
||||
post :unreblog, to: 'reblogs#destroy'
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue