Allow replying with quoting message part.

This commit is contained in:
John Preston 2023-10-11 21:49:26 +04:00
parent 00db325e91
commit ef0539c9fc
20 changed files with 167 additions and 29 deletions

View File

@ -71,11 +71,7 @@ void ApplyPeerCloudDraft(
textWithTags,
FullReplyTo{
.messageId = FullMsgId(replyPeerId, reply.messageId),
.quote = TextWithTags{
reply.quote.text,
TextUtilities::ConvertEntitiesToTextTags(
reply.quote.entities),
},
.quote = reply.quote,
.storyId = (reply.storyId
? FullStoryId{ replyPeerId, reply.storyId }
: FullStoryId()),

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_histories.h"
#include "api/api_text_entities.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@ -46,10 +47,20 @@ MTPInputReplyTo ReplyToForMTP(
}
} else if (replyTo.messageId || replyTo.topicRootId) {
const auto external = (replyTo.messageId.peer != history->peer->id);
const auto quoteEntities = Api::EntitiesToMTP(
&history->session(),
replyTo.quote.entities,
Api::ConvertOption::SkipLocal);
using Flag = MTPDinputReplyToMessage::Flag;
return MTP_inputReplyToMessage(
MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag())
| (external ? Flag::f_reply_to_peer_id : Flag())),
| (external ? Flag::f_reply_to_peer_id : Flag())
| (replyTo.quote.text.isEmpty()
? Flag()
: Flag::f_quote_text)
| (quoteEntities.v.isEmpty()
? Flag()
: Flag::f_quote_entities)),
MTP_int(replyTo.messageId
? replyTo.messageId.msg
: replyTo.topicRootId),
@ -57,8 +68,8 @@ MTPInputReplyTo ReplyToForMTP(
(external
? owner->peer(replyTo.messageId.peer)->input
: MTPInputPeer()),
MTPstring(), // quote_text
MTPVector<MTPMessageEntity>()); // quote_entities
MTP_string(replyTo.quote.text),
quoteEntities);
}
return MTPInputReplyTo();
}
@ -945,6 +956,7 @@ int Histories::sendPreparedMessage(
}
const auto realReplyTo = FullReplyTo{
.messageId = convertTopicReplyToId(history, replyTo.messageId),
.quote = replyTo.quote,
.storyId = replyTo.storyId,
.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
};

View File

@ -158,7 +158,7 @@ Q_DECLARE_METATYPE(FullMsgId);
struct FullReplyTo {
FullMsgId messageId;
TextWithTags quote;
TextWithEntities quote;
FullStoryId storyId;
MsgId topicRootId = 0;

View File

@ -2101,6 +2101,20 @@ void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
}
TextWithEntities HistoryInner::selectedQuote(
not_null<HistoryItem*> item) const {
if (_selected.size() != 1
|| _selected.begin()->first != item
|| _selected.begin()->second == FullSelection) {
return {};
}
const auto view = item->mainView();
if (!view) {
return {};
}
return view->selectedQuote(_selected.begin()->second);
}
void HistoryInner::contextMenuEvent(QContextMenuEvent *e) {
showContextMenu(e);
}
@ -2215,13 +2229,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
return true;
}();
if (canReply) {
const auto quote = selectedQuote(item);
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
if (canSendReply) {
_widget->replyToMessage({ itemId });
_widget->replyToMessage({ itemId, quote });
} else {
HistoryView::Controls::ShowReplyToChatBox(
controller->uiShow(),
{ itemId });
{ itemId, quote });
}
}, &st::menuIconReply);
}

View File

@ -314,6 +314,8 @@ private:
QPoint mapPointToItem(QPoint p, const Element *view) const;
QPoint mapPointToItem(QPoint p, const HistoryItem *item) const;
[[nodiscard]] TextWithEntities selectedQuote(
not_null<HistoryItem*> item) const;
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
void cancelContextDownload(not_null<DocumentData*> document);

View File

@ -7072,7 +7072,7 @@ void HistoryWidget::replyToMessage(FullReplyTo id) {
void HistoryWidget::replyToMessage(
not_null<HistoryItem*> item,
TextWithTags quote) {
TextWithEntities quote) {
if (isJoinChannel()) {
return;
}

View File

@ -185,7 +185,7 @@ public:
void replyToMessage(FullReplyTo id);
void replyToMessage(
not_null<HistoryItem*> item,
TextWithTags quote = {});
TextWithEntities quote = {});
void editMessage(FullMsgId itemId);
void editMessage(not_null<HistoryItem*> item);

View File

@ -388,7 +388,10 @@ public:
int bottom,
QPoint point,
InfoDisplayType type) const;
virtual TextForMimeData selectedText(
virtual TextForMimeData selectedText(TextSelection selection) const = 0;
virtual TextWithEntities selectedQuote(TextSelection selection) const = 0;
virtual TextWithEntities selectedQuote(
const Ui::Text::String &text,
TextSelection selection) const = 0;
[[nodiscard]] virtual TextSelection adjustSelection(
TextSelection selection,

View File

@ -2611,6 +2611,80 @@ TextForMimeData Message::selectedText(TextSelection selection) const {
return result;
}
TextWithEntities Message::selectedQuote(TextSelection selection) const {
const auto item = data();
const auto &translated = item->translatedText();
const auto &original = item->originalText();
if (&translated != &original
|| selection.empty()
|| selection == FullSelection) {
return {};
} else if (hasVisibleText()) {
return selectedQuote(text(), selection);
} else if (const auto media = this->media()) {
if (media->isDisplayed() || isHiddenByGroup()) {
return media->selectedQuote(selection);
}
}
return {};
}
TextWithEntities Message::selectedQuote(
const Ui::Text::String &text,
TextSelection selection) const {
if (selection.to > text.length()) {
return {};
}
auto modified = selection;
const auto &modifications = text.modifications();
for (const auto &modification : text.modifications()) {
if (modification.position >= selection.to) {
break;
} else if (modification.position <= selection.from) {
modified.from += modification.skipped;
if (modification.added
&& modification.position < selection.from) {
--modified.from;
}
}
modified.to += modification.skipped;
if (modification.added && modified.to > modified.from) {
--modified.to;
}
}
auto result = data()->originalText();
if (modified.empty() || modified.to > result.text.size()) {
return {};
}
result.text = result.text.mid(
modified.from,
modified.to - modified.from);
const auto allowed = std::array{
EntityType::Bold,
EntityType::Italic,
EntityType::Underline,
EntityType::StrikeOut,
EntityType::Spoiler,
EntityType::CustomEmoji,
};
for (auto i = result.entities.begin(); i != result.entities.end();) {
const auto offset = i->offset();
const auto till = offset + i->length();
if ((till <= modified.from)
|| (offset >= modified.to)
|| !ranges::contains(allowed, i->type())) {
i = result.entities.erase(i);
} else {
if (till > modified.to) {
i->shrinkFromRight(till - modified.to);
}
i->shiftLeft(modified.from);
++i;
}
}
return result;
}
TextSelection Message::adjustSelection(
TextSelection selection,
TextSelectType type) const {

View File

@ -99,6 +99,10 @@ public:
QPoint point,
InfoDisplayType type) const override;
TextForMimeData selectedText(TextSelection selection) const override;
TextWithEntities selectedQuote(TextSelection selection) const override;
TextWithEntities selectedQuote(
const Ui::Text::String &text,
TextSelection selection) const override;
TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override;

View File

@ -669,6 +669,16 @@ TextForMimeData Service::selectedText(TextSelection selection) const {
return text().toTextForMimeData(selection);
}
TextWithEntities Service::selectedQuote(TextSelection selection) const {
return {};
}
TextWithEntities Service::selectedQuote(
const Ui::Text::String &text,
TextSelection selection) const {
return {};
}
TextSelection Service::adjustSelection(
TextSelection selection,
TextSelectType type) const {

View File

@ -43,6 +43,10 @@ public:
StateRequest request) const override;
void updatePressed(QPoint point) override;
TextForMimeData selectedText(TextSelection selection) const override;
TextWithEntities selectedQuote(TextSelection selection) const override;
TextWithEntities selectedQuote(
const Ui::Text::String &text,
TextSelection selection) const override;
TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override;

View File

@ -1210,6 +1210,22 @@ TextForMimeData Document::selectedText(TextSelection selection) const {
return result;
}
TextWithEntities Document::selectedQuote(TextSelection selection) const {
if (const auto voice = Get<HistoryDocumentVoice>()) {
const auto length = voice->transcribeText.length();
if (selection.from < length) {
return {};
}
selection = HistoryView::UnshiftItemSelection(
selection,
voice->transcribeText);
}
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
return parent()->selectedQuote(captioned->caption, selection);
}
return {};
}
bool Document::uploading() const {
return _data->uploading();
}

View File

@ -46,6 +46,7 @@ public:
bool hasTextForCopy() const override;
TextForMimeData selectedText(TextSelection selection) const override;
TextWithEntities selectedQuote(TextSelection selection) const override;
bool uploading() const override;

View File

@ -1181,6 +1181,10 @@ TextForMimeData Gif::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection);
}
TextWithEntities Gif::selectedQuote(TextSelection selection) const {
return parent()->selectedQuote(_caption, selection);
}
bool Gif::fullFeaturedGrouped(RectParts sides) const {
return (sides & RectPart::Left) && (sides & RectPart::Right);
}

View File

@ -68,6 +68,7 @@ public:
}
TextForMimeData selectedText(TextSelection selection) const override;
TextWithEntities selectedQuote(TextSelection selection) const override;
bool uploading() const override;

View File

@ -86,7 +86,11 @@ public:
[[nodiscard]] virtual TextForMimeData selectedText(
TextSelection selection) const {
return TextForMimeData();
return {};
}
[[nodiscard]] virtual TextWithEntities selectedQuote(
TextSelection selection) const {
return {};
}
[[nodiscard]] virtual bool isDisplayed() const;

View File

@ -1049,6 +1049,10 @@ TextForMimeData Photo::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection);
}
TextWithEntities Photo::selectedQuote(TextSelection selection) const {
return parent()->selectedQuote(_caption, selection);
}
void Photo::hideSpoilers() {
_caption.setSpoilerRevealed(false, anim::type::instant);
if (_spoiler) {

View File

@ -57,6 +57,7 @@ public:
}
TextForMimeData selectedText(TextSelection selection) const override;
TextWithEntities selectedQuote(TextSelection selection) const override;
PhotoData *getPhoto() const override {
return _data;

View File

@ -1126,11 +1126,8 @@ void Account::writeDrafts(not_null<History*> history) {
auto&&) { // cursor
size += sizeof(qint64) // key
+ Serialize::stringSize(text.text)
+ sizeof(qint64) + TextUtilities::SerializeTagsSize(text.tags)
+ TextUtilities::SerializeTagsSize(text.tags)
+ sizeof(qint64) + sizeof(qint64) // messageId
+ Serialize::stringSize(reply.quote.text)
+ sizeof(qint64)
+ TextUtilities::SerializeTagsSize(reply.quote.tags)
+ sizeof(qint32); // previewState
};
EnumerateDrafts(
@ -1157,8 +1154,6 @@ void Account::writeDrafts(not_null<History*> history) {
<< TextUtilities::SerializeTags(text.tags)
<< qint64(reply.messageId.peer.value)
<< qint64(reply.messageId.msg.bare)
<< reply.quote.text
<< TextUtilities::SerializeTags(reply.quote.tags)
<< qint32(previewState);
};
EnumerateDrafts(
@ -1371,10 +1366,8 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
const auto keysOld = (tag == kMultiDraftTagOld);
const auto rich = (tag == kRichDraftsTag);
for (auto i = 0; i != count; ++i) {
TextWithTags quote;
TextWithTags text;
QByteArray textTagsSerialized;
QByteArray quoteTagsSerialized;
qint64 keyValue = 0;
qint64 messageIdPeer = 0, messageIdMsg = 0;
qint32 keyValueOld = 0, uncheckedPreviewState = 0;
@ -1396,12 +1389,7 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
>> textTagsSerialized
>> messageIdPeer
>> messageIdMsg
>> quote.text
>> quoteTagsSerialized
>> uncheckedPreviewState;
quote.tags = TextUtilities::DeserializeTags(
quoteTagsSerialized,
quote.text.size());
}
text.tags = TextUtilities::DeserializeTags(
textTagsSerialized,
@ -1422,7 +1410,6 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
.messageId = FullMsgId(
PeerId(messageIdPeer),
MsgId(messageIdMsg)),
.quote = quote,
.topicRootId = key.topicRootId(),
},
MessageCursor(),