sforkowany z mirror/soapbox
Merge branch 'friendica' into 'develop'
Friendica dislikes See merge request soapbox-pub/soapbox!2381develop^2
commit
55c0f8d6a1
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Posts: Support posts filtering on recent Mastodon versions
|
- Posts: Support posts filtering on recent Mastodon versions
|
||||||
- Reactions: Support custom emoji reactions
|
- Reactions: Support custom emoji reactions
|
||||||
- Compatbility: Support Mastodon v2 timeline filters.
|
- Compatbility: Support Mastodon v2 timeline filters.
|
||||||
|
- Posts: Support dislikes on Friendica.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Posts: truncate Nostr pubkeys in reply mentions.
|
- Posts: truncate Nostr pubkeys in reply mentions.
|
||||||
|
@ -24,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Profile: fix "load more" button height on account gallery page.
|
- Profile: fix "load more" button height on account gallery page.
|
||||||
- 18n: fixed Chinese language being detected from the browser.
|
- 18n: fixed Chinese language being detected from the browser.
|
||||||
- Conversations: fixed pagination (Mastodon).
|
- Conversations: fixed pagination (Mastodon).
|
||||||
|
- Compatibility: fix version parsing for Friendica.
|
||||||
|
|
||||||
## [3.2.0] - 2023-02-15
|
## [3.2.0] - 2023-02-15
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST';
|
||||||
const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
|
const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
|
||||||
const FAVOURITE_FAIL = 'FAVOURITE_FAIL';
|
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_REQUEST = 'UNREBLOG_REQUEST';
|
||||||
const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
|
const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
|
||||||
const UNREBLOG_FAIL = 'UNREBLOG_FAIL';
|
const UNREBLOG_FAIL = 'UNREBLOG_FAIL';
|
||||||
|
@ -28,6 +32,10 @@ const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
|
||||||
const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
|
const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
|
||||||
const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL';
|
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_REQUEST = 'REBLOGS_FETCH_REQUEST';
|
||||||
const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS';
|
const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS';
|
||||||
const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL';
|
const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL';
|
||||||
|
@ -36,6 +44,10 @@ const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
|
||||||
const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
|
const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
|
||||||
const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
|
const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
|
||||||
|
|
||||||
|
const DISLIKES_FETCH_REQUEST = 'DISLIKES_FETCH_REQUEST';
|
||||||
|
const DISLIKES_FETCH_SUCCESS = 'DISLIKES_FETCH_SUCCESS';
|
||||||
|
const DISLIKES_FETCH_FAIL = 'DISLIKES_FETCH_FAIL';
|
||||||
|
|
||||||
const REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST';
|
const REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST';
|
||||||
const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS';
|
const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS';
|
||||||
const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL';
|
const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL';
|
||||||
|
@ -96,7 +108,7 @@ const unreblog = (status: StatusEntity) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleReblog = (status: StatusEntity) =>
|
const toggleReblog = (status: StatusEntity) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch) => {
|
||||||
if (status.reblogged) {
|
if (status.reblogged) {
|
||||||
dispatch(unreblog(status));
|
dispatch(unreblog(status));
|
||||||
} else {
|
} else {
|
||||||
|
@ -169,7 +181,7 @@ const unfavourite = (status: StatusEntity) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleFavourite = (status: StatusEntity) =>
|
const toggleFavourite = (status: StatusEntity) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch) => {
|
||||||
if (status.favourited) {
|
if (status.favourited) {
|
||||||
dispatch(unfavourite(status));
|
dispatch(unfavourite(status));
|
||||||
} else {
|
} else {
|
||||||
|
@ -215,6 +227,79 @@ const unfavouriteFail = (status: StatusEntity, error: AxiosError) => ({
|
||||||
skipLoading: true,
|
skipLoading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dislike = (status: StatusEntity) =>
|
||||||
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
|
if (!isLoggedIn(getState)) return;
|
||||||
|
|
||||||
|
dispatch(dislikeRequest(status));
|
||||||
|
|
||||||
|
api(getState).post(`/api/friendica/statuses/${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/statuses/${status.get('id')}/undislike`).then(() => {
|
||||||
|
dispatch(undislikeSuccess(status));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(undislikeFail(status, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDislike = (status: StatusEntity) =>
|
||||||
|
(dispatch: AppDispatch) => {
|
||||||
|
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) =>
|
const bookmark = (status: StatusEntity) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
dispatch(bookmarkRequest(status));
|
dispatch(bookmarkRequest(status));
|
||||||
|
@ -351,6 +436,38 @@ const fetchFavouritesFail = (id: string, error: AxiosError) => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fetchDislikes = (id: string) =>
|
||||||
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
|
if (!isLoggedIn(getState)) return;
|
||||||
|
|
||||||
|
dispatch(fetchDislikesRequest(id));
|
||||||
|
|
||||||
|
api(getState).get(`/api/friendica/statuses/${id}/disliked_by`).then(response => {
|
||||||
|
dispatch(importFetchedAccounts(response.data));
|
||||||
|
dispatch(fetchRelationships(response.data.map((item: APIEntity) => item.id)));
|
||||||
|
dispatch(fetchDislikesSuccess(id, response.data));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(fetchDislikesFail(id, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchDislikesRequest = (id: string) => ({
|
||||||
|
type: DISLIKES_FETCH_REQUEST,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchDislikesSuccess = (id: string, accounts: APIEntity[]) => ({
|
||||||
|
type: DISLIKES_FETCH_SUCCESS,
|
||||||
|
id,
|
||||||
|
accounts,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchDislikesFail = (id: string, error: AxiosError) => ({
|
||||||
|
type: DISLIKES_FETCH_FAIL,
|
||||||
|
id,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
const fetchReactions = (id: string) =>
|
const fetchReactions = (id: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
dispatch(fetchReactionsRequest(id));
|
dispatch(fetchReactionsRequest(id));
|
||||||
|
@ -498,18 +615,27 @@ export {
|
||||||
FAVOURITE_REQUEST,
|
FAVOURITE_REQUEST,
|
||||||
FAVOURITE_SUCCESS,
|
FAVOURITE_SUCCESS,
|
||||||
FAVOURITE_FAIL,
|
FAVOURITE_FAIL,
|
||||||
|
DISLIKE_REQUEST,
|
||||||
|
DISLIKE_SUCCESS,
|
||||||
|
DISLIKE_FAIL,
|
||||||
UNREBLOG_REQUEST,
|
UNREBLOG_REQUEST,
|
||||||
UNREBLOG_SUCCESS,
|
UNREBLOG_SUCCESS,
|
||||||
UNREBLOG_FAIL,
|
UNREBLOG_FAIL,
|
||||||
UNFAVOURITE_REQUEST,
|
UNFAVOURITE_REQUEST,
|
||||||
UNFAVOURITE_SUCCESS,
|
UNFAVOURITE_SUCCESS,
|
||||||
UNFAVOURITE_FAIL,
|
UNFAVOURITE_FAIL,
|
||||||
|
UNDISLIKE_REQUEST,
|
||||||
|
UNDISLIKE_SUCCESS,
|
||||||
|
UNDISLIKE_FAIL,
|
||||||
REBLOGS_FETCH_REQUEST,
|
REBLOGS_FETCH_REQUEST,
|
||||||
REBLOGS_FETCH_SUCCESS,
|
REBLOGS_FETCH_SUCCESS,
|
||||||
REBLOGS_FETCH_FAIL,
|
REBLOGS_FETCH_FAIL,
|
||||||
FAVOURITES_FETCH_REQUEST,
|
FAVOURITES_FETCH_REQUEST,
|
||||||
FAVOURITES_FETCH_SUCCESS,
|
FAVOURITES_FETCH_SUCCESS,
|
||||||
FAVOURITES_FETCH_FAIL,
|
FAVOURITES_FETCH_FAIL,
|
||||||
|
DISLIKES_FETCH_REQUEST,
|
||||||
|
DISLIKES_FETCH_SUCCESS,
|
||||||
|
DISLIKES_FETCH_FAIL,
|
||||||
REACTIONS_FETCH_REQUEST,
|
REACTIONS_FETCH_REQUEST,
|
||||||
REACTIONS_FETCH_SUCCESS,
|
REACTIONS_FETCH_SUCCESS,
|
||||||
REACTIONS_FETCH_FAIL,
|
REACTIONS_FETCH_FAIL,
|
||||||
|
@ -546,6 +672,15 @@ export {
|
||||||
unfavouriteRequest,
|
unfavouriteRequest,
|
||||||
unfavouriteSuccess,
|
unfavouriteSuccess,
|
||||||
unfavouriteFail,
|
unfavouriteFail,
|
||||||
|
dislike,
|
||||||
|
undislike,
|
||||||
|
toggleDislike,
|
||||||
|
dislikeRequest,
|
||||||
|
dislikeSuccess,
|
||||||
|
dislikeFail,
|
||||||
|
undislikeRequest,
|
||||||
|
undislikeSuccess,
|
||||||
|
undislikeFail,
|
||||||
bookmark,
|
bookmark,
|
||||||
unbookmark,
|
unbookmark,
|
||||||
toggleBookmark,
|
toggleBookmark,
|
||||||
|
@ -563,6 +698,10 @@ export {
|
||||||
fetchFavouritesRequest,
|
fetchFavouritesRequest,
|
||||||
fetchFavouritesSuccess,
|
fetchFavouritesSuccess,
|
||||||
fetchFavouritesFail,
|
fetchFavouritesFail,
|
||||||
|
fetchDislikes,
|
||||||
|
fetchDislikesRequest,
|
||||||
|
fetchDislikesSuccess,
|
||||||
|
fetchDislikesFail,
|
||||||
fetchReactions,
|
fetchReactions,
|
||||||
fetchReactionsRequest,
|
fetchReactionsRequest,
|
||||||
fetchReactionsSuccess,
|
fetchReactionsSuccess,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { launchChat } from 'soapbox/actions/chats';
|
||||||
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
|
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
|
||||||
import { editEvent } from 'soapbox/actions/events';
|
import { editEvent } from 'soapbox/actions/events';
|
||||||
import { groupBlock, groupDeleteStatus, groupKick } from 'soapbox/actions/groups';
|
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 { openModal } from 'soapbox/actions/modals';
|
||||||
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||||
|
@ -45,6 +45,7 @@ const messages = defineMessages({
|
||||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
||||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be reposted' },
|
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be reposted' },
|
||||||
favourite: { id: 'status.favourite', defaultMessage: 'Like' },
|
favourite: { id: 'status.favourite', defaultMessage: 'Like' },
|
||||||
|
disfavourite: { id: 'status.disfavourite', defaultMessage: 'Disike' },
|
||||||
open: { id: 'status.open', defaultMessage: 'Expand this post' },
|
open: { id: 'status.open', defaultMessage: 'Expand this post' },
|
||||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||||
unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' },
|
unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' },
|
||||||
|
@ -161,6 +162,14 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDislikeClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
|
if (me) {
|
||||||
|
dispatch(toggleDislike(status));
|
||||||
|
} else {
|
||||||
|
onOpenUnauthorizedModal('DISLIKE');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
dispatch(toggleBookmark(status));
|
dispatch(toggleBookmark(status));
|
||||||
};
|
};
|
||||||
|
@ -645,7 +654,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
) : (
|
) : (
|
||||||
<StatusActionButton
|
<StatusActionButton
|
||||||
title={intl.formatMessage(messages.favourite)}
|
title={intl.formatMessage(messages.favourite)}
|
||||||
icon={require('@tabler/icons/heart.svg')}
|
icon={features.dislikes ? require('@tabler/icons/thumb-up.svg') : require('@tabler/icons/heart.svg')}
|
||||||
color='accent'
|
color='accent'
|
||||||
filled
|
filled
|
||||||
onClick={handleFavouriteClick}
|
onClick={handleFavouriteClick}
|
||||||
|
@ -655,6 +664,19 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{features.dislikes && (
|
||||||
|
<StatusActionButton
|
||||||
|
title={intl.formatMessage(messages.disfavourite)}
|
||||||
|
icon={require('@tabler/icons/thumb-down.svg')}
|
||||||
|
color='accent'
|
||||||
|
filled
|
||||||
|
onClick={handleDislikeClick}
|
||||||
|
active={status.disliked}
|
||||||
|
count={status.dislikes_count}
|
||||||
|
text={withLabels ? intl.formatMessage(messages.disfavourite) : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{canShare && (
|
{canShare && (
|
||||||
<StatusActionButton
|
<StatusActionButton
|
||||||
title={intl.formatMessage(messages.share)}
|
title={intl.formatMessage(messages.share)}
|
||||||
|
|
|
@ -46,6 +46,13 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onOpenDislikesModal = (username: string, statusId: string): void => {
|
||||||
|
dispatch(openModal('DISLIKES', {
|
||||||
|
username,
|
||||||
|
statusId,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const onOpenReactionsModal = (username: string, statusId: string): void => {
|
const onOpenReactionsModal = (username: string, statusId: string): void => {
|
||||||
dispatch(openModal('REACTIONS', {
|
dispatch(openModal('REACTIONS', {
|
||||||
username,
|
username,
|
||||||
|
@ -114,6 +121,13 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
||||||
else onOpenFavouritesModal(account.acct, status.id);
|
else onOpenFavouritesModal(account.acct, status.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenDislikesModal: React.EventHandler<React.MouseEvent<HTMLButtonElement>> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!me) onOpenUnauthorizedModal();
|
||||||
|
else onOpenDislikesModal(account.acct, status.id);
|
||||||
|
};
|
||||||
|
|
||||||
const getFavourites = () => {
|
const getFavourites = () => {
|
||||||
if (status.favourites_count) {
|
if (status.favourites_count) {
|
||||||
return (
|
return (
|
||||||
|
@ -130,6 +144,24 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDislikes = () => {
|
||||||
|
const dislikesCount = status.dislikes_count;
|
||||||
|
|
||||||
|
if (dislikesCount) {
|
||||||
|
return (
|
||||||
|
<InteractionCounter count={status.favourites_count} onClick={features.exposableReactions ? handleOpenDislikesModal : undefined}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.interactions.dislikes'
|
||||||
|
defaultMessage='{count, plural, one {Dislike} other {Dislikes}}'
|
||||||
|
values={{ count: dislikesCount }}
|
||||||
|
/>
|
||||||
|
</InteractionCounter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpenReactionsModal = () => {
|
const handleOpenReactionsModal = () => {
|
||||||
if (!me) {
|
if (!me) {
|
||||||
return onOpenUnauthorizedModal();
|
return onOpenUnauthorizedModal();
|
||||||
|
@ -171,6 +203,7 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
||||||
{getReposts()}
|
{getReposts()}
|
||||||
{getQuotes()}
|
{getQuotes()}
|
||||||
{features.emojiReacts ? getEmojiReacts() : getFavourites()}
|
{features.emojiReacts ? getEmojiReacts() : getFavourites()}
|
||||||
|
{getDislikes()}
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
ComposeModal,
|
ComposeModal,
|
||||||
ConfirmationModal,
|
ConfirmationModal,
|
||||||
CryptoDonateModal,
|
CryptoDonateModal,
|
||||||
|
DislikesModal,
|
||||||
EditAnnouncementModal,
|
EditAnnouncementModal,
|
||||||
EditFederationModal,
|
EditFederationModal,
|
||||||
EmbedModal,
|
EmbedModal,
|
||||||
|
@ -59,6 +60,7 @@ const MODAL_COMPONENTS = {
|
||||||
'COMPOSE_EVENT': ComposeEventModal,
|
'COMPOSE_EVENT': ComposeEventModal,
|
||||||
'CONFIRM': ConfirmationModal,
|
'CONFIRM': ConfirmationModal,
|
||||||
'CRYPTO_DONATE': CryptoDonateModal,
|
'CRYPTO_DONATE': CryptoDonateModal,
|
||||||
|
'DISLIKES': DislikesModal,
|
||||||
'EDIT_ANNOUNCEMENT': EditAnnouncementModal,
|
'EDIT_ANNOUNCEMENT': EditAnnouncementModal,
|
||||||
'EDIT_FEDERATION': EditFederationModal,
|
'EDIT_FEDERATION': EditFederationModal,
|
||||||
'EMBED': EmbedModal,
|
'EMBED': EmbedModal,
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { fetchDislikes } from 'soapbox/actions/interactions';
|
||||||
|
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||||
|
import { Modal, Spinner } from 'soapbox/components/ui';
|
||||||
|
import AccountContainer from 'soapbox/containers/account-container';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
interface IDislikesModal {
|
||||||
|
onClose: (type: string) => void
|
||||||
|
statusId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DislikesModal: React.FC<IDislikesModal> = ({ onClose, statusId }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const accountIds = useAppSelector((state) => state.user_lists.disliked_by.get(statusId)?.items);
|
||||||
|
|
||||||
|
const fetchData = () => {
|
||||||
|
dispatch(fetchDislikes(statusId));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClickClose = () => {
|
||||||
|
onClose('DISLIKES');
|
||||||
|
};
|
||||||
|
|
||||||
|
let body;
|
||||||
|
|
||||||
|
if (!accountIds) {
|
||||||
|
body = <Spinner />;
|
||||||
|
} else {
|
||||||
|
const emptyMessage = <FormattedMessage id='empty_column.dislikes' defaultMessage='No one has disliked this post yet. When someone does, they will show up here.' />;
|
||||||
|
|
||||||
|
body = (
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey='dislikes'
|
||||||
|
emptyMessage={emptyMessage}
|
||||||
|
className='max-w-full'
|
||||||
|
itemClassName='pb-3'
|
||||||
|
>
|
||||||
|
{accountIds.map(id =>
|
||||||
|
<AccountContainer key={id} id={id} />,
|
||||||
|
)}
|
||||||
|
</ScrollableList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={<FormattedMessage id='column.dislikes' defaultMessage='Dislikes' />}
|
||||||
|
onClose={onClickClose}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DislikesModal;
|
|
@ -15,7 +15,7 @@ const messages = defineMessages({
|
||||||
|
|
||||||
interface IUnauthorizedModal {
|
interface IUnauthorizedModal {
|
||||||
/** Unauthorized action type. */
|
/** Unauthorized action type. */
|
||||||
action: 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'POLL_VOTE' | 'JOIN'
|
action: 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'DISLIKE' | 'POLL_VOTE' | 'JOIN'
|
||||||
/** Close event handler. */
|
/** Close event handler. */
|
||||||
onClose: (modalType: string) => void
|
onClose: (modalType: string) => void
|
||||||
/** ActivityPub ID of the account OR status being acted upon. */
|
/** ActivityPub ID of the account OR status being acted upon. */
|
||||||
|
@ -86,6 +86,9 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
||||||
} else if (action === 'FAVOURITE') {
|
} else if (action === 'FAVOURITE') {
|
||||||
header = <FormattedMessage id='remote_interaction.favourite_title' defaultMessage='Like a post remotely' />;
|
header = <FormattedMessage id='remote_interaction.favourite_title' defaultMessage='Like a post remotely' />;
|
||||||
button = <FormattedMessage id='remote_interaction.favourite' defaultMessage='Proceed to like' />;
|
button = <FormattedMessage id='remote_interaction.favourite' defaultMessage='Proceed to like' />;
|
||||||
|
} else if (action === 'DISLIKE') {
|
||||||
|
header = <FormattedMessage id='remote_interaction.dislike_title' defaultMessage='Dislike a post remotely' />;
|
||||||
|
button = <FormattedMessage id='remote_interaction.dislike' defaultMessage='Proceed to dislike' />;
|
||||||
} else if (action === 'POLL_VOTE') {
|
} else if (action === 'POLL_VOTE') {
|
||||||
header = <FormattedMessage id='remote_interaction.poll_vote_title' defaultMessage='Vote in a poll remotely' />;
|
header = <FormattedMessage id='remote_interaction.poll_vote_title' defaultMessage='Vote in a poll remotely' />;
|
||||||
button = <FormattedMessage id='remote_interaction.poll_vote' defaultMessage='Proceed to vote' />;
|
button = <FormattedMessage id='remote_interaction.poll_vote' defaultMessage='Proceed to vote' />;
|
||||||
|
|
|
@ -190,6 +190,10 @@ export function FavouritesModal() {
|
||||||
return import(/* webpackChunkName: "features/ui" */'../components/modals/favourites-modal');
|
return import(/* webpackChunkName: "features/ui" */'../components/modals/favourites-modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DislikesModal() {
|
||||||
|
return import(/* webpackChunkName: "features/ui" */'../components/modals/dislikes-modal');
|
||||||
|
}
|
||||||
|
|
||||||
export function ReactionsModal() {
|
export function ReactionsModal() {
|
||||||
return import(/* webpackChunkName: "features/ui" */'../components/modals/reactions-modal');
|
return import(/* webpackChunkName: "features/ui" */'../components/modals/reactions-modal');
|
||||||
}
|
}
|
||||||
|
|
|
@ -316,6 +316,7 @@
|
||||||
"column.developers.service_worker": "Service Worker",
|
"column.developers.service_worker": "Service Worker",
|
||||||
"column.direct": "Direct messages",
|
"column.direct": "Direct messages",
|
||||||
"column.directory": "Browse profiles",
|
"column.directory": "Browse profiles",
|
||||||
|
"column.dislikes": "Dislikes",
|
||||||
"column.domain_blocks": "Hidden domains",
|
"column.domain_blocks": "Hidden domains",
|
||||||
"column.edit_profile": "Edit profile",
|
"column.edit_profile": "Edit profile",
|
||||||
"column.event_map": "Event location",
|
"column.event_map": "Event location",
|
||||||
|
@ -661,6 +662,7 @@
|
||||||
"empty_column.bookmarks": "You don't have any bookmarks yet. When you add one, it will show up here.",
|
"empty_column.bookmarks": "You don't have any bookmarks yet. When you add one, it will show up here.",
|
||||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||||
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||||
|
"empty_column.dislikes": "No one has disliked this post yet. When someone does, they will show up here.",
|
||||||
"empty_column.domain_blocks": "There are no hidden domains yet.",
|
"empty_column.domain_blocks": "There are no hidden domains yet.",
|
||||||
"empty_column.event_participant_requests": "There are no pending event participation requests.",
|
"empty_column.event_participant_requests": "There are no pending event participation requests.",
|
||||||
"empty_column.event_participants": "No one joined this event yet. When someone does, they will show up here.",
|
"empty_column.event_participants": "No one joined this event yet. When someone does, they will show up here.",
|
||||||
|
@ -1214,6 +1216,8 @@
|
||||||
"remote_instance.pin_host": "Pin {host}",
|
"remote_instance.pin_host": "Pin {host}",
|
||||||
"remote_instance.unpin_host": "Unpin {host}",
|
"remote_instance.unpin_host": "Unpin {host}",
|
||||||
"remote_interaction.account_placeholder": "Enter your username@domain you want to act from",
|
"remote_interaction.account_placeholder": "Enter your username@domain you want to act from",
|
||||||
|
"remote_interaction.dislike": "Proceed to dislike",
|
||||||
|
"remote_interaction.dislike_title": "Dislike a post remotely",
|
||||||
"remote_interaction.divider": "or",
|
"remote_interaction.divider": "or",
|
||||||
"remote_interaction.event_join": "Proceed to join",
|
"remote_interaction.event_join": "Proceed to join",
|
||||||
"remote_interaction.event_join_title": "Join an event remotely",
|
"remote_interaction.event_join_title": "Join an event remotely",
|
||||||
|
@ -1397,6 +1401,7 @@
|
||||||
"status.detailed_status": "Detailed conversation view",
|
"status.detailed_status": "Detailed conversation view",
|
||||||
"status.direct": "Direct message @{name}",
|
"status.direct": "Direct message @{name}",
|
||||||
"status.disabled_replies.group_membership": "Only group members can reply",
|
"status.disabled_replies.group_membership": "Only group members can reply",
|
||||||
|
"status.disfavourite": "Disike",
|
||||||
"status.edit": "Edit",
|
"status.edit": "Edit",
|
||||||
"status.embed": "Embed post",
|
"status.embed": "Embed post",
|
||||||
"status.external": "View post on {domain}",
|
"status.external": "View post on {domain}",
|
||||||
|
@ -1406,6 +1411,7 @@
|
||||||
"status.group_mod_block": "Block @{name} from group",
|
"status.group_mod_block": "Block @{name} from group",
|
||||||
"status.group_mod_delete": "Delete post from group",
|
"status.group_mod_delete": "Delete post from group",
|
||||||
"status.group_mod_kick": "Kick @{name} from group",
|
"status.group_mod_kick": "Kick @{name} from group",
|
||||||
|
"status.interactions.dislikes": "{count, plural, one {Dislike} other {Dislikes}}",
|
||||||
"status.interactions.favourites": "{count, plural, one {Like} other {Likes}}",
|
"status.interactions.favourites": "{count, plural, one {Like} other {Likes}}",
|
||||||
"status.interactions.quotes": "{count, plural, one {Quote} other {Quotes}}",
|
"status.interactions.quotes": "{count, plural, one {Quote} other {Quotes}}",
|
||||||
"status.interactions.reblogs": "{count, plural, one {Repost} other {Reposts}}",
|
"status.interactions.reblogs": "{count, plural, one {Repost} other {Reposts}}",
|
||||||
|
|
|
@ -46,6 +46,8 @@ export const StatusRecord = ImmutableRecord({
|
||||||
card: null as Card | null,
|
card: null as Card | null,
|
||||||
content: '',
|
content: '',
|
||||||
created_at: '',
|
created_at: '',
|
||||||
|
dislikes_count: 0,
|
||||||
|
disliked: false,
|
||||||
edited_at: null as string | null,
|
edited_at: null as string | null,
|
||||||
emojis: ImmutableList<Emoji>(),
|
emojis: ImmutableList<Emoji>(),
|
||||||
favourited: false,
|
favourited: false,
|
||||||
|
@ -217,6 +219,16 @@ const normalizeFilterResults = (status: ImmutableMap<string, any>) =>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const normalizeDislikes = (status: ImmutableMap<string, any>) => {
|
||||||
|
if (status.get('friendica')) {
|
||||||
|
return status
|
||||||
|
.set('dislikes_count', status.getIn(['friendica', 'dislikes_count']))
|
||||||
|
.set('disliked', status.getIn(['friendica', 'disliked']));
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
};
|
||||||
|
|
||||||
export const normalizeStatus = (status: Record<string, any>) => {
|
export const normalizeStatus = (status: Record<string, any>) => {
|
||||||
return StatusRecord(
|
return StatusRecord(
|
||||||
ImmutableMap(fromJS(status)).withMutations(status => {
|
ImmutableMap(fromJS(status)).withMutations(status => {
|
||||||
|
@ -232,6 +244,7 @@ export const normalizeStatus = (status: Record<string, any>) => {
|
||||||
normalizeEvent(status);
|
normalizeEvent(status);
|
||||||
fixContent(status);
|
fixContent(status);
|
||||||
normalizeFilterResults(status);
|
normalizeFilterResults(status);
|
||||||
|
normalizeDislikes(status);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,9 @@ import {
|
||||||
FAVOURITE_REQUEST,
|
FAVOURITE_REQUEST,
|
||||||
UNFAVOURITE_REQUEST,
|
UNFAVOURITE_REQUEST,
|
||||||
FAVOURITE_FAIL,
|
FAVOURITE_FAIL,
|
||||||
|
DISLIKE_REQUEST,
|
||||||
|
UNDISLIKE_REQUEST,
|
||||||
|
DISLIKE_FAIL,
|
||||||
} from '../actions/interactions';
|
} from '../actions/interactions';
|
||||||
import {
|
import {
|
||||||
STATUS_CREATE_REQUEST,
|
STATUS_CREATE_REQUEST,
|
||||||
|
@ -204,6 +207,25 @@ const simulateFavourite = (
|
||||||
return state.set(statusId, updatedStatus);
|
return state.set(statusId, updatedStatus);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Simulate dislike/undislike of status for optimistic interactions */
|
||||||
|
const simulateDislike = (
|
||||||
|
state: State,
|
||||||
|
statusId: string,
|
||||||
|
disliked: boolean,
|
||||||
|
): State => {
|
||||||
|
const status = state.get(statusId);
|
||||||
|
if (!status) return state;
|
||||||
|
|
||||||
|
const delta = disliked ? +1 : -1;
|
||||||
|
|
||||||
|
const updatedStatus = status.merge({
|
||||||
|
disliked,
|
||||||
|
dislikes_count: Math.max(0, status.dislikes_count + delta),
|
||||||
|
});
|
||||||
|
|
||||||
|
return state.set(statusId, updatedStatus);
|
||||||
|
};
|
||||||
|
|
||||||
interface Translation {
|
interface Translation {
|
||||||
content: string
|
content: string
|
||||||
detected_source_language: string
|
detected_source_language: string
|
||||||
|
@ -238,6 +260,10 @@ export default function statuses(state = initialState, action: AnyAction): State
|
||||||
return simulateFavourite(state, action.status.id, true);
|
return simulateFavourite(state, action.status.id, true);
|
||||||
case UNFAVOURITE_REQUEST:
|
case UNFAVOURITE_REQUEST:
|
||||||
return simulateFavourite(state, action.status.id, false);
|
return simulateFavourite(state, action.status.id, false);
|
||||||
|
case DISLIKE_REQUEST:
|
||||||
|
return simulateDislike(state, action.status.id, true);
|
||||||
|
case UNDISLIKE_REQUEST:
|
||||||
|
return simulateDislike(state, action.status.id, false);
|
||||||
case EMOJI_REACT_REQUEST:
|
case EMOJI_REACT_REQUEST:
|
||||||
return state
|
return state
|
||||||
.updateIn(
|
.updateIn(
|
||||||
|
@ -252,6 +278,8 @@ export default function statuses(state = initialState, action: AnyAction): State
|
||||||
);
|
);
|
||||||
case FAVOURITE_FAIL:
|
case FAVOURITE_FAIL:
|
||||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false);
|
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false);
|
||||||
|
case DISLIKE_FAIL:
|
||||||
|
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'disliked'], false);
|
||||||
case REBLOG_REQUEST:
|
case REBLOG_REQUEST:
|
||||||
return state.setIn([action.status.get('id'), 'reblogged'], true);
|
return state.setIn([action.status.get('id'), 'reblogged'], true);
|
||||||
case REBLOG_FAIL:
|
case REBLOG_FAIL:
|
||||||
|
|
|
@ -60,6 +60,7 @@ import {
|
||||||
import {
|
import {
|
||||||
REBLOGS_FETCH_SUCCESS,
|
REBLOGS_FETCH_SUCCESS,
|
||||||
FAVOURITES_FETCH_SUCCESS,
|
FAVOURITES_FETCH_SUCCESS,
|
||||||
|
DISLIKES_FETCH_SUCCESS,
|
||||||
REACTIONS_FETCH_SUCCESS,
|
REACTIONS_FETCH_SUCCESS,
|
||||||
} from 'soapbox/actions/interactions';
|
} from 'soapbox/actions/interactions';
|
||||||
import {
|
import {
|
||||||
|
@ -107,6 +108,7 @@ export const ReducerRecord = ImmutableRecord({
|
||||||
following: ImmutableMap<string, List>(),
|
following: ImmutableMap<string, List>(),
|
||||||
reblogged_by: ImmutableMap<string, List>(),
|
reblogged_by: ImmutableMap<string, List>(),
|
||||||
favourited_by: ImmutableMap<string, List>(),
|
favourited_by: ImmutableMap<string, List>(),
|
||||||
|
disliked_by: ImmutableMap<string, List>(),
|
||||||
reactions: ImmutableMap<string, ReactionList>(),
|
reactions: ImmutableMap<string, ReactionList>(),
|
||||||
follow_requests: ListRecord(),
|
follow_requests: ListRecord(),
|
||||||
blocks: ListRecord(),
|
blocks: ListRecord(),
|
||||||
|
@ -128,7 +130,7 @@ type ReactionList = ReturnType<typeof ReactionListRecord>;
|
||||||
type ParticipationRequest = ReturnType<typeof ParticipationRequestRecord>;
|
type ParticipationRequest = ReturnType<typeof ParticipationRequestRecord>;
|
||||||
type ParticipationRequestList = ReturnType<typeof ParticipationRequestListRecord>;
|
type ParticipationRequestList = ReturnType<typeof ParticipationRequestListRecord>;
|
||||||
type Items = ImmutableOrderedSet<string>;
|
type Items = ImmutableOrderedSet<string>;
|
||||||
type NestedListPath = ['followers' | 'following' | 'reblogged_by' | 'favourited_by' | 'reactions' | 'pinned' | 'birthday_reminders' | 'familiar_followers' | 'event_participations' | 'event_participation_requests' | 'membership_requests' | 'group_blocks', string];
|
type NestedListPath = ['followers' | 'following' | 'reblogged_by' | 'favourited_by' | 'disliked_by' | 'reactions' | 'pinned' | 'birthday_reminders' | 'familiar_followers' | 'event_participations' | 'event_participation_requests' | 'membership_requests' | 'group_blocks', string];
|
||||||
type ListPath = ['follow_requests' | 'blocks' | 'mutes' | 'directory'];
|
type ListPath = ['follow_requests' | 'blocks' | 'mutes' | 'directory'];
|
||||||
|
|
||||||
const normalizeList = (state: State, path: NestedListPath | ListPath, accounts: APIEntity[], next?: string | null) => {
|
const normalizeList = (state: State, path: NestedListPath | ListPath, accounts: APIEntity[], next?: string | null) => {
|
||||||
|
@ -173,6 +175,8 @@ export default function userLists(state = ReducerRecord(), action: AnyAction) {
|
||||||
return normalizeList(state, ['reblogged_by', action.id], action.accounts);
|
return normalizeList(state, ['reblogged_by', action.id], action.accounts);
|
||||||
case FAVOURITES_FETCH_SUCCESS:
|
case FAVOURITES_FETCH_SUCCESS:
|
||||||
return normalizeList(state, ['favourited_by', action.id], action.accounts);
|
return normalizeList(state, ['favourited_by', action.id], action.accounts);
|
||||||
|
case DISLIKES_FETCH_SUCCESS:
|
||||||
|
return normalizeList(state, ['disliked_by', action.id], action.accounts);
|
||||||
case REACTIONS_FETCH_SUCCESS:
|
case REACTIONS_FETCH_SUCCESS:
|
||||||
return state.setIn(['reactions', action.id], ReactionListRecord({
|
return state.setIn(['reactions', action.id], ReactionListRecord({
|
||||||
items: ImmutableOrderedSet<Reaction>(action.reactions.map(({ accounts, ...reaction }: APIEntity) => ReactionRecord({
|
items: ImmutableOrderedSet<Reaction>(action.reactions.map(({ accounts, ...reaction }: APIEntity) => ReactionRecord({
|
||||||
|
|
|
@ -343,6 +343,13 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
v.software === PLEROMA && gte(v.version, '0.9.9'),
|
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.3.0'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ability to edit profile information.
|
* Ability to edit profile information.
|
||||||
* @see PATCH /api/v1/accounts/update_credentials
|
* @see PATCH /api/v1/accounts/update_credentials
|
||||||
|
@ -721,6 +728,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
* @see POST /api/v1/statuses
|
* @see POST /api/v1/statuses
|
||||||
*/
|
*/
|
||||||
quotePosts: any([
|
quotePosts: any([
|
||||||
|
v.software === FRIENDICA && gte(v.version, '2023.3.0'),
|
||||||
v.software === PLEROMA && [REBASED, AKKOMA].includes(v.build!) && gte(v.version, '2.4.50'),
|
v.software === PLEROMA && [REBASED, AKKOMA].includes(v.build!) && gte(v.version, '2.4.50'),
|
||||||
features.includes('quote_posting'),
|
features.includes('quote_posting'),
|
||||||
instance.feature_quote === true,
|
instance.feature_quote === true,
|
||||||
|
|
Ładowanie…
Reference in New Issue