Support reactions on Mastodon forks, support api_versions, minor fixes

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-09-13 13:05:06 +02:00
parent 0a7bdbb7d0
commit 8794c293aa
14 changed files with 220 additions and 145 deletions

View file

@ -217,7 +217,7 @@ class PlApiClient {
#accessToken?: string;
#instance: Instance = instanceSchema.parse({});
public request = request.bind(this) as typeof request;
public features: Features = getFeatures();
public features: Features = getFeatures(this.#instance);
#socket?: {
listen: (listener: any, stream?: string) => number;
unlisten: (listener: any) => void;
@ -2015,14 +2015,34 @@ class PlApiClient {
/**
* Get an object of emoji to account mappings with accounts that reacted to the post
*
* Requires features{@link Features['emojiReacts']}.
* Requires features{@link Features['emojiReactsList']}.
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromastatusesidreactions}
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromastatusesidreactionsemoji}
*/
getStatusReactions: async (statusId: string, emoji?: string) => {
const response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions${emoji ? `/${emoji}` : ''}`);
const apiVersions = this.#instance.api_versions;
return filteredArray(emojiReactionSchema).parse(response.json);
let response;
if (apiVersions['pleroma_emoji_reactions.pleroma.pl-api'] >= 1) {
response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions${emoji ? `/${emoji}` : ''}`);
} else if (apiVersions['emoji_reaction.fedibird.pl-api'] >= 1) {
response = await this.request(`/api/v1/statuses/${statusId}/emoji_reactioned_by`);
response.json = response.json?.reduce((acc: Array<any>, cur: any) => {
if (emoji && cur.name !== emoji) return acc;
const existing = acc.find(reaction => reaction.name === cur.name);
if (existing) {
existing.accounts.push(cur.account);
existing.account_ids.push(cur.account.id);
existing.count += 1;
} else acc.push({ count: 1, accounts: [cur.account], account_ids: [cur.account.id], ...cur });
return acc;
}, []);
}
return filteredArray(emojiReactionSchema).parse(response?.json || []);
},
/**
@ -2033,7 +2053,14 @@ class PlApiClient {
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#put-apiv1pleromastatusesidreactionsemoji}
*/
createStatusReaction: async (statusId: string, emoji: string) => {
const response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions/${emoji}`, { method: 'PUT' });
const apiVersions = this.#instance.api_versions;
let response;
if (apiVersions['pleroma_emoji_reactions.pleroma.pl-api'] >= 1) {
response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions/${encodeURIComponent(emoji)}`, { method: 'PUT' });
} else {
response = await this.request(`/api/v1/statuses/${statusId}/react/${encodeURIComponent(emoji)}`, { method: 'POST' });
}
return statusSchema.parse(response.json);
},
@ -2045,7 +2072,14 @@ class PlApiClient {
* @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#delete-apiv1pleromastatusesidreactionsemoji}
*/
deleteStatusReaction: async (statusId: string, emoji: string) => {
const response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions/${emoji}`, { method: 'DELETE' });
const apiVersions = this.#instance.api_versions;
let response;
if (apiVersions['pleroma_emoji_reactions.pleroma.pl-api'] >= 1) {
response = await this.request(`/api/v1/pleroma/statuses/${statusId}/reactions/${emoji}`, { method: 'DELETE' });
} else {
response = await this.request(`/api/v1/statuses/${statusId}/unreact/${encodeURIComponent(emoji)}`, { method: 'POST' });
}
return statusSchema.parse(response.json);
},

View file

@ -12,17 +12,12 @@ const filterBadges = (tags?: string[]) =>
const preprocessAccount = (account: any) => {
if (!account?.acct) return null;
const username = account.username || account.acct.split('@')[0];
return {
username: account.username || account.acct.split('@')[0],
display_name: account.display_name.trim() || account.username,
roles: account.roles?.length ? account.roles : filterBadges(account.pleroma?.tags),
username,
avatar_static: account.avatar_static || account.avatar,
header_static: account.header_static || account.header,
source: account.source
? { ...(pick(account.pleroma?.source || {}, [
'show_role', 'no_rich_text', 'discoverable', 'actor_type', 'show_birthday',
])), ...account.source }
: undefined,
local: typeof account.pleroma?.is_local === 'boolean' ? account.pleroma.is_local : account.acct.split('@')[1] === undefined,
discoverable: account.discoverable || account.pleroma?.source?.discoverable,
verified: account.verified || account.pleroma?.tags?.includes('verified'),
@ -54,6 +49,13 @@ const preprocessAccount = (account: any) => {
...(pick(account.other_settings || {}), ['birthday', 'location']),
__meta: pick(account, ['pleroma', 'source']),
...account,
display_name: account.display_name.trim() || username,
roles: account.roles?.length ? account.roles : filterBadges(account.pleroma?.tags),
source: account.source
? { ...(pick(account.pleroma?.source || {}, [
'show_role', 'no_rich_text', 'discoverable', 'actor_type', 'show_birthday',
])), ...account.source }
: undefined,
};
};

View file

@ -8,19 +8,26 @@ const baseEmojiReactionSchema = z.object({
me: z.boolean().catch(false),
name: emojiSchema,
url: z.literal(undefined).catch(undefined),
static_url: z.literal(undefined).catch(undefined),
accounts: filteredArray(accountSchema),
account_ids: filteredArray(z.string()).catch([]),
});
const customEmojiReactionSchema = baseEmojiReactionSchema.extend({
name: z.string(),
url: z.string().url(),
static_url: z.string().url(),
});
/**
* Pleroma emoji reaction.
* @see {@link https://docs.pleroma.social/backend/development/API/differences_in_mastoapi_responses/#statuses}
*/
const emojiReactionSchema = baseEmojiReactionSchema.or(customEmojiReactionSchema);
const emojiReactionSchema = z.preprocess((reaction: any) => reaction ? {
static_url: reaction.url,
account_ids: reaction.accounts?.map((account: any) => account?.id),
...reaction,
} : null, baseEmojiReactionSchema.or(customEmojiReactionSchema));
type EmojiReaction = z.infer<typeof emojiReactionSchema>;

View file

@ -5,6 +5,83 @@ import { accountSchema } from './account';
import { ruleSchema } from './rule';
import { coerceObject, filteredArray, mimeSchema } from './utils';
const getApiVersions = (instance: any) => ({
...Object.fromEntries(instance.pleroma?.metadata?.features?.map((feature: string) => {
let string = `${feature}.pleroma.pl-api`;
if (string.startsWith('pleroma:') || string.startsWith('pleroma_')) string = string.slice(8);
if (string.startsWith('akkoma:')) string = string.slice(7);
if (string.startsWith('pl:')) string = string.slice(3);
return [string, 1];
}) || []),
...Object.fromEntries(instance.fedibird_capabilities?.map((feature: string) => [`${feature}.fedibird.pl-api`, 1]) || []),
...instance.api_versions,
});
const instanceV1ToV2 = (data: any) => {
const {
approval_required,
configuration,
contact_account,
description,
description_limit,
email,
max_media_attachments,
max_toot_chars,
poll_limits,
pleroma,
registrations,
short_description,
thumbnail,
uri,
urls,
...instance
} = instanceV1Schema.parse(data);
return {
...instance,
account_domain: instance.account_domain || uri,
configuration: {
...configuration,
polls: {
...configuration.polls,
max_characters_per_option: configuration.polls.max_characters_per_option ?? poll_limits.max_option_chars ?? 25,
max_expiration: configuration.polls.max_expiration ?? poll_limits.max_expiration ?? 2629746,
max_options: configuration.polls.max_options ?? poll_limits.max_options ?? 4,
min_expiration: configuration.polls.min_expiration ?? poll_limits.min_expiration ?? 300,
},
statuses: {
...configuration.statuses,
max_characters: configuration.statuses.max_characters ?? max_toot_chars ?? 500,
max_media_attachments: configuration.statuses.max_media_attachments ?? max_media_attachments,
},
urls: {
streaming: urls.streaming_api,
},
vapid: {
public_key: pleroma.vapid_public_key,
},
},
contact: {
account: contact_account,
email: email,
},
description: short_description || description,
domain: uri,
pleroma: {
...pleroma,
metadata: {
...pleroma.metadata,
description_limit,
},
},
registrations: {
approval_required: approval_required,
enabled: registrations,
},
thumbnail: { url: thumbnail },
};
};
const fixVersion = (version: string) => {
// Handle Mastodon release candidates
if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) {
@ -47,18 +124,18 @@ const configurationSchema = coerceObject({
video_size_limit: z.number().optional().catch(undefined),
}),
polls: coerceObject({
max_characters_per_option: z.number().optional().catch(undefined),
max_expiration: z.number().optional().catch(undefined),
max_options: z.number().optional().catch(undefined),
min_expiration: z.number().optional().catch(undefined),
max_characters_per_option: z.number().catch(25),
max_expiration: z.number().catch(2629746),
max_options: z.number().catch(4),
min_expiration: z.number().catch(300),
}),
reactions: coerceObject({
max_reactions: z.number().catch(0),
}),
statuses: coerceObject({
characters_reserved_per_url: z.number().optional().catch(undefined),
max_characters: z.number().optional().catch(undefined),
max_media_attachments: z.number().optional().catch(undefined),
max_characters: z.number().catch(500),
max_media_attachments: z.number().catch(4),
}),
translation: coerceObject({
@ -67,6 +144,9 @@ const configurationSchema = coerceObject({
urls: coerceObject({
streaming: z.string().url().optional().catch(undefined),
}),
vapid: coerceObject({
public_key: z.string().catch(''),
}),
});
const contactSchema = coerceObject({
@ -214,69 +294,14 @@ const instanceSchema = z.preprocess((data: any) => {
data.version = `0.0.0 (compatible; GoToSocial ${data.version})`;
}
if (data.domain) return { account_domain: data.domain, ...data };
const apiVersions = getApiVersions(data);
const {
approval_required,
configuration,
contact_account,
description,
description_limit,
email,
max_media_attachments,
max_toot_chars,
poll_limits,
pleroma,
registrations,
short_description,
thumbnail,
uri,
urls,
...instance
} = instanceV1Schema.parse(data);
if (data.domain) return { account_domain: data.domain, ...data, api_versions: apiVersions };
return {
...instance,
account_domain: instance.account_domain || uri,
configuration: {
...configuration,
polls: {
...configuration.polls,
max_characters_per_option: configuration.polls.max_characters_per_option ?? poll_limits.max_option_chars ?? 25,
max_expiration: configuration.polls.max_expiration ?? poll_limits.max_expiration ?? 2629746,
max_options: configuration.polls.max_options ?? poll_limits.max_options ?? 4,
min_expiration: configuration.polls.min_expiration ?? poll_limits.min_expiration ?? 300,
},
statuses: {
...configuration.statuses,
max_characters: configuration.statuses.max_characters ?? max_toot_chars ?? 500,
max_media_attachments: configuration.statuses.max_media_attachments ?? max_media_attachments,
},
urls: {
streaming: urls.streaming_api,
},
},
contact: {
account: contact_account,
email: email,
},
description: short_description || description,
domain: uri,
pleroma: {
...pleroma,
metadata: {
...pleroma.metadata,
description_limit,
},
},
registrations: {
approval_required: approval_required,
enabled: registrations,
},
thumbnail: { url: thumbnail },
};
return instanceV1ToV2({ api_versions: apiVersions, ...data });
}, coerceObject({
account_domain: z.string().catch(''),
api_versions: z.record(z.number()).catch({}),
configuration: configurationSchema,
contact: contactSchema,
description: z.string().catch(''),
@ -292,30 +317,11 @@ const instanceSchema = z.preprocess((data: any) => {
title: z.string().catch(''),
usage: usageSchema,
version: z.string().catch('0.0.0'),
}).transform(({ configuration, ...instance }) => {
}).transform((instance) => {
const version = fixVersion(instance.version);
const polls = {
...configuration.polls,
max_characters_per_option: configuration.polls.max_characters_per_option ?? 25,
max_expiration: configuration.polls.max_expiration ?? 2629746,
max_options: configuration.polls.max_options ?? 4,
min_expiration: configuration.polls.min_expiration ?? 300,
};
const statuses = {
...configuration.statuses,
max_characters: configuration.statuses.max_characters ?? 500,
max_media_attachments: configuration.statuses.max_media_attachments ?? 4,
};
return {
...instance,
configuration: {
...configuration,
polls,
statuses,
},
version,
};
}));

View file

@ -13,6 +13,7 @@ const baseNotificationSchema = z.object({
account: accountSchema,
created_at: dateSchema,
id: z.string(),
group_key: z.string(),
type: z.string(),
is_muted: z.boolean().optional().catch(undefined),
@ -68,6 +69,7 @@ const eventParticipationRequestNotificationSchema = baseNotificationSchema.exten
/** @see {@link https://docs.joinmastodon.org/entities/Notification/} */
const notificationSchema: z.ZodType<Notification> = z.preprocess((notification: any) => ({
group_key: `ungrouped-${notification.id}`,
...pick(notification.pleroma || {}, ['is_muted', 'is_seen']),
...notification,
type: notification.type === 'pleroma:report'

View file

@ -101,10 +101,9 @@ const REBASED = 'soapbox';
const UNRELEASED = 'unreleased';
/** Parse features for the given instance */
const getFeatures = (instance?: Instance) => {
const v = parseVersion(instance?.version || '');
const features = instance?.pleroma.metadata.features || [];
const federation = !!instance?.pleroma.metadata.federation.enabled;
const getFeatures = (instance: Instance) => {
const v = parseVersion(instance.version || '');
const federation = !!instance.pleroma.metadata.federation.enabled;
return {
version: v,
@ -231,11 +230,11 @@ const getFeatures = (instance?: Instance) => {
*/
bites: any([
v.software === TOKI,
features.includes('pleroma:bites'),
instance.api_versions['bites.pleroma.pl-api'] >= 1,
]),
/** Whether people who blocked you are visible through the API. */
blockersVisible: features.includes('blockers_visible'),
blockersVisible: instance.api_versions['blockers_visible.pleroma.pl-api'] >= 1,
/**
* Can group bookmarks in folders.
@ -244,7 +243,7 @@ const getFeatures = (instance?: Instance) => {
* @see PATCH /api/v1/pleroma/bookmark_folders/:id
* @see DELETE /api/v1/pleroma/bookmark_folders/:id
*/
bookmarkFolders: features.includes('pleroma:bookmark_folders'),
bookmarkFolders: instance.api_versions['bookmark_folders.pleroma.pl-api'] >= 1,
/**
* Can bookmark statuses.
@ -278,13 +277,13 @@ const getFeatures = (instance?: Instance) => {
* Can display a timeline of statuses from instances selected by instance admin.
* @see GET /api/v1/timelines/bubble
*/
bubbleTimeline: features.includes('bubble_timeline'),
bubbleTimeline: instance.api_versions['bubble_timeline.pleroma.pl-api'] >= 1,
/**
* Pleroma chats API.
* @see {@link https://docs.pleroma.social/backend/development/API/chats/}
*/
chats: features.includes('pleroma_chat_messages'),
chats: instance.api_versions['chat_messages.pleroma.pl-api'] >= 1,
/**
* Ability to delete a chat.
@ -353,8 +352,8 @@ const getFeatures = (instance?: Instance) => {
* Ability to add non-standard reactions to a status.
*/
customEmojiReacts: any([
features.includes('pleroma_custom_emoji_reactions'),
features.includes('custom_emoji_reactions'),
instance.api_versions['custom_emoji_reactions.pleroma.pl-api'] >= 1,
instance.api_versions['custom_emoji_reactions.pleroma.pl-api'] >= 1,
v.software === PLEROMA && gte(v.version, '2.6.0'),
]),
@ -403,7 +402,7 @@ const getFeatures = (instance?: Instance) => {
v.software === MASTODON,
v.software === MITRA,
v.software === TAKAHE && gte(v.version, '0.8.0'),
features.includes('editing'),
instance.api_versions['editing.pleroma.pl-api'] >= 1,
]),
/**
@ -414,7 +413,7 @@ const getFeatures = (instance?: Instance) => {
* @see GET /api/v1/pleroma/admin/email_list/unsubscribers.csv
* @see GET /api/v1/pleroma/admin/email_list/combined.csv
*/
emailList: features.includes('email_list'),
emailList: instance.api_versions['email_list.pleroma.pl-api'] >= 1,
/**
* Ability to embed posts on external sites.
@ -425,17 +424,25 @@ const getFeatures = (instance?: Instance) => {
/**
* Ability to add emoji reactions to a status.
* @see PUT /api/v1/pleroma/statuses/:id/reactions/:emoji
* @see GET /api/v1/pleroma/statuses/:id/reactions/:emoji?
* @see DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji
*/
emojiReacts: v.software === PLEROMA,
/**
* Ability to add emoji reactions to a status available in Mastodon forks.
*
* @see POST /v1/statuses/:id/react/:emoji
* @see POST /v1/statuses/:id/unreact/:emoji
*/
emojiReactsMastodon: instance ? instance.configuration.reactions.max_reactions > 0 : false,
emojiReacts: any([
v.software === PLEROMA,
instance ? instance.configuration.reactions.max_reactions > 0 : false,
]),
/**
* @see GET /api/v1/pleroma/statuses/:id/reactions/:emoji?
*
* @see GET /api/v1/statuses/:id/emoji_reactioned_by
*/
emojiReactsList: any([
v.software === PLEROMA,
instance.api_versions['emoji_reaction.fedibird.pl-api'] >= 1,
]),
/**
* Ability to create and perform actions on events.
@ -451,7 +458,7 @@ const getFeatures = (instance?: Instance) => {
* @see GET /api/v1/pleroma/events/:id/ics
* @see GET /api/v1/pleroma/search/location
*/
events: features.includes('events'),
events: instance.api_versions['events.pleroma.pl-api'] >= 1,
/** Whether to allow exporting follows/blocks/mutes to CSV by paginating the API. */
exportData: true,
@ -465,7 +472,7 @@ const getFeatures = (instance?: Instance) => {
v.software === MASTODON,
v.software === TAKAHE && gte(v.version, '0.6.1'),
v.software === TOKI,
features.includes('exposable_reactions'),
instance.api_versions['exposable_reactions.pleroma.pl-api'] >= 1,
]),
/**
@ -582,7 +589,7 @@ const getFeatures = (instance?: Instance) => {
* @see POST /api/v1/admin/groups/:group_id/unsuspend
* @see DELETE /api/v1/admin/groups/:group_id
*/
groups: features.includes('pleroma:groups'),
groups: instance.api_versions['pleroma:groups.pleroma.pl-api'] >= 1,
/**
* Can hide follows/followers lists and counts.
@ -649,13 +656,13 @@ const getFeatures = (instance?: Instance) => {
/**
* Server-side status language detection.
*/
languageDetection: features.includes('pleroma:language_detection'),
languageDetection: instance.api_versions['language_detection.pleroma.pl-api'] >= 1,
/**
* Can translate multiple statuses in a single request.
* @see POST /api/v1/pl/statuses/translate
*/
lazyTranslations: features.includes('pl:translations'),
lazyTranslations: instance.api_versions['translations.pl.pl-api'] >= 1,
/**
* Can create, view, and manage lists.
@ -744,7 +751,7 @@ const getFeatures = (instance?: Instance) => {
* Ability to include multiple language variants for a post.
* @see POST /api/v1/statuses
*/
multiLanguage: features.includes('pleroma:multi_language'),
multiLanguage: instance.api_versions['multi_language.pleroma.pl-api'] >= 1,
/**
* Ability to hide notifications from people you don't follow.
@ -898,7 +905,7 @@ const getFeatures = (instance?: Instance) => {
v.software === FRIENDICA,
v.software === MASTODON,
v.software === MITRA,
features.includes('profile_directory'),
instance.api_versions['profile_directory.pleroma.pl-api'] >= 1,
]),
/**
@ -943,7 +950,7 @@ const getFeatures = (instance?: Instance) => {
quotePosts: any([
v.software === FRIENDICA && gte(v.version, '2023.3.0'),
v.software === PLEROMA && [REBASED, AKKOMA].includes(v.build!) && gte(v.version, '2.5.0'),
features.includes('quote_posting'),
instance.api_versions['quote_posting.pleroma.pl-api'] >= 1,
instance?.feature_quote === true,
]),
@ -1082,7 +1089,7 @@ const getFeatures = (instance?: Instance) => {
v.software === FRIENDICA,
v.software === ICESHRIMP,
v.software === MASTODON,
features.includes('v2_suggestions'),
instance.api_versions['v2_suggestions.pleroma.pl-api'] >= 1,
]),
/**
@ -1101,7 +1108,7 @@ const getFeatures = (instance?: Instance) => {
v.software === FRIENDICA,
v.software === ICESHRIMP,
v.software === MASTODON,
features.includes('v2_suggestions'),
instance.api_versions['v2_suggestions.pleroma.pl-api'] >= 1,
]),
/**
@ -1109,8 +1116,8 @@ const getFeatures = (instance?: Instance) => {
* @see POST /api/v1/statuses/:id/translate
*/
translations: any([
features.includes('translation'),
features.includes('akkoma:machine_translation'),
instance.api_versions['translation.pleroma.pl-api'] >= 1,
instance.api_versions['machine_translation.akkoma.pl-api'] >= 1,
instance?.configuration.translation.enabled,
]),

View file

@ -1,6 +1,6 @@
{
"name": "pl-api",
"version": "0.0.29",
"version": "0.0.30",
"type": "module",
"homepage": "https://github.com/mkljczk/pl-fe/tree/fork/packages/pl-api",
"repository": {

View file

@ -134,7 +134,7 @@
"multiselect-react-dropdown": "^2.0.25",
"object-to-formdata": "^4.5.1",
"path-browserify": "^1.0.1",
"pl-api": "^0.0.29",
"pl-api": "^0.0.30",
"postcss": "^8.4.29",
"process": "^0.11.10",
"punycode": "^2.1.1",

View file

@ -86,6 +86,14 @@ const unEmojiReactFail = (statusId: string, emoji: string, error: unknown) => ({
error,
});
type EmojiReactsAction =
| ReturnType<typeof emojiReactRequest>
| ReturnType<typeof emojiReactSuccess>
| ReturnType<typeof emojiReactFail>
| ReturnType<typeof unEmojiReactRequest>
| ReturnType<typeof unEmojiReactSuccess>
| ReturnType<typeof unEmojiReactFail>
export {
EMOJI_REACT_REQUEST,
EMOJI_REACT_SUCCESS,
@ -101,4 +109,5 @@ export {
unEmojiReactRequest,
unEmojiReactSuccess,
unEmojiReactFail,
type EmojiReactsAction,
};

View file

@ -418,7 +418,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
return menu;
}
if (status.emoji_reactions.length && features.exposableReactions) {
if (status.emoji_reactions.length && features.exposableReactions && features.emojiReactsList) {
menu.push({
text: intl.formatMessage(messages.viewReactions),
action: handleOpenReactionsModal,
@ -755,7 +755,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
/>
)}
{me && expandable && (features.emojiReacts || features.emojiReactsMastodon) && (
{me && expandable && (features.emojiReacts) && (
<EmojiPickerDropdown
onPickEmoji={handlePickEmoji}
theme={statusActionButtonTheme}

View file

@ -8,7 +8,7 @@ import { emojiReact, unEmojiReact } from 'pl-fe/actions/emoji-reacts';
import { openModal } from 'pl-fe/actions/modals';
import EmojiPickerDropdown from 'pl-fe/features/emoji/containers/emoji-picker-dropdown-container';
import unicodeMapping from 'pl-fe/features/emoji/mapping';
import { useAppDispatch, useLoggedIn, useSettings } from 'pl-fe/hooks';
import { useAppDispatch, useFeatures, useLoggedIn, useSettings } from 'pl-fe/hooks';
import AnimatedNumber from './animated-number';
import { Emoji, HStack, Icon, Text } from './ui';
@ -36,9 +36,10 @@ interface IStatusReaction {
const StatusReaction: React.FC<IStatusReaction> = ({ reaction, status, obfuscate, unauthenticated }) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const features = useFeatures();
const bind = useLongPress((e) => {
if (e.type !== 'touchstart') return;
if (!features.emojiReactsList || e.type !== 'touchstart') return;
e.stopPropagation();
@ -52,6 +53,7 @@ const StatusReaction: React.FC<IStatusReaction> = ({ reaction, status, obfuscate
e.stopPropagation();
if (unauthenticated) {
if (!features.emojiReactsList) return;
dispatch(openModal('REACTIONS', { statusId: status.id, reaction: reaction.name }));
} else if (reaction.me) {
dispatch(unEmojiReact(status, reaction.name));

View file

@ -4,7 +4,12 @@ import omit from 'lodash/omit';
import { normalizeStatus, normalizeTranslation, Status as StatusRecord } from 'pl-fe/normalizers';
import { simulateEmojiReact, simulateUnEmojiReact } from 'pl-fe/utils/emoji-reacts';
import { EMOJI_REACT_REQUEST, UNEMOJI_REACT_REQUEST } from '../actions/emoji-reacts';
import {
EMOJI_REACT_FAIL,
EMOJI_REACT_REQUEST,
UNEMOJI_REACT_REQUEST,
type EmojiReactsAction,
} from '../actions/emoji-reacts';
import {
EVENT_JOIN_REQUEST,
EVENT_JOIN_FAIL,
@ -181,7 +186,7 @@ const deleteTranslation = (state: State, statusId: string) => state.deleteIn([st
const initialState: State = ImmutableMap();
const statuses = (state = initialState, action: AnyAction): State => {
const statuses = (state = initialState, action: EmojiReactsAction | AnyAction): State => {
switch (action.type) {
case STATUS_IMPORT:
return importStatus(state, action.status);
@ -206,6 +211,7 @@ const statuses = (state = initialState, action: AnyAction): State => {
emojiReacts => simulateEmojiReact(emojiReacts as any, action.emoji, action.custom),
);
case UNEMOJI_REACT_REQUEST:
case EMOJI_REACT_FAIL:
return state
.updateIn(
[action.statusId, 'emoji_reactions'],

View file

@ -60,7 +60,7 @@ const getAuthUserUrl = (state: RootState) => {
/** Get the VAPID public key. */
const getVapidKey = (state: RootState) =>
state.auth.app?.vapid_key || state.instance.pleroma.vapid_public_key;
state.auth.app?.vapid_key || state.instance.configuration.vapid.public_key;
const getMeUrl = (state: RootState) => selectOwnAccount(state)?.url;

View file

@ -8240,10 +8240,10 @@ pkg-types@^1.0.3:
mlly "^1.2.0"
pathe "^1.1.0"
pl-api@^0.0.29:
version "0.0.29"
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.0.29.tgz#59005a6051932c8b0defb552b1ecda15e1241462"
integrity sha512-NDtii+PkJ/pCJHbVx0guAPtfxcXwMYPc7Bm6517I0fJtqhuY3c+QXcaL6DXuQV+OP7+WVcdVIYqIYhhgzCMkXQ==
pl-api@^0.0.30:
version "0.0.30"
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.0.30.tgz#4b093d9336604e3f1d38d790d9d2908fc36bfa99"
integrity sha512-ZTrdcrLPhXQIJSK9smBoN96VngEIrFugmQvwKJPZLJfMXme+kXsV2X4GbNahSByYaoDRFZy6I4vgKicMQlSxig==
dependencies:
blurhash "^2.0.5"
http-link-header "^1.1.3"