diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js
index 60f46f0cbcedb3..b9713e32155337 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 141483dd0118f2..f047a6ef411cc4 100644
--- a/app/javascript/flavours/glitch/components/status.jsx
+++ b/app/javascript/flavours/glitch/components/status.jsx
@@ -29,7 +29,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 81443d20555e14..b026bd17a810e7 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 38001ae9bc0a4c..046adba294082b 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx
@@ -9,6 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
+import MoodIcon from '@/material-icons/400-24px/mood.svg?react';
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
import { AnimatedNumber } from 'flavours/glitch/components/animated_number';
@@ -25,7 +26,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';
@@ -143,6 +144,7 @@ class DetailedStatus extends ImmutablePureComponent {
const reblogIcon = 'retweet';
const reblogIconComponent = RepeatIcon;
let favouriteLink = '';
+ let reactionLink = '';
let edited = '';
// Depending on user settings, some media are considered as parts of the
@@ -288,6 +290,14 @@ class DetailedStatus extends ImmutablePureComponent {
);
+ reactionLink = (
+
+
+
+ total + obj.get('count'), 0)} />
+
+
+ );
} else {
favouriteLink = (
@@ -297,6 +307,14 @@ class DetailedStatus extends ImmutablePureComponent {
);
+ reactionLink = (
+
+
+
+ total + obj.get('count'), 0)} />
+
+
+ );
}
if (status.get('edited_at')) {
@@ -347,7 +365,7 @@ class DetailedStatus extends ImmutablePureComponent {
- {edited}{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink}
+ {edited}{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink} · {reactionLink}
diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx
index ba7e521b751a11..5a904cf5d8c903 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,
@@ -231,6 +232,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 762df7ecf4140a..22244e4e6c7989 100644
--- a/app/javascript/flavours/glitch/features/ui/util/async-components.js
+++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js
@@ -82,6 +82,10 @@ export function Following () {
return import(/* webpackChunkName: "flavours/glitch/async/following" */'flavours/glitch/features/following');
}
+export function Reactions () {
+ return import(/* webpackChunkName: "flavours/glitch/async/reactions" */'flavours/glitch/features/reactions');
+}
+
export function Reblogs () {
return import(/* webpackChunkName: "flavours/glitch/async/reblogs" */'flavours/glitch/features/reblogs');
}
diff --git a/app/javascript/flavours/glitch/reducers/user_lists.js b/app/javascript/flavours/glitch/reducers/user_lists.js
index 3eb80da4379bfa..4eee00d49d07be 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:
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 7a5cd17b626e2e..6e27cbf268f87c 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -635,7 +635,8 @@
}
.detailed-status__favorites,
-.detailed-status__reblogs {
+.detailed-status__reblogs,
+.detailed-status__reactions {
font-weight: 500;
font-size: 12px;
line-height: 18px;
diff --git a/app/javascript/material-icons/400-24px/mood-fill.svg b/app/javascript/material-icons/400-24px/mood-fill.svg
new file mode 100644
index 00000000000000..9480d0fb92aaa3
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/mood-fill.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/mood.svg b/app/javascript/material-icons/400-24px/mood.svg
new file mode 100644
index 00000000000000..46cafa76808008
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/mood.svg
@@ -0,0 +1 @@
+
\ No newline at end of file