From 353e76c9c9a32db7f1ddf6b663ecbae5dfee1dc6 Mon Sep 17 00:00:00 2001
From: KMY <tt@kmycode.net>
Date: Wed, 20 Sep 2023 16:49:16 +0900
Subject: [PATCH] Add quote menu

---
 app/javascript/mastodon/actions/compose.js            |  3 ++-
 .../mastodon/components/status_action_bar.jsx         |  8 ++++++++
 .../mastodon/containers/status_container.jsx          |  6 +++++-
 .../features/status/components/action_bar.jsx         |  7 +++++++
 app/javascript/mastodon/features/status/index.jsx     |  7 ++++++-
 app/javascript/mastodon/locales/en.json               |  1 +
 app/javascript/mastodon/locales/ja.json               |  1 +
 app/javascript/mastodon/reducers/compose.js           | 11 ++++++-----
 8 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 3ea159f019..1f682d1321 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -785,11 +785,12 @@ export function insertExpirationCompose(position, data) {
   };
 }
 
-export function insertReferenceCompose(position, url) {
+export function insertReferenceCompose(position, url, attributeType) {
   return {
     type: COMPOSE_REFERENCE_INSERT,
     position,
     url,
+    attributeType,
   };
 }
 
diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx
index d689b1ed03..02a42a92dd 100644
--- a/app/javascript/mastodon/components/status_action_bar.jsx
+++ b/app/javascript/mastodon/components/status_action_bar.jsx
@@ -52,6 +52,7 @@ const messages = defineMessages({
   admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
   copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
   reference: { id: 'status.reference', defaultMessage: 'Add reference' },
+  quote: { id: 'status.quote', defaultMessage: 'Add ref (quote in other servers)' },
   hide: { id: 'status.hide', defaultMessage: 'Hide post' },
   blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
@@ -97,6 +98,8 @@ class StatusActionBar extends ImmutablePureComponent {
     onBookmarkCategoryAdder: PropTypes.func,
     onFilter: PropTypes.func,
     onAddFilter: PropTypes.func,
+    onReference: PropTypes.func,
+    onQuote: PropTypes.func,
     onInteractionModal: PropTypes.func,
     withDismiss: PropTypes.bool,
     withCounters: PropTypes.bool,
@@ -271,6 +274,10 @@ class StatusActionBar extends ImmutablePureComponent {
     this.props.onReference(this.props.status);
   };
 
+  handleQuote = () => {
+    this.props.onQuote(this.props.status);
+  };
+
   handleHideClick = () => {
     this.props.onFilter();
   };
@@ -316,6 +323,7 @@ class StatusActionBar extends ImmutablePureComponent {
 
       if (publicStatus) {
         menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
+        menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
       }
 
       menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal });
diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx
index 5023b7ef03..da3058334b 100644
--- a/app/javascript/mastodon/containers/status_container.jsx
+++ b/app/javascript/mastodon/containers/status_container.jsx
@@ -203,7 +203,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
   },
 
   onReference (status) {
-    dispatch(insertReferenceCompose(0, status.get('url')));
+    dispatch(insertReferenceCompose(0, status.get('url'), 'BT'));
+  },
+
+  onQuote (status) {
+    dispatch(insertReferenceCompose(0, status.get('url'), 'QT'));
   },
 
   onTranslate (status) {
diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx
index e80a83c907..56086075fc 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.jsx
+++ b/app/javascript/mastodon/features/status/components/action_bar.jsx
@@ -46,6 +46,7 @@ const messages = defineMessages({
   admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
   copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
   reference: { id: 'status.reference', defaultMessage: 'Add reference' },
+  quote: { id: 'status.quote', defaultMessage: 'Add ref (quote in other servers)' },
   blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
@@ -74,6 +75,7 @@ class ActionBar extends PureComponent {
     onFavourite: PropTypes.func.isRequired,
     onEmojiReact: PropTypes.func.isRequired,
     onReference: PropTypes.func.isRequired,
+    onQuote: PropTypes.func.isRequired,
     onBookmark: PropTypes.func.isRequired,
     onBookmarkCategoryAdder: PropTypes.func.isRequired,
     onDelete: PropTypes.func.isRequired,
@@ -208,6 +210,10 @@ class ActionBar extends PureComponent {
     this.props.onReference(this.props.status);
   };
 
+  handleQuote = () => {
+    this.props.onQuote(this.props.status);
+  };
+
   handleEmojiPick = (data) => {
     this.props.onEmojiReact(this.props.status, data);
   };
@@ -248,6 +254,7 @@ class ActionBar extends PureComponent {
 
       if (publicStatus) {
         menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
+        menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
       }
       menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick });
 
diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx
index cf4c2bd6ce..03a721d9c8 100644
--- a/app/javascript/mastodon/features/status/index.jsx
+++ b/app/javascript/mastodon/features/status/index.jsx
@@ -363,7 +363,11 @@ class Status extends ImmutablePureComponent {
   };
 
   handleReference = (status) => {
-    this.props.dispatch(insertReferenceCompose(0, status.get('url')));
+    this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'BT'));
+  };
+
+  handleQuote = (status) => {
+    this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'QT'));
   };
 
   handleBookmarkClick = (status) => {
@@ -750,6 +754,7 @@ class Status extends ImmutablePureComponent {
                   onReblog={this.handleReblogClick}
                   onReblogForceModal={this.handleReblogForceModalClick}
                   onReference={this.handleReference}
+                  onQuote={this.handleQuote}
                   onBookmark={this.handleBookmarkClick}
                   onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick}
                   onDelete={this.handleDeleteClick}
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 9de9b72162..b986b45b30 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -683,6 +683,7 @@
   "status.open": "Expand this post",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned post",
+  "status.quote": "Ref (quote in other servers)",
   "status.read_more": "Read more",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost with original visibility",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index d294d85e0b..e598512178 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -769,6 +769,7 @@
   "status.open": "詳細を表示",
   "status.pin": "プロフィールに固定表示",
   "status.pinned": "固定された投稿",
+  "status.quote": "参照 (他サーバーで引用扱い)",
   "status.read_more": "もっと見る",
   "status.reblog": "ブースト",
   "status.reblog_private": "ブースト",
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 59be1ae6da..612ee01d9b 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -252,10 +252,11 @@ const insertExpiration = (state, position, data) => {
   });
 };
 
-const insertReference = (state, url) => {
+const insertReference = (state, url, attributeType) => {
   const oldText = state.get('text');
+  const attribute = attributeType || 'BT';
 
-  if (oldText.indexOf(`BT ${url}`) >= 0) {
+  if (oldText.indexOf(`${attribute} ${url}`) >= 0) {
     return state;
   }
 
@@ -271,12 +272,12 @@ const insertReference = (state, url) => {
 
   if (oldText.length > 0) {
     const lastLine = oldText.slice(oldText.lastIndexOf('\n') + 1, oldText.length - 1);
-    if (lastLine.startsWith('BT ')) {
+    if (lastLine.startsWith(`${attribute} `)) {
       newLine = '\n';
     }
   }
 
-  const referenceText = `${newLine}BT ${url}`;
+  const referenceText = `${newLine}${attribute} ${url}`;
   const text = `${oldText}${referenceText}`;
 
   return state.merge({
@@ -526,7 +527,7 @@ export default function compose(state = initialState, action) {
   case COMPOSE_EXPIRATION_INSERT:
     return insertExpiration(state, action.position, action.data);
   case COMPOSE_REFERENCE_INSERT:
-    return insertReference(state, action.url);
+    return insertReference(state, action.url, action.attributeType);
   case COMPOSE_UPLOAD_CHANGE_SUCCESS:
     return state
       .set('is_changing_upload', false)