mirror of https://github.com/procxx/kepka.git
				
				
				
			Some refactoring in working with text entities.
Also move this code to TextUtilities namespace.
This commit is contained in:
		
							parent
							
								
									f38fad2f92
								
							
						
					
					
						commit
						da0d78135d
					
				| 
						 | 
					@ -87,7 +87,7 @@ void ApiWrap::addLocalChangelogs(int oldAppVersion) {
 | 
				
			||||||
	auto addedSome = false;
 | 
						auto addedSome = false;
 | 
				
			||||||
	auto addLocalChangelog = [this, &addedSome](const QString &text) {
 | 
						auto addLocalChangelog = [this, &addedSome](const QString &text) {
 | 
				
			||||||
		auto textWithEntities = TextWithEntities { text };
 | 
							auto textWithEntities = TextWithEntities { text };
 | 
				
			||||||
		textParseEntities(textWithEntities.text, TextParseLinks, &textWithEntities.entities);
 | 
							TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
 | 
				
			||||||
		App::wnd()->serviceNotification(textWithEntities, MTP_messageMediaEmpty(), unixtime());
 | 
							App::wnd()->serviceNotification(textWithEntities, MTP_messageMediaEmpty(), unixtime());
 | 
				
			||||||
		addedSome = true;
 | 
							addedSome = true;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
| 
						 | 
					@ -1177,7 +1177,7 @@ void ApiWrap::saveDraftsToCloud() {
 | 
				
			||||||
		if (!textWithTags.tags.isEmpty()) {
 | 
							if (!textWithTags.tags.isEmpty()) {
 | 
				
			||||||
			flags |= MTPmessages_SaveDraft::Flag::f_entities;
 | 
								flags |= MTPmessages_SaveDraft::Flag::f_entities;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		auto entities = linksToMTP(ConvertTextTagsToEntities(textWithTags.tags), true);
 | 
							auto entities = TextUtilities::EntitiesToMTP(ConvertTextTagsToEntities(textWithTags.tags), TextUtilities::ConvertOption::SkipLocal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(MTP_flags(flags), MTP_int(cloudDraft->msgId), history->peer->input, MTP_string(textWithTags.text), entities)).done([this, history](const MTPBool &result, mtpRequestId requestId) {
 | 
							cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(MTP_flags(flags), MTP_int(cloudDraft->msgId), history->peer->input, MTP_string(textWithTags.text), entities)).done([this, history](const MTPBool &result, mtpRequestId requestId) {
 | 
				
			||||||
			if (auto cloudDraft = history->cloudDraft()) {
 | 
								if (auto cloudDraft = history->cloudDraft()) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -459,11 +459,11 @@ namespace {
 | 
				
			||||||
				// apply first_name and last_name from minimal user only if we don't have
 | 
									// apply first_name and last_name from minimal user only if we don't have
 | 
				
			||||||
				// local values for first name and last name already, otherwise skip
 | 
									// local values for first name and last name already, otherwise skip
 | 
				
			||||||
				bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty();
 | 
									bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty();
 | 
				
			||||||
				QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName;
 | 
									QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? TextUtilities::SingleLine(qs(d.vfirst_name)) : QString()) : data->firstName;
 | 
				
			||||||
				QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName;
 | 
									QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? TextUtilities::SingleLine(qs(d.vlast_name)) : QString()) : data->lastName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString());
 | 
									QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString());
 | 
				
			||||||
				QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString());
 | 
									QString uname = minimal ? data->username : (d.has_username() ? TextUtilities::SingleLine(qs(d.vusername)) : QString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				bool phoneChanged = (data->phone() != phone);
 | 
									bool phoneChanged = (data->phone() != phone);
 | 
				
			||||||
				if (phoneChanged) {
 | 
									if (phoneChanged) {
 | 
				
			||||||
| 
						 | 
					@ -714,7 +714,7 @@ namespace {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			cdata->flagsUpdated();
 | 
								cdata->flagsUpdated();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString();
 | 
								QString uname = d.has_username() ? TextUtilities::SingleLine(qs(d.vusername)) : QString();
 | 
				
			||||||
			cdata->setName(qs(d.vtitle), uname);
 | 
								cdata->setName(qs(d.vtitle), uname);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			cdata->setIsForbidden(false);
 | 
								cdata->setIsForbidden(false);
 | 
				
			||||||
| 
						 | 
					@ -1096,7 +1096,7 @@ namespace {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
 | 
							if (auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
 | 
				
			||||||
			auto text = qs(m.vmessage);
 | 
								auto text = qs(m.vmessage);
 | 
				
			||||||
			auto entities = m.has_entities() ? entitiesFromMTP(m.ventities.v) : EntitiesInText();
 | 
								auto entities = m.has_entities() ? TextUtilities::EntitiesFromMTP(m.ventities.v) : EntitiesInText();
 | 
				
			||||||
			existing->setText({ text, entities });
 | 
								existing->setText({ text, entities });
 | 
				
			||||||
			existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
 | 
								existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
 | 
				
			||||||
			existing->updateReplyMarkup(m.has_reply_markup() ? (&m.vreply_markup) : nullptr);
 | 
								existing->updateReplyMarkup(m.has_reply_markup() ? (&m.vreply_markup) : nullptr);
 | 
				
			||||||
| 
						 | 
					@ -1334,7 +1334,7 @@ namespace {
 | 
				
			||||||
			bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact;
 | 
								bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact;
 | 
				
			||||||
			bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone));
 | 
								bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone));
 | 
				
			||||||
			if (showPhoneChanged) {
 | 
								if (showPhoneChanged) {
 | 
				
			||||||
				user->setName(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), textOneLine(user->username));
 | 
									user->setName(TextUtilities::SingleLine(user->firstName), TextUtilities::SingleLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), TextUtilities::SingleLine(user->username));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			markPeerUpdated(user);
 | 
								markPeerUpdated(user);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -1497,13 +1497,13 @@ namespace {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert) {
 | 
						WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert) {
 | 
				
			||||||
		auto description = TextWithEntities { webpage.has_description() ? textClean(qs(webpage.vdescription)) : QString() };
 | 
							auto description = TextWithEntities { webpage.has_description() ? TextUtilities::Clean(qs(webpage.vdescription)) : QString() };
 | 
				
			||||||
		auto siteName = webpage.has_site_name() ? qs(webpage.vsite_name) : QString();
 | 
							auto siteName = webpage.has_site_name() ? qs(webpage.vsite_name) : QString();
 | 
				
			||||||
		auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
							auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
				
			||||||
		if (siteName == qstr("Twitter") || siteName == qstr("Instagram")) {
 | 
							if (siteName == qstr("Twitter") || siteName == qstr("Instagram")) {
 | 
				
			||||||
			parseFlags |= TextParseHashtags | TextParseMentions;
 | 
								parseFlags |= TextParseHashtags | TextParseMentions;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		textParseEntities(description.text, parseFlags, &description.entities);
 | 
							TextUtilities::ParseEntities(description, parseFlags);
 | 
				
			||||||
		return App::webPageSet(webpage.vid.v, convert, webpage.has_type() ? qs(webpage.vtype) : qsl("article"), qs(webpage.vurl), qs(webpage.vdisplay_url), siteName, webpage.has_title() ? qs(webpage.vtitle) : QString(), description, webpage.has_photo() ? App::feedPhoto(webpage.vphoto) : nullptr, webpage.has_document() ? App::feedDocument(webpage.vdocument) : nullptr, webpage.has_duration() ? webpage.vduration.v : 0, webpage.has_author() ? qs(webpage.vauthor) : QString(), 0);
 | 
							return App::webPageSet(webpage.vid.v, convert, webpage.has_type() ? qs(webpage.vtype) : qsl("article"), qs(webpage.vurl), qs(webpage.vdisplay_url), siteName, webpage.has_title() ? qs(webpage.vtitle) : QString(), description, webpage.has_photo() ? App::feedPhoto(webpage.vphoto) : nullptr, webpage.has_document() ? App::feedDocument(webpage.vdocument) : nullptr, webpage.has_duration() ? webpage.vduration.v : 0, webpage.has_author() ? qs(webpage.vauthor) : QString(), 0);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1800,15 +1800,15 @@ namespace {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill && pendingTill >= -1)) {
 | 
								if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill && pendingTill >= -1)) {
 | 
				
			||||||
				convert->type = toWebPageType(type);
 | 
									convert->type = toWebPageType(type);
 | 
				
			||||||
				convert->url = textClean(url);
 | 
									convert->url = TextUtilities::Clean(url);
 | 
				
			||||||
				convert->displayUrl = textClean(displayUrl);
 | 
									convert->displayUrl = TextUtilities::Clean(displayUrl);
 | 
				
			||||||
				convert->siteName = textClean(siteName);
 | 
									convert->siteName = TextUtilities::Clean(siteName);
 | 
				
			||||||
				convert->title = textOneLine(textClean(title));
 | 
									convert->title = TextUtilities::SingleLine(title);
 | 
				
			||||||
				convert->description = description;
 | 
									convert->description = description;
 | 
				
			||||||
				convert->photo = photo;
 | 
									convert->photo = photo;
 | 
				
			||||||
				convert->document = document;
 | 
									convert->document = document;
 | 
				
			||||||
				convert->duration = duration;
 | 
									convert->duration = duration;
 | 
				
			||||||
				convert->author = textClean(author);
 | 
									convert->author = TextUtilities::Clean(author);
 | 
				
			||||||
				if (convert->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(convert);
 | 
									if (convert->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(convert);
 | 
				
			||||||
				convert->pendingTill = pendingTill;
 | 
									convert->pendingTill = pendingTill;
 | 
				
			||||||
				if (App::main()) App::main()->webPageUpdated(convert);
 | 
									if (App::main()) App::main()->webPageUpdated(convert);
 | 
				
			||||||
| 
						 | 
					@ -1831,15 +1831,15 @@ namespace {
 | 
				
			||||||
			if (result != convert) {
 | 
								if (result != convert) {
 | 
				
			||||||
				if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill && pendingTill >= -1)) {
 | 
									if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill && pendingTill >= -1)) {
 | 
				
			||||||
					result->type = toWebPageType(type);
 | 
										result->type = toWebPageType(type);
 | 
				
			||||||
					result->url = textClean(url);
 | 
										result->url = TextUtilities::Clean(url);
 | 
				
			||||||
					result->displayUrl = textClean(displayUrl);
 | 
										result->displayUrl = TextUtilities::Clean(displayUrl);
 | 
				
			||||||
					result->siteName = textClean(siteName);
 | 
										result->siteName = TextUtilities::Clean(siteName);
 | 
				
			||||||
					result->title = textOneLine(textClean(title));
 | 
										result->title = TextUtilities::SingleLine(title);
 | 
				
			||||||
					result->description = description;
 | 
										result->description = description;
 | 
				
			||||||
					result->photo = photo;
 | 
										result->photo = photo;
 | 
				
			||||||
					result->document = document;
 | 
										result->document = document;
 | 
				
			||||||
					result->duration = duration;
 | 
										result->duration = duration;
 | 
				
			||||||
					result->author = textClean(author);
 | 
										result->author = TextUtilities::Clean(author);
 | 
				
			||||||
					if (result->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(result);
 | 
										if (result->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(result);
 | 
				
			||||||
					result->pendingTill = pendingTill;
 | 
										result->pendingTill = pendingTill;
 | 
				
			||||||
					if (App::main()) App::main()->webPageUpdated(result);
 | 
										if (App::main()) App::main()->webPageUpdated(result);
 | 
				
			||||||
| 
						 | 
					@ -1869,9 +1869,9 @@ namespace {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if (!convert->accessHash && accessHash) {
 | 
								if (!convert->accessHash && accessHash) {
 | 
				
			||||||
				convert->accessHash = accessHash;
 | 
									convert->accessHash = accessHash;
 | 
				
			||||||
				convert->shortName = textClean(shortName);
 | 
									convert->shortName = TextUtilities::Clean(shortName);
 | 
				
			||||||
				convert->title = textOneLine(textClean(title));
 | 
									convert->title = TextUtilities::SingleLine(title);
 | 
				
			||||||
				convert->description = textClean(description);
 | 
									convert->description = TextUtilities::Clean(description);
 | 
				
			||||||
				convert->photo = photo;
 | 
									convert->photo = photo;
 | 
				
			||||||
				convert->document = document;
 | 
									convert->document = document;
 | 
				
			||||||
				if (App::main()) App::main()->gameUpdated(convert);
 | 
									if (App::main()) App::main()->gameUpdated(convert);
 | 
				
			||||||
| 
						 | 
					@ -1891,9 +1891,9 @@ namespace {
 | 
				
			||||||
			if (result != convert) {
 | 
								if (result != convert) {
 | 
				
			||||||
				if (!result->accessHash && accessHash) {
 | 
									if (!result->accessHash && accessHash) {
 | 
				
			||||||
					result->accessHash = accessHash;
 | 
										result->accessHash = accessHash;
 | 
				
			||||||
					result->shortName = textClean(shortName);
 | 
										result->shortName = TextUtilities::Clean(shortName);
 | 
				
			||||||
					result->title = textOneLine(textClean(title));
 | 
										result->title = TextUtilities::SingleLine(title);
 | 
				
			||||||
					result->description = textClean(description);
 | 
										result->description = TextUtilities::Clean(description);
 | 
				
			||||||
					result->photo = photo;
 | 
										result->photo = photo;
 | 
				
			||||||
					result->document = document;
 | 
										result->document = document;
 | 
				
			||||||
					if (App::main()) App::main()->gameUpdated(result);
 | 
										if (App::main()) App::main()->gameUpdated(result);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -173,9 +173,9 @@ void AddContactBox::onSubmit() {
 | 
				
			||||||
void AddContactBox::onSave() {
 | 
					void AddContactBox::onSave() {
 | 
				
			||||||
	if (_addRequest) return;
 | 
						if (_addRequest) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString firstName = prepareText(_first->getLastText());
 | 
						auto firstName = TextUtilities::PrepareForSending(_first->getLastText());
 | 
				
			||||||
	QString lastName = prepareText(_last->getLastText());
 | 
						auto lastName = TextUtilities::PrepareForSending(_last->getLastText());
 | 
				
			||||||
	QString phone = _phone->getLastText().trimmed();
 | 
						auto phone = _phone->getLastText().trimmed();
 | 
				
			||||||
	if (firstName.isEmpty() && lastName.isEmpty()) {
 | 
						if (firstName.isEmpty() && lastName.isEmpty()) {
 | 
				
			||||||
		if (_invertOrder) {
 | 
							if (_invertOrder) {
 | 
				
			||||||
			_last->setFocus();
 | 
								_last->setFocus();
 | 
				
			||||||
| 
						 | 
					@ -368,8 +368,8 @@ void GroupInfoBox::onNameSubmit() {
 | 
				
			||||||
void GroupInfoBox::onNext() {
 | 
					void GroupInfoBox::onNext() {
 | 
				
			||||||
	if (_creationRequestId) return;
 | 
						if (_creationRequestId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto title = prepareText(_title->getLastText());
 | 
						auto title = TextUtilities::PrepareForSending(_title->getLastText());
 | 
				
			||||||
	auto description = _description ? prepareText(_description->getLastText(), true) : QString();
 | 
						auto description = _description ? TextUtilities::PrepareForSending(_description->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString();
 | 
				
			||||||
	if (title.isEmpty()) {
 | 
						if (title.isEmpty()) {
 | 
				
			||||||
		_title->setFocus();
 | 
							_title->setFocus();
 | 
				
			||||||
		_title->showError();
 | 
							_title->showError();
 | 
				
			||||||
| 
						 | 
					@ -695,7 +695,7 @@ void SetupChannelBox::privacyChanged(Privacy value) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SetupChannelBox::onUpdateDone(const MTPBool &result) {
 | 
					void SetupChannelBox::onUpdateDone(const MTPBool &result) {
 | 
				
			||||||
	_channel->setName(textOneLine(_channel->name), _sentUsername);
 | 
						_channel->setName(TextUtilities::SingleLine(_channel->name), _sentUsername);
 | 
				
			||||||
	closeBox();
 | 
						closeBox();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -705,7 +705,7 @@ bool SetupChannelBox::onUpdateFail(const RPCError &error) {
 | 
				
			||||||
	_saveRequestId = 0;
 | 
						_saveRequestId = 0;
 | 
				
			||||||
	QString err(error.type());
 | 
						QString err(error.type());
 | 
				
			||||||
	if (err == "USERNAME_NOT_MODIFIED" || _sentUsername == _channel->username) {
 | 
						if (err == "USERNAME_NOT_MODIFIED" || _sentUsername == _channel->username) {
 | 
				
			||||||
		_channel->setName(textOneLine(_channel->name), textOneLine(_sentUsername));
 | 
							_channel->setName(TextUtilities::SingleLine(_channel->name), TextUtilities::SingleLine(_sentUsername));
 | 
				
			||||||
		closeBox();
 | 
							closeBox();
 | 
				
			||||||
		return true;
 | 
							return true;
 | 
				
			||||||
	} else if (err == "USERNAME_INVALID") {
 | 
						} else if (err == "USERNAME_INVALID") {
 | 
				
			||||||
| 
						 | 
					@ -872,7 +872,8 @@ void EditNameTitleBox::resizeEvent(QResizeEvent *e) {
 | 
				
			||||||
void EditNameTitleBox::onSave() {
 | 
					void EditNameTitleBox::onSave() {
 | 
				
			||||||
	if (_requestId) return;
 | 
						if (_requestId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString first = prepareText(_first->getLastText()), last = prepareText(_last->getLastText());
 | 
						auto first = TextUtilities::PrepareForSending(_first->getLastText());
 | 
				
			||||||
 | 
						auto last = TextUtilities::PrepareForSending(_last->getLastText());
 | 
				
			||||||
	if (first.isEmpty() && last.isEmpty()) {
 | 
						if (first.isEmpty() && last.isEmpty()) {
 | 
				
			||||||
		if (_invertOrder) {
 | 
							if (_invertOrder) {
 | 
				
			||||||
			_last->setFocus();
 | 
								_last->setFocus();
 | 
				
			||||||
| 
						 | 
					@ -904,10 +905,11 @@ void EditNameTitleBox::onSaveSelfDone(const MTPUser &user) {
 | 
				
			||||||
bool EditNameTitleBox::onSaveSelfFail(const RPCError &error) {
 | 
					bool EditNameTitleBox::onSaveSelfFail(const RPCError &error) {
 | 
				
			||||||
	if (MTP::isDefaultHandledError(error)) return false;
 | 
						if (MTP::isDefaultHandledError(error)) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString err(error.type());
 | 
						auto err = error.type();
 | 
				
			||||||
	QString first = textOneLine(_first->getLastText().trimmed()), last = textOneLine(_last->getLastText().trimmed());
 | 
						auto first = TextUtilities::SingleLine(_first->getLastText().trimmed());
 | 
				
			||||||
 | 
						auto last = TextUtilities::SingleLine(_last->getLastText().trimmed());
 | 
				
			||||||
	if (err == "NAME_NOT_MODIFIED") {
 | 
						if (err == "NAME_NOT_MODIFIED") {
 | 
				
			||||||
		App::self()->setName(first, last, QString(), textOneLine(App::self()->username));
 | 
							App::self()->setName(first, last, QString(), TextUtilities::SingleLine(App::self()->username));
 | 
				
			||||||
		closeBox();
 | 
							closeBox();
 | 
				
			||||||
		return true;
 | 
							return true;
 | 
				
			||||||
	} else if (err == "FIRSTNAME_INVALID") {
 | 
						} else if (err == "FIRSTNAME_INVALID") {
 | 
				
			||||||
| 
						 | 
					@ -1073,7 +1075,8 @@ void EditChannelBox::paintEvent(QPaintEvent *e) {
 | 
				
			||||||
void EditChannelBox::onSave() {
 | 
					void EditChannelBox::onSave() {
 | 
				
			||||||
	if (_saveTitleRequestId || _saveDescriptionRequestId || _saveSignRequestId || _saveInvitesRequestId) return;
 | 
						if (_saveTitleRequestId || _saveDescriptionRequestId || _saveSignRequestId || _saveInvitesRequestId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString title = prepareText(_title->getLastText()), description = prepareText(_description->getLastText(), true);
 | 
						auto title = TextUtilities::PrepareForSending(_title->getLastText());
 | 
				
			||||||
 | 
						auto description = TextUtilities::PrepareForSending(_description->getLastText(), TextUtilities::PrepareTextOption::CheckLinks);
 | 
				
			||||||
	if (title.isEmpty()) {
 | 
						if (title.isEmpty()) {
 | 
				
			||||||
		_title->setFocus();
 | 
							_title->setFocus();
 | 
				
			||||||
		_title->showError();
 | 
							_title->showError();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1526,22 +1526,11 @@ void ContactsBox::Inner::updateSelection() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ContactsBox::Inner::updateFilter(QString filter) {
 | 
					void ContactsBox::Inner::updateFilter(QString filter) {
 | 
				
			||||||
	_lastQuery = filter.toLower().trimmed();
 | 
						_lastQuery = filter.toLower().trimmed();
 | 
				
			||||||
	filter = textSearchKey(filter);
 | 
					
 | 
				
			||||||
 | 
						auto words = TextUtilities::PrepareSearchWords(_lastQuery);
 | 
				
			||||||
 | 
						filter = words.isEmpty() ? QString() : words.join(' ');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_time = unixtime();
 | 
						_time = unixtime();
 | 
				
			||||||
	QStringList f;
 | 
					 | 
				
			||||||
	if (!filter.isEmpty()) {
 | 
					 | 
				
			||||||
		QStringList filterList = filter.split(cWordSplit(), QString::SkipEmptyParts);
 | 
					 | 
				
			||||||
		int l = filterList.size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		f.reserve(l);
 | 
					 | 
				
			||||||
		for (int i = 0; i < l; ++i) {
 | 
					 | 
				
			||||||
			QString filterName = filterList[i].trimmed();
 | 
					 | 
				
			||||||
			if (filterName.isEmpty()) continue;
 | 
					 | 
				
			||||||
			f.push_back(filterName);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		filter = f.join(' ');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (_filter != filter) {
 | 
						if (_filter != filter) {
 | 
				
			||||||
		_filter = filter;
 | 
							_filter = filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1560,10 +1549,10 @@ void ContactsBox::Inner::updateFilter(QString filter) {
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			if (!_addContactLnk->isHidden()) _addContactLnk->hide();
 | 
								if (!_addContactLnk->isHidden()) _addContactLnk->hide();
 | 
				
			||||||
			if (!_allAdmins->isHidden()) _allAdmins->hide();
 | 
								if (!_allAdmins->isHidden()) _allAdmins->hide();
 | 
				
			||||||
			QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
 | 
								QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			_filtered.clear();
 | 
								_filtered.clear();
 | 
				
			||||||
			if (!f.isEmpty()) {
 | 
								if (!words.isEmpty()) {
 | 
				
			||||||
				const Dialogs::List *toFilter = nullptr;
 | 
									const Dialogs::List *toFilter = nullptr;
 | 
				
			||||||
				if (!_contacts->isEmpty()) {
 | 
									if (!_contacts->isEmpty()) {
 | 
				
			||||||
					for (fi = fb; fi != fe; ++fi) {
 | 
										for (fi = fb; fi != fe; ++fi) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -984,7 +984,7 @@ void PeerListBox::Inner::checkScrollForPreload() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PeerListBox::Inner::searchQueryChanged(QString query) {
 | 
					void PeerListBox::Inner::searchQueryChanged(QString query) {
 | 
				
			||||||
	auto searchWordsList = query.isEmpty() ? QStringList() : query.split(cWordSplit(), QString::SkipEmptyParts);
 | 
						auto searchWordsList = TextUtilities::PrepareSearchWords(query);
 | 
				
			||||||
	auto normalizedQuery = searchWordsList.isEmpty() ? QString() : searchWordsList.join(' ');
 | 
						auto normalizedQuery = searchWordsList.isEmpty() ? QString() : searchWordsList.join(' ');
 | 
				
			||||||
	if (_normalizedSearchQuery != normalizedQuery) {
 | 
						if (_normalizedSearchQuery != normalizedQuery) {
 | 
				
			||||||
		setSearchQuery(query, normalizedQuery);
 | 
							setSearchQuery(query, normalizedQuery);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -438,7 +438,7 @@ void SendFilesBox::onSend(bool ctrlShiftEnter) {
 | 
				
			||||||
	_confirmed = true;
 | 
						_confirmed = true;
 | 
				
			||||||
	if (_confirmedCallback) {
 | 
						if (_confirmedCallback) {
 | 
				
			||||||
		auto compressed = _compressed ? _compressed->checked() : false;
 | 
							auto compressed = _compressed ? _compressed->checked() : false;
 | 
				
			||||||
		auto caption = _caption ? prepareText(_caption->getLastText(), true) : QString();
 | 
							auto caption = _caption ? TextUtilities::PrepareForSending(_caption->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString();
 | 
				
			||||||
		_confirmedCallback(_files, _animated ? QImage() : _image, std::move(_information), compressed, caption, ctrlShiftEnter);
 | 
							_confirmedCallback(_files, _animated ? QImage() : _image, std::move(_information), compressed, caption, ctrlShiftEnter);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	closeBox();
 | 
						closeBox();
 | 
				
			||||||
| 
						 | 
					@ -766,7 +766,7 @@ void EditCaptionBox::onSave(bool ctrlShiftEnter) {
 | 
				
			||||||
	if (!sentEntities.v.isEmpty()) {
 | 
						if (!sentEntities.v.isEmpty()) {
 | 
				
			||||||
		flags |= MTPmessages_EditMessage::Flag::f_entities;
 | 
							flags |= MTPmessages_EditMessage::Flag::f_entities;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto text = prepareText(_field->getLastText(), true);
 | 
						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), MTPnullMarkup, sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail));
 | 
						_saveRequestId = MTP::send(MTPmessages_EditMessage(MTP_flags(flags), item->history()->peer->input, MTP_int(item->id), MTP_string(text), MTPnullMarkup, sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -674,21 +674,9 @@ bool ShareBox::Inner::hasSelected() const {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ShareBox::Inner::updateFilter(QString filter) {
 | 
					void ShareBox::Inner::updateFilter(QString filter) {
 | 
				
			||||||
	_lastQuery = filter.toLower().trimmed();
 | 
						_lastQuery = filter.toLower().trimmed();
 | 
				
			||||||
	filter = textSearchKey(filter);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QStringList f;
 | 
						auto words = TextUtilities::PrepareSearchWords(_lastQuery);
 | 
				
			||||||
	if (!filter.isEmpty()) {
 | 
						filter = words.isEmpty() ? QString() : words.join(' ');
 | 
				
			||||||
		QStringList filterList = filter.split(cWordSplit(), QString::SkipEmptyParts);
 | 
					 | 
				
			||||||
		int l = filterList.size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		f.reserve(l);
 | 
					 | 
				
			||||||
		for (int i = 0; i < l; ++i) {
 | 
					 | 
				
			||||||
			QString filterName = filterList[i].trimmed();
 | 
					 | 
				
			||||||
			if (filterName.isEmpty()) continue;
 | 
					 | 
				
			||||||
			f.push_back(filterName);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		filter = f.join(' ');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (_filter != filter) {
 | 
						if (_filter != filter) {
 | 
				
			||||||
		_filter = filter;
 | 
							_filter = filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -701,10 +689,10 @@ void ShareBox::Inner::updateFilter(QString filter) {
 | 
				
			||||||
		if (_filter.isEmpty()) {
 | 
							if (_filter.isEmpty()) {
 | 
				
			||||||
			refresh();
 | 
								refresh();
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
 | 
								QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			_filtered.clear();
 | 
								_filtered.clear();
 | 
				
			||||||
			if (!f.isEmpty()) {
 | 
								if (!words.isEmpty()) {
 | 
				
			||||||
				const Dialogs::List *toFilter = nullptr;
 | 
									const Dialogs::List *toFilter = nullptr;
 | 
				
			||||||
				if (!_chatsIndexed->isEmpty()) {
 | 
									if (!_chatsIndexed->isEmpty()) {
 | 
				
			||||||
					for (fi = fb; fi != fe; ++fi) {
 | 
										for (fi = fb; fi != fe; ++fi) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -428,18 +428,17 @@ bool StickerSetBox::Inner::official() const {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
base::lambda<TextWithEntities()> StickerSetBox::Inner::title() const {
 | 
					base::lambda<TextWithEntities()> StickerSetBox::Inner::title() const {
 | 
				
			||||||
	auto text = _setTitle;
 | 
						auto text = TextWithEntities { _setTitle };
 | 
				
			||||||
	auto entities = EntitiesInText();
 | 
					 | 
				
			||||||
	if (_loaded) {
 | 
						if (_loaded) {
 | 
				
			||||||
		if (_pack.isEmpty()) {
 | 
							if (_pack.isEmpty()) {
 | 
				
			||||||
			return [] { return TextWithEntities { lang(lng_attach_failed), EntitiesInText() }; };
 | 
								return [] { return TextWithEntities { lang(lng_attach_failed), EntitiesInText() }; };
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			textParseEntities(text, TextParseMentions, &entities);
 | 
								TextUtilities::ParseEntities(text, TextParseMentions);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return [] { return TextWithEntities { lang(lng_contacts_loading), EntitiesInText() }; };
 | 
							return [] { return TextWithEntities { lang(lng_contacts_loading), EntitiesInText() }; };
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return [text, entities] { return TextWithEntities { text, entities }; };
 | 
						return [text] { return text; };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString StickerSetBox::Inner::shortName() const {
 | 
					QString StickerSetBox::Inner::shortName() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,7 +175,7 @@ bool UsernameBox::onUpdateFail(const RPCError &error) {
 | 
				
			||||||
	_saveRequestId = 0;
 | 
						_saveRequestId = 0;
 | 
				
			||||||
	QString err(error.type());
 | 
						QString err(error.type());
 | 
				
			||||||
	if (err == qstr("USERNAME_NOT_MODIFIED") || _sentUsername == App::self()->username) {
 | 
						if (err == qstr("USERNAME_NOT_MODIFIED") || _sentUsername == App::self()->username) {
 | 
				
			||||||
		App::self()->setName(textOneLine(App::self()->firstName), textOneLine(App::self()->lastName), textOneLine(App::self()->nameOrPhone), textOneLine(_sentUsername));
 | 
							App::self()->setName(TextUtilities::SingleLine(App::self()->firstName), TextUtilities::SingleLine(App::self()->lastName), TextUtilities::SingleLine(App::self()->nameOrPhone), TextUtilities::SingleLine(_sentUsername));
 | 
				
			||||||
		closeBox();
 | 
							closeBox();
 | 
				
			||||||
		return true;
 | 
							return true;
 | 
				
			||||||
	} else if (err == qstr("USERNAME_INVALID")) {
 | 
						} else if (err == qstr("USERNAME_INVALID")) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,7 +99,7 @@ void FieldAutocomplete::showFiltered(PeerData *peer, QString query, bool addInli
 | 
				
			||||||
	bool resetScroll = (_type != type || _filter != plainQuery);
 | 
						bool resetScroll = (_type != type || _filter != plainQuery);
 | 
				
			||||||
	if (resetScroll) {
 | 
						if (resetScroll) {
 | 
				
			||||||
		_type = type;
 | 
							_type = type;
 | 
				
			||||||
		_filter = textAccentFold(plainQuery.toString());
 | 
							_filter = TextUtilities::RemoveAccents(plainQuery.toString());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_addInlineBots = addInlineBots;
 | 
						_addInlineBots = addInlineBots;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -336,11 +336,6 @@ enum {
 | 
				
			||||||
	ForwardOnAdd = 100, // how many messages from chat history server should forward to user, that was added to this chat
 | 
						ForwardOnAdd = 100, // how many messages from chat history server should forward to user, that was added to this chat
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline const QRegularExpression &cWordSplit() {
 | 
					 | 
				
			||||||
	static QRegularExpression regexp(qsl("[\\@\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\:\\!\\_\\;\\\"\\'\\x0]"));
 | 
					 | 
				
			||||||
	return regexp;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
inline const QRegularExpression &cRussianLetters() {
 | 
					inline const QRegularExpression &cRussianLetters() {
 | 
				
			||||||
	static QRegularExpression regexp(QString::fromUtf8("[а-яА-ЯёЁ]"));
 | 
						static QRegularExpression regexp(QString::fromUtf8("[а-яА-ЯёЁ]"));
 | 
				
			||||||
	return regexp;
 | 
						return regexp;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,10 +40,9 @@ Draft::Draft(const Ui::FlatTextarea *field, MsgId msgId, bool previewCancelled,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) {
 | 
					void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) {
 | 
				
			||||||
	auto history = App::history(peerId);
 | 
						auto history = App::history(peerId);
 | 
				
			||||||
	auto text = qs(draft.vmessage);
 | 
						auto text = TextWithEntities { qs(draft.vmessage), draft.has_entities() ? TextUtilities::EntitiesFromMTP(draft.ventities.v) : EntitiesInText() };
 | 
				
			||||||
	auto entities = draft.has_entities() ? entitiesFromMTP(draft.ventities.v) : EntitiesInText();
 | 
						auto textWithTags = TextWithTags { TextUtilities::ApplyEntities(text), ConvertEntitiesToTextTags(text.entities) };
 | 
				
			||||||
	TextWithTags textWithTags = { textApplyEntities(text, entities), ConvertEntitiesToTextTags(entities) };
 | 
						auto replyTo = draft.has_reply_to_msg_id() ? draft.vreply_to_msg_id.v : MsgId(0);
 | 
				
			||||||
	MsgId replyTo = draft.has_reply_to_msg_id() ? draft.vreply_to_msg_id.v : 0;
 | 
					 | 
				
			||||||
	auto cloudDraft = std::make_unique<Draft>(textWithTags, replyTo, MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), draft.is_no_webpage());
 | 
						auto cloudDraft = std::make_unique<Draft>(textWithTags, replyTo, MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), draft.is_no_webpage());
 | 
				
			||||||
	cloudDraft->date = ::date(draft.vdate);
 | 
						cloudDraft->date = ::date(draft.vdate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,7 +99,7 @@ void paintRow(Painter &p, const RippleRow *row, History *history, HistoryItem *i
 | 
				
			||||||
		if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) {
 | 
							if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) {
 | 
				
			||||||
			if (history->cloudDraftTextCache.isEmpty()) {
 | 
								if (history->cloudDraftTextCache.isEmpty()) {
 | 
				
			||||||
				auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft)));
 | 
									auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft)));
 | 
				
			||||||
				auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, textClean(draft->textWithTags.text));
 | 
									auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text));
 | 
				
			||||||
				history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, _textDlgOptions);
 | 
									history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, _textDlgOptions);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
 | 
								p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1263,21 +1263,8 @@ void DialogsInner::onPeerPhotoChanged(PeerData *peer) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
 | 
					void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
 | 
				
			||||||
	newFilter = textSearchKey(newFilter);
 | 
						auto words = TextUtilities::PrepareSearchWords(newFilter);
 | 
				
			||||||
	if (newFilter != _filter || force) {
 | 
						newFilter = words.isEmpty() ? QString() : words.join(' ');
 | 
				
			||||||
		QStringList f;
 | 
					 | 
				
			||||||
		if (!newFilter.isEmpty()) {
 | 
					 | 
				
			||||||
			QStringList filterList = newFilter.split(cWordSplit(), QString::SkipEmptyParts);
 | 
					 | 
				
			||||||
			int l = filterList.size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			f.reserve(l);
 | 
					 | 
				
			||||||
			for (int i = 0; i < l; ++i) {
 | 
					 | 
				
			||||||
				QString filterName = filterList[i].trimmed();
 | 
					 | 
				
			||||||
				if (filterName.isEmpty()) continue;
 | 
					 | 
				
			||||||
				f.push_back(filterName);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			newFilter = f.join(' ');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	if (newFilter != _filter || force) {
 | 
						if (newFilter != _filter || force) {
 | 
				
			||||||
		_filter = newFilter;
 | 
							_filter = newFilter;
 | 
				
			||||||
		if (!_searchInPeer && _filter.isEmpty()) {
 | 
							if (!_searchInPeer && _filter.isEmpty()) {
 | 
				
			||||||
| 
						 | 
					@ -1290,11 +1277,11 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
 | 
				
			||||||
			_lastSearchPeer = 0;
 | 
								_lastSearchPeer = 0;
 | 
				
			||||||
			_lastSearchId = _lastSearchMigratedId = 0;
 | 
								_lastSearchId = _lastSearchMigratedId = 0;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
				QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
 | 
								QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			_state = FilteredState;
 | 
								_state = FilteredState;
 | 
				
			||||||
			_filterResults.clear();
 | 
								_filterResults.clear();
 | 
				
			||||||
				if (!_searchInPeer && !f.isEmpty()) {
 | 
								if (!_searchInPeer && !words.isEmpty()) {
 | 
				
			||||||
				const Dialogs::List *toFilter = nullptr;
 | 
									const Dialogs::List *toFilter = nullptr;
 | 
				
			||||||
				if (!_dialogs->isEmpty()) {
 | 
									if (!_dialogs->isEmpty()) {
 | 
				
			||||||
					for (fi = fb; fi != fe; ++fi) {
 | 
										for (fi = fb; fi != fe; ++fi) {
 | 
				
			||||||
| 
						 | 
					@ -1364,7 +1351,6 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		refresh(true);
 | 
							refresh(true);
 | 
				
			||||||
		setMouseSelection(false, true);
 | 
							setMouseSelection(false, true);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -2038,10 +2024,10 @@ bool DialogsInner::choosePeer() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DialogsInner::saveRecentHashtags(const QString &text) {
 | 
					void DialogsInner::saveRecentHashtags(const QString &text) {
 | 
				
			||||||
	bool found = false;
 | 
						auto found = false;
 | 
				
			||||||
	QRegularExpressionMatch m;
 | 
						QRegularExpressionMatch m;
 | 
				
			||||||
	RecentHashtagPack recent(cRecentSearchHashtags());
 | 
						auto recent = cRecentSearchHashtags();
 | 
				
			||||||
	for (int32 i = 0, next = 0; (m = reHashtag().match(text, i)).hasMatch(); i = next) {
 | 
						for (int32 i = 0, next = 0; (m = TextUtilities::RegExpHashtag().match(text, i)).hasMatch(); i = next) {
 | 
				
			||||||
		i = m.capturedStart();
 | 
							i = m.capturedStart();
 | 
				
			||||||
		next = m.capturedEnd();
 | 
							next = m.capturedEnd();
 | 
				
			||||||
		if (m.hasMatch()) {
 | 
							if (m.hasMatch()) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,11 +56,10 @@ constexpr auto kNewBlockEachMessage = 50;
 | 
				
			||||||
auto GlobalPinnedIndex = 0;
 | 
					auto GlobalPinnedIndex = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
 | 
					HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
 | 
				
			||||||
	QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")));
 | 
						auto text = TextWithEntities { lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")) };
 | 
				
			||||||
	EntitiesInText entities;
 | 
						TextUtilities::ParseEntities(text, _historyTextNoMonoOptions.flags);
 | 
				
			||||||
	textParseEntities(text, _historyTextNoMonoOptions.flags, &entities);
 | 
						text.entities.push_front(EntityInText(EntityInTextItalic, 0, text.text.size()));
 | 
				
			||||||
	entities.push_front(EntityInText(EntityInTextItalic, 0, text.size()));
 | 
						return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, text);
 | 
				
			||||||
	return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, { text, entities });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace
 | 
					} // namespace
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -392,7 +392,7 @@ void InnerWidget::updateEmptyText() {
 | 
				
			||||||
	auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) };
 | 
						auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) };
 | 
				
			||||||
	text.entities.append(EntityInText(EntityInTextBold, 0, text.text.size()));
 | 
						text.entities.append(EntityInText(EntityInTextBold, 0, text.text.size()));
 | 
				
			||||||
	auto description = hasSearch
 | 
						auto description = hasSearch
 | 
				
			||||||
		? lng_admin_log_no_results_search_text(lt_query, textClean(_searchQuery))
 | 
							? lng_admin_log_no_results_search_text(lt_query, TextUtilities::Clean(_searchQuery))
 | 
				
			||||||
		: lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text);
 | 
							: lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text);
 | 
				
			||||||
	text.text.append(qstr("\n\n") + description);
 | 
						text.text.append(qstr("\n\n") + description);
 | 
				
			||||||
	_emptyText.setMarkedText(st::defaultTextStyle, text, options);
 | 
						_emptyText.setMarkedText(st::defaultTextStyle, text, options);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,14 +30,14 @@ namespace AdminLog {
 | 
				
			||||||
namespace {
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
 | 
					TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
 | 
				
			||||||
	auto result = TextWithEntities { textClean(value) };
 | 
						auto result = TextWithEntities { TextUtilities::Clean(value) };
 | 
				
			||||||
	if (result.text.isEmpty()) {
 | 
						if (result.text.isEmpty()) {
 | 
				
			||||||
		result.text = emptyValue;
 | 
							result.text = emptyValue;
 | 
				
			||||||
		if (!emptyValue.isEmpty()) {
 | 
							if (!emptyValue.isEmpty()) {
 | 
				
			||||||
			result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size()));
 | 
								result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size()));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		textParseEntities(result.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &result.entities);
 | 
							TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -79,8 +79,8 @@ TextWithEntities ExtractEditedText(const MTPMessage &message) {
 | 
				
			||||||
	} else if (mediaType == mtpc_messageMediaPhoto) {
 | 
						} else if (mediaType == mtpc_messageMediaPhoto) {
 | 
				
			||||||
		return PrepareText(qs(data.vmedia.c_messageMediaPhoto().vcaption), QString());
 | 
							return PrepareText(qs(data.vmedia.c_messageMediaPhoto().vcaption), QString());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto text = textClean(qs(data.vmessage));
 | 
						auto text = TextUtilities::Clean(qs(data.vmessage));
 | 
				
			||||||
	auto entities = data.has_entities() ? entitiesFromMTP(data.ventities.v) : EntitiesInText();
 | 
						auto entities = data.has_entities() ? TextUtilities::EntitiesFromMTP(data.ventities.v) : EntitiesInText();
 | 
				
			||||||
	return { text, entities };
 | 
						return { text, entities };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1522,7 +1522,7 @@ TextWithEntities HistoryInner::getSelectedText() const {
 | 
				
			||||||
		int y = itemTop(item);
 | 
							int y = itemTop(item);
 | 
				
			||||||
		if (y >= 0) {
 | 
							if (y >= 0) {
 | 
				
			||||||
			part.text.append(item->author()->name).append(time);
 | 
								part.text.append(item->author()->name).append(time);
 | 
				
			||||||
			appendTextWithEntities(part, std::move(unwrapped));
 | 
								TextUtilities::Append(part, std::move(unwrapped));
 | 
				
			||||||
			texts.insert(y, part);
 | 
								texts.insert(y, part);
 | 
				
			||||||
			fullSize += size;
 | 
								fullSize += size;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -1532,7 +1532,7 @@ TextWithEntities HistoryInner::getSelectedText() const {
 | 
				
			||||||
	auto sep = qsl("\n\n");
 | 
						auto sep = qsl("\n\n");
 | 
				
			||||||
	result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
						result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
				
			||||||
	for (auto i = texts.begin(), e = texts.end(); i != e; ++i) {
 | 
						for (auto i = texts.begin(), e = texts.end(); i != e; ++i) {
 | 
				
			||||||
		appendTextWithEntities(result, std::move(i.value()));
 | 
							TextUtilities::Append(result, std::move(i.value()));
 | 
				
			||||||
		if (i + 1 != e) {
 | 
							if (i + 1 != e) {
 | 
				
			||||||
			result.text.append(sep);
 | 
								result.text.append(sep);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -115,7 +115,7 @@ ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s)
 | 
				
			||||||
				auto str = row.at(j).text;
 | 
									auto str = row.at(j).text;
 | 
				
			||||||
				button.type = row.at(j).type;
 | 
									button.type = row.at(j).type;
 | 
				
			||||||
				button.link = MakeShared<ReplyMarkupClickHandler>(item, i, j);
 | 
									button.link = MakeShared<ReplyMarkupClickHandler>(item, i, j);
 | 
				
			||||||
				button.text.setText(_st->textStyle(), textOneLine(str), _textPlainOptions);
 | 
									button.text.setText(_st->textStyle(), TextUtilities::SingleLine(str), _textPlainOptions);
 | 
				
			||||||
				button.characters = str.isEmpty() ? 1 : str.size();
 | 
									button.characters = str.isEmpty() ? 1 : str.size();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_rows.push_back(newRow);
 | 
								_rows.push_back(newRow);
 | 
				
			||||||
| 
						 | 
					@ -1141,12 +1141,12 @@ QString HistoryItem::inDialogsText() const {
 | 
				
			||||||
		if (emptyText()) {
 | 
							if (emptyText()) {
 | 
				
			||||||
			return _media ? _media->inDialogsText() : QString();
 | 
								return _media ? _media->inDialogsText() : QString();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return textClean(_text.originalText());
 | 
							return TextUtilities::Clean(_text.originalText());
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	auto plainText = getText();
 | 
						auto plainText = getText();
 | 
				
			||||||
	if ((!_history->peer->isUser() || out()) && !isPost() && !isEmpty()) {
 | 
						if ((!_history->peer->isUser() || out()) && !isPost() && !isEmpty()) {
 | 
				
			||||||
		auto fromText = author()->isSelf() ? lang(lng_from_you) : author()->shortName();
 | 
							auto fromText = author()->isSelf() ? lang(lng_from_you) : author()->shortName();
 | 
				
			||||||
		auto fromWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, textClean(fromText)));
 | 
							auto fromWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, TextUtilities::Clean(fromText)));
 | 
				
			||||||
		return lng_dialogs_text_with_from(lt_from_part, fromWrapped, lt_message, plainText);
 | 
							return lng_dialogs_text_with_from(lt_from_part, fromWrapped, lt_message, plainText);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return plainText;
 | 
						return plainText;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ public:
 | 
				
			||||||
	// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
 | 
						// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
 | 
				
			||||||
	virtual QString inDialogsText() const {
 | 
						virtual QString inDialogsText() const {
 | 
				
			||||||
		auto result = notificationText();
 | 
							auto result = notificationText();
 | 
				
			||||||
		return result.isEmpty() ? QString() : textcmdLink(1, textClean(result));
 | 
							return result.isEmpty() ? QString() : textcmdLink(1, TextUtilities::Clean(result));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	virtual TextWithEntities selectedText(TextSelection selection) const = 0;
 | 
						virtual TextWithEntities selectedText(TextSelection selection) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,7 +130,7 @@ TextWithEntities captionedSelectedText(const QString &attachType, const Text &ca
 | 
				
			||||||
	result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
 | 
						result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
 | 
				
			||||||
	if (!caption.isEmpty()) {
 | 
						if (!caption.isEmpty()) {
 | 
				
			||||||
		result.text.append(qstr("\n"));
 | 
							result.text.append(qstr("\n"));
 | 
				
			||||||
		appendTextWithEntities(result, std::move(original));
 | 
							TextUtilities::Append(result, std::move(original));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -147,11 +147,11 @@ QString captionedNotificationText(const QString &attachType, const Text &caption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString captionedInDialogsText(const QString &attachType, const Text &caption) {
 | 
					QString captionedInDialogsText(const QString &attachType, const Text &caption) {
 | 
				
			||||||
	if (caption.isEmpty()) {
 | 
						if (caption.isEmpty()) {
 | 
				
			||||||
		return textcmdLink(1, textClean(attachType));
 | 
							return textcmdLink(1, TextUtilities::Clean(attachType));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto captionText = textClean(caption.originalText());
 | 
						auto captionText = TextUtilities::Clean(caption.originalText());
 | 
				
			||||||
	auto attachTypeWrapped = textcmdLink(1, lng_dialogs_text_media_wrapped(lt_media, textClean(attachType)));
 | 
						auto attachTypeWrapped = textcmdLink(1, lng_dialogs_text_media_wrapped(lt_media, TextUtilities::Clean(attachType)));
 | 
				
			||||||
	return lng_dialogs_text_media(lt_media_part, attachTypeWrapped, lt_caption, captionText);
 | 
						return lng_dialogs_text_media(lt_media_part, attachTypeWrapped, lt_caption, captionText);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3164,7 +3164,7 @@ void HistoryWebPage::initDimensions() {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// init layout
 | 
						// init layout
 | 
				
			||||||
	auto title = textOneLine(_data->title.isEmpty() ? _data->author : _data->title);
 | 
						auto title = TextUtilities::SingleLine(_data->title.isEmpty() ? _data->author : _data->title);
 | 
				
			||||||
	if (!_data->description.text.isEmpty() && title.isEmpty() && _data->siteName.isEmpty() && !_data->url.isEmpty()) {
 | 
						if (!_data->description.text.isEmpty() && title.isEmpty() && _data->siteName.isEmpty() && !_data->url.isEmpty()) {
 | 
				
			||||||
		_data->siteName = siteNameFromUrl(_data->url);
 | 
							_data->siteName = siteNameFromUrl(_data->url);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -3642,7 +3642,7 @@ TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	titleResult.text += '\n';
 | 
						titleResult.text += '\n';
 | 
				
			||||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
						TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
				
			||||||
	return titleResult;
 | 
						return titleResult;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3700,7 +3700,7 @@ void HistoryGame::initDimensions() {
 | 
				
			||||||
		_openl = MakeShared<ReplyMarkupClickHandler>(_parent, 0, 0);
 | 
							_openl = MakeShared<ReplyMarkupClickHandler>(_parent, 0, 0);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto title = textOneLine(_data->title);
 | 
						auto title = TextUtilities::SingleLine(_data->title);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// init attach
 | 
						// init attach
 | 
				
			||||||
	if (!_attach) {
 | 
						if (!_attach) {
 | 
				
			||||||
| 
						 | 
					@ -3728,7 +3728,7 @@ void HistoryGame::initDimensions() {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			auto marked = TextWithEntities { text };
 | 
								auto marked = TextWithEntities { text };
 | 
				
			||||||
			auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
								auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
				
			||||||
			textParseEntities(marked.text, parseFlags, &marked.entities);
 | 
								TextUtilities::ParseEntities(marked, parseFlags);
 | 
				
			||||||
			_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
								_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -4042,7 +4042,7 @@ TextWithEntities HistoryGame::selectedText(TextSelection selection) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	titleResult.text += '\n';
 | 
						titleResult.text += '\n';
 | 
				
			||||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
						TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
				
			||||||
	return titleResult;
 | 
						return titleResult;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4156,10 +4156,10 @@ void HistoryInvoice::fillFromData(const MTPDmessageMediaInvoice &data) {
 | 
				
			||||||
	if (!description.isEmpty()) {
 | 
						if (!description.isEmpty()) {
 | 
				
			||||||
		auto marked = TextWithEntities { description };
 | 
							auto marked = TextWithEntities { description };
 | 
				
			||||||
		auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
							auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
				
			||||||
		textParseEntities(marked.text, parseFlags, &marked.entities);
 | 
							TextUtilities::ParseEntities(marked, parseFlags);
 | 
				
			||||||
		_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
							_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto title = textOneLine(qs(data.vtitle));
 | 
						auto title = TextUtilities::SingleLine(qs(data.vtitle));
 | 
				
			||||||
	if (!title.isEmpty()) {
 | 
						if (!title.isEmpty()) {
 | 
				
			||||||
		_title.setText(st::webPageTitleStyle, title, _webpageTitleOptions);
 | 
							_title.setText(st::webPageTitleStyle, title, _webpageTitleOptions);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -4435,7 +4435,7 @@ TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	titleResult.text += '\n';
 | 
						titleResult.text += '\n';
 | 
				
			||||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
						TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
				
			||||||
	return titleResult;
 | 
						return titleResult;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4460,12 +4460,12 @@ HistoryLocation::HistoryLocation(gsl::not_null<HistoryItem*> parent, const Locat
 | 
				
			||||||
, _description(st::msgMinWidth)
 | 
					, _description(st::msgMinWidth)
 | 
				
			||||||
, _link(MakeShared<LocationClickHandler>(coords)) {
 | 
					, _link(MakeShared<LocationClickHandler>(coords)) {
 | 
				
			||||||
	if (!title.isEmpty()) {
 | 
						if (!title.isEmpty()) {
 | 
				
			||||||
		_title.setText(st::webPageTitleStyle, textClean(title), _webpageTitleOptions);
 | 
							_title.setText(st::webPageTitleStyle, TextUtilities::Clean(title), _webpageTitleOptions);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (!description.isEmpty()) {
 | 
						if (!description.isEmpty()) {
 | 
				
			||||||
		auto marked = TextWithEntities { textClean(description) };
 | 
							auto marked = TextWithEntities { TextUtilities::Clean(description) };
 | 
				
			||||||
		auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
							auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
				
			||||||
		textParseEntities(marked.text, parseFlags, &marked.entities);
 | 
							TextUtilities::ParseEntities(marked, parseFlags);
 | 
				
			||||||
		_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
							_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4700,7 +4700,7 @@ TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
 | 
				
			||||||
		TextWithEntities result = { qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), EntitiesInText() };
 | 
							TextWithEntities result = { qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), EntitiesInText() };
 | 
				
			||||||
		auto info = selectedText(AllTextSelection);
 | 
							auto info = selectedText(AllTextSelection);
 | 
				
			||||||
		if (!info.text.isEmpty()) {
 | 
							if (!info.text.isEmpty()) {
 | 
				
			||||||
			appendTextWithEntities(result, std::move(info));
 | 
								TextUtilities::Append(result, std::move(info));
 | 
				
			||||||
			result.text.append('\n');
 | 
								result.text.append('\n');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		result.text += _link->dragText();
 | 
							result.text += _link->dragText();
 | 
				
			||||||
| 
						 | 
					@ -4715,7 +4715,7 @@ TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
 | 
				
			||||||
		return titleResult;
 | 
							return titleResult;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	titleResult.text += '\n';
 | 
						titleResult.text += '\n';
 | 
				
			||||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
						TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
				
			||||||
	return titleResult;
 | 
						return titleResult;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -221,7 +221,7 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (replyToMsg) {
 | 
						if (replyToMsg) {
 | 
				
			||||||
		replyToText.setText(st::messageTextStyle, textClean(replyToMsg->inReplyText()), _textDlgOptions);
 | 
							replyToText.setText(st::messageTextStyle, TextUtilities::Clean(replyToMsg->inReplyText()), _textDlgOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		updateName();
 | 
							updateName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -432,8 +432,8 @@ HistoryMessage::HistoryMessage(gsl::not_null<History*> history, const MTPDmessag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	initMedia(msg.has_media() ? (&msg.vmedia) : nullptr);
 | 
						initMedia(msg.has_media() ? (&msg.vmedia) : nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto text = textClean(qs(msg.vmessage));
 | 
						auto text = TextUtilities::Clean(qs(msg.vmessage));
 | 
				
			||||||
	auto entities = msg.has_entities() ? entitiesFromMTP(msg.ventities.v) : EntitiesInText();
 | 
						auto entities = msg.has_entities() ? TextUtilities::EntitiesFromMTP(msg.ventities.v) : EntitiesInText();
 | 
				
			||||||
	setText({ text, entities });
 | 
						setText({ text, entities });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -980,7 +980,7 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() };
 | 
						TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() };
 | 
				
			||||||
	if (message.has_entities()) {
 | 
						if (message.has_entities()) {
 | 
				
			||||||
		textWithEntities.entities = entitiesFromMTP(message.ventities.v);
 | 
							textWithEntities.entities = TextUtilities::EntitiesFromMTP(message.ventities.v);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	setText(textWithEntities);
 | 
						setText(textWithEntities);
 | 
				
			||||||
	setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr);
 | 
						setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr);
 | 
				
			||||||
| 
						 | 
					@ -1075,13 +1075,13 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
 | 
				
			||||||
		result = std::move(mediaResult);
 | 
							result = std::move(mediaResult);
 | 
				
			||||||
	} else if (!mediaResult.text.isEmpty()) {
 | 
						} else if (!mediaResult.text.isEmpty()) {
 | 
				
			||||||
		result.text += qstr("\n\n");
 | 
							result.text += qstr("\n\n");
 | 
				
			||||||
		appendTextWithEntities(result, std::move(mediaResult));
 | 
							TextUtilities::Append(result, std::move(mediaResult));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (result.text.isEmpty()) {
 | 
						if (result.text.isEmpty()) {
 | 
				
			||||||
		result = std::move(logEntryOriginalResult);
 | 
							result = std::move(logEntryOriginalResult);
 | 
				
			||||||
	} else if (!logEntryOriginalResult.text.isEmpty()) {
 | 
						} else if (!logEntryOriginalResult.text.isEmpty()) {
 | 
				
			||||||
		result.text += qstr("\n\n");
 | 
							result.text += qstr("\n\n");
 | 
				
			||||||
		appendTextWithEntities(result, std::move(logEntryOriginalResult));
 | 
							TextUtilities::Append(result, std::move(logEntryOriginalResult));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (auto forwarded = Get<HistoryMessageForwarded>()) {
 | 
						if (auto forwarded = Get<HistoryMessageForwarded>()) {
 | 
				
			||||||
		if (selection == FullSelection) {
 | 
							if (selection == FullSelection) {
 | 
				
			||||||
| 
						 | 
					@ -1090,9 +1090,9 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
 | 
				
			||||||
			wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
 | 
								wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
 | 
				
			||||||
			wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
 | 
								wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
 | 
				
			||||||
			wrapped.text.append('[');
 | 
								wrapped.text.append('[');
 | 
				
			||||||
			appendTextWithEntities(wrapped, std::move(fwdinfo));
 | 
								TextUtilities::Append(wrapped, std::move(fwdinfo));
 | 
				
			||||||
			wrapped.text.append(qsl("]\n"));
 | 
								wrapped.text.append(qsl("]\n"));
 | 
				
			||||||
			appendTextWithEntities(wrapped, std::move(result));
 | 
								TextUtilities::Append(wrapped, std::move(result));
 | 
				
			||||||
			result = wrapped;
 | 
								result = wrapped;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1101,7 +1101,7 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
 | 
				
			||||||
			TextWithEntities wrapped;
 | 
								TextWithEntities wrapped;
 | 
				
			||||||
			wrapped.text.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.text.size());
 | 
								wrapped.text.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.text.size());
 | 
				
			||||||
			wrapped.text.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n"));
 | 
								wrapped.text.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n"));
 | 
				
			||||||
			appendTextWithEntities(wrapped, std::move(result));
 | 
								TextUtilities::Append(wrapped, std::move(result));
 | 
				
			||||||
			result = wrapped;
 | 
								result = wrapped;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
 | 
				
			||||||
	auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) {
 | 
						auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) {
 | 
				
			||||||
		auto result = PreparedText {};
 | 
							auto result = PreparedText {};
 | 
				
			||||||
		result.links.push_back(fromLink());
 | 
							result.links.push_back(fromLink());
 | 
				
			||||||
		result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle)));
 | 
							result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle)));
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,7 +99,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
 | 
				
			||||||
			result.text = lang(lng_action_created_channel);
 | 
								result.text = lang(lng_action_created_channel);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			result.links.push_back(fromLink());
 | 
								result.links.push_back(fromLink());
 | 
				
			||||||
			result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle)));
 | 
								result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle)));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
| 
						 | 
					@ -143,10 +143,10 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
 | 
				
			||||||
	auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) {
 | 
						auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) {
 | 
				
			||||||
		auto result = PreparedText {};
 | 
							auto result = PreparedText {};
 | 
				
			||||||
		if (isPost()) {
 | 
							if (isPost()) {
 | 
				
			||||||
			result.text = lng_action_changed_title_channel(lt_title, textClean(qs(action.vtitle)));
 | 
								result.text = lng_action_changed_title_channel(lt_title, TextUtilities::Clean(qs(action.vtitle)));
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			result.links.push_back(fromLink());
 | 
								result.links.push_back(fromLink());
 | 
				
			||||||
			result.text = lng_action_changed_title(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle)));
 | 
								result.text = lng_action_changed_title(lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle)));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
| 
						 | 
					@ -423,7 +423,7 @@ TextWithEntities HistoryService::selectedText(TextSelection selection) const {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString HistoryService::inDialogsText() const {
 | 
					QString HistoryService::inDialogsText() const {
 | 
				
			||||||
	return textcmdLink(1, textClean(notificationText()));
 | 
						return textcmdLink(1, TextUtilities::Clean(notificationText()));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString HistoryService::inReplyText() const {
 | 
					QString HistoryService::inReplyText() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2873,14 +2873,15 @@ void HistoryWidget::saveEditMsg() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto &textWithTags = _field->getTextWithTags();
 | 
						auto &textWithTags = _field->getTextWithTags();
 | 
				
			||||||
	auto prepareFlags = itemTextOptions(_history, App::self()).flags;
 | 
						auto prepareFlags = itemTextOptions(_history, App::self()).flags;
 | 
				
			||||||
	EntitiesInText sendingEntities, leftEntities = ConvertTextTagsToEntities(textWithTags.tags);
 | 
						auto sending = TextWithEntities();
 | 
				
			||||||
	QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
 | 
						auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
 | 
				
			||||||
 | 
						TextUtilities::PrepareForSending(left, prepareFlags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | 
						if (!TextUtilities::CutPart(sending, left, MaxMessageSize)) {
 | 
				
			||||||
		_field->selectAll();
 | 
							_field->selectAll();
 | 
				
			||||||
		_field->setFocus();
 | 
							_field->setFocus();
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	} else if (!leftText.isEmpty()) {
 | 
						} else if (!left.text.isEmpty()) {
 | 
				
			||||||
		Ui::show(Box<InformBox>(lang(lng_edit_too_long)));
 | 
							Ui::show(Box<InformBox>(lang(lng_edit_too_long)));
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -2889,12 +2890,12 @@ void HistoryWidget::saveEditMsg() {
 | 
				
			||||||
	if (webPageId == CancelledWebPageId) {
 | 
						if (webPageId == CancelledWebPageId) {
 | 
				
			||||||
		sendFlags |= MTPmessages_EditMessage::Flag::f_no_webpage;
 | 
							sendFlags |= MTPmessages_EditMessage::Flag::f_no_webpage;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto localEntities = linksToMTP(sendingEntities);
 | 
						auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
 | 
				
			||||||
	auto sentEntities = linksToMTP(sendingEntities, true);
 | 
						auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal);
 | 
				
			||||||
	if (!sentEntities.v.isEmpty()) {
 | 
						if (!sentEntities.v.isEmpty()) {
 | 
				
			||||||
		sendFlags |= MTPmessages_EditMessage::Flag::f_entities;
 | 
							sendFlags |= MTPmessages_EditMessage::Flag::f_entities;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_saveEditMsgRequestId = MTP::send(MTPmessages_EditMessage(MTP_flags(sendFlags), _history->peer->input, MTP_int(_editMsgId), MTP_string(sendingText), MTPnullMarkup, sentEntities), rpcDone(&HistoryWidget::saveEditMsgDone, _history), rpcFail(&HistoryWidget::saveEditMsgFail, _history));
 | 
						_saveEditMsgRequestId = MTP::send(MTPmessages_EditMessage(MTP_flags(sendFlags), _history->peer->input, MTP_int(_editMsgId), MTP_string(sending.text), MTPnullMarkup, sentEntities), rpcDone(&HistoryWidget::saveEditMsgDone, _history), rpcFail(&HistoryWidget::saveEditMsgFail, _history));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req) {
 | 
					void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req) {
 | 
				
			||||||
| 
						 | 
					@ -3841,7 +3842,7 @@ void HistoryWidget::onKbToggle(bool manual) {
 | 
				
			||||||
		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
							_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
				
			||||||
		if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
 | 
							if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
 | 
				
			||||||
			updateReplyToName();
 | 
								updateReplyToName();
 | 
				
			||||||
			_replyEditMsgText.setText(st::messageTextStyle, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
								_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
				
			||||||
			_fieldBarCancel->show();
 | 
								_fieldBarCancel->show();
 | 
				
			||||||
			updateMouseTracking();
 | 
								updateMouseTracking();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -3860,7 +3861,7 @@ void HistoryWidget::onKbToggle(bool manual) {
 | 
				
			||||||
		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
							_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
				
			||||||
		if (_kbReplyTo && !_editMsgId && !_replyToId) {
 | 
							if (_kbReplyTo && !_editMsgId && !_replyToId) {
 | 
				
			||||||
			updateReplyToName();
 | 
								updateReplyToName();
 | 
				
			||||||
			_replyEditMsgText.setText(st::messageTextStyle, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
								_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
				
			||||||
			_fieldBarCancel->show();
 | 
								_fieldBarCancel->show();
 | 
				
			||||||
			updateMouseTracking();
 | 
								updateMouseTracking();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -5181,7 +5182,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 | 
				
			||||||
			_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
								_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
				
			||||||
			if (_kbReplyTo && !_replyToId) {
 | 
								if (_kbReplyTo && !_replyToId) {
 | 
				
			||||||
				updateReplyToName();
 | 
									updateReplyToName();
 | 
				
			||||||
				_replyEditMsgText.setText(st::messageTextStyle, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
									_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
				
			||||||
				_fieldBarCancel->show();
 | 
									_fieldBarCancel->show();
 | 
				
			||||||
				updateMouseTracking();
 | 
									updateMouseTracking();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -5440,7 +5441,7 @@ void HistoryWidget::updatePinnedBar(bool force) {
 | 
				
			||||||
		_pinnedBar->msg = App::histItemById(_history->channelId(), _pinnedBar->msgId);
 | 
							_pinnedBar->msg = App::histItemById(_history->channelId(), _pinnedBar->msgId);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (_pinnedBar->msg) {
 | 
						if (_pinnedBar->msg) {
 | 
				
			||||||
		_pinnedBar->text.setText(st::messageTextStyle, textClean(_pinnedBar->msg->notificationText()), _textDlgOptions);
 | 
							_pinnedBar->text.setText(st::messageTextStyle, TextUtilities::Clean(_pinnedBar->msg->notificationText()), _textDlgOptions);
 | 
				
			||||||
		update();
 | 
							update();
 | 
				
			||||||
	} else if (force) {
 | 
						} else if (force) {
 | 
				
			||||||
		if (_peer && _peer->isMegagroup()) {
 | 
							if (_peer && _peer->isMegagroup()) {
 | 
				
			||||||
| 
						 | 
					@ -5667,7 +5668,7 @@ void HistoryWidget::onReplyToMessage() {
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		_replyEditMsg = to;
 | 
							_replyEditMsg = to;
 | 
				
			||||||
		_replyToId = to->id;
 | 
							_replyToId = to->id;
 | 
				
			||||||
		_replyEditMsgText.setText(st::messageTextStyle, textClean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
							_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		updateBotKeyboard();
 | 
							updateBotKeyboard();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5709,10 +5710,8 @@ void HistoryWidget::onEditMessage() {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto original = to->originalText();
 | 
						auto original = to->originalText();
 | 
				
			||||||
	auto editText = textApplyEntities(original.text, original.entities);
 | 
						auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) };
 | 
				
			||||||
	auto editTags = ConvertEntitiesToTextTags(original.entities);
 | 
						auto cursor = MessageCursor { editData.text.size(), editData.text.size(), QFIXED_MAX };
 | 
				
			||||||
	TextWithTags editData = { editText, editTags };
 | 
					 | 
				
			||||||
	MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
 | 
					 | 
				
			||||||
	_history->setEditDraft(std::make_unique<Data::Draft>(editData, to->id, cursor, false));
 | 
						_history->setEditDraft(std::make_unique<Data::Draft>(editData, to->id, cursor, false));
 | 
				
			||||||
	applyDraft(false);
 | 
						applyDraft(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6013,7 +6012,7 @@ void HistoryWidget::updatePreview() {
 | 
				
			||||||
#else // OS_MAC_OLD
 | 
					#else // OS_MAC_OLD
 | 
				
			||||||
			auto linkText = _previewLinks.split(' ').at(0);
 | 
								auto linkText = _previewLinks.split(' ').at(0);
 | 
				
			||||||
#endif // OS_MAC_OLD
 | 
					#endif // OS_MAC_OLD
 | 
				
			||||||
			_previewDescription.setText(st::messageTextStyle, textClean(linkText), _textDlgOptions);
 | 
								_previewDescription.setText(st::messageTextStyle, TextUtilities::Clean(linkText), _textDlgOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			int32 t = (_previewData->pendingTill - unixtime()) * 1000;
 | 
								int32 t = (_previewData->pendingTill - unixtime()) * 1000;
 | 
				
			||||||
			if (t <= 0) t = 1;
 | 
								if (t <= 0) t = 1;
 | 
				
			||||||
| 
						 | 
					@ -6045,7 +6044,7 @@ void HistoryWidget::updatePreview() {
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_previewTitle.setText(st::msgNameStyle, title, _textNameOptions);
 | 
								_previewTitle.setText(st::msgNameStyle, title, _textNameOptions);
 | 
				
			||||||
			_previewDescription.setText(st::messageTextStyle, textClean(desc), _textDlgOptions);
 | 
								_previewDescription.setText(st::messageTextStyle, TextUtilities::Clean(desc), _textDlgOptions);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else if (!readyToForward() && !replyToId() && !_editMsgId) {
 | 
						} else if (!readyToForward() && !replyToId() && !_editMsgId) {
 | 
				
			||||||
		_fieldBarCancel->hide();
 | 
							_fieldBarCancel->hide();
 | 
				
			||||||
| 
						 | 
					@ -6060,9 +6059,7 @@ void HistoryWidget::onCancel() {
 | 
				
			||||||
		onInlineBotCancel();
 | 
							onInlineBotCancel();
 | 
				
			||||||
	} else if (_editMsgId) {
 | 
						} else if (_editMsgId) {
 | 
				
			||||||
		auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
 | 
							auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
 | 
				
			||||||
		auto editText = textApplyEntities(original.text, original.entities);
 | 
							auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) };
 | 
				
			||||||
		auto editTags = ConvertEntitiesToTextTags(original.entities);
 | 
					 | 
				
			||||||
		TextWithTags editData = { editText, editTags };
 | 
					 | 
				
			||||||
		if (_replyEditMsg && editData != _field->getTextWithTags()) {
 | 
							if (_replyEditMsg && editData != _field->getTextWithTags()) {
 | 
				
			||||||
			Ui::show(Box<ConfirmBox>(
 | 
								Ui::show(Box<ConfirmBox>(
 | 
				
			||||||
				lang(lng_cancel_edit_post_sure),
 | 
									lang(lng_cancel_edit_post_sure),
 | 
				
			||||||
| 
						 | 
					@ -6329,7 +6326,7 @@ void HistoryWidget::updateReplyEditTexts(bool force) {
 | 
				
			||||||
		_replyEditMsg = App::histItemById(_channel, _editMsgId ? _editMsgId : _replyToId);
 | 
							_replyEditMsg = App::histItemById(_channel, _editMsgId ? _editMsgId : _replyToId);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (_replyEditMsg) {
 | 
						if (_replyEditMsg) {
 | 
				
			||||||
		_replyEditMsgText.setText(st::messageTextStyle, textClean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
							_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		updateBotKeyboard();
 | 
							updateBotKeyboard();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6390,7 +6387,7 @@ void HistoryWidget::updateForwardingTexts() {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_toForwardFrom.setText(st::msgNameStyle, from, _textNameOptions);
 | 
						_toForwardFrom.setText(st::msgNameStyle, from, _textNameOptions);
 | 
				
			||||||
	_toForwardText.setText(st::messageTextStyle, textClean(text), _textDlgOptions);
 | 
						_toForwardText.setText(st::messageTextStyle, TextUtilities::Clean(text), _textDlgOptions);
 | 
				
			||||||
	_toForwardNameVersion = version;
 | 
						_toForwardNameVersion = version;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -570,7 +570,7 @@ void Video::initDimensions() {
 | 
				
			||||||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
						_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
				
			||||||
	int32 textWidth = _maxw - (withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0);
 | 
						int32 textWidth = _maxw - (withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0);
 | 
				
			||||||
	TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
				
			||||||
	QString title = textOneLine(_result->getLayoutTitle());
 | 
						auto title = TextUtilities::SingleLine(_result->getLayoutTitle());
 | 
				
			||||||
	if (title.isEmpty()) {
 | 
						if (title.isEmpty()) {
 | 
				
			||||||
		title = lang(lng_media_video);
 | 
							title = lang(lng_media_video);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -685,7 +685,7 @@ void File::initDimensions() {
 | 
				
			||||||
	int textWidth = _maxw - (st::msgFileSize + st::inlineThumbSkip);
 | 
						int textWidth = _maxw - (st::msgFileSize + st::inlineThumbSkip);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
				
			||||||
	_title.setText(st::semiboldTextStyle, textOneLine(_result->getLayoutTitle()), titleOpts);
 | 
						_title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto };
 | 
				
			||||||
	_description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
 | 
						_description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
 | 
				
			||||||
| 
						 | 
					@ -890,7 +890,7 @@ void Contact::initDimensions() {
 | 
				
			||||||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
						_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
				
			||||||
	int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
 | 
						int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
 | 
				
			||||||
	TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
				
			||||||
	_title.setText(st::semiboldTextStyle, textOneLine(_result->getLayoutTitle()), titleOpts);
 | 
						_title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
 | 
				
			||||||
	int32 titleHeight = qMin(_title.countHeight(_maxw), st::semiboldFont->height);
 | 
						int32 titleHeight = qMin(_title.countHeight(_maxw), st::semiboldFont->height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto };
 | 
				
			||||||
| 
						 | 
					@ -987,7 +987,7 @@ void Article::initDimensions() {
 | 
				
			||||||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
						_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
				
			||||||
	int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0);
 | 
						int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0);
 | 
				
			||||||
	TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
				
			||||||
	_title.setText(st::semiboldTextStyle, textOneLine(_result->getLayoutTitle()), titleOpts);
 | 
						_title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
 | 
				
			||||||
	int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
 | 
						int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int32 descriptionLines = (_withThumb || _url) ? 2 : 3;
 | 
						int32 descriptionLines = (_withThumb || _url) ? 2 : 3;
 | 
				
			||||||
| 
						 | 
					@ -1155,7 +1155,7 @@ void Game::initDimensions() {
 | 
				
			||||||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
						_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
				
			||||||
	int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
 | 
						int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
 | 
				
			||||||
	TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
				
			||||||
	_title.setText(st::semiboldTextStyle, textOneLine(_result->getLayoutTitle()), titleOpts);
 | 
						_title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
 | 
				
			||||||
	int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
 | 
						int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int32 descriptionLines = 2;
 | 
						int32 descriptionLines = 2;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,7 +136,7 @@ std::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case mtpc_botInlineMessageText: {
 | 
						case mtpc_botInlineMessageText: {
 | 
				
			||||||
		auto &r = message->c_botInlineMessageText();
 | 
							auto &r = message->c_botInlineMessageText();
 | 
				
			||||||
		auto entities = r.has_entities() ? entitiesFromMTP(r.ventities.v) : EntitiesInText();
 | 
							auto entities = r.has_entities() ? TextUtilities::EntitiesFromMTP(r.ventities.v) : EntitiesInText();
 | 
				
			||||||
		result->sendData = std::make_unique<internal::SendText>(qs(r.vmessage), entities, r.is_no_webpage());
 | 
							result->sendData = std::make_unique<internal::SendText>(qs(r.vmessage), entities, r.is_no_webpage());
 | 
				
			||||||
		if (result->_type == Type::Photo) {
 | 
							if (result->_type == Type::Photo) {
 | 
				
			||||||
			result->createPhoto();
 | 
								result->createPhoto();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@ QString SendDataCommon::getErrorOnSend(const Result *owner, History *history) co
 | 
				
			||||||
SendDataCommon::SentMTPMessageFields SendText::getSentMessageFields() const {
 | 
					SendDataCommon::SentMTPMessageFields SendText::getSentMessageFields() const {
 | 
				
			||||||
	SentMTPMessageFields result;
 | 
						SentMTPMessageFields result;
 | 
				
			||||||
	result.text = MTP_string(_message);
 | 
						result.text = MTP_string(_message);
 | 
				
			||||||
	result.entities = linksToMTP(_entities);
 | 
						result.entities = TextUtilities::EntitiesToMTP(_entities);
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1479,23 +1479,24 @@ void MainWidget::sendMessage(const MessageToSend &message) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	saveRecentHashtags(textWithTags.text);
 | 
						saveRecentHashtags(textWithTags.text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	EntitiesInText sendingEntities, leftEntities = ConvertTextTagsToEntities(textWithTags.tags);
 | 
						auto sending = TextWithEntities();
 | 
				
			||||||
 | 
						auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
 | 
				
			||||||
	auto prepareFlags = itemTextOptions(history, App::self()).flags;
 | 
						auto prepareFlags = itemTextOptions(history, App::self()).flags;
 | 
				
			||||||
	QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
 | 
						TextUtilities::PrepareForSending(left, prepareFlags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	HistoryItem *lastMessage = nullptr;
 | 
						HistoryItem *lastMessage = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	MsgId replyTo = (message.replyTo < 0) ? _history->replyToId() : message.replyTo;
 | 
						auto replyTo = (message.replyTo < 0) ? _history->replyToId() : message.replyTo;
 | 
				
			||||||
	while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | 
						while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
 | 
				
			||||||
		FullMsgId newId(peerToChannel(history->peer->id), clientMsgId());
 | 
							auto newId = FullMsgId(peerToChannel(history->peer->id), clientMsgId());
 | 
				
			||||||
		uint64 randomId = rand_value<uint64>();
 | 
							auto randomId = rand_value<uint64>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		trimTextWithEntities(sendingText, &sendingEntities);
 | 
							TextUtilities::Trim(sending);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		App::historyRegRandom(randomId, newId);
 | 
							App::historyRegRandom(randomId, newId);
 | 
				
			||||||
		App::historyRegSentData(randomId, history->peer->id, sendingText);
 | 
							App::historyRegSentData(randomId, history->peer->id, sending.text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		MTPstring msgText(MTP_string(sendingText));
 | 
							MTPstring msgText(MTP_string(sending.text));
 | 
				
			||||||
		auto flags = NewMessageFlags(history->peer) | MTPDmessage::Flag::f_entities; // unread, out
 | 
							auto flags = NewMessageFlags(history->peer) | MTPDmessage::Flag::f_entities; // unread, out
 | 
				
			||||||
		auto sendFlags = MTPmessages_SendMessage::Flags(0);
 | 
							auto sendFlags = MTPmessages_SendMessage::Flags(0);
 | 
				
			||||||
		if (replyTo) {
 | 
							if (replyTo) {
 | 
				
			||||||
| 
						 | 
					@ -1523,8 +1524,8 @@ void MainWidget::sendMessage(const MessageToSend &message) {
 | 
				
			||||||
		if (silentPost) {
 | 
							if (silentPost) {
 | 
				
			||||||
			sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
 | 
								sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		auto localEntities = linksToMTP(sendingEntities);
 | 
							auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
 | 
				
			||||||
		auto sentEntities = linksToMTP(sendingEntities, true);
 | 
							auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal);
 | 
				
			||||||
		if (!sentEntities.v.isEmpty()) {
 | 
							if (!sentEntities.v.isEmpty()) {
 | 
				
			||||||
			sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
 | 
								sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -1546,7 +1547,7 @@ void MainWidget::saveRecentHashtags(const QString &text) {
 | 
				
			||||||
	bool found = false;
 | 
						bool found = false;
 | 
				
			||||||
	QRegularExpressionMatch m;
 | 
						QRegularExpressionMatch m;
 | 
				
			||||||
	RecentHashtagPack recent(cRecentWriteHashtags());
 | 
						RecentHashtagPack recent(cRecentWriteHashtags());
 | 
				
			||||||
	for (int32 i = 0, next = 0; (m = reHashtag().match(text, i)).hasMatch(); i = next) {
 | 
						for (int32 i = 0, next = 0; (m = TextUtilities::RegExpHashtag().match(text, i)).hasMatch(); i = next) {
 | 
				
			||||||
		i = m.capturedStart();
 | 
							i = m.capturedStart();
 | 
				
			||||||
		next = m.capturedEnd();
 | 
							next = m.capturedEnd();
 | 
				
			||||||
		if (m.hasMatch()) {
 | 
							if (m.hasMatch()) {
 | 
				
			||||||
| 
						 | 
					@ -2093,12 +2094,11 @@ void MainWidget::dialogsCancelled() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWidget::insertCheckedServiceNotification(const TextWithEntities &message, const MTPMessageMedia &media, int32 date) {
 | 
					void MainWidget::insertCheckedServiceNotification(const TextWithEntities &message, const MTPMessageMedia &media, int32 date) {
 | 
				
			||||||
	auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id | MTPDmessage_ClientFlag::f_clientside_unread;
 | 
						auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id | MTPDmessage_ClientFlag::f_clientside_unread;
 | 
				
			||||||
	QString sendingText, leftText = message.text;
 | 
						auto sending = TextWithEntities(), left = message;
 | 
				
			||||||
	EntitiesInText sendingEntities, leftEntities = message.entities;
 | 
					 | 
				
			||||||
	HistoryItem *item = nullptr;
 | 
						HistoryItem *item = nullptr;
 | 
				
			||||||
	while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | 
						while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
 | 
				
			||||||
		MTPVector<MTPMessageEntity> localEntities = linksToMTP(sendingEntities);
 | 
							auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
 | 
				
			||||||
		item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(AuthSession::CurrentUserId())), MTPnullFwdHeader, MTPint(), MTPint(), MTP_int(date), MTP_string(sendingText), media, MTPnullMarkup, localEntities, MTPint(), MTPint()), NewMessageUnread);
 | 
							item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(AuthSession::CurrentUserId())), MTPnullFwdHeader, MTPint(), MTPint(), MTP_int(date), MTP_string(sending.text), media, MTPnullMarkup, localEntities, MTPint(), MTPint()), NewMessageUnread);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (item) {
 | 
						if (item) {
 | 
				
			||||||
		_history->peerMessagesUpdated(item->history()->peer->id);
 | 
							_history->peerMessagesUpdated(item->history()->peer->id);
 | 
				
			||||||
| 
						 | 
					@ -4878,7 +4878,7 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
 | 
				
			||||||
					if (d.has_entities() && !mentionUsersLoaded(d.ventities)) {
 | 
										if (d.has_entities() && !mentionUsersLoaded(d.ventities)) {
 | 
				
			||||||
						AuthSession::Current().api().requestMessageData(item->history()->peer->asChannel(), item->id, ApiWrap::RequestMessageDataCallback());
 | 
											AuthSession::Current().api().requestMessageData(item->history()->peer->asChannel(), item->id, ApiWrap::RequestMessageDataCallback());
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					auto entities = d.has_entities() ? entitiesFromMTP(d.ventities.v) : EntitiesInText();
 | 
										auto entities = d.has_entities() ? TextUtilities::EntitiesFromMTP(d.ventities.v) : EntitiesInText();
 | 
				
			||||||
					item->setText({ text, entities });
 | 
										item->setText({ text, entities });
 | 
				
			||||||
					item->updateMedia(d.has_media() ? (&d.vmedia) : nullptr);
 | 
										item->updateMedia(d.has_media() ? (&d.vmedia) : nullptr);
 | 
				
			||||||
					item->addToOverview(AddToOverviewNew);
 | 
										item->addToOverview(AddToOverviewNew);
 | 
				
			||||||
| 
						 | 
					@ -5129,9 +5129,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 | 
				
			||||||
		auto &d = update.c_updateUserName();
 | 
							auto &d = update.c_updateUserName();
 | 
				
			||||||
		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | 
							if (auto user = App::userLoaded(d.vuser_id.v)) {
 | 
				
			||||||
			if (user->contact <= 0) {
 | 
								if (user->contact <= 0) {
 | 
				
			||||||
				user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername)));
 | 
									user->setName(TextUtilities::SingleLine(qs(d.vfirst_name)), TextUtilities::SingleLine(qs(d.vlast_name)), user->nameOrPhone, TextUtilities::SingleLine(qs(d.vusername)));
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				user->setName(textOneLine(user->firstName), textOneLine(user->lastName), user->nameOrPhone, textOneLine(qs(d.vusername)));
 | 
									user->setName(TextUtilities::SingleLine(user->firstName), TextUtilities::SingleLine(user->lastName), user->nameOrPhone, TextUtilities::SingleLine(qs(d.vusername)));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			App::markPeerUpdated(user);
 | 
								App::markPeerUpdated(user);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -5234,7 +5234,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 | 
				
			||||||
		if (d.is_popup()) {
 | 
							if (d.is_popup()) {
 | 
				
			||||||
			Ui::show(Box<InformBox>(qs(d.vmessage)));
 | 
								Ui::show(Box<InformBox>(qs(d.vmessage)));
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			App::wnd()->serviceNotification({ qs(d.vmessage), entitiesFromMTP(d.ventities.v) }, d.vmedia);
 | 
								App::wnd()->serviceNotification({ qs(d.vmessage), TextUtilities::EntitiesFromMTP(d.ventities.v) }, d.vmedia);
 | 
				
			||||||
			emit App::wnd()->checkNewAuthorization();
 | 
								emit App::wnd()->checkNewAuthorization();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} break;
 | 
						} break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -323,7 +323,7 @@ void CoverWidget::handleSongChange() {
 | 
				
			||||||
	if (song->performer.isEmpty()) {
 | 
						if (song->performer.isEmpty()) {
 | 
				
			||||||
		textWithEntities.text = song->title.isEmpty() ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
 | 
							textWithEntities.text = song->title.isEmpty() ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title);
 | 
							auto title = song->title.isEmpty() ? qsl("Unknown Track") : TextUtilities::Clean(song->title);
 | 
				
			||||||
		textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
 | 
							textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
 | 
				
			||||||
		textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | 
							textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -500,7 +500,7 @@ void Widget::handleSongChange() {
 | 
				
			||||||
		if (!song || song->performer.isEmpty()) {
 | 
							if (!song || song->performer.isEmpty()) {
 | 
				
			||||||
			textWithEntities.text = (!song || song->title.isEmpty()) ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
 | 
								textWithEntities.text = (!song || song->title.isEmpty()) ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title);
 | 
								auto title = song->title.isEmpty() ? qsl("Unknown Track") : TextUtilities::Clean(song->title);
 | 
				
			||||||
			textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
 | 
								textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
 | 
				
			||||||
			textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | 
								textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -506,7 +506,7 @@ Voice::Voice(DocumentData *voice, HistoryItem *parent, const style::OverviewFile
 | 
				
			||||||
	setDocumentLinks(_data);
 | 
						setDocumentLinks(_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	updateName();
 | 
						updateName();
 | 
				
			||||||
	QString d = textcmdLink(1, textRichPrepare(langDateTime(date(_data->date))));
 | 
						QString d = textcmdLink(1, TextUtilities::EscapeForRichParsing(langDateTime(date(_data->date))));
 | 
				
			||||||
	TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto };
 | 
						TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto };
 | 
				
			||||||
	_details.setText(st::defaultTextStyle, lng_date_and_duration(lt_date, d, lt_duration, formatDurationText(_data->voice()->duration)), opts);
 | 
						_details.setText(st::defaultTextStyle, lng_date_and_duration(lt_date, d, lt_duration, formatDurationText(_data->voice()->duration)), opts);
 | 
				
			||||||
	_details.setLink(1, goToMessageClickHandler(parent));
 | 
						_details.setLink(1, goToMessageClickHandler(parent));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -309,9 +309,9 @@ void GroupMembersWidget::refreshLimitReached() {
 | 
				
			||||||
	bool limitReachedShown = (itemsCount() >= Global::ChatSizeMax()) && chat->amCreator() && !emptyTitle();
 | 
						bool limitReachedShown = (itemsCount() >= Global::ChatSizeMax()) && chat->amCreator() && !emptyTitle();
 | 
				
			||||||
	if (limitReachedShown && !_limitReachedInfo) {
 | 
						if (limitReachedShown && !_limitReachedInfo) {
 | 
				
			||||||
		_limitReachedInfo.create(this, st::profileLimitReachedLabel);
 | 
							_limitReachedInfo.create(this, st::profileLimitReachedLabel);
 | 
				
			||||||
		QString title = textRichPrepare(lng_profile_migrate_reached(lt_count, Global::ChatSizeMax()));
 | 
							QString title = TextUtilities::EscapeForRichParsing(lng_profile_migrate_reached(lt_count, Global::ChatSizeMax()));
 | 
				
			||||||
		QString body = textRichPrepare(lang(lng_profile_migrate_body));
 | 
							QString body = TextUtilities::EscapeForRichParsing(lang(lng_profile_migrate_body));
 | 
				
			||||||
		QString link = textRichPrepare(lang(lng_profile_migrate_learn_more));
 | 
							QString link = TextUtilities::EscapeForRichParsing(lang(lng_profile_migrate_learn_more));
 | 
				
			||||||
		QString text = qsl("%1%2%3\n%4 [a href=\"https://telegram.org/blog/supergroups5k\"]%5[/a]").arg(textcmdStartSemibold()).arg(title).arg(textcmdStopSemibold()).arg(body).arg(link);
 | 
							QString text = qsl("%1%2%3\n%4 [a href=\"https://telegram.org/blog/supergroups5k\"]%5[/a]").arg(textcmdStartSemibold()).arg(title).arg(textcmdStopSemibold()).arg(body).arg(link);
 | 
				
			||||||
		_limitReachedInfo->setRichText(text);
 | 
							_limitReachedInfo->setRichText(text);
 | 
				
			||||||
		_limitReachedInfo->setClickHandlerHook([this](const ClickHandlerPtr &handler, Qt::MouseButton button) {
 | 
							_limitReachedInfo->setClickHandlerHook([this](const ClickHandlerPtr &handler, Qt::MouseButton button) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,12 +149,12 @@ void InfoWidget::refreshAbout() {
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_about.destroy();
 | 
						_about.destroy();
 | 
				
			||||||
	auto aboutText = TextWithEntities { textClean(getAboutText()) };
 | 
						auto aboutText = TextWithEntities { TextUtilities::Clean(getAboutText()) };
 | 
				
			||||||
	if (!aboutText.text.isEmpty()) {
 | 
						if (!aboutText.text.isEmpty()) {
 | 
				
			||||||
		_about.create(this, st::profileBlockTextPart);
 | 
							_about.create(this, st::profileBlockTextPart);
 | 
				
			||||||
		_about->show();
 | 
							_about->show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		textParseEntities(aboutText.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &aboutText.entities);
 | 
							TextUtilities::ParseEntities(aboutText, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
 | 
				
			||||||
		_about->setMarkedText(aboutText);
 | 
							_about->setMarkedText(aboutText);
 | 
				
			||||||
		_about->setSelectable(true);
 | 
							_about->setSelectable(true);
 | 
				
			||||||
		_about->setClickHandlerHook([this](const ClickHandlerPtr &handler, Qt::MouseButton button) {
 | 
							_about->setClickHandlerHook([this](const ClickHandlerPtr &handler, Qt::MouseButton button) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1120,16 +1120,7 @@ void AddParticipantBoxSearchController::searchGlobalDone(mtpRequestId requestId,
 | 
				
			||||||
void AddParticipantBoxSearchController::addChatsContacts() {
 | 
					void AddParticipantBoxSearchController::addChatsContacts() {
 | 
				
			||||||
	_chatsContactsAdded = true;
 | 
						_chatsContactsAdded = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto filterWordList = _query.split(cWordSplit(), QString::SkipEmptyParts);
 | 
						auto wordList = TextUtilities::PrepareSearchWords(_query);
 | 
				
			||||||
	auto wordsCount = filterWordList.size();
 | 
					 | 
				
			||||||
	auto wordList = QStringList();
 | 
					 | 
				
			||||||
	wordList.reserve(wordsCount);
 | 
					 | 
				
			||||||
	for_const (auto &word, filterWordList) {
 | 
					 | 
				
			||||||
		auto trimmed = word.trimmed();
 | 
					 | 
				
			||||||
		if (!trimmed.isEmpty()) {
 | 
					 | 
				
			||||||
			wordList.push_back(trimmed);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (wordList.empty()) {
 | 
						if (wordList.empty()) {
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -461,22 +461,22 @@ void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer a
 | 
				
			||||||
void PeerData::fillNames() {
 | 
					void PeerData::fillNames() {
 | 
				
			||||||
	names.clear();
 | 
						names.clear();
 | 
				
			||||||
	chars.clear();
 | 
						chars.clear();
 | 
				
			||||||
	QString toIndex = textAccentFold(name);
 | 
						auto toIndex = TextUtilities::RemoveAccents(name);
 | 
				
			||||||
	if (cRussianLetters().match(toIndex).hasMatch()) {
 | 
						if (cRussianLetters().match(toIndex).hasMatch()) {
 | 
				
			||||||
		toIndex += ' ' + translitRusEng(toIndex);
 | 
							toIndex += ' ' + translitRusEng(toIndex);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (isUser()) {
 | 
						if (isUser()) {
 | 
				
			||||||
		if (!asUser()->nameOrPhone.isEmpty() && asUser()->nameOrPhone != name) toIndex += ' ' + textAccentFold(asUser()->nameOrPhone);
 | 
							if (!asUser()->nameOrPhone.isEmpty() && asUser()->nameOrPhone != name) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->nameOrPhone);
 | 
				
			||||||
		if (!asUser()->username.isEmpty()) toIndex += ' ' + textAccentFold(asUser()->username);
 | 
							if (!asUser()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->username);
 | 
				
			||||||
	} else if (isChannel()) {
 | 
						} else if (isChannel()) {
 | 
				
			||||||
		if (!asChannel()->username.isEmpty()) toIndex += ' ' + textAccentFold(asChannel()->username);
 | 
							if (!asChannel()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asChannel()->username);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex);
 | 
						toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QStringList namesList = toIndex.toLower().split(cWordSplit(), QString::SkipEmptyParts);
 | 
						auto namesList = TextUtilities::PrepareSearchWords(toIndex);
 | 
				
			||||||
	for (QStringList::const_iterator i = namesList.cbegin(), e = namesList.cend(); i != e; ++i) {
 | 
						for (auto &name : namesList) {
 | 
				
			||||||
		names.insert(*i);
 | 
							names.insert(name);
 | 
				
			||||||
		chars.insert(i->at(0));
 | 
							chars.insert(name[0]);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -417,21 +417,8 @@ void CountrySelectBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void CountrySelectBox::Inner::updateFilter(QString filter) {
 | 
					void CountrySelectBox::Inner::updateFilter(QString filter) {
 | 
				
			||||||
	filter = textSearchKey(filter);
 | 
						auto words = TextUtilities::PrepareSearchWords(filter);
 | 
				
			||||||
 | 
						filter = words.isEmpty() ? QString() : words.join(' ');
 | 
				
			||||||
	QStringList f;
 | 
					 | 
				
			||||||
	if (!filter.isEmpty()) {
 | 
					 | 
				
			||||||
		QStringList filterList = filter.split(cWordSplit(), QString::SkipEmptyParts);
 | 
					 | 
				
			||||||
		int l = filterList.size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		f.reserve(l);
 | 
					 | 
				
			||||||
		for (int i = 0; i < l; ++i) {
 | 
					 | 
				
			||||||
			QString filterName = filterList[i].trimmed();
 | 
					 | 
				
			||||||
			if (filterName.isEmpty()) continue;
 | 
					 | 
				
			||||||
			f.push_back(filterName);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		filter = f.join(' ');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (_filter != filter) {
 | 
						if (_filter != filter) {
 | 
				
			||||||
		_filter = filter;
 | 
							_filter = filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -441,7 +428,7 @@ void CountrySelectBox::Inner::updateFilter(QString filter) {
 | 
				
			||||||
			QChar first = _filter[0].toLower();
 | 
								QChar first = _filter[0].toLower();
 | 
				
			||||||
			CountriesIds &ids(countriesByLetter[first]);
 | 
								CountriesIds &ids(countriesByLetter[first]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
 | 
								QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			countriesFiltered.clear();
 | 
								countriesFiltered.clear();
 | 
				
			||||||
			for (CountriesIds::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
 | 
								for (CountriesIds::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -197,29 +197,30 @@ inline QString Filename(int index = Index()) {
 | 
				
			||||||
	return QString::fromLatin1(EmojiNames[index]);
 | 
						return QString::fromLatin1(EmojiNames[index]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline void appendPartToResult(QString &result, const QChar *start, const QChar *from, const QChar *to, EntitiesInText *inOutEntities) {
 | 
					inline void AppendPartToResult(TextWithEntities &result, const QChar *start, const QChar *from, const QChar *to) {
 | 
				
			||||||
	if (to > from) {
 | 
						if (to > from) {
 | 
				
			||||||
		for (auto &entity : *inOutEntities) {
 | 
							for (auto &entity : result.entities) {
 | 
				
			||||||
			if (entity.offset() >= to - start) break;
 | 
								if (entity.offset() >= to - start) break;
 | 
				
			||||||
			if (entity.offset() + entity.length() < from - start) continue;
 | 
								if (entity.offset() + entity.length() < from - start) continue;
 | 
				
			||||||
			if (entity.offset() >= from - start) {
 | 
								if (entity.offset() >= from - start) {
 | 
				
			||||||
				entity.extendToLeft(from - start - result.size());
 | 
									entity.extendToLeft(from - start - result.text.size());
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if (entity.offset() + entity.length() <= to - start) {
 | 
								if (entity.offset() + entity.length() <= to - start) {
 | 
				
			||||||
				entity.shrinkFromRight(from - start - result.size());
 | 
									entity.shrinkFromRight(from - start - result.text.size());
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		result.append(from, to - from);
 | 
							result.text.append(from, to - from);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities) {
 | 
					inline void ReplaceInText(TextWithEntities &result) {
 | 
				
			||||||
	auto result = QString();
 | 
						auto newText = TextWithEntities();
 | 
				
			||||||
	auto currentEntity = inOutEntities->begin();
 | 
						newText.entities = std::move(result.entities);
 | 
				
			||||||
	auto entitiesEnd = inOutEntities->end();
 | 
						auto currentEntity = newText.entities.begin();
 | 
				
			||||||
	auto emojiStart = text.constData();
 | 
						auto entitiesEnd = newText.entities.end();
 | 
				
			||||||
 | 
						auto emojiStart = result.text.constData();
 | 
				
			||||||
	auto emojiEnd = emojiStart;
 | 
						auto emojiEnd = emojiStart;
 | 
				
			||||||
	auto end = emojiStart + text.size();
 | 
						auto end = emojiStart + result.text.size();
 | 
				
			||||||
	auto canFindEmoji = true;
 | 
						auto canFindEmoji = true;
 | 
				
			||||||
	for (auto ch = emojiEnd; ch != end;) {
 | 
						for (auto ch = emojiEnd; ch != end;) {
 | 
				
			||||||
		auto emojiLength = 0;
 | 
							auto emojiLength = 0;
 | 
				
			||||||
| 
						 | 
					@ -234,9 +235,9 @@ inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities)
 | 
				
			||||||
		    (newEmojiEnd == end || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) &&
 | 
							    (newEmojiEnd == end || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) &&
 | 
				
			||||||
			(currentEntity == entitiesEnd || (ch < emojiStart + currentEntity->offset() && newEmojiEnd <= emojiStart + currentEntity->offset()) || (ch >= emojiStart + currentEntity->offset() + currentEntity->length() && newEmojiEnd > emojiStart + currentEntity->offset() + currentEntity->length()))
 | 
								(currentEntity == entitiesEnd || (ch < emojiStart + currentEntity->offset() && newEmojiEnd <= emojiStart + currentEntity->offset()) || (ch >= emojiStart + currentEntity->offset() + currentEntity->length() && newEmojiEnd > emojiStart + currentEntity->offset() + currentEntity->length()))
 | 
				
			||||||
		) {
 | 
							) {
 | 
				
			||||||
			if (result.isEmpty()) result.reserve(text.size());
 | 
								if (newText.text.isEmpty()) newText.text.reserve(result.text.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			appendPartToResult(result, emojiStart, emojiEnd, ch, inOutEntities);
 | 
								AppendPartToResult(newText, emojiStart, emojiEnd, ch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (emoji->hasVariants()) {
 | 
								if (emoji->hasVariants()) {
 | 
				
			||||||
				auto it = cEmojiVariants().constFind(emoji->nonColoredId());
 | 
									auto it = cEmojiVariants().constFind(emoji->nonColoredId());
 | 
				
			||||||
| 
						 | 
					@ -244,7 +245,7 @@ inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities)
 | 
				
			||||||
					emoji = emoji->variant(it.value());
 | 
										emoji = emoji->variant(it.value());
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			result.append(emoji->text());
 | 
								newText.text.append(emoji->text());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ch = emojiEnd = newEmojiEnd;
 | 
								ch = emojiEnd = newEmojiEnd;
 | 
				
			||||||
			canFindEmoji = true;
 | 
								canFindEmoji = true;
 | 
				
			||||||
| 
						 | 
					@ -257,11 +258,12 @@ inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities)
 | 
				
			||||||
			++ch;
 | 
								++ch;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (result.isEmpty()) return text;
 | 
						if (newText.text.isEmpty()) {
 | 
				
			||||||
 | 
							result.entities = std::move(newText.entities);
 | 
				
			||||||
	appendPartToResult(result, emojiStart, emojiEnd, end, inOutEntities);
 | 
						} else {
 | 
				
			||||||
 | 
							AppendPartToResult(newText, emojiStart, emojiEnd, end);
 | 
				
			||||||
	return result;
 | 
							result = std::move(newText);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline RecentEmojiPack &GetRecent() {
 | 
					inline RecentEmojiPack &GetRecent() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -493,33 +493,32 @@ public:
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextParser(Text *t, const QString &text, const TextParseOptions &options) : _t(t),
 | 
						TextParser(Text *t, const QString &text, const TextParseOptions &options) : _t(t),
 | 
				
			||||||
		src(text),
 | 
							source { text },
 | 
				
			||||||
		rich(options.flags & TextParseRichText),
 | 
							rich(options.flags & TextParseRichText),
 | 
				
			||||||
		multiline(options.flags & TextParseMultiline),
 | 
							multiline(options.flags & TextParseMultiline),
 | 
				
			||||||
		stopAfterWidth(QFIXED_MAX) {
 | 
							stopAfterWidth(QFIXED_MAX) {
 | 
				
			||||||
		if (options.flags & TextParseLinks) {
 | 
							if (options.flags & TextParseLinks) {
 | 
				
			||||||
			textParseEntities(src, options.flags, &entities, rich);
 | 
								TextUtilities::ParseEntities(source, options.flags, rich);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		parse(options);
 | 
							parse(options);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TextParser(Text *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t),
 | 
						TextParser(Text *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t),
 | 
				
			||||||
		src(textWithEntities.text),
 | 
							source(textWithEntities),
 | 
				
			||||||
		rich(options.flags & TextParseRichText),
 | 
							rich(options.flags & TextParseRichText),
 | 
				
			||||||
		multiline(options.flags & TextParseMultiline),
 | 
							multiline(options.flags & TextParseMultiline),
 | 
				
			||||||
		stopAfterWidth(QFIXED_MAX) {
 | 
							stopAfterWidth(QFIXED_MAX) {
 | 
				
			||||||
		auto preparsed = textWithEntities.entities;
 | 
							auto &preparsed = textWithEntities.entities;
 | 
				
			||||||
		if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
 | 
							if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
 | 
				
			||||||
			bool parseMentions = (options.flags & TextParseMentions);
 | 
								bool parseMentions = (options.flags & TextParseMentions);
 | 
				
			||||||
			bool parseHashtags = (options.flags & TextParseHashtags);
 | 
								bool parseHashtags = (options.flags & TextParseHashtags);
 | 
				
			||||||
			bool parseBotCommands = (options.flags & TextParseBotCommands);
 | 
								bool parseBotCommands = (options.flags & TextParseBotCommands);
 | 
				
			||||||
			bool parseMono = (options.flags & TextParseMono);
 | 
								bool parseMono = (options.flags & TextParseMono);
 | 
				
			||||||
			if (parseMentions && parseHashtags && parseBotCommands && parseMono) {
 | 
								if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMono) {
 | 
				
			||||||
				entities = preparsed;
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				int32 i = 0, l = preparsed.size();
 | 
									int32 i = 0, l = preparsed.size();
 | 
				
			||||||
				entities.reserve(l);
 | 
									source.entities.clear();
 | 
				
			||||||
				const QChar s = src.size();
 | 
									source.entities.reserve(l);
 | 
				
			||||||
 | 
									const QChar s = source.text.size();
 | 
				
			||||||
				for (; i < l; ++i) {
 | 
									for (; i < l; ++i) {
 | 
				
			||||||
					auto type = preparsed.at(i).type();
 | 
										auto type = preparsed.at(i).type();
 | 
				
			||||||
					if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
 | 
										if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
 | 
				
			||||||
| 
						 | 
					@ -528,7 +527,7 @@ public:
 | 
				
			||||||
						((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMono)) {
 | 
											((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMono)) {
 | 
				
			||||||
						continue;
 | 
											continue;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					entities.push_back(preparsed.at(i));
 | 
										source.entities.push_back(preparsed.at(i));
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -540,13 +539,13 @@ public:
 | 
				
			||||||
			stopAfterWidth = ((options.maxh / _t->_st->font->height) + 1) * options.maxw;
 | 
								stopAfterWidth = ((options.maxh / _t->_st->font->height) + 1) * options.maxw;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		start = src.constData();
 | 
							start = source.text.constData();
 | 
				
			||||||
		end = start + src.size();
 | 
							end = start + source.text.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		entitiesEnd = entities.cend();
 | 
							entitiesEnd = source.entities.cend();
 | 
				
			||||||
		waitingEntity = entities.cbegin();
 | 
							waitingEntity = source.entities.cbegin();
 | 
				
			||||||
		while (waitingEntity != entitiesEnd && waitingEntity->length() <= 0) ++waitingEntity;
 | 
							while (waitingEntity != entitiesEnd && waitingEntity->length() <= 0) ++waitingEntity;
 | 
				
			||||||
		int firstMonospaceOffset = EntityInText::firstMonospaceOffset(entities, end - start);
 | 
							int firstMonospaceOffset = EntityInText::firstMonospaceOffset(source.entities, end - start);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ptr = start;
 | 
							ptr = start;
 | 
				
			||||||
		while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
 | 
							while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
 | 
				
			||||||
| 
						 | 
					@ -587,51 +586,51 @@ public:
 | 
				
			||||||
		removeFlags.clear();
 | 
							removeFlags.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_t->_links.resize(maxLnkIndex);
 | 
							_t->_links.resize(maxLnkIndex);
 | 
				
			||||||
		for (Text::TextBlocks::const_iterator i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) {
 | 
							for (auto i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) {
 | 
				
			||||||
			ITextBlock *b = *i;
 | 
								auto b = *i;
 | 
				
			||||||
			if (b->lnkIndex() > 0x8000) {
 | 
								if (b->lnkIndex() > 0x8000) {
 | 
				
			||||||
				lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
 | 
									lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
 | 
				
			||||||
				if (_t->_links.size() < lnkIndex) {
 | 
									if (_t->_links.size() < lnkIndex) {
 | 
				
			||||||
					_t->_links.resize(lnkIndex);
 | 
										_t->_links.resize(lnkIndex);
 | 
				
			||||||
					const TextLinkData &link(links[lnkIndex - maxLnkIndex - 1]);
 | 
										auto &link = links[lnkIndex - maxLnkIndex - 1];
 | 
				
			||||||
					ClickHandlerPtr handler;
 | 
										ClickHandlerPtr handler;
 | 
				
			||||||
					switch (link.type) {
 | 
										switch (link.type) {
 | 
				
			||||||
					case EntityInTextCustomUrl: handler.reset(new HiddenUrlClickHandler(link.data)); break;
 | 
										case EntityInTextCustomUrl: handler = MakeShared<HiddenUrlClickHandler>(link.data); break;
 | 
				
			||||||
					case EntityInTextEmail:
 | 
										case EntityInTextEmail:
 | 
				
			||||||
					case EntityInTextUrl: handler.reset(new UrlClickHandler(link.data, link.displayStatus == LinkDisplayedFull)); break;
 | 
										case EntityInTextUrl: handler = MakeShared<UrlClickHandler>(link.data, link.displayStatus == LinkDisplayedFull); break;
 | 
				
			||||||
					case EntityInTextBotCommand: handler.reset(new BotCommandClickHandler(link.data)); break;
 | 
										case EntityInTextBotCommand: handler = MakeShared<BotCommandClickHandler>(link.data); break;
 | 
				
			||||||
					case EntityInTextHashtag:
 | 
										case EntityInTextHashtag:
 | 
				
			||||||
						if (options.flags & TextTwitterMentions) {
 | 
											if (options.flags & TextTwitterMentions) {
 | 
				
			||||||
							handler.reset(new UrlClickHandler(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true));
 | 
												handler = MakeShared<UrlClickHandler>(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true);
 | 
				
			||||||
						} else if (options.flags & TextInstagramMentions) {
 | 
											} else if (options.flags & TextInstagramMentions) {
 | 
				
			||||||
							handler.reset(new UrlClickHandler(qsl("https://instagram.com/explore/tags/") + link.data.mid(1) + '/', true));
 | 
												handler = MakeShared<UrlClickHandler>(qsl("https://instagram.com/explore/tags/") + link.data.mid(1) + '/', true);
 | 
				
			||||||
						} else {
 | 
											} else {
 | 
				
			||||||
							handler.reset(new HashtagClickHandler(link.data));
 | 
												handler = MakeShared<HashtagClickHandler>(link.data);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case EntityInTextMention:
 | 
										case EntityInTextMention:
 | 
				
			||||||
						if (options.flags & TextTwitterMentions) {
 | 
											if (options.flags & TextTwitterMentions) {
 | 
				
			||||||
							handler.reset(new UrlClickHandler(qsl("https://twitter.com/") + link.data.mid(1), true));
 | 
												handler = MakeShared<UrlClickHandler>(qsl("https://twitter.com/") + link.data.mid(1), true);
 | 
				
			||||||
						} else if (options.flags & TextInstagramMentions) {
 | 
											} else if (options.flags & TextInstagramMentions) {
 | 
				
			||||||
							handler.reset(new UrlClickHandler(qsl("https://instagram.com/") + link.data.mid(1) + '/', true));
 | 
												handler = MakeShared<UrlClickHandler>(qsl("https://instagram.com/") + link.data.mid(1) + '/', true);
 | 
				
			||||||
						} else {
 | 
											} else {
 | 
				
			||||||
							handler.reset(new MentionClickHandler(link.data));
 | 
												handler = MakeShared<MentionClickHandler>(link.data);
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
					case EntityInTextMentionName: {
 | 
										case EntityInTextMentionName: {
 | 
				
			||||||
						UserId userId = 0;
 | 
											auto fields = TextUtilities::MentionNameDataToFields(link.data);
 | 
				
			||||||
						uint64 accessHash = 0;
 | 
											if (fields.userId) {
 | 
				
			||||||
						if (mentionNameToFields(link.data, &userId, &accessHash)) {
 | 
												handler = MakeShared<MentionNameClickHandler>(link.text, fields.userId, fields.accessHash);
 | 
				
			||||||
							handler.reset(new MentionNameClickHandler(link.text, userId, accessHash));
 | 
					 | 
				
			||||||
						} else {
 | 
											} else {
 | 
				
			||||||
							LOG(("Bad mention name: %1").arg(link.data));
 | 
												LOG(("Bad mention name: %1").arg(link.data));
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					} break;
 | 
										} break;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					t_assert(!handler.isNull());
 | 
										if (!handler.isNull()) {
 | 
				
			||||||
						_t->setLink(lnkIndex, handler);
 | 
											_t->setLink(lnkIndex, handler);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				b->setLnkIndex(lnkIndex);
 | 
									b->setLnkIndex(lnkIndex);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -667,11 +666,9 @@ private:
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Text *_t;
 | 
						Text *_t;
 | 
				
			||||||
	QString src;
 | 
						TextWithEntities source;
 | 
				
			||||||
	const QChar *start, *end, *ptr;
 | 
						const QChar *start, *end, *ptr;
 | 
				
			||||||
	bool rich, multiline;
 | 
						bool rich, multiline;
 | 
				
			||||||
 | 
					 | 
				
			||||||
	EntitiesInText entities;
 | 
					 | 
				
			||||||
	EntitiesInText::const_iterator waitingEntity, entitiesEnd;
 | 
						EntitiesInText::const_iterator waitingEntity, entitiesEnd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	typedef QVector<TextLinkData> TextLinks;
 | 
						typedef QVector<TextLinkData> TextLinks;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -258,15 +258,6 @@ inline TextSelection unshiftSelection(TextSelection selection, const Text &byTex
 | 
				
			||||||
	return unshiftSelection(selection, byText.length());
 | 
						return unshiftSelection(selection, byText.length());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void initLinkSets();
 | 
					 | 
				
			||||||
const QSet<int32> &validProtocols();
 | 
					 | 
				
			||||||
const QSet<int32> &validTopDomains();
 | 
					 | 
				
			||||||
const QRegularExpression &reDomain();
 | 
					 | 
				
			||||||
const QRegularExpression &reMailName();
 | 
					 | 
				
			||||||
const QRegularExpression &reMailStart();
 | 
					 | 
				
			||||||
const QRegularExpression &reHashtag();
 | 
					 | 
				
			||||||
const QRegularExpression &reBotCommand();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// textcmd
 | 
					// textcmd
 | 
				
			||||||
QString textcmdSkipBlock(ushort w, ushort h);
 | 
					QString textcmdSkipBlock(ushort w, ushort h);
 | 
				
			||||||
QString textcmdStartLink(ushort lnkIndex);
 | 
					QString textcmdStartLink(ushort lnkIndex);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -116,22 +116,6 @@ struct TextWithEntities {
 | 
				
			||||||
	QString text;
 | 
						QString text;
 | 
				
			||||||
	EntitiesInText entities;
 | 
						EntitiesInText entities;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
inline void appendTextWithEntities(TextWithEntities &to, TextWithEntities &&append) {
 | 
					 | 
				
			||||||
	int entitiesShiftRight = to.text.size();
 | 
					 | 
				
			||||||
	for (auto &entity : append.entities) {
 | 
					 | 
				
			||||||
		entity.shiftRight(entitiesShiftRight);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	to.text += append.text;
 | 
					 | 
				
			||||||
	to.entities += append.entities;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// text preprocess
 | 
					 | 
				
			||||||
QString textClean(const QString &text);
 | 
					 | 
				
			||||||
QString textRichPrepare(const QString &text);
 | 
					 | 
				
			||||||
QString textOneLine(const QString &text, bool trim = true, bool rich = false);
 | 
					 | 
				
			||||||
QString textAccentFold(const QString &text);
 | 
					 | 
				
			||||||
QString textSearchKey(const QString &text);
 | 
					 | 
				
			||||||
bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &leftText, EntitiesInText &leftEntities, int32 limit);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum {
 | 
					enum {
 | 
				
			||||||
	TextParseMultiline = 0x001,
 | 
						TextParseMultiline = 0x001,
 | 
				
			||||||
| 
						 | 
					@ -148,39 +132,91 @@ enum {
 | 
				
			||||||
	TextInstagramHashtags = 0x800,
 | 
						TextInstagramHashtags = 0x800,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline bool mentionNameToFields(const QString &data, int32 *outUserId, uint64 *outAccessHash) {
 | 
					// Parsing helpers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace TextUtilities {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool IsValidProtocol(const QString &protocol);
 | 
				
			||||||
 | 
					bool IsValidTopDomain(const QString &domain);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpDomain();
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpDomainExplicit();
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpMailNameAtEnd();
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpHashtag();
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpMention();
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpBotCommand();
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpMonoInline();
 | 
				
			||||||
 | 
					const QRegularExpression &RegExpMonoBlock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline void Append(TextWithEntities &to, TextWithEntities &&append) {
 | 
				
			||||||
 | 
						auto entitiesShiftRight = to.text.size();
 | 
				
			||||||
 | 
						for (auto &entity : append.entities) {
 | 
				
			||||||
 | 
							entity.shiftRight(entitiesShiftRight);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						to.text += append.text;
 | 
				
			||||||
 | 
						to.entities += append.entities;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Text preprocess.
 | 
				
			||||||
 | 
					QString Clean(const QString &text);
 | 
				
			||||||
 | 
					QString EscapeForRichParsing(const QString &text);
 | 
				
			||||||
 | 
					QString SingleLine(const QString &text);
 | 
				
			||||||
 | 
					QString RemoveAccents(const QString &text);
 | 
				
			||||||
 | 
					QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride = nullptr);
 | 
				
			||||||
 | 
					bool CutPart(TextWithEntities &sending, TextWithEntities &left, int limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MentionNameFields {
 | 
				
			||||||
 | 
						MentionNameFields(int32 userId = 0, uint64 accessHash = 0) : userId(userId), accessHash(accessHash) {
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						int32 userId = 0;
 | 
				
			||||||
 | 
						uint64 accessHash = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					inline MentionNameFields MentionNameDataToFields(const QString &data) {
 | 
				
			||||||
	auto components = data.split('.');
 | 
						auto components = data.split('.');
 | 
				
			||||||
	if (!components.isEmpty()) {
 | 
						if (!components.isEmpty()) {
 | 
				
			||||||
		*outUserId = components.at(0).toInt();
 | 
							return { components.at(0).toInt(), (components.size() > 1) ? components.at(1).toULongLong() : 0 };
 | 
				
			||||||
		*outAccessHash = (components.size() > 1) ? components.at(1).toULongLong() : 0;
 | 
					 | 
				
			||||||
		return (*outUserId != 0);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return false;
 | 
						return MentionNameFields {};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline QString mentionNameFromFields(int32 userId, uint64 accessHash) {
 | 
					inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
 | 
				
			||||||
	return QString::number(userId) + '.' + QString::number(accessHash);
 | 
						auto result = QString::number(fields.userId);
 | 
				
			||||||
 | 
						if (fields.accessHash) {
 | 
				
			||||||
 | 
							result += '.' + QString::number(fields.accessHash);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities);
 | 
					EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities);
 | 
				
			||||||
MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending = false);
 | 
					enum class ConvertOption {
 | 
				
			||||||
 | 
						WithLocal,
 | 
				
			||||||
 | 
						SkipLocal,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &links, ConvertOption option = ConvertOption::WithLocal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// New entities are added to the ones that are already in inOutEntities.
 | 
					// New entities are added to the ones that are already in result.
 | 
				
			||||||
// Changes text if (flags & TextParseMono).
 | 
					// Changes text if (flags & TextParseMono).
 | 
				
			||||||
void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities, bool rich = false);
 | 
					void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
 | 
				
			||||||
QString textApplyEntities(const QString &text, const EntitiesInText &entities);
 | 
					QString ApplyEntities(const TextWithEntities &text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString prepareTextWithEntities(QString result, int32 flags, EntitiesInText *inOutEntities);
 | 
					void PrepareForSending(TextWithEntities &result, int32 flags);
 | 
				
			||||||
 | 
					void Trim(TextWithEntities &result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline QString prepareText(QString result, bool checkLinks = false) {
 | 
					enum class PrepareTextOption {
 | 
				
			||||||
	EntitiesInText entities;
 | 
						IgnoreLinks,
 | 
				
			||||||
	auto prepareFlags = checkLinks ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0;
 | 
						CheckLinks,
 | 
				
			||||||
	return prepareTextWithEntities(result, prepareFlags, &entities);
 | 
					};
 | 
				
			||||||
 | 
					inline QString PrepareForSending(const QString &text, PrepareTextOption option = PrepareTextOption::IgnoreLinks) {
 | 
				
			||||||
 | 
						auto result = TextWithEntities { text };
 | 
				
			||||||
 | 
						auto prepareFlags = (option == PrepareTextOption::CheckLinks) ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0;
 | 
				
			||||||
 | 
						PrepareForSending(result, prepareFlags);
 | 
				
			||||||
 | 
						return result.text;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// replace bad symbols with space and remove \r
 | 
					// Replace bad symbols with space and remove '\r'.
 | 
				
			||||||
void cleanTextWithEntities(QString &result, EntitiesInText *inOutEntities);
 | 
					void ApplyServerCleaning(TextWithEntities &result);
 | 
				
			||||||
void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities);
 | 
					
 | 
				
			||||||
 | 
					} // namespace TextUtilities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lang {
 | 
					namespace Lang {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -203,4 +239,4 @@ struct ReplaceTag<TextWithEntities> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					} // namespace Lang
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@ Widget::Widget(QWidget *parent, const Config &config) : TWidget(parent)
 | 
				
			||||||
	if (_multiline) {
 | 
						if (_multiline) {
 | 
				
			||||||
		toastOptions.maxh *= kToastMaxLines;
 | 
							toastOptions.maxh *= kToastMaxLines;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_text.setText(st::toastTextStyle, _multiline ? config.text : textOneLine(config.text), toastOptions);
 | 
						_text.setText(st::toastTextStyle, _multiline ? config.text : TextUtilities::SingleLine(config.text), toastOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	setAttribute(Qt::WA_TransparentForMouseEvents);
 | 
						setAttribute(Qt::WA_TransparentForMouseEvents);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -840,25 +840,22 @@ void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp!
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	initLinkSets();
 | 
						auto len = text.size();
 | 
				
			||||||
 | 
					 | 
				
			||||||
	int32 len = text.size();
 | 
					 | 
				
			||||||
	const QChar *start = text.unicode(), *end = start + text.size();
 | 
						const QChar *start = text.unicode(), *end = start + text.size();
 | 
				
			||||||
	for (int32 offset = 0, matchOffset = offset; offset < len;) {
 | 
						for (auto offset = 0, matchOffset = offset; offset < len;) {
 | 
				
			||||||
		QRegularExpressionMatch m = reDomain().match(text, matchOffset);
 | 
							auto m = TextUtilities::RegExpDomain().match(text, matchOffset);
 | 
				
			||||||
		if (!m.hasMatch()) break;
 | 
							if (!m.hasMatch()) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		int32 domainOffset = m.capturedStart();
 | 
							auto domainOffset = m.capturedStart();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		QString protocol = m.captured(1).toLower();
 | 
							auto protocol = m.captured(1).toLower();
 | 
				
			||||||
		QString topDomain = m.captured(3).toLower();
 | 
							auto topDomain = m.captured(3).toLower();
 | 
				
			||||||
 | 
							auto isProtocolValid = protocol.isEmpty() || TextUtilities::IsValidProtocol(protocol);
 | 
				
			||||||
		bool isProtocolValid = protocol.isEmpty() || validProtocols().contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
 | 
							auto isTopDomainValid = !protocol.isEmpty() || TextUtilities::IsValidTopDomain(topDomain);
 | 
				
			||||||
		bool isTopDomainValid = !protocol.isEmpty() || validTopDomains().contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar)));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
 | 
							if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
 | 
				
			||||||
			QString forMailName = text.mid(offset, domainOffset - offset - 1);
 | 
								auto forMailName = text.mid(offset, domainOffset - offset - 1);
 | 
				
			||||||
			QRegularExpressionMatch mMailName = reMailName().match(forMailName);
 | 
								auto mMailName = TextUtilities::RegExpMailNameAtEnd().match(forMailName);
 | 
				
			||||||
			if (mMailName.hasMatch()) {
 | 
								if (mMailName.hasMatch()) {
 | 
				
			||||||
				offset = matchOffset = m.capturedEnd();
 | 
									offset = matchOffset = m.capturedEnd();
 | 
				
			||||||
				continue;
 | 
									continue;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -164,7 +164,7 @@ void EditorBlock::Row::fillValueString() {
 | 
				
			||||||
void EditorBlock::Row::fillSearchIndex() {
 | 
					void EditorBlock::Row::fillSearchIndex() {
 | 
				
			||||||
	_searchWords.clear();
 | 
						_searchWords.clear();
 | 
				
			||||||
	_searchStartChars.clear();
 | 
						_searchStartChars.clear();
 | 
				
			||||||
	auto toIndex = _name + ' ' + _copyOf + ' ' + textAccentFold(_description.originalText()) + ' ' + _valueString;
 | 
						auto toIndex = _name + ' ' + _copyOf + ' ' + TextUtilities::RemoveAccents(_description.originalText()) + ' ' + _valueString;
 | 
				
			||||||
	auto words = toIndex.toLower().split(SearchSplitter, QString::SkipEmptyParts);
 | 
						auto words = toIndex.toLower().split(SearchSplitter, QString::SkipEmptyParts);
 | 
				
			||||||
	for_const (auto &word, words) {
 | 
						for_const (auto &word, words) {
 | 
				
			||||||
		_searchWords.insert(word);
 | 
							_searchWords.insert(word);
 | 
				
			||||||
| 
						 | 
					@ -346,11 +346,8 @@ void EditorBlock::scrollToSelected() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void EditorBlock::searchByQuery(QString query) {
 | 
					void EditorBlock::searchByQuery(QString query) {
 | 
				
			||||||
	auto searchWords = QStringList();
 | 
						auto words = TextUtilities::PrepareSearchWords(query, &SearchSplitter);
 | 
				
			||||||
	if (!query.isEmpty()) {
 | 
						query = words.isEmpty() ? QString() : words.join(' ');
 | 
				
			||||||
		searchWords = textAccentFold(query.trimmed().toLower()).split(SearchSplitter, QString::SkipEmptyParts);
 | 
					 | 
				
			||||||
		query = searchWords.join(' ');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (_searchQuery != query) {
 | 
						if (_searchQuery != query) {
 | 
				
			||||||
		setSelected(-1);
 | 
							setSelected(-1);
 | 
				
			||||||
		setPressed(-1);
 | 
							setPressed(-1);
 | 
				
			||||||
| 
						 | 
					@ -359,7 +356,7 @@ void EditorBlock::searchByQuery(QString query) {
 | 
				
			||||||
		_searchResults.clear();
 | 
							_searchResults.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		auto toFilter = OrderedSet<int>();
 | 
							auto toFilter = OrderedSet<int>();
 | 
				
			||||||
		for_const (auto &word, searchWords) {
 | 
							for_const (auto &word, words) {
 | 
				
			||||||
			if (word.isEmpty()) continue;
 | 
								if (word.isEmpty()) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			auto testToFilter = _searchIndex.value(word[0]);
 | 
								auto testToFilter = _searchIndex.value(word[0]);
 | 
				
			||||||
| 
						 | 
					@ -371,8 +368,8 @@ void EditorBlock::searchByQuery(QString query) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (!toFilter.isEmpty()) {
 | 
							if (!toFilter.isEmpty()) {
 | 
				
			||||||
			auto allWordsFound = [&searchWords](const Row &row) {
 | 
								auto allWordsFound = [&words](const Row &row) {
 | 
				
			||||||
				for_const (auto &word, searchWords) {
 | 
									for_const (auto &word, words) {
 | 
				
			||||||
					if (!row.searchWordsContain(word)) {
 | 
										if (!row.searchWordsContain(word)) {
 | 
				
			||||||
						return false;
 | 
											return false;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue