diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js
index b9713e3215..c837e144c0 100644
--- a/app/javascript/flavours/glitch/actions/interactions.js
+++ b/app/javascript/flavours/glitch/actions/interactions.js
@@ -303,7 +303,7 @@ export function fetchReactions(id) {
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(fetchReactionsSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(accounts.map(item => item.id)));
}).catch(error => {
dispatch(fetchReactionsFail(id, error));
@@ -318,11 +318,11 @@ export function fetchReactionsRequest(id) {
};
}
-export function fetchReactionsSuccess(id, accounts, next) {
+export function fetchReactionsSuccess(id, reactions, next) {
return {
type: REACTIONS_FETCH_SUCCESS,
id,
- accounts,
+ reactions,
next,
};
}
@@ -349,7 +349,7 @@ export function expandReactions(id) {
const accounts = response.data.map(item => item.account);
dispatch(importFetchedAccounts(accounts));
- dispatch(expandReactionsSuccess(id, accounts, next ? next.uri : null));
+ dispatch(expandReactionsSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(accounts.map(item => item.id)));
}).catch(error => dispatch(expandReactionsFail(id, error)));
};
@@ -362,11 +362,11 @@ export function expandReactionsRequest(id) {
};
}
-export function expandReactionsSuccess(id, accounts, next) {
+export function expandReactionsSuccess(id, reactions, next) {
return {
type: REACTIONS_EXPAND_SUCCESS,
id,
- accounts,
+ reactions,
next,
};
}
diff --git a/app/javascript/flavours/glitch/api_types/reaction.ts b/app/javascript/flavours/glitch/api_types/reaction.ts
new file mode 100644
index 0000000000..c644246ef1
--- /dev/null
+++ b/app/javascript/flavours/glitch/api_types/reaction.ts
@@ -0,0 +1,5 @@
+export interface ApiStatusReactionJSON {
+ name: string;
+ static_url?: string | undefined;
+ url?: string | undefined;
+}
diff --git a/app/javascript/flavours/glitch/components/account.jsx b/app/javascript/flavours/glitch/components/account.jsx
index 7e5209653e..f1be32eba0 100644
--- a/app/javascript/flavours/glitch/components/account.jsx
+++ b/app/javascript/flavours/glitch/components/account.jsx
@@ -14,6 +14,7 @@ import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
import { me } from '../initial_state';
import { Avatar } from './avatar';
+import { AvatarOverlay } from './avatar_overlay';
import { Button } from './button';
import { FollowersCounter } from './counters';
import { DisplayName } from './display_name';
@@ -42,6 +43,7 @@ class Account extends ImmutablePureComponent {
onMute: PropTypes.func,
onMuteNotifications: PropTypes.func,
intl: PropTypes.object.isRequired,
+ overlayEmoji: PropTypes.object,
hidden: PropTypes.bool,
minimal: PropTypes.bool,
defaultAction: PropTypes.string,
@@ -50,6 +52,7 @@ class Account extends ImmutablePureComponent {
static defaultProps = {
size: 46,
+ overlayEmoji: { name: null }
};
handleFollow = () => {
@@ -73,7 +76,7 @@ class Account extends ImmutablePureComponent {
};
render () {
- const { account, intl, hidden, withBio, defaultAction, size, minimal } = this.props;
+ const { account, intl, hidden, withBio, defaultAction, overlayEmoji, size, minimal } = this.props;
if (!account) {
return ;
@@ -138,12 +141,19 @@ class Account extends ImmutablePureComponent {
verification = ;
}
+ let statusAvatar;
+ if (!overlayEmoji.name) {
+ statusAvatar = ;
+ } else {
+ statusAvatar = ;
+ }
+
return (
diff --git a/app/javascript/flavours/glitch/components/avatar_overlay.tsx b/app/javascript/flavours/glitch/components/avatar_overlay.tsx
index cddca43d5b..1f806f7fb1 100644
--- a/app/javascript/flavours/glitch/components/avatar_overlay.tsx
+++ b/app/javascript/flavours/glitch/components/avatar_overlay.tsx
@@ -1,11 +1,15 @@
import type { Account } from 'flavours/glitch/models/account';
+import type { StatusReaction } from 'flavours/glitch/models/reaction';
import { useHovering } from '../hooks/useHovering';
import { autoPlayGif } from '../initial_state';
+import { Emoji } from './status_reactions';
+
interface Props {
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
- friend: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
+ friend?: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
+ emoji?: StatusReaction | undefined;
size?: number;
baseSize?: number;
overlaySize?: number;
@@ -14,6 +18,7 @@ interface Props {
export const AvatarOverlay: React.FC
= ({
account,
friend,
+ emoji,
size = 46,
baseSize = 36,
overlaySize = 24,
@@ -27,6 +32,32 @@ export const AvatarOverlay: React.FC = ({
? friend?.get('avatar')
: friend?.get('avatar_static');
+ let overlayElement;
+ if (friendSrc) {
+ overlayElement = (
+
+ {friendSrc &&
}
+
+ );
+ } else {
+ overlayElement = (
+
+ {emoji && (
+
+ )}
+
+ );
+ }
+
return (
= ({
{accountSrc &&
}
-
-
- {friendSrc &&
}
-
-
+ {overlayElement}
);
};
diff --git a/app/javascript/flavours/glitch/features/reactions/index.jsx b/app/javascript/flavours/glitch/features/reactions/index.jsx
index 5a849e70af..1ef01ad114 100644
--- a/app/javascript/flavours/glitch/features/reactions/index.jsx
+++ b/app/javascript/flavours/glitch/features/reactions/index.jsx
@@ -27,9 +27,9 @@ const messages = defineMessages({
});
const mapStateToProps = (state, props) => ({
- accountIds: state.getIn(['user_lists', 'reactions', props.params.statusId, 'items']),
- hasMore: !!state.getIn(['user_lists', 'reactions', props.params.statusId, 'next']),
- isLoading: state.getIn(['user_lists', 'reactions', props.params.statusId, 'isLoading'], true),
+ reactions: state.getIn(['status_reactions', 'reactions', props.params.statusId, 'items']),
+ hasMore: !!state.getIn(['status_reactions', 'reactions', props.params.statusId, 'next']),
+ isLoading: state.getIn(['status_reactions', 'reactions', props.params.statusId, 'isLoading'], true),
});
class Reactions extends ImmutablePureComponent {
@@ -37,7 +37,7 @@ class Reactions extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.list,
+ reactions: ImmutablePropTypes.orderedSet,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
multiColumn: PropTypes.bool,
@@ -67,9 +67,9 @@ class Reactions extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
- const { intl, accountIds, hasMore, isLoading, multiColumn } = this.props;
+ const { intl, reactions, hasMore, isLoading, multiColumn } = this.props;
- if (!accountIds) {
+ if (!reactions) {
return (
@@ -77,6 +77,9 @@ class Reactions extends ImmutablePureComponent {
);
}
+ const accountIds = reactions.map(v => v.account);
+ const reactionsByAccount = new Map(reactions.map(v => [v.account, v]));
+
const emptyMessage = ;
return (
@@ -102,7 +105,7 @@ class Reactions extends ImmutablePureComponent {
bindToDocument={!multiColumn}
>
{accountIds.map(id =>
- ,
+ ,
)}
diff --git a/app/javascript/flavours/glitch/models/reaction.ts b/app/javascript/flavours/glitch/models/reaction.ts
new file mode 100644
index 0000000000..7c02b43ee9
--- /dev/null
+++ b/app/javascript/flavours/glitch/models/reaction.ts
@@ -0,0 +1,13 @@
+import type { RecordOf } from 'immutable';
+import { Record } from 'immutable';
+
+import type { ApiStatusReactionJSON } from 'flavours/glitch/api_types/reaction';
+
+type StatusReactionShape = Required;
+export type StatusReaction = RecordOf;
+
+export const CustomEmojiFactory = Record({
+ name: '',
+ static_url: '',
+ url: '',
+});
diff --git a/app/javascript/flavours/glitch/reducers/index.ts b/app/javascript/flavours/glitch/reducers/index.ts
index c67349e51f..6b2ddee99c 100644
--- a/app/javascript/flavours/glitch/reducers/index.ts
+++ b/app/javascript/flavours/glitch/reducers/index.ts
@@ -38,6 +38,7 @@ import search from './search';
import server from './server';
import settings from './settings';
import status_lists from './status_lists';
+import status_reactions from './status_reactions';
import statuses from './statuses';
import suggestions from './suggestions';
import tags from './tags';
@@ -86,6 +87,7 @@ const reducers = {
history,
tags,
followed_tags,
+ status_reactions,
notificationPolicy: notificationPolicyReducer,
notificationRequests: notificationRequestsReducer,
};
diff --git a/app/javascript/flavours/glitch/reducers/status_reactions.js b/app/javascript/flavours/glitch/reducers/status_reactions.js
new file mode 100644
index 0000000000..3ba6a83b39
--- /dev/null
+++ b/app/javascript/flavours/glitch/reducers/status_reactions.js
@@ -0,0 +1,57 @@
+import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
+
+import {
+ REACTIONS_FETCH_SUCCESS,
+ REACTIONS_EXPAND_SUCCESS,
+ REACTIONS_FETCH_REQUEST,
+ REACTIONS_EXPAND_REQUEST,
+ REACTIONS_FETCH_FAIL,
+ REACTIONS_EXPAND_FAIL,
+} from '../actions/interactions';
+
+const initialState = ImmutableMap({
+ reactions: ImmutableMap({
+ next: null,
+ isLoading: false,
+ items: ImmutableOrderedSet(),
+ }),
+});
+
+const normalizeList = (state, path, reactions, next) => {
+ const filteredReactions = reactions.map(v => {
+ v.account = v.account.id;
+ return v;
+ });
+ return state.setIn(path, ImmutableMap({
+ next,
+ items: ImmutableOrderedSet(filteredReactions),
+ isLoading: false,
+ }));
+};
+
+const appendToList = (state, path, reactions, next) => {
+ const filteredReactions = reactions.map(v => {
+ v.account = v.account.id;
+ return v;
+ });
+ return state.updateIn(path, map => {
+ return map.set('next', next).set('isLoading', false).update('items', list => list.concat(filteredReactions));
+ });
+};
+
+export default function statusReactions(state = initialState, action) {
+ switch(action.type) {
+ case REACTIONS_FETCH_SUCCESS:
+ return normalizeList(state, ['reactions', action.id], action.reactions, action.next);
+ case REACTIONS_EXPAND_SUCCESS:
+ return appendToList(state, ['reactions', action.id], action.reactions, 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);
+ default:
+ return state;
+ }
+}
diff --git a/app/javascript/flavours/glitch/reducers/user_lists.js b/app/javascript/flavours/glitch/reducers/user_lists.js
index 4eee00d49d..3eb80da437 100644
--- a/app/javascript/flavours/glitch/reducers/user_lists.js
+++ b/app/javascript/flavours/glitch/reducers/user_lists.js
@@ -57,12 +57,6 @@ 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,
@@ -83,7 +77,6 @@ const initialListState = ImmutableMap({
const initialState = ImmutableMap({
followers: initialListState,
following: initialListState,
- reactions: initialListState,
reblogged_by: initialListState,
favourited_by: initialListState,
follow_requests: initialListState,
@@ -146,16 +139,6 @@ 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: