feat: search in deleted messages

This commit is contained in:
AlexeyZavar
2026-01-26 22:51:07 +03:00
parent 6e368fedeb
commit 48aecae2d9
8 changed files with 245 additions and 26 deletions
+26 -2
View File
@@ -296,14 +296,38 @@ void addDeletedMessage(const DeletedMessage &message) {
}
}
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit) {
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit, const std::string &searchQuery) {
if (searchQuery.empty()) {
return storage.get_all<DeletedMessage>(
where(
column<DeletedMessage>(&DeletedMessage::userId) == userId and
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
(column<DeletedMessage>(&DeletedMessage::topicId) == topicId or topicId == 0) and
(column<DeletedMessage>(&DeletedMessage::messageId) > minId or minId == 0) and
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0)
),
order_by(column<DeletedMessage>(&DeletedMessage::messageId)).desc(),
limit(totalLimit)
);
}
std::string escaped;
escaped.reserve(searchQuery.size());
for (const auto c : searchQuery) {
if (c == '%' || c == '_' || c == '\\') {
escaped += '\\';
}
escaped += c;
}
const auto pattern = "%" + escaped + "%";
return storage.get_all<DeletedMessage>(
where(
column<DeletedMessage>(&DeletedMessage::userId) == userId and
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
(column<DeletedMessage>(&DeletedMessage::topicId) == topicId or topicId == 0) and
(column<DeletedMessage>(&DeletedMessage::messageId) > minId or minId == 0) and
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0)
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0) and
like(column<DeletedMessage>(&DeletedMessage::text), pattern, "\\")
),
order_by(column<DeletedMessage>(&DeletedMessage::messageId)).desc(),
limit(totalLimit)
+1 -1
View File
@@ -26,7 +26,7 @@ std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageI
bool hasRevisions(ID userId, ID dialogId, ID messageId);
void addDeletedMessage(const DeletedMessage &message);
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit);
std::vector<DeletedMessage> getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit, const std::string &searchQuery = "");
bool hasDeletedMessages(ID userId, ID dialogId, ID topicId);
std::vector<RegexFilter> getAllRegexFilters();
@@ -127,10 +127,10 @@ void addDeletedMessage(not_null<HistoryItem*> item) {
}
std::vector<AyuMessageBase>
getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit) {
getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit, const QString &searchQuery) {
const ID userId = peer->session().userId().bare & PeerId::kChatTypeMask;
return convertToBase(
AyuDatabase::getDeletedMessages(userId, getDialogIdFromPeer(peer), topicId, minId, maxId, totalLimit));
AyuDatabase::getDeletedMessages(userId, getDialogIdFromPeer(peer), topicId, minId, maxId, totalLimit, searchQuery.toStdString()));
}
bool hasDeletedMessages(not_null<PeerData*> peer, ID topicId) {
@@ -17,7 +17,7 @@ std::vector<AyuMessageBase> getEditedMessages(not_null<HistoryItem*> item, ID mi
bool hasRevisions(not_null<HistoryItem*> item);
void addDeletedMessage(not_null<HistoryItem*> item);
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit);
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID topicId, ID minId, ID maxId, int totalLimit, const QString &searchQuery = QString());
bool hasDeletedMessages(not_null<PeerData*> peer, ID topicId);
}
@@ -472,6 +472,9 @@ void InnerWidget::checkPreloadMore() {
}
void InnerWidget::updateEmptyText() {
// auto text = !_searchQuery.isEmpty()
// ? tr::lng_admin_log_no_results_title(tr::now)
// : tr::lng_search_messages_none(tr::now);
_emptyText.setMarkedText(st::defaultTextStyle, Ui::Text::Semibold(tr::lng_search_messages_none(tr::now)));
}
@@ -639,6 +642,7 @@ void InnerWidget::saveState(not_null<SectionMemento*> memento) {
base::take(_messageIds),
_upLoaded,
_downLoaded);
memento->setSearchQuery(base::take(_searchQuery));
base::take(_itemsByData);
_upLoaded = _downLoaded = true; // Don't load or handle anything anymore.
}
@@ -652,10 +656,30 @@ void InnerWidget::restoreState(not_null<SectionMemento*> memento) {
_messageIds = memento->takeMessageIds();
_upLoaded = memento->upLoaded();
_downLoaded = memento->downLoaded();
_searchQuery = memento->takeSearchQuery();
updateMinMaxIds();
updateSize();
}
void InnerWidget::applySearch(const QString &query) {
if (_item) {
return;
}
if (_searchQuery != query) {
_searchQuery = query;
_upLoaded = false;
_downLoaded = true;
_items.clear();
_messageIds.clear();
_itemsByData.clear();
_itemDates.clear();
updateMinMaxIds();
updateEmptyText();
updateSize();
preloadMore(Direction::Up);
}
}
void InnerWidget::preloadMore(Direction direction) {
auto &loadedFlag = (direction == Direction::Up) ? _upLoaded : _downLoaded;
if (loadedFlag) {
@@ -672,7 +696,7 @@ void InnerWidget::preloadMore(Direction direction) {
messages = AyuMessages::getEditedMessages(_item, minId, maxId, perPage);
} else {
// viewing deleted messages
messages = AyuMessages::getDeletedMessages(_peer, _topicId, minId, maxId, perPage);
messages = AyuMessages::getDeletedMessages(_peer, _topicId, minId, maxId, perPage, _searchQuery);
}
crl::on_main([=]
@@ -78,6 +78,8 @@ public:
void saveState(not_null<SectionMemento*> memento);
void restoreState(not_null<SectionMemento*> memento);
void applySearch(const QString &query);
// Ui::AbstractTooltipShower interface.
QString tooltipText() const override;
QPoint tooltipPos() const override;
@@ -292,6 +294,7 @@ private:
bool _upLoaded = true;
bool _downLoaded = true;
Ui::Text::String _emptyText;
QString _searchQuery;
MouseAction _mouseAction = MouseAction::None;
TextSelectType _mouseSelectType = TextSelectType::Letters;
@@ -9,6 +9,7 @@
#include "apiwrap.h"
#include "ayu/ui/message_history/history_inner.h"
#include "base/timer.h"
#include "core/shortcuts.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "info/profile/info_profile_values.h"
@@ -17,10 +18,12 @@
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_info.h"
#include "ui/effects/animations.h"
#include "ui/ui_utility.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h"
#include "window/window_adaptive.h"
@@ -35,13 +38,25 @@ public:
FixedBar(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
not_null<PeerData*> peer,
bool searchEnabled);
[[nodiscard]] rpl::producer<> searchCancelRequests() const;
[[nodiscard]] rpl::producer<QString> searchRequests() const;
// When animating mode is enabled the content is hidden and the
// whole fixed bar acts like a back button.
void setAnimatingMode(bool enabled);
void goBack();
void showSearch();
bool setSearchFocus() {
if (_searchShown) {
_field->setFocus();
return true;
}
return false;
}
protected:
void mousePressEvent(QMouseEvent *e) override;
@@ -49,12 +64,27 @@ protected:
int resizeGetHeight(int newWidth) override;
private:
void toggleSearch();
void cancelSearch();
void searchUpdated();
void applySearch();
void searchAnimationCallback();
not_null<Window::SessionController*> _controller;
not_null<PeerData*> _peer;
object_ptr<Ui::InputField> _field;
object_ptr<Profile::BackButton> _backButton;
object_ptr<Ui::IconButton> _search;
object_ptr<Ui::CrossButton> _cancel;
Ui::Animations::Simple _searchShownAnimation;
bool _searchShown = false;
bool _searchEnabled = true;
bool _animatingMode = false;
base::Timer _searchTimer;
rpl::event_stream<> _searchCancelRequests;
rpl::event_stream<QString> _searchRequests;
};
object_ptr<Window::SectionWidget> SectionMemento::createWidget(
@@ -73,18 +103,34 @@ object_ptr<Window::SectionWidget> SectionMemento::createWidget(
FixedBar::FixedBar(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer) : Ui::RpWidget(parent)
not_null<PeerData*> peer,
bool searchEnabled) : Ui::RpWidget(parent)
, _controller(controller)
, _peer(peer)
, _field(this, st::defaultMultiSelectSearchField, tr::lng_dlg_filter())
, _backButton(this)
, _cancel(this, st::historyAdminLogCancelSearch) {
, _search(this, st::topBarSearch)
, _cancel(this, st::historyAdminLogCancelSearch)
, _searchEnabled(searchEnabled) {
_backButton->moveToLeft(0, 0);
_backButton->setClickedCallback([=]
{
goBack();
});
_backButton->setClickedCallback([=] { goBack(); });
_search->setClickedCallback([=] { showSearch(); });
_cancel->setClickedCallback([=] { cancelSearch(); });
_field->hide();
_field->cancelled() | rpl::on_next([=] {
cancelSearch();
}, _field->lifetime());
_field->changes() | rpl::on_next([=] {
searchUpdated();
}, _field->lifetime());
_field->submits(
) | rpl::on_next([=] { applySearch(); }, _field->lifetime());
_searchTimer.setCallback([=] { applySearch(); });
_cancel->hide(anim::type::instant);
if (!_searchEnabled) {
_search->hide();
}
Info::Profile::NameValue(peer) | rpl::on_next([=](QString name) {
_backButton->setText(name);
@@ -99,19 +145,106 @@ void FixedBar::goBack() {
_controller->showBackFromStack();
}
void FixedBar::showSearch() {
if (_searchEnabled && !_searchShown) {
toggleSearch();
}
}
void FixedBar::toggleSearch() {
_searchShown = !_searchShown;
_cancel->toggle(_searchShown, anim::type::normal);
_searchShownAnimation.start(
[=] { searchAnimationCallback(); },
_searchShown ? 0. : 1.,
_searchShown ? 1. : 0.,
st::historyAdminLogSearchSlideDuration);
_search->setDisabled(_searchShown);
if (_searchShown) {
_field->show();
_field->setFocus();
} else {
_searchCancelRequests.fire({});
}
}
void FixedBar::searchAnimationCallback() {
if (!_searchShownAnimation.animating()) {
_field->setVisible(_searchShown);
_search->setIconOverride(
_searchShown ? &st::topBarSearch.icon : nullptr,
_searchShown ? &st::topBarSearch.icon : nullptr);
_search->setRippleColorOverride(
_searchShown ? &st::topBarBg : nullptr);
_search->setCursor(
_searchShown ? style::cur_default : style::cur_pointer);
_backButton->setOpacity(1.);
}
resizeToWidth(width());
}
void FixedBar::cancelSearch() {
if (_searchShown) {
if (!_field->getLastText().isEmpty()) {
_field->clear();
_field->setFocus();
applySearch();
} else {
toggleSearch();
}
}
}
void FixedBar::searchUpdated() {
if (_field->getLastText().isEmpty()) {
applySearch();
} else {
_searchTimer.callOnce(AutoSearchTimeout);
}
}
void FixedBar::applySearch() {
_searchRequests.fire_copy(_field->getLastText());
}
rpl::producer<> FixedBar::searchCancelRequests() const {
return _searchCancelRequests.events();
}
rpl::producer<QString> FixedBar::searchRequests() const {
return _searchRequests.events();
}
int FixedBar::resizeGetHeight(int newWidth) {
auto filterLeft = newWidth;
auto cancelLeft = filterLeft - _cancel->width();
_cancel->moveToLeft(cancelLeft, 0);
auto searchShownLeft = st::topBarArrowPadding.left();
auto searchHiddenLeft = filterLeft - 0;
auto searchCurrentLeft = anim::interpolate(searchHiddenLeft, searchShownLeft, 0.0);
const auto offset = st::historySendRight + st::lineWidth;
const auto searchShownLeft = st::topBarArrowPadding.left();
const auto searchHiddenLeft = _searchEnabled
? newWidth - _search->width() - offset
: newWidth;
const auto searchShown = _searchShownAnimation.value(_searchShown
? 1.
: 0.);
const auto searchCurrentLeft = anim::interpolate(
searchHiddenLeft,
searchShownLeft,
searchShown);
if (_searchEnabled) {
_search->moveToLeft(searchCurrentLeft, 0);
}
_backButton->setOpacity(1. - searchShown);
_backButton->resizeToWidth(searchCurrentLeft);
_backButton->moveToLeft(0, 0);
auto newHeight = _backButton->height();
const auto cancelLeft = newWidth - _cancel->width() - offset;
_cancel->moveToLeft(cancelLeft, 0);
const auto newHeight = _backButton->height();
const auto fieldLeft = searchShownLeft + _search->width();
_field->setGeometryToLeft(
fieldLeft,
st::historyAdminLogSearchTop,
cancelLeft - fieldLeft,
_field->height());
return newHeight;
}
@@ -126,7 +259,11 @@ void FixedBar::setAnimatingMode(bool enabled) {
} else {
setAttribute(Qt::WA_OpaquePaintEvent);
showChildren();
_field->hide();
_cancel->setVisible(false);
if (!_searchEnabled) {
_search->hide();
}
}
show();
}
@@ -155,12 +292,20 @@ Widget::Widget(
ID topicId)
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(peer)),
_scroll(this, st::historyScroll, false),
_fixedBar(this, controller, peer),
_fixedBar(this, controller, peer, !item),
_fixedBarShadow(this),
_item(item),
_topicId(topicId) {
_fixedBar->move(0, 0);
_fixedBar->resizeToWidth(width());
_fixedBar->searchCancelRequests(
) | rpl::on_next([=] {
setInnerFocus();
}, lifetime());
_fixedBar->searchRequests(
) | rpl::on_next([=](const QString &query) {
_inner->applySearch(query);
}, lifetime());
_fixedBar->show();
_fixedBarShadow->raise();
@@ -219,7 +364,9 @@ QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams &params) {
}
void Widget::doSetInnerFocus() {
_inner->setFocus();
if (!_fixedBar->setSearchFocus()) {
_inner->setFocus();
}
}
bool Widget::showInternal(
@@ -241,7 +388,19 @@ void Widget::setInternalState(const QRect &geometry, not_null<SectionMemento*> m
}
void Widget::setupShortcuts() {
// todo: smth
Shortcuts::Requests(
) | rpl::filter([=] {
return Ui::AppInFocus()
&& Ui::InFocusChain(this)
&& !controller()->isLayerShown()
&& isActiveWindow();
}) | rpl::on_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;
request->check(Command::Search, 2) && request->handle([=] {
_fixedBar->showSearch();
return true;
});
}, lifetime());
}
std::shared_ptr<Window::SectionMemento> Widget::createMemento() {
@@ -139,6 +139,14 @@ public:
return _downLoaded;
}
void setSearchQuery(QString &&query) {
_searchQuery = std::move(query);
}
QString takeSearchQuery() {
return std::move(_searchQuery);
}
private:
not_null<PeerData*> _peer;
HistoryItem *_item;
@@ -148,6 +156,7 @@ private:
std::set<uint64> _messageIds;
bool _upLoaded = false;
bool _downLoaded = true;
QString _searchQuery;
};
} // namespace MessageHistory