mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Add a slow way to search a room
This commit is contained in:
parent
857d9cf2b6
commit
f1c1f18f81
8 changed files with 120 additions and 48 deletions
|
@ -22,6 +22,8 @@ Item {
|
|||
|
||||
property int availableWidth: width
|
||||
|
||||
property string searchString: ""
|
||||
|
||||
ScrollBar {
|
||||
id: scrollbar
|
||||
parent: chat.parent
|
||||
|
@ -43,9 +45,10 @@ Item {
|
|||
id: filteredTimeline
|
||||
source: room
|
||||
filterByThread: room ? room.thread : ""
|
||||
filterByContent: chatRoot.searchString
|
||||
}
|
||||
|
||||
model: filteredTimeline.filterByThread ? filteredTimeline : room
|
||||
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
|
||||
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
|
||||
//onModelChanged: if (room) room.sendReset()
|
||||
//reuseItems: true
|
||||
|
@ -403,7 +406,7 @@ Item {
|
|||
required property bool isEditable
|
||||
required property bool isEdited
|
||||
required property bool isStateEvent
|
||||
required property bool previousMessageIsStateEvent
|
||||
property bool previousMessageIsStateEvent: chat.model.dataByIndex(index+1, Room.IsStateEvent)
|
||||
required property string replyTo
|
||||
required property string threadId
|
||||
required property string userId
|
||||
|
@ -417,9 +420,9 @@ Item {
|
|||
required property int status
|
||||
required property int index
|
||||
required property int relatedEventCacheBuster
|
||||
required property string previousMessageUserId
|
||||
required property string day
|
||||
required property string previousMessageDay
|
||||
property string previousMessageUserId: chat.model.dataByIndex(index+1, Room.UserId)
|
||||
property string previousMessageDay: chat.model.dataByIndex(index+1, Room.Day)
|
||||
required property string userName
|
||||
property bool scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ Item {
|
|||
spacing: 0
|
||||
|
||||
TopBar {
|
||||
id: topBar
|
||||
|
||||
showBackButton: timelineView.showBackButton
|
||||
}
|
||||
|
||||
|
@ -102,6 +104,7 @@ Item {
|
|||
|
||||
MessageView {
|
||||
implicitHeight: msgView.height - typingIndicator.height
|
||||
searchString: topBar.searchString
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,14 @@ Pane {
|
|||
property bool isDirect: room ? room.isDirect : false
|
||||
property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
|
||||
|
||||
property string searchString: ""
|
||||
|
||||
onRoomIdChanged: {
|
||||
searchString = "";
|
||||
searchButton.searchActive = false;
|
||||
searchField.text = ""
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
||||
z: 3
|
||||
|
@ -177,12 +185,42 @@ Pane {
|
|||
text: roomTopic
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
ImageButton {
|
||||
id: pinButton
|
||||
|
||||
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
||||
|
||||
visible: !!room && room.pinnedMessages.length > 0
|
||||
Layout.column: 3
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Show or hide pinned messages")
|
||||
onClicked: {
|
||||
var ps = Settings.hiddenPins;
|
||||
if (pinsShown) {
|
||||
ps.push(roomId);
|
||||
} else {
|
||||
const index = ps.indexOf(roomId);
|
||||
if (index > -1) {
|
||||
ps.splice(index, 1);
|
||||
}
|
||||
}
|
||||
Settings.hiddenPins = ps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
Layout.column: 4
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.maximumWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
|
||||
contentItem: EncryptionIndicator {
|
||||
sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio
|
||||
|
@ -216,40 +254,37 @@ Pane {
|
|||
}
|
||||
|
||||
ImageButton {
|
||||
id: pinButton
|
||||
id: searchButton
|
||||
|
||||
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
||||
property bool searchActive: false
|
||||
|
||||
visible: !!room && room.pinnedMessages.length > 0
|
||||
Layout.column: 4
|
||||
visible: !!room
|
||||
Layout.column: 5
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
||||
image: ":/icons/icons/ui/search.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Show or hide pinned messages")
|
||||
ToolTip.text: qsTr("Search this room")
|
||||
onClicked: {
|
||||
var ps = Settings.hiddenPins;
|
||||
if (pinsShown) {
|
||||
ps.push(roomId);
|
||||
} else {
|
||||
const index = ps.indexOf(roomId);
|
||||
if (index > -1) {
|
||||
ps.splice(index, 1);
|
||||
searchActive = !searchActive
|
||||
if (searchActive) {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
else {
|
||||
searchField.clear();
|
||||
topBar.searchString = "";
|
||||
}
|
||||
}
|
||||
Settings.hiddenPins = ps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: roomOptionsButton
|
||||
|
||||
visible: !!room
|
||||
Layout.column: 5
|
||||
Layout.column: 6
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
@ -293,7 +328,7 @@ Pane {
|
|||
|
||||
Layout.row: 3
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 3
|
||||
Layout.columnSpan: 4
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
||||
|
@ -374,7 +409,7 @@ Pane {
|
|||
|
||||
Layout.row: 4
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 1
|
||||
Layout.columnSpan: 4
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
|
||||
|
@ -404,6 +439,20 @@ Pane {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: searchField
|
||||
visible: searchButton.searchActive
|
||||
|
||||
Layout.row: 5
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 4
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
placeholderText: qsTr("Enter search query")
|
||||
onAccepted: topBar.searchString = text
|
||||
}
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
|
|
|
@ -898,6 +898,7 @@ EventStore::fetchMore()
|
|||
mtx::http::MessagesOpts opts;
|
||||
opts.room_id = room_id_;
|
||||
opts.from = cache::client()->previousBatchToken(room_id_);
|
||||
opts.limit = 80;
|
||||
|
||||
nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from);
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "TimelineFilter.h"
|
||||
|
||||
#include "Logging.h"
|
||||
|
@ -19,6 +23,17 @@ TimelineFilter::setThreadId(const QString &t)
|
|||
emit threadIdChanged();
|
||||
}
|
||||
|
||||
void
|
||||
TimelineFilter::setContentFilter(const QString &c)
|
||||
{
|
||||
nhlog::ui()->debug("Filtering by content '{}'", c.toStdString());
|
||||
if (this->contentFilter != c) {
|
||||
this->contentFilter = c;
|
||||
invalidateFilter();
|
||||
}
|
||||
emit contentFilterChanged();
|
||||
}
|
||||
|
||||
void
|
||||
TimelineFilter::setSource(TimelineModel *s)
|
||||
{
|
||||
|
@ -62,11 +77,20 @@ TimelineFilter::currentIndex() const
|
|||
bool
|
||||
TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
|
||||
{
|
||||
if (threadId.isEmpty())
|
||||
if (threadId.isEmpty() && contentFilter.isEmpty())
|
||||
return true;
|
||||
|
||||
if (auto s = sourceModel()) {
|
||||
auto idx = s->index(source_row, 0);
|
||||
if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body)
|
||||
.toString()
|
||||
.contains(contentFilter, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (threadId.isEmpty())
|
||||
return true;
|
||||
|
||||
return s->data(idx, TimelineModel::EventId) == threadId ||
|
||||
s->data(idx, TimelineModel::ThreadId) == threadId;
|
||||
} else {
|
||||
|
|
|
@ -16,6 +16,8 @@ class TimelineFilter : public QSortFilterProxyModel
|
|||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged)
|
||||
Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY
|
||||
contentFilterChanged)
|
||||
Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged)
|
||||
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
|
||||
|
||||
|
@ -23,15 +25,23 @@ public:
|
|||
explicit TimelineFilter(QObject *parent = nullptr);
|
||||
|
||||
QString filterByThread() const { return threadId; }
|
||||
QString filterByContent() const { return contentFilter; }
|
||||
TimelineModel *source() const;
|
||||
int currentIndex() const;
|
||||
|
||||
void setThreadId(const QString &t);
|
||||
void setContentFilter(const QString &t);
|
||||
void setSource(TimelineModel *t);
|
||||
void setCurrentIndex(int idx);
|
||||
|
||||
Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const
|
||||
{
|
||||
return data(index(i, 0), role);
|
||||
}
|
||||
|
||||
signals:
|
||||
void threadIdChanged();
|
||||
void contentFilterChanged();
|
||||
void sourceChanged();
|
||||
void currentIndexChanged();
|
||||
|
||||
|
@ -39,5 +49,5 @@ protected:
|
|||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
private:
|
||||
QString threadId;
|
||||
QString threadId, contentFilter;
|
||||
};
|
||||
|
|
|
@ -481,12 +481,9 @@ TimelineModel::roleNames() const
|
|||
{IsOnlyEmoji, "isOnlyEmoji"},
|
||||
{Body, "body"},
|
||||
{FormattedBody, "formattedBody"},
|
||||
{PreviousMessageUserId, "previousMessageUserId"},
|
||||
{IsSender, "isSender"},
|
||||
{UserId, "userId"},
|
||||
{UserName, "userName"},
|
||||
{PreviousMessageDay, "previousMessageDay"},
|
||||
{PreviousMessageIsStateEvent, "previousMessageIsStateEvent"},
|
||||
{Day, "day"},
|
||||
{Timestamp, "timestamp"},
|
||||
{Url, "url"},
|
||||
|
@ -804,22 +801,6 @@ TimelineModel::data(const QModelIndex &index, int role) const
|
|||
if (!event)
|
||||
return "";
|
||||
|
||||
if (role == PreviousMessageDay || role == PreviousMessageUserId ||
|
||||
role == PreviousMessageIsStateEvent) {
|
||||
int prevIdx = rowCount() - index.row() - 2;
|
||||
if (prevIdx < 0)
|
||||
return {};
|
||||
auto tempEv = events.get(prevIdx);
|
||||
if (!tempEv)
|
||||
return {};
|
||||
if (role == PreviousMessageUserId)
|
||||
return data(*tempEv, UserId);
|
||||
else if (role == PreviousMessageDay)
|
||||
return data(*tempEv, Day);
|
||||
else
|
||||
return data(*tempEv, IsStateEvent);
|
||||
}
|
||||
|
||||
return data(*event, role);
|
||||
}
|
||||
|
||||
|
|
|
@ -214,12 +214,9 @@ public:
|
|||
IsOnlyEmoji,
|
||||
Body,
|
||||
FormattedBody,
|
||||
PreviousMessageUserId,
|
||||
IsSender,
|
||||
UserId,
|
||||
UserName,
|
||||
PreviousMessageDay,
|
||||
PreviousMessageIsStateEvent,
|
||||
Day,
|
||||
Timestamp,
|
||||
Url,
|
||||
|
@ -257,6 +254,10 @@ public:
|
|||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
|
||||
Q_INVOKABLE QVariant dataById(const QString &id, int role, const QString &relatedTo);
|
||||
Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const
|
||||
{
|
||||
return data(index(i), role);
|
||||
}
|
||||
|
||||
bool canFetchMore(const QModelIndex &) const override;
|
||||
void fetchMore(const QModelIndex &) override;
|
||||
|
|
Loading…
Reference in a new issue