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 addLocalChangelog = [this, &addedSome](const QString &text) {
 | 
			
		||||
		auto textWithEntities = TextWithEntities { text };
 | 
			
		||||
		textParseEntities(textWithEntities.text, TextParseLinks, &textWithEntities.entities);
 | 
			
		||||
		TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
 | 
			
		||||
		App::wnd()->serviceNotification(textWithEntities, MTP_messageMediaEmpty(), unixtime());
 | 
			
		||||
		addedSome = true;
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			@ -1177,7 +1177,7 @@ void ApiWrap::saveDraftsToCloud() {
 | 
			
		|||
		if (!textWithTags.tags.isEmpty()) {
 | 
			
		||||
			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) {
 | 
			
		||||
			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
 | 
			
		||||
				// local values for first name and last name already, otherwise skip
 | 
			
		||||
				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 lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName;
 | 
			
		||||
				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() ? TextUtilities::SingleLine(qs(d.vlast_name)) : QString()) : data->lastName;
 | 
			
		||||
 | 
			
		||||
				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);
 | 
			
		||||
				if (phoneChanged) {
 | 
			
		||||
| 
						 | 
				
			
			@ -714,7 +714,7 @@ namespace {
 | 
			
		|||
			}
 | 
			
		||||
			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->setIsForbidden(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -1096,7 +1096,7 @@ namespace {
 | 
			
		|||
		}
 | 
			
		||||
		if (auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
 | 
			
		||||
			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->updateMedia(m.has_media() ? (&m.vmedia) : 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 showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone));
 | 
			
		||||
			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);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1497,13 +1497,13 @@ namespace {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	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 parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
			
		||||
		if (siteName == qstr("Twitter") || siteName == qstr("Instagram")) {
 | 
			
		||||
			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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1800,15 +1800,15 @@ namespace {
 | 
			
		|||
			}
 | 
			
		||||
			if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill && pendingTill >= -1)) {
 | 
			
		||||
				convert->type = toWebPageType(type);
 | 
			
		||||
				convert->url = textClean(url);
 | 
			
		||||
				convert->displayUrl = textClean(displayUrl);
 | 
			
		||||
				convert->siteName = textClean(siteName);
 | 
			
		||||
				convert->title = textOneLine(textClean(title));
 | 
			
		||||
				convert->url = TextUtilities::Clean(url);
 | 
			
		||||
				convert->displayUrl = TextUtilities::Clean(displayUrl);
 | 
			
		||||
				convert->siteName = TextUtilities::Clean(siteName);
 | 
			
		||||
				convert->title = TextUtilities::SingleLine(title);
 | 
			
		||||
				convert->description = description;
 | 
			
		||||
				convert->photo = photo;
 | 
			
		||||
				convert->document = document;
 | 
			
		||||
				convert->duration = duration;
 | 
			
		||||
				convert->author = textClean(author);
 | 
			
		||||
				convert->author = TextUtilities::Clean(author);
 | 
			
		||||
				if (convert->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(convert);
 | 
			
		||||
				convert->pendingTill = pendingTill;
 | 
			
		||||
				if (App::main()) App::main()->webPageUpdated(convert);
 | 
			
		||||
| 
						 | 
				
			
			@ -1831,15 +1831,15 @@ namespace {
 | 
			
		|||
			if (result != convert) {
 | 
			
		||||
				if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill && pendingTill >= -1)) {
 | 
			
		||||
					result->type = toWebPageType(type);
 | 
			
		||||
					result->url = textClean(url);
 | 
			
		||||
					result->displayUrl = textClean(displayUrl);
 | 
			
		||||
					result->siteName = textClean(siteName);
 | 
			
		||||
					result->title = textOneLine(textClean(title));
 | 
			
		||||
					result->url = TextUtilities::Clean(url);
 | 
			
		||||
					result->displayUrl = TextUtilities::Clean(displayUrl);
 | 
			
		||||
					result->siteName = TextUtilities::Clean(siteName);
 | 
			
		||||
					result->title = TextUtilities::SingleLine(title);
 | 
			
		||||
					result->description = description;
 | 
			
		||||
					result->photo = photo;
 | 
			
		||||
					result->document = document;
 | 
			
		||||
					result->duration = duration;
 | 
			
		||||
					result->author = textClean(author);
 | 
			
		||||
					result->author = TextUtilities::Clean(author);
 | 
			
		||||
					if (result->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(result);
 | 
			
		||||
					result->pendingTill = pendingTill;
 | 
			
		||||
					if (App::main()) App::main()->webPageUpdated(result);
 | 
			
		||||
| 
						 | 
				
			
			@ -1869,9 +1869,9 @@ namespace {
 | 
			
		|||
			}
 | 
			
		||||
			if (!convert->accessHash && accessHash) {
 | 
			
		||||
				convert->accessHash = accessHash;
 | 
			
		||||
				convert->shortName = textClean(shortName);
 | 
			
		||||
				convert->title = textOneLine(textClean(title));
 | 
			
		||||
				convert->description = textClean(description);
 | 
			
		||||
				convert->shortName = TextUtilities::Clean(shortName);
 | 
			
		||||
				convert->title = TextUtilities::SingleLine(title);
 | 
			
		||||
				convert->description = TextUtilities::Clean(description);
 | 
			
		||||
				convert->photo = photo;
 | 
			
		||||
				convert->document = document;
 | 
			
		||||
				if (App::main()) App::main()->gameUpdated(convert);
 | 
			
		||||
| 
						 | 
				
			
			@ -1891,9 +1891,9 @@ namespace {
 | 
			
		|||
			if (result != convert) {
 | 
			
		||||
				if (!result->accessHash && accessHash) {
 | 
			
		||||
					result->accessHash = accessHash;
 | 
			
		||||
					result->shortName = textClean(shortName);
 | 
			
		||||
					result->title = textOneLine(textClean(title));
 | 
			
		||||
					result->description = textClean(description);
 | 
			
		||||
					result->shortName = TextUtilities::Clean(shortName);
 | 
			
		||||
					result->title = TextUtilities::SingleLine(title);
 | 
			
		||||
					result->description = TextUtilities::Clean(description);
 | 
			
		||||
					result->photo = photo;
 | 
			
		||||
					result->document = document;
 | 
			
		||||
					if (App::main()) App::main()->gameUpdated(result);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -173,9 +173,9 @@ void AddContactBox::onSubmit() {
 | 
			
		|||
void AddContactBox::onSave() {
 | 
			
		||||
	if (_addRequest) return;
 | 
			
		||||
 | 
			
		||||
	QString firstName = prepareText(_first->getLastText());
 | 
			
		||||
	QString lastName = prepareText(_last->getLastText());
 | 
			
		||||
	QString phone = _phone->getLastText().trimmed();
 | 
			
		||||
	auto firstName = TextUtilities::PrepareForSending(_first->getLastText());
 | 
			
		||||
	auto lastName = TextUtilities::PrepareForSending(_last->getLastText());
 | 
			
		||||
	auto phone = _phone->getLastText().trimmed();
 | 
			
		||||
	if (firstName.isEmpty() && lastName.isEmpty()) {
 | 
			
		||||
		if (_invertOrder) {
 | 
			
		||||
			_last->setFocus();
 | 
			
		||||
| 
						 | 
				
			
			@ -368,8 +368,8 @@ void GroupInfoBox::onNameSubmit() {
 | 
			
		|||
void GroupInfoBox::onNext() {
 | 
			
		||||
	if (_creationRequestId) return;
 | 
			
		||||
 | 
			
		||||
	auto title = prepareText(_title->getLastText());
 | 
			
		||||
	auto description = _description ? prepareText(_description->getLastText(), true) : QString();
 | 
			
		||||
	auto title = TextUtilities::PrepareForSending(_title->getLastText());
 | 
			
		||||
	auto description = _description ? TextUtilities::PrepareForSending(_description->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString();
 | 
			
		||||
	if (title.isEmpty()) {
 | 
			
		||||
		_title->setFocus();
 | 
			
		||||
		_title->showError();
 | 
			
		||||
| 
						 | 
				
			
			@ -695,7 +695,7 @@ void SetupChannelBox::privacyChanged(Privacy value) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void SetupChannelBox::onUpdateDone(const MTPBool &result) {
 | 
			
		||||
	_channel->setName(textOneLine(_channel->name), _sentUsername);
 | 
			
		||||
	_channel->setName(TextUtilities::SingleLine(_channel->name), _sentUsername);
 | 
			
		||||
	closeBox();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -705,7 +705,7 @@ bool SetupChannelBox::onUpdateFail(const RPCError &error) {
 | 
			
		|||
	_saveRequestId = 0;
 | 
			
		||||
	QString err(error.type());
 | 
			
		||||
	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();
 | 
			
		||||
		return true;
 | 
			
		||||
	} else if (err == "USERNAME_INVALID") {
 | 
			
		||||
| 
						 | 
				
			
			@ -872,7 +872,8 @@ void EditNameTitleBox::resizeEvent(QResizeEvent *e) {
 | 
			
		|||
void EditNameTitleBox::onSave() {
 | 
			
		||||
	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 (_invertOrder) {
 | 
			
		||||
			_last->setFocus();
 | 
			
		||||
| 
						 | 
				
			
			@ -904,10 +905,11 @@ void EditNameTitleBox::onSaveSelfDone(const MTPUser &user) {
 | 
			
		|||
bool EditNameTitleBox::onSaveSelfFail(const RPCError &error) {
 | 
			
		||||
	if (MTP::isDefaultHandledError(error)) return false;
 | 
			
		||||
 | 
			
		||||
	QString err(error.type());
 | 
			
		||||
	QString first = textOneLine(_first->getLastText().trimmed()), last = textOneLine(_last->getLastText().trimmed());
 | 
			
		||||
	auto err = error.type();
 | 
			
		||||
	auto first = TextUtilities::SingleLine(_first->getLastText().trimmed());
 | 
			
		||||
	auto last = TextUtilities::SingleLine(_last->getLastText().trimmed());
 | 
			
		||||
	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();
 | 
			
		||||
		return true;
 | 
			
		||||
	} else if (err == "FIRSTNAME_INVALID") {
 | 
			
		||||
| 
						 | 
				
			
			@ -1073,7 +1075,8 @@ void EditChannelBox::paintEvent(QPaintEvent *e) {
 | 
			
		|||
void EditChannelBox::onSave() {
 | 
			
		||||
	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()) {
 | 
			
		||||
		_title->setFocus();
 | 
			
		||||
		_title->showError();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1526,22 +1526,11 @@ void ContactsBox::Inner::updateSelection() {
 | 
			
		|||
 | 
			
		||||
void ContactsBox::Inner::updateFilter(QString filter) {
 | 
			
		||||
	_lastQuery = filter.toLower().trimmed();
 | 
			
		||||
	filter = textSearchKey(filter);
 | 
			
		||||
 | 
			
		||||
	auto words = TextUtilities::PrepareSearchWords(_lastQuery);
 | 
			
		||||
	filter = words.isEmpty() ? QString() : words.join(' ');
 | 
			
		||||
 | 
			
		||||
	_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) {
 | 
			
		||||
		_filter = filter;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1560,10 +1549,10 @@ void ContactsBox::Inner::updateFilter(QString filter) {
 | 
			
		|||
		} else {
 | 
			
		||||
			if (!_addContactLnk->isHidden()) _addContactLnk->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();
 | 
			
		||||
			if (!f.isEmpty()) {
 | 
			
		||||
			if (!words.isEmpty()) {
 | 
			
		||||
				const Dialogs::List *toFilter = nullptr;
 | 
			
		||||
				if (!_contacts->isEmpty()) {
 | 
			
		||||
					for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -984,7 +984,7 @@ void PeerListBox::Inner::checkScrollForPreload() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
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(' ');
 | 
			
		||||
	if (_normalizedSearchQuery != normalizedQuery) {
 | 
			
		||||
		setSearchQuery(query, normalizedQuery);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -438,7 +438,7 @@ void SendFilesBox::onSend(bool ctrlShiftEnter) {
 | 
			
		|||
	_confirmed = true;
 | 
			
		||||
	if (_confirmedCallback) {
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
	closeBox();
 | 
			
		||||
| 
						 | 
				
			
			@ -766,7 +766,7 @@ void EditCaptionBox::onSave(bool ctrlShiftEnter) {
 | 
			
		|||
	if (!sentEntities.v.isEmpty()) {
 | 
			
		||||
		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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -674,21 +674,9 @@ bool ShareBox::Inner::hasSelected() const {
 | 
			
		|||
 | 
			
		||||
void ShareBox::Inner::updateFilter(QString filter) {
 | 
			
		||||
	_lastQuery = filter.toLower().trimmed();
 | 
			
		||||
	filter = textSearchKey(filter);
 | 
			
		||||
 | 
			
		||||
	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(' ');
 | 
			
		||||
	}
 | 
			
		||||
	auto words = TextUtilities::PrepareSearchWords(_lastQuery);
 | 
			
		||||
	filter = words.isEmpty() ? QString() : words.join(' ');
 | 
			
		||||
	if (_filter != filter) {
 | 
			
		||||
		_filter = filter;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -701,10 +689,10 @@ void ShareBox::Inner::updateFilter(QString filter) {
 | 
			
		|||
		if (_filter.isEmpty()) {
 | 
			
		||||
			refresh();
 | 
			
		||||
		} else {
 | 
			
		||||
			QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
 | 
			
		||||
			QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
 | 
			
		||||
 | 
			
		||||
			_filtered.clear();
 | 
			
		||||
			if (!f.isEmpty()) {
 | 
			
		||||
			if (!words.isEmpty()) {
 | 
			
		||||
				const Dialogs::List *toFilter = nullptr;
 | 
			
		||||
				if (!_chatsIndexed->isEmpty()) {
 | 
			
		||||
					for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -428,18 +428,17 @@ bool StickerSetBox::Inner::official() const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
base::lambda<TextWithEntities()> StickerSetBox::Inner::title() const {
 | 
			
		||||
	auto text = _setTitle;
 | 
			
		||||
	auto entities = EntitiesInText();
 | 
			
		||||
	auto text = TextWithEntities { _setTitle };
 | 
			
		||||
	if (_loaded) {
 | 
			
		||||
		if (_pack.isEmpty()) {
 | 
			
		||||
			return [] { return TextWithEntities { lang(lng_attach_failed), EntitiesInText() }; };
 | 
			
		||||
		} else {
 | 
			
		||||
			textParseEntities(text, TextParseMentions, &entities);
 | 
			
		||||
			TextUtilities::ParseEntities(text, TextParseMentions);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return [] { return TextWithEntities { lang(lng_contacts_loading), EntitiesInText() }; };
 | 
			
		||||
	}
 | 
			
		||||
	return [text, entities] { return TextWithEntities { text, entities }; };
 | 
			
		||||
	return [text] { return text; };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString StickerSetBox::Inner::shortName() const {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -175,7 +175,7 @@ bool UsernameBox::onUpdateFail(const RPCError &error) {
 | 
			
		|||
	_saveRequestId = 0;
 | 
			
		||||
	QString err(error.type());
 | 
			
		||||
	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();
 | 
			
		||||
		return true;
 | 
			
		||||
	} 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);
 | 
			
		||||
	if (resetScroll) {
 | 
			
		||||
		_type = type;
 | 
			
		||||
		_filter = textAccentFold(plainQuery.toString());
 | 
			
		||||
		_filter = TextUtilities::RemoveAccents(plainQuery.toString());
 | 
			
		||||
	}
 | 
			
		||||
	_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
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
inline const QRegularExpression &cWordSplit() {
 | 
			
		||||
	static QRegularExpression regexp(qsl("[\\@\\s\\-\\+\\(\\)\\[\\]\\{\\}\\<\\>\\,\\.\\:\\!\\_\\;\\\"\\'\\x0]"));
 | 
			
		||||
	return regexp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline const QRegularExpression &cRussianLetters() {
 | 
			
		||||
	static QRegularExpression regexp(QString::fromUtf8("[а-яА-ЯёЁ]"));
 | 
			
		||||
	return regexp;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,10 +40,9 @@ Draft::Draft(const Ui::FlatTextarea *field, MsgId msgId, bool previewCancelled,
 | 
			
		|||
 | 
			
		||||
void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) {
 | 
			
		||||
	auto history = App::history(peerId);
 | 
			
		||||
	auto text = qs(draft.vmessage);
 | 
			
		||||
	auto entities = draft.has_entities() ? entitiesFromMTP(draft.ventities.v) : EntitiesInText();
 | 
			
		||||
	TextWithTags textWithTags = { textApplyEntities(text, entities), ConvertEntitiesToTextTags(entities) };
 | 
			
		||||
	MsgId replyTo = draft.has_reply_to_msg_id() ? draft.vreply_to_msg_id.v : 0;
 | 
			
		||||
	auto text = TextWithEntities { qs(draft.vmessage), draft.has_entities() ? TextUtilities::EntitiesFromMTP(draft.ventities.v) : EntitiesInText() };
 | 
			
		||||
	auto textWithTags = TextWithTags { TextUtilities::ApplyEntities(text), ConvertEntitiesToTextTags(text.entities) };
 | 
			
		||||
	auto replyTo = draft.has_reply_to_msg_id() ? draft.vreply_to_msg_id.v : MsgId(0);
 | 
			
		||||
	auto cloudDraft = std::make_unique<Draft>(textWithTags, replyTo, MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), draft.is_no_webpage());
 | 
			
		||||
	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->cloudDraftTextCache.isEmpty()) {
 | 
			
		||||
				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);
 | 
			
		||||
			}
 | 
			
		||||
			p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1263,104 +1263,90 @@ void DialogsInner::onPeerPhotoChanged(PeerData *peer) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
 | 
			
		||||
	newFilter = textSearchKey(newFilter);
 | 
			
		||||
	auto words = TextUtilities::PrepareSearchWords(newFilter);
 | 
			
		||||
	newFilter = words.isEmpty() ? QString() : words.join(' ');
 | 
			
		||||
	if (newFilter != _filter || force) {
 | 
			
		||||
		QStringList f;
 | 
			
		||||
		if (!newFilter.isEmpty()) {
 | 
			
		||||
			QStringList filterList = newFilter.split(cWordSplit(), QString::SkipEmptyParts);
 | 
			
		||||
			int l = filterList.size();
 | 
			
		||||
		_filter = newFilter;
 | 
			
		||||
		if (!_searchInPeer && _filter.isEmpty()) {
 | 
			
		||||
			_state = DefaultState;
 | 
			
		||||
			_hashtagResults.clear();
 | 
			
		||||
			_filterResults.clear();
 | 
			
		||||
			_peerSearchResults.clear();
 | 
			
		||||
			_searchResults.clear();
 | 
			
		||||
			_lastSearchDate = 0;
 | 
			
		||||
			_lastSearchPeer = 0;
 | 
			
		||||
			_lastSearchId = _lastSearchMigratedId = 0;
 | 
			
		||||
		} else {
 | 
			
		||||
			QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
 | 
			
		||||
 | 
			
		||||
			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) {
 | 
			
		||||
			_filter = newFilter;
 | 
			
		||||
			if (!_searchInPeer && _filter.isEmpty()) {
 | 
			
		||||
				_state = DefaultState;
 | 
			
		||||
				_hashtagResults.clear();
 | 
			
		||||
				_filterResults.clear();
 | 
			
		||||
				_peerSearchResults.clear();
 | 
			
		||||
				_searchResults.clear();
 | 
			
		||||
				_lastSearchDate = 0;
 | 
			
		||||
				_lastSearchPeer = 0;
 | 
			
		||||
				_lastSearchId = _lastSearchMigratedId = 0;
 | 
			
		||||
			} else {
 | 
			
		||||
				QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi;
 | 
			
		||||
 | 
			
		||||
				_state = FilteredState;
 | 
			
		||||
				_filterResults.clear();
 | 
			
		||||
				if (!_searchInPeer && !f.isEmpty()) {
 | 
			
		||||
					const Dialogs::List *toFilter = nullptr;
 | 
			
		||||
					if (!_dialogs->isEmpty()) {
 | 
			
		||||
						for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
							auto found = _dialogs->filtered(fi->at(0));
 | 
			
		||||
							if (found->isEmpty()) {
 | 
			
		||||
								toFilter = nullptr;
 | 
			
		||||
								break;
 | 
			
		||||
							}
 | 
			
		||||
							if (!toFilter || toFilter->size() > found->size()) {
 | 
			
		||||
								toFilter = found;
 | 
			
		||||
							}
 | 
			
		||||
			_state = FilteredState;
 | 
			
		||||
			_filterResults.clear();
 | 
			
		||||
			if (!_searchInPeer && !words.isEmpty()) {
 | 
			
		||||
				const Dialogs::List *toFilter = nullptr;
 | 
			
		||||
				if (!_dialogs->isEmpty()) {
 | 
			
		||||
					for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
						auto found = _dialogs->filtered(fi->at(0));
 | 
			
		||||
						if (found->isEmpty()) {
 | 
			
		||||
							toFilter = nullptr;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
						if (!toFilter || toFilter->size() > found->size()) {
 | 
			
		||||
							toFilter = found;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					const Dialogs::List *toFilterContacts = nullptr;
 | 
			
		||||
					if (!_contactsNoDialogs->isEmpty()) {
 | 
			
		||||
						for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
							auto found = _contactsNoDialogs->filtered(fi->at(0));
 | 
			
		||||
							if (found->isEmpty()) {
 | 
			
		||||
								toFilterContacts = nullptr;
 | 
			
		||||
								break;
 | 
			
		||||
							}
 | 
			
		||||
							if (!toFilterContacts || toFilterContacts->size() > found->size()) {
 | 
			
		||||
								toFilterContacts = found;
 | 
			
		||||
							}
 | 
			
		||||
				}
 | 
			
		||||
				const Dialogs::List *toFilterContacts = nullptr;
 | 
			
		||||
				if (!_contactsNoDialogs->isEmpty()) {
 | 
			
		||||
					for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
						auto found = _contactsNoDialogs->filtered(fi->at(0));
 | 
			
		||||
						if (found->isEmpty()) {
 | 
			
		||||
							toFilterContacts = nullptr;
 | 
			
		||||
							break;
 | 
			
		||||
						}
 | 
			
		||||
						if (!toFilterContacts || toFilterContacts->size() > found->size()) {
 | 
			
		||||
							toFilterContacts = found;
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					_filterResults.reserve((toFilter ? toFilter->size() : 0) + (toFilterContacts ? toFilterContacts->size() : 0));
 | 
			
		||||
					if (toFilter) {
 | 
			
		||||
						for_const (auto row, *toFilter) {
 | 
			
		||||
							const PeerData::Names &names(row->history()->peer->names);
 | 
			
		||||
							PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni;
 | 
			
		||||
							for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
								QString filterName(*fi);
 | 
			
		||||
								for (ni = nb; ni != ne; ++ni) {
 | 
			
		||||
									if (ni->startsWith(*fi)) {
 | 
			
		||||
										break;
 | 
			
		||||
									}
 | 
			
		||||
								}
 | 
			
		||||
								if (ni == ne) {
 | 
			
		||||
				}
 | 
			
		||||
				_filterResults.reserve((toFilter ? toFilter->size() : 0) + (toFilterContacts ? toFilterContacts->size() : 0));
 | 
			
		||||
				if (toFilter) {
 | 
			
		||||
					for_const (auto row, *toFilter) {
 | 
			
		||||
						const PeerData::Names &names(row->history()->peer->names);
 | 
			
		||||
						PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni;
 | 
			
		||||
						for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
							QString filterName(*fi);
 | 
			
		||||
							for (ni = nb; ni != ne; ++ni) {
 | 
			
		||||
								if (ni->startsWith(*fi)) {
 | 
			
		||||
									break;
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							if (fi == fe) {
 | 
			
		||||
								_filterResults.push_back(row);
 | 
			
		||||
							if (ni == ne) {
 | 
			
		||||
								break;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						if (fi == fe) {
 | 
			
		||||
							_filterResults.push_back(row);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					if (toFilterContacts) {
 | 
			
		||||
						for_const (auto row, *toFilterContacts) {
 | 
			
		||||
							const PeerData::Names &names(row->history()->peer->names);
 | 
			
		||||
							PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni;
 | 
			
		||||
							for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
								QString filterName(*fi);
 | 
			
		||||
								for (ni = nb; ni != ne; ++ni) {
 | 
			
		||||
									if (ni->startsWith(*fi)) {
 | 
			
		||||
										break;
 | 
			
		||||
									}
 | 
			
		||||
								}
 | 
			
		||||
								if (ni == ne) {
 | 
			
		||||
				}
 | 
			
		||||
				if (toFilterContacts) {
 | 
			
		||||
					for_const (auto row, *toFilterContacts) {
 | 
			
		||||
						const PeerData::Names &names(row->history()->peer->names);
 | 
			
		||||
						PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni;
 | 
			
		||||
						for (fi = fb; fi != fe; ++fi) {
 | 
			
		||||
							QString filterName(*fi);
 | 
			
		||||
							for (ni = nb; ni != ne; ++ni) {
 | 
			
		||||
								if (ni->startsWith(*fi)) {
 | 
			
		||||
									break;
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							if (fi == fe) {
 | 
			
		||||
								_filterResults.push_back(row);
 | 
			
		||||
							if (ni == ne) {
 | 
			
		||||
								break;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						if (fi == fe) {
 | 
			
		||||
							_filterResults.push_back(row);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -2038,10 +2024,10 @@ bool DialogsInner::choosePeer() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void DialogsInner::saveRecentHashtags(const QString &text) {
 | 
			
		||||
	bool found = false;
 | 
			
		||||
	auto found = false;
 | 
			
		||||
	QRegularExpressionMatch m;
 | 
			
		||||
	RecentHashtagPack recent(cRecentSearchHashtags());
 | 
			
		||||
	for (int32 i = 0, next = 0; (m = reHashtag().match(text, i)).hasMatch(); i = next) {
 | 
			
		||||
	auto recent = cRecentSearchHashtags();
 | 
			
		||||
	for (int32 i = 0, next = 0; (m = TextUtilities::RegExpHashtag().match(text, i)).hasMatch(); i = next) {
 | 
			
		||||
		i = m.capturedStart();
 | 
			
		||||
		next = m.capturedEnd();
 | 
			
		||||
		if (m.hasMatch()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,11 +56,10 @@ constexpr auto kNewBlockEachMessage = 50;
 | 
			
		|||
auto GlobalPinnedIndex = 0;
 | 
			
		||||
 | 
			
		||||
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")));
 | 
			
		||||
	EntitiesInText entities;
 | 
			
		||||
	textParseEntities(text, _historyTextNoMonoOptions.flags, &entities);
 | 
			
		||||
	entities.push_front(EntityInText(EntityInTextItalic, 0, text.size()));
 | 
			
		||||
	return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, { text, entities });
 | 
			
		||||
	auto text = TextWithEntities { lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")) };
 | 
			
		||||
	TextUtilities::ParseEntities(text, _historyTextNoMonoOptions.flags);
 | 
			
		||||
	text.entities.push_front(EntityInText(EntityInTextItalic, 0, text.text.size()));
 | 
			
		||||
	return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // 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) };
 | 
			
		||||
	text.entities.append(EntityInText(EntityInTextBold, 0, text.text.size()));
 | 
			
		||||
	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);
 | 
			
		||||
	text.text.append(qstr("\n\n") + description);
 | 
			
		||||
	_emptyText.setMarkedText(st::defaultTextStyle, text, options);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,14 +30,14 @@ namespace AdminLog {
 | 
			
		|||
namespace {
 | 
			
		||||
 | 
			
		||||
TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
 | 
			
		||||
	auto result = TextWithEntities { textClean(value) };
 | 
			
		||||
	auto result = TextWithEntities { TextUtilities::Clean(value) };
 | 
			
		||||
	if (result.text.isEmpty()) {
 | 
			
		||||
		result.text = emptyValue;
 | 
			
		||||
		if (!emptyValue.isEmpty()) {
 | 
			
		||||
			result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size()));
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		textParseEntities(result.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &result.entities);
 | 
			
		||||
		TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -79,8 +79,8 @@ TextWithEntities ExtractEditedText(const MTPMessage &message) {
 | 
			
		|||
	} else if (mediaType == mtpc_messageMediaPhoto) {
 | 
			
		||||
		return PrepareText(qs(data.vmedia.c_messageMediaPhoto().vcaption), QString());
 | 
			
		||||
	}
 | 
			
		||||
	auto text = textClean(qs(data.vmessage));
 | 
			
		||||
	auto entities = data.has_entities() ? entitiesFromMTP(data.ventities.v) : EntitiesInText();
 | 
			
		||||
	auto text = TextUtilities::Clean(qs(data.vmessage));
 | 
			
		||||
	auto entities = data.has_entities() ? TextUtilities::EntitiesFromMTP(data.ventities.v) : EntitiesInText();
 | 
			
		||||
	return { text, entities };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1522,7 +1522,7 @@ TextWithEntities HistoryInner::getSelectedText() const {
 | 
			
		|||
		int y = itemTop(item);
 | 
			
		||||
		if (y >= 0) {
 | 
			
		||||
			part.text.append(item->author()->name).append(time);
 | 
			
		||||
			appendTextWithEntities(part, std::move(unwrapped));
 | 
			
		||||
			TextUtilities::Append(part, std::move(unwrapped));
 | 
			
		||||
			texts.insert(y, part);
 | 
			
		||||
			fullSize += size;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1532,7 +1532,7 @@ TextWithEntities HistoryInner::getSelectedText() const {
 | 
			
		|||
	auto sep = qsl("\n\n");
 | 
			
		||||
	result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
 | 
			
		||||
	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) {
 | 
			
		||||
			result.text.append(sep);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -115,7 +115,7 @@ ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s)
 | 
			
		|||
				auto str = row.at(j).text;
 | 
			
		||||
				button.type = row.at(j).type;
 | 
			
		||||
				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();
 | 
			
		||||
			}
 | 
			
		||||
			_rows.push_back(newRow);
 | 
			
		||||
| 
						 | 
				
			
			@ -1141,12 +1141,12 @@ QString HistoryItem::inDialogsText() const {
 | 
			
		|||
		if (emptyText()) {
 | 
			
		||||
			return _media ? _media->inDialogsText() : QString();
 | 
			
		||||
		}
 | 
			
		||||
		return textClean(_text.originalText());
 | 
			
		||||
		return TextUtilities::Clean(_text.originalText());
 | 
			
		||||
	};
 | 
			
		||||
	auto plainText = getText();
 | 
			
		||||
	if ((!_history->peer->isUser() || out()) && !isPost() && !isEmpty()) {
 | 
			
		||||
		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 plainText;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ public:
 | 
			
		|||
	// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
 | 
			
		||||
	virtual QString inDialogsText() const {
 | 
			
		||||
		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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -130,7 +130,7 @@ TextWithEntities captionedSelectedText(const QString &attachType, const Text &ca
 | 
			
		|||
	result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
 | 
			
		||||
	if (!caption.isEmpty()) {
 | 
			
		||||
		result.text.append(qstr("\n"));
 | 
			
		||||
		appendTextWithEntities(result, std::move(original));
 | 
			
		||||
		TextUtilities::Append(result, std::move(original));
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -147,11 +147,11 @@ QString captionedNotificationText(const QString &attachType, const Text &caption
 | 
			
		|||
 | 
			
		||||
QString captionedInDialogsText(const QString &attachType, const Text &caption) {
 | 
			
		||||
	if (caption.isEmpty()) {
 | 
			
		||||
		return textcmdLink(1, textClean(attachType));
 | 
			
		||||
		return textcmdLink(1, TextUtilities::Clean(attachType));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto captionText = textClean(caption.originalText());
 | 
			
		||||
	auto attachTypeWrapped = textcmdLink(1, lng_dialogs_text_media_wrapped(lt_media, textClean(attachType)));
 | 
			
		||||
	auto captionText = TextUtilities::Clean(caption.originalText());
 | 
			
		||||
	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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3164,7 +3164,7 @@ void HistoryWebPage::initDimensions() {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// 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()) {
 | 
			
		||||
		_data->siteName = siteNameFromUrl(_data->url);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -3642,7 +3642,7 @@ TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	titleResult.text += '\n';
 | 
			
		||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
			
		||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
			
		||||
	return titleResult;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3700,7 +3700,7 @@ void HistoryGame::initDimensions() {
 | 
			
		|||
		_openl = MakeShared<ReplyMarkupClickHandler>(_parent, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto title = textOneLine(_data->title);
 | 
			
		||||
	auto title = TextUtilities::SingleLine(_data->title);
 | 
			
		||||
 | 
			
		||||
	// init attach
 | 
			
		||||
	if (!_attach) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3728,7 +3728,7 @@ void HistoryGame::initDimensions() {
 | 
			
		|||
			}
 | 
			
		||||
			auto marked = TextWithEntities { text };
 | 
			
		||||
			auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
			
		||||
			textParseEntities(marked.text, parseFlags, &marked.entities);
 | 
			
		||||
			TextUtilities::ParseEntities(marked, parseFlags);
 | 
			
		||||
			_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -4042,7 +4042,7 @@ TextWithEntities HistoryGame::selectedText(TextSelection selection) const {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	titleResult.text += '\n';
 | 
			
		||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
			
		||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
			
		||||
	return titleResult;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4156,10 +4156,10 @@ void HistoryInvoice::fillFromData(const MTPDmessageMediaInvoice &data) {
 | 
			
		|||
	if (!description.isEmpty()) {
 | 
			
		||||
		auto marked = TextWithEntities { description };
 | 
			
		||||
		auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
			
		||||
		textParseEntities(marked.text, parseFlags, &marked.entities);
 | 
			
		||||
		TextUtilities::ParseEntities(marked, parseFlags);
 | 
			
		||||
		_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
 | 
			
		||||
	}
 | 
			
		||||
	auto title = textOneLine(qs(data.vtitle));
 | 
			
		||||
	auto title = TextUtilities::SingleLine(qs(data.vtitle));
 | 
			
		||||
	if (!title.isEmpty()) {
 | 
			
		||||
		_title.setText(st::webPageTitleStyle, title, _webpageTitleOptions);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -4435,7 +4435,7 @@ TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	titleResult.text += '\n';
 | 
			
		||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
			
		||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
			
		||||
	return titleResult;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4460,12 +4460,12 @@ HistoryLocation::HistoryLocation(gsl::not_null<HistoryItem*> parent, const Locat
 | 
			
		|||
, _description(st::msgMinWidth)
 | 
			
		||||
, _link(MakeShared<LocationClickHandler>(coords)) {
 | 
			
		||||
	if (!title.isEmpty()) {
 | 
			
		||||
		_title.setText(st::webPageTitleStyle, textClean(title), _webpageTitleOptions);
 | 
			
		||||
		_title.setText(st::webPageTitleStyle, TextUtilities::Clean(title), _webpageTitleOptions);
 | 
			
		||||
	}
 | 
			
		||||
	if (!description.isEmpty()) {
 | 
			
		||||
		auto marked = TextWithEntities { textClean(description) };
 | 
			
		||||
		auto marked = TextWithEntities { TextUtilities::Clean(description) };
 | 
			
		||||
		auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
 | 
			
		||||
		textParseEntities(marked.text, parseFlags, &marked.entities);
 | 
			
		||||
		TextUtilities::ParseEntities(marked, parseFlags);
 | 
			
		||||
		_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() };
 | 
			
		||||
		auto info = selectedText(AllTextSelection);
 | 
			
		||||
		if (!info.text.isEmpty()) {
 | 
			
		||||
			appendTextWithEntities(result, std::move(info));
 | 
			
		||||
			TextUtilities::Append(result, std::move(info));
 | 
			
		||||
			result.text.append('\n');
 | 
			
		||||
		}
 | 
			
		||||
		result.text += _link->dragText();
 | 
			
		||||
| 
						 | 
				
			
			@ -4715,7 +4715,7 @@ TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
 | 
			
		|||
		return titleResult;
 | 
			
		||||
	}
 | 
			
		||||
	titleResult.text += '\n';
 | 
			
		||||
	appendTextWithEntities(titleResult, std::move(descriptionResult));
 | 
			
		||||
	TextUtilities::Append(titleResult, std::move(descriptionResult));
 | 
			
		||||
	return titleResult;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -221,7 +221,7 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if (replyToMsg) {
 | 
			
		||||
		replyToText.setText(st::messageTextStyle, textClean(replyToMsg->inReplyText()), _textDlgOptions);
 | 
			
		||||
		replyToText.setText(st::messageTextStyle, TextUtilities::Clean(replyToMsg->inReplyText()), _textDlgOptions);
 | 
			
		||||
 | 
			
		||||
		updateName();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -432,8 +432,8 @@ HistoryMessage::HistoryMessage(gsl::not_null<History*> history, const MTPDmessag
 | 
			
		|||
 | 
			
		||||
	initMedia(msg.has_media() ? (&msg.vmedia) : nullptr);
 | 
			
		||||
 | 
			
		||||
	auto text = textClean(qs(msg.vmessage));
 | 
			
		||||
	auto entities = msg.has_entities() ? entitiesFromMTP(msg.ventities.v) : EntitiesInText();
 | 
			
		||||
	auto text = TextUtilities::Clean(qs(msg.vmessage));
 | 
			
		||||
	auto entities = msg.has_entities() ? TextUtilities::EntitiesFromMTP(msg.ventities.v) : EntitiesInText();
 | 
			
		||||
	setText({ text, entities });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -980,7 +980,7 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
 | 
			
		|||
 | 
			
		||||
	TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() };
 | 
			
		||||
	if (message.has_entities()) {
 | 
			
		||||
		textWithEntities.entities = entitiesFromMTP(message.ventities.v);
 | 
			
		||||
		textWithEntities.entities = TextUtilities::EntitiesFromMTP(message.ventities.v);
 | 
			
		||||
	}
 | 
			
		||||
	setText(textWithEntities);
 | 
			
		||||
	setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr);
 | 
			
		||||
| 
						 | 
				
			
			@ -1075,13 +1075,13 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
 | 
			
		|||
		result = std::move(mediaResult);
 | 
			
		||||
	} else if (!mediaResult.text.isEmpty()) {
 | 
			
		||||
		result.text += qstr("\n\n");
 | 
			
		||||
		appendTextWithEntities(result, std::move(mediaResult));
 | 
			
		||||
		TextUtilities::Append(result, std::move(mediaResult));
 | 
			
		||||
	}
 | 
			
		||||
	if (result.text.isEmpty()) {
 | 
			
		||||
		result = std::move(logEntryOriginalResult);
 | 
			
		||||
	} else if (!logEntryOriginalResult.text.isEmpty()) {
 | 
			
		||||
		result.text += qstr("\n\n");
 | 
			
		||||
		appendTextWithEntities(result, std::move(logEntryOriginalResult));
 | 
			
		||||
		TextUtilities::Append(result, std::move(logEntryOriginalResult));
 | 
			
		||||
	}
 | 
			
		||||
	if (auto forwarded = Get<HistoryMessageForwarded>()) {
 | 
			
		||||
		if (selection == FullSelection) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1090,9 +1090,9 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
 | 
			
		|||
			wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
 | 
			
		||||
			wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
 | 
			
		||||
			wrapped.text.append('[');
 | 
			
		||||
			appendTextWithEntities(wrapped, std::move(fwdinfo));
 | 
			
		||||
			TextUtilities::Append(wrapped, std::move(fwdinfo));
 | 
			
		||||
			wrapped.text.append(qsl("]\n"));
 | 
			
		||||
			appendTextWithEntities(wrapped, std::move(result));
 | 
			
		||||
			TextUtilities::Append(wrapped, std::move(result));
 | 
			
		||||
			result = wrapped;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -1101,7 +1101,7 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
 | 
			
		|||
			TextWithEntities wrapped;
 | 
			
		||||
			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"));
 | 
			
		||||
			appendTextWithEntities(wrapped, std::move(result));
 | 
			
		||||
			TextUtilities::Append(wrapped, std::move(result));
 | 
			
		||||
			result = wrapped;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,7 +89,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
 | 
			
		|||
	auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) {
 | 
			
		||||
		auto result = PreparedText {};
 | 
			
		||||
		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;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +99,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
 | 
			
		|||
			result.text = lang(lng_action_created_channel);
 | 
			
		||||
		} else {
 | 
			
		||||
			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;
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			@ -143,10 +143,10 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
 | 
			
		|||
	auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) {
 | 
			
		||||
		auto result = PreparedText {};
 | 
			
		||||
		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 {
 | 
			
		||||
			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;
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			@ -423,7 +423,7 @@ TextWithEntities HistoryService::selectedText(TextSelection selection) const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
QString HistoryService::inDialogsText() const {
 | 
			
		||||
	return textcmdLink(1, textClean(notificationText()));
 | 
			
		||||
	return textcmdLink(1, TextUtilities::Clean(notificationText()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString HistoryService::inReplyText() const {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2873,14 +2873,15 @@ void HistoryWidget::saveEditMsg() {
 | 
			
		|||
 | 
			
		||||
	auto &textWithTags = _field->getTextWithTags();
 | 
			
		||||
	auto prepareFlags = itemTextOptions(_history, App::self()).flags;
 | 
			
		||||
	EntitiesInText sendingEntities, leftEntities = ConvertTextTagsToEntities(textWithTags.tags);
 | 
			
		||||
	QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
 | 
			
		||||
	auto sending = TextWithEntities();
 | 
			
		||||
	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->setFocus();
 | 
			
		||||
		return;
 | 
			
		||||
	} else if (!leftText.isEmpty()) {
 | 
			
		||||
	} else if (!left.text.isEmpty()) {
 | 
			
		||||
		Ui::show(Box<InformBox>(lang(lng_edit_too_long)));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -2889,12 +2890,12 @@ void HistoryWidget::saveEditMsg() {
 | 
			
		|||
	if (webPageId == CancelledWebPageId) {
 | 
			
		||||
		sendFlags |= MTPmessages_EditMessage::Flag::f_no_webpage;
 | 
			
		||||
	}
 | 
			
		||||
	auto localEntities = linksToMTP(sendingEntities);
 | 
			
		||||
	auto sentEntities = linksToMTP(sendingEntities, true);
 | 
			
		||||
	auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
 | 
			
		||||
	auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal);
 | 
			
		||||
	if (!sentEntities.v.isEmpty()) {
 | 
			
		||||
		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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3841,7 +3842,7 @@ void HistoryWidget::onKbToggle(bool manual) {
 | 
			
		|||
		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
			
		||||
		if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
 | 
			
		||||
			updateReplyToName();
 | 
			
		||||
			_replyEditMsgText.setText(st::messageTextStyle, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
			
		||||
			_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
			
		||||
			_fieldBarCancel->show();
 | 
			
		||||
			updateMouseTracking();
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -3860,7 +3861,7 @@ void HistoryWidget::onKbToggle(bool manual) {
 | 
			
		|||
		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
			
		||||
		if (_kbReplyTo && !_editMsgId && !_replyToId) {
 | 
			
		||||
			updateReplyToName();
 | 
			
		||||
			_replyEditMsgText.setText(st::messageTextStyle, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
			
		||||
			_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
			
		||||
			_fieldBarCancel->show();
 | 
			
		||||
			updateMouseTracking();
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -5181,7 +5182,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
 | 
			
		|||
			_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
 | 
			
		||||
			if (_kbReplyTo && !_replyToId) {
 | 
			
		||||
				updateReplyToName();
 | 
			
		||||
				_replyEditMsgText.setText(st::messageTextStyle, textClean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
			
		||||
				_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
 | 
			
		||||
				_fieldBarCancel->show();
 | 
			
		||||
				updateMouseTracking();
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -5440,7 +5441,7 @@ void HistoryWidget::updatePinnedBar(bool force) {
 | 
			
		|||
		_pinnedBar->msg = App::histItemById(_history->channelId(), _pinnedBar->msgId);
 | 
			
		||||
	}
 | 
			
		||||
	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();
 | 
			
		||||
	} else if (force) {
 | 
			
		||||
		if (_peer && _peer->isMegagroup()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -5667,7 +5668,7 @@ void HistoryWidget::onReplyToMessage() {
 | 
			
		|||
	} else {
 | 
			
		||||
		_replyEditMsg = to;
 | 
			
		||||
		_replyToId = to->id;
 | 
			
		||||
		_replyEditMsgText.setText(st::messageTextStyle, textClean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
			
		||||
		_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
			
		||||
 | 
			
		||||
		updateBotKeyboard();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5709,10 +5710,8 @@ void HistoryWidget::onEditMessage() {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	auto original = to->originalText();
 | 
			
		||||
	auto editText = textApplyEntities(original.text, original.entities);
 | 
			
		||||
	auto editTags = ConvertEntitiesToTextTags(original.entities);
 | 
			
		||||
	TextWithTags editData = { editText, editTags };
 | 
			
		||||
	MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
 | 
			
		||||
	auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) };
 | 
			
		||||
	auto cursor = MessageCursor { editData.text.size(), editData.text.size(), QFIXED_MAX };
 | 
			
		||||
	_history->setEditDraft(std::make_unique<Data::Draft>(editData, to->id, cursor, false));
 | 
			
		||||
	applyDraft(false);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6013,7 +6012,7 @@ void HistoryWidget::updatePreview() {
 | 
			
		|||
#else // OS_MAC_OLD
 | 
			
		||||
			auto linkText = _previewLinks.split(' ').at(0);
 | 
			
		||||
#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;
 | 
			
		||||
			if (t <= 0) t = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -6045,7 +6044,7 @@ void HistoryWidget::updatePreview() {
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
			_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) {
 | 
			
		||||
		_fieldBarCancel->hide();
 | 
			
		||||
| 
						 | 
				
			
			@ -6060,9 +6059,7 @@ void HistoryWidget::onCancel() {
 | 
			
		|||
		onInlineBotCancel();
 | 
			
		||||
	} else if (_editMsgId) {
 | 
			
		||||
		auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
 | 
			
		||||
		auto editText = textApplyEntities(original.text, original.entities);
 | 
			
		||||
		auto editTags = ConvertEntitiesToTextTags(original.entities);
 | 
			
		||||
		TextWithTags editData = { editText, editTags };
 | 
			
		||||
		auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) };
 | 
			
		||||
		if (_replyEditMsg && editData != _field->getTextWithTags()) {
 | 
			
		||||
			Ui::show(Box<ConfirmBox>(
 | 
			
		||||
				lang(lng_cancel_edit_post_sure),
 | 
			
		||||
| 
						 | 
				
			
			@ -6329,7 +6326,7 @@ void HistoryWidget::updateReplyEditTexts(bool force) {
 | 
			
		|||
		_replyEditMsg = App::histItemById(_channel, _editMsgId ? _editMsgId : _replyToId);
 | 
			
		||||
	}
 | 
			
		||||
	if (_replyEditMsg) {
 | 
			
		||||
		_replyEditMsgText.setText(st::messageTextStyle, textClean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
			
		||||
		_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);
 | 
			
		||||
 | 
			
		||||
		updateBotKeyboard();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6390,7 +6387,7 @@ void HistoryWidget::updateForwardingTexts() {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
	_toForwardFrom.setText(st::msgNameStyle, from, _textNameOptions);
 | 
			
		||||
	_toForwardText.setText(st::messageTextStyle, textClean(text), _textDlgOptions);
 | 
			
		||||
	_toForwardText.setText(st::messageTextStyle, TextUtilities::Clean(text), _textDlgOptions);
 | 
			
		||||
	_toForwardNameVersion = version;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -570,7 +570,7 @@ void Video::initDimensions() {
 | 
			
		|||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
			
		||||
	int32 textWidth = _maxw - (withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0);
 | 
			
		||||
	TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
 | 
			
		||||
	QString title = textOneLine(_result->getLayoutTitle());
 | 
			
		||||
	auto title = TextUtilities::SingleLine(_result->getLayoutTitle());
 | 
			
		||||
	if (title.isEmpty()) {
 | 
			
		||||
		title = lang(lng_media_video);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -685,7 +685,7 @@ void File::initDimensions() {
 | 
			
		|||
	int textWidth = _maxw - (st::msgFileSize + st::inlineThumbSkip);
 | 
			
		||||
 | 
			
		||||
	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 };
 | 
			
		||||
	_description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
 | 
			
		||||
| 
						 | 
				
			
			@ -890,7 +890,7 @@ void Contact::initDimensions() {
 | 
			
		|||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
			
		||||
	int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
	TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto };
 | 
			
		||||
| 
						 | 
				
			
			@ -987,7 +987,7 @@ void Article::initDimensions() {
 | 
			
		|||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
			
		||||
	int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0);
 | 
			
		||||
	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 descriptionLines = (_withThumb || _url) ? 2 : 3;
 | 
			
		||||
| 
						 | 
				
			
			@ -1155,7 +1155,7 @@ void Game::initDimensions() {
 | 
			
		|||
	_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
 | 
			
		||||
	int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
 | 
			
		||||
	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 descriptionLines = 2;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -136,7 +136,7 @@ std::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
 | 
			
		|||
 | 
			
		||||
	case mtpc_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());
 | 
			
		||||
		if (result->_type == Type::Photo) {
 | 
			
		||||
			result->createPhoto();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ QString SendDataCommon::getErrorOnSend(const Result *owner, History *history) co
 | 
			
		|||
SendDataCommon::SentMTPMessageFields SendText::getSentMessageFields() const {
 | 
			
		||||
	SentMTPMessageFields result;
 | 
			
		||||
	result.text = MTP_string(_message);
 | 
			
		||||
	result.entities = linksToMTP(_entities);
 | 
			
		||||
	result.entities = TextUtilities::EntitiesToMTP(_entities);
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1479,23 +1479,24 @@ void MainWidget::sendMessage(const MessageToSend &message) {
 | 
			
		|||
	}
 | 
			
		||||
	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;
 | 
			
		||||
	QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
 | 
			
		||||
	TextUtilities::PrepareForSending(left, prepareFlags);
 | 
			
		||||
 | 
			
		||||
	HistoryItem *lastMessage = nullptr;
 | 
			
		||||
 | 
			
		||||
	MsgId replyTo = (message.replyTo < 0) ? _history->replyToId() : message.replyTo;
 | 
			
		||||
	while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | 
			
		||||
		FullMsgId newId(peerToChannel(history->peer->id), clientMsgId());
 | 
			
		||||
		uint64 randomId = rand_value<uint64>();
 | 
			
		||||
	auto replyTo = (message.replyTo < 0) ? _history->replyToId() : message.replyTo;
 | 
			
		||||
	while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
 | 
			
		||||
		auto newId = FullMsgId(peerToChannel(history->peer->id), clientMsgId());
 | 
			
		||||
		auto randomId = rand_value<uint64>();
 | 
			
		||||
 | 
			
		||||
		trimTextWithEntities(sendingText, &sendingEntities);
 | 
			
		||||
		TextUtilities::Trim(sending);
 | 
			
		||||
 | 
			
		||||
		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 sendFlags = MTPmessages_SendMessage::Flags(0);
 | 
			
		||||
		if (replyTo) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1523,8 +1524,8 @@ void MainWidget::sendMessage(const MessageToSend &message) {
 | 
			
		|||
		if (silentPost) {
 | 
			
		||||
			sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
 | 
			
		||||
		}
 | 
			
		||||
		auto localEntities = linksToMTP(sendingEntities);
 | 
			
		||||
		auto sentEntities = linksToMTP(sendingEntities, true);
 | 
			
		||||
		auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
 | 
			
		||||
		auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal);
 | 
			
		||||
		if (!sentEntities.v.isEmpty()) {
 | 
			
		||||
			sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1546,7 +1547,7 @@ void MainWidget::saveRecentHashtags(const QString &text) {
 | 
			
		|||
	bool found = false;
 | 
			
		||||
	QRegularExpressionMatch m;
 | 
			
		||||
	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();
 | 
			
		||||
		next = m.capturedEnd();
 | 
			
		||||
		if (m.hasMatch()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2093,12 +2094,11 @@ void MainWidget::dialogsCancelled() {
 | 
			
		|||
 | 
			
		||||
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;
 | 
			
		||||
	QString sendingText, leftText = message.text;
 | 
			
		||||
	EntitiesInText sendingEntities, leftEntities = message.entities;
 | 
			
		||||
	auto sending = TextWithEntities(), left = message;
 | 
			
		||||
	HistoryItem *item = nullptr;
 | 
			
		||||
	while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
 | 
			
		||||
		MTPVector<MTPMessageEntity> localEntities = linksToMTP(sendingEntities);
 | 
			
		||||
		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);
 | 
			
		||||
	while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
 | 
			
		||||
		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(sending.text), media, MTPnullMarkup, localEntities, MTPint(), MTPint()), NewMessageUnread);
 | 
			
		||||
	}
 | 
			
		||||
	if (item) {
 | 
			
		||||
		_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)) {
 | 
			
		||||
						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->updateMedia(d.has_media() ? (&d.vmedia) : nullptr);
 | 
			
		||||
					item->addToOverview(AddToOverviewNew);
 | 
			
		||||
| 
						 | 
				
			
			@ -5129,9 +5129,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 | 
			
		|||
		auto &d = update.c_updateUserName();
 | 
			
		||||
		if (auto user = App::userLoaded(d.vuser_id.v)) {
 | 
			
		||||
			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 {
 | 
			
		||||
				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);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -5234,7 +5234,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 | 
			
		|||
		if (d.is_popup()) {
 | 
			
		||||
			Ui::show(Box<InformBox>(qs(d.vmessage)));
 | 
			
		||||
		} 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();
 | 
			
		||||
		}
 | 
			
		||||
	} break;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -323,7 +323,7 @@ void CoverWidget::handleSongChange() {
 | 
			
		|||
	if (song->performer.isEmpty()) {
 | 
			
		||||
		textWithEntities.text = song->title.isEmpty() ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
 | 
			
		||||
	} 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.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -500,7 +500,7 @@ void Widget::handleSongChange() {
 | 
			
		|||
		if (!song || song->performer.isEmpty()) {
 | 
			
		||||
			textWithEntities.text = (!song || song->title.isEmpty()) ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
 | 
			
		||||
		} 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.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -506,7 +506,7 @@ Voice::Voice(DocumentData *voice, HistoryItem *parent, const style::OverviewFile
 | 
			
		|||
	setDocumentLinks(_data);
 | 
			
		||||
 | 
			
		||||
	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 };
 | 
			
		||||
	_details.setText(st::defaultTextStyle, lng_date_and_duration(lt_date, d, lt_duration, formatDurationText(_data->voice()->duration)), opts);
 | 
			
		||||
	_details.setLink(1, goToMessageClickHandler(parent));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -309,9 +309,9 @@ void GroupMembersWidget::refreshLimitReached() {
 | 
			
		|||
	bool limitReachedShown = (itemsCount() >= Global::ChatSizeMax()) && chat->amCreator() && !emptyTitle();
 | 
			
		||||
	if (limitReachedShown && !_limitReachedInfo) {
 | 
			
		||||
		_limitReachedInfo.create(this, st::profileLimitReachedLabel);
 | 
			
		||||
		QString title = textRichPrepare(lng_profile_migrate_reached(lt_count, Global::ChatSizeMax()));
 | 
			
		||||
		QString body = textRichPrepare(lang(lng_profile_migrate_body));
 | 
			
		||||
		QString link = textRichPrepare(lang(lng_profile_migrate_learn_more));
 | 
			
		||||
		QString title = TextUtilities::EscapeForRichParsing(lng_profile_migrate_reached(lt_count, Global::ChatSizeMax()));
 | 
			
		||||
		QString body = TextUtilities::EscapeForRichParsing(lang(lng_profile_migrate_body));
 | 
			
		||||
		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);
 | 
			
		||||
		_limitReachedInfo->setRichText(text);
 | 
			
		||||
		_limitReachedInfo->setClickHandlerHook([this](const ClickHandlerPtr &handler, Qt::MouseButton button) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -149,12 +149,12 @@ void InfoWidget::refreshAbout() {
 | 
			
		|||
	};
 | 
			
		||||
 | 
			
		||||
	_about.destroy();
 | 
			
		||||
	auto aboutText = TextWithEntities { textClean(getAboutText()) };
 | 
			
		||||
	auto aboutText = TextWithEntities { TextUtilities::Clean(getAboutText()) };
 | 
			
		||||
	if (!aboutText.text.isEmpty()) {
 | 
			
		||||
		_about.create(this, st::profileBlockTextPart);
 | 
			
		||||
		_about->show();
 | 
			
		||||
 | 
			
		||||
		textParseEntities(aboutText.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &aboutText.entities);
 | 
			
		||||
		TextUtilities::ParseEntities(aboutText, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands);
 | 
			
		||||
		_about->setMarkedText(aboutText);
 | 
			
		||||
		_about->setSelectable(true);
 | 
			
		||||
		_about->setClickHandlerHook([this](const ClickHandlerPtr &handler, Qt::MouseButton button) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1120,16 +1120,7 @@ void AddParticipantBoxSearchController::searchGlobalDone(mtpRequestId requestId,
 | 
			
		|||
void AddParticipantBoxSearchController::addChatsContacts() {
 | 
			
		||||
	_chatsContactsAdded = true;
 | 
			
		||||
 | 
			
		||||
	auto filterWordList = _query.split(cWordSplit(), QString::SkipEmptyParts);
 | 
			
		||||
	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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	auto wordList = TextUtilities::PrepareSearchWords(_query);
 | 
			
		||||
	if (wordList.empty()) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -461,22 +461,22 @@ void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer a
 | 
			
		|||
void PeerData::fillNames() {
 | 
			
		||||
	names.clear();
 | 
			
		||||
	chars.clear();
 | 
			
		||||
	QString toIndex = textAccentFold(name);
 | 
			
		||||
	auto toIndex = TextUtilities::RemoveAccents(name);
 | 
			
		||||
	if (cRussianLetters().match(toIndex).hasMatch()) {
 | 
			
		||||
		toIndex += ' ' + translitRusEng(toIndex);
 | 
			
		||||
	}
 | 
			
		||||
	if (isUser()) {
 | 
			
		||||
		if (!asUser()->nameOrPhone.isEmpty() && asUser()->nameOrPhone != name) toIndex += ' ' + textAccentFold(asUser()->nameOrPhone);
 | 
			
		||||
		if (!asUser()->username.isEmpty()) toIndex += ' ' + textAccentFold(asUser()->username);
 | 
			
		||||
		if (!asUser()->nameOrPhone.isEmpty() && asUser()->nameOrPhone != name) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->nameOrPhone);
 | 
			
		||||
		if (!asUser()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->username);
 | 
			
		||||
	} else if (isChannel()) {
 | 
			
		||||
		if (!asChannel()->username.isEmpty()) toIndex += ' ' + textAccentFold(asChannel()->username);
 | 
			
		||||
		if (!asChannel()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asChannel()->username);
 | 
			
		||||
	}
 | 
			
		||||
	toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex);
 | 
			
		||||
 | 
			
		||||
	QStringList namesList = toIndex.toLower().split(cWordSplit(), QString::SkipEmptyParts);
 | 
			
		||||
	for (QStringList::const_iterator i = namesList.cbegin(), e = namesList.cend(); i != e; ++i) {
 | 
			
		||||
		names.insert(*i);
 | 
			
		||||
		chars.insert(i->at(0));
 | 
			
		||||
	auto namesList = TextUtilities::PrepareSearchWords(toIndex);
 | 
			
		||||
	for (auto &name : namesList) {
 | 
			
		||||
		names.insert(name);
 | 
			
		||||
		chars.insert(name[0]);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -417,21 +417,8 @@ void CountrySelectBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void CountrySelectBox::Inner::updateFilter(QString filter) {
 | 
			
		||||
	filter = textSearchKey(filter);
 | 
			
		||||
 | 
			
		||||
	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(' ');
 | 
			
		||||
	}
 | 
			
		||||
	auto words = TextUtilities::PrepareSearchWords(filter);
 | 
			
		||||
	filter = words.isEmpty() ? QString() : words.join(' ');
 | 
			
		||||
	if (_filter != filter) {
 | 
			
		||||
		_filter = filter;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -441,7 +428,7 @@ void CountrySelectBox::Inner::updateFilter(QString filter) {
 | 
			
		|||
			QChar first = _filter[0].toLower();
 | 
			
		||||
			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();
 | 
			
		||||
			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]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
		for (auto &entity : *inOutEntities) {
 | 
			
		||||
		for (auto &entity : result.entities) {
 | 
			
		||||
			if (entity.offset() >= to - start) break;
 | 
			
		||||
			if (entity.offset() + entity.length() < from - start) continue;
 | 
			
		||||
			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) {
 | 
			
		||||
				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) {
 | 
			
		||||
	auto result = QString();
 | 
			
		||||
	auto currentEntity = inOutEntities->begin();
 | 
			
		||||
	auto entitiesEnd = inOutEntities->end();
 | 
			
		||||
	auto emojiStart = text.constData();
 | 
			
		||||
inline void ReplaceInText(TextWithEntities &result) {
 | 
			
		||||
	auto newText = TextWithEntities();
 | 
			
		||||
	newText.entities = std::move(result.entities);
 | 
			
		||||
	auto currentEntity = newText.entities.begin();
 | 
			
		||||
	auto entitiesEnd = newText.entities.end();
 | 
			
		||||
	auto emojiStart = result.text.constData();
 | 
			
		||||
	auto emojiEnd = emojiStart;
 | 
			
		||||
	auto end = emojiStart + text.size();
 | 
			
		||||
	auto end = emojiStart + result.text.size();
 | 
			
		||||
	auto canFindEmoji = true;
 | 
			
		||||
	for (auto ch = emojiEnd; ch != end;) {
 | 
			
		||||
		auto emojiLength = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -234,9 +235,9 @@ inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities)
 | 
			
		|||
		    (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()))
 | 
			
		||||
		) {
 | 
			
		||||
			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()) {
 | 
			
		||||
				auto it = cEmojiVariants().constFind(emoji->nonColoredId());
 | 
			
		||||
| 
						 | 
				
			
			@ -244,7 +245,7 @@ inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities)
 | 
			
		|||
					emoji = emoji->variant(it.value());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			result.append(emoji->text());
 | 
			
		||||
			newText.text.append(emoji->text());
 | 
			
		||||
 | 
			
		||||
			ch = emojiEnd = newEmojiEnd;
 | 
			
		||||
			canFindEmoji = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -257,11 +258,12 @@ inline QString ReplaceInText(const QString &text, EntitiesInText *inOutEntities)
 | 
			
		|||
			++ch;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (result.isEmpty()) return text;
 | 
			
		||||
 | 
			
		||||
	appendPartToResult(result, emojiStart, emojiEnd, end, inOutEntities);
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
	if (newText.text.isEmpty()) {
 | 
			
		||||
		result.entities = std::move(newText.entities);
 | 
			
		||||
	} else {
 | 
			
		||||
		AppendPartToResult(newText, emojiStart, emojiEnd, end);
 | 
			
		||||
		result = std::move(newText);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline RecentEmojiPack &GetRecent() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -493,33 +493,32 @@ public:
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	TextParser(Text *t, const QString &text, const TextParseOptions &options) : _t(t),
 | 
			
		||||
		src(text),
 | 
			
		||||
		source { text },
 | 
			
		||||
		rich(options.flags & TextParseRichText),
 | 
			
		||||
		multiline(options.flags & TextParseMultiline),
 | 
			
		||||
		stopAfterWidth(QFIXED_MAX) {
 | 
			
		||||
		if (options.flags & TextParseLinks) {
 | 
			
		||||
			textParseEntities(src, options.flags, &entities, rich);
 | 
			
		||||
			TextUtilities::ParseEntities(source, options.flags, rich);
 | 
			
		||||
		}
 | 
			
		||||
		parse(options);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TextParser(Text *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t),
 | 
			
		||||
		src(textWithEntities.text),
 | 
			
		||||
		source(textWithEntities),
 | 
			
		||||
		rich(options.flags & TextParseRichText),
 | 
			
		||||
		multiline(options.flags & TextParseMultiline),
 | 
			
		||||
		stopAfterWidth(QFIXED_MAX) {
 | 
			
		||||
		auto preparsed = textWithEntities.entities;
 | 
			
		||||
		auto &preparsed = textWithEntities.entities;
 | 
			
		||||
		if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
 | 
			
		||||
			bool parseMentions = (options.flags & TextParseMentions);
 | 
			
		||||
			bool parseHashtags = (options.flags & TextParseHashtags);
 | 
			
		||||
			bool parseBotCommands = (options.flags & TextParseBotCommands);
 | 
			
		||||
			bool parseMono = (options.flags & TextParseMono);
 | 
			
		||||
			if (parseMentions && parseHashtags && parseBotCommands && parseMono) {
 | 
			
		||||
				entities = preparsed;
 | 
			
		||||
			} else {
 | 
			
		||||
			if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMono) {
 | 
			
		||||
				int32 i = 0, l = preparsed.size();
 | 
			
		||||
				entities.reserve(l);
 | 
			
		||||
				const QChar s = src.size();
 | 
			
		||||
				source.entities.clear();
 | 
			
		||||
				source.entities.reserve(l);
 | 
			
		||||
				const QChar s = source.text.size();
 | 
			
		||||
				for (; i < l; ++i) {
 | 
			
		||||
					auto type = preparsed.at(i).type();
 | 
			
		||||
					if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
 | 
			
		||||
| 
						 | 
				
			
			@ -528,7 +527,7 @@ public:
 | 
			
		|||
						((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMono)) {
 | 
			
		||||
						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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		start = src.constData();
 | 
			
		||||
		end = start + src.size();
 | 
			
		||||
		start = source.text.constData();
 | 
			
		||||
		end = start + source.text.size();
 | 
			
		||||
 | 
			
		||||
		entitiesEnd = entities.cend();
 | 
			
		||||
		waitingEntity = entities.cbegin();
 | 
			
		||||
		entitiesEnd = source.entities.cend();
 | 
			
		||||
		waitingEntity = source.entities.cbegin();
 | 
			
		||||
		while (waitingEntity != entitiesEnd && waitingEntity->length() <= 0) ++waitingEntity;
 | 
			
		||||
		int firstMonospaceOffset = EntityInText::firstMonospaceOffset(entities, end - start);
 | 
			
		||||
		int firstMonospaceOffset = EntityInText::firstMonospaceOffset(source.entities, end - start);
 | 
			
		||||
 | 
			
		||||
		ptr = start;
 | 
			
		||||
		while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
 | 
			
		||||
| 
						 | 
				
			
			@ -587,50 +586,50 @@ public:
 | 
			
		|||
		removeFlags.clear();
 | 
			
		||||
 | 
			
		||||
		_t->_links.resize(maxLnkIndex);
 | 
			
		||||
		for (Text::TextBlocks::const_iterator i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) {
 | 
			
		||||
			ITextBlock *b = *i;
 | 
			
		||||
		for (auto i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) {
 | 
			
		||||
			auto b = *i;
 | 
			
		||||
			if (b->lnkIndex() > 0x8000) {
 | 
			
		||||
				lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
 | 
			
		||||
				if (_t->_links.size() < lnkIndex) {
 | 
			
		||||
					_t->_links.resize(lnkIndex);
 | 
			
		||||
					const TextLinkData &link(links[lnkIndex - maxLnkIndex - 1]);
 | 
			
		||||
					auto &link = links[lnkIndex - maxLnkIndex - 1];
 | 
			
		||||
					ClickHandlerPtr handler;
 | 
			
		||||
					switch (link.type) {
 | 
			
		||||
					case EntityInTextCustomUrl: handler.reset(new HiddenUrlClickHandler(link.data)); break;
 | 
			
		||||
					case EntityInTextCustomUrl: handler = MakeShared<HiddenUrlClickHandler>(link.data); break;
 | 
			
		||||
					case EntityInTextEmail:
 | 
			
		||||
					case EntityInTextUrl: handler.reset(new UrlClickHandler(link.data, link.displayStatus == LinkDisplayedFull)); break;
 | 
			
		||||
					case EntityInTextBotCommand: handler.reset(new BotCommandClickHandler(link.data)); break;
 | 
			
		||||
					case EntityInTextUrl: handler = MakeShared<UrlClickHandler>(link.data, link.displayStatus == LinkDisplayedFull); break;
 | 
			
		||||
					case EntityInTextBotCommand: handler = MakeShared<BotCommandClickHandler>(link.data); break;
 | 
			
		||||
					case EntityInTextHashtag:
 | 
			
		||||
						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) {
 | 
			
		||||
							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 {
 | 
			
		||||
							handler.reset(new HashtagClickHandler(link.data));
 | 
			
		||||
							handler = MakeShared<HashtagClickHandler>(link.data);
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case EntityInTextMention:
 | 
			
		||||
						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) {
 | 
			
		||||
							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 {
 | 
			
		||||
							handler.reset(new MentionClickHandler(link.data));
 | 
			
		||||
							handler = MakeShared<MentionClickHandler>(link.data);
 | 
			
		||||
						}
 | 
			
		||||
					break;
 | 
			
		||||
					case EntityInTextMentionName: {
 | 
			
		||||
						UserId userId = 0;
 | 
			
		||||
						uint64 accessHash = 0;
 | 
			
		||||
						if (mentionNameToFields(link.data, &userId, &accessHash)) {
 | 
			
		||||
							handler.reset(new MentionNameClickHandler(link.text, userId, accessHash));
 | 
			
		||||
						auto fields = TextUtilities::MentionNameDataToFields(link.data);
 | 
			
		||||
						if (fields.userId) {
 | 
			
		||||
							handler = MakeShared<MentionNameClickHandler>(link.text, fields.userId, fields.accessHash);
 | 
			
		||||
						} else {
 | 
			
		||||
							LOG(("Bad mention name: %1").arg(link.data));
 | 
			
		||||
						}
 | 
			
		||||
					} break;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					t_assert(!handler.isNull());
 | 
			
		||||
					_t->setLink(lnkIndex, handler);
 | 
			
		||||
					if (!handler.isNull()) {
 | 
			
		||||
						_t->setLink(lnkIndex, handler);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				b->setLnkIndex(lnkIndex);
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -667,11 +666,9 @@ private:
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	Text *_t;
 | 
			
		||||
	QString src;
 | 
			
		||||
	TextWithEntities source;
 | 
			
		||||
	const QChar *start, *end, *ptr;
 | 
			
		||||
	bool rich, multiline;
 | 
			
		||||
 | 
			
		||||
	EntitiesInText entities;
 | 
			
		||||
	EntitiesInText::const_iterator waitingEntity, entitiesEnd;
 | 
			
		||||
 | 
			
		||||
	typedef QVector<TextLinkData> TextLinks;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -258,15 +258,6 @@ inline TextSelection unshiftSelection(TextSelection selection, const Text &byTex
 | 
			
		|||
	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
 | 
			
		||||
QString textcmdSkipBlock(ushort w, ushort h);
 | 
			
		||||
QString textcmdStartLink(ushort lnkIndex);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -116,22 +116,6 @@ struct TextWithEntities {
 | 
			
		|||
	QString text;
 | 
			
		||||
	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 {
 | 
			
		||||
	TextParseMultiline = 0x001,
 | 
			
		||||
| 
						 | 
				
			
			@ -148,39 +132,91 @@ enum {
 | 
			
		|||
	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('.');
 | 
			
		||||
	if (!components.isEmpty()) {
 | 
			
		||||
		*outUserId = components.at(0).toInt();
 | 
			
		||||
		*outAccessHash = (components.size() > 1) ? components.at(1).toULongLong() : 0;
 | 
			
		||||
		return (*outUserId != 0);
 | 
			
		||||
		return { components.at(0).toInt(), (components.size() > 1) ? components.at(1).toULongLong() : 0 };
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
	return MentionNameFields {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline QString mentionNameFromFields(int32 userId, uint64 accessHash) {
 | 
			
		||||
	return QString::number(userId) + '.' + QString::number(accessHash);
 | 
			
		||||
inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
 | 
			
		||||
	auto result = QString::number(fields.userId);
 | 
			
		||||
	if (fields.accessHash) {
 | 
			
		||||
		result += '.' + QString::number(fields.accessHash);
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities);
 | 
			
		||||
MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending = false);
 | 
			
		||||
EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities);
 | 
			
		||||
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).
 | 
			
		||||
void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities, bool rich = false);
 | 
			
		||||
QString textApplyEntities(const QString &text, const EntitiesInText &entities);
 | 
			
		||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
 | 
			
		||||
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) {
 | 
			
		||||
	EntitiesInText entities;
 | 
			
		||||
	auto prepareFlags = checkLinks ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0;
 | 
			
		||||
	return prepareTextWithEntities(result, prepareFlags, &entities);
 | 
			
		||||
enum class PrepareTextOption {
 | 
			
		||||
	IgnoreLinks,
 | 
			
		||||
	CheckLinks,
 | 
			
		||||
};
 | 
			
		||||
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
 | 
			
		||||
void cleanTextWithEntities(QString &result, EntitiesInText *inOutEntities);
 | 
			
		||||
void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities);
 | 
			
		||||
// Replace bad symbols with space and remove '\r'.
 | 
			
		||||
void ApplyServerCleaning(TextWithEntities &result);
 | 
			
		||||
 | 
			
		||||
} // namespace TextUtilities
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
		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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -840,25 +840,22 @@ void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp!
 | 
			
		|||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	initLinkSets();
 | 
			
		||||
 | 
			
		||||
	int32 len = text.size();
 | 
			
		||||
	auto len = text.size();
 | 
			
		||||
	const QChar *start = text.unicode(), *end = start + text.size();
 | 
			
		||||
	for (int32 offset = 0, matchOffset = offset; offset < len;) {
 | 
			
		||||
		QRegularExpressionMatch m = reDomain().match(text, matchOffset);
 | 
			
		||||
	for (auto offset = 0, matchOffset = offset; offset < len;) {
 | 
			
		||||
		auto m = TextUtilities::RegExpDomain().match(text, matchOffset);
 | 
			
		||||
		if (!m.hasMatch()) break;
 | 
			
		||||
 | 
			
		||||
		int32 domainOffset = m.capturedStart();
 | 
			
		||||
		auto domainOffset = m.capturedStart();
 | 
			
		||||
 | 
			
		||||
		QString protocol = m.captured(1).toLower();
 | 
			
		||||
		QString topDomain = m.captured(3).toLower();
 | 
			
		||||
 | 
			
		||||
		bool isProtocolValid = protocol.isEmpty() || validProtocols().contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
 | 
			
		||||
		bool isTopDomainValid = !protocol.isEmpty() || validTopDomains().contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar)));
 | 
			
		||||
		auto protocol = m.captured(1).toLower();
 | 
			
		||||
		auto topDomain = m.captured(3).toLower();
 | 
			
		||||
		auto isProtocolValid = protocol.isEmpty() || TextUtilities::IsValidProtocol(protocol);
 | 
			
		||||
		auto isTopDomainValid = !protocol.isEmpty() || TextUtilities::IsValidTopDomain(topDomain);
 | 
			
		||||
 | 
			
		||||
		if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
 | 
			
		||||
			QString forMailName = text.mid(offset, domainOffset - offset - 1);
 | 
			
		||||
			QRegularExpressionMatch mMailName = reMailName().match(forMailName);
 | 
			
		||||
			auto forMailName = text.mid(offset, domainOffset - offset - 1);
 | 
			
		||||
			auto mMailName = TextUtilities::RegExpMailNameAtEnd().match(forMailName);
 | 
			
		||||
			if (mMailName.hasMatch()) {
 | 
			
		||||
				offset = matchOffset = m.capturedEnd();
 | 
			
		||||
				continue;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -164,7 +164,7 @@ void EditorBlock::Row::fillValueString() {
 | 
			
		|||
void EditorBlock::Row::fillSearchIndex() {
 | 
			
		||||
	_searchWords.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);
 | 
			
		||||
	for_const (auto &word, words) {
 | 
			
		||||
		_searchWords.insert(word);
 | 
			
		||||
| 
						 | 
				
			
			@ -346,11 +346,8 @@ void EditorBlock::scrollToSelected() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void EditorBlock::searchByQuery(QString query) {
 | 
			
		||||
	auto searchWords = QStringList();
 | 
			
		||||
	if (!query.isEmpty()) {
 | 
			
		||||
		searchWords = textAccentFold(query.trimmed().toLower()).split(SearchSplitter, QString::SkipEmptyParts);
 | 
			
		||||
		query = searchWords.join(' ');
 | 
			
		||||
	}
 | 
			
		||||
	auto words = TextUtilities::PrepareSearchWords(query, &SearchSplitter);
 | 
			
		||||
	query = words.isEmpty() ? QString() : words.join(' ');
 | 
			
		||||
	if (_searchQuery != query) {
 | 
			
		||||
		setSelected(-1);
 | 
			
		||||
		setPressed(-1);
 | 
			
		||||
| 
						 | 
				
			
			@ -359,7 +356,7 @@ void EditorBlock::searchByQuery(QString query) {
 | 
			
		|||
		_searchResults.clear();
 | 
			
		||||
 | 
			
		||||
		auto toFilter = OrderedSet<int>();
 | 
			
		||||
		for_const (auto &word, searchWords) {
 | 
			
		||||
		for_const (auto &word, words) {
 | 
			
		||||
			if (word.isEmpty()) continue;
 | 
			
		||||
 | 
			
		||||
			auto testToFilter = _searchIndex.value(word[0]);
 | 
			
		||||
| 
						 | 
				
			
			@ -371,8 +368,8 @@ void EditorBlock::searchByQuery(QString query) {
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (!toFilter.isEmpty()) {
 | 
			
		||||
			auto allWordsFound = [&searchWords](const Row &row) {
 | 
			
		||||
				for_const (auto &word, searchWords) {
 | 
			
		||||
			auto allWordsFound = [&words](const Row &row) {
 | 
			
		||||
				for_const (auto &word, words) {
 | 
			
		||||
					if (!row.searchWordsContain(word)) {
 | 
			
		||||
						return false;
 | 
			
		||||
					}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue