Add header and emoji to list of reactions to posts
This commit is contained in:
parent
7af54f7d79
commit
56fa09dbd8
25 changed files with 677 additions and 116 deletions
|
@ -1,31 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseController
|
||||
class Api::V1::Statuses::EmojiReactionedByController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||
before_action -> { authorize_if_got_token! :read, :'read:favourites' }
|
||||
before_action :set_status
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||
@emoji_reactions = load_emoji_reactions
|
||||
render json: @emoji_reactions, each_serializer: REST::EmojiReactionSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_accounts
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
||||
def load_emoji_reactions
|
||||
scope = default_emoji_reactions
|
||||
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
|
||||
Account
|
||||
.without_suspended
|
||||
.includes(:emoji_reactions, :account_stat)
|
||||
.references(:emoji_reactions)
|
||||
.where(emoji_reactions: { status_id: @status.id })
|
||||
def default_emoji_reactions
|
||||
EmojiReaction
|
||||
.where(status_id: @status.id)
|
||||
end
|
||||
|
||||
def paginated_emoji_reactions
|
||||
|
@ -47,21 +44,21 @@ class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseControll
|
|||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
unless @emoji_reactions.empty?
|
||||
api_v1_status_emoji_reactioned_by_index_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.emoji_reactions.last.id
|
||||
@emoji_reactions.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.emoji_reactions.first.id
|
||||
@emoji_reactions.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
@emoji_reactions.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def set_status
|
|
@ -1,5 +1,6 @@
|
|||
import api from '../api';
|
||||
import api, { getLinks } from '../api';
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { me } from '../initial_state';
|
||||
|
||||
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
|
||||
|
@ -22,18 +23,34 @@ export const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST';
|
|||
export const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS';
|
||||
export const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL';
|
||||
|
||||
export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
|
||||
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
|
||||
export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
|
||||
|
||||
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 FAVOURITES_EXPAND_REQUEST = 'FAVOURITES_EXPAND_REQUEST';
|
||||
export const FAVOURITES_EXPAND_SUCCESS = 'FAVOURITES_EXPAND_SUCCESS';
|
||||
export const FAVOURITES_EXPAND_FAIL = 'FAVOURITES_EXPAND_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 EMOJI_REACTIONS_EXPAND_REQUEST = 'EMOJI_REACTIONS_EXPAND_REQUEST';
|
||||
export const EMOJI_REACTIONS_EXPAND_SUCCESS = 'EMOJI_REACTIONS_EXPAND_SUCCESS';
|
||||
export const EMOJI_REACTIONS_EXPAND_FAIL = 'EMOJI_REACTIONS_EXPAND_FAIL';
|
||||
|
||||
export const MENTIONS_FETCH_REQUEST = 'MENTIONS_FETCH_REQUEST';
|
||||
export const MENTIONS_FETCH_SUCCESS = 'MENTIONS_FETCH_SUCCESS';
|
||||
export const MENTIONS_FETCH_FAIL = 'MENTIONS_FETCH_FAIL';
|
||||
|
||||
export const MENTIONS_EXPAND_REQUEST = 'MENTIONS_EXPAND_REQUEST';
|
||||
export const MENTIONS_EXPAND_SUCCESS = 'MENTIONS_EXPAND_SUCCESS';
|
||||
export const MENTIONS_EXPAND_FAIL = 'MENTIONS_EXPAND_FAIL';
|
||||
|
||||
export const PIN_REQUEST = 'PIN_REQUEST';
|
||||
export const PIN_SUCCESS = 'PIN_SUCCESS';
|
||||
export const PIN_FAIL = 'PIN_FAIL';
|
||||
|
@ -291,8 +308,10 @@ export function fetchReblogs(id) {
|
|||
dispatch(fetchReblogsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchReblogsSuccess(id, response.data));
|
||||
dispatch(fetchRelationships(response.data.map(accounts => accounts.id)));
|
||||
dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchReblogsFail(id, error));
|
||||
});
|
||||
|
@ -306,11 +325,12 @@ export function fetchReblogsRequest(id) {
|
|||
};
|
||||
};
|
||||
|
||||
export function fetchReblogsSuccess(id, accounts) {
|
||||
export function fetchReblogsSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: REBLOGS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -321,13 +341,59 @@ export function fetchReblogsFail(id, error) {
|
|||
};
|
||||
};
|
||||
|
||||
export function expandReblogs(id) {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['user_lists', 'reblogged_by', id, 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['user_lists', 'reblogged_by', id, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandReblogsRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchRelationships(response.data.map(accounts => accounts.id)));
|
||||
dispatch(expandReblogsSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandReblogsFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function expandReblogsRequest(id) {
|
||||
return {
|
||||
type: REBLOGS_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandReblogsSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: REBLOGS_EXPAND_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandReblogsFail(id, error) {
|
||||
return {
|
||||
type: REBLOGS_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchFavourites(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchFavouritesRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchFavouritesSuccess(id, response.data));
|
||||
dispatch(fetchRelationships(response.data.map(accounts => accounts.id)));
|
||||
dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchFavouritesFail(id, error));
|
||||
});
|
||||
|
@ -341,11 +407,12 @@ export function fetchFavouritesRequest(id) {
|
|||
};
|
||||
};
|
||||
|
||||
export function fetchFavouritesSuccess(id, accounts) {
|
||||
export function fetchFavouritesSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: FAVOURITES_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -356,13 +423,59 @@ export function fetchFavouritesFail(id, error) {
|
|||
};
|
||||
};
|
||||
|
||||
export function expandFavourites(id) {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['user_lists', 'favourited_by', id, 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['user_lists', 'favourited_by', id, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandFavouritesRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchRelationships(response.data.map(accounts => accounts.id)));
|
||||
dispatch(expandFavouritesSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandFavouritesFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function expandFavouritesRequest(id) {
|
||||
return {
|
||||
type: FAVOURITES_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandFavouritesSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: FAVOURITES_EXPAND_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandFavouritesFail(id, error) {
|
||||
return {
|
||||
type: FAVOURITES_EXPAND_FAIL,
|
||||
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));
|
||||
dispatch(fetchEmojiReactionsSuccess(id, response.data));
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data.map(emojiReaction => emojiReaction.account)));
|
||||
dispatch(fetchRelationships(response.data.map(emojiReaction => emojiReaction.account.id)));
|
||||
dispatch(fetchEmojiReactionsSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchEmojiReactionsFail(id, error));
|
||||
});
|
||||
|
@ -376,11 +489,12 @@ export function fetchEmojiReactionsRequest(id) {
|
|||
};
|
||||
};
|
||||
|
||||
export function fetchEmojiReactionsSuccess(id, accounts) {
|
||||
export function fetchEmojiReactionsSuccess(id, emojiReactions, next) {
|
||||
return {
|
||||
type: EMOJI_REACTIONS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
emojiReactions,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -391,13 +505,59 @@ export function fetchEmojiReactionsFail(id, error) {
|
|||
};
|
||||
};
|
||||
|
||||
export function expandEmojiReactions(id) {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['user_lists', 'emoji_reactioned_by', id, 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['user_lists', 'emoji_reactioned_by', id, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandEmojiReactionsRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data.map(emojiReaction => emojiReaction.account)));
|
||||
dispatch(fetchRelationships(response.data.map(emojiReaction => emojiReaction.account.id)));
|
||||
dispatch(expandEmojiReactionsSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandEmojiReactionsFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function expandEmojiReactionsRequest(id) {
|
||||
return {
|
||||
type: EMOJI_REACTIONS_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandEmojiReactionsSuccess(id, emojiReactions, next) {
|
||||
return {
|
||||
type: EMOJI_REACTIONS_EXPAND_SUCCESS,
|
||||
id,
|
||||
emojiReactions,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandEmojiReactionsFail(id, error) {
|
||||
return {
|
||||
type: EMOJI_REACTIONS_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchMentions(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchMentionsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${id}/mentioned_by`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchMentionsSuccess(id, response.data));
|
||||
dispatch(fetchRelationships(response.data.map(accounts => accounts.id)));
|
||||
dispatch(fetchMentionsSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchMentionsFail(id, error));
|
||||
});
|
||||
|
@ -411,11 +571,12 @@ export function fetchMentionsRequest(id) {
|
|||
};
|
||||
};
|
||||
|
||||
export function fetchMentionsSuccess(id, accounts) {
|
||||
export function fetchMentionsSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: MENTIONS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -426,6 +587,50 @@ export function fetchMentionsFail(id, error) {
|
|||
};
|
||||
};
|
||||
|
||||
export function expandMentions(id) {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['user_lists', 'emoji_reactioned_by', id, 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['user_lists', 'emoji_reactioned_by', id, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandMentionsRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchRelationships(response.data.map(accounts => accounts.id)));
|
||||
dispatch(expandMentionsSuccess(id, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandMentionsFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function expandMentionsRequest(id) {
|
||||
return {
|
||||
type: MENTIONS_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandMentionsSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: MENTIONS_EXPAND_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandMentionsFail(id, error) {
|
||||
return {
|
||||
type: MENTIONS_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function pin(status) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(pinRequest(status));
|
||||
|
|
|
@ -47,7 +47,7 @@ export function fetchSuggestionsFail(error) {
|
|||
};
|
||||
};
|
||||
|
||||
export const dismissSuggestion = accountId => (dispatch, getState) => {
|
||||
export const dismissSuggestion = (accountId, withRelationships = false) => (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: SUGGESTIONS_DISMISS,
|
||||
id: accountId,
|
||||
|
@ -59,6 +59,10 @@ export const dismissSuggestion = accountId => (dispatch, getState) => {
|
|||
api(getState).get('/api/v2/suggestions').then(response => {
|
||||
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
|
||||
dispatch(fetchSuggestionsSuccess(response.data));
|
||||
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(response.data.map(item => item.account.id)));
|
||||
}
|
||||
}).catch(error => dispatch(fetchSuggestionsFail(error)));
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
|
|
@ -38,6 +38,7 @@ class Account extends ImmutablePureComponent {
|
|||
actionIcon: PropTypes.string,
|
||||
actionTitle: PropTypes.string,
|
||||
onActionClick: PropTypes.func,
|
||||
append: PropTypes.node,
|
||||
};
|
||||
|
||||
handleFollow = (e) => {
|
||||
|
@ -77,7 +78,7 @@ class Account extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle } = this.props;
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, append } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return <div />;
|
||||
|
@ -175,6 +176,8 @@ class Account extends ImmutablePureComponent {
|
|||
<div className='account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
|
||||
{append}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -34,7 +34,9 @@ const messages = defineMessages({
|
|||
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
||||
emoji_reaction: { id: 'status.emoji_reaction', defaultMessage: 'Emoji reaction' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
open_emoji_reactions: { id: 'status.open_emoji_reactions', defaultMessage: 'Open emoji reactions list to this post' },
|
||||
show_reblogs: { id: 'status.show_reblogs', defaultMessage: 'Show boosted users' },
|
||||
show_favourites: { id: 'status.show_favourites', defaultMessage: 'Show favourited users' },
|
||||
show_emoji_reactions: { id: 'status.show_emoji_reactions', defaultMessage: 'Show emoji reactioned users' },
|
||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
||||
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
||||
|
@ -225,10 +227,6 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
this.props.onEmbed(this.props.status);
|
||||
}
|
||||
|
||||
handleEmojiReactions = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}/emoji_reactions`);
|
||||
}
|
||||
|
||||
handleReport = () => {
|
||||
this.props.onReport(this.props.status);
|
||||
}
|
||||
|
@ -256,6 +254,18 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleReblogs = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}/reblogs`);
|
||||
}
|
||||
|
||||
handleFavourites = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}/favourites`);
|
||||
}
|
||||
|
||||
handleEmojiReactions = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}/emoji_reactions`);
|
||||
}
|
||||
|
||||
handleEmojiPick = data => {
|
||||
const { addEmojiReaction, status } = this.props;
|
||||
addEmojiReaction(status, data.native.replace(/:/g, ''), null, null, null);
|
||||
|
@ -275,6 +285,13 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const limitedByMe = status.get('visibility') === 'limited' && status.get('circle_id');
|
||||
const reblogged = status.get('reblogged');
|
||||
const favourited = status.get('favourited');
|
||||
const bookmarked = status.get('bookmarked');
|
||||
const emoji_reactioned = status.get('emoji_reactioned');
|
||||
const reblogsCount = status.get('reblogs_count');
|
||||
const favouritesCount = status.get('favourites_count');
|
||||
const [ _, domain ] = account.get('acct').split('@');
|
||||
|
||||
let menu = [];
|
||||
|
||||
|
@ -285,19 +302,38 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.open_emoji_reactions), action: this.handleEmojiReactions });
|
||||
if (reblogsCount > 0 || favouritesCount > 0 || !status.get('emoji_reactions').isEmpty()) {
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (reblogsCount > 0) {
|
||||
menu.push({ text: intl.formatMessage(messages.show_reblogs), action: this.handleReblogs });
|
||||
}
|
||||
|
||||
if (favouritesCount > 0) {
|
||||
menu.push({ text: intl.formatMessage(messages.show_favourites), action: this.handleFavourites });
|
||||
}
|
||||
|
||||
if (!status.get('emoji_reactions').isEmpty()) {
|
||||
menu.push({ text: intl.formatMessage(messages.show_emoji_reactions), action: this.handleEmojiReactions });
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.openDomainTimeline, { domain }), action: this.handleOpenDomainTimeline });
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (!show_bookmark_button) {
|
||||
menu.push({ text: intl.formatMessage(bookmarked ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
}
|
||||
|
||||
if (writtenByMe && publicStatus && !expired) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
}
|
||||
|
||||
if (!show_bookmark_button || writtenByMe && publicStatus && !expired) {
|
||||
if (!show_bookmark_button) {
|
||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
}
|
||||
|
||||
if (writtenByMe && publicStatus && !expired) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
|
@ -333,9 +369,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport });
|
||||
|
||||
if (account.get('acct') !== account.get('username')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
if (domain) {
|
||||
menu.push(null);
|
||||
|
||||
if (relationship && relationship.get('domain_blocking')) {
|
||||
|
@ -343,7 +377,6 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain });
|
||||
}
|
||||
menu.push({ text: intl.formatMessage(messages.openDomainTimeline, { domain }), action: this.handleOpenDomainTimeline });
|
||||
}
|
||||
|
||||
if (isStaff) {
|
||||
|
@ -366,7 +399,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let reblogTitle = '';
|
||||
if (status.get('reblogged')) {
|
||||
if (reblogged) {
|
||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||
} else if (publicStatus) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog);
|
||||
|
@ -383,18 +416,18 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar-button' disabled={expired} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate || expired} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate disabled={!status.get('favourited') && expired} active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate || expired} active={reblogged} pressed={reblogged} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate disabled={!favourited && expired} active={favourited} pressed={favourited} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
{show_quote_button && <IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus || expired} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} />}
|
||||
{shareButton}
|
||||
{show_bookmark_button && <IconButton className='status__action-bar-button bookmark-icon' disabled={!status.get('bookmarked') && expired} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />}
|
||||
{show_bookmark_button && <IconButton className='status__action-bar-button bookmark-icon' disabled={!bookmarked && expired} active={bookmarked} pressed={bookmarked} title={intl.formatMessage(bookmarked ? messages.removeBookmark : messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />}
|
||||
|
||||
{enableReaction && <div className='status__action-bar-dropdown'>
|
||||
<ReactionPickerDropdownContainer
|
||||
scrollKey={scrollKey}
|
||||
disabled={expired}
|
||||
active={status.get('emoji_reactioned')}
|
||||
pressed={status.get('emoji_reactioned')}
|
||||
active={emoji_reactioned}
|
||||
pressed={emoji_reactioned}
|
||||
className='status__action-bar-button'
|
||||
disabled={anonymousAccess}
|
||||
status={status}
|
||||
|
|
|
@ -10,9 +10,9 @@ const mapStateToProps = state => ({
|
|||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchSuggestions: () => dispatch(fetchSuggestions()),
|
||||
fetchSuggestions: () => dispatch(fetchSuggestions(true)),
|
||||
expandSearch: type => dispatch(expandSearch(type)),
|
||||
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
|
||||
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'), true)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SearchResults);
|
||||
|
|
|
@ -4,22 +4,58 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchEmojiReactions } from '../../actions/interactions';
|
||||
import { fetchEmojiReactions, expandEmojiReactions } from '../../actions/interactions';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import Emoji from '../../components/emoji';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import ReactedHeaderContaier from '../reactioned/containers/header_container';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId]),
|
||||
emojiReactions: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'items']),
|
||||
isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'next']),
|
||||
emojiMap: customEmojiMap(state),
|
||||
});
|
||||
|
||||
class Reaction extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
emojiReaction: ImmutablePropTypes.map.isRequired,
|
||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
hovered: false,
|
||||
};
|
||||
|
||||
handleMouseEnter = () => this.setState({ hovered: true })
|
||||
|
||||
handleMouseLeave = () => this.setState({ hovered: false })
|
||||
|
||||
render () {
|
||||
const { emojiReaction, emojiMap } = this.props;
|
||||
|
||||
return (
|
||||
<div className='account__emoji_reaction' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<Emoji hovered={this.state.hovered} emoji={emojiReaction.get('name')} emojiMap={emojiMap} url={emojiReaction.get('url')} static_url={emojiReaction.get('static_url')} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class EmojiReactions extends ImmutablePureComponent {
|
||||
|
@ -27,13 +63,16 @@ class EmojiReactions extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
emojiReactions: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
if (!this.props.accountIds) {
|
||||
if (!this.props.emojiReactions) {
|
||||
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
|
||||
}
|
||||
}
|
||||
|
@ -48,10 +87,14 @@ class EmojiReactions extends ImmutablePureComponent {
|
|||
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, accountIds, multiColumn } = this.props;
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandEmojiReactions(this.props.params.statusId));
|
||||
}, 300, { leading: true })
|
||||
|
||||
if (!accountIds) {
|
||||
render () {
|
||||
const { intl, emojiReactions, multiColumn, emojiMap, hasMore, isLoading } = this.props;
|
||||
|
||||
if (!emojiReactions) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
|
@ -71,13 +114,18 @@ class EmojiReactions extends ImmutablePureComponent {
|
|||
)}
|
||||
/>
|
||||
|
||||
<ReactedHeaderContaier statusId={this.props.params.statusId} />
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='emoji_reactions'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />,
|
||||
{emojiReactions.map(emojiReaction =>
|
||||
<AccountContainer key={emojiReaction.get('account')+emojiReaction.get('name')} id={emojiReaction.get('account')} withNote={false} append={<Reaction emojiReaction={emojiReaction} emojiMap={emojiMap} />} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
|
|
@ -4,20 +4,24 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchFavourites } from '../../actions/interactions';
|
||||
import { fetchFavourites, expandFavourites } from '../../actions/interactions';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ReactedHeaderContaier from '../reactioned/containers/header_container';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
|
||||
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'items']),
|
||||
isLoading: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'next']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
|
@ -30,6 +34,8 @@ class Favourites extends ImmutablePureComponent {
|
|||
accountIds: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -48,8 +54,12 @@ class Favourites extends ImmutablePureComponent {
|
|||
this.props.dispatch(fetchFavourites(this.props.params.statusId));
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFavourites(this.props.params.statusId));
|
||||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
const { intl, accountIds, multiColumn } = this.props;
|
||||
const { intl, accountIds, multiColumn, hasMore, isLoading } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
@ -71,8 +81,13 @@ class Favourites extends ImmutablePureComponent {
|
|||
)}
|
||||
/>
|
||||
|
||||
<ReactedHeaderContaier statusId={this.props.params.statusId} />
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='favourites'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
|
|
|
@ -4,15 +4,18 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchMentions } from '../../actions/interactions';
|
||||
import { fetchMentions, expandMentions } from '../../actions/interactions';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'mentioned_by', props.params.statusId]),
|
||||
accountIds: state.getIn(['user_lists', 'mentioned_by', props.params.statusId, 'items']),
|
||||
isLoading: state.getIn(['user_lists', 'mentioned_by', props.params.statusId, 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['user_lists', 'mentioned_by', props.params.statusId, 'next']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
|
@ -25,6 +28,8 @@ class Mentions extends ImmutablePureComponent {
|
|||
accountIds: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -39,8 +44,12 @@ class Mentions extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandMentions(this.props.params.statusId));
|
||||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
const { accountIds, multiColumn } = this.props;
|
||||
const { accountIds, multiColumn, hasMore, isLoading } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
@ -61,6 +70,9 @@ class Mentions extends ImmutablePureComponent {
|
|||
|
||||
<ScrollableList
|
||||
scrollKey='mentions'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Icon from '../../../components/icon';
|
||||
|
||||
export default class Header extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
hideTabs: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleStatusBackClick = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { status, hideTabs } = this.props;
|
||||
|
||||
if (status === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='status-reactioned__header'>
|
||||
<div className='status-reactioned__back-to-post-button'>
|
||||
<button onClick={this.handleStatusBackClick} className='column-header__back-button'>
|
||||
<Icon id='chevron-left' className='reactioned-back-button__icon' fixedWidth />
|
||||
<FormattedMessage id='column_back_button_to_pots.label' defaultMessage='Back to post detail' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!hideTabs && (
|
||||
<div className='status-reactioned__section-headline'>
|
||||
<NavLink exact to={`/statuses/${status.get('id')}/reblogs`}><FormattedMessage id='status.reblog' defaultMessage='Boost' /></NavLink>
|
||||
<NavLink exact to={`/statuses/${status.get('id')}/favourites`}><FormattedMessage id='status.favourite' defaultMessage='Favourite' /></NavLink>
|
||||
<NavLink exact to={`/statuses/${status.get('id')}/emoji_reactions`}><FormattedMessage id='status.emoji' defaultMessage='Emoji' /></NavLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeGetStatus } from '../../../selectors';
|
||||
import Header from '../components/header';
|
||||
import { injectIntl } from 'react-intl';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
const mapStateToProps = (state, { statusId }) => ({
|
||||
status: getStatus(state, { id: statusId }),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps)(Header));
|
|
@ -4,20 +4,24 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchReblogs } from '../../actions/interactions';
|
||||
import { fetchReblogs, expandReblogs } from '../../actions/interactions';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ReactedHeaderContaier from '../reactioned/containers/header_container';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
|
||||
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'items']),
|
||||
isLoading: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'next']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
|
@ -30,6 +34,8 @@ class Reblogs extends ImmutablePureComponent {
|
|||
accountIds: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -48,8 +54,12 @@ class Reblogs extends ImmutablePureComponent {
|
|||
this.props.dispatch(fetchReblogs(this.props.params.statusId));
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandReblogs(this.props.params.statusId));
|
||||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
const { intl, accountIds, multiColumn } = this.props;
|
||||
const { intl, accountIds, multiColumn, hasMore, isLoading } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
|
@ -71,8 +81,13 @@ class Reblogs extends ImmutablePureComponent {
|
|||
)}
|
||||
/>
|
||||
|
||||
<ReactedHeaderContaier statusId={this.props.params.statusId} />
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='reblogs'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
|
|
|
@ -25,6 +25,9 @@ const messages = defineMessages({
|
|||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
emoji_reaction: { id: 'status.emoji_reaction', defaultMessage: 'Emoji reaction' },
|
||||
show_reblogs: { id: 'status.show_reblogs', defaultMessage: 'Show boosted users' },
|
||||
show_favourites: { id: 'status.show_favourites', defaultMessage: 'Show favourited users' },
|
||||
show_emoji_reactions: { id: 'status.show_emoji_reactions', defaultMessage: 'Show emoji reactioned users' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
|
||||
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
||||
|
@ -209,6 +212,18 @@ class ActionBar extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleReblogs = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}/reblogs`);
|
||||
}
|
||||
|
||||
handleFavourites = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}/favourites`);
|
||||
}
|
||||
|
||||
handleEmojiReactions = () => {
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}/emoji_reactions`);
|
||||
}
|
||||
|
||||
handleEmojiPick = data => {
|
||||
const { addEmojiReaction, status } = this.props;
|
||||
addEmojiReaction(status, data.native.replace(/:/g, ''), null, null, null);
|
||||
|
@ -227,6 +242,13 @@ class ActionBar extends React.PureComponent {
|
|||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const limitedByMe = status.get('visibility') === 'limited' && status.get('circle_id');
|
||||
const reblogged = status.get('reblogged');
|
||||
const favourited = status.get('favourited');
|
||||
const bookmarked = status.get('bookmarked');
|
||||
const emoji_reactioned = status.get('emoji_reactioned');
|
||||
const reblogsCount = status.get('reblogs_count');
|
||||
const favouritesCount = status.get('favourites_count');
|
||||
const [ _, domain ] = account.get('acct').split('@');
|
||||
|
||||
const expires_at = status.get('expires_at')
|
||||
const expires_date = expires_at && new Date(expires_at)
|
||||
|
@ -237,9 +259,31 @@ class ActionBar extends React.PureComponent {
|
|||
if (publicStatus && !expired) {
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
}
|
||||
|
||||
if (reblogsCount > 0 || favouritesCount > 0 || !status.get('emoji_reactions').isEmpty()) {
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (reblogsCount > 0) {
|
||||
menu.push({ text: intl.formatMessage(messages.show_reblogs), action: this.handleReblogs });
|
||||
}
|
||||
|
||||
if (favouritesCount > 0) {
|
||||
menu.push({ text: intl.formatMessage(messages.show_favourites), action: this.handleFavourites });
|
||||
}
|
||||
|
||||
if (!status.get('emoji_reactions').isEmpty()) {
|
||||
menu.push({ text: intl.formatMessage(messages.show_emoji_reactions), action: this.handleEmojiReactions });
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.openDomainTimeline, { domain }), action: this.handleOpenDomainTimeline });
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (writtenByMe) {
|
||||
if (publicStatus && !expired) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
|
@ -248,9 +292,9 @@ class ActionBar extends React.PureComponent {
|
|||
|
||||
if (limitedByMe) {
|
||||
menu.push({ text: intl.formatMessage(messages.showMemberList), action: this.handleMemberListClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
if (!expired) {
|
||||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
menu.push(null);
|
||||
|
@ -276,9 +320,7 @@ class ActionBar extends React.PureComponent {
|
|||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
||||
|
||||
if (account.get('acct') !== account.get('username')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
if (domain) {
|
||||
menu.push(null);
|
||||
|
||||
if (relationship && relationship.get('domain_blocking')) {
|
||||
|
@ -286,7 +328,6 @@ class ActionBar extends React.PureComponent {
|
|||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain });
|
||||
}
|
||||
menu.push({ text: intl.formatMessage(messages.openDomainTimeline, { domain }), action: this.handleOpenDomainTimeline });
|
||||
}
|
||||
|
||||
if (isStaff) {
|
||||
|
@ -310,7 +351,7 @@ class ActionBar extends React.PureComponent {
|
|||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let reblogTitle;
|
||||
if (status.get('reblogged')) {
|
||||
if (reblogged) {
|
||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||
} else if (publicStatus) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog);
|
||||
|
@ -323,17 +364,17 @@ class ActionBar extends React.PureComponent {
|
|||
return (
|
||||
<div className='detailed-status__action-bar'>
|
||||
<div className='detailed-status__button'><IconButton disabled={expired} title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate || expired} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} disabled={!status.get('favourited') && expired} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate || expired} active={reblogged} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={favourited} disabled={!favourited && expired} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
{show_quote_button && <div className='detailed-status__button'><IconButton disabled={!publicStatus || expired} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} /></div>}
|
||||
{shareButton}
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} disabled={!status.get('bookmarked') && expired} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={bookmarked} disabled={!bookmarked && expired} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
|
||||
|
||||
{enableReaction && <div className='detailed-status__action-bar-dropdown'>
|
||||
<ReactionPickerDropdownContainer
|
||||
disabled={expired}
|
||||
active={status.get('emoji_reactioned')}
|
||||
pressed={status.get('emoji_reactioned')}
|
||||
active={emoji_reactioned}
|
||||
pressed={emoji_reactioned}
|
||||
className='status__action-bar-button'
|
||||
status={status}
|
||||
title={intl.formatMessage(messages.emoji_reaction)}
|
||||
|
|
|
@ -43,13 +43,13 @@ class Suggestions extends ImmutablePureComponent {
|
|||
fetchSuggestions = () => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(fetchSuggestions());
|
||||
dispatch(fetchSuggestions(true));
|
||||
}
|
||||
|
||||
dismissSuggestion = account => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(dismissSuggestion(account.get('id')));
|
||||
dispatch(dismissSuggestion(account.get('id'), true));
|
||||
}
|
||||
|
||||
handlePin = () => {
|
||||
|
|
|
@ -103,6 +103,7 @@
|
|||
"column.pins": "Pinned posts",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
"column_back_button_to_pots.label": "Back to post detail",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
|
@ -495,6 +496,7 @@
|
|||
"status.detailed_status": "Detailed conversation view",
|
||||
"status.direct": "Direct message @{name}",
|
||||
"status.embed": "Embed",
|
||||
"status.emoji": "Emoji",
|
||||
"status.emoji_reaction": "Emoji reaction",
|
||||
"status.favourite": "Favourite",
|
||||
"status.filtered": "Filtered",
|
||||
|
@ -506,7 +508,6 @@
|
|||
"status.mute_conversation": "Mute conversation",
|
||||
"status.muted_quote": "Muted quote",
|
||||
"status.open": "Expand this post",
|
||||
"status.open_emoji_reactions": "Open emoji reactions list to this post",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned post",
|
||||
"status.quote": "Quote",
|
||||
|
@ -522,12 +523,15 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_emoji_reactions": "Show emoji reactioned users",
|
||||
"status.show_favourites": "Show favourited users",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_member_list": "Show member list",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
"status.show_poll": "Show poll",
|
||||
"status.show_reblogs": "Show boosted users",
|
||||
"status.show_thread": "Show thread",
|
||||
"status.uncached_media_warning": "Not available",
|
||||
"status.unlisted_quote": "Unlisted quote",
|
||||
|
|
|
@ -103,6 +103,7 @@
|
|||
"column.pins": "固定された投稿",
|
||||
"column.public": "連合タイムライン",
|
||||
"column_back_button.label": "戻る",
|
||||
"column_back_button_to_pots.label": "投稿の詳細に戻る",
|
||||
"column_header.hide_settings": "設定を隠す",
|
||||
"column_header.moveLeft_settings": "カラムを左に移動する",
|
||||
"column_header.moveRight_settings": "カラムを右に移動する",
|
||||
|
@ -496,6 +497,7 @@
|
|||
"status.detailed_status": "詳細な会話ビュー",
|
||||
"status.direct": "@{name}さんにダイレクトメッセージ",
|
||||
"status.embed": "埋め込み",
|
||||
"status.emoji": "絵文字",
|
||||
"status.emoji_reaction": "絵文字リアクション",
|
||||
"status.favourite": "お気に入り",
|
||||
"status.filtered": "フィルターされました",
|
||||
|
@ -507,7 +509,6 @@
|
|||
"status.mute_conversation": "会話をミュート",
|
||||
"status.muted_quote": "ミュートされた引用",
|
||||
"status.open": "詳細を表示",
|
||||
"status.open_emoji_reactions": "この投稿への絵文字リアクション一覧",
|
||||
"status.pin": "プロフィールに固定表示",
|
||||
"status.pinned": "固定された投稿",
|
||||
"status.quote": "引用",
|
||||
|
@ -523,12 +524,15 @@
|
|||
"status.report": "@{name}さんを通報",
|
||||
"status.sensitive_warning": "閲覧注意",
|
||||
"status.share": "共有",
|
||||
"status.show_emoji_reactions": "絵文字リアクションしたユーザーを表示",
|
||||
"status.show_favourites": "お気に入りしたユーザーを表示",
|
||||
"status.show_less": "隠す",
|
||||
"status.show_less_all": "全て隠す",
|
||||
"status.show_member_list": "メンバーリストを表示",
|
||||
"status.show_more": "もっと見る",
|
||||
"status.show_more_all": "全て見る",
|
||||
"status.show_poll": "アンケートを表示",
|
||||
"status.show_reblogs": "ブーストしたユーザーを表示",
|
||||
"status.show_thread": "スレッドを表示",
|
||||
"status.uncached_media_warning": "利用できません",
|
||||
"status.unlisted_quote": "未収載の引用",
|
||||
|
|
|
@ -31,9 +31,29 @@ import {
|
|||
} from '../actions/accounts';
|
||||
import {
|
||||
REBLOGS_FETCH_SUCCESS,
|
||||
REBLOGS_FETCH_REQUEST,
|
||||
REBLOGS_FETCH_FAIL,
|
||||
REBLOGS_EXPAND_SUCCESS,
|
||||
REBLOGS_EXPAND_REQUEST,
|
||||
REBLOGS_EXPAND_FAIL,
|
||||
FAVOURITES_FETCH_SUCCESS,
|
||||
FAVOURITES_FETCH_REQUEST,
|
||||
FAVOURITES_FETCH_FAIL,
|
||||
FAVOURITES_EXPAND_SUCCESS,
|
||||
FAVOURITES_EXPAND_REQUEST,
|
||||
FAVOURITES_EXPAND_FAIL,
|
||||
EMOJI_REACTIONS_FETCH_REQUEST,
|
||||
EMOJI_REACTIONS_FETCH_SUCCESS,
|
||||
EMOJI_REACTIONS_FETCH_FAIL,
|
||||
EMOJI_REACTIONS_EXPAND_REQUEST,
|
||||
EMOJI_REACTIONS_EXPAND_SUCCESS,
|
||||
EMOJI_REACTIONS_EXPAND_FAIL,
|
||||
MENTIONS_FETCH_SUCCESS,
|
||||
MENTIONS_FETCH_REQUEST,
|
||||
MENTIONS_FETCH_FAIL,
|
||||
MENTIONS_EXPAND_SUCCESS,
|
||||
MENTIONS_EXPAND_REQUEST,
|
||||
MENTIONS_EXPAND_FAIL,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
BLOCKS_FETCH_REQUEST,
|
||||
|
@ -67,7 +87,7 @@ import {
|
|||
DIRECTORY_EXPAND_SUCCESS,
|
||||
DIRECTORY_EXPAND_FAIL,
|
||||
} from 'mastodon/actions/directory';
|
||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
|
||||
const initialListState = ImmutableMap({
|
||||
next: null,
|
||||
|
@ -108,6 +128,25 @@ const normalizeFollowRequest = (state, notification) => {
|
|||
});
|
||||
};
|
||||
|
||||
const normalizeEmojiReaction = emojiReaction => {
|
||||
const normalizeEmojiReaction = { ...emojiReaction, account: emojiReaction.account.id };
|
||||
return fromJS(normalizeEmojiReaction);
|
||||
};
|
||||
|
||||
const normalizeEmojiReactions = (state, path, emojiReactions, next) => {
|
||||
return state.setIn(path, ImmutableMap({
|
||||
next,
|
||||
items: ImmutableList(emojiReactions.map(normalizeEmojiReaction)),
|
||||
isLoading: false,
|
||||
}));
|
||||
};
|
||||
|
||||
const appendToEmojiReactions = (state, path, emojiReactions, next) => {
|
||||
return state.updateIn(path, map => {
|
||||
return map.set('next', next).set('isLoading', false).update('items', list => list.concat(emojiReactions.map(normalizeEmojiReaction)));
|
||||
});
|
||||
};
|
||||
|
||||
export default function userLists(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case FOLLOWERS_FETCH_SUCCESS:
|
||||
|
@ -141,13 +180,45 @@ export default function userLists(state = initialState, action) {
|
|||
case SUBSCRIBING_EXPAND_FAIL:
|
||||
return state.setIn(['subscribing', action.id, 'isLoading'], false);
|
||||
case REBLOGS_FETCH_SUCCESS:
|
||||
return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
return normalizeList(state, ['reblogged_by', action.id], action.accounts, action.next);
|
||||
case REBLOGS_EXPAND_SUCCESS:
|
||||
return appendToList(state, ['reblogged_by', action.id], action.accounts, action.next);
|
||||
case REBLOGS_FETCH_REQUEST:
|
||||
case REBLOGS_EXPAND_REQUEST:
|
||||
return state.setIn(['reblogged_by', action.id, 'isLoading'], true);
|
||||
case REBLOGS_FETCH_FAIL:
|
||||
case REBLOGS_EXPAND_FAIL:
|
||||
return state.setIn(['reblogged_by', action.id, 'isLoading'], false);
|
||||
case FAVOURITES_FETCH_SUCCESS:
|
||||
return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
return normalizeList(state, ['favourited_by', action.id], action.accounts, action.next);
|
||||
case FAVOURITES_EXPAND_SUCCESS:
|
||||
return appendToList(state, ['favourited_by', action.id], action.accounts, action.next);
|
||||
case FAVOURITES_FETCH_REQUEST:
|
||||
case FAVOURITES_EXPAND_REQUEST:
|
||||
return state.setIn(['favourited_by', action.id, 'isLoading'], true);
|
||||
case FAVOURITES_FETCH_FAIL:
|
||||
case FAVOURITES_EXPAND_FAIL:
|
||||
return state.setIn(['favourited_by', action.id, 'isLoading'], false);
|
||||
case EMOJI_REACTIONS_FETCH_SUCCESS:
|
||||
return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
return normalizeEmojiReactions(state, ['emoji_reactioned_by', action.id], action.emojiReactions, action.next);
|
||||
case EMOJI_REACTIONS_EXPAND_SUCCESS:
|
||||
return appendToEmojiReactions(state, ['emoji_reactioned_by', action.id], action.emojiReactions, action.next);
|
||||
case EMOJI_REACTIONS_FETCH_REQUEST:
|
||||
case EMOJI_REACTIONS_EXPAND_REQUEST:
|
||||
return state.setIn(['emoji_reactioned_by', action.id, 'isLoading'], true);
|
||||
case EMOJI_REACTIONS_FETCH_FAIL:
|
||||
case EMOJI_REACTIONS_EXPAND_FAIL:
|
||||
return state.setIn(['emoji_reactioned_by', action.id, 'isLoading'], false);
|
||||
case MENTIONS_FETCH_SUCCESS:
|
||||
return state.setIn(['mentioned_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
return normalizeList(state, ['mentioned_by', action.id], action.accounts, action.next);
|
||||
case MENTIONS_EXPAND_SUCCESS:
|
||||
return appendToList(state, ['mentioned_by', action.id], action.accounts, action.next);
|
||||
case MENTIONS_FETCH_REQUEST:
|
||||
case MENTIONS_EXPAND_REQUEST:
|
||||
return state.setIn(['mentioned_by', action.id, 'isLoading'], true);
|
||||
case MENTIONS_FETCH_FAIL:
|
||||
case MENTIONS_EXPAND_FAIL:
|
||||
return state.setIn(['mentioned_by', action.id, 'isLoading'], false);
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
|
||||
case FOLLOW_REQUESTS_FETCH_SUCCESS:
|
||||
|
|
|
@ -76,7 +76,8 @@ html {
|
|||
.column-header__button,
|
||||
.column-header__button.active,
|
||||
.account__header__bar,
|
||||
.directory__card__extra {
|
||||
.directory__card__extra,
|
||||
.status-reactioned__back-to-post-button {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
|
|
|
@ -1619,10 +1619,17 @@ a .account__avatar {
|
|||
|
||||
.account__relationship {
|
||||
height: 18px;
|
||||
padding: 10px;
|
||||
padding: 9px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.account__emoji_reaction {
|
||||
.emojione {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.account__disclaimer {
|
||||
padding: 10px;
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
|
@ -3059,6 +3066,11 @@ a.account__display-name {
|
|||
}
|
||||
}
|
||||
|
||||
.status-reactioned__back-to-post-button {
|
||||
padding: 12px 14px;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
.column-header__back-button {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
border: 0;
|
||||
|
@ -6534,7 +6546,8 @@ a.status-card.compact:hover {
|
|||
}
|
||||
|
||||
.notification__filter-bar,
|
||||
.account__section-headline {
|
||||
.account__section-headline,
|
||||
.status-reactioned__section-headline {
|
||||
background: darken($ui-base-color, 4%);
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
cursor: default;
|
||||
|
|
|
@ -20,7 +20,7 @@ class InlineRenderer
|
|||
when :reaction
|
||||
serializer = REST::ReactionSerializer
|
||||
when :emoji_reaction
|
||||
serializer = REST::EmojiReactionSerializer
|
||||
serializer = REST::GroupedEmojiReactionSerializer
|
||||
when :encrypted_message
|
||||
serializer = REST::EncryptedMessageSerializer
|
||||
else
|
||||
|
|
|
@ -311,7 +311,7 @@ class Status < ApplicationRecord
|
|||
|
||||
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')
|
||||
Oj.dump(ActiveModelSerializers::SerializableResource.new(records, each_serializer: REST::EmojiReactionSerializer, scope: nil, scope_name: :current_user))
|
||||
Oj.dump(ActiveModelSerializers::SerializableResource.new(records, each_serializer: REST::GroupedEmojiReactionSerializer, scope: nil, scope_name: :current_user))
|
||||
end
|
||||
|
||||
def refresh_grouped_emoji_reactions!
|
||||
|
|
|
@ -3,30 +3,18 @@
|
|||
class REST::EmojiReactionSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
attributes :name, :count
|
||||
attributes :name
|
||||
|
||||
attribute :me, if: :current_user?
|
||||
attribute :url, if: :custom_emoji?
|
||||
attribute :static_url, if: :custom_emoji?
|
||||
attribute :domain, if: :custom_emoji?
|
||||
attribute :account_ids, if: :has_account_ids?
|
||||
|
||||
def count
|
||||
object.respond_to?(:count) ? object.count : 0
|
||||
end
|
||||
|
||||
def current_user?
|
||||
!current_user.nil?
|
||||
end
|
||||
belongs_to :account, serializer: REST::AccountSerializer
|
||||
|
||||
def custom_emoji?
|
||||
object.custom_emoji.present?
|
||||
end
|
||||
|
||||
def has_account_ids?
|
||||
object.respond_to?(:account_ids)
|
||||
end
|
||||
|
||||
def url
|
||||
full_asset_url(object.custom_emoji.image.url)
|
||||
end
|
||||
|
@ -39,4 +27,3 @@ class REST::EmojiReactionSerializer < ActiveModel::Serializer
|
|||
object.custom_emoji.domain
|
||||
end
|
||||
end
|
||||
|
41
app/serializers/rest/grouped_emoji_reaction_serializer.rb
Normal file
41
app/serializers/rest/grouped_emoji_reaction_serializer.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::GroupedEmojiReactionSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
attributes :name, :count
|
||||
|
||||
attribute :me, if: :current_user?
|
||||
attribute :url, if: :custom_emoji?
|
||||
attribute :static_url, if: :custom_emoji?
|
||||
attribute :domain, if: :custom_emoji?
|
||||
attribute :account_ids, if: :has_account_ids?
|
||||
|
||||
def count
|
||||
object.respond_to?(:count) ? object.count : 0
|
||||
end
|
||||
|
||||
def current_user?
|
||||
!current_user.nil?
|
||||
end
|
||||
|
||||
def custom_emoji?
|
||||
object.custom_emoji.present?
|
||||
end
|
||||
|
||||
def has_account_ids?
|
||||
object.respond_to?(:account_ids)
|
||||
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
|
||||
end
|
|
@ -24,7 +24,7 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||
object.type == :emoji_reaction
|
||||
end
|
||||
|
||||
class EmojiReactionSerializer < REST::EmojiReactionSerializer
|
||||
class EmojiReactionSerializer < REST::GroupedEmojiReactionSerializer
|
||||
attributes :me
|
||||
|
||||
def me
|
||||
|
|
|
@ -333,7 +333,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
|
||||
resources :emoji_reactioned_by, controller: :emoji_reactioned_by, only: :index
|
||||
resources :mentioned_by, controller: :mentioned_by_accounts, only: :index
|
||||
resource :reblog, only: :create
|
||||
post :unreblog, to: 'reblogs#destroy'
|
||||
|
|
Loading…
Reference in a new issue