diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index c69521035..3084be741 100644 --- a/resources/desktop/com.github.rssguard.appdata.xml +++ b/resources/desktop/com.github.rssguard.appdata.xml @@ -26,7 +26,7 @@ https://github.com/sponsors/martinrotter - + none diff --git a/resources/graphics/misc/image-placeholder-error.png b/resources/graphics/misc/image-placeholder-error.png new file mode 100755 index 000000000..279c1750a Binary files /dev/null and b/resources/graphics/misc/image-placeholder-error.png differ diff --git a/resources/rssguard.qrc b/resources/rssguard.qrc index 85dc7bf5f..03cd65c7b 100644 --- a/resources/rssguard.qrc +++ b/resources/rssguard.qrc @@ -36,6 +36,7 @@ graphics/misc/gmail.png graphics/misc/google.png graphics/misc/image-placeholder.png + graphics/misc/image-placeholder-error.png graphics/misc/inoreader.png graphics/misc/newsblur.png graphics/misc/nextcloud.png diff --git a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp index def199771..83705b888 100644 --- a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp +++ b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp @@ -2,6 +2,7 @@ #include "gui/webviewers/qtextbrowser/textbrowserviewer.h" +#include "3rd-party/boolinq/boolinq.h" #include "gui/dialogs/formmain.h" #include "gui/messagebox.h" #include "gui/webbrowser.h" @@ -20,7 +21,8 @@ TextBrowserViewer::TextBrowserViewer(QWidget* parent) : QTextBrowser(parent), m_resourcesEnabled(false), m_resourceDownloader(new Downloader(this)), - m_placeholderImage(qApp->icons()->miscPixmap("image-placeholder")), m_downloader(new Downloader(this)), + m_placeholderImage(qApp->icons()->miscPixmap("image-placeholder")), + m_placeholderImageError(qApp->icons()->miscPixmap("image-placeholder-error")), m_downloader(new Downloader(this)), m_document(new TextBrowserDocument(this)) { setAutoFillBackground(true); setFrameShape(QFrame::Shape::NoFrame); @@ -29,9 +31,6 @@ TextBrowserViewer::TextBrowserViewer(QWidget* parent) setOpenLinks(false); viewport()->setAutoFillBackground(true); - m_resourceTimer.setSingleShot(false); - m_resourceTimer.setInterval(100); - setResourcesEnabled(qApp->settings()->value(GROUP(Messages), SETTING(Messages::ShowResourcesInArticles)).toBool()); setDocument(m_document.data()); @@ -41,7 +40,6 @@ TextBrowserViewer::TextBrowserViewer(QWidget* parent) setVerticalScrollBarPosition(scr); }); - connect(&m_resourceTimer, &QTimer::timeout, this, &TextBrowserViewer::reloadHtmlDelayed); connect(m_resourceDownloader.data(), &Downloader::completed, this, &TextBrowserViewer::resourceDownloaded); connect(this, &QTextBrowser::anchorClicked, this, &TextBrowserViewer::onAnchorClicked); connect(this, QOverload::of(&QTextBrowser::highlighted), this, &TextBrowserViewer::linkMouseHighlighted); @@ -59,39 +57,37 @@ QVariant TextBrowserViewer::loadOneResource(int type, const QUrl& name) { return {}; } - if (!m_resourcesEnabled) { + if (!m_resourcesEnabled || !m_loadedResources.contains(name)) { // Resources are not enabled. return m_placeholderImage; } - if (m_loadedResources.contains(name)) { - // Resources are enabled and we already have the resource. - return QImage::fromData(m_loadedResources.value(name)); + // Resources are enabled and we already have the resource. + QByteArray resource_data = m_loadedResources.value(name); + + if (resource_data.isEmpty()) { + return m_placeholderImageError; } else { - // Resources are not enabled and we need to download the resource. - if (!m_neededResources.contains(name) && m_resourceTimer.isActive()) { - m_neededResources.append(name); - m_resourceTimer.start(); - } - - return m_placeholderImage; + return QImage::fromData(m_loadedResources.value(name)); } } -QPair TextBrowserViewer::prepareHtmlForMessage(const QList& messages, - RootItem* selected_item) const { - QString html; +PreparedHtml TextBrowserViewer::prepareHtmlForMessage(const QList& messages, RootItem* selected_item) const { + PreparedHtml html; for (const Message& message : messages) { - html += QString("

%1

").arg(message.m_title); - if (!message.m_url.isEmpty()) { - html += QString("[url] %1
").arg(message.m_url); + html.m_html += QSL("

%1

").arg(message.m_title, message.m_url); + } + else { + html.m_html += QSL("

%1

").arg(message.m_title); } + html.m_html += QSL("
"); + for (const Enclosure& enc : message.m_enclosures) { - html += QString("[%2] %1
").arg(enc.m_url, enc.m_mimeType); + html.m_html += QString("[%2] %1
").arg(enc.m_url, enc.m_mimeType); } static QRegularExpression img_tag_rgx("\\]*src\\s*=\\s*[\"\']([^\"\']*)[\"\'][^\\>]*\\>", @@ -102,8 +98,9 @@ QPair TextBrowserViewer::prepareHtmlForMessage(const QList[%1] %2").arg(tr("image"), match.captured(1)); + pictures_html += QString("
[%1] %2").arg(tr("image"), captured_url); } QString cnts = message.m_contents; @@ -111,12 +108,14 @@ QPair TextBrowserViewer::prepareHtmlForMessage(const QListsettings()->value(GROUP(Messages), SETTING(Messages::MessageHeadImageHeight)).toInt(); // Fixup all "img" tags. - html += cnts.replace(img_tag_rgx, - QSL("") - .arg(forced_img_size <= 0 ? QString() : QString::number(forced_img_size))); - html += pictures_html; + html.m_html += cnts.replace(img_tag_rgx, + QSL("") + .arg(forced_img_size <= 0 ? QString() : QString::number(forced_img_size))); + html.m_html += pictures_html; } + html.m_html += QSL("
"); + QColor a_color = qApp->skins()->currentSkin().colorForModel(SkinEnums::PaletteColors::FgInteresting).value(); if (!a_color.isValid()) { @@ -138,14 +137,17 @@ QPair TextBrowserViewer::prepareHtmlForMessage(const QList" - "" - "%1" - "") - .arg(html, a_color.name()), - base_url}; + // Final html, with replaced link colors. + html.m_html = QSL("" + "" + "%1" + "") + .arg(html.m_html, a_color.name()); + html.m_baseUrl = base_url; + + return html; } void TextBrowserViewer::bindToBrowser(WebBrowser* browser) { @@ -203,6 +205,7 @@ BlockingResult TextBrowserViewer::blockedWithAdblock(const QUrl& url) { void TextBrowserViewer::setUrl(const QUrl& url) { emit loadingStarted(); + QString html_str; QUrl nonconst_url = url; bool is_error = false; @@ -262,7 +265,7 @@ void TextBrowserViewer::loadMessages(const QList& messages, RootItem* r auto html_messages = prepareHtmlForMessage(messages, root); - setHtml(html_messages.first, html_messages.second); + setHtml(html_messages.m_html, html_messages.m_baseUrl); emit loadingFinished(true); } @@ -428,12 +431,37 @@ void TextBrowserViewer::onAnchorClicked(const QUrl& url) { } void TextBrowserViewer::setHtml(const QString& html, const QUrl& base_url) { - m_resourceTimer.stop(); - m_neededResources.clear(); - m_resourceTimer.start(); + setVerticalScrollBarPosition(0.0); + + static QRegularExpression img_tag_rgx("\\]*src\\s*=\\s*[\"\']([^\"\']*)[\"\'][^\\>]*\\>", + QRegularExpression::PatternOption::CaseInsensitiveOption | + QRegularExpression::PatternOption::InvertedGreedinessOption); + QRegularExpressionMatchIterator i = img_tag_rgx.globalMatch(html); + QList found_resources; + + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + auto captured_url = match.captured(1); + + if (!found_resources.contains(captured_url)) { + found_resources.append(captured_url); + } + } + + auto really_needed_resources = boolinq::from(found_resources) + .where([this](const QUrl& res) { + return !m_loadedResources.contains(res); + }) + .toStdList(); + + m_neededResources = FROM_STD_LIST(QList, really_needed_resources); setHtmlPrivate(html, base_url); + if (!m_neededResources.isEmpty()) { + QTimer::singleShot(20, this, &TextBrowserViewer::reloadHtmlDelayed); + } + // TODO: implement RTL for viewers somehow? /* auto to = document()->defaultTextOption(); @@ -464,10 +492,6 @@ QVariant TextBrowserDocument::loadResource(int type, const QUrl& name) { } void TextBrowserViewer::reloadHtmlDelayed() { - // Timer has elapsed, we do not wait for other resources, - // we download what we know about. - m_resourceTimer.stop(); - if (!m_neededResources.isEmpty()) { downloadNextNeededResource(); } @@ -479,7 +503,9 @@ void TextBrowserViewer::downloadNextNeededResource() { emit reloadDocument(); } else { - m_resourceDownloader.data()->manipulateData(m_neededResources.takeFirst().toString(), + QUrl res = m_neededResources.takeFirst(); + + m_resourceDownloader.data()->manipulateData(res.toString(), QNetworkAccessManager::Operation::GetOperation, {}, 5000); @@ -487,9 +513,12 @@ void TextBrowserViewer::downloadNextNeededResource() { } void TextBrowserViewer::resourceDownloaded(const QUrl& url, QNetworkReply::NetworkError status, QByteArray contents) { - if (status == QNetworkReply::NetworkError::NoError && !m_loadedResources.contains(url)) { + if (status == QNetworkReply::NetworkError::NoError) { m_loadedResources.insert(url, contents); } + else { + m_loadedResources.insert(url, {}); + } downloadNextNeededResource(); } diff --git a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.h b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.h index ca7848c55..8422b6563 100644 --- a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.h +++ b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.h @@ -34,6 +34,11 @@ class TextBrowserDocument : public QTextDocument { QPointer m_viewer; }; +struct PreparedHtml { + QString m_html; + QUrl m_baseUrl; +}; + class TextBrowserViewer : public QTextBrowser, public WebViewer { Q_OBJECT Q_INTERFACES(WebViewer) @@ -85,11 +90,11 @@ class TextBrowserViewer : public QTextBrowser, public WebViewer { private: bool m_resourcesEnabled; - QTimer m_resourceTimer; QList m_neededResources; QScopedPointer m_resourceDownloader; QMap m_loadedResources; QPixmap m_placeholderImage; + QPixmap m_placeholderImageError; signals: void pageTitleChanged(const QString& new_title); @@ -107,7 +112,7 @@ class TextBrowserViewer : public QTextBrowser, public WebViewer { BlockingResult blockedWithAdblock(const QUrl& url); QScopedPointer m_downloader; - QPair prepareHtmlForMessage(const QList& messages, RootItem* selected_item) const; + PreparedHtml prepareHtmlForMessage(const QList& messages, RootItem* selected_item) const; private: QUrl m_currentUrl; diff --git a/src/librssguard/network-web/downloader.cpp b/src/librssguard/network-web/downloader.cpp index 66cdfab74..032294bc5 100644 --- a/src/librssguard/network-web/downloader.cpp +++ b/src/librssguard/network-web/downloader.cpp @@ -211,7 +211,7 @@ void Downloader::finished() { m_inputMultipartData->deleteLater(); } - emit completed(reply->url(), m_lastOutputError, m_lastOutputData); + emit completed(reply->request().url(), m_lastOutputError, m_lastOutputData); } }