diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
index d2fe8851f6..415cf7bf10 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
+++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx
@@ -4,12 +4,17 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { Button } from 'mastodon/components/button';
import { ShortNumber } from 'mastodon/components/short_number';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
+import { withIdentity } from 'mastodon/identity_context';
+import { PERMISSION_MANAGE_TAXONOMIES } from 'mastodon/permissions';
const messages = defineMessages({
followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' },
+ adminModeration: { id: 'hashtag.admin_moderation', defaultMessage: 'Open moderation interface for #{name}' },
});
const usesRenderer = (displayNumber, pluralReady) => (
@@ -45,11 +50,18 @@ const usesTodayRenderer = (displayNumber, pluralReady) => (
/>
);
-export const HashtagHeader = injectIntl(({ tag, intl, disabled, onClick }) => {
+export const HashtagHeader = withIdentity(injectIntl(({ tag, intl, disabled, onClick, identity }) => {
if (!tag) {
return null;
}
+ const { signedIn, permissions } = identity;
+ const menu = [];
+
+ if (signedIn && (permissions & PERMISSION_MANAGE_TAXONOMIES) === PERMISSION_MANAGE_TAXONOMIES ) {
+ menu.push({ text: intl.formatMessage(messages.adminModeration, { name: tag.get("name") }), href: `/admin/tags/${tag.get('id')}` });
+ }
+
const [uses, people] = tag.get('history').reduce((arr, day) => [arr[0] + day.get('uses') * 1, arr[1] + day.get('accounts') * 1], [0, 0]);
const dividingCircle = {' ยท '};
@@ -57,7 +69,10 @@ export const HashtagHeader = injectIntl(({ tag, intl, disabled, onClick }) => {
#{tag.get('name')}
-
+
+ { menu.length > 0 && }
+
+
@@ -69,7 +84,7 @@ export const HashtagHeader = injectIntl(({ tag, intl, disabled, onClick }) => {
);
-});
+}));
HashtagHeader.propTypes = {
tag: ImmutablePropTypes.map,
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 6a44856837..71ab620226 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -363,6 +363,7 @@
"footer.status": "Status",
"generic.saved": "Saved",
"getting_started.heading": "Getting started",
+ "hashtag.admin_moderation": "Open moderation interface for #{name}",
"hashtag.column_header.tag_mode.all": "and {additional}",
"hashtag.column_header.tag_mode.any": "or {additional}",
"hashtag.column_header.tag_mode.none": "without {additional}",
diff --git a/app/javascript/mastodon/permissions.ts b/app/javascript/mastodon/permissions.ts
index 8f015610ea..d7695d2f5c 100644
--- a/app/javascript/mastodon/permissions.ts
+++ b/app/javascript/mastodon/permissions.ts
@@ -1,5 +1,6 @@
export const PERMISSION_INVITE_USERS = 0x0000000000010000;
export const PERMISSION_MANAGE_USERS = 0x0000000000000400;
+export const PERMISSION_MANAGE_TAXONOMIES = 0x0000000000000100;
export const PERMISSION_MANAGE_FEDERATION = 0x0000000000000020;
export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 0bb5b00412..df3a770648 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -8000,7 +8000,7 @@ noscript {
}
.icon-button {
- border: 1px solid lighten($ui-base-color, 12%);
+ border: 1px solid var(--background-border-color);
border-radius: 4px;
box-sizing: content-box;
padding: 5px;
@@ -9952,6 +9952,30 @@ noscript {
line-height: 33px;
font-weight: 700;
}
+
+ &__buttons {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .button {
+ flex-shrink: 1;
+ white-space: nowrap;
+ min-width: 80px;
+ }
+
+ .icon-button {
+ border: 1px solid var(--background-border-color);
+ border-radius: 4px;
+ box-sizing: content-box;
+ padding: 5px;
+
+ .icon {
+ width: 24px;
+ height: 24px;
+ }
+ }
+ }
}
}
diff --git a/app/serializers/rest/tag_serializer.rb b/app/serializers/rest/tag_serializer.rb
index 017b572718..a2bcb5fd1f 100644
--- a/app/serializers/rest/tag_serializer.rb
+++ b/app/serializers/rest/tag_serializer.rb
@@ -3,10 +3,14 @@
class REST::TagSerializer < ActiveModel::Serializer
include RoutingHelper
- attributes :name, :url, :history
+ attributes :id, :name, :url, :history
attribute :following, if: :current_user?
+ def id
+ object.id.to_s
+ end
+
def url
tag_url(object)
end