Allow to filter RSS by simple string

Adds a search bar for RSS items. It supports plain text search (no regex), applies only to the selected tab, updates results as you type, and shows all items when the field is empty.

PR #23278.
Resolves #14719, resolves #15538, resolves #18444, resolves #18183, resolves #22570.

---------

Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
This commit is contained in:
Samuel Lachance
2025-10-03 20:12:26 -04:00
committed by GitHub
parent eed0e56d1a
commit ee881d4889
5 changed files with 70 additions and 7 deletions

View File

@@ -64,7 +64,7 @@ QListWidgetItem *ArticleListWidget::mapRSSArticle(RSS::Article *rssArticle) cons
return m_rssArticleToListItemMapping.value(rssArticle);
}
void ArticleListWidget::setRSSItem(RSS::Item *rssItem, bool unreadOnly)
void ArticleListWidget::setRSSItem(RSS::Item *rssItem, bool unreadOnly, const QString &filter)
{
// Clear the list first
clear();
@@ -82,7 +82,7 @@ void ArticleListWidget::setRSSItem(RSS::Item *rssItem, bool unreadOnly)
for (auto *article : asConst(rssItem->articles()))
{
if (!(m_unreadOnly && article->isRead()))
if (!(m_unreadOnly && article->isRead()) && (filter.isEmpty() || article->title().contains(filter, Qt::CaseInsensitive)))
{
auto *item = createItem(article);
addItem(item);

View File

@@ -48,7 +48,7 @@ public:
RSS::Article *getRSSArticle(QListWidgetItem *item) const;
QListWidgetItem *mapRSSArticle(RSS::Article *rssArticle) const;
void setRSSItem(RSS::Item *rssItem, bool unreadOnly = false);
void setRSSItem(RSS::Item *rssItem, bool unreadOnly, const QString &filter);
private slots:
void handleArticleAdded(RSS::Article *rssArticle);

View File

@@ -49,6 +49,7 @@
#include "base/rss/rss_session.h"
#include "gui/autoexpandabledialog.h"
#include "gui/interfaces/iguiapplication.h"
#include "gui/lineedit.h"
#include "gui/uithememanager.h"
#include "gui/utils/keysequence.h"
#include "articlelistwidget.h"
@@ -108,6 +109,7 @@ namespace
RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
: GUIApplicationComponent(app, parent)
, m_ui {new Ui::RSSWidget}
, m_rssFilter {new LineEdit(this)}
{
m_ui->setupUi(this);
@@ -130,6 +132,13 @@ RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
m_ui->rssDownloaderBtn->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_s, u"download"_s));
#endif
m_rssFilter->setMaximumWidth(200);
m_rssFilter->setPlaceholderText(tr("Filter feed items..."));
const int spacerIndex = m_ui->horizontalLayout->indexOf(m_ui->spacer1);
m_ui->horizontalLayout->insertWidget((spacerIndex + 1), m_rssFilter);
connect(m_rssFilter, &QLineEdit::textChanged, this, &RSSWidget::handleRSSFilterTextChanged);
connect(m_ui->articleListWidget, &ArticleListWidget::customContextMenuRequested, this, &RSSWidget::displayItemsListMenu);
connect(m_ui->articleListWidget, &ArticleListWidget::currentItemChanged, this, &RSSWidget::handleCurrentArticleItemChanged);
connect(m_ui->articleListWidget, &ArticleListWidget::itemDoubleClicked, this, &RSSWidget::downloadSelectedTorrents);
@@ -568,7 +577,8 @@ void RSSWidget::copySelectedFeedsURL()
void RSSWidget::handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem)
{
m_ui->articleListWidget->setRSSItem(m_ui->feedListWidget->getRSSItem(currentItem)
, (currentItem == m_ui->feedListWidget->stickyUnreadItem()));
, (currentItem == m_ui->feedListWidget->stickyUnreadItem())
, m_rssFilter->text());
}
void RSSWidget::on_markReadButton_clicked()
@@ -641,6 +651,14 @@ void RSSWidget::handleUnreadCountChanged()
emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
}
void RSSWidget::handleRSSFilterTextChanged(const QString &newFilter)
{
QTreeWidgetItem *currentItem = m_ui->feedListWidget->currentItem();
m_ui->articleListWidget->setRSSItem(m_ui->feedListWidget->getRSSItem(currentItem)
, (currentItem == m_ui->feedListWidget->stickyUnreadItem())
, newFilter);
}
bool RSSWidget::eventFilter(QObject *obj, QEvent *event)
{
if ((obj == m_ui->textBrowser) && (event->type() == QEvent::PaletteChange))

View File

@@ -34,6 +34,7 @@
#include "gui/guiapplicationcomponent.h"
class LineEdit;
class QListWidgetItem;
class QTreeWidgetItem;
@@ -85,10 +86,12 @@ private slots:
void on_rssDownloaderBtn_clicked();
void handleSessionProcessingStateChanged(bool enabled);
void handleUnreadCountChanged();
void handleRSSFilterTextChanged(const QString &newFilter);
private:
bool eventFilter(QObject *obj, QEvent *event) override;
void renderArticle(const RSS::Article *article) const;
Ui::RSSWidget *m_ui = nullptr;
LineEdit *m_rssFilter = nullptr;
};

View File

@@ -89,10 +89,32 @@
padding: 3px 6px;
}
#rssButtonBar input {
background-color: var(--color-background-default);
background-image: url("../images/edit-find.svg");
background-position: 2px;
background-repeat: no-repeat;
background-size: 1.5em;
border: 1px solid var(--color-border-default);
border-radius: 3px;
min-width: 170px;
padding: 2px 2px 2px 25px;
}
#rssButtonBar div {
display: inline-block;
vertical-align: top;
}
#rssButtonBar button img {
margin: 0 5px -3px 0;
}
#rssFilterToolbar {
float: right;
margin-right: 5px;
}
#rssContentView table {
width: 100%;
}
@@ -120,9 +142,12 @@
<button type="button" id="updateAllButton" onclick="qBittorrent.Rss.refreshAllFeeds()">
<img alt="QBT_TR(Update all)QBT_TR[CONTEXT=RSSWidget]" src="images/view-refresh.svg" width="16" height="16">QBT_TR(Update all)QBT_TR[CONTEXT=RSSWidget]
</button>
<button type="button" id="rssDownloaderButton" class="alignRight" onclick="qBittorrent.Rss.openRssDownloader()">
<img alt="QBT_TR(RSS Downloader...)QBT_TR[CONTEXT=RSSWidget]" src="images/downloading.svg" width="16" height="16">QBT_TR(RSS Downloader...)QBT_TR[CONTEXT=RSSWidget]
</button>
<div id="rssFilterToolbar" class="alignRight">
<input type="search" id="rssFilterInput" placeholder="QBT_TR(Filter feed items...)QBT_TR[CONTEXT=MainWindow]" aria-label="QBT_TR(Filter feed items...)QBT_TR[CONTEXT=MainWindow]" autocorrect="off" autocapitalize="none">
<button type="button" id="rssDownloaderButton" onclick="qBittorrent.Rss.openRssDownloader()">
<img alt="QBT_TR(RSS Downloader...)QBT_TR[CONTEXT=RSSWidget]" src="images/downloading.svg" width="16" height="16">QBT_TR(RSS Downloader...)QBT_TR[CONTEXT=RSSWidget]
</button>
</div>
</div>
</div>
<div id="rssContentView">
@@ -277,6 +302,17 @@
rssFeedContextMenu.updateMenuItems();
}
});
document.getElementById("rssFilterInput").addEventListener("input", (event) => {
const rowId = rssFeedTable.selectedRows[0];
let path = "";
for (const row of rssFeedTable.getRowValues()) {
if (row.rowId === rowId) {
path = row.full_data.dataPath;
break;
}
}
qBittorrent.Rss.showRssFeed(path);
});
document.getElementById("CopyFeedURL").addEventListener("click", async (event) => {
let joined = "";
@@ -406,6 +442,12 @@
if (path === "")
visibleArticles = visibleArticles.filter((a) => !a.isRead);
const rssFilterInput = document.getElementById("rssFilterInput");
if (rssFilterInput.value.length > 0) {
const lowerFilter = rssFilterInput.value.toLowerCase();
visibleArticles = visibleArticles.filter((a) => a.title.toLowerCase().includes(lowerFilter));
}
let rowID = -1;
visibleArticles.sort((e1, e2) => new Date(e2.date) - new Date(e1.date))
.each((torrentEntry) => {