Allow sending custom webpage previews.

This commit is contained in:
John Preston 2023-10-20 17:48:15 +04:00
parent b1823d981b
commit 8b42161898
28 changed files with 396 additions and 173 deletions

View File

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_drafts.h"
class History;
namespace Data {
@ -22,7 +24,6 @@ struct SendOptions {
TimeId scheduled = 0;
bool silent = false;
bool handleSupportSwitch = false;
bool removeWebPageId = false;
bool hideViaBot = false;
};
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
@ -54,7 +55,7 @@ struct MessageToSend {
SendAction action;
TextWithTags textWithTags;
WebPageId webPageId = 0;
Data::WebPageDraft webPage;
};
struct RemoteFileInfo {

View File

@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_media.h"
#include "api/api_text_entities.h"
#include "ui/boxes/confirm_box.h"
#include "data/data_histories.h"
#include "data/data_scheduled_messages.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
@ -45,6 +47,7 @@ template <typename DoneCallback, typename FailCallback>
mtpRequestId EditMessage(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
@ -71,9 +74,15 @@ mtpRequestId EditMessage(
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (options.removeWebPageId
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
@ -89,7 +98,7 @@ mtpRequestId EditMessage(
item->history()->peer->input,
MTP_int(id),
MTP_string(text),
inputMedia.value_or(MTPInputMedia()),
inputMedia.value_or(Data::WebPageForMTP(webpage)),
MTPReplyMarkup(),
sentEntities,
MTP_int(options.scheduled)
@ -133,9 +142,15 @@ mtpRequestId EditMessage(
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
const auto &text = item->originalText();
const auto webpage = (!item->media() || !item->media()->webpage())
? Data::WebPageDraft{ .removed = true }
: Data::WebPageDraft{
.id = item->media()->webpage()->id,
};
return EditMessage(
item,
text,
webpage,
options,
std::forward<DoneCallback>(done),
std::forward<FailCallback>(fail),
@ -216,12 +231,19 @@ mtpRequestId EditCaption(
SendOptions options,
Fn<void()> done,
Fn<void(const QString &)> fail) {
return EditMessage(item, caption, options, done, fail);
return EditMessage(
item,
caption,
Data::WebPageDraft(),
options,
done,
fail);
}
mtpRequestId EditTextMessage(
not_null<HistoryItem*> item,
const TextWithEntities &caption,
Data::WebPageDraft webpage,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &, mtpRequestId requestId)> fail) {
@ -229,7 +251,7 @@ mtpRequestId EditTextMessage(
applyUpdates();
done(id);
};
return EditMessage(item, caption, options, callback, fail);
return EditMessage(item, caption, webpage, options, callback, fail);
}
} // namespace Api

View File

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
namespace Data {
struct WebPageDraft;
} // namespace Data
namespace MTP {
class Error;
} // namespace MTP
@ -48,6 +52,7 @@ mtpRequestId EditCaption(
mtpRequestId EditTextMessage(
not_null<HistoryItem*> item,
const TextWithEntities &caption,
Data::WebPageDraft webpage,
SendOptions options,
Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail);

View File

@ -2159,15 +2159,7 @@ void ApiWrap::saveDraftsToCloud() {
history->peer->input,
MTP_string(textWithTags.text),
entities,
MTP_inputMediaWebPage(
MTP_flags(PageFlag::f_optional
| (cloudDraft->webpage.forceLargeMedia
? PageFlag::f_force_large_media
: PageFlag())
| (cloudDraft->webpage.forceSmallMedia
? PageFlag::f_force_small_media
: PageFlag())),
MTP_string(cloudDraft->webpage.url))
Data::WebPageForMTP(cloudDraft->webpage)
)).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
@ -3630,29 +3622,48 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTPstring msgText(MTP_string(sending.text));
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMessage::Flags(0);
auto mediaFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to;
mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto replyHeader = NewMessageReplyHeader(action);
MTPMessageMedia media = MTP_messageMediaEmpty();
if (message.webPageId == CancelledWebPageId) {
if (message.webPage.removed) {
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
} else if (message.webPageId) {
auto page = _session->data().webpage(message.webPageId);
} else if (const auto fields = message.webPage; fields.id) {
using PageFlag = MTPDmessageMediaWebPage::Flag;
using PendingFlag = MTPDwebPagePending::Flag;
const auto page = _session->data().webpage(fields.id);
media = MTP_messageMediaWebPage(
MTP_flags(0),
MTP_flags(PageFlag()
| (fields.manual ? PageFlag::f_manual : PageFlag())
| (fields.forceLargeMedia
? PageFlag::f_force_large_media
: PageFlag())
| (fields.forceSmallMedia
? PageFlag::f_force_small_media
: PageFlag())),
MTP_webPagePending(
MTP_flags(0),
MTP_flags(page->url.isEmpty()
? PendingFlag()
: PendingFlag::f_url),
MTP_long(page->id),
MTPstring(), // url
MTP_string(page->url),
MTP_int(page->pendingTill)));
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
if (message.webPage.id && message.webPage.invert) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
if (silentPost) {
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
mediaFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto sentEntities = Api::EntitiesToMTP(
_session,
@ -3665,6 +3676,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
const auto topicRootId = action.replyTo.topicRootId;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
}
@ -3676,6 +3688,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
: _session->userPeerId();
if (sendAs) {
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name()
@ -3683,6 +3696,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
const auto viaBotId = UserId();
lastMessage = history->addNewLocalMessage(
@ -3696,27 +3710,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sending,
media,
HistoryMessageMarkupData());
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), [=](const MTPUpdates &result, const MTP::Response &response) {
const auto done = [=](
const MTPUpdates &result,
const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
}, [=](const MTP::Error &error, const MTP::Response &response) {
};
const auto fail = [=](
const MTP::Error &error,
const MTP::Response &response) {
if (error.type() == u"MESSAGE_EMPTY"_q) {
lastMessage->destroy();
} else {
@ -3727,7 +3732,44 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
}
});
};
if (!message.webPage.removed
&& (message.webPage.manual || sending.empty())
&& !message.webPage.url.isEmpty()) {
using PageFlag = MTPDinputMediaWebPage::Flag;
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(mediaFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::WebPageForMTP(message.webPage),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(message.action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), done, fail);
} else {
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), done, fail);
}
}
finishForwarding(action);

View File

@ -74,6 +74,15 @@ MTPInputReplyTo ReplyToForMTP(
return MTPInputReplyTo();
}
MTPInputMedia WebPageForMTP(const Data::WebPageDraft &draft) {
using Flag = MTPDinputMediaWebPage::Flag;
return MTP_inputMediaWebPage(
MTP_flags(Flag::f_optional
| (draft.forceLargeMedia ? Flag::f_force_large_media : Flag())
| (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())),
MTP_string(draft.url));
}
Histories::Histories(not_null<Session*> owner)
: _owner(owner)
, _readRequestsTimer([=] { sendReadRequests(); }) {

View File

@ -25,10 +25,12 @@ namespace Data {
class Session;
class Folder;
struct WebPageDraft;
[[nodiscard]] MTPInputReplyTo ReplyToForMTP(
not_null<History*> history,
FullReplyTo replyTo);
[[nodiscard]] MTPInputMedia WebPageForMTP(const Data::WebPageDraft &draft);
class Histories final {
public:

View File

@ -1500,10 +1500,13 @@ bool MediaWebPage::replyPreviewLoaded() const {
}
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
return { .text = options.translated
auto text = options.translated
? parent()->translatedText()
: parent()->originalText()
};
: parent()->originalText();
if (text.empty()) {
text = Ui::Text::Colorized(_page->url);
}
return { .text = text };
}
TextWithEntities MediaWebPage::notificationText() const {

View File

@ -3260,6 +3260,7 @@ not_null<WebPageData*> Session::processWebpage(const MTPWebPage &data) {
return processWebpage(data.c_webPage());
case mtpc_webPageEmpty: {
const auto result = webpage(data.c_webPageEmpty().vid().v);
result->type = WebPageType::None;
if (result->pendingTill > 0) {
result->pendingTill = 0;
result->failed = 1;
@ -3283,13 +3284,13 @@ not_null<WebPageData*> Session::processWebpage(const MTPDwebPage &data) {
return result;
}
not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
not_null<WebPageData*> Session::processWebpage(
const MTPDwebPagePending &data) {
constexpr auto kDefaultPendingTimeout = 60;
const auto result = webpage(data.vid().v);
webpageApplyFields(
result,
WebPageType::Article,
false,
WebPageType::None,
QString(),
QString(),
QString(),
@ -3301,6 +3302,7 @@ not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
WebPageCollage(),
0,
QString(),
false,
data.vdate().v
? data.vdate().v
: (base::unixtime::now() + kDefaultPendingTimeout));
@ -3314,7 +3316,6 @@ not_null<WebPageData*> Session::webpage(
return webpage(
id,
WebPageType::Article,
false,
QString(),
QString(),
siteName,
@ -3325,13 +3326,13 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage(),
0,
QString(),
false,
TimeId(0));
}
not_null<WebPageData*> Session::webpage(
WebPageId id,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,
@ -3342,12 +3343,12 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage &&collage,
int duration,
const QString &author,
bool hasLargeMedia,
TimeId pendingTill) {
const auto result = webpage(id);
webpageApplyFields(
result,
type,
hasLargeMedia,
url,
displayUrl,
siteName,
@ -3359,6 +3360,7 @@ not_null<WebPageData*> Session::webpage(
std::move(collage),
duration,
author,
hasLargeMedia,
pendingTill);
return result;
}
@ -3439,7 +3441,6 @@ void Session::webpageApplyFields(
webpageApplyFields(
page,
(story ? WebPageType::Story : ParseWebPageType(data)),
data.is_has_large_media(),
qs(data.vurl()),
qs(data.vdisplay_url()),
siteName,
@ -3459,13 +3460,13 @@ void Session::webpageApplyFields(
WebPageCollage(this, data),
data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()),
data.is_has_large_media(),
pendingTill);
}
void Session::webpageApplyFields(
not_null<WebPageData*> page,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,
@ -3477,11 +3478,11 @@ void Session::webpageApplyFields(
WebPageCollage &&collage,
int duration,
const QString &author,
bool hasLargeMedia,
TimeId pendingTill) {
const auto requestPending = (!page->pendingTill && pendingTill > 0);
const auto changed = page->applyChanges(
type,
hasLargeMedia,
url,
displayUrl,
siteName,
@ -3493,6 +3494,7 @@ void Session::webpageApplyFields(
std::move(collage),
duration,
author,
hasLargeMedia,
pendingTill);
if (requestPending) {
_session->api().requestWebPageDelayed(page);

View File

@ -552,7 +552,6 @@ public:
[[nodiscard]] not_null<WebPageData*> webpage(
WebPageId id,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,
@ -563,6 +562,7 @@ public:
WebPageCollage &&collage,
int duration,
const QString &author,
bool hasLargeMedia,
TimeId pendingTill);
[[nodiscard]] not_null<GameData*> game(GameId id);
@ -814,7 +814,6 @@ private:
void webpageApplyFields(
not_null<WebPageData*> page,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,
@ -826,6 +825,7 @@ private:
WebPageCollage &&collage,
int duration,
const QString &author,
bool hasLargeMedia,
TimeId pendingTill);
void gameApplyFields(

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "lang/lang_keys.h"
#include "ui/image/image.h"
#include "ui/text/text_entity.h"
@ -213,7 +214,6 @@ Main::Session &WebPageData::session() const {
bool WebPageData::applyChanges(
WebPageType newType,
bool newHasLargeMedia,
const QString &newUrl,
const QString &newDisplayUrl,
const QString &newSiteName,
@ -225,6 +225,7 @@ bool WebPageData::applyChanges(
WebPageCollage &&newCollage,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
int newPendingTill) {
if (newPendingTill != 0
&& (!url.isEmpty() || failed)
@ -255,7 +256,6 @@ bool WebPageData::applyChanges(
}();
if (type == newType
&& hasLargeMedia == newHasLargeMedia
&& url == resultUrl
&& displayUrl == resultDisplayUrl
&& siteName == resultSiteName
@ -267,6 +267,7 @@ bool WebPageData::applyChanges(
&& collage.items == newCollage.items
&& duration == newDuration
&& author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
&& pendingTill == newPendingTill) {
return false;
}
@ -274,7 +275,7 @@ bool WebPageData::applyChanges(
_owner->session().api().clearWebPageRequest(this);
}
type = newType;
hasLargeMedia = newHasLargeMedia;
hasLargeMedia = newHasLargeMedia ? 1 : 0;
url = resultUrl;
displayUrl = resultDisplayUrl;
siteName = resultSiteName;
@ -346,3 +347,11 @@ void WebPageData::ApplyChanges(
}
session->data().sendWebPageGamePollNotifications();
}
QString WebPageData::displayedSiteName() const {
return (document && document->isWallPaper())
? tr::lng_media_chat_background(tr::now)
: (document && document->isTheme())
? tr::lng_media_color_theme(tr::now)
: siteName;
}

View File

@ -18,6 +18,8 @@ class Session;
} // namespace Data
enum class WebPageType : uint8 {
None,
Message,
Group,
@ -67,7 +69,6 @@ struct WebPageData {
bool applyChanges(
WebPageType newType,
bool newHasLargeMedia,
const QString &newUrl,
const QString &newDisplayUrl,
const QString &newSiteName,
@ -79,6 +80,7 @@ struct WebPageData {
WebPageCollage &&newCollage,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
int newPendingTill);
static void ApplyChanges(
@ -86,9 +88,10 @@ struct WebPageData {
ChannelData *channel,
const MTPmessages_Messages &result);
WebPageId id = 0;
WebPageType type = WebPageType::Article;
bool hasLargeMedia = false;
[[nodiscard]] QString displayedSiteName() const;
const WebPageId id = 0;
WebPageType type = WebPageType::None;
QString url;
QString displayUrl;
QString siteName;
@ -101,7 +104,8 @@ struct WebPageData {
WebPageCollage collage;
int duration = 0;
TimeId pendingTill = 0;
uint32 version : 31 = 0;
uint32 version : 30 = 0;
uint32 hasLargeMedia : 1 = 0;
uint32 failed : 1 = 0;
private:

View File

@ -1509,6 +1509,11 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
} else {
_flags &= ~MessageFlag::HideEdited;
}
if (edition.invertMedia) {
_flags |= MessageFlag::InvertMedia;
} else {
_flags &= ~MessageFlag::InvertMedia;
}
if (edition.editDate != -1) {
//_flags |= MTPDmessage::Flag::f_edit_date;

View File

@ -29,6 +29,7 @@ HistoryMessageEdition::HistoryMessageEdition(
if (const auto mtpReplies = message.vreplies()) {
replies = HistoryMessageRepliesData(mtpReplies);
}
invertMedia = message.is_invert_media();
const auto period = message.vttl_period();
ttl = (period && period->v > 0) ? (message.vdate().v + period->v) : 0;

View File

@ -30,6 +30,7 @@ struct HistoryMessageEdition {
bool useSameMarkup = false;
bool useSameReactions = false;
bool savePreviousMedia = false;
bool invertMedia = false;
TextWithEntities textWithEntities;
HistoryMessageMarkupData replyMarkup;
HistoryMessageRepliesData replies;

View File

@ -2149,6 +2149,7 @@ void HistoryWidget::showHistory(
_photoEditMedia = nullptr;
updateReplaceMediaButton();
_previewData = nullptr;
_previewDraft = {};
_previewCache.clear();
_fieldBarCancel->hide();
@ -3761,12 +3762,6 @@ void HistoryWidget::saveEditMsg() {
cancelEdit();
return;
}
const auto webPageId = _previewDraft.removed
? CancelledWebPageId
: (_previewData && !_previewData->failed)
? _previewData->id
: WebPageId();
const auto textWithTags = _field->getTextWithAppliedMarkdown();
const auto prepareFlags = Ui::ItemTextOptions(
_history,
@ -3833,10 +3828,10 @@ void HistoryWidget::saveEditMsg() {
};
auto options = Api::SendOptions();
options.removeWebPageId = (webPageId == CancelledWebPageId);
_saveEditMsgRequestId = Api::EditTextMessage(
item,
sending,
_previewDraft,
options,
done,
fail);
@ -3920,15 +3915,9 @@ void HistoryWidget::send(Api::SendOptions options) {
_cornerButtons.clearReplyReturns();
}
const auto webPageId = _previewDraft.removed
? CancelledWebPageId
: (_previewData && !_previewData->failed)
? _previewData->id
: WebPageId();
auto message = Api::MessageToSend(prepareSendAction(options));
message.textWithTags = _field->getTextWithAppliedMarkdown();
message.webPageId = webPageId;
message.webPage = _previewDraft;
const auto ignoreSlowmodeCountdown = (options.scheduled != 0);
if (showSendMessageError(
@ -4338,23 +4327,23 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) {
void HistoryWidget::updateOverStates(QPoint pos) {
const auto isReadyToForward = readyToForward();
const auto skip = isReadyToForward ? 0 : st::historyReplySkip;
const auto replyEditForwardInfoRect = QRect(
const auto detailsRect = QRect(
skip,
_field->y() - st::historySendPadding - st::historyReplyHeight,
width() - skip - _fieldBarCancel->width(),
st::historyReplyHeight);
auto inReplyEditForward = (_editMsgId || replyTo() || isReadyToForward)
&& replyEditForwardInfoRect.contains(pos);
auto inPhotoEdit = inReplyEditForward
const auto hasWebPage = _previewData && !_previewData->failed;
const auto inDetails = detailsRect.contains(pos)
&& (_editMsgId || replyTo() || isReadyToForward || hasWebPage);
const auto inPhotoEdit = inDetails
&& _photoEditMedia
&& QRect(
replyEditForwardInfoRect.x(),
(replyEditForwardInfoRect.y()
+ (replyEditForwardInfoRect.height()
- st::historyReplyPreview) / 2),
detailsRect.x(),
(detailsRect.y()
+ (detailsRect.height() - st::historyReplyPreview) / 2),
st::historyReplyPreview,
st::historyReplyPreview).contains(pos);
auto inClickable = inReplyEditForward;
const auto inClickable = inDetails;
if (_inPhotoEdit != inPhotoEdit) {
_inPhotoEdit = inPhotoEdit;
if (_photoEditMedia) {
@ -4367,7 +4356,7 @@ void HistoryWidget::updateOverStates(QPoint pos) {
_inPhotoEditOver.stop();
}
}
_inReplyEditForward = inReplyEditForward && !inPhotoEdit;
_inDetails = inDetails && !inPhotoEdit;
if (inClickable != _inClickable) {
_inClickable = inClickable;
setCursor(_inClickable ? style::cur_pointer : style::cur_default);
@ -4605,6 +4594,9 @@ bool HistoryWidget::showRecordButton() const {
&& !_voiceRecordBar->isListenState()
&& !_voiceRecordBar->isRecordingByAnotherBar()
&& !HasSendText(_field)
&& (!_previewData
|| _previewData->failed
|| _previewData->pendingTill)
&& !readyToForward()
&& !_editMsgId;
}
@ -6224,42 +6216,43 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
{ _history->peer->id, _editMsgId },
_field->getTextWithTags(),
crl::guard(_list, [=] { cancelEdit(); }));
} else if (_inReplyEditForward) {
if (isReadyToForward) {
if (e->button() != Qt::LeftButton) {
_forwardPanel->editToNextOption();
} else {
_forwardPanel->editOptions(controller()->uiShow());
}
} else if (const auto reply = replyTo()) {
const auto highlight = [=] {
controller()->showPeerHistory(
reply.messageId.peer,
Window::SectionShow::Way::Forward,
reply.messageId.msg);
};
const auto history = _history;
using namespace HistoryView::Controls;
EditReplyOptions(
controller()->uiShow(),
reply,
highlight,
[=] { ClearDraftReplyTo(history, reply.messageId); });
} else if (_editMsgId) {
controller()->showPeerHistory(
_peer,
Window::SectionShow::Way::Forward,
_editMsgId);
} else if (_previewData
&& !_previewData->failed
&& !_previewData->pendingTill) {
//const auto history = _history;
//using namespace HistoryView::Controls;
//EditWebPageOptions(
// controller()->uiShow(),
// _previewData,
// _previewDraft);
} else if (!_inDetails) {
return;
} else if (_previewData
&& !_previewData->failed
&& !_previewData->pendingTill) {
const auto history = _history;
using namespace HistoryView::Controls;
EditWebPageOptions(
controller()->uiShow(),
_previewData,
_previewDraft,
[=](Data::WebPageDraft draft) { applyPreview(draft); });
} else if (isReadyToForward) {
if (e->button() != Qt::LeftButton) {
_forwardPanel->editToNextOption();
} else {
_forwardPanel->editOptions(controller()->uiShow());
}
} else if (const auto reply = replyTo()) {
const auto highlight = [=] {
controller()->showPeerHistory(
reply.messageId.peer,
Window::SectionShow::Way::Forward,
reply.messageId.msg);
};
const auto history = _history;
using namespace HistoryView::Controls;
EditReplyOptions(
controller()->uiShow(),
reply,
highlight,
[=] { ClearDraftReplyTo(history, reply.messageId); });
} else if (_editMsgId) {
controller()->showPeerHistory(
_peer,
Window::SectionShow::Way::Forward,
_editMsgId);
}
}
@ -7073,8 +7066,10 @@ void HistoryWidget::setFieldText(
_textUpdateEvents = TextUpdateEvent::SaveDraft
| TextUpdateEvent::SendTyping;
previewCancel();
_previewDraft = {};
if (!_previewDraft.manual) {
previewCancel();
_previewDraft = {};
}
}
void HistoryWidget::clearFieldText(
@ -7424,12 +7419,7 @@ void HistoryWidget::cancelFieldAreaState() {
controller()->hideLayer();
_replyForwardPressed = false;
if (_previewData && !_previewData->failed) {
_previewDraft = { .removed = true };
previewCancel();
_saveDraftText = true;
_saveDraftStart = crl::now();
saveDraft();
applyPreview({ .removed = true });
} else if (_editMsgId) {
cancelEdit();
} else if (readyToForward()) {
@ -7441,6 +7431,20 @@ void HistoryWidget::cancelFieldAreaState() {
}
}
void HistoryWidget::applyPreview(Data::WebPageDraft draft) {
_previewDraft = draft;
if (draft.removed) {
previewCancel();
} else if (draft.id) {
_previewData = session().data().webpage(draft.id).get();
requestPreview();
}
_saveDraftText = true;
_saveDraftStart = crl::now();
saveDraft();
}
void HistoryWidget::previewCancel() {
_api.request(base::take(_previewRequest)).cancel();
_previewData = nullptr;
@ -7455,6 +7459,8 @@ void HistoryWidget::checkPreview() {
if (_previewDraft.removed || previewRestricted) {
previewCancel();
return;
} else if (_previewDraft.manual) {
return;
}
const auto links = _parsedLinks.join(' ');
if (_previewLinks != links) {
@ -7476,6 +7482,8 @@ void HistoryWidget::checkPreview() {
}).send();
} else if (i.value()) {
_previewData = session().data().webpage(i.value());
_previewDraft.id = _previewData->id;
_previewDraft.url = _previewData->url;
updatePreview();
} else if (_previewData && !_previewData->failed) {
previewCancel();
@ -7518,6 +7526,12 @@ void HistoryWidget::gotPreview(
_previewData = (page->id && !page->failed)
? page.get()
: nullptr;
if (_previewData) {
_previewDraft.id = _previewData->id;
_previewDraft.url = _previewData->url;
} else {
_previewDraft = {};
}
updatePreview();
}
session().data().sendWebPageGamePollNotifications();
@ -7525,6 +7539,7 @@ void HistoryWidget::gotPreview(
_previewCache.insert(links, 0);
if (links == _previewLinks && !_previewDraft.removed) {
_previewData = nullptr;
_previewDraft = {};
updatePreview();
}
}

View File

@ -406,6 +406,7 @@ private:
void startBotCommand();
void hidePinnedMessage();
void cancelFieldAreaState();
void applyPreview(Data::WebPageDraft draft);
void unblockUser();
void sendBotStartCommand();
void joinChannel();
@ -759,7 +760,7 @@ private:
object_ptr<Ui::InputField> _field;
base::unique_qptr<Ui::RpWidget> _fieldDisabled;
Ui::Animations::Simple _inPhotoEditOver;
bool _inReplyEditForward = false;
bool _inDetails = false;
bool _inPhotoEdit = false;
bool _inClickable = false;

View File

@ -365,7 +365,7 @@ public:
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
[[nodiscard]] rpl::producer<> editPhotoRequests() const;
[[nodiscard]] MessageToEdit queryToEdit();
[[nodiscard]] WebPageId webPageId() const;
[[nodiscard]] Data::WebPageDraft webPageDraft() const;
[[nodiscard]] FullReplyTo getDraftReply() const;
[[nodiscard]] rpl::producer<> editCancelled() const {
@ -399,6 +399,7 @@ private:
struct Preview {
WebPageData *data = nullptr;
Data::WebPageDraft draft;
Ui::Text::String title;
Ui::Text::String description;
bool cancelled = false;
@ -958,8 +959,10 @@ bool FieldHeader::hasPreview() const {
return ShowWebPagePreview(_preview.data);
}
WebPageId FieldHeader::webPageId() const {
return hasPreview() ? _preview.data->id : CancelledWebPageId;
Data::WebPageDraft FieldHeader::webPageDraft() const {
return hasPreview()
? Data::WebPageDraft{ .id = _preview.data->id }
: Data::WebPageDraft{ .removed = true };
}
FullReplyTo FieldHeader::getDraftReply() const {
@ -1027,10 +1030,7 @@ MessageToEdit FieldHeader::queryToEdit() {
}
return {
.fullId = item->fullId(),
.options = {
.scheduled = item->isScheduled() ? item->date() : 0,
.removeWebPageId = !hasPreview(),
},
.options = { .scheduled = item->isScheduled() ? item->date() : 0 },
};
}
@ -3119,8 +3119,8 @@ void ComposeControls::initForwardProcess() {
updateForwarding();
}
WebPageId ComposeControls::webPageId() const {
return _header->webPageId();
Data::WebPageDraft ComposeControls::webPageDraft() const {
return _header->webPageDraft();
}
rpl::producer<Data::MessagePosition> ComposeControls::scrollRequests() const {

View File

@ -44,6 +44,7 @@ struct MessagePosition;
struct Draft;
class DraftKey;
class PhotoMedia;
struct WebPageDraft;
} // namespace Data
namespace InlineBots {
@ -207,7 +208,7 @@ public:
void tryProcessKeyInput(not_null<QKeyEvent*> e);
[[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
[[nodiscard]] WebPageId webPageId() const;
[[nodiscard]] Data::WebPageDraft webPageDraft() const;
void setText(const TextWithTags &text);
void clear();
void hidePanelsAnimated();

View File

@ -547,4 +547,87 @@ void EditReplyOptions(
}));
}
void EditWebPageOptions(
std::shared_ptr<ChatHelpers::Show> show,
not_null<WebPageData*> webpage,
Data::WebPageDraft draft,
Fn<void(Data::WebPageDraft)> done) {
show->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(rpl::single(u"Link Preview"_q));
struct State {
rpl::variable<Data::WebPageDraft> result;
Ui::SettingsButton *large = nullptr;
Ui::SettingsButton *small = nullptr;
};
const auto state = box->lifetime().make_state<State>(State{
.result = draft,
});
state->large = Settings::AddButton(
box->verticalLayout(),
rpl::single(u"Force large media"_q),
st::settingsButton,
{ &st::menuIconMakeBig });
state->large->setClickedCallback([=] {
auto copy = state->result.current();
copy.forceLargeMedia = true;
copy.forceSmallMedia = false;
state->result = copy;
});
state->small = Settings::AddButton(
box->verticalLayout(),
rpl::single(u"Force small media"_q),
st::settingsButton,
{ &st::menuIconMakeSmall });
state->small->setClickedCallback([=] {
auto copy = state->result.current();
copy.forceSmallMedia = true;
copy.forceLargeMedia = false;
state->result = copy;
});
state->result.value(
) | rpl::start_with_next([=](const Data::WebPageDraft &draft) {
state->large->setColorOverride(draft.forceLargeMedia
? st::windowActiveTextFg->c
: std::optional<QColor>());
state->small->setColorOverride(draft.forceSmallMedia
? st::windowActiveTextFg->c
: std::optional<QColor>());
}, box->lifetime());
Settings::AddButton(
box->verticalLayout(),
state->result.value(
) | rpl::map([=](const Data::WebPageDraft &draft) {
return draft.invert
? u"Above message"_q
: u"Below message"_q;
}),
st::settingsButton,
{ &st::menuIconChangeOrder }
)->setClickedCallback([=] {
auto copy = state->result.current();
copy.invert = !copy.invert;
state->result = copy;
});
box->addButton(tr::lng_settings_save(), [=] {
const auto weak = Ui::MakeWeak(box.get());
auto result = state->result.current();
result.manual = true;
done(result);
if (const auto strong = weak.data()) {
strong->closeBox();
}
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}));
}
} // namespace HistoryView::Controls

View File

@ -20,6 +20,7 @@ class SpoilerAnimation;
namespace Data {
class Thread;
struct WebPageDraft;
} // namespace Data
namespace Window {
@ -88,4 +89,10 @@ void EditReplyOptions(
Fn<void()> highlight,
Fn<void()> clearOldDraft = nullptr);
void EditWebPageOptions(
std::shared_ptr<ChatHelpers::Show> show,
not_null<WebPageData*> webpage,
Data::WebPageDraft draft,
Fn<void(Data::WebPageDraft)> done);
} // namespace HistoryView::Controls

View File

@ -539,9 +539,6 @@ bool AddRescheduleAction(
if (!item || !item->isScheduled()) {
continue;
}
if (!item->media() || !item->media()->webpage()) {
options.removeWebPageId = true;
}
Api::RescheduleMessage(item, options);
// Increase the scheduled date by 1s to keep the order.
options.scheduled += 1;

View File

@ -2568,25 +2568,38 @@ void Message::updatePressed(QPoint point) {
TextForMimeData Message::selectedText(TextSelection selection) const {
const auto media = this->media();
auto logEntryOriginalResult = TextForMimeData();
const auto mediaDisplayed = (media && media->isDisplayed());
const auto textSelection = (mediaDisplayed && invertMedia())
? media->skipSelection(selection)
: selection;
const auto mediaSelection = !invertMedia()
? skipTextSelection(selection)
: selection;
auto textResult = hasVisibleText()
? text().toTextForMimeData(selection)
? text().toTextForMimeData(textSelection)
: TextForMimeData();
auto skipped = skipTextSelection(selection);
auto mediaDisplayed = (media && media->isDisplayed());
auto mediaResult = (mediaDisplayed || isHiddenByGroup())
? media->selectedText(skipped)
? media->selectedText(mediaSelection)
: TextForMimeData();
if (auto entry = logEntryOriginal()) {
const auto originalSelection = mediaDisplayed
? media->skipSelection(skipped)
: skipped;
const auto originalSelection = (mediaDisplayed && invertMedia())
? skipTextSelection(textSelection)
: mediaDisplayed
? media->skipSelection(mediaSelection)
: skipTextSelection(selection);
logEntryOriginalResult = entry->selectedText(originalSelection);
}
auto result = textResult;
auto &first = (mediaDisplayed && invertMedia())
? mediaResult
: textResult;
auto &second = (mediaDisplayed && invertMedia())
? textResult
: mediaResult;
auto result = first;
if (result.empty()) {
result = std::move(mediaResult);
} else if (!mediaResult.empty()) {
result.append(u"\n\n"_q).append(std::move(mediaResult));
result = std::move(second);
} else if (!second.empty()) {
result.append(u"\n\n"_q).append(std::move(second));
}
if (result.empty()) {
result = std::move(logEntryOriginalResult);

View File

@ -1170,11 +1170,9 @@ void RepliesWidget::send(Api::SendOptions options) {
_cornerButtons.clearReplyReturns();
}
const auto webPageId = _composeControls->webPageId();
auto message = Api::MessageToSend(prepareSendAction(options));
message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
message.webPageId = webPageId;
message.webPage = _composeControls->webPageDraft();
const auto error = GetErrorTextForSending(
_history->peer,
@ -1213,6 +1211,7 @@ void RepliesWidget::edit(
return;
}
const auto textWithTags = _composeControls->getTextWithAppliedMarkdown();
const auto webpage = _composeControls->webPageDraft();
const auto prepareFlags = Ui::ItemTextOptions(
_history,
session().user()).flags;
@ -1274,6 +1273,7 @@ void RepliesWidget::edit(
*saveEditMsgRequestId = Api::EditTextMessage(
item,
sending,
webpage,
options,
crl::guard(this, done),
crl::guard(this, fail));

View File

@ -585,11 +585,11 @@ void ScheduledWidget::send() {
}
void ScheduledWidget::send(Api::SendOptions options) {
const auto webPageId = _composeControls->webPageId();
const auto webPageDraft = _composeControls->webPageDraft();
auto message = Api::MessageToSend(prepareSendAction(options));
message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
message.webPageId = webPageId;
message.webPage = webPageDraft;
session().api().sendMessage(std::move(message));
@ -635,6 +635,7 @@ void ScheduledWidget::edit(
return;
}
const auto textWithTags = _composeControls->getTextWithAppliedMarkdown();
const auto webpage = _composeControls->webPageDraft();
const auto prepareFlags = Ui::ItemTextOptions(
_history,
session().user()).flags;
@ -696,6 +697,7 @@ void ScheduledWidget::edit(
*saveEditMsgRequestId = Api::EditTextMessage(
item,
sending,
webpage,
options,
crl::guard(this, done),
crl::guard(this, fail));

View File

@ -308,11 +308,12 @@ QSize WebPage::countOptimalSize() {
Ui::WebpageTextDescriptionOptions(),
context);
}
if (!displayedSiteName().isEmpty()) {
const auto siteName = _data->displayedSiteName();
if (!siteName.isEmpty()) {
_siteNameLines = 1;
_siteName.setMarkedText(
st::webPageTitleStyle,
Ui::Text::Link(displayedSiteName(), _data->url),
Ui::Text::Link(siteName, _data->url),
Ui::WebpageTextTitleOptions());
}
if (_title.isEmpty() && !title.isEmpty()) {
@ -868,6 +869,10 @@ TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType t
return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to };
}
uint16 WebPage::fullSelectionLength() const {
return _siteName.length() + _title.length() + _description.length();
}
void WebPage::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
@ -994,14 +999,6 @@ int WebPage::bottomInfoPadding() const {
return result;
}
QString WebPage::displayedSiteName() const {
return (_data->document && _data->document->isWallPaper())
? tr::lng_media_chat_background(tr::now)
: (_data->document && _data->document->isTheme())
? tr::lng_media_color_theme(tr::now)
: _data->siteName;
}
WebPage::~WebPage() {
history()->owner().unregisterWebPageView(_data, _parent);
if (_photoMedia) {

View File

@ -41,9 +41,7 @@ public:
[[nodiscard]] TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override;
uint16 fullSelectionLength() const override {
return _title.length() + _description.length();
}
uint16 fullSelectionLength() const override;
bool hasTextForCopy() const override {
// We do not add _title and _description in FullSelection text copy.
return false;
@ -119,7 +117,6 @@ private:
[[nodiscard]] int bottomInfoPadding() const;
[[nodiscard]] bool isLogEntryOriginal() const;
[[nodiscard]] QString displayedSiteName() const;
[[nodiscard]] ClickHandlerPtr replaceAttachLink(
const ClickHandlerPtr &link) const;
[[nodiscard]] bool asArticle() const;

View File

@ -193,11 +193,11 @@ void ReplyArea::sendReaction(const Data::ReactionId &id) {
}
void ReplyArea::send(Api::SendOptions options) {
const auto webPageId = _controls->webPageId();
const auto webPageDraft = _controls->webPageDraft();
auto message = Api::MessageToSend(prepareSendAction(options));
message.textWithTags = _controls->getTextWithAppliedMarkdown();
message.webPageId = webPageId;
message.webPage = webPageDraft;
send(std::move(message), options);
}

View File

@ -202,3 +202,7 @@ mediaSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewMenuFg
mediaSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewTextLinkFg }};
mediaSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewMenuFg }};
mediaSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewTextLinkFg }};
menuIconMakeBig: icon {{ "player/player_fullscreen", menuIconColor }};
menuIconMakeSmall: icon {{ "player/player_minimize", menuIconColor }};
menuIconChangeOrder: icon {{ "player/player_order", menuIconColor }};