diff --git a/app/soapbox/actions/interactions.ts b/app/soapbox/actions/interactions.ts index 9e43d0f40..eb046098e 100644 --- a/app/soapbox/actions/interactions.ts +++ b/app/soapbox/actions/interactions.ts @@ -20,6 +20,10 @@ const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST'; const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; +const DISLIKE_REQUEST = 'DISLIKE_REQUEST'; +const DISLIKE_SUCCESS = 'DISLIKE_SUCCESS'; +const DISLIKE_FAIL = 'DISLIKE_FAIL'; + const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST'; const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS'; const UNREBLOG_FAIL = 'UNREBLOG_FAIL'; @@ -28,6 +32,10 @@ const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST'; const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS'; const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL'; +const UNDISLIKE_REQUEST = 'UNDISLIKE_REQUEST'; +const UNDISLIKE_SUCCESS = 'UNDISLIKE_SUCCESS'; +const UNDISLIKE_FAIL = 'UNDISLIKE_FAIL'; + const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST'; const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS'; const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL'; @@ -215,6 +223,79 @@ const unfavouriteFail = (status: StatusEntity, error: AxiosError) => ({ skipLoading: true, }); +const dislike = (status: StatusEntity) => + (dispatch: AppDispatch, getState: () => RootState) => { + if (!isLoggedIn(getState)) return; + + dispatch(dislikeRequest(status)); + + api(getState).post(`/api/friendica/${status.get('id')}/dislike`).then(function() { + dispatch(dislikeSuccess(status)); + }).catch(function(error) { + dispatch(dislikeFail(status, error)); + }); + }; + +const undislike = (status: StatusEntity) => + (dispatch: AppDispatch, getState: () => RootState) => { + if (!isLoggedIn(getState)) return; + + dispatch(undislikeRequest(status)); + + api(getState).post(`/api/friendica/${status.get('id')}/undislike`).then(() => { + dispatch(undislikeSuccess(status)); + }).catch(error => { + dispatch(undislikeFail(status, error)); + }); + }; + +const toggleDislike = (status: StatusEntity) => + (dispatch: AppDispatch, getState: () => RootState) => { + if (status.disliked) { + dispatch(undislike(status)); + } else { + dispatch(dislike(status)); + } + }; + +const dislikeRequest = (status: StatusEntity) => ({ + type: DISLIKE_REQUEST, + status: status, + skipLoading: true, +}); + +const dislikeSuccess = (status: StatusEntity) => ({ + type: DISLIKE_SUCCESS, + status: status, + skipLoading: true, +}); + +const dislikeFail = (status: StatusEntity, error: AxiosError) => ({ + type: DISLIKE_FAIL, + status: status, + error: error, + skipLoading: true, +}); + +const undislikeRequest = (status: StatusEntity) => ({ + type: UNDISLIKE_REQUEST, + status: status, + skipLoading: true, +}); + +const undislikeSuccess = (status: StatusEntity) => ({ + type: UNDISLIKE_SUCCESS, + status: status, + skipLoading: true, +}); + +const undislikeFail = (status: StatusEntity, error: AxiosError) => ({ + type: UNDISLIKE_FAIL, + status: status, + error: error, + skipLoading: true, +}); + const bookmark = (status: StatusEntity) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(bookmarkRequest(status)); @@ -498,12 +579,18 @@ export { FAVOURITE_REQUEST, FAVOURITE_SUCCESS, FAVOURITE_FAIL, + DISLIKE_REQUEST, + DISLIKE_SUCCESS, + DISLIKE_FAIL, UNREBLOG_REQUEST, UNREBLOG_SUCCESS, UNREBLOG_FAIL, UNFAVOURITE_REQUEST, UNFAVOURITE_SUCCESS, UNFAVOURITE_FAIL, + UNDISLIKE_REQUEST, + UNDISLIKE_SUCCESS, + UNDISLIKE_FAIL, REBLOGS_FETCH_REQUEST, REBLOGS_FETCH_SUCCESS, REBLOGS_FETCH_FAIL, @@ -546,6 +633,15 @@ export { unfavouriteRequest, unfavouriteSuccess, unfavouriteFail, + dislike, + undislike, + toggleDislike, + dislikeRequest, + dislikeSuccess, + dislikeFail, + undislikeRequest, + undislikeSuccess, + undislikeFail, bookmark, unbookmark, toggleBookmark, diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index f36bb34e9..2123c44e9 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -8,7 +8,7 @@ import { launchChat } from 'soapbox/actions/chats'; import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose'; import { editEvent } from 'soapbox/actions/events'; import { groupBlock, groupDeleteStatus, groupKick } from 'soapbox/actions/groups'; -import { toggleBookmark, toggleFavourite, togglePin, toggleReblog } from 'soapbox/actions/interactions'; +import { toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog } from 'soapbox/actions/interactions'; import { openModal } from 'soapbox/actions/modals'; import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation'; import { initMuteModal } from 'soapbox/actions/mutes'; @@ -45,6 +45,7 @@ const messages = defineMessages({ cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be reposted' }, favourite: { id: 'status.favourite', defaultMessage: 'Like' }, + disfavourite: { id: 'status.disfavourite', defaultMessage: 'Disike' }, open: { id: 'status.open', defaultMessage: 'Expand this post' }, bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' }, @@ -161,6 +162,14 @@ const StatusActionBar: React.FC = ({ } }; + const handleDislikeClick: React.EventHandler = (e) => { + if (me) { + dispatch(toggleDislike(status)); + } else { + onOpenUnauthorizedModal('DISLIKE'); + } + }; + const handleBookmarkClick: React.EventHandler = (e) => { dispatch(toggleBookmark(status)); }; @@ -645,7 +654,7 @@ const StatusActionBar: React.FC = ({ ) : ( = ({ /> )} + {features.dislikes && ( + + )} + {canShare && ( void /** ActivityPub ID of the account OR status being acted upon. */ @@ -86,6 +86,9 @@ const UnauthorizedModal: React.FC = ({ action, onClose, acco } else if (action === 'FAVOURITE') { header = ; button = ; + } else if (action === 'DISLIKE') { + header = ; + button = ; } else if (action === 'POLL_VOTE') { header = ; button = ; diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 3de7f6f4f..b60d927c8 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -51,6 +51,7 @@ export const StatusRecord = ImmutableRecord({ favourited: false, favourites_count: 0, filtered: ImmutableList(), + friendica: ImmutableMap(), group: null as EmbeddedEntity, in_reply_to_account_id: null as string | null, in_reply_to_id: null as string | null, diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index b9d0e2210..483cccc68 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -343,6 +343,13 @@ const getInstanceFeatures = (instance: Instance) => { v.software === PLEROMA && gte(v.version, '0.9.9'), ]), + /** + * @see POST /api/friendica/statuses/:id/dislike + * @see POST /api/friendica/statuses/:id/undislike + * @see GET /api/friendica/statuses/:id/disliked_by + */ + dislikes: v.software === FRIENDICA && gte(v.version, '2023.03.0'), + /** * Ability to edit profile information. * @see PATCH /api/v1/accounts/update_credentials