diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js index 60f46f0cbc..b9713e3215 100644 --- a/app/javascript/flavours/glitch/actions/interactions.js +++ b/app/javascript/flavours/glitch/actions/interactions.js @@ -7,6 +7,10 @@ export const REBLOG_REQUEST = 'REBLOG_REQUEST'; export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; export const REBLOG_FAIL = 'REBLOG_FAIL'; +export const REACTIONS_EXPAND_REQUEST = 'REACTIONS_EXPAND_REQUEST'; +export const REACTIONS_EXPAND_SUCCESS = 'REACTIONS_EXPAND_SUCCESS'; +export const REACTIONS_EXPAND_FAIL = 'REACTIONS_EXPAND_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'; @@ -23,6 +27,10 @@ export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST'; export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS'; export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL'; +export const REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST'; +export const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS'; +export const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL'; + export const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST'; export const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS'; export const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL'; @@ -287,6 +295,90 @@ export function unbookmarkFail(status, error) { }; } +export function fetchReactions(id) { + return (dispatch, getState) => { + dispatch(fetchReactionsRequest(id)); + + api(getState).get(`/api/v1/statuses/${id}/reactions`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + const accounts = response.data.map(item => item.account); + dispatch(importFetchedAccounts(accounts)); + dispatch(fetchReactionsSuccess(id, accounts, next ? next.uri : null)); + dispatch(fetchRelationships(accounts.map(item => item.id))); + }).catch(error => { + dispatch(fetchReactionsFail(id, error)); + }); + }; +} + +export function fetchReactionsRequest(id) { + return { + type: REACTIONS_FETCH_REQUEST, + id, + }; +} + +export function fetchReactionsSuccess(id, accounts, next) { + return { + type: REACTIONS_FETCH_SUCCESS, + id, + accounts, + next, + }; +} + +export function fetchReactionsFail(id, error) { + return { + type: REACTIONS_FETCH_FAIL, + id, + error, + }; +} + +export function expandReactions(id) { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'reactions', id, 'next']); + if (url === null) { + return; + } + + dispatch(expandReactionsRequest(id)); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + const accounts = response.data.map(item => item.account); + + dispatch(importFetchedAccounts(accounts)); + dispatch(expandReactionsSuccess(id, accounts, next ? next.uri : null)); + dispatch(fetchRelationships(accounts.map(item => item.id))); + }).catch(error => dispatch(expandReactionsFail(id, error))); + }; +} + +export function expandReactionsRequest(id) { + return { + type: REACTIONS_EXPAND_REQUEST, + id, + }; +} + +export function expandReactionsSuccess(id, accounts, next) { + return { + type: REACTIONS_EXPAND_SUCCESS, + id, + accounts, + next, + }; +} + +export function expandReactionsFail(id, error) { + return { + type: REACTIONS_EXPAND_FAIL, + id, + error, + }; +} + export function fetchReblogs(id) { return (dispatch, getState) => { dispatch(fetchReblogsRequest(id)); diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 0faa6d1dbd..0ea9342f2e 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -32,7 +32,7 @@ import StatusContent from './status_content'; import StatusHeader from './status_header'; import StatusIcons from './status_icons'; import StatusPrepend from './status_prepend'; -import StatusReactions from './status_reactions'; +import { StatusReactions } from './status_reactions'; const domParser = new DOMParser(); diff --git a/app/javascript/flavours/glitch/components/status_reactions.jsx b/app/javascript/flavours/glitch/components/status_reactions.jsx index 81443d2055..b026bd17a8 100644 --- a/app/javascript/flavours/glitch/components/status_reactions.jsx +++ b/app/javascript/flavours/glitch/components/status_reactions.jsx @@ -15,7 +15,7 @@ import { assetHost } from '../utils/config'; import { AnimatedNumber } from './animated_number'; -export default class StatusReactions extends ImmutablePureComponent { +export class StatusReactions extends ImmutablePureComponent { static propTypes = { statusId: PropTypes.string.isRequired, @@ -107,6 +107,7 @@ class Reaction extends ImmutablePureComponent { return ( + )} + /> + + + {accountIds.map(id => + , + )} + + + + + + + ); + } +} + +export default connect(mapStateToProps)(injectIntl(Reactions)); diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index 01a47639e6..3e8f4cd7d3 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -21,7 +21,7 @@ import { Avatar } from '../../../components/avatar'; import { DisplayName } from '../../../components/display_name'; import MediaGallery from '../../../components/media_gallery'; import StatusContent from '../../../components/status_content'; -import StatusReactions from '../../../components/status_reactions'; +import { StatusReactions } from '../../../components/status_reactions'; import Audio from '../../audio'; import scheduleIdleTask from '../../ui/util/schedule_idle_task'; import Video from '../../video'; @@ -137,6 +137,7 @@ class DetailedStatus extends ImmutablePureComponent { let applicationLink = ''; let reblogLink = ''; let favouriteLink = ''; + let reactionLink = ''; // Depending on user settings, some media are considered as parts of the // contents (affected by CW) while other will be displayed outside of the @@ -275,6 +276,14 @@ class DetailedStatus extends ImmutablePureComponent { ); + reactionLink = ( + + + total + obj.get('count'), 0)} /> + + total + obj.get('count'), 0) }} /> + + ); } else { favouriteLink = ( @@ -284,6 +293,14 @@ class DetailedStatus extends ImmutablePureComponent { ); + reactionLink = ( + + + total + obj.get('count'), 0)} /> + + total + obj.get('count'), 0) }} /> + + ); } const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); @@ -339,6 +356,8 @@ class DetailedStatus extends ImmutablePureComponent { {reblogLink} {reblogLink && <>·} {favouriteLink} + · + {reactionLink} diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 51ac4ac145..a9f8958f8d 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -45,6 +45,7 @@ import { HomeTimeline, Followers, Following, + Reactions, Reblogs, Favourites, DirectTimeline, @@ -234,6 +235,7 @@ class SwitchingColumnsArea extends PureComponent { + diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js index 6a140e3fd7..ff6a3fccd1 100644 --- a/app/javascript/flavours/glitch/features/ui/util/async-components.js +++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js @@ -90,6 +90,10 @@ export function Favourites () { return import(/* webpackChunkName: "flavours/glitch/async/favourites" */'../../favourites'); } +export function Reactions () { + return import(/* webpackChunkName: "flavours/glitch/async/reactions" */'../../reactions'); +} + export function FollowRequests () { return import(/* webpackChunkName: "flavours/glitch/async/follow_requests" */'../../follow_requests'); } diff --git a/app/javascript/flavours/glitch/reducers/user_lists.js b/app/javascript/flavours/glitch/reducers/user_lists.js index 3eb80da437..4eee00d49d 100644 --- a/app/javascript/flavours/glitch/reducers/user_lists.js +++ b/app/javascript/flavours/glitch/reducers/user_lists.js @@ -57,6 +57,12 @@ import { FAVOURITES_EXPAND_REQUEST, FAVOURITES_EXPAND_SUCCESS, FAVOURITES_EXPAND_FAIL, + REACTIONS_FETCH_SUCCESS, + REACTIONS_EXPAND_SUCCESS, + REACTIONS_FETCH_REQUEST, + REACTIONS_EXPAND_REQUEST, + REACTIONS_FETCH_FAIL, + REACTIONS_EXPAND_FAIL, } from '../actions/interactions'; import { MUTES_FETCH_REQUEST, @@ -77,6 +83,7 @@ const initialListState = ImmutableMap({ const initialState = ImmutableMap({ followers: initialListState, following: initialListState, + reactions: initialListState, reblogged_by: initialListState, favourited_by: initialListState, follow_requests: initialListState, @@ -139,6 +146,16 @@ export default function userLists(state = initialState, action) { case FOLLOWING_FETCH_FAIL: case FOLLOWING_EXPAND_FAIL: return state.setIn(['following', action.id, 'isLoading'], false); + case REACTIONS_FETCH_SUCCESS: + return normalizeList(state, ['reactions', action.id], action.accounts, action.next); + case REACTIONS_EXPAND_SUCCESS: + return appendToList(state, ['reactions', action.id], action.accounts, action.next); + case REACTIONS_FETCH_REQUEST: + case REACTIONS_EXPAND_REQUEST: + return state.setIn(['reactions', action.id, 'isLoading'], true); + case REACTIONS_FETCH_FAIL: + case REACTIONS_EXPAND_FAIL: + return state.setIn(['reactions', action.id, 'isLoading'], false); case REBLOGS_FETCH_SUCCESS: return normalizeList(state, ['reblogged_by', action.id], action.accounts, action.next); case REBLOGS_EXPAND_SUCCESS: