diff --git a/app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb b/app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb index 92cd5228cc..9c2fb3d4a5 100644 --- a/app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/emoji_reactioned_by_accounts_controller.rb @@ -33,7 +33,7 @@ class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseControll def paginated_emoji_reactions EmojiReaction.paginate_by_max_id( - limit_param(1000), # limit_param(DEFAULT_ACCOUNTS_LIMIT), + limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id] ) diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index a5f6729f41..b361809309 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -51,6 +51,10 @@ 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 PIN_REQUEST = 'PIN_REQUEST'; export const PIN_SUCCESS = 'PIN_SUCCESS'; export const PIN_FAIL = 'PIN_FAIL'; @@ -547,8 +551,9 @@ export function fetchEmojiReactions(id) { dispatch(fetchEmojiReactionsRequest(id)); api(getState).get(`/api/v1/statuses/${id}/emoji_reactioned_by`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map((er) => er.account))); - dispatch(fetchEmojiReactionsSuccess(id, response.data)); + dispatch(fetchEmojiReactionsSuccess(id, response.data, next ? next.uri : null)); }).catch(error => { dispatch(fetchEmojiReactionsFail(id, error)); }); @@ -562,11 +567,12 @@ export function fetchEmojiReactionsRequest(id) { }; } -export function fetchEmojiReactionsSuccess(id, accounts) { +export function fetchEmojiReactionsSuccess(id, accounts, next) { return { type: EMOJI_REACTIONS_FETCH_SUCCESS, id, accounts, + next, }; } @@ -577,6 +583,48 @@ export function fetchEmojiReactionsFail(id, error) { }; } +export function expandEmojiReactions(id) { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'emoji_reactioned_by', id, 'next']); + if (url === null) { + 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((er) => er.account))); + 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, accounts, next) { + return { + type: EMOJI_REACTIONS_EXPAND_SUCCESS, + id, + accounts, + next, + }; +} + +export function expandEmojiReactionsFail(id, error) { + return { + type: EMOJI_REACTIONS_EXPAND_FAIL, + id, + error, + }; +} + export function fetchStatusReferences(id) { return (dispatch, getState) => { dispatch(fetchStatusReferencesRequest(id)); diff --git a/app/javascript/mastodon/features/emoji_reactions/index.jsx b/app/javascript/mastodon/features/emoji_reactions/index.jsx index 78d088e947..5ab2a34958 100644 --- a/app/javascript/mastodon/features/emoji_reactions/index.jsx +++ b/app/javascript/mastodon/features/emoji_reactions/index.jsx @@ -8,7 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { fetchEmojiReactions } from 'mastodon/actions/interactions'; +import { debounce } from 'lodash'; + +import { fetchEmojiReactions, expandEmojiReactions } from 'mastodon/actions/interactions'; import ColumnHeader from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; @@ -25,7 +27,9 @@ const messages = defineMessages({ const mapStateToProps = (state, props) => { return { - accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId]), + accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'items']), + hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'next']), + isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'isLoading'], true), }; }; @@ -35,6 +39,8 @@ class EmojiReactions extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.list, + hasMore: PropTypes.bool, + isLoading: PropTypes.bool, multiColumn: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -45,18 +51,16 @@ class EmojiReactions extends ImmutablePureComponent { } } - 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)); }; + handleLoadMore = debounce(() => { + this.props.dispatch(expandEmojiReactions(this.props.params.statusId)); + }, 300, { leading: true }); + render () { - const { intl, accountIds, multiColumn } = this.props; + const { intl, accountIds, hasMore, isLoading, multiColumn } = this.props; if (!accountIds) { return ( @@ -68,7 +72,7 @@ class EmojiReactions extends ImmutablePureComponent { let groups = {}; for (const emoji_reaction of accountIds) { - const key = emoji_reaction.account.id; + const key = emoji_reaction.account_id; const value = emoji_reaction; if (!groups[key]) groups[key] = [value]; else groups[key].push(value); @@ -82,12 +86,15 @@ class EmojiReactions extends ImmutablePureComponent { showBackButton multiColumn={multiColumn} extraButton={( - + )} /> diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index edc83bd3d4..2cb41cd03d 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -57,7 +57,12 @@ import { FAVOURITES_EXPAND_REQUEST, FAVOURITES_EXPAND_SUCCESS, 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, STATUS_REFERENCES_FETCH_SUCCESS, } from '../actions/interactions'; import { @@ -101,12 +106,33 @@ const normalizeList = (state, path, accounts, next) => { })); }; +const normalizeEmojiReactionList = (state, path, rows, next) => { + return state.setIn(path, ImmutableMap({ + next, + items: ImmutableList(rows.map(normalizeEmojiReactionRow)), + isLoading: false, + })); +}; + const appendToList = (state, path, accounts, next) => { return state.updateIn(path, map => { return map.set('next', next).set('isLoading', false).update('items', list => list.concat(accounts.map(item => item.id))); }); }; +const appendToEmojiReactionList = (state, path, rows, next) => { + return state.updateIn(path, map => { + return map.set('next', next).set('isLoading', false).update('items', list => list.concat(rows.map(normalizeEmojiReactionRow))); + }); +}; + +const normalizeEmojiReactionRow = (row) => { + const accountId = row.account ? row.account.id : 0; + delete row.account; + row.account_id = accountId; + return row; +}; + const normalizeFollowRequest = (state, notification) => { return state.updateIn(['follow_requests', 'items'], list => { return list.filterNot(item => item === notification.account.id).unshift(notification.account.id); @@ -167,8 +193,16 @@ export default function userLists(state = initialState, action) { case FAVOURITES_FETCH_FAIL: case FAVOURITES_EXPAND_FAIL: return state.setIn(['favourited_by', action.id, 'isLoading'], false); + 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 EMOJI_REACTIONS_FETCH_SUCCESS: - return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts)); + return normalizeEmojiReactionList(state, ['emoji_reactioned_by', action.id], action.accounts, action.next); + case EMOJI_REACTIONS_EXPAND_SUCCESS: + return appendToEmojiReactionList(state, ['emoji_reactioned_by', action.id], action.accounts, action.next); case STATUS_REFERENCES_FETCH_SUCCESS: return state.setIn(['referred_by', action.id], ImmutableList(action.statuses.map(item => item.id))); case NOTIFICATIONS_UPDATE: