mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 03:27:52 +00:00
fix: Update tweet character count logic (#2709)
This commit is contained in:
@@ -2,6 +2,33 @@
|
|||||||
/* global axios */
|
/* global axios */
|
||||||
import ApiClient from '../ApiClient';
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
|
export const buildCreatePayload = ({
|
||||||
|
message,
|
||||||
|
isPrivate,
|
||||||
|
contentAttributes,
|
||||||
|
echoId,
|
||||||
|
file,
|
||||||
|
}) => {
|
||||||
|
let payload;
|
||||||
|
if (file) {
|
||||||
|
payload = new FormData();
|
||||||
|
payload.append('attachments[]', file, file.name);
|
||||||
|
if (message) {
|
||||||
|
payload.append('content', message);
|
||||||
|
}
|
||||||
|
payload.append('private', isPrivate);
|
||||||
|
payload.append('echo_id', echoId);
|
||||||
|
} else {
|
||||||
|
payload = {
|
||||||
|
content: message,
|
||||||
|
private: isPrivate,
|
||||||
|
echo_id: echoId,
|
||||||
|
content_attributes: contentAttributes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
};
|
||||||
|
|
||||||
class MessageApi extends ApiClient {
|
class MessageApi extends ApiClient {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('conversations', { accountScoped: true });
|
super('conversations', { accountScoped: true });
|
||||||
@@ -15,18 +42,16 @@ class MessageApi extends ApiClient {
|
|||||||
echo_id: echoId,
|
echo_id: echoId,
|
||||||
file,
|
file,
|
||||||
}) {
|
}) {
|
||||||
const formData = new FormData();
|
|
||||||
if (file) formData.append('attachments[]', file, file.name);
|
|
||||||
if (message) formData.append('content', message);
|
|
||||||
if (contentAttributes)
|
|
||||||
formData.append('content_attributes', JSON.stringify(contentAttributes));
|
|
||||||
|
|
||||||
formData.append('private', isPrivate);
|
|
||||||
formData.append('echo_id', echoId);
|
|
||||||
return axios({
|
return axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `${this.url}/${conversationId}/messages`,
|
url: `${this.url}/${conversationId}/messages`,
|
||||||
data: formData,
|
data: buildCreatePayload({
|
||||||
|
message,
|
||||||
|
isPrivate,
|
||||||
|
contentAttributes,
|
||||||
|
echoId,
|
||||||
|
file,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import messageAPI from '../../inbox/message';
|
import messageAPI, { buildCreatePayload } from '../../inbox/message';
|
||||||
import ApiClient from '../../ApiClient';
|
import ApiClient from '../../ApiClient';
|
||||||
import describeWithAPIMock from '../apiSpecHelper';
|
import describeWithAPIMock from '../apiSpecHelper';
|
||||||
|
|
||||||
@@ -29,4 +29,34 @@ describe('#ConversationAPI', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#buildCreatePayload', () => {
|
||||||
|
it('builds form payload if file is available', () => {
|
||||||
|
const formPayload = buildCreatePayload({
|
||||||
|
message: 'test content',
|
||||||
|
echoId: 12,
|
||||||
|
isPrivate: true,
|
||||||
|
file: new Blob(['test-content'], { type: 'application/pdf' }),
|
||||||
|
});
|
||||||
|
expect(formPayload).toBeInstanceOf(FormData);
|
||||||
|
expect(formPayload.get('content')).toEqual('test content');
|
||||||
|
expect(formPayload.get('echo_id')).toEqual('12');
|
||||||
|
expect(formPayload.get('private')).toEqual('true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds object payload if file is not available', () => {
|
||||||
|
expect(
|
||||||
|
buildCreatePayload({
|
||||||
|
message: 'test content',
|
||||||
|
isPrivate: false,
|
||||||
|
echoId: 12,
|
||||||
|
contentAttributes: { in_reply_to: 12 },
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
content: 'test content',
|
||||||
|
private: false,
|
||||||
|
echo_id: 12,
|
||||||
|
content_attributes: { in_reply_to: 12 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -53,9 +53,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({ currentChat: 'getSelectedChat' }),
|
||||||
currentChat: 'getSelectedChat',
|
|
||||||
}),
|
|
||||||
showContactPanel() {
|
showContactPanel() {
|
||||||
return this.isContactPanelOpen && this.currentChat.id;
|
return this.isContactPanelOpen && this.currentChat.id;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
:message-type="data.message_type"
|
:message-type="data.message_type"
|
||||||
:readable-time="readableTime"
|
:readable-time="readableTime"
|
||||||
:source-id="data.source_id"
|
:source-id="data.source_id"
|
||||||
|
:inbox-id="data.inbox_id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<spinner v-if="isPending" size="tiny" />
|
<spinner v-if="isPending" size="tiny" />
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
|
|
||||||
<div v-if="isATweet" class="banner">
|
<div v-if="isATweet" class="banner">
|
||||||
<span v-if="!selectedTweetId">
|
<span v-if="!selectedTweetId">
|
||||||
{{ $t('CONVERSATION.LAST_INCOMING_TWEET') }}
|
{{ $t('CONVERSATION.SELECT_A_TWEET_TO_REPLY') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ $t('CONVERSATION.REPLYING_TO') }}
|
{{ $t('CONVERSATION.REPLYING_TO') }}
|
||||||
{{ selectedTweet }}
|
{{ selectedTweet.content || '' }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
v-if="selectedTweetId"
|
v-if="selectedTweetId"
|
||||||
@@ -89,9 +89,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ReplyBox
|
<reply-box
|
||||||
:conversation-id="currentChat.id"
|
:conversation-id="currentChat.id"
|
||||||
:in-reply-to="selectedTweetId"
|
:is-a-tweet="isATweet"
|
||||||
|
:selected-tweet="selectedTweet"
|
||||||
@scrollToMessage="scrollToBottom"
|
@scrollToMessage="scrollToBottom"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -207,10 +208,10 @@ export default {
|
|||||||
selectedTweet() {
|
selectedTweet() {
|
||||||
if (this.selectedTweetId) {
|
if (this.selectedTweetId) {
|
||||||
const { messages = [] } = this.getMessages;
|
const { messages = [] } = this.getMessages;
|
||||||
const [selectedMessage = {}] = messages.filter(
|
const [selectedMessage] = messages.filter(
|
||||||
message => message.id === this.selectedTweetId
|
message => message.id === this.selectedTweetId
|
||||||
);
|
);
|
||||||
return selectedMessage.content || '';
|
return selectedMessage || {};
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,9 +107,13 @@ export default {
|
|||||||
},
|
},
|
||||||
mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
|
mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
|
||||||
props: {
|
props: {
|
||||||
inReplyTo: {
|
selectedTweet: {
|
||||||
type: [String, Number],
|
type: [Object, String],
|
||||||
default: '',
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
isATweet: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -169,11 +173,14 @@ export default {
|
|||||||
return this.maxLength - this.message.length;
|
return this.maxLength - this.message.length;
|
||||||
},
|
},
|
||||||
isReplyButtonDisabled() {
|
isReplyButtonDisabled() {
|
||||||
const isMessageEmpty = this.isMessageEmpty;
|
if (this.isATweet && !this.inReplyTo) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.hasAttachments) return false;
|
if (this.hasAttachments) return false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isMessageEmpty ||
|
this.isMessageEmpty ||
|
||||||
this.message.length === 0 ||
|
this.message.length === 0 ||
|
||||||
this.message.length > this.maxLength
|
this.message.length > this.maxLength
|
||||||
);
|
);
|
||||||
@@ -198,7 +205,7 @@ export default {
|
|||||||
}
|
}
|
||||||
if (this.isATwitterInbox) {
|
if (this.isATwitterInbox) {
|
||||||
if (this.conversationType === 'tweet') {
|
if (this.conversationType === 'tweet') {
|
||||||
return MESSAGE_MAX_LENGTH.TWEET;
|
return MESSAGE_MAX_LENGTH.TWEET - this.replyToUserLength - 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MESSAGE_MAX_LENGTH.GENERAL;
|
return MESSAGE_MAX_LENGTH.GENERAL;
|
||||||
@@ -235,9 +242,22 @@ export default {
|
|||||||
isOnPrivateNote() {
|
isOnPrivateNote() {
|
||||||
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
||||||
},
|
},
|
||||||
|
inReplyTo() {
|
||||||
|
const selectedTweet = this.selectedTweet || {};
|
||||||
|
return selectedTweet.id;
|
||||||
|
},
|
||||||
|
replyToUserLength() {
|
||||||
|
const selectedTweet = this.selectedTweet || {};
|
||||||
|
const {
|
||||||
|
sender: {
|
||||||
|
additional_attributes: { screen_name: screenName = '' } = {},
|
||||||
|
} = {},
|
||||||
|
} = selectedTweet;
|
||||||
|
return screenName ? screenName.length : 0;
|
||||||
|
},
|
||||||
isMessageEmpty() {
|
isMessageEmpty() {
|
||||||
if(!this.message) {
|
if (!this.message) {
|
||||||
this.message = '';
|
return true;
|
||||||
}
|
}
|
||||||
return !this.message.trim().replace(/\n/g, '').length;
|
return !this.message.trim().replace(/\n/g, '').length;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
@mouseleave="isHovered = false"
|
@mouseleave="isHovered = false"
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
v-if="isATweet && isIncoming"
|
v-if="isATweet && (isIncoming || isOutgoing) && sourceId"
|
||||||
v-tooltip.top-start="$t('CHAT_LIST.REPLY_TO_TWEET')"
|
v-tooltip.top-start="$t('CHAT_LIST.REPLY_TO_TWEET')"
|
||||||
class="icon ion-reply cursor-pointer"
|
class="icon ion-reply cursor-pointer"
|
||||||
@click="onTweetReply"
|
@click="onTweetReply"
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
v-if="isATweet && isIncoming"
|
v-if="isATweet && (isOutgoing || isIncoming) && linkToTweet"
|
||||||
:href="linkToTweet"
|
:href="linkToTweet"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer nofollow"
|
rel="noopener noreferrer nofollow"
|
||||||
@@ -71,19 +71,33 @@ export default {
|
|||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
inboxId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
inbox() {
|
||||||
|
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
||||||
|
},
|
||||||
isIncoming() {
|
isIncoming() {
|
||||||
return MESSAGE_TYPE.INCOMING === this.messageType;
|
return MESSAGE_TYPE.INCOMING === this.messageType;
|
||||||
},
|
},
|
||||||
|
isOutgoing() {
|
||||||
|
return MESSAGE_TYPE.OUTGOING === this.messageType;
|
||||||
|
},
|
||||||
screenName() {
|
screenName() {
|
||||||
const { additional_attributes: additionalAttributes = {} } =
|
const { additional_attributes: additionalAttributes = {} } =
|
||||||
this.sender || {};
|
this.sender || {};
|
||||||
return additionalAttributes?.screen_name || '';
|
return additionalAttributes?.screen_name || '';
|
||||||
},
|
},
|
||||||
linkToTweet() {
|
linkToTweet() {
|
||||||
|
if (!this.sourceId || !this.inbox.name) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
const { screenName, sourceId } = this;
|
const { screenName, sourceId } = this;
|
||||||
return `https://twitter.com/${screenName}/status/${sourceId}`;
|
return `https://twitter.com/${screenName ||
|
||||||
|
this.inbox.name}/status/${sourceId}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -113,6 +127,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
.ion-reply,
|
||||||
|
.ion-android-open {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message-text--metadata {
|
.message-text--metadata {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"24_HOURS_WINDOW": "24 hour message window restriction",
|
"24_HOURS_WINDOW": "24 hour message window restriction",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
||||||
"LAST_INCOMING_TWEET": "You are replying to the last incoming tweet",
|
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||||
"REPLYING_TO": "You are replying to:",
|
"REPLYING_TO": "You are replying to:",
|
||||||
"REMOVE_SELECTION": "Remove Selection",
|
"REMOVE_SELECTION": "Remove Selection",
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ class Twitter::SendOnTwitterService < Base::SendOnChannelService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def screen_name
|
def screen_name
|
||||||
|
return "@#{reply_to_message.inbox.name}" if reply_to_message.outgoing?
|
||||||
|
|
||||||
"@#{reply_to_message.sender&.additional_attributes.try(:[], 'screen_name') || ''}"
|
"@#{reply_to_message.sender&.additional_attributes.try(:[], 'screen_name') || ''}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe Twitter::SendOnTwitterService do
|
|||||||
let(:widget_inbox) { create(:inbox, account: account) }
|
let(:widget_inbox) { create(:inbox, account: account) }
|
||||||
let(:twitter_channel) { create(:channel_twitter_profile, account: account) }
|
let(:twitter_channel) { create(:channel_twitter_profile, account: account) }
|
||||||
let(:twitter_inbox) { create(:inbox, channel: twitter_channel, account: account) }
|
let(:twitter_inbox) { create(:inbox, channel: twitter_channel, account: account) }
|
||||||
let(:contact) { create(:contact, account: account) }
|
let(:contact) { create(:contact, account: account, additional_attributes: { screen_name: 'test_user' }) }
|
||||||
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: twitter_inbox) }
|
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: twitter_inbox) }
|
||||||
let(:dm_conversation) do
|
let(:dm_conversation) do
|
||||||
create(
|
create(
|
||||||
@@ -73,12 +73,50 @@ describe Twitter::SendOnTwitterService do
|
|||||||
expect(twitter_client).to have_received(:send_direct_message)
|
expect(twitter_client).to have_received(:send_direct_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'if conversation is a tweet' do
|
context 'when conversation is a tweet' do
|
||||||
create(:message, message_type: :incoming, inbox: twitter_inbox, account: account, conversation: tweet_conversation)
|
it 'creates a response with correct reply if reply to message is incoming' do
|
||||||
message = create(:message, message_type: :outgoing, inbox: twitter_inbox, account: account, conversation: tweet_conversation)
|
create(
|
||||||
::Twitter::SendOnTwitterService.new(message: message).perform
|
:message,
|
||||||
expect(twitter_client).to have_received(:send_tweet_reply)
|
message_type: :incoming,
|
||||||
expect(message.reload.source_id).to eq '12345'
|
sender: contact,
|
||||||
|
source_id: 'test-source-id-1',
|
||||||
|
inbox: twitter_inbox,
|
||||||
|
account: account,
|
||||||
|
conversation: tweet_conversation
|
||||||
|
)
|
||||||
|
message = create(:message, message_type: :outgoing, inbox: twitter_inbox, account: account, conversation: tweet_conversation)
|
||||||
|
::Twitter::SendOnTwitterService.new(message: message).perform
|
||||||
|
expect(twitter_client).to have_received(:send_tweet_reply).with(
|
||||||
|
reply_to_tweet_id: 'test-source-id-1',
|
||||||
|
tweet: "@test_user #{message.content}"
|
||||||
|
)
|
||||||
|
expect(message.reload.source_id).to eq '12345'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a response with correct reply if reply to message is outgoing' do
|
||||||
|
outgoing_message = create(
|
||||||
|
:message,
|
||||||
|
message_type: :outgoing,
|
||||||
|
source_id: 'test-source-id-1',
|
||||||
|
inbox: twitter_inbox,
|
||||||
|
account: account,
|
||||||
|
conversation: tweet_conversation
|
||||||
|
)
|
||||||
|
reply_message = create(
|
||||||
|
:message,
|
||||||
|
message_type: :outgoing,
|
||||||
|
inbox: twitter_inbox,
|
||||||
|
account: account,
|
||||||
|
conversation: tweet_conversation,
|
||||||
|
in_reply_to: outgoing_message.id
|
||||||
|
)
|
||||||
|
::Twitter::SendOnTwitterService.new(message: reply_message).perform
|
||||||
|
expect(twitter_client).to have_received(:send_tweet_reply).with(
|
||||||
|
reply_to_tweet_id: 'test-source-id-1',
|
||||||
|
tweet: "@#{twitter_inbox.name} #{reply_message.content}"
|
||||||
|
)
|
||||||
|
expect(reply_message.reload.source_id).to eq '12345'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user