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