diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx
index 524dbb927b..fd137115cb 100644
--- a/app/javascript/mastodon/components/dropdown_menu.jsx
+++ b/app/javascript/mastodon/components/dropdown_menu.jsx
@@ -170,6 +170,7 @@ class Dropdown extends PureComponent {
title: PropTypes.string,
disabled: PropTypes.bool,
scrollable: PropTypes.bool,
+ active: PropTypes.bool,
status: ImmutablePropTypes.map,
isUserTouching: PropTypes.func,
onOpen: PropTypes.func.isRequired,
@@ -284,6 +285,7 @@ class Dropdown extends PureComponent {
children,
renderItem,
renderHeader,
+ active,
} = this.props;
const open = this.state.id === openDropdownId;
@@ -299,7 +301,7 @@ class Dropdown extends PureComponent {
icon={!open ? icon : 'close'}
iconComponent={iconComponent}
title={title}
- active={open}
+ active={open || active}
disabled={disabled}
size={size}
onClick={this.handleClick}
diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx
index 5730a5a8e5..9eadc0f921 100644
--- a/app/javascript/mastodon/components/status_action_bar.jsx
+++ b/app/javascript/mastodon/components/status_action_bar.jsx
@@ -29,7 +29,7 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import DropdownMenuContainer from '../containers/dropdown_menu_container';
import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
-import { enableEmojiReaction , bookmarkCategoryNeeded, simpleTimelineMenu, me, isHideItem } from '../initial_state';
+import { enableEmojiReaction , bookmarkCategoryNeeded, simpleTimelineMenu, me, isHideItem, boostMenu, boostModal } from '../initial_state';
import { IconButton } from './icon_button';
@@ -48,6 +48,7 @@ const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' },
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
+ reblog_with_detail: { id: 'status.reblog_with_detail', defaultMessage: 'Boost with visibility' },
cancelReblog: { id: 'status.cancel_reblog', defaultMessage: 'Unboost' },
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
@@ -345,16 +346,16 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push(null);
}
- if (status.get('visibility_ex') !== 'limited') {
- menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancelReblog : messages.reblog), action: this.handleReblogForceModalClick });
- }
+ if (!boostMenu) {
+ menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancelReblog : messages.reblog), action: this.handleReblogForceModalClick, tag: 'reblog' });
- if (publicStatus) {
- if (allowQuote) {
- menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
+ if (publicStatus) {
+ if (allowQuote) {
+ menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote, tag: 'reblog' });
+ }
+
+ menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' });
}
-
- menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
}
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal });
@@ -428,6 +429,24 @@ class StatusActionBar extends ImmutablePureComponent {
}
}
+ let reblogMenu = [];
+
+ if (boostMenu) {
+ reblogMenu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancelReblog : messages.reblog), action: this.handleReblogClick });
+
+ if (!status.get('reblogged') && !boostModal) {
+ reblogMenu.push({ text: intl.formatMessage(messages.reblog_with_detail), action: this.handleReblogForceModalClick });
+ }
+
+ if (publicStatus) {
+ if (allowQuote) {
+ reblogMenu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
+ }
+
+ reblogMenu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
+ }
+ }
+
let replyIcon;
let replyIconComponent;
let replyTitle;
@@ -458,6 +477,8 @@ class StatusActionBar extends ImmutablePureComponent {
} else {
reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
+ menu = menu.filter((item) => !item?.tag || item.tag !== 'reblog');
+ reblogMenu = [];
}
const filterButton = this.props.onFilter && (
@@ -484,7 +505,22 @@ class StatusActionBar extends ImmutablePureComponent {
return (
-
+ {reblogMenu.length === 0 ? (
+
+ ) : (
+
+ )}
{emojiPickerDropdown}
diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx
index 2ef4e7c9a8..b6077e8aec 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.jsx
+++ b/app/javascript/mastodon/features/status/components/action_bar.jsx
@@ -28,7 +28,7 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { IconButton } from '../../../components/icon_button';
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
-import { enableEmojiReaction , bookmarkCategoryNeeded, me, isHideItem } from '../../../initial_state';
+import { enableEmojiReaction , bookmarkCategoryNeeded, me, isHideItem, boostMenu, boostModal } from '../../../initial_state';
import EmojiPickerDropdown from '../../compose/containers/emoji_picker_dropdown_container';
const messages = defineMessages({
@@ -40,6 +40,7 @@ const messages = defineMessages({
mentions: { id: 'status.mentions', defaultMessage: 'Mentioned users' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
+ reblog_with_detail: { id: 'status.reblog_with_detail', defaultMessage: 'Boost with visibility' },
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
@@ -272,17 +273,18 @@ class ActionBar extends PureComponent {
if (signedIn) {
menu.push(null);
- if (status.get('visibility_ex') !== 'limited') {
- menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogForceModalClick });
- }
+ if (!boostMenu) {
+ menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogForceModalClick, tag: 'reblog' });
- if (publicStatus) {
- if (allowQuote) {
- menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
+ if (publicStatus) {
+ if (allowQuote) {
+ menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote, tag: 'reblog' });
+ }
+
+ menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' });
}
-
- menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
}
+
menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick });
if (writtenByMe) {
@@ -344,6 +346,24 @@ class ActionBar extends PureComponent {
}
}
+ let reblogMenu = [];
+
+ if (boostMenu) {
+ reblogMenu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogClick });
+
+ if (!status.get('reblogged') && !boostModal) {
+ reblogMenu.push({ text: intl.formatMessage(messages.reblog_with_detail), action: this.handleReblogForceModalClick });
+ }
+
+ if (publicStatus) {
+ if (allowQuote) {
+ reblogMenu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
+ }
+
+ reblogMenu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
+ }
+ }
+
let replyIcon;
let replyIconComponent;
@@ -371,6 +391,8 @@ class ActionBar extends PureComponent {
} else {
reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
+ menu = menu.filter((item) => !item?.tag || item.tag !== 'reblog');
+ reblogMenu = [];
}
const emojiReactionAvailableServer = !isHideItem('emoji_reaction_unavailable_server') || account.get('emoji_reaction_available_server');
@@ -393,7 +415,23 @@ class ActionBar extends PureComponent {
return (
-
+ {reblogMenu.length === 0 ? (
+
+ ) : (
+
+
+
+ )}
{emojiPickerDropdown}
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 4176d854b1..800fffc649 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -56,7 +56,8 @@
* @property {string} repository
* @property {boolean} search_enabled
* @property {boolean} trends_enabled
- * @property {string} simple_timeline_menu
+ * @property {boolean} simple_timeline_menu
+ * @property {boolean} boost_menu
* @property {boolean} single_user_mode
* @property {string} source_url
* @property {string} streaming_api_base_url
@@ -145,6 +146,7 @@ export const searchEnabled = getMeta('search_enabled');
export const trendsEnabled = getMeta('trends_enabled');
export const showTrends = getMeta('show_trends');
export const simpleTimelineMenu = getMeta('simple_timeline_menu');
+export const boostMenu = getMeta('boost_menu');
export const singleUserMode = getMeta('single_user_mode');
export const source_url = getMeta('source_url');
export const timelinePreview = getMeta('timeline_preview');
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 7384938187..7a0ae537bd 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -892,6 +892,7 @@
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",
+ "status.reblog_with_detail": "Boost with visibility",
"status.reblogged_by": "{name} boosted",
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
"status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 52dfb0e195..857141aabc 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -832,6 +832,7 @@
"status.block": "@{name}さんをブロック",
"status.bookmark": "ブックマーク",
"status.bookmark_category": "ブックマーク分類を指定",
+ "status.cancel_reblog": "ブースト解除",
"status.cancel_reblog_private": "ブースト解除",
"status.cannot_reblog": "この投稿はブーストできません",
"status.copy": "投稿へのリンクをコピー",
@@ -875,6 +876,7 @@
"status.read_more": "もっと見る",
"status.reblog": "ブースト",
"status.reblog_private": "ブースト",
+ "status.reblog_with_detail": "公開範囲を指定してブースト",
"status.reblogged_by": "{name}さんがブースト",
"status.reblogs.empty": "まだ誰もブーストしていません。ブーストされるとここに表示されます。",
"status.redraft": "削除して下書きに戻す",
diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb
index 92098010cd..1b5899ac4e 100644
--- a/app/models/concerns/user/has_settings.rb
+++ b/app/models/concerns/user/has_settings.rb
@@ -55,6 +55,10 @@ module User::HasSettings
settings['web.simple_timeline_menu']
end
+ def setting_boost_menu
+ settings['web.boost_menu']
+ end
+
def setting_default_sensitive
settings['default_sensitive']
end
diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb
index 109f516173..ffe2f44e26 100644
--- a/app/models/user_settings.rb
+++ b/app/models/user_settings.rb
@@ -69,6 +69,7 @@ class UserSettings
setting :display_media_expand, default: true
setting :auto_play, default: true
setting :simple_timeline_menu, default: false
+ setting :boost_menu, default: false
setting :show_quote_in_home, default: true
setting :show_quote_in_public, default: false
setting :show_relationships, default: true
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 37eabed2d0..f5ffa246d6 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -36,6 +36,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:show_trends] = Setting.trends && object_account_user.setting_trends
store[:bookmark_category_needed] = object_account_user.setting_bookmark_category_needed
store[:simple_timeline_menu] = object_account_user.setting_simple_timeline_menu
+ store[:boost_menu] = object_account_user.setting_boost_menu
store[:hide_items] = [
object_account_user.setting_hide_favourite_menu ? 'favourite_menu' : nil,
object_account_user.setting_hide_recent_emojis ? 'recent_emojis' : nil,
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index e9e4cd3588..3c16087727 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -82,10 +82,11 @@
= ff.input :'web.show_quote_in_public', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_quote_in_public'), hint: false
= ff.input :'web.show_blocking_quote', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_blocking_quote'), hint: false
- %h4= t 'appearance.timelines'
+ %h4= t 'appearance.status_action_bar'
.fields-group
= ff.input :'web.simple_timeline_menu', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_simple_timeline_menu')
+ = ff.input :'web.boost_menu', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_boost_menu')
%h4= t 'appearance.discovery'
@@ -93,12 +94,19 @@
= ff.input :'web.trends', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_trends')
= ff.input :'web.show_relationships', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_relationships')
+ %h4= t 'appearance.media'
+
+ .fields-group
+ = ff.input :'web.display_media_expand',
+ kmyblue: true,
+ hint: I18n.t('simple_form.hints.defaults.setting_display_media_expand'),
+ label: I18n.t('simple_form.labels.defaults.setting_display_media_expand'),
+ wrapper: :with_label
+
%h4= t 'appearance.confirmation_dialogs'
.fields-group
- = ff.input :'web.reblog_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_boost_modal'), hint: I18n.t('simple_form.hints.defaults.setting_boost_modal')
-
- .fields-group
+ = ff.input :'web.reblog_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_boost_modal')
= ff.input :'web.delete_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_delete_modal')
%h4= t 'appearance.sensitive_content'
@@ -114,13 +122,6 @@
label: I18n.t('simple_form.labels.defaults.setting_display_media'),
wrapper: :with_floating_label
- .fields-group
- = ff.input :'web.display_media_expand',
- kmyblue: true,
- hint: I18n.t('simple_form.hints.defaults.setting_display_media_expand'),
- label: I18n.t('simple_form.labels.defaults.setting_display_media_expand'),
- wrapper: :with_label
-
.fields-group
= ff.input :'web.use_blurhash',
hint: I18n.t('simple_form.hints.defaults.setting_use_blurhash'),
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a25b9df1af..6520016247 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1353,10 +1353,11 @@ en:
body: Mastodon is translated by volunteers.
guide_link: https://crowdin.com/project/mastodon
guide_link_text: Everyone can contribute.
+ media: Media
quotes: Quotes
saved_posts: Saving posts
sensitive_content: Sensitive content
- timelines: Timelines
+ status_action_bar: Post menu
application_mailer:
notification_preferences: Change e-mail preferences
salutation: "%{name},"
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index c5d43f3a71..25002b1071 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -1346,10 +1346,11 @@ ja:
body: Mastodonは有志によって翻訳されています。
guide_link: https://ja.crowdin.com/project/mastodon
guide_link_text: 誰でも参加することができます。
+ media: メディア
quotes: 引用
saved_posts: 投稿の記録
sensitive_content: 閲覧注意コンテンツ
- timelines: タイムライン
+ status_action_bar: 投稿のメニュー
application_mailer:
notification_preferences: メール設定の変更
salutation: "%{name}さん"
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index a5e6608b3c..2addb07e36 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -243,6 +243,7 @@ en:
setting_auto_play_gif: Auto-play animated GIFs
setting_bio_markdown: Enable profile markdown
setting_bookmark_category_needed: Category selection needed when registering bookmark on web
+ setting_boost_menu: Show popup when click boost button
setting_boost_modal: Show confirmation dialog before boosting
setting_default_language: Posting language
setting_default_privacy: Posting privacy
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index d8bfc1f62c..69383096e0 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -62,7 +62,6 @@ ja:
setting_allow_quote: ひかえめな引用はこの設定に関わらず可能です。kmyblue以外からは自由に引用できます
setting_always_send_emails: 通常、Mastodon からメール通知は行われません。
setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります
- setting_boost_modal: ブーストの公開範囲が指定できるようになります
setting_default_searchability: kmyblue・Fedibirdでは検索許可設定に基づき検索されます。Misskeyでは当設定に関係なく、全ての公開・ローカル公開・非収載投稿が検索されます。Mastodon・Firefishでは検索許可の代わりにプロフィール設定の「公開投稿を他のサーバーで自由に検索できるようにする」設定が適用されます
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
setting_disallow_unlisted_public_searchability: この設定を有効にすると、非収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります
@@ -254,6 +253,7 @@ ja:
setting_auto_play_gif: アニメーションGIFを自動再生する
setting_bio_markdown: プロフィールのMarkdownを有効にする
setting_bookmark_category_needed: Webでブックマーク時にカテゴリの選択を必須にする
+ setting_boost_menu: ブーストボタンを押したときにポップアップメニューを表示する
setting_boost_modal: ブーストする前に確認ダイアログを表示する
setting_default_language: 投稿する言語
setting_default_privacy: 投稿の公開範囲