diff --git a/app/soapbox/components/column_back_button.js b/app/soapbox/components/column_back_button.js
index 1ce0d8f1f..bbc2fc701 100644
--- a/app/soapbox/components/column_back_button.js
+++ b/app/soapbox/components/column_back_button.js
@@ -5,12 +5,20 @@ import Icon from 'soapbox/components/icon';
export default class ColumnBackButton extends React.PureComponent {
+ static propTypes = {
+ to: PropTypes.string,
+ };
+
static contextTypes = {
router: PropTypes.object,
};
handleClick = () => {
- if (window.history && window.history.length === 1) {
+ const { to } = this.props;
+
+ if (to) {
+ this.context.router.history.push(to);
+ } else if (window.history && window.history.length === 1) {
this.context.router.history.push('/');
} else {
this.context.router.history.goBack();
diff --git a/app/soapbox/features/reactions/index.js b/app/soapbox/features/reactions/index.js
new file mode 100644
index 000000000..260c8726b
--- /dev/null
+++ b/app/soapbox/features/reactions/index.js
@@ -0,0 +1,110 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { OrderedSet as ImmutableOrderedSet } from 'immutable';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import LoadingIndicator from '../../components/loading_indicator';
+import MissingIndicator from '../../components/missing_indicator';
+import { fetchFavourites, fetchReactions } from '../../actions/interactions';
+import { fetchStatus } from '../../actions/statuses';
+import { FormattedMessage } from 'react-intl';
+import AccountContainer from '../../containers/account_container';
+import Column from '../ui/components/column';
+import ScrollableList from '../../components/scrollable_list';
+import { makeGetStatus } from '../../selectors';
+import { NavLink } from 'react-router-dom';
+
+const mapStateToProps = (state, props) => {
+ const getStatus = makeGetStatus();
+ const status = getStatus(state, {
+ id: props.params.statusId,
+ username: props.params.username,
+ });
+
+ const favourites = state.getIn(['user_lists', 'favourited_by', props.params.statusId]);
+ const reactions = state.getIn(['user_lists', 'reactions', props.params.statusId]);
+ const allReactions = favourites && reactions && ImmutableOrderedSet(favourites ? [{ accounts: favourites, count: favourites.size, name: '👍' }] : []).union(reactions || []);
+
+ return {
+ status,
+ reactions: allReactions,
+ accounts: allReactions && (props.params.reaction
+ ? allReactions.find(reaction => reaction.name === props.params.reaction).accounts.map(account => ({ id: account, reaction: props.params.reaction }))
+ : allReactions.map(reaction => reaction.accounts.map(account => ({ id: account, reaction: reaction.name }))).flatten()),
+ };
+};
+
+export default @connect(mapStateToProps)
+class Reactions extends ImmutablePureComponent {
+
+ static propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.array.isRequired,
+ reactions: PropTypes.array,
+ accounts: PropTypes.array,
+ status: ImmutablePropTypes.map,
+ };
+
+ componentDidMount() {
+ this.props.dispatch(fetchFavourites(this.props.params.statusId));
+ this.props.dispatch(fetchReactions(this.props.params.statusId));
+ this.props.dispatch(fetchStatus(this.props.params.statusId));
+ }
+
+ componentDidUpdate(prevProps) {
+ const { params } = this.props;
+ if (params.statusId !== prevProps.params.statusId && params.statusId) {
+ this.props.dispatch(fetchFavourites(this.props.params.statusId));
+ prevProps.dispatch(fetchReactions(params.statusId));
+ prevProps.dispatch(fetchStatus(params.statusId));
+ }
+ }
+
+ render() {
+ const { params, reactions, accounts, status } = this.props;
+ const { username, statusId } = params;
+
+ const back = `/@${username}/posts/${statusId}`;
+
+ if (!accounts) {
+ return (
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/status/components/status_interaction_bar.js b/app/soapbox/features/status/components/status_interaction_bar.js
index d921efca0..fbd943a63 100644
--- a/app/soapbox/features/status/components/status_interaction_bar.js
+++ b/app/soapbox/features/status/components/status_interaction_bar.js
@@ -49,35 +49,45 @@ class StatusInteractionBar extends ImmutablePureComponent {
return '';
}
- render() {
+ getEmojiReacts = () => {
+ const { status } = this.props;
+
const emojiReacts = this.getNormalizedReacts();
const count = emojiReacts.reduce((acc, cur) => (
acc + cur.get('count')
), 0);
- const repost = this.getRepost();
- const EmojiReactsContainer = () => (
-
-
- {emojiReacts.map((e, i) => (
-
-
- {e.get('count')}
-
- ))}
+ if (count > 0) {
+ return (
+
+
+ {emojiReacts.map((e, i) => (
+
+
+ {e.get('count')}
+
+ ))}
+
+
+ {count}
+
-
- {count}
-
-
- );
+ );
+ }
+
+ return '';
+ };
+
+ render() {
+ const emojiReacts = this.getEmojiReacts();
+ const repost = this.getRepost();
return (
- {count > 0 && }
+ {emojiReacts}
{repost}
);
diff --git a/app/soapbox/features/ui/components/column.js b/app/soapbox/features/ui/components/column.js
index 5f31399e3..e915e5e70 100644
--- a/app/soapbox/features/ui/components/column.js
+++ b/app/soapbox/features/ui/components/column.js
@@ -12,12 +12,13 @@ export default class Column extends React.PureComponent {
children: PropTypes.node,
active: PropTypes.bool,
backBtnSlim: PropTypes.bool,
+ back: PropTypes.string,
};
render() {
- const { heading, icon, children, active, backBtnSlim } = this.props;
+ const { heading, icon, children, active, backBtnSlim, back } = this.props;
const columnHeaderId = heading && heading.replace(/ /g, '-');
- const backBtn = backBtnSlim ? (
) : (
);
+ const backBtn = backBtnSlim ? (
) : (
);
return (
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js
index 010242b0d..c6136f69b 100644
--- a/app/soapbox/features/ui/index.js
+++ b/app/soapbox/features/ui/index.js
@@ -56,6 +56,7 @@ import {
Followers,
Following,
Reblogs,
+ Reactions,
// Favourites,
DirectTimeline,
HashtagTimeline,
@@ -261,6 +262,7 @@ class SwitchingColumnsArea extends React.PureComponent {
+
diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js
index 1fe50212c..61f6c2143 100644
--- a/app/soapbox/features/ui/util/async-components.js
+++ b/app/soapbox/features/ui/util/async-components.js
@@ -94,6 +94,10 @@ export function Reblogs() {
return import(/* webpackChunkName: "features/reblogs" */'../../reblogs');
}
+export function Reactions() {
+ return import(/* webpackChunkName: "features/reblogs" */'../../reactions');
+}
+
export function Favourites() {
return import(/* webpackChunkName: "features/favourites" */'../../favourites');
}
diff --git a/app/soapbox/reducers/__tests__/user_lists-test.js b/app/soapbox/reducers/__tests__/user_lists-test.js
index feaaca3e6..7d571e208 100644
--- a/app/soapbox/reducers/__tests__/user_lists-test.js
+++ b/app/soapbox/reducers/__tests__/user_lists-test.js
@@ -10,6 +10,7 @@ describe('user_lists reducer', () => {
favourited_by: ImmutableMap(),
follow_requests: ImmutableMap(),
blocks: ImmutableMap(),
+ reactions: ImmutableMap(),
mutes: ImmutableMap(),
groups: ImmutableMap(),
groups_removed_accounts: ImmutableMap(),
diff --git a/app/soapbox/reducers/user_lists.js b/app/soapbox/reducers/user_lists.js
index 9afaf55b9..f0d80de77 100644
--- a/app/soapbox/reducers/user_lists.js
+++ b/app/soapbox/reducers/user_lists.js
@@ -14,6 +14,7 @@ import {
import {
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS,
+ REACTIONS_FETCH_SUCCESS,
} from '../actions/interactions';
import {
BLOCKS_FETCH_SUCCESS,
@@ -37,6 +38,7 @@ const initialState = ImmutableMap({
following: ImmutableMap(),
reblogged_by: ImmutableMap(),
favourited_by: ImmutableMap(),
+ reactions: ImmutableMap(),
follow_requests: ImmutableMap(),
blocks: ImmutableMap(),
mutes: ImmutableMap(),
@@ -77,6 +79,8 @@ export default function userLists(state = initialState, action) {
return state.setIn(['reblogged_by', action.id], ImmutableOrderedSet(action.accounts.map(item => item.id)));
case FAVOURITES_FETCH_SUCCESS:
return state.setIn(['favourited_by', action.id], ImmutableOrderedSet(action.accounts.map(item => item.id)));
+ case REACTIONS_FETCH_SUCCESS:
+ return state.setIn(['reactions', action.id], action.reactions.map(({ accounts, ...reaction }) => ({ ...reaction, accounts: ImmutableOrderedSet(accounts.map(account => account.id)) })));
case NOTIFICATIONS_UPDATE:
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS:
diff --git a/app/styles/accounts.scss b/app/styles/accounts.scss
index ad2190be5..14f74f59a 100644
--- a/app/styles/accounts.scss
+++ b/app/styles/accounts.scss
@@ -216,6 +216,13 @@
.account__avatar-wrapper {
float: left;
margin-right: 12px;
+
+ .emoji-react__emoji {
+ position: absolute;
+ top: 36px;
+ left: 32px;
+ z-index: 1;
+ }
}
.account__avatar {
diff --git a/app/styles/components/emoji-reacts.scss b/app/styles/components/emoji-reacts.scss
index 4fca2108c..bc69b0542 100644
--- a/app/styles/components/emoji-reacts.scss
+++ b/app/styles/components/emoji-reacts.scss
@@ -1,6 +1,8 @@
.emoji-react {
display: inline-block;
transition: 0.1s;
+ color: var(--primary-text-color--faint);
+ text-decoration: none;
&__emoji {
img {
@@ -20,8 +22,6 @@
}
.emoji-react--reblogs {
- color: var(--primary-text-color--faint);
- text-decoration: none;
vertical-align: middle;
display: inline-flex;
diff --git a/app/styles/ui.scss b/app/styles/ui.scss
index bf8dd9f7e..72c55a6bd 100644
--- a/app/styles/ui.scss
+++ b/app/styles/ui.scss
@@ -611,7 +611,8 @@
.notification__filter-bar,
.search__filter-bar,
-.account__section-headline {
+.account__section-headline,
+.reaction__filter-bar {
border-bottom: 1px solid var(--brand-color--faint);
cursor: default;
display: flex;
@@ -661,6 +662,17 @@
}
}
+.reaction__filter-bar {
+ overflow-x: auto;
+ overflow-y: hidden;
+
+ a {
+ flex: unset;
+ padding: 15px 24px;
+ min-width: max-content;
+ }
+}
+
::-webkit-scrollbar-thumb {
border-radius: 0;
}