diff --git a/app/soapbox/components/status_reply_mentions.js b/app/soapbox/components/status_reply_mentions.js
index 580880fee..1bb43562d 100644
--- a/app/soapbox/components/status_reply_mentions.js
+++ b/app/soapbox/components/status_reply_mentions.js
@@ -1,15 +1,35 @@
import React from 'react';
+import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
+import { openModal } from 'soapbox/actions/modal';
-export default @injectIntl
+const mapDispatchToProps = (dispatch) => ({
+ onOpenMentionsModal(username, statusId) {
+ dispatch(openModal('MENTIONS', {
+ username,
+ statusId,
+ }));
+ },
+});
+
+export default @connect(null, mapDispatchToProps)
+@injectIntl
class StatusReplyMentions extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
+ onOpenMentionsModal: PropTypes.func,
+ }
+
+ handleOpenMentionsModal = () => {
+ const { status, onOpenMentionsModal } = this.props;
+
+ onOpenMentionsModal(status.getIn(['account', 'acct']), status.get('id'));
}
render() {
@@ -54,6 +74,7 @@ class StatusReplyMentions extends ImmutablePureComponent {
}
}
+
// The typical case with a reply-to and a list of mentions.
return (
@@ -68,9 +89,13 @@ class StatusReplyMentions extends ImmutablePureComponent {
{' '}
>)),
more: to.size > 2 && (
-
+
+
-
+
),
}}
/>
diff --git a/app/soapbox/features/favourites/index.js b/app/soapbox/features/favourites/index.js
deleted file mode 100644
index 7bf1852d9..000000000
--- a/app/soapbox/features/favourites/index.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-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 { fetchFavourites } from '../../actions/interactions';
-import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ScrollableList from '../../components/scrollable_list';
-
-const messages = defineMessages({
- heading: { id: 'column.favourites', defaultMessage: 'Likes' },
-});
-
-const mapStateToProps = (state, props) => ({
- accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Favourites extends ImmutablePureComponent {
-
- static propTypes = {
- intl: PropTypes.object.isRequired,
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.orderedSet,
- };
-
- componentDidMount() {
- this.props.dispatch(fetchFavourites(this.props.params.statusId));
- }
-
- componentDidUpdate(prevProps) {
- const { statusId } = this.props.params;
- const { prevStatusId } = prevProps.params;
-
- if (statusId !== prevStatusId && statusId) {
- this.props.dispatch(fetchFavourites(statusId));
- }
- }
-
- render() {
- const { intl, accountIds } = this.props;
-
- if (!accountIds) {
- return (
-
-
-
- );
- }
-
- const emptyMessage =
;
-
- return (
-
-
- {accountIds.map(id =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/mentions/index.js b/app/soapbox/features/mentions/index.js
deleted file mode 100644
index d77e7b8f2..000000000
--- a/app/soapbox/features/mentions/index.js
+++ /dev/null
@@ -1,96 +0,0 @@
-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 { fetchStatus } from '../../actions/statuses';
-import { injectIntl, defineMessages } 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';
-
-const messages = defineMessages({
- heading: { id: 'column.mentions', defaultMessage: 'Mentions' },
-});
-
-const mapStateToProps = (state, props) => {
- const getStatus = makeGetStatus();
- const status = getStatus(state, {
- id: props.params.statusId,
- username: props.params.username,
- });
-
- return {
- status,
- accountIds: status ? ImmutableOrderedSet(status.get('mentions').map(m => m.get('id'))) : null,
- };
-};
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Mentions extends ImmutablePureComponent {
-
- static propTypes = {
- intl: PropTypes.object.isRequired,
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.orderedSet,
- status: ImmutablePropTypes.map,
- };
-
- fetchData = () => {
- const { dispatch, params } = this.props;
- const { statusId } = params;
-
- dispatch(fetchStatus(statusId));
- }
-
- componentDidMount() {
- this.fetchData();
- }
-
- componentDidUpdate(prevProps) {
- const { params } = this.props;
-
- if (params.statusId !== prevProps.params.statusId) {
- this.fetchData();
- }
- }
-
- render() {
- const { intl, accountIds, status } = this.props;
-
- if (!accountIds) {
- return (
-
-
-
- );
- }
-
- if (!status) {
- return (
-
-
-
- );
- }
-
- return (
-
-
- {accountIds.map(id =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/reactions/index.js b/app/soapbox/features/reactions/index.js
deleted file mode 100644
index 5750c3053..000000000
--- a/app/soapbox/features/reactions/index.js
+++ /dev/null
@@ -1,128 +0,0 @@
-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 { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
-import LoadingIndicator from '../../components/loading_indicator';
-import MissingIndicator from '../../components/missing_indicator';
-import { fetchFavourites, fetchReactions } from '../../actions/interactions';
-import { fetchStatus } from '../../actions/statuses';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ScrollableList from '../../components/scrollable_list';
-import { makeGetStatus } from '../../selectors';
-
-const messages = defineMessages({
- heading: { id: 'column.reactions', defaultMessage: 'Reactions' },
-});
-
-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)
-@injectIntl
-class Reactions extends ImmutablePureComponent {
-
- static contextTypes = {
- router: PropTypes.object.isRequired,
- };
-
- static propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- reactions: ImmutablePropTypes.orderedSet,
- accounts: ImmutablePropTypes.orderedSet,
- status: ImmutablePropTypes.map,
- };
-
- fetchData = () => {
- const { dispatch, params } = this.props;
- const { statusId } = params;
-
- dispatch(fetchFavourites(statusId));
- dispatch(fetchReactions(statusId));
- dispatch(fetchStatus(statusId));
- }
-
- componentDidMount() {
- this.fetchData();
- }
-
- componentDidUpdate(prevProps) {
- const { params } = this.props;
-
- if (params.statusId !== prevProps.params.statusId) {
- this.fetchData();
- }
- }
-
- handleFilterChange = (reaction) => () => {
- const { params } = this.props;
- const { username, statusId } = params;
-
- this.context.router.history.replace(`/@${username}/posts/${statusId}/reactions/${reaction}`);
- };
-
- render() {
- const { intl, params, reactions, accounts, status } = this.props;
-
- if (!accounts) {
- return (
-
-
-
- );
- }
-
- if (!status) {
- return (
-
-
-
- );
- }
-
- const emptyMessage =
;
-
- return (
-
- {
- reactions.size > 0 && (
-
-
- {reactions?.filter(reaction => reaction.count).map(reaction => )}
-
- )
- }
-
- {accounts.map((account) =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/reblogs/index.js b/app/soapbox/features/reblogs/index.js
deleted file mode 100644
index ea536940c..000000000
--- a/app/soapbox/features/reblogs/index.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-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 { fetchReblogs } from '../../actions/interactions';
-import { fetchStatus } from '../../actions/statuses';
-import { injectIntl, defineMessages, 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';
-
-const messages = defineMessages({
- heading: { id: 'column.reblogs', defaultMessage: 'Reposts' },
-});
-
-const mapStateToProps = (state, props) => {
- const getStatus = makeGetStatus();
- const status = getStatus(state, {
- id: props.params.statusId,
- username: props.params.username,
- });
-
- return {
- status,
- accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
- };
-};
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Reblogs extends ImmutablePureComponent {
-
- static propTypes = {
- intl: PropTypes.object.isRequired,
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- accountIds: ImmutablePropTypes.orderedSet,
- status: ImmutablePropTypes.map,
- };
-
- fetchData = () => {
- const { dispatch, params } = this.props;
- const { statusId } = params;
-
- dispatch(fetchReblogs(statusId));
- dispatch(fetchStatus(statusId));
- }
-
- componentDidMount() {
- this.fetchData();
- }
-
- componentDidUpdate(prevProps) {
- const { params } = this.props;
-
- if (params.statusId !== prevProps.params.statusId) {
- this.fetchData();
- }
- }
-
- render() {
- const { intl, accountIds, status } = this.props;
-
- if (!accountIds) {
- return (
-
-
-
- );
- }
-
- if (!status) {
- return (
-
-
-
- );
- }
-
- const emptyMessage =
;
-
- return (
-
-
- {accountIds.map(id =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/status/components/status_interaction_bar.js b/app/soapbox/features/status/components/status_interaction_bar.js
index b75769d66..e7f2b0f05 100644
--- a/app/soapbox/features/status/components/status_interaction_bar.js
+++ b/app/soapbox/features/status/components/status_interaction_bar.js
@@ -8,9 +8,9 @@ import emojify from 'soapbox/features/emoji/emoji';
import { reduceEmoji } from 'soapbox/utils/emoji_reacts';
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
import { getFeatures } from 'soapbox/utils/features';
-import { Link } from 'react-router-dom';
import Icon from 'soapbox/components/icon';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
+import { openModal } from 'soapbox/actions/modal';
const mapStateToProps = state => {
const instance = state.get('instance');
@@ -21,7 +21,29 @@ const mapStateToProps = state => {
};
};
-export default @connect(mapStateToProps)
+const mapDispatchToProps = (dispatch) => ({
+ onOpenReblogsModal(username, statusId) {
+ dispatch(openModal('REBLOGS', {
+ username,
+ statusId,
+ }));
+ },
+ onOpenFavouritesModal(username, statusId) {
+ dispatch(openModal('FAVOURITES', {
+ username,
+ statusId,
+ }));
+ },
+ onOpenReactionsModal(username, statusId, reaction) {
+ dispatch(openModal('REACTIONS', {
+ username,
+ statusId,
+ reaction,
+ }));
+ },
+});
+
+export default @connect(mapStateToProps, mapDispatchToProps)
class StatusInteractionBar extends ImmutablePureComponent {
static propTypes = {
@@ -29,6 +51,8 @@ class StatusInteractionBar extends ImmutablePureComponent {
me: SoapboxPropTypes.me,
allowedEmoji: ImmutablePropTypes.list,
features: PropTypes.object.isRequired,
+ onOpenReblogsModal: PropTypes.func,
+ onOpenReactionsModal: PropTypes.func,
}
getNormalizedReacts = () => {
@@ -41,22 +65,39 @@ class StatusInteractionBar extends ImmutablePureComponent {
).reverse();
}
+ handleOpenReblogsModal = () => {
+ const { status, onOpenReblogsModal } = this.props;
+
+ onOpenReblogsModal(status.getIn(['account', 'acct']), status.get('id'));
+ }
+
getReposts = () => {
const { status } = this.props;
+
if (status.get('reblogs_count')) {
return (
-
+
-
+
);
}
return '';
}
+ handleOpenFavouritesModal = () => {
+ const { status, onOpenFavouritesModal } = this.props;
+
+ onOpenFavouritesModal(status.getIn(['account', 'acct']), status.get('id'));
+ }
+
getFavourites = () => {
const { features, status } = this.props;
@@ -72,9 +113,13 @@ class StatusInteractionBar extends ImmutablePureComponent {
if (features.exposableReactions) {
return (
-
+
{favourites}
-
+
);
} else {
return (
@@ -88,8 +133,14 @@ class StatusInteractionBar extends ImmutablePureComponent {
return '';
}
+ handleOpenReactionsModal = (reaction) => () => {
+ const { status, onOpenReactionsModal } = this.props;
+
+ onOpenReactionsModal(status.getIn(['account', 'acct']), status.get('id'), reaction.get('name'));
+ }
+
getEmojiReacts = () => {
- const { status, features } = this.props;
+ const { features } = this.props;
const emojiReacts = this.getNormalizedReacts();
const count = emojiReacts.reduce((acc, cur) => (
@@ -112,7 +163,16 @@ class StatusInteractionBar extends ImmutablePureComponent {
);
if (features.exposableReactions) {
- return
{emojiReact};
+ return (
+
+ {emojiReact}
+
+ );
}
return
{emojiReact};
diff --git a/app/soapbox/features/ui/components/favourites_modal.js b/app/soapbox/features/ui/components/favourites_modal.js
new file mode 100644
index 000000000..f0cf51965
--- /dev/null
+++ b/app/soapbox/features/ui/components/favourites_modal.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
+import IconButton from 'soapbox/components/icon_button';
+import LoadingIndicator from 'soapbox/components/loading_indicator';
+import AccountContainer from 'soapbox/containers/account_container';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { fetchFavourites } from 'soapbox/actions/interactions';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+const mapStateToProps = (state, props) => {
+ return {
+ accountIds: state.getIn(['user_lists', 'favourited_by', props.statusId]),
+ };
+};
+
+export default @connect(mapStateToProps)
+@injectIntl
+class FavouritesModal extends React.PureComponent {
+
+ static propTypes = {
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ statusId: PropTypes.string.isRequired,
+ username: PropTypes.string.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.orderedSet,
+ };
+
+ fetchData = () => {
+ const { dispatch, statusId } = this.props;
+
+ dispatch(fetchFavourites(statusId));
+ }
+
+ componentDidMount() {
+ this.fetchData();
+ }
+
+ onClickClose = () => {
+ this.props.onClose('FAVOURITES');
+ };
+
+ render() {
+ const { intl, accountIds } = this.props;
+
+ let body;
+
+ if (!accountIds) {
+ body =
;
+ } else {
+ const emptyMessage =
;
+
+ body = (
+
+ {accountIds.map(id =>
+ ,
+ )}
+
+ );
+ }
+
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/ui/components/mentions_modal.js b/app/soapbox/features/ui/components/mentions_modal.js
new file mode 100644
index 000000000..b1554c2d2
--- /dev/null
+++ b/app/soapbox/features/ui/components/mentions_modal.js
@@ -0,0 +1,94 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { OrderedSet as ImmutableOrderedSet } from 'immutable';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
+import IconButton from 'soapbox/components/icon_button';
+import LoadingIndicator from 'soapbox/components/loading_indicator';
+import AccountContainer from 'soapbox/containers/account_container';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { makeGetStatus } from 'soapbox/selectors';
+import { fetchStatus } from 'soapbox/actions/statuses';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+const mapStateToProps = (state, props) => {
+ const getStatus = makeGetStatus();
+ const status = getStatus(state, {
+ id: props.statusId,
+ username: props.username,
+ });
+
+ return {
+ accountIds: status ? ImmutableOrderedSet(status.get('mentions').map(m => m.get('id'))) : null,
+ };
+};
+
+export default @connect(mapStateToProps)
+@injectIntl
+class MentionsModal extends React.PureComponent {
+
+ static propTypes = {
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ statusId: PropTypes.string.isRequired,
+ username: PropTypes.string.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.orderedSet,
+ };
+
+ fetchData = () => {
+ const { dispatch, statusId } = this.props;
+
+ dispatch(fetchStatus(statusId));
+ }
+
+ componentDidMount() {
+ this.fetchData();
+ }
+
+ onClickClose = () => {
+ this.props.onClose('MENTIONS');
+ };
+
+ render() {
+ const { intl, accountIds } = this.props;
+
+ let body;
+
+ if (!accountIds) {
+ body =
;
+ } else {
+ body = (
+
+ {accountIds.map(id =>
+ ,
+ )}
+
+ );
+ }
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/ui/components/modal_root.js b/app/soapbox/features/ui/components/modal_root.js
index dd5d4970b..52f94871d 100644
--- a/app/soapbox/features/ui/components/modal_root.js
+++ b/app/soapbox/features/ui/components/modal_root.js
@@ -25,6 +25,10 @@ import {
UnauthorizedModal,
EditFederationModal,
ComponentModal,
+ ReactionsModal,
+ FavouritesModal,
+ ReblogsModal,
+ MentionsModal,
} from '../../../features/ui/util/async-components';
const MODAL_COMPONENTS = {
@@ -47,6 +51,10 @@ const MODAL_COMPONENTS = {
'CRYPTO_DONATE': CryptoDonateModal,
'EDIT_FEDERATION': EditFederationModal,
'COMPONENT': ComponentModal,
+ 'REBLOGS': ReblogsModal,
+ 'FAVOURITES': FavouritesModal,
+ 'REACTIONS': ReactionsModal,
+ 'MENTIONS': MentionsModal,
};
export default class ModalRoot extends React.PureComponent {
diff --git a/app/soapbox/features/ui/components/reactions_modal.js b/app/soapbox/features/ui/components/reactions_modal.js
new file mode 100644
index 000000000..8b905ac25
--- /dev/null
+++ b/app/soapbox/features/ui/components/reactions_modal.js
@@ -0,0 +1,119 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { List as ImmutableList } from 'immutable';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
+import IconButton from 'soapbox/components/icon_button';
+import LoadingIndicator from 'soapbox/components/loading_indicator';
+import AccountContainer from 'soapbox/containers/account_container';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { fetchFavourites, fetchReactions } from 'soapbox/actions/interactions';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+const mapStateToProps = (state, props) => {
+
+ const favourites = state.getIn(['user_lists', 'favourited_by', props.statusId]);
+ const reactions = state.getIn(['user_lists', 'reactions', props.statusId]);
+ const allReactions = favourites && reactions && ImmutableList(favourites ? [{ accounts: favourites, count: favourites.size, name: '👍' }] : []).concat(reactions || []);
+
+ return {
+ reactions: allReactions,
+ };
+};
+
+export default @connect(mapStateToProps)
+@injectIntl
+class ReactionsModal extends React.PureComponent {
+
+ static propTypes = {
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ statusId: PropTypes.string.isRequired,
+ username: PropTypes.string.isRequired,
+ reaction: PropTypes.string,
+ dispatch: PropTypes.func.isRequired,
+ reactions: ImmutablePropTypes.list,
+ };
+
+ state = {
+ reaction: this.props.reaction,
+ }
+
+ fetchData = () => {
+ const { dispatch, statusId } = this.props;
+
+ dispatch(fetchFavourites(statusId));
+ dispatch(fetchReactions(statusId));
+ }
+
+ componentDidMount() {
+ this.fetchData();
+ }
+
+ onClickClose = () => {
+ this.props.onClose('REACTIONS');
+ };
+
+ handleFilterChange = (reaction) => () => {
+ this.setState({ reaction });
+ };
+
+ render() {
+ const { intl, reactions } = this.props;
+ const { reaction } = this.state;
+
+ const accounts = reactions && (reaction
+ ? reactions.find(reaction => reaction.name === this.state.reaction).accounts.map(account => ({ id: account, reaction: this.state.reaction }))
+ : reactions.map(reaction => reaction.accounts.map(account => ({ id: account, reaction: reaction.name }))).flatten());
+
+ let body;
+
+ if (!accounts) {
+ body =
;
+ } else {
+ const emptyMessage =
;
+
+ body = (<>
+ {
+ reactions.size > 0 && (
+
+
+ {reactions?.filter(reaction => reaction.count).map(reaction => )}
+
+ )
+ }
+
+ {accounts.map((account) =>
+ ,
+ )}
+
+ >);
+ }
+
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/ui/components/reblogs_modal.js b/app/soapbox/features/ui/components/reblogs_modal.js
new file mode 100644
index 000000000..c6d4811f5
--- /dev/null
+++ b/app/soapbox/features/ui/components/reblogs_modal.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
+import IconButton from 'soapbox/components/icon_button';
+import LoadingIndicator from 'soapbox/components/loading_indicator';
+import AccountContainer from 'soapbox/containers/account_container';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { fetchReblogs } from 'soapbox/actions/interactions';
+import { fetchStatus } from 'soapbox/actions/statuses';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+const mapStateToProps = (state, props) => {
+ return {
+ accountIds: state.getIn(['user_lists', 'reblogged_by', props.statusId]),
+ };
+};
+
+export default @connect(mapStateToProps)
+@injectIntl
+class ReblogsModal extends React.PureComponent {
+
+ static propTypes = {
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ statusId: PropTypes.string.isRequired,
+ username: PropTypes.string.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accountIds: ImmutablePropTypes.orderedSet,
+ };
+
+ fetchData = () => {
+ const { dispatch, statusId } = this.props;
+
+ dispatch(fetchReblogs(statusId));
+ dispatch(fetchStatus(statusId));
+ }
+
+ componentDidMount() {
+ this.fetchData();
+ }
+
+ onClickClose = () => {
+ this.props.onClose('REBLOGS');
+ };
+
+ render() {
+ const { intl, accountIds } = this.props;
+
+ let body;
+
+ if (!accountIds) {
+ body =
;
+ } else {
+ const emptyMessage =
;
+
+ body = (
+
+ {accountIds.map(id =>
+ ,
+ )}
+
+ );
+ }
+
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js
index 2de0dcbfb..9f13aedf4 100644
--- a/app/soapbox/features/ui/index.js
+++ b/app/soapbox/features/ui/index.js
@@ -56,10 +56,6 @@ import {
HomeTimeline,
Followers,
Following,
- Reblogs,
- Reactions,
- Mentions,
- Favourites,
DirectTimeline,
Conversations,
HashtagTimeline,
@@ -298,10 +294,6 @@ 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 69dd8aff6..c80b53ee9 100644
--- a/app/soapbox/features/ui/util/async-components.js
+++ b/app/soapbox/features/ui/util/async-components.js
@@ -94,22 +94,6 @@ export function Following() {
return import(/* webpackChunkName: "features/following" */'../../following');
}
-export function Reblogs() {
- return import(/* webpackChunkName: "features/reblogs" */'../../reblogs');
-}
-
-export function Reactions() {
- return import(/* webpackChunkName: "features/reactions" */'../../reactions');
-}
-
-export function Mentions() {
- return import(/* webpackChunkName: "features/mentions" */'../../mentions');
-}
-
-export function Favourites() {
- return import(/* webpackChunkName: "features/favourites" */'../../favourites');
-}
-
export function FollowRequests() {
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests');
}
@@ -214,6 +198,22 @@ export function ComponentModal() {
return import(/* webpackChunkName: "features/ui" */'../components/component_modal');
}
+export function ReblogsModal() {
+ return import(/* webpackChunkName: "features/ui" */'../components/reblogs_modal');
+}
+
+export function FavouritesModal() {
+ return import(/* webpackChunkName: "features/ui" */'../components/favourites_modal');
+}
+
+export function ReactionsModal() {
+ return import(/* webpackChunkName: "features/ui" */'../components/reactions_modal');
+}
+
+export function MentionsModal() {
+ return import(/* webpackChunkName: "features/ui" */'../components/mentions_modal');
+}
+
export function ListEditor() {
return import(/* webpackChunkName: "features/list_editor" */'../../list_editor');
}
diff --git a/app/styles/components/modal.scss b/app/styles/components/modal.scss
index b39da7545..4c1a61598 100644
--- a/app/styles/components/modal.scss
+++ b/app/styles/components/modal.scss
@@ -339,7 +339,10 @@
.confirmation-modal,
.report-modal,
.actions-modal,
-.mute-modal {
+.mute-modal,
+.reactions-modal,
+.reblogs-modal,
+.mentions-modal {
position: relative;
flex-direction: column;
overflow: hidden;
@@ -385,6 +388,16 @@
}
}
+.reactions-modal,
+.reblogs-modal,
+.mentions-modal {
+ height: 80vh;
+
+ .slist {
+ overflow: auto;
+ }
+}
+
.boost-modal__container {
overflow-x: scroll;
padding: 10px;