Add ability to feature and unfeature hashtags from web UI (#34490)

This commit is contained in:
Eugen Rochko 2025-04-28 13:44:01 +02:00 committed by GitHub
parent 926c67c648
commit 40157e063d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 74 additions and 13 deletions

View File

@ -1,4 +1,10 @@
import { apiGetTag, apiFollowTag, apiUnfollowTag } from 'mastodon/api/tags'; import {
apiGetTag,
apiFollowTag,
apiUnfollowTag,
apiFeatureTag,
apiUnfeatureTag,
} from 'mastodon/api/tags';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
export const fetchHashtag = createDataLoadingThunk( export const fetchHashtag = createDataLoadingThunk(
@ -15,3 +21,13 @@ export const unfollowHashtag = createDataLoadingThunk(
'tags/unfollow', 'tags/unfollow',
({ tagId }: { tagId: string }) => apiUnfollowTag(tagId), ({ tagId }: { tagId: string }) => apiUnfollowTag(tagId),
); );
export const featureHashtag = createDataLoadingThunk(
'tags/feature',
({ tagId }: { tagId: string }) => apiFeatureTag(tagId),
);
export const unfeatureHashtag = createDataLoadingThunk(
'tags/unfeature',
({ tagId }: { tagId: string }) => apiUnfeatureTag(tagId),
);

View File

@ -10,6 +10,12 @@ export const apiFollowTag = (tagId: string) =>
export const apiUnfollowTag = (tagId: string) => export const apiUnfollowTag = (tagId: string) =>
apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/unfollow`); apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/unfollow`);
export const apiFeatureTag = (tagId: string) =>
apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/feature`);
export const apiUnfeatureTag = (tagId: string) =>
apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/unfeature`);
export const apiGetFollowedTags = async (url?: string) => { export const apiGetFollowedTags = async (url?: string) => {
const response = await api().request<ApiHashtagJSON[]>({ const response = await api().request<ApiHashtagJSON[]>({
method: 'GET', method: 'GET',

View File

@ -10,4 +10,5 @@ export interface ApiHashtagJSON {
url: string; url: string;
history: [ApiHistoryJSON, ...ApiHistoryJSON[]]; history: [ApiHistoryJSON, ...ApiHistoryJSON[]];
following?: boolean; following?: boolean;
featuring?: boolean;
} }

View File

@ -9,6 +9,8 @@ import {
fetchHashtag, fetchHashtag,
followHashtag, followHashtag,
unfollowHashtag, unfollowHashtag,
featureHashtag,
unfeatureHashtag,
} from 'mastodon/actions/tags_typed'; } from 'mastodon/actions/tags_typed';
import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
import { Button } from 'mastodon/components/button'; import { Button } from 'mastodon/components/button';
@ -28,6 +30,11 @@ const messages = defineMessages({
id: 'hashtag.admin_moderation', id: 'hashtag.admin_moderation',
defaultMessage: 'Open moderation interface for #{name}', defaultMessage: 'Open moderation interface for #{name}',
}, },
feature: { id: 'hashtag.feature', defaultMessage: 'Feature on profile' },
unfeature: {
id: 'hashtag.unfeature',
defaultMessage: "Don't feature on profile",
},
}); });
const usesRenderer = (displayNumber: React.ReactNode, pluralReady: number) => ( const usesRenderer = (displayNumber: React.ReactNode, pluralReady: number) => (
@ -88,22 +95,51 @@ export const HashtagHeader: React.FC<{
}, [dispatch, tagId, setTag]); }, [dispatch, tagId, setTag]);
const menu = useMemo(() => { const menu = useMemo(() => {
const tmp = []; const arr = [];
if ( if (tag && signedIn) {
tag && const handleFeature = () => {
signedIn && if (tag.featuring) {
(permissions & PERMISSION_MANAGE_TAXONOMIES) === void dispatch(unfeatureHashtag({ tagId })).then((result) => {
PERMISSION_MANAGE_TAXONOMIES if (isFulfilled(result)) {
) { setTag(result.payload);
tmp.push({ }
text: intl.formatMessage(messages.adminModeration, { name: tag.id }),
href: `/admin/tags/${tag.id}`, return '';
});
} else {
void dispatch(featureHashtag({ tagId })).then((result) => {
if (isFulfilled(result)) {
setTag(result.payload);
}
return '';
});
}
};
arr.push({
text: intl.formatMessage(
tag.featuring ? messages.unfeature : messages.feature,
),
action: handleFeature,
}); });
arr.push(null);
if (
(permissions & PERMISSION_MANAGE_TAXONOMIES) ===
PERMISSION_MANAGE_TAXONOMIES
) {
arr.push({
text: intl.formatMessage(messages.adminModeration, { name: tagId }),
href: `/admin/tags/${tag.id}`,
});
}
} }
return tmp; return arr;
}, [signedIn, permissions, intl, tag]); }, [setTag, dispatch, tagId, signedIn, permissions, intl, tag]);
const handleFollow = useCallback(() => { const handleFollow = useCallback(() => {
if (!signedIn || !tag) { if (!signedIn || !tag) {

View File

@ -405,8 +405,10 @@
"hashtag.counter_by_accounts": "{count, plural, one {{counter} participant} other {{counter} participants}}", "hashtag.counter_by_accounts": "{count, plural, one {{counter} participant} other {{counter} participants}}",
"hashtag.counter_by_uses": "{count, plural, one {{counter} post} other {{counter} posts}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} post} other {{counter} posts}}",
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} post} other {{counter} posts}} today", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} post} other {{counter} posts}} today",
"hashtag.feature": "Feature on profile",
"hashtag.follow": "Follow hashtag", "hashtag.follow": "Follow hashtag",
"hashtag.mute": "Mute #{hashtag}", "hashtag.mute": "Mute #{hashtag}",
"hashtag.unfeature": "Don't feature on profile",
"hashtag.unfollow": "Unfollow hashtag", "hashtag.unfollow": "Unfollow hashtag",
"hashtags.and_other": "…and {count, plural, other {# more}}", "hashtags.and_other": "…and {count, plural, other {# more}}",
"hints.profiles.followers_may_be_missing": "Followers for this profile may be missing.", "hints.profiles.followers_may_be_missing": "Followers for this profile may be missing.",