mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-20 08:03:45 +08:00
Enable rich editing for template messages
Task: 2026/07/23/enable-rich-template-message-editing
This commit is contained in:
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/components/welcome_messages.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
@@ -534,6 +535,25 @@ mtpRequestId EditTextMessage(
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail,
|
||||
bool spoilered,
|
||||
VideoCoverEdit videoCover) {
|
||||
if (item->isWelcomeTemplate()) {
|
||||
const auto history = item->history();
|
||||
auto &welcome = history->session().welcomeMessages();
|
||||
welcome.edit(
|
||||
history,
|
||||
welcome.lookupId(item),
|
||||
caption,
|
||||
[=] {
|
||||
if (done) {
|
||||
done(0);
|
||||
}
|
||||
},
|
||||
[=](const QString &error) {
|
||||
if (fail) {
|
||||
fail(error, 0);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
const auto media = item->media();
|
||||
const auto spoilerChanged = media
|
||||
&& HistoryView::MediaEditManager::CanBeSpoilered(item)
|
||||
@@ -686,6 +706,25 @@ mtpRequestId EditRichMessage(
|
||||
SendOptions options,
|
||||
Fn<void(mtpRequestId requestId)> done,
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail) {
|
||||
if (item->isWelcomeTemplate()) {
|
||||
const auto history = item->history();
|
||||
auto &welcome = history->session().welcomeMessages();
|
||||
welcome.editRich(
|
||||
history,
|
||||
welcome.lookupId(item),
|
||||
std::move(richMessage),
|
||||
[=] {
|
||||
if (done) {
|
||||
done(0);
|
||||
}
|
||||
},
|
||||
[=](const QString &error) {
|
||||
if (fail) {
|
||||
fail(error, 0);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
const auto session = &item->history()->session();
|
||||
const auto api = &session->api();
|
||||
const auto sentEntities = MTPVector<MTPMessageEntity>();
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_edition.h"
|
||||
#include "iv/iv_rich_page.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -181,6 +182,54 @@ void WelcomeMessages::send(
|
||||
}).send();
|
||||
}
|
||||
|
||||
void WelcomeMessages::sendRich(
|
||||
not_null<History*> history,
|
||||
Fn<std::optional<MTPInputRichMessage>()> richMessage) {
|
||||
auto first = richMessage
|
||||
? richMessage()
|
||||
: std::optional<MTPInputRichMessage>();
|
||||
if (!first) {
|
||||
LOG(("API Error: no rich welcome template to send."));
|
||||
return;
|
||||
}
|
||||
using Flag = MTPephemeral_SendMessage::Flag;
|
||||
const auto flags = Flag::f_welcome_template | Flag::f_rich_message;
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
const auto send = [=](
|
||||
const auto &send,
|
||||
const MTPInputRichMessage &rich,
|
||||
int attempt) -> void {
|
||||
_session->api().request(MTPephemeral_SendMessage(
|
||||
MTP_flags(flags),
|
||||
history->peer->input(),
|
||||
MTP_inputUserEmpty(),
|
||||
MTPlong(), // query_id
|
||||
MTP_string(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTPInputMedia(),
|
||||
MTPReplyMarkup(),
|
||||
rich,
|
||||
MTP_long(randomId),
|
||||
MTPInputReplyTo()
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
if (!attempt
|
||||
&& (error.code() == 400)
|
||||
&& type.startsWith(u"FILE_REFERENCE_"_q)) {
|
||||
if (auto rebuilt = richMessage()) {
|
||||
send(send, *rebuilt, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG(("API Error: send rich welcome template - %1"
|
||||
).arg(type));
|
||||
}).send();
|
||||
};
|
||||
send(send, *first, 0);
|
||||
}
|
||||
|
||||
void WelcomeMessages::edit(
|
||||
not_null<History*> history,
|
||||
int32 ephemeralId,
|
||||
@@ -217,6 +266,60 @@ void WelcomeMessages::edit(
|
||||
}).send();
|
||||
}
|
||||
|
||||
void WelcomeMessages::editRich(
|
||||
not_null<History*> history,
|
||||
int32 ephemeralId,
|
||||
Fn<std::optional<MTPInputRichMessage>()> richMessage,
|
||||
Fn<void()> done,
|
||||
Fn<void(const QString &)> fail) {
|
||||
auto first = richMessage
|
||||
? richMessage()
|
||||
: std::optional<MTPInputRichMessage>();
|
||||
if (!first) {
|
||||
if (fail) {
|
||||
fail(QString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
using Flag = MTPephemeral_EditMessage::Flag;
|
||||
const auto flags = Flag::f_welcome_template | Flag::f_rich_message;
|
||||
const auto send = [=](
|
||||
const auto &send,
|
||||
const MTPInputRichMessage &rich,
|
||||
int attempt) -> void {
|
||||
_session->api().request(MTPephemeral_EditMessage(
|
||||
MTP_flags(flags),
|
||||
history->peer->input(),
|
||||
MTP_inputUserEmpty(),
|
||||
MTP_int(ephemeralId),
|
||||
MTPstring(),
|
||||
MTPInputMedia(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTPReplyMarkup(),
|
||||
rich
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
if (!attempt
|
||||
&& (error.code() == 400)
|
||||
&& type.startsWith(u"FILE_REFERENCE_"_q)) {
|
||||
if (auto rebuilt = richMessage()) {
|
||||
send(send, *rebuilt, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fail(type);
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
send(send, *first, 0);
|
||||
}
|
||||
|
||||
void WelcomeMessages::deleteTemplate(not_null<HistoryItem*> item) {
|
||||
const auto history = item->history();
|
||||
const auto id = lookupId(item);
|
||||
@@ -368,6 +471,9 @@ HistoryItem *WelcomeMessages::append(
|
||||
| (data.is_out()
|
||||
? MessageFlag::Outgoing
|
||||
: MessageFlag())
|
||||
| (data.is_invert_media()
|
||||
? MessageFlag::InvertMedia
|
||||
: MessageFlag())
|
||||
| (fromId ? MessageFlag::HasFromId : MessageFlag())
|
||||
| (data.vreply_markup()
|
||||
? MessageFlag::HasReplyMarkup
|
||||
@@ -385,6 +491,10 @@ HistoryItem *WelcomeMessages::append(
|
||||
(data.vmedia()
|
||||
? *data.vmedia()
|
||||
: MTPMessageMedia(MTP_messageMediaEmpty())));
|
||||
if (const auto richMessage = data.vrich_message()) {
|
||||
item->applyLocalRichPage(
|
||||
Iv::ParseRichPage(_session, *richMessage));
|
||||
}
|
||||
list.items.emplace_back(item);
|
||||
list.itemById.emplace(id, item);
|
||||
return item;
|
||||
@@ -409,6 +519,10 @@ void WelcomeMessages::applyEdition(
|
||||
};
|
||||
edition.replyMarkup = HistoryMessageMarkupData(data.vreply_markup());
|
||||
edition.mtpMedia = data.vmedia();
|
||||
edition.invertMedia = data.is_invert_media();
|
||||
if (const auto richMessage = data.vrich_message()) {
|
||||
edition.richPage = Iv::ParseRichPage(_session, *richMessage);
|
||||
}
|
||||
item->applyEdition(std::move(edition));
|
||||
}
|
||||
|
||||
|
||||
@@ -44,12 +44,21 @@ public:
|
||||
bool applyDelete(not_null<PeerData*> peer, int32 ephemeralId);
|
||||
|
||||
void send(not_null<History*> history, TextWithEntities text);
|
||||
void sendRich(
|
||||
not_null<History*> history,
|
||||
Fn<std::optional<MTPInputRichMessage>()> richMessage);
|
||||
void edit(
|
||||
not_null<History*> history,
|
||||
int32 ephemeralId,
|
||||
TextWithEntities text,
|
||||
Fn<void()> done,
|
||||
Fn<void(const QString &)> fail);
|
||||
void editRich(
|
||||
not_null<History*> history,
|
||||
int32 ephemeralId,
|
||||
Fn<std::optional<MTPInputRichMessage>()> richMessage,
|
||||
Fn<void()> done,
|
||||
Fn<void(const QString &)> fail);
|
||||
void deleteTemplate(not_null<HistoryItem*> item);
|
||||
void deleteAll(not_null<History*> history);
|
||||
|
||||
|
||||
@@ -1446,17 +1446,10 @@ void HistoryWidget::showRichEditor() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto action = prepareSendAction({});
|
||||
if (ShowEphemeralReplyTextOnlyError(
|
||||
window->uiShow(),
|
||||
&session(),
|
||||
action.replyTo.messageId)) {
|
||||
return;
|
||||
}
|
||||
Iv::Editor::ShowComposeBox(
|
||||
window,
|
||||
_history->peer,
|
||||
std::move(action),
|
||||
prepareSendAction({}),
|
||||
sendMenuDetails(),
|
||||
_field->getTextWithAppliedMarkdown(),
|
||||
crl::guard(this, [=] {
|
||||
|
||||
@@ -67,7 +67,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/power_saving.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/view/controls/history_view_characters_limit.h"
|
||||
#include "history/view/controls/history_view_compose_ai_button.h"
|
||||
#include "history/view/controls/history_view_compose_ai_tooltip.h"
|
||||
@@ -2162,6 +2161,15 @@ bool ComposeControls::isShortcutComposeEligible() const {
|
||||
&& (_shortcutId > 0);
|
||||
}
|
||||
|
||||
bool ComposeControls::isWelcomeComposeEligible() const {
|
||||
return _features.richEditor
|
||||
&& _history
|
||||
&& !isEditingMessage()
|
||||
&& (_mode == Mode::Normal)
|
||||
&& (_currentDialogsEntryState.section
|
||||
== Dialogs::EntryState::Section::WelcomeMessages);
|
||||
}
|
||||
|
||||
bool ComposeControls::hasEditDraft() const {
|
||||
return _history
|
||||
&& (_history->draft(draftKey(DraftType::Edit)) != nullptr);
|
||||
@@ -2257,6 +2265,16 @@ void ComposeControls::migrateShortcutFieldToRichEditor(
|
||||
_history->clearDraft(Data::DraftKey::Shortcut(expectedShortcutId));
|
||||
}
|
||||
|
||||
void ComposeControls::migrateWelcomeFieldToRichEditor() {
|
||||
if (!isWelcomeComposeEligible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelPendingDraftSaves();
|
||||
clearFieldText();
|
||||
_history->clearDraft(Data::DraftKey::WelcomeMessages());
|
||||
}
|
||||
|
||||
void ComposeControls::clearFieldText(
|
||||
TextUpdateEvents events,
|
||||
FieldHistoryAction fieldHistoryAction) {
|
||||
@@ -3872,22 +3890,12 @@ void ComposeControls::showRichEditor() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto ephemeralError = [&](const Api::SendAction &action) {
|
||||
return ShowEphemeralReplyTextOnlyError(
|
||||
_show,
|
||||
&_show->session(),
|
||||
action.replyTo.messageId);
|
||||
};
|
||||
if (_mode == Mode::Scheduled) {
|
||||
auto action = _sendActionFactory();
|
||||
if (ephemeralError(action)) {
|
||||
return;
|
||||
}
|
||||
using Options = Iv::Editor::ComposeBoxOptions;
|
||||
Iv::Editor::ShowComposeBox(
|
||||
_regularWindow,
|
||||
_history->peer,
|
||||
std::move(action),
|
||||
_sendActionFactory(),
|
||||
sendMenuDetails(),
|
||||
getTextWithAppliedMarkdown(),
|
||||
crl::guard(_wrap.get(), [=] {
|
||||
@@ -3916,9 +3924,6 @@ void ComposeControls::showRichEditor() {
|
||||
|| action.options.shortcutId != expectedShortcutId) {
|
||||
return;
|
||||
}
|
||||
if (ephemeralError(action)) {
|
||||
return;
|
||||
}
|
||||
auto fieldText = getTextWithAppliedMarkdown();
|
||||
using Options = Iv::Editor::ComposeBoxOptions;
|
||||
Iv::Editor::ShowComposeBox(
|
||||
@@ -3943,17 +3948,41 @@ void ComposeControls::showRichEditor() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (_mode != Mode::Normal || !hasRichDraftThreadScope()) {
|
||||
if (_currentDialogsEntryState.section
|
||||
== Dialogs::EntryState::Section::WelcomeMessages) {
|
||||
if (!isWelcomeComposeEligible()) {
|
||||
return;
|
||||
}
|
||||
using Options = Iv::Editor::ComposeBoxOptions;
|
||||
Iv::Editor::ShowComposeBox(
|
||||
_regularWindow,
|
||||
_history->peer,
|
||||
_sendActionFactory(),
|
||||
sendMenuDetails(),
|
||||
getTextWithAppliedMarkdown(),
|
||||
crl::guard(_wrap.get(), [=] {
|
||||
migrateWelcomeFieldToRichEditor();
|
||||
}),
|
||||
Options{
|
||||
.scope = Options::Scope::Detached,
|
||||
.returnText = crl::guard(
|
||||
_wrap.get(),
|
||||
[=](TextWithTags text) {
|
||||
if (isWelcomeComposeEligible()) {
|
||||
setText(text);
|
||||
}
|
||||
}),
|
||||
.welcomeTemplates = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto action = _sendActionFactory();
|
||||
if (ephemeralError(action)) {
|
||||
if (_mode != Mode::Normal || !hasRichDraftThreadScope()) {
|
||||
return;
|
||||
}
|
||||
Iv::Editor::ShowComposeBox(
|
||||
_regularWindow,
|
||||
_history->peer,
|
||||
std::move(action),
|
||||
_sendActionFactory(),
|
||||
sendMenuDetails(),
|
||||
getTextWithAppliedMarkdown(),
|
||||
crl::guard(_wrap.get(), [=] {
|
||||
@@ -4334,7 +4363,8 @@ bool ComposeControls::canShowRichEditor() const {
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto composeEligible = (_mode == Mode::Scheduled)
|
||||
|| ((_mode == Mode::Normal) && hasRichDraftThreadScope())
|
||||
|| isShortcutComposeEligible();
|
||||
|| isShortcutComposeEligible()
|
||||
|| isWelcomeComposeEligible();
|
||||
return _history
|
||||
&& _regularWindow
|
||||
&& _sendActionFactory
|
||||
|
||||
@@ -453,6 +453,7 @@ private:
|
||||
[[nodiscard]] bool isComposeBoxOpen() const;
|
||||
[[nodiscard]] bool hasRichDraftThreadScope() const;
|
||||
[[nodiscard]] bool isShortcutComposeEligible() const;
|
||||
[[nodiscard]] bool isWelcomeComposeEligible() const;
|
||||
[[nodiscard]] bool bypassNormalDraftHandling() const;
|
||||
[[nodiscard]] bool hasEditDraft() const;
|
||||
[[nodiscard]] bool shouldShowRichDraftPreview() const;
|
||||
@@ -461,6 +462,7 @@ private:
|
||||
void migrateScheduledFieldToRichEditor();
|
||||
void migrateShortcutFieldToRichEditor(
|
||||
BusinessShortcutId expectedShortcutId);
|
||||
void migrateWelcomeFieldToRichEditor();
|
||||
|
||||
const style::ComposeControls &_st;
|
||||
ChatHelpers::ComposeFeatures _features;
|
||||
|
||||
@@ -204,6 +204,7 @@ void WelcomeMessagesWidget::setupComposeControls() {
|
||||
});
|
||||
_composeControls->setHistory({
|
||||
.history = _history.get(),
|
||||
.sendActionFactory = [=] { return Api::SendAction(_history); },
|
||||
.writeRestriction = std::move(writeRestriction),
|
||||
});
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_file_origin.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "data/components/ephemeral_messages.h"
|
||||
#include "data/components/welcome_messages.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -905,6 +906,27 @@ private:
|
||||
== ComposeBoxOptions::Scope::Detached;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool welcomeTemplatesCompose() const {
|
||||
return (_mode == Mode::Compose)
|
||||
&& _composeOptions.welcomeTemplates
|
||||
&& _composeAction;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool welcomeTemplatesLimitReached() const {
|
||||
Expects(_composeAction.has_value());
|
||||
|
||||
const auto history = _composeAction->history;
|
||||
return _session->welcomeMessages().count(history)
|
||||
>= ::Data::WelcomeMessagesLimit(_session);
|
||||
}
|
||||
|
||||
void showWelcomeTemplatesLimitToast() const {
|
||||
showToast(tr::lng_business_limit_reached(
|
||||
tr::now,
|
||||
lt_count,
|
||||
::Data::WelcomeMessagesLimit(_session)));
|
||||
}
|
||||
|
||||
void dropDetachedReturnText() {
|
||||
if (detachedCompose()) {
|
||||
(void)base::take(_composeOptions.returnText);
|
||||
@@ -991,6 +1013,7 @@ private:
|
||||
if (_mode != Mode::Compose
|
||||
|| !_composeAction
|
||||
|| _submitOptions.scheduled
|
||||
|| welcomeTemplatesCompose()
|
||||
|| submitWouldBeEphemeral(simple)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1065,6 +1088,10 @@ private:
|
||||
showRichMessageLimitToast(*error);
|
||||
return false;
|
||||
}
|
||||
if (welcomeTemplatesCompose() && welcomeTemplatesLimitReached()) {
|
||||
showWelcomeTemplatesLimitToast();
|
||||
return false;
|
||||
}
|
||||
_submittedPage = page;
|
||||
if (submittedAttachmentsReady()
|
||||
&& serializeSubmittedPage().status
|
||||
@@ -1095,6 +1122,18 @@ private:
|
||||
showToast(tr::lng_edit_error(tr::now));
|
||||
return false;
|
||||
}
|
||||
if (welcomeTemplatesCompose()) {
|
||||
if (welcomeTemplatesLimitReached()) {
|
||||
showWelcomeTemplatesLimitToast();
|
||||
return false;
|
||||
}
|
||||
cancelRichDraftAutosave();
|
||||
dropDetachedReturnText();
|
||||
_session->welcomeMessages().send(
|
||||
_composeAction->history,
|
||||
std::move(text));
|
||||
return true;
|
||||
}
|
||||
auto action = *_composeAction;
|
||||
action.options = _submitOptions;
|
||||
action.clearDraft = !detachedCompose();
|
||||
@@ -1269,6 +1308,9 @@ private:
|
||||
|
||||
[[nodiscard]] bool applySubmittedLocalState(
|
||||
const std::shared_ptr<const RichPage> &page) {
|
||||
if (welcomeTemplatesCompose()) {
|
||||
return true;
|
||||
}
|
||||
const auto item = (_mode == Mode::Compose)
|
||||
? ensureComposeLocalItem()
|
||||
: currentSubmittedItem();
|
||||
@@ -1520,6 +1562,31 @@ private:
|
||||
failSubmittedWork(true);
|
||||
return;
|
||||
}
|
||||
if (welcomeTemplatesCompose()) {
|
||||
if (welcomeTemplatesLimitReached()) {
|
||||
showWelcomeTemplatesLimitToast();
|
||||
finishSubmittedWork();
|
||||
restartRichDraftAutosave();
|
||||
return;
|
||||
}
|
||||
dropDetachedReturnText();
|
||||
const auto session = _session;
|
||||
_session->welcomeMessages().sendRich(
|
||||
_composeAction->history,
|
||||
[session, page = _submittedPage] {
|
||||
auto serialized = SerializeInputRichMessage(
|
||||
session,
|
||||
*page,
|
||||
SerializeInputRichMessageMode::FinalSubmit);
|
||||
const auto success = (serialized.status
|
||||
== SerializeInputRichMessageStatus::Success);
|
||||
return (success && serialized.value)
|
||||
? std::move(serialized.value)
|
||||
: std::optional<MTPInputRichMessage>();
|
||||
});
|
||||
finishSubmittedWork();
|
||||
return;
|
||||
}
|
||||
const auto item = currentSubmittedItem();
|
||||
if (!item) {
|
||||
finishSubmittedWork();
|
||||
|
||||
@@ -60,6 +60,7 @@ struct ComposeBoxOptions {
|
||||
Scope scope = Scope::Thread;
|
||||
SubmitPolicy submitPolicy = SubmitPolicy::Immediate;
|
||||
Fn<void(TextWithTags)> returnText;
|
||||
bool welcomeTemplates = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> ActiveWindowShow(
|
||||
|
||||
Reference in New Issue
Block a user