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: