From f832b5698aa73cbc8bbe12417decff3665f60e70 Mon Sep 17 00:00:00 2001
From: KMY <tt@kmycode.net>
Date: Thu, 18 May 2023 12:25:56 +0900
Subject: [PATCH] Add reblog menu with force modal

---
 app/javascript/mastodon/components/status.jsx            | 1 +
 app/javascript/mastodon/components/status_action_bar.jsx | 7 +++++++
 app/javascript/mastodon/containers/status_container.jsx  | 4 ++++
 .../notifications/containers/notification_container.js   | 8 ++++++++
 .../mastodon/features/status/components/action_bar.jsx   | 7 +++++++
 .../status/containers/detailed_status_container.js       | 8 ++++++++
 app/javascript/mastodon/features/status/index.jsx        | 9 +++++++--
 7 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx
index 7778265151..5d01f00140 100644
--- a/app/javascript/mastodon/components/status.jsx
+++ b/app/javascript/mastodon/components/status.jsx
@@ -79,6 +79,7 @@ class Status extends ImmutablePureComponent {
     onEmojiReact: PropTypes.func,
     onUnEmojiReact: PropTypes.func,
     onReblog: PropTypes.func,
+    onReblogForceModal: PropTypes.func,
     onDelete: PropTypes.func,
     onDirect: PropTypes.func,
     onMention: PropTypes.func,
diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx
index 6768f45643..ba42a3b09a 100644
--- a/app/javascript/mastodon/components/status_action_bar.jsx
+++ b/app/javascript/mastodon/components/status_action_bar.jsx
@@ -24,6 +24,7 @@ const messages = defineMessages({
   more: { id: 'status.more', defaultMessage: 'More' },
   replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
   reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
+  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' },
   cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
@@ -69,6 +70,7 @@ class StatusActionBar extends ImmutablePureComponent {
     onFavourite: PropTypes.func,
     onEmojiReact: PropTypes.func,
     onReblog: PropTypes.func,
+    onReblogForceModal: PropTypes.func,
     onDelete: PropTypes.func,
     onDirect: PropTypes.func,
     onMention: PropTypes.func,
@@ -151,6 +153,10 @@ class StatusActionBar extends ImmutablePureComponent {
     }
   };
 
+  handleReblogForceModalClick = e => {
+    this.props.onReblogForceModal(this.props.status, e);
+  };
+
   handleBookmarkClick = () => {
     this.props.onBookmark(this.props.status);
   };
@@ -272,6 +278,7 @@ class StatusActionBar extends ImmutablePureComponent {
 
     menu.push(null);
 
+    menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancelReblog : messages.reblog), action: this.handleReblogForceModalClick });
     menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
 
     if (writtenByMe && pinnableStatus) {
diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx
index 1b581a3119..85b0092ac9 100644
--- a/app/javascript/mastodon/containers/status_container.jsx
+++ b/app/javascript/mastodon/containers/status_container.jsx
@@ -110,6 +110,10 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
     }
   },
 
+  onReblogForceModal (status) {
+    dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
+  },
+
   onFavourite (status) {
     if (status.get('favourited')) {
       dispatch(unfavourite(status));
diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js
index 2353cb8222..51052cdf38 100644
--- a/app/javascript/mastodon/features/notifications/containers/notification_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js
@@ -54,6 +54,14 @@ const mapDispatchToProps = dispatch => ({
     }
   },
 
+  onReblogForceModal (status) {
+    if (status.get('reblogged')) {
+      dispatch(unreblog(status));
+    } else {
+      dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
+    }
+  },
+
   onFavourite (status) {
     if (status.get('favourited')) {
       dispatch(unfavourite(status));
diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx
index d6e89a6e3d..ae963fd5bd 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.jsx
+++ b/app/javascript/mastodon/features/status/components/action_bar.jsx
@@ -61,6 +61,7 @@ class ActionBar extends React.PureComponent {
     relationship: ImmutablePropTypes.map,
     onReply: PropTypes.func.isRequired,
     onReblog: PropTypes.func.isRequired,
+    onReblogForceModal: PropTypes.func.isRequired,
     onFavourite: PropTypes.func.isRequired,
     onEmojiReact: PropTypes.func.isRequired,
     onBookmark: PropTypes.func.isRequired,
@@ -89,6 +90,10 @@ class ActionBar extends React.PureComponent {
     this.props.onReblog(this.props.status, e);
   };
 
+  handleReblogForceModalClick = (e) => {
+    this.props.onReblogForceModal(this.props.status, e);
+  };
+
   handleFavouriteClick = () => {
     this.props.onFavourite(this.props.status);
   };
@@ -208,6 +213,8 @@ class ActionBar extends React.PureComponent {
       menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
       menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.reblog), action: this.handleReblogForceModalClick });
+      menu.push(null);
     }
 
     if (writtenByMe) {
diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js
index 99c4c3b2f4..f2c1d8187b 100644
--- a/app/javascript/mastodon/features/status/containers/detailed_status_container.js
+++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js
@@ -87,6 +87,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     }
   },
 
+  onReblogForceModal (status) {
+    if (status.get('reblogged')) {
+      dispatch(unreblog(status));
+    } else {
+      dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
+    }
+  },
+
   onFavourite (status) {
     if (status.get('favourited')) {
       dispatch(unfavourite(status));
diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx
index 0e65a6d974..e0f536f56c 100644
--- a/app/javascript/mastodon/features/status/index.jsx
+++ b/app/javascript/mastodon/features/status/index.jsx
@@ -310,7 +310,7 @@ class Status extends ImmutablePureComponent {
     this.props.dispatch(reblog(status, privacy));
   };
 
-  handleReblogClick = (status, e) => {
+  handleReblogClick = (status, e, force = false) => {
     const { dispatch } = this.props;
     const { signedIn } = this.context.identity;
 
@@ -318,7 +318,7 @@ class Status extends ImmutablePureComponent {
       if (status.get('reblogged')) {
         dispatch(unreblog(status));
       } else {
-        if ((e && e.shiftKey) || !boostModal) {
+        if (!force && ((e && e.shiftKey) || !boostModal)) {
           this.handleModalReblog(status);
         } else {
           dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
@@ -333,6 +333,10 @@ class Status extends ImmutablePureComponent {
     }
   };
 
+  handleReblogForceModalClick = (status, e) => {
+    this.handleReblogClick(status, e, true);
+  };
+
   handleBookmarkClick = (status) => {
     if (status.get('bookmarked')) {
       this.props.dispatch(unbookmark(status));
@@ -678,6 +682,7 @@ class Status extends ImmutablePureComponent {
                   onFavourite={this.handleFavouriteClick}
                   onEmojiReact={this.handleEmojiReact}
                   onReblog={this.handleReblogClick}
+                  onReblogForceModal={this.handleReblogForceModalClick}
                   onBookmark={this.handleBookmarkClick}
                   onDelete={this.handleDeleteClick}
                   onEdit={this.handleEditClick}