mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-08 08:32:31 -06:00
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:
@@ -64,7 +64,7 @@ QListWidgetItem *ArticleListWidget::mapRSSArticle(RSS::Article *rssArticle) cons
|
|||||||
return m_rssArticleToListItemMapping.value(rssArticle);
|
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 the list first
|
||||||
clear();
|
clear();
|
||||||
@@ -82,7 +82,7 @@ void ArticleListWidget::setRSSItem(RSS::Item *rssItem, bool unreadOnly)
|
|||||||
|
|
||||||
for (auto *article : asConst(rssItem->articles()))
|
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);
|
auto *item = createItem(article);
|
||||||
addItem(item);
|
addItem(item);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public:
|
|||||||
RSS::Article *getRSSArticle(QListWidgetItem *item) const;
|
RSS::Article *getRSSArticle(QListWidgetItem *item) const;
|
||||||
QListWidgetItem *mapRSSArticle(RSS::Article *rssArticle) 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:
|
private slots:
|
||||||
void handleArticleAdded(RSS::Article *rssArticle);
|
void handleArticleAdded(RSS::Article *rssArticle);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
#include "base/rss/rss_session.h"
|
#include "base/rss/rss_session.h"
|
||||||
#include "gui/autoexpandabledialog.h"
|
#include "gui/autoexpandabledialog.h"
|
||||||
#include "gui/interfaces/iguiapplication.h"
|
#include "gui/interfaces/iguiapplication.h"
|
||||||
|
#include "gui/lineedit.h"
|
||||||
#include "gui/uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
#include "gui/utils/keysequence.h"
|
#include "gui/utils/keysequence.h"
|
||||||
#include "articlelistwidget.h"
|
#include "articlelistwidget.h"
|
||||||
@@ -108,6 +109,7 @@ namespace
|
|||||||
RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
|
RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
|
||||||
: GUIApplicationComponent(app, parent)
|
: GUIApplicationComponent(app, parent)
|
||||||
, m_ui {new Ui::RSSWidget}
|
, m_ui {new Ui::RSSWidget}
|
||||||
|
, m_rssFilter {new LineEdit(this)}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(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));
|
m_ui->rssDownloaderBtn->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_s, u"download"_s));
|
||||||
#endif
|
#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::customContextMenuRequested, this, &RSSWidget::displayItemsListMenu);
|
||||||
connect(m_ui->articleListWidget, &ArticleListWidget::currentItemChanged, this, &RSSWidget::handleCurrentArticleItemChanged);
|
connect(m_ui->articleListWidget, &ArticleListWidget::currentItemChanged, this, &RSSWidget::handleCurrentArticleItemChanged);
|
||||||
connect(m_ui->articleListWidget, &ArticleListWidget::itemDoubleClicked, this, &RSSWidget::downloadSelectedTorrents);
|
connect(m_ui->articleListWidget, &ArticleListWidget::itemDoubleClicked, this, &RSSWidget::downloadSelectedTorrents);
|
||||||
@@ -568,7 +577,8 @@ void RSSWidget::copySelectedFeedsURL()
|
|||||||
void RSSWidget::handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem)
|
void RSSWidget::handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem)
|
||||||
{
|
{
|
||||||
m_ui->articleListWidget->setRSSItem(m_ui->feedListWidget->getRSSItem(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()
|
void RSSWidget::on_markReadButton_clicked()
|
||||||
@@ -641,6 +651,14 @@ void RSSWidget::handleUnreadCountChanged()
|
|||||||
emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
|
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)
|
bool RSSWidget::eventFilter(QObject *obj, QEvent *event)
|
||||||
{
|
{
|
||||||
if ((obj == m_ui->textBrowser) && (event->type() == QEvent::PaletteChange))
|
if ((obj == m_ui->textBrowser) && (event->type() == QEvent::PaletteChange))
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
#include "gui/guiapplicationcomponent.h"
|
#include "gui/guiapplicationcomponent.h"
|
||||||
|
|
||||||
|
class LineEdit;
|
||||||
class QListWidgetItem;
|
class QListWidgetItem;
|
||||||
class QTreeWidgetItem;
|
class QTreeWidgetItem;
|
||||||
|
|
||||||
@@ -85,10 +86,12 @@ private slots:
|
|||||||
void on_rssDownloaderBtn_clicked();
|
void on_rssDownloaderBtn_clicked();
|
||||||
void handleSessionProcessingStateChanged(bool enabled);
|
void handleSessionProcessingStateChanged(bool enabled);
|
||||||
void handleUnreadCountChanged();
|
void handleUnreadCountChanged();
|
||||||
|
void handleRSSFilterTextChanged(const QString &newFilter);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
void renderArticle(const RSS::Article *article) const;
|
void renderArticle(const RSS::Article *article) const;
|
||||||
|
|
||||||
Ui::RSSWidget *m_ui = nullptr;
|
Ui::RSSWidget *m_ui = nullptr;
|
||||||
|
LineEdit *m_rssFilter = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -89,10 +89,32 @@
|
|||||||
padding: 3px 6px;
|
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 {
|
#rssButtonBar button img {
|
||||||
margin: 0 5px -3px 0;
|
margin: 0 5px -3px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#rssFilterToolbar {
|
||||||
|
float: right;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
#rssContentView table {
|
#rssContentView table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -120,9 +142,12 @@
|
|||||||
<button type="button" id="updateAllButton" onclick="qBittorrent.Rss.refreshAllFeeds()">
|
<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]
|
<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>
|
||||||
<button type="button" id="rssDownloaderButton" class="alignRight" onclick="qBittorrent.Rss.openRssDownloader()">
|
<div id="rssFilterToolbar" class="alignRight">
|
||||||
<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]
|
<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>
|
<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>
|
</div>
|
||||||
<div id="rssContentView">
|
<div id="rssContentView">
|
||||||
@@ -277,6 +302,17 @@
|
|||||||
rssFeedContextMenu.updateMenuItems();
|
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) => {
|
document.getElementById("CopyFeedURL").addEventListener("click", async (event) => {
|
||||||
let joined = "";
|
let joined = "";
|
||||||
@@ -406,6 +442,12 @@
|
|||||||
if (path === "")
|
if (path === "")
|
||||||
visibleArticles = visibleArticles.filter((a) => !a.isRead);
|
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;
|
let rowID = -1;
|
||||||
visibleArticles.sort((e1, e2) => new Date(e2.date) - new Date(e1.date))
|
visibleArticles.sort((e1, e2) => new Date(e2.date) - new Date(e1.date))
|
||||||
.each((torrentEntry) => {
|
.each((torrentEntry) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user