From 5e7642b42a4d1fa85587b6463f8814754b378169 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 24 May 2018 16:03:21 +0300
Subject: [PATCH] Support markdown and replaces in media captions.

---
 Telegram/Resources/basic.style                |  2 +-
 Telegram/SourceFiles/apiwrap.cpp              | 12 +++---
 Telegram/SourceFiles/apiwrap.h                |  2 +-
 .../SourceFiles/boxes/add_contact_box.cpp     |  6 +++
 .../SourceFiles/boxes/edit_caption_box.cpp    | 29 ++++++++++---
 .../boxes/peers/edit_peer_info_box.cpp        |  2 +
 Telegram/SourceFiles/boxes/send_files_box.cpp |  9 ++--
 Telegram/SourceFiles/boxes/send_files_box.h   |  4 +-
 .../chat_helpers/message_field.cpp            | 10 +----
 Telegram/SourceFiles/facades.cpp              | 10 +++++
 Telegram/SourceFiles/facades.h                |  2 +
 .../SourceFiles/history/history_widget.cpp    | 42 ++++++++++++-------
 Telegram/SourceFiles/history/history_widget.h |  2 +-
 .../SourceFiles/media/view/mediaview.style    |  1 +
 .../platform/mac/notifications_manager_mac.mm |  5 ++-
 .../SourceFiles/storage/localimageloader.cpp  |  6 +--
 .../SourceFiles/storage/localimageloader.h    | 10 ++---
 .../SourceFiles/ui/widgets/input_fields.cpp   | 20 ++++++---
 .../SourceFiles/ui/widgets/input_fields.h     |  6 ++-
 .../window/notifications_manager.cpp          |  4 +-
 .../window/notifications_manager.h            |  5 ++-
 .../window/notifications_manager_default.cpp  |  7 +++-
 22 files changed, 133 insertions(+), 63 deletions(-)

diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style
index 0515d0140..5ff0320d8 100644
--- a/Telegram/Resources/basic.style
+++ b/Telegram/Resources/basic.style
@@ -40,7 +40,7 @@ lineWidth: 1px;
 
 defaultTextPalette: TextPalette {
 	linkFg: windowActiveTextFg;
-	monoFg: windowSubTextFg;
+	monoFg: msgInMonoFg;
 	selectBg: msgInBgSelected;
 	selectFg: transparent; // use painter current pen instead
 	selectLinkFg: historyLinkInFgSelected;
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index b2bbb5b4a..5b78f61aa 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -3675,7 +3675,7 @@ void ApiWrap::sendVoiceMessage(
 		VoiceWaveform waveform,
 		int duration,
 		const SendOptions &options) {
-	const auto caption = QString();
+	const auto caption = TextWithTags();
 	const auto to = FileLoadTaskOptions(options);
 	_fileLoader->addTask(std::make_unique<FileLoadTask>(
 		result,
@@ -3688,16 +3688,16 @@ void ApiWrap::sendVoiceMessage(
 void ApiWrap::sendFiles(
 		Storage::PreparedList &&list,
 		SendMediaType type,
-		QString caption,
+		TextWithTags &&caption,
 		std::shared_ptr<SendingAlbum> album,
 		const SendOptions &options) {
-	if (list.files.size() > 1 && !caption.isEmpty()) {
+	if (list.files.size() > 1 && !caption.text.isEmpty()) {
 		auto message = MainWidget::MessageToSend(options.history);
-		message.textWithTags = { caption, TextWithTags::Tags() };
+		message.textWithTags = std::move(caption);
 		message.replyTo = options.replyTo;
 		message.clearDraft = false;
 		App::main()->sendMessage(message);
-		caption = QString();
+		caption = TextWithTags();
 	}
 
 	const auto to = FileLoadTaskOptions(options);
@@ -3742,7 +3742,7 @@ void ApiWrap::sendFile(
 		SendMediaType type,
 		const SendOptions &options) {
 	auto to = FileLoadTaskOptions(options);
-	auto caption = QString();
+	auto caption = TextWithTags();
 	_fileLoader->addTask(std::make_unique<FileLoadTask>(
 		QString(),
 		fileContent,
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 4bb74a047..fd3a8d300 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -253,7 +253,7 @@ public:
 	void sendFiles(
 		Storage::PreparedList &&list,
 		SendMediaType type,
-		QString caption,
+		TextWithTags &&caption,
 		std::shared_ptr<SendingAlbum> album,
 		const SendOptions &options);
 	void sendFile(
diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp
index 8290aa5d2..1061cac91 100644
--- a/Telegram/SourceFiles/boxes/add_contact_box.cpp
+++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp
@@ -329,6 +329,9 @@ void GroupInfoBox::prepare() {
 			langFactory(lng_create_group_description));
 		_description->show();
 		_description->setMaxLength(kMaxChannelDescription);
+		_description->setInstantReplaces(Ui::InstantReplaces::Default());
+		_description->setInstantReplacesEnabled(
+			Global::ReplaceEmojiValue());
 
 		connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized()));
 		connect(_description, SIGNAL(submitted(bool)), this, SLOT(onNext()));
@@ -1080,6 +1083,7 @@ void EditBioBox::prepare() {
 	connect(_bio, &Ui::InputField::resized, this, [this] { updateMaxHeight(); });
 	connect(_bio, &Ui::InputField::changed, this, [this] { handleBioUpdated(); });
 	_bio->setInstantReplaces(Ui::InstantReplaces::Default());
+	_bio->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
 	handleBioUpdated();
 	updateMaxHeight();
 }
@@ -1161,6 +1165,8 @@ void EditChannelBox::prepare() {
 
 	_title->setMaxLength(kMaxGroupChannelTitle);
 	_description->setMaxLength(kMaxChannelDescription);
+	_description->setInstantReplaces(Ui::InstantReplaces::Default());
+	_description->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
 
 	connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized()));
 	connect(_description, SIGNAL(submitted(bool)), this, SLOT(onSave()));
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index 43c1485c6..77070a0cf 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_photo.h"
 #include "data/data_document.h"
 #include "lang/lang_keys.h"
+#include "chat_helpers/message_field.h"
 #include "window/window_controller.h"
 #include "mainwidget.h"
 #include "layout.h"
@@ -50,7 +51,11 @@ EditCaptionBox::EditCaptionBox(
 		}
 		doc = document;
 	}
-	auto caption = item->originalText().text;
+	const auto original = item->originalText();
+	const auto editData = TextWithTags {
+		original.text,
+		ConvertEntitiesToTextTags(original.entities)
+	};
 
 	if (!_animated && (dimensions.isEmpty() || doc || image->isNull())) {
 		if (image->isNull()) {
@@ -135,10 +140,12 @@ EditCaptionBox::EditCaptionBox(
 		st::confirmCaptionArea,
 		Ui::InputField::Mode::MultiLine,
 		langFactory(lng_photo_caption),
-		caption);
+		editData);
 	_field->setMaxLength(MaxPhotoCaption);
 	_field->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
 	_field->setInstantReplaces(Ui::InstantReplaces::Default());
+	_field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
+	_field->setMarkdownReplacesEnabled(Global::ReplaceEmojiValue());
 }
 
 void EditCaptionBox::prepareGifPreview(DocumentData *document) {
@@ -338,17 +345,29 @@ void EditCaptionBox::save() {
 	if (_previewCancelled) {
 		flags |= MTPmessages_EditMessage::Flag::f_no_webpage;
 	}
-	MTPVector<MTPMessageEntity> sentEntities;
+	const auto textWithTags = _field->getTextWithTags();
+	auto sending = TextWithEntities{
+		textWithTags.text,
+		ConvertTextTagsToEntities(textWithTags.tags)
+	};
+	const auto prepareFlags = Ui::ItemTextOptions(
+		item->history(),
+		App::self()).flags;
+	TextUtilities::PrepareForSending(sending, prepareFlags);
+	TextUtilities::Trim(sending);
+
+	const auto sentEntities = TextUtilities::EntitiesToMTP(
+		sending.entities,
+		TextUtilities::ConvertOption::SkipLocal);
 	if (!sentEntities.v.isEmpty()) {
 		flags |= MTPmessages_EditMessage::Flag::f_entities;
 	}
-	auto text = TextUtilities::PrepareForSending(_field->getLastText(), TextUtilities::PrepareTextOption::CheckLinks);
 	_saveRequestId = MTP::send(
 		MTPmessages_EditMessage(
 			MTP_flags(flags),
 			item->history()->peer->input,
 			MTP_int(item->id),
-			MTP_string(text),
+			MTP_string(sending.text),
 			MTPnullMarkup,
 			sentEntities,
 			MTP_inputGeoPointEmpty()),
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index 716a99b09..ac95ffb51 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -328,6 +328,8 @@ object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
 		st::editPeerDescriptionMargins);
 	result->entity()->setMaxLength(kMaxChannelDescription);
 	result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
+	result->entity()->setInstantReplacesEnabled(
+		Global::ReplaceEmojiValue());
 
 	QObject::connect(
 		result->entity(),
diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp
index 354824d49..4336a51f1 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_files_box.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/storage_media_prepare.h"
 #include "mainwidget.h"
 #include "history/history_media_types.h"
+#include "chat_helpers/message_field.h"
 #include "core/file_utilities.h"
 #include "ui/widgets/checkbox.h"
 #include "ui/widgets/buttons.h"
@@ -1578,6 +1579,8 @@ void SendFilesBox::setupCaption() {
 		Unexpected("action in MimeData hook.");
 	});
 	_caption->setInstantReplaces(Ui::InstantReplaces::Default());
+	_caption->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
+	_caption->setMarkdownReplacesEnabled(Global::ReplaceEmojiValue());
 }
 
 void SendFilesBox::captionResized() {
@@ -1789,10 +1792,8 @@ void SendFilesBox::send(bool ctrlShiftEnter) {
 	_confirmed = true;
 	if (_confirmedCallback) {
 		auto caption = _caption
-			? TextUtilities::PrepareForSending(
-				_caption->getLastText(),
-				TextUtilities::PrepareTextOption::CheckLinks)
-			: QString();
+			? _caption->getTextWithTags()
+			: TextWithTags();
 		_confirmedCallback(
 			std::move(_list),
 			way,
diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h
index fee61dfff..76cee507e 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.h
+++ b/Telegram/SourceFiles/boxes/send_files_box.h
@@ -39,7 +39,7 @@ public:
 		base::lambda<void(
 			Storage::PreparedList &&list,
 			SendFilesWay way,
-			const QString &caption,
+			TextWithTags &&caption,
 			bool ctrlShiftEnter)> callback) {
 		_confirmedCallback = std::move(callback);
 	}
@@ -98,7 +98,7 @@ private:
 	base::lambda<void(
 		Storage::PreparedList &&list,
 		SendFilesWay way,
-		const QString &caption,
+		TextWithTags &&caption,
 		bool ctrlShiftEnter)> _confirmedCallback;
 	base::lambda<void()> _cancelledCallback;
 	bool _confirmed = false;
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp
index d174ad043..7affe6c93 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.cpp
+++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp
@@ -146,14 +146,8 @@ void InitMessageField(not_null<Ui::InputField*> field) {
 
 	field->customTab(true);
 	field->setInstantReplaces(Ui::InstantReplaces::Default());
-	field->enableInstantReplaces(Global::ReplaceEmoji());
-	field->enableMarkdownSupport(Global::ReplaceEmoji());
-	auto &changed = Global::RefReplaceEmojiChanged();
-	Ui::AttachAsChild(field, changed.add_subscription([=] {
-		field->enableInstantReplaces(Global::ReplaceEmoji());
-		field->enableMarkdownSupport(Global::ReplaceEmoji());
-	}));
-	field->window()->activateWindow();
+	field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
+	field->setMarkdownReplacesEnabled(Global::ReplaceEmojiValue());
 }
 
 bool HasSendText(not_null<const Ui::InputField*> field) {
diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp
index f3b4a2698..f4a9520b4 100644
--- a/Telegram/SourceFiles/facades.cpp
+++ b/Telegram/SourceFiles/facades.cpp
@@ -711,4 +711,14 @@ DefineRefVar(Global, base::Variable<DBIWorkMode>, WorkMode);
 DefineRefVar(Global, base::Observable<void>, UnreadCounterUpdate);
 DefineRefVar(Global, base::Observable<void>, PeerChooseCancel);
 
+rpl::producer<bool> ReplaceEmojiValue() {
+	return rpl::single(
+		Global::ReplaceEmoji()
+	) | rpl::then(base::ObservableViewer(
+		Global::RefReplaceEmojiChanged()
+	) | rpl::map([] {
+		return Global::ReplaceEmoji();
+	}));
+}
+
 } // namespace Global
diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h
index 0782651e7..0df55026f 100644
--- a/Telegram/SourceFiles/facades.h
+++ b/Telegram/SourceFiles/facades.h
@@ -391,6 +391,8 @@ DeclareRefVar(base::Variable<DBIWorkMode>, WorkMode);
 DeclareRefVar(base::Observable<void>, UnreadCounterUpdate);
 DeclareRefVar(base::Observable<void>, PeerChooseCancel);
 
+rpl::producer<bool> ReplaceEmojiValue();
+
 } // namespace Global
 
 namespace Adaptive {
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index ca3c10759..65272cfea 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -4167,7 +4167,7 @@ bool HistoryWidget::confirmSendingFiles(
 	box->setConfirmedCallback(base::lambda_guarded(this, [=](
 			Storage::PreparedList &&list,
 			SendFilesWay way,
-			const QString &caption,
+			TextWithTags &&caption,
 			bool ctrlShiftEnter) {
 		if (showSendingFilesError(list)) {
 			return;
@@ -4181,7 +4181,7 @@ bool HistoryWidget::confirmSendingFiles(
 		uploadFilesAfterConfirmation(
 			std::move(list),
 			type,
-			caption,
+			std::move(caption),
 			replyToId(),
 			album);
 	}));
@@ -4282,18 +4282,17 @@ void HistoryWidget::uploadFiles(
 		SendMediaType type) {
 	ActivateWindowDelayed(controller());
 
-	auto caption = QString();
 	uploadFilesAfterConfirmation(
 		std::move(list),
 		type,
-		caption,
+		TextWithTags(),
 		replyToId());
 }
 
 void HistoryWidget::uploadFilesAfterConfirmation(
 		Storage::PreparedList &&list,
 		SendMediaType type,
-		QString caption,
+		TextWithTags &&caption,
 		MsgId replyTo,
 		std::shared_ptr<SendingAlbum> album) {
 	Assert(canWriteMessage());
@@ -4303,7 +4302,7 @@ void HistoryWidget::uploadFilesAfterConfirmation(
 	Auth().api().sendFiles(
 		std::move(list),
 		type,
-		caption,
+		std::move(caption),
 		album,
 		options);
 }
@@ -4356,8 +4355,23 @@ void HistoryWidget::sendFileConfirmed(
 	options.generateLocal = true;
 	Auth().api().sendAction(options);
 
-	auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
-	if (file->to.replyTo) flags |= MTPDmessage::Flag::f_reply_to_msg_id;
+	auto caption = TextWithEntities{
+		file->caption.text,
+		ConvertTextTagsToEntities(file->caption.tags)
+	};
+	const auto prepareFlags = Ui::ItemTextOptions(
+		history,
+		App::self()).flags;
+	TextUtilities::PrepareForSending(caption, prepareFlags);
+	TextUtilities::Trim(caption);
+	auto localEntities = TextUtilities::EntitiesToMTP(caption.entities);
+
+	auto flags = NewMessageFlags(peer)
+		| MTPDmessage::Flag::f_entities
+		| MTPDmessage::Flag::f_media;
+	if (file->to.replyTo) {
+		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
+	}
 	bool channelPost = peer->isChannel() && !peer->isMegagroup();
 	bool silentPost = channelPost && file->to.silent;
 	if (channelPost) {
@@ -4393,10 +4407,10 @@ void HistoryWidget::sendFileConfirmed(
 				MTPint(),
 				MTP_int(file->to.replyTo),
 				MTP_int(unixtime()),
-				MTP_string(file->caption),
+				MTP_string(caption.text),
 				photo,
 				MTPnullMarkup,
-				MTPnullEntities, // #TODO caption entities
+				localEntities,
 				MTP_int(1),
 				MTPint(),
 				MTP_string(messagePostAuthor),
@@ -4418,10 +4432,10 @@ void HistoryWidget::sendFileConfirmed(
 				MTPint(),
 				MTP_int(file->to.replyTo),
 				MTP_int(unixtime()),
-				MTP_string(file->caption),
+				MTP_string(caption.text),
 				document,
 				MTPnullMarkup,
-				MTPnullEntities, // #TODO caption entities
+				localEntities,
 				MTP_int(1),
 				MTPint(),
 				MTP_string(messagePostAuthor),
@@ -4446,10 +4460,10 @@ void HistoryWidget::sendFileConfirmed(
 				MTPint(),
 				MTP_int(file->to.replyTo),
 				MTP_int(unixtime()),
-				MTP_string(file->caption),
+				MTP_string(caption.text),
 				document,
 				MTPnullMarkup,
-				MTPnullEntities, // #TODO caption entities
+				localEntities,
 				MTP_int(1),
 				MTPint(),
 				MTP_string(messagePostAuthor),
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index e227ad1be..5a6b92874 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -516,7 +516,7 @@ private:
 	void uploadFilesAfterConfirmation(
 		Storage::PreparedList &&list,
 		SendMediaType type,
-		QString caption,
+		TextWithTags &&caption,
 		MsgId replyTo,
 		std::shared_ptr<SendingAlbum> album = nullptr);
 
diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style
index dc470f832..e40747682 100644
--- a/Telegram/SourceFiles/media/view/mediaview.style
+++ b/Telegram/SourceFiles/media/view/mediaview.style
@@ -149,6 +149,7 @@ mediaviewSaveMsgStyle: TextStyle(defaultTextStyle) {
 }
 mediaviewTextPalette: TextPalette(defaultTextPalette) {
 	linkFg: mediaviewTextLinkFg;
+	monoFg: mediaviewCaptionFg;
 }
 
 mediaviewCaptionStyle: defaultTextStyle;
diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm
index aa5d67f52..49836a697 100644
--- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm
+++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm
@@ -101,7 +101,10 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm);
 		const auto notificationReply = QString::fromUtf8([[[notification response] string] UTF8String]);
 		const auto manager = _manager;
 		crl::on_main(manager, [=] {
-			manager->notificationReplied(notificationPeerId, notificationMsgId, notificationReply);
+			manager->notificationReplied(
+				notificationPeerId,
+				notificationMsgId,
+				{ notificationReply, {} });
 		});
 	} else if (notification.activationType == NSUserNotificationActivationTypeContentsClicked) {
 		const auto manager = _manager;
diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp
index 3004c7e86..1fef6e523 100644
--- a/Telegram/SourceFiles/storage/localimageloader.cpp
+++ b/Telegram/SourceFiles/storage/localimageloader.cpp
@@ -178,7 +178,7 @@ FileLoadResult::FileLoadResult(
 	TaskId taskId,
 	uint64 id,
 	const FileLoadTo &to,
-	const QString &caption,
+	const TextWithTags &caption,
 	std::shared_ptr<SendingAlbum> album)
 : taskId(taskId)
 , id(id)
@@ -193,7 +193,7 @@ FileLoadTask::FileLoadTask(
 	std::unique_ptr<FileMediaInformation> information,
 	SendMediaType type,
 	const FileLoadTo &to,
-	const QString &caption,
+	const TextWithTags &caption,
 	std::shared_ptr<SendingAlbum> album)
 : _id(rand_value<uint64>())
 , _to(to)
@@ -210,7 +210,7 @@ FileLoadTask::FileLoadTask(
 	int32 duration,
 	const VoiceWaveform &waveform,
 	const FileLoadTo &to,
-	const QString &caption)
+	const TextWithTags &caption)
 : _id(rand_value<uint64>())
 , _to(to)
 , _content(voice)
diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h
index fa70c2954..fad2e8853 100644
--- a/Telegram/SourceFiles/storage/localimageloader.h
+++ b/Telegram/SourceFiles/storage/localimageloader.h
@@ -189,7 +189,7 @@ struct FileLoadResult {
 		TaskId taskId,
 		uint64 id,
 		const FileLoadTo &to,
-		const QString &caption,
+		const TextWithTags &caption,
 		std::shared_ptr<SendingAlbum> album);
 
 	TaskId taskId;
@@ -217,7 +217,7 @@ struct FileLoadResult {
 	MTPDocument document;
 
 	PreparedPhotoThumbs photoThumbs;
-	QString caption;
+	TextWithTags caption;
 
 	void setFileData(const QByteArray &filedata) {
 		if (filedata.isEmpty()) {
@@ -281,14 +281,14 @@ public:
 		std::unique_ptr<FileMediaInformation> information,
 		SendMediaType type,
 		const FileLoadTo &to,
-		const QString &caption,
+		const TextWithTags &caption,
 		std::shared_ptr<SendingAlbum> album = nullptr);
 	FileLoadTask(
 		const QByteArray &voice,
 		int32 duration,
 		const VoiceWaveform &waveform,
 		const FileLoadTo &to,
-		const QString &caption);
+		const TextWithTags &caption);
 
 	uint64 fileid() const {
 		return _id;
@@ -328,7 +328,7 @@ private:
 	int32 _duration = 0;
 	VoiceWaveform _waveform;
 	SendMediaType _type;
-	QString _caption;
+	TextWithTags _caption;
 
 	std::shared_ptr<FileLoadResult> _result;
 
diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp
index f10757d3e..069f01953 100644
--- a/Telegram/SourceFiles/ui/widgets/input_fields.cpp
+++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp
@@ -1168,16 +1168,24 @@ void InputField::onTouchTimer() {
 	_touchRightButton = true;
 }
 
-void InputField::enableMarkdownSupport(bool enabled) {
-	_markdownEnabled = enabled;
-}
-
 void InputField::setInstantReplaces(const InstantReplaces &replaces) {
 	_mutableInstantReplaces = replaces;
 }
 
-void InputField::enableInstantReplaces(bool enabled) {
-	_instantReplacesEnabled = enabled;
+void InputField::setInstantReplacesEnabled(rpl::producer<bool> enabled) {
+	std::move(
+		enabled
+	) | rpl::start_with_next([=](bool value) {
+		_instantReplacesEnabled = value;
+	}, lifetime());
+}
+
+void InputField::setMarkdownReplacesEnabled(rpl::producer<bool> enabled) {
+	std::move(
+		enabled
+	) | rpl::start_with_next([=](bool value) {
+		_markdownEnabled = value;
+	}, lifetime());
 }
 
 void InputField::setTagMimeProcessor(
diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.h b/Telegram/SourceFiles/ui/widgets/input_fields.h
index 43336cc7e..9c6318dbb 100644
--- a/Telegram/SourceFiles/ui/widgets/input_fields.h
+++ b/Telegram/SourceFiles/ui/widgets/input_fields.h
@@ -187,7 +187,8 @@ public:
 	void setAdditionalMargin(int margin);
 
 	void setInstantReplaces(const InstantReplaces &replaces);
-	void enableInstantReplaces(bool enabled);
+	void setInstantReplacesEnabled(rpl::producer<bool> enabled);
+	void setMarkdownReplacesEnabled(rpl::producer<bool> enabled);
 	void commitInstantReplacement(
 		int from,
 		int till,
@@ -229,8 +230,8 @@ public:
 		Both,
 	};
 	void setSubmitSettings(SubmitSettings settings);
-	void enableMarkdownSupport(bool enabled = true);
 	void customUpDown(bool isCustom);
+	void customTab(bool isCustom);
 
 	not_null<QTextDocument*> document();
 	not_null<const QTextDocument*> document() const;
@@ -381,6 +382,7 @@ private:
 	int _additionalMargin = 0;
 
 	bool _customUpDown = false;
+	bool _customTab = false;
 
 	QString _placeholder;
 	base::lambda<QString()> _placeholderFactory;
diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp
index 63809471f..81c413ddd 100644
--- a/Telegram/SourceFiles/window/notifications_manager.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager.cpp
@@ -440,13 +440,13 @@ void Manager::openNotificationMessage(
 void Manager::notificationReplied(
 		PeerId peerId,
 		MsgId msgId,
-		const QString &reply) {
+		const TextWithTags &reply) {
 	if (!peerId) return;
 
 	auto history = App::history(peerId);
 
 	auto message = MainWidget::MessageToSend(history);
-	message.textWithTags = { reply, TextWithTags::Tags() };
+	message.textWithTags = reply;
 	message.replyTo = (msgId > 0 && !history->peer->isUser()) ? msgId : 0;
 	message.clearDraft = false;
 	if (auto main = App::main()) {
diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h
index 9d90ec632..b40094582 100644
--- a/Telegram/SourceFiles/window/notifications_manager.h
+++ b/Telegram/SourceFiles/window/notifications_manager.h
@@ -132,7 +132,10 @@ public:
 	}
 
 	void notificationActivated(PeerId peerId, MsgId msgId);
-	void notificationReplied(PeerId peerId, MsgId msgId, const QString &reply);
+	void notificationReplied(
+		PeerId peerId,
+		MsgId msgId,
+		const TextWithTags &reply);
 
 	struct DisplayOptions {
 		bool hideNameAndPhoto;
diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp
index ff2325ec3..56bba694d 100644
--- a/Telegram/SourceFiles/window/notifications_manager_default.cpp
+++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp
@@ -772,6 +772,8 @@ void Notification::showReplyField() {
 	_replyArea->setMaxLength(MaxMessageSize);
 	_replyArea->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
 	_replyArea->setInstantReplaces(Ui::InstantReplaces::Default());
+	_replyArea->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
+	_replyArea->setMarkdownReplacesEnabled(Global::ReplaceEmojiValue());
 
 	// Catch mouse press event to activate the window.
 	QCoreApplication::instance()->installEventFilter(this);
@@ -795,7 +797,10 @@ void Notification::sendReply() {
 
 	auto peerId = _history->peer->id;
 	auto msgId = _item ? _item->id : ShowAtUnreadMsgId;
-	manager()->notificationReplied(peerId, msgId, _replyArea->getLastText());
+	manager()->notificationReplied(
+		peerId,
+		msgId,
+		_replyArea->getTextWithTags());
 
 	manager()->startAllHiding();
 }