From d7468dc76947ee406e18d000ada1946f0e0ca796 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Mon, 29 Nov 2021 07:56:21 +0100 Subject: [PATCH 01/19] missing icons in article filters dialog --- resources/desktop/com.github.rssguard.appdata.xml | 2 +- resources/icons.qrc | 6 ++++++ src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index c99de6204..de4a88404 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/icons.qrc b/resources/icons.qrc index c01757f41..e762e973a 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -24,6 +24,8 @@ ./graphics/Breeze/actions/22/edit-clear.svg ./graphics/Breeze/actions/22/edit-copy.svg ./graphics/Breeze/actions/32/edit-reset.svg + ./graphics/Breeze/actions/22/edit-select-all.svg + ./graphics/Breeze/actions/22/edit-select-none.svg ./graphics/Breeze/places/96/folder.svg ./graphics/Breeze/actions/22/format-indent-more.svg ./graphics/Breeze/actions/22/format-justify-fill.svg @@ -89,6 +91,8 @@ ./graphics/Breeze Dark/actions/22/edit-clear.svg ./graphics/Breeze Dark/actions/22/edit-copy.svg ./graphics/Breeze Dark/actions/32/edit-reset.svg + ./graphics/Breeze Dark/actions/22/edit-select-all.svg + ./graphics/Breeze Dark/actions/22/edit-select-none.svg ./graphics/Breeze Dark/places/96/folder.svg ./graphics/Breeze Dark/actions/22/format-indent-more.svg ./graphics/Breeze Dark/actions/22/format-justify-fill.svg @@ -153,6 +157,7 @@ ./graphics/Faenza/actions/64/down.png ./graphics/Faenza/actions/64/edit-clear.png ./graphics/Faenza/actions/64/edit-copy.png + ./graphics/Faenza/actions/64/edit-select-all.png ./graphics/Faenza/emblems/64/emblem-downloads.png ./graphics/Faenza/emblems/64/emblem-system.png ./graphics/Faenza/places/64/folder.png @@ -223,6 +228,7 @@ ./graphics/Numix/22/actions/download.svg ./graphics/Numix/22/actions/edit-clear.svg ./graphics/Numix/22/actions/edit-copy.svg + ./graphics/Numix/22/actions/edit-select-all.svg ./graphics/Numix/22/emblems/emblem-downloads.svg ./graphics/Numix/22/emblems/emblem-system.svg ./graphics/Numix/22/places/folder.svg diff --git a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp index 21466e898..8261e5754 100644 --- a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp +++ b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp @@ -36,8 +36,8 @@ FormMessageFiltersManager::FormMessageFiltersManager(FeedReader* reader, const Q m_ui.m_treeFeeds->setIndentation(FEEDS_VIEW_INDENTATION); m_ui.m_treeFeeds->setModel(m_feedsModel); - m_ui.m_btnCheckAll->setIcon(qApp->icons()->fromTheme(QSL("dialog-yes"))); - m_ui.m_btnUncheckAll->setIcon(qApp->icons()->fromTheme(QSL("dialog-no"))); + m_ui.m_btnCheckAll->setIcon(qApp->icons()->fromTheme(QSL("dialog-yes"), QSL("edit-select-all"))); + m_ui.m_btnUncheckAll->setIcon(qApp->icons()->fromTheme(QSL("dialog-no"), QSL("edit-select-none"))); m_ui.m_btnAddNew->setIcon(qApp->icons()->fromTheme(QSL("list-add"))); m_ui.m_btnRemoveSelected->setIcon(qApp->icons()->fromTheme(QSL("list-remove"))); m_ui.m_btnBeautify->setIcon(qApp->icons()->fromTheme(QSL("format-justify-fill"))); From 3b7be451d5b4b2519e0dd6e8a9e07892c7d399fa Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Mon, 29 Nov 2021 08:10:33 +0100 Subject: [PATCH 02/19] lang sync, better sync script --- localization/rssguard_en_GB.ts | 8 +- localization/rssguard_nl.ts | 433 +++++++++++----------- resources/scripts/update-localizations.sh | 12 +- 3 files changed, 233 insertions(+), 220 deletions(-) diff --git a/localization/rssguard_en_GB.ts b/localization/rssguard_en_GB.ts index e49ac9f86..ca01c1dbe 100644 --- a/localization/rssguard_en_GB.ts +++ b/localization/rssguard_en_GB.ts @@ -229,11 +229,11 @@ version by clicking this popup notification. ColorToolButton Click me to change color! - + Click me to change colour! Select new color - + Select new colour @@ -3607,7 +3607,7 @@ List of supported readers: OK-ish color - + OK-ish colour @@ -4446,7 +4446,7 @@ Authors of this application are NOT responsible for lost data. Fetch color from activated skin - + Fetch colour from activated skin diff --git a/localization/rssguard_nl.ts b/localization/rssguard_nl.ts index d0d5a7586..e4893f36c 100644 --- a/localization/rssguard_nl.ts +++ b/localization/rssguard_nl.ts @@ -46,7 +46,7 @@ There is some error in AdBlock component and it cannot be enabled. Check error message below (or application debug log) for more information. - Er is een fout in de AdBlock-component en deze kan niet worden ingeschakeld. Controleer het onderstaande foutbericht (of het foutopsporingslogboek van de toepassing) voor meer informatie. + Er is een fout in de AdBlock-component en deze kan niet worden ingeschakeld. Controleer het onderstaande foutbericht (of het foutopsporingslogboek van de toepassing) voor meer informatie. OK! @@ -66,15 +66,15 @@ Fout: %1 No additional info. - + Geen aanvullende informatie. It seems your AdBlock runs fine, but wait few seconds to be sure. - + Het lijkt erop dat AdBlock goed werkt, maar wacht een paar seconden om zeker te zijn. There is error, check application log for more details and head to online documentation. Also make sure that Node.js is installed. - + Er is een fout opgetreden, controleer het toepassingslogboek voor meer details en ga naar de online documentatie. Zorg er ook voor dat Node.js is geïnstalleerd. @@ -127,11 +127,11 @@ Fout: %1 Unread articles fetched - + Ongelezen artikelen opgehaald Go to changelog - + Ga naar changelog AdBlock needs to be configured @@ -143,23 +143,23 @@ Fout: %1 Configure now - + Nu configureren RSS Guard has Discord server! - + RSS Guard heeft Discord server! You can visit it now! Click me! - + Je kunt het nu bezoeken! Klik hier! Go to Discord! - + Ga naar Discord! Welcome - + Welkom Welcome to %1. @@ -172,7 +172,7 @@ Om te zien wat er NIEUW is in deze versie: klik op deze melding. Already running - + Is al gestart @@ -226,7 +226,7 @@ Om te zien wat er NIEUW is in deze versie: klik op deze melding. BaseLineEdit Show/hide the password - + Toon/verberg het paswoord @@ -268,7 +268,7 @@ Om te zien wat er NIEUW is in deze versie: klik op deze melding. Removing old articles... - Oude artikelen verwijderd + Oude artikelen verwijderen... Old articles purged... @@ -314,7 +314,7 @@ Om te zien wat er NIEUW is in deze versie: klik op deze melding. Not supported by account - + Niet ondersteund door account @@ -407,7 +407,7 @@ Klik hier om de bovenliggende map te openen. Open folder - + Map openen @@ -498,16 +498,16 @@ Status: %3 uses global settings (%n minute(s) to next auto-fetch of articles) Describes feed auto-update status. - + gebruikt globale instellingen (%n minuut naar de volgende automatische fetch van artikelen)gebruikt globalee instellingen (%n minuten naar de volgende automatische fetch van artikelen) uses global settings (global auto-fetching of articles is disabled) - + gebruik globale instellingen (globale auto-halende artikelen zijn uitgeschakeld) uses specific settings (%n minute(s) to next auto-fetching of new articles) Describes feed auto-update status. - + Gebruikt specifieke instellingen (%n minuut naar de volgende auto-halende artikel)Gebruikt specifieke instellingen (%n minuten naar de volgende auto-halende artikelen) has new articles @@ -515,11 +515,11 @@ Status: %3 parsing error - + fout ontleden error - + fout @@ -537,19 +537,19 @@ Status: %3 FeedReader Starting auto-download of some feeds' articles - + Automatisch downloaden van sommige feeds-artikelen starten I will auto-download new articles for %n feed(s). - + automatisch nieuwe artikelen downloaden voor %n feed.automatisch nieuwe artikelen downloaden voor %n feeds. Cannot fetch articles at this point - + Kan op dit moment geen artikelen ophalen You cannot fetch new articles now because another critical operation is ongoing. - + U kunt nu geen nieuwe artikelen ophalen omdat er een andere kritieke bewerking aan de gang is. @@ -644,7 +644,7 @@ Status: %3 Only download newest X articles per feed - + Download alleen de nieuwste X-artikelen per feed Download unread articles only @@ -652,7 +652,7 @@ Status: %3 Beware of downloading too many articles, because Feedly permanently caches ALL articles of the feed, so you might end up with thousands of articles which you will never read anyway. - + Pas op voor het downloaden van te veel artikelen, omdat Feedly ALLE artikelen van de feed permanent in de cache opslaat, dus u kunt eindigen met duizenden artikelen die u toch nooit zult lezen. @@ -675,7 +675,7 @@ Status: %3 Login - + Inloggen @@ -809,11 +809,11 @@ of omdat deze functie nog niet is geïmplementeerd. Context menu for important articles - + Contextmenu voor belangrijke artikelen Not supported by account - + Niet ondersteund door een account @@ -900,11 +900,11 @@ of omdat deze functie nog niet is geïmplementeerd. Database location - + Database locatie GNU LGPL License (applies to Breeze source code) - + GNU LGPL-licentie (van toepassing op breeze broncode) @@ -1230,23 +1230,23 @@ of omdat deze functie nog niet is geïmplementeerd. Optimize database file - + Databasebestand optimaliseren Remove all read articles - + Verwijder alle gelezen artikelen Remove all articles from recycle bin - + Verwijder alle gelezen artikelen uit prullebak Remove all articles older than - + Verwijder alle artikelen ouder dan Remove all starred articles - + Verwijder alle artikelen met ster @@ -1326,19 +1326,19 @@ of omdat deze functie nog niet is geïmplementeerd. Fetch articles using global interval - + Ophalen van artikelen met behulp van de globale interval Fetch articles every - + Ophalen artikelen elke Disable auto-fetching of articles - + Auto-ophalende artikelen uitschakelen Cannot save feed properties - + Kan geen feed-eigenschappen opslaan @@ -1597,11 +1597,11 @@ of omdat deze functie nog niet is geïmplementeerd. Fetch &selected - Ophalen &geselecteerd + Ophalen &geselecteerd Fetch selected feeds - + Ophalen geselecteerde feeds Mark articles &read @@ -1609,31 +1609,31 @@ of omdat deze functie nog niet is geïmplementeerd. Mark selected articles read - + Markeer geselecteerde artikelen als gelezen Mark articles &unread - + Markeer artikelen als &ongelezen Mark selected articles unread - + Markeer geselecteerde artikelen als ongelezen Switch &importance - + Wijzig &belang Switch importance of selected articles - + Belang van geselecteerde artikelen wisselen &Mark selected item read - + &Markeer geselecteerde artikelen als gelezen &Mark selected item unread - + &Markeer geselecteerde artikelen als ongelezen &Delete articles @@ -1641,23 +1641,23 @@ of omdat deze functie nog niet is geïmplementeerd. &Clean selected item - + &Geselecteerd item opschonen Open in &external browser - + &Open in de externe browser Open selected articles in external browser - + Open geselecteerde artikelen in externe browser Open in &internal browser - + Open in &externe browser Open selected articles in internal browser - + Open geselecteerde artikelen in externe browser &Mark all read @@ -1665,7 +1665,7 @@ of omdat deze functie nog niet is geïmplementeerd. View selected item in &newspaper mode - + Geselecteerd item bekijken in &krantenmodus &Clean all @@ -1681,19 +1681,19 @@ of omdat deze functie nog niet is geïmplementeerd. &List headers - + &Lijstkoppen &Restore settings - + &Herstel instellingen &Backup settings - + &Backup instellingen Switch layout - + Van lay-out wisselen Send via e-mail @@ -1701,11 +1701,11 @@ of omdat deze functie nog niet is geïmplementeerd. Send selected articles via e-mail - + Zend geselecteerde artikelen via E-mail Show unread items only - Toon alleen ongelezen items + Toon alleen ongelezen items &Add account @@ -1713,43 +1713,43 @@ of omdat deze functie nog niet is geïmplementeerd. &Restore articles - + &Artikelen herstellen Next &unread article - + Volgende &ongelezen artikel Stop ongoing fetching - + Stop met ophalen New browser tab - + Nieuwe browser tab &Enable article preview - + &Artikelvoorbeeld inschakelen &Copy URLs of selected item - + &Kopieer URL's van geselecteerd item Show &unread articles only - + Alleen &ongelezen artikelen weergeven &Show tree expanders - + &Structuur uitbreidingen weergeven Fetch feeds with &custom auto-download policy - + Feeds ophalen met &aangepast beleid voor automatisch downloaden Automatically &expand item when selected - + Automatisch &item uitbreiden wanneer geselecteerd Close opened modal dialogs first. @@ -1757,7 +1757,7 @@ of omdat deze functie nog niet is geïmplementeerd. F&eeds - + F&eeds Art&icles @@ -1765,31 +1765,31 @@ of omdat deze functie nog niet is geïmplementeerd. &Web browser && tabs - + &Webbrowser &&tabbladen Ta&bs - + Tab&bladen Fetching common data - + Algemene gegevens ophalen Minimize (or hide) main window - + Minimaliseer (of verberg) het hoofdvenster Article &filters - + Artikelen &filters Close &current tab - + Sluit &huidige tabblad Close dialogs - + Dialogen sluiten &Next item @@ -1808,7 +1808,7 @@ of omdat deze functie nog niet is geïmplementeerd. &Check all - Alles sele&cteren + Alles &controleren &Uncheck all @@ -1920,30 +1920,33 @@ of omdat deze functie nog niet is geïmplementeerd. Existing articles - + Bestaande artikelen Sample article - + Voorbeeld artikelen Filter articles like this - + Filter artikelen zoals dit New article filter - + Nieuw artikel filter EXISTING articles filtering error: '%1'. - + BESTAANDE artikelen filterfout:'%1'. + Article will be %1. - + Artikel zal zijn %1. + + Output (modified) article is: @@ -1954,28 +1957,36 @@ of omdat deze functie nog niet is geïmplementeerd. Created on = '%6' Contents = '%7' RAW contents = '%8' - + Uitvoer (aangepast) artikel is: + Titel = '%1' + URL = '%2' + Auteur = '%3' + Is gelezen/belangrijk = '%4/%5' + Gemaakt op = '%6' + Inhoud = '%7' + RAW-inhoud = '%8' SAMPLE article filtering error: '%1'. - + VOORBEELD artikel filterfout: '%1'. + Article filters - + Artikel filters Article filter details - + Artikel filter details Title of article filter - + Naam van Artikel filter Your JavaScript-based article filtering logic - + Uw op JavaScript gebaseerde logica voor artikelfiltering @@ -2205,15 +2216,15 @@ Je moet handmatig herstarten. &Check all feeds - + &Controleer alle feeds &Uncheck all feeds - + &Alle feeds uitvinken Operation result - + Operatie resultaat @@ -2451,7 +2462,7 @@ Installeer het nu. Only download newest X articles per feed - + Download alleen de nieuwste X-artikelen per feed Download unread articles only @@ -2490,7 +2501,7 @@ Installeer het nu. Login - + Inloggen @@ -2626,7 +2637,7 @@ Logintoken verloopt: %2 Only download newest X articles per feed - + Download alleen de nieuwste X-artikelen per feed Download unread articles only @@ -2634,11 +2645,11 @@ Logintoken verloopt: %2 Intelligent synchronization algorithm - + Intelligent synchronisatie-algoritme Fetch articles newer than - + Ophalen artikelen nieuwer dan OAuth 2.0 settings @@ -2662,15 +2673,15 @@ Logintoken verloopt: %2 Some feeds might contain tens of thousands of articles and downloading all of them could take great amount of time, so sometimes it is good to download only certain amount of newest messages. - + Sommige feeds kunnen tienduizenden artikelen bevatten en het downloaden van al deze artikelen kan veel tijd kosten, dus soms is het goed om slechts een bepaald aantal nieuwste berichten te downloaden. If you select intelligent synchronization, then only not-yet-fetched or updated articles are downloaded. Network usage is greatly reduced and overall synchronization speed is greatly improved, but first feed fetching could be slow anyway if your feed contains huge number of articles. - + Als u intelligente synchronisatie selecteert, worden alleen nog niet opgehaalde of bijgewerkte artikelen gedownload. Het netwerkgebruik is aanzienlijk verminderd en de algehele synchronisatiesnelheid is aanzienlijk verbeterd, maar het ophalen van de eerste feed kan hoe dan ook traag zijn als uw feed een groot aantal artikelen bevat. There are some preconfigured OAuth tokens so you do not have to fill in your client ID/secret, but it is strongly recommended to obtain your own as preconfigured tokens have limited global usage quota. If you wish to use preconfigured tokens, simply leave all above fields to their default values even if they are empty. - + Er zijn enkele vooraf geconfigureerde OAuth-tokens, dus u hoeft uw client-ID/geheim niet in te vullen, maar het wordt sterk aanbevolen om uw eigen tokens te verkrijgen, aangezien vooraf geconfigureerde tokens een beperkt algemeen gebruiksquotum hebben. Als u vooraf geconfigureerde tokens wilt gebruiken, laat u alle bovenstaande velden gewoon op hun standaardwaarden staan, zelfs als ze leeg zijn. You have to fill in your client ID/secret and also fill in correct redirect URL. @@ -2713,7 +2724,7 @@ Logintoken verloopt: %2 GreaderNetwork login failed - + login mislukt Inoreader: authentication error @@ -2733,7 +2744,7 @@ Logintoken verloopt: %2 Login - + login @@ -2747,7 +2758,7 @@ Logintoken verloopt: %2 HelpSpoiler View more information on this - + Bekijk hier meer informatie over @@ -2765,11 +2776,11 @@ Logintoken verloopt: %2 ImportantNode Important articles - + belangrijke artikelen You can find all important articles here. - + Alle belangrijke artikelen vind je hier. @@ -2780,7 +2791,7 @@ Logintoken verloopt: %2 No labels found - + Geen labels gevonden @@ -2894,7 +2905,7 @@ Logintoken verloopt: %2 article - + artikel articles @@ -2905,15 +2916,15 @@ Logintoken verloopt: %2 MessagePreviewer Mark article read - + Markeer artikelen als gelezen Mark article unread - + Markeer artikelen als ongelezen Switch article importance - + Artikelbelang wijzigen @@ -3038,15 +3049,15 @@ Logintoken verloopt: %2 Loading of articles failed, maybe messages could not be downloaded. - + Het laden van artikelen is mislukt, mogelijk konden berichten niet worden gedownload. ID of the article. - + ID van het artikel. Is article read? - + Is artikel gelezen? Is article important? @@ -3058,23 +3069,23 @@ Logintoken verloopt: %2 Is article permanently deleted from recycle bin? - + Wordt het artikel permanent uit de prullenbak verwijderd? ID of feed which this article belongs to. - + ID van de feed waartoe dit artikel behoort. Title of the article. - Titel van artikel + Titel van artikel. Url of the article. - + URL van het artikel. Author of the article. - Schrijver van het artikel + Schrijver van het artikel. Creation date of the article. @@ -3082,35 +3093,35 @@ Logintoken verloopt: %2 Contents of the article. - + Inhoud van het artikel. Score of the article. - + Score van het artikel. Account ID of the article. - + Account-ID van het artikel. Custom ID of the article - + Aangepaste ID van het artikel Custom hash of the article. - + Aangepaste hash van het artikel. Custom ID of feed of the article. - + Aangepaste ID van de feed van het artikel. Indication of enclosures presence within the article. - + Vermelding van de aanwezigheid van bijlagen in het artikel. Loading of articles from item '%1' failed - + Laden van artikelen van item '%1' is mislukt @@ -3129,27 +3140,27 @@ Logintoken verloopt: %2 Article search box - + Artikel zoekvak Menu for highlighting articles - + Menu voor het markeren van artikelen Highlight unread articles - + Markeer ongelezen artikelen Highlight important articles - + Markeer belangrijke artikelen Display all articles - + Toon alle artikelen Article highlighter - + Artikel markeerstift @@ -3180,7 +3191,7 @@ Logintoken verloopt: %2 Context menu for articles - + Contextmenu voor artikelen @@ -3332,15 +3343,15 @@ Logintoken verloopt: %2 Show more articles (%n remaining) - + Toon artikel (%n resterende)Toon meer artikelen (%n resterende) Cannot show more articles - + Kan niet meer artikelen tonen Cannot show more articles because parent feed was removed. - + Kan niet meer artikelen weergeven omdat de oudere feed is verwijderd. @@ -3371,7 +3382,7 @@ Logintoken verloopt: %2 Login - + Inloggen @@ -3482,7 +3493,7 @@ Logintoken verloopt: %2 Only download newest X articles per feed - + Download alleen de nieuwste X-artikelen per feed articles @@ -3490,7 +3501,7 @@ Logintoken verloopt: %2 Force execution of server-side feeds update - + Forceer uitvoering van feeds-update aan serverzijde @@ -3565,53 +3576,55 @@ Feedly is een beveiligde ruimte waar u de onderwerpen en trends die voor u van b Cannot insert article filter, because current database cannot return last inserted row ID. - + Kan artikelfilter niet invoegen, omdat de huidige database de laatst ingevoegde rij-ID niet kan retourneren. Fetching articles right now - + Ben nu artikelen aan het ophalen Login data refreshed - + Inloggegevens vernieuwd New %1 version is available - + Nieuw %1 versie beschikbaar Miscellaneous events - + Diverse evenementen Unknown event - + Onbekende gebeurtenis New (unread) articles fetched - + Nieuwe (ongelezen) artikelen opgehaald XML problem: %1 - + XML probleem: %1 Google Reader API is used by many online RSS readers. List of supported readers: - + Google Reader API wordt door veel online RSS-lezers gebruikt. + +Lijst met ondersteunde lezers: Login failed - + login mislukt This service offers integration with standard online RSS/RDF/ATOM/JSON feeds and podcasts. - + Deze service biedt integratie met standaard online RSS/RDF/ATOM/JSON-feeds en podcasts. Simplistic Reddit client. - + Simplistische Reddit-client. interesting stuff @@ -3650,11 +3663,11 @@ List of supported readers: Recycle bin contains all deleted articles from all feeds. - + Prullenbak bevat alle verwijderde artikelen uit alle feeds. %n deleted article(s). - + %n verwijderde artikel.%n verwijderde artikelen. @@ -3685,7 +3698,7 @@ List of supported readers: Only download newest X articles per feed - + Download alleen de nieuwste X-artikelen per feed &Login @@ -3752,7 +3765,7 @@ List of supported readers: RedditCategory Subscriptions - + Abonnementen @@ -3763,7 +3776,7 @@ List of supported readers: Reddit: authentication error - + Reddit: fout bij inloggen Click this to login again. Error is: '%1' @@ -3771,11 +3784,11 @@ List of supported readers: Login - + Inloggen Reddit: authorization denied - + Reddit: inloggegevens niet geaccepteerd Click this to login again. @@ -3804,7 +3817,7 @@ Logintoken verloopt: %2 %n unread article(s). Tooltip for "unread" column of feed list. - + %n Ongelezen artikel.%n Ongelezen artikelen. @@ -3853,7 +3866,7 @@ Logintoken verloopt: %2 Synchronize article cache - + Synchroniseer artkel cache @@ -3991,19 +4004,19 @@ File filter for external e-mail selection dialog. Always open hyperlinks in external web browser - + Hyperlinks altijd in externe browser openen &Add tool - + &Gereedschap toevoegen &Edit selected tool - + &Bewerk geselekteerd gereedschap &Delete selected tool - + &Verwijder geselekteerd gereedschap @@ -4225,87 +4238,87 @@ Auteurs van RSS Guard zijn NIET verantwoordelijk voor verlies van gegevens. Remove all read articles from all feeds on application exit - + Verwijder alle gelezen artikelen uit alle feeds bij het afsluiten van de applicatie Display real icons of feeds in list of articles instead of read/unread icons - + Toon echte iconen van feeds in de lijst met artikelen in plaats van gelezen/ongelezen iconen Bring application window to front once article is opened in external web browser - + Breng het toepassingsvenster naar voren zodra het artikel is geopend in een externe webbrowser Article list font - + Lettertype van artikellijst Article browser font - + Artikel browser lettertype Feeds & articles - + Feeds & artikelen Auto-fetch articles for all feeds every - + Automatisch artikelen ophalen voor alle feeds elke Only auto-fetch articles if application is unfocused - + Automatisch artikelen alleen ophalen als de toepassing niet gefocust is Article count format in feed list - + Formaat aantal artikelen in feedlijst Enter format for count of articles displayed next to each feed/category in feed list. Use "%all" and "%unread" strings which are placeholders for the actual count of all (or unread) articles. - + Voer de indeling in voor het aantal artikelen dat wordt weergegeven naast elke feed/categorie in de feedlijst. Gebruik "%alles" en "%ongelezen teksten die tijdelijke aanduidingen zijn voor het daadwerkelijke aantal (of ongelezen) artikelen. Hide article counts if there are no unread articles - + Artikel tellingen verbergen als er geen ongelezen artikelen zijn Display tooltips for feeds and articles - + Knopinfo voor feeds en artikelen weergeven Keep article selection in the middle of the article list viewport - + Artikelselectie in het midden van de viewport van de artikellijst houden Fetch all articles on startup with initial delay of - + Haal alle artikelen op bij het opstarten met een initiële vertraging van Allow only basic keyboard shortcuts for feed/article list - + Sta alleen standaard sneltoetsen toe voor feed/artikellijst Display attached pictures directly in article - + Geef bijgevoegde foto's direct in artikel weer Use custom date/time format - + Aangepaste datum-/tijdnotatie gebruiken Feed list row height - + Rijhoogte feedlijst Article list row height - + Artikellijst rijhoogte Ignore changes in article body when new articles are being fetched - + Negeer wijzigingen in de hoofdtekst van het artikel wanneer nieuwe artikelen worden opgehaald Show only time for today articles - + Toon alleen tijd voor artikelen van vandaag @@ -4460,7 +4473,7 @@ Auteurs van RSS Guard zijn NIET verantwoordelijk voor verlies van gegevens. Tray area - + Systeemvak Toolbar for articles list @@ -4518,11 +4531,11 @@ Auteurs van RSS Guard zijn NIET verantwoordelijk voor verlies van gegevens. You must have "tray icon" activated to have balloon notifications working. - + U moet het "traypictogram" hebben geactiveerd om ballonmeldingen te laten werken. There are some built-in sounds. Just start typing ":" and they will show up. - + Er zijn enkele ingebouwde geluiden. Begin gewoon ":" te typen en ze zullen verschijnen. @@ -4555,7 +4568,7 @@ Auteurs van RSS Guard zijn NIET verantwoordelijk voor verlies van gegevens. Full path to your WAV sound file - + Volledig pad naar het WAV bestand &Browse @@ -4571,15 +4584,15 @@ Auteurs van RSS Guard zijn NIET verantwoordelijk voor verlies van gegevens. Select sound file - Selecteer geluidsbestand + Selecteer geluidsbestand \ Volume - + Volume WAV files (*.wav);;MP3 files (*.mp3) - + WAV bestanden (*.wav);;MP3 bestanden (*.mp3) @@ -4612,7 +4625,7 @@ Auteurs van RSS Guard zijn NIET verantwoordelijk voor verlies van gegevens. Cannot save category data - + Kan categoriegegevens niet opslaan @@ -4649,15 +4662,17 @@ Auteurs van RSS Guard zijn NIET verantwoordelijk voor verlies van gegevens. Encoding: %2 Type: %3 - + +Codering: %2 +Model: %3 Cannot save feed data - + Kan feed gegevens niet opslaan Cannot move feed - + Kan feed niet verplaatsen @@ -4982,7 +4997,7 @@ Type: %3 See new version info - + Zie nieuwe versie info @@ -5034,7 +5049,7 @@ Ongelezen nieuws: %2 Browse your feeds and articles - + Blader door uw feeds en artikelen @@ -5115,7 +5130,7 @@ Ongelezen nieuws: %2 Close dialogs - + Dialogen sluiten @@ -5206,7 +5221,7 @@ Ongelezen nieuws: %2 Network error: '%1'. - Netwerkfout: '%1' + Netwerkfout: '%1'. Network error, have you entered correct Tiny Tiny RSS API endpoint and password? @@ -5266,11 +5281,11 @@ Ongelezen nieuws: %2 Only download newest X articles per feed - + Download alleen de nieuwste X-artikelen per feed Force execution of server-side feeds update - + Uitvoering van feeds-update aan serverzijde forceren @@ -5289,7 +5304,7 @@ Ongelezen nieuws: %2 Full feed URL including scheme - + Volledige feed-URL inclusief schema Provide URL for your feed. @@ -5468,11 +5483,11 @@ Laatste login: %4 Navigate to website manually - + Navigeer handmatig naar de website %1 was unable to launch your web browser with the given URL, you need to open the below website URL in your web browser manually. - + %1 kon uw webbrowser niet starten met de opgegeven URL, u moet de onderstaande website-URL handmatig in uw webbrowser openen. diff --git a/resources/scripts/update-localizations.sh b/resources/scripts/update-localizations.sh index 87d6e0b62..eaa88f7c7 100755 --- a/resources/scripts/update-localizations.sh +++ b/resources/scripts/update-localizations.sh @@ -1,16 +1,16 @@ #!/bin/bash # Transka executable. -TRANSKA=./transka +TRANSKA="$(dirname "$0")/transka/transka" # Get credentials. -read -p "Username: " USERNAME +read -e -p "Username: " -i "martinrotter" USERNAME read -p "Password: " PASSWORD # Setup parameters. -RESOURCE=../../../localization/rssguard_en.ts +RESOURCE="./localization/rssguard_en.ts" CODES="cs da de en_GB es fi fr gl he id it ja lt nl pl pt_BR pt_PT ru sv uk zh_CN zh_TW" -TRANSLATION='../../../localization/rssguard_$CODE.ts' +TRANSLATION='./localization/rssguard_$CODE.ts' declare PARAMS @@ -20,6 +20,4 @@ for CODE in $CODES; do PARAMS+="-dt "$CODE" "$(eval echo $TRANSLATION)" " done -cd ./transka - -$TRANSKA $PARAMS +$TRANSKA $PARAMS \ No newline at end of file From 92aa7c8d974ab0092977c4a0b3e8685509648c06 Mon Sep 17 00:00:00 2001 From: akinokonomi <91767492+akinokonomi@users.noreply.github.com> Date: Thu, 9 Dec 2021 07:11:18 +0000 Subject: [PATCH 03/19] Update "nudus" skin and more (#540) * Update dark style colours in skinfactory.cpp * Update vergilius metadata.xml, add %8 id * Update nudus skin. Separate it to light and dark versions (SCSS) Co-authored-by: akinokonomi --- .gitignore | 3 +- resources/rssguard.qrc | 21 +- .../{nudus => nudus-base}/html_adblocked.html | 0 .../nudus-base/html_enclosure_every.html | 1 + .../html_enclosure_image.html | 0 .../skins/nudus-base/html_single_message.html | 16 + .../skins/nudus-base/html_style_base.scss | 432 +++++++++++++++ resources/skins/nudus-base/html_wrapper.html | 16 + resources/skins/nudus-dark/html_style.css | 505 ++++++++++++++++++ resources/skins/nudus-dark/html_style.scss | 313 +++++++++++ resources/skins/nudus-dark/html_wrapper.html | 29 + resources/skins/nudus-dark/metadata.xml | 13 + resources/skins/nudus-dark/qt_style.qss | 32 ++ resources/skins/nudus-light/html_style.css | 353 ++++++++++++ resources/skins/nudus-light/html_style.scss | 39 ++ resources/skins/nudus-light/metadata.xml | 13 + resources/skins/nudus-light/qt_style.qss | 14 + .../skins/nudus/html_enclosure_every.html | 1 - .../skins/nudus/html_single_message.html | 12 - resources/skins/nudus/html_wrapper.html | 287 ---------- resources/skins/nudus/metadata.xml | 13 - resources/skins/nudus/qt_style.qss | 11 - .../skins/vergilius/html_single_message.html | 2 +- resources/skins/vergilius/metadata.xml | 12 +- src/librssguard/miscellaneous/skinfactory.cpp | 73 +-- 25 files changed, 1838 insertions(+), 373 deletions(-) rename resources/skins/{nudus => nudus-base}/html_adblocked.html (100%) create mode 100644 resources/skins/nudus-base/html_enclosure_every.html rename resources/skins/{nudus => nudus-base}/html_enclosure_image.html (100%) create mode 100644 resources/skins/nudus-base/html_single_message.html create mode 100644 resources/skins/nudus-base/html_style_base.scss create mode 100644 resources/skins/nudus-base/html_wrapper.html create mode 100644 resources/skins/nudus-dark/html_style.css create mode 100644 resources/skins/nudus-dark/html_style.scss create mode 100644 resources/skins/nudus-dark/html_wrapper.html create mode 100644 resources/skins/nudus-dark/metadata.xml create mode 100644 resources/skins/nudus-dark/qt_style.qss create mode 100644 resources/skins/nudus-light/html_style.css create mode 100644 resources/skins/nudus-light/html_style.scss create mode 100644 resources/skins/nudus-light/metadata.xml create mode 100644 resources/skins/nudus-light/qt_style.qss delete mode 100644 resources/skins/nudus/html_enclosure_every.html delete mode 100644 resources/skins/nudus/html_single_message.html delete mode 100644 resources/skins/nudus/html_wrapper.html delete mode 100644 resources/skins/nudus/metadata.xml delete mode 100644 resources/skins/nudus/qt_style.qss diff --git a/.gitignore b/.gitignore index 6265b37da..c630ebfa2 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ *.autosave *.user* localization/*qm -*.TMP \ No newline at end of file +*.TMP +resources/skins/*/*.map \ No newline at end of file diff --git a/resources/rssguard.qrc b/resources/rssguard.qrc index cef7b8859..5f4e6c42f 100644 --- a/resources/rssguard.qrc +++ b/resources/rssguard.qrc @@ -81,13 +81,20 @@ skins/vergilius/metadata.xml skins/vergilius/qt_style.qss - skins/nudus/html_adblocked.html - skins/nudus/html_enclosure_every.html - skins/nudus/html_enclosure_image.html - skins/nudus/html_single_message.html - skins/nudus/html_wrapper.html - skins/nudus/metadata.xml - skins/nudus/qt_style.qss + skins/nudus-base/html_adblocked.html + skins/nudus-base/html_enclosure_every.html + skins/nudus-base/html_enclosure_image.html + skins/nudus-base/html_single_message.html + skins/nudus-base/html_wrapper.html + + skins/nudus-dark/html_wrapper.html + skins/nudus-dark/html_style.css + skins/nudus-dark/metadata.xml + skins/nudus-dark/qt_style.qss + + skins/nudus-light/html_style.css + skins/nudus-light/metadata.xml + skins/nudus-light/qt_style.qss initial_feeds/feeds-en.opml diff --git a/resources/skins/nudus/html_adblocked.html b/resources/skins/nudus-base/html_adblocked.html similarity index 100% rename from resources/skins/nudus/html_adblocked.html rename to resources/skins/nudus-base/html_adblocked.html diff --git a/resources/skins/nudus-base/html_enclosure_every.html b/resources/skins/nudus-base/html_enclosure_every.html new file mode 100644 index 000000000..72c33ecae --- /dev/null +++ b/resources/skins/nudus-base/html_enclosure_every.html @@ -0,0 +1 @@ + / %2%3 \ No newline at end of file diff --git a/resources/skins/nudus/html_enclosure_image.html b/resources/skins/nudus-base/html_enclosure_image.html similarity index 100% rename from resources/skins/nudus/html_enclosure_image.html rename to resources/skins/nudus-base/html_enclosure_image.html diff --git a/resources/skins/nudus-base/html_single_message.html b/resources/skins/nudus-base/html_single_message.html new file mode 100644 index 000000000..33e813da3 --- /dev/null +++ b/resources/skins/nudus-base/html_single_message.html @@ -0,0 +1,16 @@ +
+ +
+
%7
+ %2 +

%1%6URL / 

+ %5 +
+ +
+ +
+ %4 +
+ +
\ No newline at end of file diff --git a/resources/skins/nudus-base/html_style_base.scss b/resources/skins/nudus-base/html_style_base.scss new file mode 100644 index 000000000..ec97b3aa8 --- /dev/null +++ b/resources/skins/nudus-base/html_style_base.scss @@ -0,0 +1,432 @@ +// ___________________________ +// < I'm an expert in my field. > +// --------------------------- +// \ ^__^ +// \ (oo)\_______ +// (__)\ )\/\ +// ||----w | +// || || + +// +// Variables +// + +$base-unit: 10px !default; + +// +// Styling +// + +// Let the font be customised via RSS Guard settings +// Note: Font size there related **only** to that font alone, it is +// not absolute, and nothing else can be done from my side +// E.g. "Roboto 10" <-- something like this is send from RSS Guard side +* { + font-family: inherit; +} + +// +// Reset some basic elements +// + +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; +} + +// +// Add some basic styling +// + +body { + background-color: $cbg00; + + box-sizing: border-box; + color: $cfg00; + //cursor: default; + -webkit-text-size-adjust: 100%; + -webkit-font-feature-settings: "kern" 1; + font-feature-settings: "kern" 1; + font-kerning: normal; + min-height: 100vh; +} + +::selection { + background-color: $clink; + text-shadow: none; +} + +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +ul, ol, dl, figure, +details { + margin-bottom: $base-unit; +} + +hr { + background-color: $cbor2; + border: none; + display: block; + height: 2px; + margin: $base-unit 0; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 600 !important; +} + +h1 { font-size: 1.25rem !important; } +h2 { font-size: 1.20rem !important; } +h3 { font-size: 1.15rem !important; } +h4 { font-size: 1.1rem !important; } +h5 { font-size: 1rem !important; } +h6 { font-size: .95rem !important; } + +b { + font-weight: bold !important; +} + +i { + font-style: italic !important; +} + +strong { + font-weight: 800 !important; +} + +em { + font-style: oblique !important; +} + +mark { + background-color: $cmark; +} + +sub, +sup { + font-size: .8rem !important; +} + +small { + font-size: .9rem !important; +} + +abbr { + cursor: help; + font-style: italic !important; + font-weight: 100 !important; +} + +q { + font-style: italic !important; + + &::before { + content: '“'; + } + + &::after { + content: '”'; + } +} + +time { + font-weight: 450 !important; +} + +var { + font-style: oblique !important; + font-weight: 500 !important; +} + +a { + color: $clink; + + &:hover { + text-decoration: none; + } + + &:focus { + box-shadow: none; + outline: none; + } +} + +cite { + font-style: italic !important; + font-weight: bold !important; +} + +figure > img { + display: block; +} + +figcaption { + font-size: .8rem !important; +} + +blockquote { + border-left: .3em solid $cbor2; + margin-left: 0; + padding: 0 $base-unit; + + &, + p { + color: $cfg11; + } +} + +pre, +code { + border: 1px solid $cbor3; + border-radius: $radius-unit; + color: $cfg10; + // cursor: text; +} + +code { + background-color: $ccode; + padding: 0 .25em; + word-break: break-word; +} + +pre { + background-color: $ccodeblock; + overflow-x: auto; + padding: 7px 13px; + tab-size: 2; + // For
+    white-space: pre !important;
+
+    // For 
+    width: unset !important;
+
+    > code {
+        background-color: unset;
+        border: none;
+        color: unset;
+        padding-right: 0;
+        padding-left: 0;
+        tab-size: 2;
+    }
+}
+
+kbd {
+    background: $ccode;
+    border: 1px solid $cbor3;
+    border-bottom: 3px solid darken($cbor3, 3%);
+    border-radius: $radius-unit;
+    box-shadow:
+        0 2px 4px darken($cbg00, 6%),
+        inset 0 1px $cbg00
+    ;
+    font-size: .9rem !important;
+    padding: .1em .4em .2em .4em;
+}
+
+select {
+    background-color: $ccodeblock;
+    border: 1px solid $cbor3;
+    border-radius: $radius-unit;
+    color: $cfg00;
+    padding: .04em .25em;
+    // Do not use max-width here
+    width: 100%;
+
+    &:focus {
+        box-shadow: none;
+        outline: none;
+
+        background-color: $cbg00;
+    }
+
+    > option {
+        background-color: $cbg00;
+    }
+}
+
+table {
+    border-collapse: collapse;
+    // `!important` is set to override something like 
+    width: 100% !important;
+}
+
+// Return this if something goes wrong, and return the JS override for dark theme
+//table,
+//th,
+//td {
+//    color: $cfg00;
+//}
+
+li {
+    display: list-item;
+}
+
+ul,
+ol {
+    padding-left: 1.5em;
+}
+
+ul {
+    list-style-type: disc;
+
+    li ul {
+        list-style-type: square;
+    }
+}
+
+ol {
+    list-style-type: decimal;
+
+    li ol {
+        list-style-type: lower-roman;
+    }
+}
+
+img {
+    // Let the width be defined (see .rssguard-mbody img), but keep aspect ratio
+    height: auto;
+    // `width auto` creates many problems even if set as a fallback
+    //width: auto;
+}
+
+details {
+    border: 1px solid $ccodeblock;
+    border-radius: $radius-unit;
+    padding: .5em .5em 0;
+
+    > summary {
+        background-color: $ccodeblock;
+        border-radius: $radius-unit * 0.9;
+        cursor: pointer;
+        margin: -.5em -.5em 0;
+        padding-left: .5em;
+
+        &:focus {
+            box-shadow: none;
+            outline: none;
+        }
+    }
+
+    & *:last-child {
+        margin-bottom: 0;
+    }
+}
+
+details[open] {
+    border-color: $cbor3;
+    padding: .5em;
+
+    > summary {
+        border-bottom: 1px solid $cbor3;
+        border-radius: ($radius-unit * 0.9) ($radius-unit * 0.9) 0 0;
+        margin-bottom: .5em;
+    }
+}
+
+iframe {
+    max-width: 100%;
+    height: auto;
+    width: auto;
+}
+
+a,
+select,
+summary {
+
+    &:focus {
+        background-color: $cbor3;
+        text-shadow: 0 -1px $cbg00;
+    }
+}
+
+:target {
+    outline: 1px solid $clink;
+}
+
+// m* == message*
+body:hover .rssguard-mwrapper .rssguard-mhead .mwrapurl {
+
+    a,
+    span {
+        visibility: visible;
+    }
+}
+
+.rssguard-mwrapper .rssguard-mhead .mwrapurl a:focus {
+
+    &,
+    & + span {
+        visibility: visible !important;
+    }
+}
+
+.rssguard-mwrapper {
+    padding: $base-unit !important;
+
+    .rssguard-mhead {
+
+        .msmall,
+        .mlinks {
+            opacity: .8;
+        }
+
+        > h1 {
+            margin: 0;
+        }
+
+        .msmall {
+            font-size: .9em;
+        }
+
+        .mlinks {
+
+            .menc {
+                word-break: break-word;
+            }
+
+            .mwrapurl {
+                display: inline-flex;
+
+                a {
+                    order: 1;
+                }
+
+                a,
+                span {
+                    visibility: hidden;
+                }
+            }
+        }
+    }
+
+    .rssguard-mbody img {
+        // Needs to be `!important` when max-width is defined by image style
+        // alt
+        max-width: 450px !important;
+        // For cases when they both are set
+        max-height: unset !important;
+
+        @media only screen and (max-width: 800px) {
+            // `!important` to override `!important` that is set above
+            max-width: 100% !important;
+        }
+    }
+}
+
+//
+// Other
+//
+
+// For articles without any html elements;
+// If not applied to _all_, *must* be applied to links in mbody
+// mbody == article body
+.rssguard-mbody {
+    word-break: break-word;
+}
+
+// Fix at least some mess produced by above
+table {
+    word-break: normal;
+}
diff --git a/resources/skins/nudus-base/html_wrapper.html b/resources/skins/nudus-base/html_wrapper.html
new file mode 100644
index 000000000..a85c25b83
--- /dev/null
+++ b/resources/skins/nudus-base/html_wrapper.html
@@ -0,0 +1,16 @@
+
+
+
+%1
+
+
+
+
+
+
+%2
+
+
+
\ No newline at end of file
diff --git a/resources/skins/nudus-dark/html_style.css b/resources/skins/nudus-dark/html_style.css
new file mode 100644
index 000000000..7b34533d3
--- /dev/null
+++ b/resources/skins/nudus-dark/html_style.css
@@ -0,0 +1,505 @@
+@charset "UTF-8";
+* {
+  font-family: inherit;
+}
+
+body, h1, h2, h3, h4, h5, h6,
+p, blockquote, pre, hr,
+dl, dd, ol, ul, figure {
+  margin: 0;
+  padding: 0;
+}
+
+body {
+  background-color: #373A3D;
+  box-sizing: border-box;
+  color: #f5f5f5;
+  -webkit-text-size-adjust: 100%;
+  -webkit-font-feature-settings: "kern" 1;
+  font-feature-settings: "kern" 1;
+  font-kerning: normal;
+  min-height: 100vh;
+}
+
+::selection {
+  background-color: #8291AD;
+  text-shadow: none;
+}
+
+h1, h2, h3, h4, h5, h6,
+p, blockquote, pre,
+ul, ol, dl, figure,
+details {
+  margin-bottom: 10px;
+}
+
+hr {
+  background-color: #545556;
+  border: none;
+  display: block;
+  height: 2px;
+  margin: 10px 0;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  font-weight: 600 !important;
+}
+
+h1 {
+  font-size: 1.25rem  !important;
+}
+
+h2 {
+  font-size: 1.20rem  !important;
+}
+
+h3 {
+  font-size: 1.15rem  !important;
+}
+
+h4 {
+  font-size: 1.1rem   !important;
+}
+
+h5 {
+  font-size: 1rem     !important;
+}
+
+h6 {
+  font-size: .95rem   !important;
+}
+
+b {
+  font-weight: bold !important;
+}
+
+i {
+  font-style: italic !important;
+}
+
+strong {
+  font-weight: 800 !important;
+}
+
+em {
+  font-style: oblique !important;
+}
+
+mark {
+  background-color: #f8d08c66;
+}
+
+sub,
+sup {
+  font-size: .8rem !important;
+}
+
+small {
+  font-size: .9rem !important;
+}
+
+abbr {
+  cursor: help;
+  font-style: italic !important;
+  font-weight: 100 !important;
+}
+
+q {
+  font-style: italic !important;
+}
+q::before {
+  content: '“';
+}
+q::after {
+  content: '”';
+}
+
+time {
+  font-weight: 450 !important;
+}
+
+var {
+  font-style: oblique !important;
+  font-weight: 500 !important;
+}
+
+a {
+  color: #8291AD;
+}
+a:hover {
+  text-decoration: none;
+}
+a:focus {
+  box-shadow: none;
+  outline: none;
+}
+
+cite {
+  font-style: italic !important;
+  font-weight: bold !important;
+}
+
+figure > img {
+  display: block;
+}
+
+figcaption {
+  font-size: .8rem !important;
+}
+
+blockquote {
+  border-left: 0.3em solid #545556;
+  margin-left: 0;
+  padding: 0 10px;
+}
+blockquote,
+blockquote p {
+  color: #D8D8D8;
+}
+
+pre,
+code {
+  border: 1px solid #282a2c;
+  border-radius: 0.3em;
+  color: #D8D8D8;
+}
+
+code {
+  background-color: rgba(33, 35, 39, 0.4);
+  padding: 0 .25em;
+  word-break: break-word;
+}
+
+pre {
+  background-color: rgba(33, 35, 39, 0.4);
+  overflow-x: auto;
+  padding: 7px 13px;
+  tab-size: 2;
+  white-space: pre !important;
+  width: unset !important;
+}
+pre > code {
+  background-color: unset;
+  border: none;
+  color: unset;
+  padding-right: 0;
+  padding-left: 0;
+  tab-size: 2;
+}
+
+kbd {
+  background: rgba(33, 35, 39, 0.4);
+  border: 1px solid #282a2c;
+  border-bottom: 3px solid #212224;
+  border-radius: 0.3em;
+  box-shadow: 0 2px 4px #282b2d, inset 0 1px #373A3D;
+  font-size: .9rem !important;
+  padding: .1em .4em .2em .4em;
+}
+
+select {
+  background-color: rgba(33, 35, 39, 0.4);
+  border: 1px solid #282a2c;
+  border-radius: 0.3em;
+  color: #f5f5f5;
+  padding: .04em .25em;
+  width: 100%;
+}
+select:focus {
+  box-shadow: none;
+  outline: none;
+  background-color: #373A3D;
+}
+select > option {
+  background-color: #373A3D;
+}
+
+table {
+  border-collapse: collapse;
+  width: 100% !important;
+}
+
+li {
+  display: list-item;
+}
+
+ul,
+ol {
+  padding-left: 1.5em;
+}
+
+ul {
+  list-style-type: disc;
+}
+ul li ul {
+  list-style-type: square;
+}
+
+ol {
+  list-style-type: decimal;
+}
+ol li ol {
+  list-style-type: lower-roman;
+}
+
+img {
+  height: auto;
+}
+
+details {
+  border: 1px solid rgba(33, 35, 39, 0.4);
+  border-radius: 0.3em;
+  padding: .5em .5em 0;
+}
+details > summary {
+  background-color: rgba(33, 35, 39, 0.4);
+  border-radius: 0.27em;
+  cursor: pointer;
+  margin: -.5em -.5em 0;
+  padding-left: .5em;
+}
+details > summary:focus {
+  box-shadow: none;
+  outline: none;
+}
+details *:last-child {
+  margin-bottom: 0;
+}
+
+details[open] {
+  border-color: #282a2c;
+  padding: .5em;
+}
+details[open] > summary {
+  border-bottom: 1px solid #282a2c;
+  border-radius: 0.27em 0.27em 0 0;
+  margin-bottom: .5em;
+}
+
+iframe {
+  max-width: 100%;
+  height: auto;
+  width: auto;
+}
+
+a:focus,
+select:focus,
+summary:focus {
+  background-color: #282a2c;
+  text-shadow: 0 -1px #373A3D;
+}
+
+:target {
+  outline: 1px solid #8291AD;
+}
+
+body:hover .rssguard-mwrapper .rssguard-mhead .mwrapurl a,
+body:hover .rssguard-mwrapper .rssguard-mhead .mwrapurl span {
+  visibility: visible;
+}
+
+.rssguard-mwrapper .rssguard-mhead .mwrapurl a:focus, .rssguard-mwrapper .rssguard-mhead .mwrapurl a:focus + span {
+  visibility: visible !important;
+}
+
+.rssguard-mwrapper {
+  padding: 10px !important;
+}
+.rssguard-mwrapper .rssguard-mhead .msmall,
+.rssguard-mwrapper .rssguard-mhead .mlinks {
+  opacity: .8;
+}
+.rssguard-mwrapper .rssguard-mhead > h1 {
+  margin: 0;
+}
+.rssguard-mwrapper .rssguard-mhead .msmall {
+  font-size: .9em;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .menc {
+  word-break: break-word;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl {
+  display: inline-flex;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl a {
+  order: 1;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl a,
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl span {
+  visibility: hidden;
+}
+.rssguard-mwrapper .rssguard-mbody img {
+  max-width: 450px !important;
+  max-height: unset !important;
+}
+@media only screen and (max-width: 800px) {
+  .rssguard-mwrapper .rssguard-mbody img {
+    max-width: 100% !important;
+  }
+}
+
+.rssguard-mbody {
+  word-break: break-word;
+}
+
+table {
+  word-break: normal;
+}
+
+html::before,
+html::after,
+body::before,
+body::after {
+  content: "";
+  background-color: #282a2c;
+  display: block;
+  position: fixed;
+  z-index: 5;
+}
+
+html::before {
+  height: 1px;
+  left: 0;
+  right: 0;
+  top: 0;
+}
+
+html::after {
+  width: 1px;
+  top: 0;
+  right: 0;
+  bottom: 0;
+}
+
+body::before {
+  height: 1px;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+
+body::after {
+  width: 1px;
+  top: 0;
+  bottom: 0;
+  left: 0;
+}
+
+::-webkit-scrollbar {
+  height: 13px;
+  width: 14px;
+}
+
+::-webkit-scrollbar-track,
+::-webkit-scrollbar-corner {
+  background-color: #37393c;
+  box-shadow: inset 1px 1px #323437;
+}
+
+::-webkit-scrollbar-corner {
+  border-radius: 0 0 0.3em 0;
+}
+
+::-webkit-scrollbar-thumb {
+  box-shadow: inset 1px 1px #565a5f, inset -1px -1px #565a5f, inset 0px 1px #565a5f, inset 0px -1px #565a5f, inset 1px 0px #565a5f, inset 1px -1px #565a5f, inset -1px 0px #565a5f, inset -1px 1px #565a5f;
+}
+::-webkit-scrollbar-thumb:horizontal {
+  background-image: linear-gradient(#414347 5%, #3c3e42);
+  min-width: 25px;
+}
+::-webkit-scrollbar-thumb:horizontal:hover {
+  background-image: linear-gradient(#43464a 25%, #3c3e42);
+}
+::-webkit-scrollbar-thumb:vertical {
+  background-image: linear-gradient(to right, #414347 5%, #3c3e42);
+  min-height: 25px;
+}
+::-webkit-scrollbar-thumb:vertical:hover {
+  background-image: linear-gradient(to right, #43464a 25%, #3c3e42);
+}
+::-webkit-scrollbar-thumb:active {
+  background-image: linear-gradient(#3c3e42, #3c3e42) !important;
+}
+
+:not(body)::-webkit-scrollbar-thumb:horizontal {
+  box-shadow: inset 1px 1px #565a5f, inset -1px -1px #565a5f, inset 0px 1px #565a5f, inset 0px -1px #565a5f, inset 1px 0px #565a5f, inset 1px -1px #565a5f, inset -1px 0px #565a5f, inset -1px 1px #565a5f, 1px 0px #282a2c, 1px 1px #282a2c, -1px 1px #282a2c, -1px 0px #282a2c;
+}
+:not(body)::-webkit-scrollbar-thumb:vertical {
+  box-shadow: inset 1px 1px #565a5f, inset -1px -1px #565a5f, inset 0px 1px #565a5f, inset 0px -1px #565a5f, inset 1px 0px #565a5f, inset 1px -1px #565a5f, inset -1px 0px #565a5f, inset -1px 1px #565a5f, 0px -1px #282a2c, 1px -1px #282a2c, 1px 1px #282a2c, 0px 1px #282a2c;
+}
+
+::-webkit-scrollbar-thumb:horizontal,
+::-webkit-scrollbar-track:horizontal {
+  border-top: 1px solid #282a2c;
+}
+::-webkit-scrollbar-thumb:vertical,
+::-webkit-scrollbar-track:vertical {
+  border-left: 1px solid #282a2c;
+}
+
+body::-webkit-scrollbar-thumb:horizontal, body::-webkit-scrollbar-thumb:vertical,
+body::-webkit-scrollbar-track:horizontal,
+body::-webkit-scrollbar-track:vertical {
+  border: 1px solid #282a2c;
+}
+body::-webkit-scrollbar-thumb:horizontal,
+body::-webkit-scrollbar-track:horizontal {
+  border-top: none;
+}
+body::-webkit-scrollbar-thumb:vertical,
+body::-webkit-scrollbar-track:vertical {
+  border-left: none;
+}
+
+body::-webkit-scrollbar-corner {
+  border: 1px solid #282a2c;
+  border-top: none;
+  border-left: none;
+}
+
+::-webkit-scrollbar-track:corner-present:horizontal,
+::-webkit-scrollbar-thumb:corner-present:horizontal {
+  border-radius: 0 0 0 0.3em;
+}
+::-webkit-scrollbar-track:corner-present:vertical,
+::-webkit-scrollbar-thumb:corner-present:vertical {
+  border-radius: 0 0.3em 0 0;
+}
+::-webkit-scrollbar-track:horizontal,
+::-webkit-scrollbar-thumb:horizontal {
+  border-radius: 0 0 0.3em 0.3em;
+}
+::-webkit-scrollbar-track:vertical,
+::-webkit-scrollbar-thumb:vertical {
+  border-radius: 0 0.3em 0.3em 0;
+}
+
+/* Please enable JS for additional font-colouring features */
+:root {
+  --rssguard-red: 0;
+  --rssguard-green: 0;
+  --rssguard-blue: 0;
+  --rssguard-threshold: 0.5;
+}
+
+:root {
+  --rssguard-r: calc(var(--rssguard-red) * 0.2126);
+  --rssguard-g: calc(var(--rssguard-green) * 0.7152);
+  --rssguard-b: calc(var(--rssguard-blue) * 0.0722);
+  --rssguard-sum:
+      calc(
+          var(--rssguard-r) +
+          var(--rssguard-g) +
+          var(--rssguard-b)
+      );
+  --rssguard-perceived-lightness: calc(var(--rssguard-sum) / 255);
+}
+
+body,
+::selection,
+mark, code, pre, pre > code,
+blockquote {
+  color: hsla(0, 0%, calc( ( var(--rssguard-perceived-lightness) - var(--rssguard-threshold) ) * -10000000% ), 0.9);
+}
+
+/*# sourceMappingURL=html_style.css.map */
diff --git a/resources/skins/nudus-dark/html_style.scss b/resources/skins/nudus-dark/html_style.scss
new file mode 100644
index 000000000..6f4ccb00f
--- /dev/null
+++ b/resources/skins/nudus-dark/html_style.scss
@@ -0,0 +1,313 @@
+@charset "utf-8";
+
+$qtbg-base:     #373A3D !default; // clr_basbg
+$qtbg-button:   #323437 !default; // clr_altbg // button bg (scrollbar, alt bg)
+$qcselbg:       #8291AD !default; // clr_selbg
+
+//
+// Emulate fusion colour processing (dark only)
+//
+
+//
+// Scrollbar colours
+//
+
+//$qcbgbg:    lighten($qtbg-button, 6%); // See toolbar bg
+$bgscroll:      lighten($qtbg-button, 2%) !default; // track and corner bg
+$tr-border:     darken($qtbg-button, 4%) !default; // track brdr
+
+//
+// Scrollbar thumb
+//
+
+// bg gradient
+
+// Normal
+$thscrlin:      lighten($qtbg-button, 6%) !default;
+$thscrlout:     lighten($qtbg-button, 4%) !default;
+
+// Hover
+$thscrlhvin:    lighten($qtbg-button, 7%) !default;
+$thscrlhvout:   $thscrlout;
+
+// Light outline
+$th-border:     lighten($qtbg-button, 15%) !default;
+
+//
+// HTML palette (Colours)
+//
+
+$cbg00: $qtbg-base;
+
+// Irrelevant, because fg is overridden by the switcher ~~~
+$cfg00: #f5f5f5 !default;
+$cfg10: #D8D8D8 !default;
+$cfg11: $cfg10;
+// ~~~
+
+$cbor2: #545556 !default;
+
+$ccodeblock:    rgba(33, 35, 39, 0.4) !default;
+$ccode:         $ccodeblock;
+$cbor3:         $tr-border;
+$cmark:         #f8d08c66 !default; // 40% transparency
+
+$clink: $qcselbg;
+
+//
+// Other
+//
+
+$radius-unit: .3em !default;
+
+@import
+    "../nudus-base/html_style_base"
+;
+
+//
+// Dark HTML-style has following additions:
+//
+
+//
+// Border around viewport
+
+//  https://csswizardry.com/2010/12/simplified-page-borders-in-pure-css/
+//
+
+html::before,
+html::after,
+body::before,
+body::after {
+    content: "";
+    background-color: $tr-border;
+    display: block;
+    position: fixed;
+    z-index: 5;
+}
+
+html::before {
+    height: 1px;
+    left: 0;
+    right: 0;
+    top: 0;
+}
+
+html::after {
+    width: 1px;
+    top: 0;
+    right: 0;
+    bottom: 0;
+}
+
+body::before {
+    height: 1px;
+    right: 0;
+    bottom: 0;
+    left: 0;
+}
+
+body::after {
+    width: 1px;
+    top: 0;
+    bottom: 0;
+    left: 0;
+}
+
+//
+// Enhanced scrollbar
+//
+
+::-webkit-scrollbar {
+    height: 13px;
+    width: 14px;
+}
+
+::-webkit-scrollbar-track,
+::-webkit-scrollbar-corner {
+    background-color: $bgscroll;
+    box-shadow: inset 1px 1px lighten($tr-border, 4%);
+}
+
+// Part where vertical and horizontal scrollbars meet
+::-webkit-scrollbar-corner {
+    border-radius: 0 0 $radius-unit 0;
+}
+
+// TODO: Can be simplified to @include and function
+$th-outline:
+    inset 1px 1px   $th-border,
+    inset -1px -1px $th-border,
+
+    inset 0px 1px   $th-border,
+    inset 0px -1px  $th-border,
+
+    inset 1px 0px   $th-border,
+    inset 1px -1px  $th-border,
+
+    inset -1px 0px  $th-border,
+    inset -1px 1px  $th-border
+;
+
+::-webkit-scrollbar-thumb {
+    $th-min-unit: 25px;
+    box-shadow: $th-outline;
+
+    &:horizontal {
+        background-image: linear-gradient($thscrlin 5%, $thscrlout);
+        min-width: $th-min-unit;
+
+        &:hover {
+            background-image: linear-gradient($thscrlhvin 25%, $thscrlhvout);
+        }
+    }
+
+    &:vertical {
+        background-image: linear-gradient(to right, $thscrlin 5%, $thscrlout);
+        min-height: $th-min-unit;
+
+        &:hover {
+            background-image: linear-gradient(to right, $thscrlhvin 25%, $thscrlhvout);
+        }
+    }
+
+    &:active {
+        background-image: linear-gradient($thscrlout, $thscrlout) !important;
+    }
+}
+
+// Light and dark borders to outline the thumb
+// Clockwise (x y)
+:not(body)::-webkit-scrollbar-thumb {
+
+    &:horizontal {
+        box-shadow:
+            $th-outline,
+            1px 0px   $tr-border,
+            1px 1px   $tr-border,
+            -1px 1px  $tr-border,
+            -1px 0px  $tr-border
+        ;
+    }
+
+    &:vertical {
+        box-shadow:
+            $th-outline,
+            0px -1px  $tr-border,
+            1px -1px  $tr-border,
+            1px 1px   $tr-border,
+            0px 1px   $tr-border
+        ;
+    }
+}
+
+::-webkit-scrollbar-thumb,
+::-webkit-scrollbar-track {
+
+    &:horizontal {
+        border-top: 1px solid $tr-border;
+    }
+
+    &:vertical {
+        border-left: 1px solid $tr-border;
+    }
+}
+
+// More complete borders for `body` scrollbar
+body::-webkit-scrollbar-thumb,
+body::-webkit-scrollbar-track {
+
+    &:horizontal,
+    &:vertical {
+        border: 1px solid $tr-border;
+    }
+
+    &:horizontal {
+        border-top: none;
+    }
+
+    &:vertical {
+        border-left: none;
+    }
+}
+
+body::-webkit-scrollbar-corner {
+    border: 1px solid $tr-border;
+    border-top: none;
+    border-left: none;
+}
+
+::-webkit-scrollbar-track,
+::-webkit-scrollbar-thumb {
+
+    &:corner-present {
+
+        &:horizontal {
+            border-radius: 0 0 0 $radius-unit;
+        }
+
+        &:vertical {
+            border-radius: 0 $radius-unit 0 0;
+        }
+    }
+
+    &:horizontal {
+        border-radius: 0 0 $radius-unit $radius-unit;
+    }
+
+    &:vertical {
+        border-radius: 0 $radius-unit $radius-unit 0;
+    }
+}
+
+//
+// Font colour switcher
+
+//  Thank you so much!!
+//  https://css-tricks.com/switch-font-color-for-different-backgrounds-with-css/
+//
+
+/* Please enable JS for additional font-colouring features */
+:root {
+    // Default RGB values for background colour
+    --rssguard-red: 0;
+    --rssguard-green: 0;
+    --rssguard-blue: 0;
+    // The threshold at which colours are considered "light
+    // Range: decimals from 0 to 1, recommended 0.5 - 0.6
+    --rssguard-threshold: 0.5;
+}
+
+:root {
+    // Calculates perceived lightness using the sRGB Luma method
+    // Luma = (red * 0.2126 + green * 0.7152 + blue * 0.0722) / 255
+    --rssguard-r: calc(var(--rssguard-red) * 0.2126);
+    --rssguard-g: calc(var(--rssguard-green) * 0.7152);
+    --rssguard-b: calc(var(--rssguard-blue) * 0.0722);
+    --rssguard-sum:
+        calc(
+            var(--rssguard-r) +
+            var(--rssguard-g) +
+            var(--rssguard-b)
+        );
+    --rssguard-perceived-lightness: calc(var(--rssguard-sum) / 255);
+}
+
+// Shows either white or black colour depending on perceived lightness
+body,
+::selection,
+mark, code, pre, pre > code,
+blockquote {
+    color:
+        hsla(
+            0,
+            0%,
+            calc(
+                (
+                    var(--rssguard-perceived-lightness) -
+                    var(--rssguard-threshold)
+                ) *
+                -10000000%
+            ),
+            .9
+        );
+}
diff --git a/resources/skins/nudus-dark/html_wrapper.html b/resources/skins/nudus-dark/html_wrapper.html
new file mode 100644
index 000000000..e9f5b6666
--- /dev/null
+++ b/resources/skins/nudus-dark/html_wrapper.html
@@ -0,0 +1,29 @@
+
+
+
+%1
+
+
+
+
+
+
+%2
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/skins/nudus-dark/metadata.xml b/resources/skins/nudus-dark/metadata.xml
new file mode 100644
index 000000000..57a3ebb36
--- /dev/null
+++ b/resources/skins/nudus-dark/metadata.xml
@@ -0,0 +1,13 @@
+
+
+  
+    akinokonomi
+  
+  
+    #85ACF6
+    #D9E3F7
+    #DF5656
+    #910303
+    #44AA44
+  
+
diff --git a/resources/skins/nudus-dark/qt_style.qss b/resources/skins/nudus-dark/qt_style.qss
new file mode 100644
index 000000000..e8a66b4e5
--- /dev/null
+++ b/resources/skins/nudus-dark/qt_style.qss
@@ -0,0 +1,32 @@
+QListWidget,
+QScrollArea {
+    border: 1px solid palette(dark);
+}
+
+
+QPlainTextEdit:focus {
+    border: 1px solid palette(highlight);
+}
+
+QToolTip {
+    background-color: palette(window);
+    border: 1px solid palette(dark);
+    border-radius: 2px;
+}
+
+/* TODO: Fine for now, may be improved in future */
+QProgressBar {
+    background-color: palette(highlight);
+    color: palette(window);
+}
+
+QSplitter::handle {
+    background: palette(dark);
+}
+
+/*
+ * For `qt5-styleplugins`: motif, cde, gtk2, etc.
+ */
+QStatusBar::item {
+    border: none;
+}
diff --git a/resources/skins/nudus-light/html_style.css b/resources/skins/nudus-light/html_style.css
new file mode 100644
index 000000000..72dce3e0c
--- /dev/null
+++ b/resources/skins/nudus-light/html_style.css
@@ -0,0 +1,353 @@
+@charset "UTF-8";
+* {
+  font-family: inherit;
+}
+
+body, h1, h2, h3, h4, h5, h6,
+p, blockquote, pre, hr,
+dl, dd, ol, ul, figure {
+  margin: 0;
+  padding: 0;
+}
+
+body {
+  background-color: #FBFBFB;
+  box-sizing: border-box;
+  color: #000000;
+  -webkit-text-size-adjust: 100%;
+  -webkit-font-feature-settings: "kern" 1;
+  font-feature-settings: "kern" 1;
+  font-kerning: normal;
+  min-height: 100vh;
+}
+
+::selection {
+  background-color: #5D88D2;
+  text-shadow: none;
+}
+
+h1, h2, h3, h4, h5, h6,
+p, blockquote, pre,
+ul, ol, dl, figure,
+details {
+  margin-bottom: 10px;
+}
+
+hr {
+  background-color: #CFCFCF;
+  border: none;
+  display: block;
+  height: 2px;
+  margin: 10px 0;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  font-weight: 600 !important;
+}
+
+h1 {
+  font-size: 1.25rem  !important;
+}
+
+h2 {
+  font-size: 1.20rem  !important;
+}
+
+h3 {
+  font-size: 1.15rem  !important;
+}
+
+h4 {
+  font-size: 1.1rem   !important;
+}
+
+h5 {
+  font-size: 1rem     !important;
+}
+
+h6 {
+  font-size: .95rem   !important;
+}
+
+b {
+  font-weight: bold !important;
+}
+
+i {
+  font-style: italic !important;
+}
+
+strong {
+  font-weight: 800 !important;
+}
+
+em {
+  font-style: oblique !important;
+}
+
+mark {
+  background-color: #FFECCC;
+}
+
+sub,
+sup {
+  font-size: .8rem !important;
+}
+
+small {
+  font-size: .9rem !important;
+}
+
+abbr {
+  cursor: help;
+  font-style: italic !important;
+  font-weight: 100 !important;
+}
+
+q {
+  font-style: italic !important;
+}
+q::before {
+  content: '“';
+}
+q::after {
+  content: '”';
+}
+
+time {
+  font-weight: 450 !important;
+}
+
+var {
+  font-style: oblique !important;
+  font-weight: 500 !important;
+}
+
+a {
+  color: #5D88D2;
+}
+a:hover {
+  text-decoration: none;
+}
+a:focus {
+  box-shadow: none;
+  outline: none;
+}
+
+cite {
+  font-style: italic !important;
+  font-weight: bold !important;
+}
+
+figure > img {
+  display: block;
+}
+
+figcaption {
+  font-size: .8rem !important;
+}
+
+blockquote {
+  border-left: 0.3em solid #CFCFCF;
+  margin-left: 0;
+  padding: 0 10px;
+}
+blockquote,
+blockquote p {
+  color: #343434;
+}
+
+pre,
+code {
+  border: 1px solid #DEDEDE;
+  border-radius: 0.1em;
+  color: #343434;
+}
+
+code {
+  background-color: #F1F1F1;
+  padding: 0 .25em;
+  word-break: break-word;
+}
+
+pre {
+  background-color: #F1F1F1;
+  overflow-x: auto;
+  padding: 7px 13px;
+  tab-size: 2;
+  white-space: pre !important;
+  width: unset !important;
+}
+pre > code {
+  background-color: unset;
+  border: none;
+  color: unset;
+  padding-right: 0;
+  padding-left: 0;
+  tab-size: 2;
+}
+
+kbd {
+  background: #F1F1F1;
+  border: 1px solid #DEDEDE;
+  border-bottom: 3px solid #d6d6d6;
+  border-radius: 0.1em;
+  box-shadow: 0 2px 4px #ececec, inset 0 1px #FBFBFB;
+  font-size: .9rem !important;
+  padding: .1em .4em .2em .4em;
+}
+
+select {
+  background-color: #F1F1F1;
+  border: 1px solid #DEDEDE;
+  border-radius: 0.1em;
+  color: #000000;
+  padding: .04em .25em;
+  width: 100%;
+}
+select:focus {
+  box-shadow: none;
+  outline: none;
+  background-color: #FBFBFB;
+}
+select > option {
+  background-color: #FBFBFB;
+}
+
+table {
+  border-collapse: collapse;
+  width: 100% !important;
+}
+
+li {
+  display: list-item;
+}
+
+ul,
+ol {
+  padding-left: 1.5em;
+}
+
+ul {
+  list-style-type: disc;
+}
+ul li ul {
+  list-style-type: square;
+}
+
+ol {
+  list-style-type: decimal;
+}
+ol li ol {
+  list-style-type: lower-roman;
+}
+
+img {
+  height: auto;
+}
+
+details {
+  border: 1px solid #F1F1F1;
+  border-radius: 0.1em;
+  padding: .5em .5em 0;
+}
+details > summary {
+  background-color: #F1F1F1;
+  border-radius: 0.09em;
+  cursor: pointer;
+  margin: -.5em -.5em 0;
+  padding-left: .5em;
+}
+details > summary:focus {
+  box-shadow: none;
+  outline: none;
+}
+details *:last-child {
+  margin-bottom: 0;
+}
+
+details[open] {
+  border-color: #DEDEDE;
+  padding: .5em;
+}
+details[open] > summary {
+  border-bottom: 1px solid #DEDEDE;
+  border-radius: 0.09em 0.09em 0 0;
+  margin-bottom: .5em;
+}
+
+iframe {
+  max-width: 100%;
+  height: auto;
+  width: auto;
+}
+
+a:focus,
+select:focus,
+summary:focus {
+  background-color: #DEDEDE;
+  text-shadow: 0 -1px #FBFBFB;
+}
+
+:target {
+  outline: 1px solid #5D88D2;
+}
+
+body:hover .rssguard-mwrapper .rssguard-mhead .mwrapurl a,
+body:hover .rssguard-mwrapper .rssguard-mhead .mwrapurl span {
+  visibility: visible;
+}
+
+.rssguard-mwrapper .rssguard-mhead .mwrapurl a:focus, .rssguard-mwrapper .rssguard-mhead .mwrapurl a:focus + span {
+  visibility: visible !important;
+}
+
+.rssguard-mwrapper {
+  padding: 10px !important;
+}
+.rssguard-mwrapper .rssguard-mhead .msmall,
+.rssguard-mwrapper .rssguard-mhead .mlinks {
+  opacity: .8;
+}
+.rssguard-mwrapper .rssguard-mhead > h1 {
+  margin: 0;
+}
+.rssguard-mwrapper .rssguard-mhead .msmall {
+  font-size: .9em;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .menc {
+  word-break: break-word;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl {
+  display: inline-flex;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl a {
+  order: 1;
+}
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl a,
+.rssguard-mwrapper .rssguard-mhead .mlinks .mwrapurl span {
+  visibility: hidden;
+}
+.rssguard-mwrapper .rssguard-mbody img {
+  max-width: 450px !important;
+  max-height: unset !important;
+}
+@media only screen and (max-width: 800px) {
+  .rssguard-mwrapper .rssguard-mbody img {
+    max-width: 100% !important;
+  }
+}
+
+.rssguard-mbody {
+  word-break: break-word;
+}
+
+table {
+  word-break: normal;
+}
+
+::selection {
+  color: #F1F1F1;
+}
+
+/*# sourceMappingURL=html_style.css.map */
diff --git a/resources/skins/nudus-light/html_style.scss b/resources/skins/nudus-light/html_style.scss
new file mode 100644
index 000000000..f6aa22d7d
--- /dev/null
+++ b/resources/skins/nudus-light/html_style.scss
@@ -0,0 +1,39 @@
+@charset "utf-8";
+
+//
+// Colours
+//
+
+$cbg00: #FBFBFB !default; // Background // Qt5 fusion bg light toolbar-grey
+
+$cfg00: #000000 !default;
+//$cfg10: #A23542 !default; // TODO: fg for code
+$cfg11: #343434 !default; // Lighter fg for blockquote
+$cfg10: $cfg11;
+
+$cbor2: #CFCFCF !default; // hr and blockquote border
+
+$ccodeblock:    #F1F1F1     !default; // bg for `pre > code` and `details > summ`
+$ccode:         $ccodeblock !default; // bg for `code`
+$cbor3:         #DEDEDE     !default; // code/pre border
+$cmark:         #FFECCC     !default;
+
+$clink: #5D88D2 !default; // Else use steelblue
+
+//
+// Other
+//
+
+$radius-unit: .1em !default;
+
+@import
+    "../nudus-base/html_style_base"
+;
+
+//
+// Light style has following additions:
+//
+
+::selection {
+    color: #F1F1F1;
+}
diff --git a/resources/skins/nudus-light/metadata.xml b/resources/skins/nudus-light/metadata.xml
new file mode 100644
index 000000000..d26cbaed0
--- /dev/null
+++ b/resources/skins/nudus-light/metadata.xml
@@ -0,0 +1,13 @@
+
+
+  
+    akinokonomi
+  
+  
+    #3A6FE4
+    #F0F2FC
+    #E74343
+    #FFD7D7
+    #77dd77
+  
+
diff --git a/resources/skins/nudus-light/qt_style.qss b/resources/skins/nudus-light/qt_style.qss
new file mode 100644
index 000000000..c6342bc9c
--- /dev/null
+++ b/resources/skins/nudus-light/qt_style.qss
@@ -0,0 +1,14 @@
+QPlainTextEdit:focus {
+    border: 1px solid palette(highlight);
+}
+
+QSplitter::handle {
+  background: palette(dark);
+}
+
+/*
+ * For `qt5-styleplugins`: motif, cde, gtk2, etc.
+ */
+QStatusBar::item {
+    border: none;
+}
diff --git a/resources/skins/nudus/html_enclosure_every.html b/resources/skins/nudus/html_enclosure_every.html
deleted file mode 100644
index c1827e96e..000000000
--- a/resources/skins/nudus/html_enclosure_every.html
+++ /dev/null
@@ -1 +0,0 @@
- / %2%3
\ No newline at end of file
diff --git a/resources/skins/nudus/html_single_message.html b/resources/skins/nudus/html_single_message.html
deleted file mode 100644
index 4bb7d2c83..000000000
--- a/resources/skins/nudus/html_single_message.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
%7
- %2 -

%1%6 / URL

- %5 -
-
-
- %4 -
-
diff --git a/resources/skins/nudus/html_wrapper.html b/resources/skins/nudus/html_wrapper.html deleted file mode 100644 index c227d95d7..000000000 --- a/resources/skins/nudus/html_wrapper.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - %1 - - - - %2 - - - diff --git a/resources/skins/nudus/metadata.xml b/resources/skins/nudus/metadata.xml deleted file mode 100644 index 7fd862261..000000000 --- a/resources/skins/nudus/metadata.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - Maki Blackwell - - - #3A4EE4 - #ff66cc - #4EE43A - #ff99ff - #00ff99 - - \ No newline at end of file diff --git a/resources/skins/nudus/qt_style.qss b/resources/skins/nudus/qt_style.qss deleted file mode 100644 index 656bfe9c8..000000000 --- a/resources/skins/nudus/qt_style.qss +++ /dev/null @@ -1,11 +0,0 @@ -QTextEdit { - selection-background-color: #4861f0; -} - -QStatusBar::item { - border: none; -} - -QSplitter::handle { - background: rgba(117, 117, 117, 0.5); -} diff --git a/resources/skins/vergilius/html_single_message.html b/resources/skins/vergilius/html_single_message.html index 2f4f60b60..95119a5ad 100644 --- a/resources/skins/vergilius/html_single_message.html +++ b/resources/skins/vergilius/html_single_message.html @@ -1,4 +1,4 @@ -
+
%1 🔗URL %6
diff --git a/resources/skins/vergilius/metadata.xml b/resources/skins/vergilius/metadata.xml index 78fb75561..8f5188f57 100644 --- a/resources/skins/vergilius/metadata.xml +++ b/resources/skins/vergilius/metadata.xml @@ -1,13 +1,13 @@ - + Martin Rotter - #3A4EE4 - #ff66cc - #4EE43A - #ff99ff - #00ff99 + #3A4EE4 + #F0F2FC + #DF5656 + #FFD7D7 + #77dd77 \ No newline at end of file diff --git a/src/librssguard/miscellaneous/skinfactory.cpp b/src/librssguard/miscellaneous/skinfactory.cpp index e63973c3e..d06929087 100644 --- a/src/librssguard/miscellaneous/skinfactory.cpp +++ b/src/librssguard/miscellaneous/skinfactory.cpp @@ -68,36 +68,40 @@ void SkinFactory::loadSkinFromData(const Skin& skin) { qDebugNN << LOGSEC_GUI << "Activating dark palette for Fusion style."; QPalette fusion_palette = qApp->palette(); - QColor clr_bg(QSL("#2D2F32")); + QColor clr_maibg(QSL("#2D2F32")); + QColor clr_basbg(QSL("#373A3D")); QColor clr_altbg(QSL("#323437")); QColor clr_selbg(QSL("#8291AD")); - QColor clr_fg(QSL("#D8D8D8")); - QColor clr_brdr(QSL("#585C65")); - QColor clr_tooltip_brdr(QSL("#707580")); - QColor clr_link(QSL("#a1acc1")); - QColor clr_dis_fg(QSL("#727272")); + QColor clr_selfg(QSL("#FFFFFF")); + QColor clr_btnfg(QSL("#E7E7E7")); + QColor clr_dibfg(QSL("#A7A7A7")); + QColor clr_winfg(QSL("#D8D8D8")); + QColor clr_diwfg(QSL("#999999")); + QColor clr_brdbg(QSL("#202224")); // Use colour picker on dark brdr under list header for this one + QColor clr_wlink(QSL("#a1acc1")); // // Normal state. // // Backgrounds & bases. - fusion_palette.setColor(QPalette::ColorRole::Window, clr_bg); - fusion_palette.setColor(QPalette::ColorRole::Base, clr_bg); - fusion_palette.setColor(QPalette::ColorRole::Dark, clr_bg); + fusion_palette.setColor(QPalette::ColorRole::Window, clr_maibg); + fusion_palette.setColor(QPalette::ColorRole::Base, clr_basbg); + fusion_palette.setColor(QPalette::ColorRole::Dark, clr_brdbg); fusion_palette.setColor(QPalette::ColorRole::AlternateBase, clr_altbg); - fusion_palette.setColor(QPalette::ColorRole::Button, clr_altbg); - fusion_palette.setColor(QPalette::ColorRole::Highlight, clr_selbg); + fusion_palette.setColor(QPalette::ColorRole::Button, clr_altbg); + fusion_palette.setColor(QPalette::ColorRole::Light, clr_altbg); // Bright + fusion_palette.setColor(QPalette::ColorRole::Highlight, clr_selbg); // Texts. - fusion_palette.setColor(QPalette::ColorRole::WindowText, clr_fg); - fusion_palette.setColor(QPalette::ColorRole::ButtonText, clr_fg); - fusion_palette.setColor(QPalette::ColorRole::BrightText, clr_fg); - fusion_palette.setColor(QPalette::ColorRole::Text, clr_fg); - fusion_palette.setColor(QPalette::ColorRole::PlaceholderText, clr_fg); - fusion_palette.setColor(QPalette::ColorRole::Link, clr_link); - fusion_palette.setColor(QPalette::ColorRole::LinkVisited, clr_link); - fusion_palette.setColor(QPalette::ColorRole::HighlightedText, clr_fg); + fusion_palette.setColor(QPalette::ColorRole::ButtonText, clr_btnfg); + fusion_palette.setColor(QPalette::ColorRole::WindowText, clr_winfg); + fusion_palette.setColor(QPalette::ColorRole::BrightText, clr_basbg); + fusion_palette.setColor(QPalette::ColorRole::Text, clr_winfg); // Normal text + fusion_palette.setColor(QPalette::ColorRole::PlaceholderText, clr_dibfg); + fusion_palette.setColor(QPalette::ColorRole::Link, clr_wlink); + fusion_palette.setColor(QPalette::ColorRole::LinkVisited, clr_wlink); + fusion_palette.setColor(QPalette::ColorRole::HighlightedText, clr_selfg); // // Inactive state. @@ -112,29 +116,30 @@ void SkinFactory::loadSkinFromData(const Skin& skin) { // // Backgrounds & bases. - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Window, clr_altbg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Base, clr_altbg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Dark, clr_altbg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Window, clr_maibg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Base, clr_basbg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Dark, clr_brdbg); fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::AlternateBase, clr_altbg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Button, Qt::GlobalColor::red); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Highlight, clr_selbg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Button, clr_altbg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Light, clr_altbg); // Bright + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Highlight, clr_selbg); // Texts. - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::WindowText, clr_dis_fg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText, clr_dis_fg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::BrightText, clr_fg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Text, clr_dis_fg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::PlaceholderText, clr_fg); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Link, clr_link); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::LinkVisited, clr_link); - fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::HighlightedText, clr_fg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText, clr_dibfg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::WindowText, clr_diwfg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::BrightText, clr_basbg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Text, clr_diwfg); // Normal text + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::PlaceholderText, clr_dibfg); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Link, clr_wlink); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::LinkVisited, clr_wlink); + fusion_palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::HighlightedText, clr_selfg); // // Tooltips. // - fusion_palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::ToolTipBase, clr_bg); - fusion_palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::ToolTipText, clr_fg); + fusion_palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::ToolTipBase, clr_maibg); + fusion_palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::ToolTipText, clr_winfg); QToolTip::setPalette(fusion_palette); qApp->setPalette(fusion_palette); From 24db7c5371cdd8924f3d6e03515869113198d169 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Fri, 10 Dec 2021 12:07:26 +0100 Subject: [PATCH 04/19] unify scrape scripts --- .../desktop/com.github.rssguard.appdata.xml | 2 +- resources/scripts/scrapers/scrape-as-rss2.py | 55 ----------- .../scripts/scrapers/scrape-full-articles.py | 96 +++++++++++++++++++ resources/scripts/scrapers/scrape-rss2.py | 56 ----------- 4 files changed, 97 insertions(+), 112 deletions(-) delete mode 100644 resources/scripts/scrapers/scrape-as-rss2.py create mode 100644 resources/scripts/scrapers/scrape-full-articles.py delete mode 100644 resources/scripts/scrapers/scrape-rss2.py diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index de4a88404..091df44fe 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/scripts/scrapers/scrape-as-rss2.py b/resources/scripts/scrapers/scrape-as-rss2.py deleted file mode 100644 index 8defd7659..000000000 --- a/resources/scripts/scrapers/scrape-as-rss2.py +++ /dev/null @@ -1,55 +0,0 @@ -# Downloads full articles for RSS 2.0 feed and replaces original articles. -# -# Make sure to have all dependencies installed: -# pip3 install asyncio (if using parallel version of the script) -# -# You must provide raw RSS 2.0 UTF-8 feed XML data as input, for example with curl: -# curl 'http://rss.cnn.com/rss/edition.rss' | python ./scrape-rss2.py "4" -# -# You must provide three command line arguments: -# scrape-rss2.py [NUMBER-OF-PARALLEL-THREADS] - -import json -import re -import sys -import time -import html -import urllib.request -import distutils.util -import xml.etree.ElementTree as ET - -no_threads = int(sys.argv[1]) - -if no_threads > 1: - import asyncio - from concurrent.futures import ThreadPoolExecutor - -sys.stdin.reconfigure(encoding='utf-8') -rss_data = sys.stdin.read() -rss_document = ET.fromstring(rss_data) - -def process_article(article): - try: - link = "https://us-central1-technews-251304.cloudfunctions.net/article-parser?url=" + article.find("link").text - response = urllib.request.urlopen(link) - text = response.read().decode("utf-8") - js = json.loads(text) - - if int(js["error"]) == 0: - article.find("description").text = js["data"]["content"] - except: - pass - -# Scrape articles. -if no_threads > 1: - with ThreadPoolExecutor(max_workers = no_threads) as executor: - futures = [] - for article in rss_document.findall(".//item"): - futures.append(executor.submit(process_article, article)) - for future in futures: - future.result() -else: - for article in rss_document.findall(".//item"): - process_article(article) - -print(ET.tostring(rss_document, encoding = "unicode")) \ No newline at end of file diff --git a/resources/scripts/scrapers/scrape-full-articles.py b/resources/scripts/scrapers/scrape-full-articles.py new file mode 100644 index 000000000..a40b69f24 --- /dev/null +++ b/resources/scripts/scrapers/scrape-full-articles.py @@ -0,0 +1,96 @@ +# Downloads full (HTML) articles for ATOM or RSS 2.0 feed and replaces original articles. +# +# Make sure to have all dependencies installed: +# pip3 install asyncio (if using parallel version of the script) +# +# You must provide raw ATOM or RSS 2.0 UTF-8 feed XML data as input, for example with curl: +# curl 'http://rss.cnn.com/rss/edition.rss' | python ./scrape-full-articles.py "4" +# +# You must provide three command line arguments: +# scrape-full-articles.py [NUMBER-OF-PARALLEL-THREADS] + +import json +import sys +import urllib.request +import xml.etree.ElementTree as ET + +# Globals. +atom_ns = {"atom": "http://www.w3.org/2005/Atom"} +article_parser_url = "https://us-central1-technews-251304.cloudfunctions.net/article-parser?url=" + + +# Methods. +def process_article(article, is_rss, is_atom): + try: + # Extract link. + scraped_article = "" + + if is_rss: + article_link = article.find("link").text + elif is_atom: + article_link = article.find("atom:link", atom_ns).attrib['href'] + + # Scrape with article-parser. + link = article_parser_url + article_link + + response = urllib.request.urlopen(link) + text = response.read().decode("utf-8") + js = json.loads(text) + + if int(js["error"]) == 0: + scraped_article = js["data"]["content"] + + # Save scraped data. + if scraped_article: + if is_rss: + article.find("description").text = scraped_article + elif is_atom: + article.find("atom:content", atom_ns).text = scraped_article + except: + pass + + +def main(): + no_threads = int(sys.argv[1]) if len(sys.argv) >= 2 else 1 + + if no_threads > 1: + import asyncio + from concurrent.futures import ThreadPoolExecutor + + sys.stdin.reconfigure(encoding="utf-8") + + #feed_data = urllib.request.urlopen("https://dilbert.com/feed").read() + feed_data = sys.stdin.read() + feed_document = ET.fromstring(feed_data) + + # Determine feed type. + is_rss = feed_document.tag == "rss" + is_atom = feed_document.tag == "{http://www.w3.org/2005/Atom}feed" + + if not is_rss and not is_atom: + sys.exit("Passed file is neither ATOM nor RSS 2.0 feed.") + + # Extract articles. + if is_rss: + feed_articles = feed_document.findall(".//item") + elif is_atom: + feed_articles = feed_document.findall(".//atom:entry", atom_ns) + + # Scrape articles. + if no_threads > 1: + with ThreadPoolExecutor(max_workers=no_threads) as executor: + futures = [] + for article in feed_articles: + futures.append( + executor.submit(process_article, article, is_rss, is_atom)) + for future in futures: + future.result() + else: + for article in feed_articles: + process_article(article, is_rss, is_atom) + + print(ET.tostring(feed_document, encoding="unicode")) + + +if __name__ == '__main__': + main() diff --git a/resources/scripts/scrapers/scrape-rss2.py b/resources/scripts/scrapers/scrape-rss2.py deleted file mode 100644 index 63ab40eb7..000000000 --- a/resources/scripts/scrapers/scrape-rss2.py +++ /dev/null @@ -1,56 +0,0 @@ -# Downloads full articles for RSS 2.0 feed and replaces original articles. -# -# Make sure to have all dependencies installed: -# pip3 install newspaper3k -# pip3 install asyncio (if using parallel version of the script) -# -# You must provide raw RSS 2.0 UTF-8 feed XML data as input, for example with curl: -# curl 'http://rss.cnn.com/rss/edition.rss' | python ./scrape-rss2.py "4" -# -# You must provide three command line arguments: -# scrape-rss2.py [NUMBER-OF-PARALLEL-THREADS] - -import json -import re -import sys -import time -import html -import requests -import distutils.util -import xml.etree.ElementTree as ET -from newspaper import Article - -no_threads = int(sys.argv[1]) - -if no_threads > 1: - import asyncio - from concurrent.futures import ThreadPoolExecutor - -sys.stdin.reconfigure(encoding='utf-8') -rss_data = sys.stdin.read() -rss_document = ET.fromstring(rss_data) - -def process_article(article): - try: - link = article.find("link").text - - f = Article(link, keep_article_html = True) - f.download() - f.parse() - article.find("description").text = f.article_html - except: - pass - -# Scrape articles. -if no_threads > 1: - with ThreadPoolExecutor(max_workers = no_threads) as executor: - futures = [] - for article in rss_document.findall(".//item"): - futures.append(executor.submit(process_article, article)) - for future in futures: - future.result() -else: - for article in rss_document.findall(".//item"): - process_article(article) - -print(ET.tostring(rss_document, encoding = "unicode")) \ No newline at end of file From 94585e5f97e655d67657bb8ad298c9e28fa24b70 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 14 Dec 2021 11:34:54 +0100 Subject: [PATCH 05/19] use qsoundeffect as it has ALSA support for WAV files, use qmediaplayer for other files --- .../desktop/com.github.rssguard.appdata.xml | 2 +- .../miscellaneous/notification.cpp | 96 ++++++++++++------- 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 091df44fe..a72007242 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/src/librssguard/miscellaneous/notification.cpp b/src/librssguard/miscellaneous/notification.cpp index eba6229d2..c483d7d6d 100644 --- a/src/librssguard/miscellaneous/notification.cpp +++ b/src/librssguard/miscellaneous/notification.cpp @@ -8,6 +8,7 @@ #if !defined(Q_OS_OS2) #include +#include #if QT_VERSION_MAJOR == 6 #include @@ -36,50 +37,77 @@ void Notification::setSoundPath(const QString& sound_path) { void Notification::playSound(Application* app) const { if (!m_soundPath.isEmpty()) { #if !defined(Q_OS_OS2) - QMediaPlayer* play = new QMediaPlayer(app); + if (m_soundPath.endsWith(QSL(".wav"), Qt::CaseSensitivity::CaseInsensitive)) { + qDebugNN << LOGSEC_CORE << "Using QSoundEffect to play notification sound."; + + QSoundEffect* play = new QSoundEffect(app); + + QObject::connect(play, &QSoundEffect::playingChanged, play, [play]() { + if (!play->isPlaying()) { + play->deleteLater(); + } + }); + + if (m_soundPath.startsWith(QSL(":"))) { + play->setSource(QUrl(QSL("qrc") + m_soundPath)); + + } + else { + play->setSource(QUrl::fromLocalFile( + QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath)))); + } + + play->setVolume(m_volume); + play->play(); + } + else { + qDebugNN << LOGSEC_CORE << "Using QMediaPlayer to play notification sound."; + + QMediaPlayer* play = new QMediaPlayer(app); #if QT_VERSION_MAJOR == 6 - QAudioOutput* out = new QAudioOutput(app); + QAudioOutput* out = new QAudioOutput(app); - play->setAudioOutput(out); + play->setAudioOutput(out); + + QObject::connect(play, &QMediaPlayer::playbackStateChanged, play, [play, out](QMediaPlayer::PlaybackState state) { + if (state == QMediaPlayer::PlaybackState::StoppedState) { + out->deleteLater(); + play->deleteLater(); + } + }); + + if (m_soundPath.startsWith(QSL(":"))) { + play->setSource(QUrl(QSL("qrc") + m_soundPath)); - QObject::connect(play, &QMediaPlayer::playbackStateChanged, play, [play, out](QMediaPlayer::PlaybackState state) { - if (state == QMediaPlayer::PlaybackState::StoppedState) { - out->deleteLater(); - play->deleteLater(); } - }); + else { + play->setSource(QUrl::fromLocalFile(QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath)))); + } - if (m_soundPath.startsWith(QSL(":"))) { - play->setSource(QUrl(QSL("qrc") + m_soundPath)); - - } - else { - play->setSource(QUrl::fromLocalFile(QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath)))); - } - - play->audioOutput()->setVolume((m_volume * 1.0f) / 100.0f); - play->play(); + play->audioOutput()->setVolume((m_volume * 1.0f) / 100.0f); + play->play(); #else - QObject::connect(play, &QMediaPlayer::stateChanged, play, [play](QMediaPlayer::State state) { - if (state == QMediaPlayer::State::StoppedState) { - play->deleteLater(); + QObject::connect(play, &QMediaPlayer::stateChanged, play, [play](QMediaPlayer::State state) { + if (state == QMediaPlayer::State::StoppedState) { + play->deleteLater(); + } + }); + + if (m_soundPath.startsWith(QSL(":"))) { + play->setMedia(QMediaContent(QUrl(QSL("qrc") + m_soundPath))); + + } + else { + play->setMedia(QMediaContent( + QUrl::fromLocalFile( + QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath))))); } - }); - if (m_soundPath.startsWith(QSL(":"))) { - play->setMedia(QMediaContent(QUrl(QSL("qrc") + m_soundPath))); - - } - else { - play->setMedia(QMediaContent( - QUrl::fromLocalFile( - QDir::toNativeSeparators(app->replaceDataUserDataFolderPlaceholder(m_soundPath))))); - } - - play->setVolume(m_volume); - play->play(); + play->setVolume(m_volume); + play->play(); #endif + } #endif } } From 6dbe7a82dbf016c23d4fd8631b814dce2e97ac0e Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 14 Dec 2021 14:39:57 +0100 Subject: [PATCH 06/19] fix removing of db articles when older than days is 0 --- src/librssguard/database/databasequeries.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/librssguard/database/databasequeries.cpp b/src/librssguard/database/databasequeries.cpp index b93bf9863..725a40c08 100644 --- a/src/librssguard/database/databasequeries.cpp +++ b/src/librssguard/database/databasequeries.cpp @@ -431,7 +431,9 @@ bool DatabaseQueries::purgeReadMessages(const QSqlDatabase& db) { bool DatabaseQueries::purgeOldMessages(const QSqlDatabase& db, int older_than_days) { QSqlQuery q(db); - const qint64 since_epoch = QDateTime::currentDateTimeUtc().addDays(-older_than_days).toMSecsSinceEpoch(); + const qint64 since_epoch = older_than_days == 0 + ? QDateTime::currentDateTimeUtc().addYears(10).toMSecsSinceEpoch() + : QDateTime::currentDateTimeUtc().addDays(-older_than_days).toMSecsSinceEpoch(); q.setForwardOnly(true); q.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important AND date_created < :date_created;")); From b48f2477ae71dcc582a2fa9f623c893c01dbb37d Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Wed, 15 Dec 2021 08:06:38 +0100 Subject: [PATCH 07/19] allow launching of external programs in article filters --- .../desktop/com.github.rssguard.appdata.xml | 2 +- src/librssguard/core/filterutils.cpp | 19 +++++++++++++++++++ src/librssguard/core/filterutils.h | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index a72007242..ae258f422 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/src/librssguard/core/filterutils.cpp b/src/librssguard/core/filterutils.cpp index 76bfce161..d9854ed4e 100644 --- a/src/librssguard/core/filterutils.cpp +++ b/src/librssguard/core/filterutils.cpp @@ -9,6 +9,7 @@ #include #include #include +#include FilterUtils::FilterUtils(QObject* parent) : QObject(parent) {} @@ -84,3 +85,21 @@ QString FilterUtils::fromXmlToJson(const QString& xml) const { QDateTime FilterUtils::parseDateTime(const QString& dat) const { return TextFactory::parseDateTime(dat); } + +QString FilterUtils::runExecutableGetOutput(const QString& executable, const QStringList& arguments) const { + QProcess proc; + + proc.setProgram(executable); + proc.setArguments(arguments); + + proc.start(); + + if (proc.waitForFinished() && + proc.exitStatus() == QProcess::ExitStatus::NormalExit && + proc.exitCode() == EXIT_SUCCESS) { + return proc.readAllStandardOutput(); + } + else { + return proc.readAllStandardError().simplified(); + } +} diff --git a/src/librssguard/core/filterutils.h b/src/librssguard/core/filterutils.h index 565836254..0387dad9e 100644 --- a/src/librssguard/core/filterutils.h +++ b/src/librssguard/core/filterutils.h @@ -22,6 +22,7 @@ class FilterUtils : public QObject { // Parses string into date/time object. Q_INVOKABLE QDateTime parseDateTime(const QString& dat) const; + Q_INVOKABLE QString runExecutableGetOutput(const QString& executable, const QStringList& arguments = {}) const; }; #endif // FILTERUTILS_H From 7cf80321bdcd7fbc3cd696b1dfa7f38e3ffc50f7 Mon Sep 17 00:00:00 2001 From: martinrotter Date: Thu, 16 Dec 2021 08:08:00 +0100 Subject: [PATCH 08/19] Update Documentation.md --- resources/docs/Documentation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/docs/Documentation.md b/resources/docs/Documentation.md index 49ba1da3b..18c6ba07a 100644 --- a/resources/docs/Documentation.md +++ b/resources/docs/Documentation.md @@ -167,6 +167,7 @@ Note that `MessageObject` attributes which can be synchronized with service are | Method | `hostname()` | `String` | `utils.hostname()` | Returns name of your PC. | Method | `fromXmlToJson(String)` | `String` | `utils.fromXmlToJson('

hello

')` | Converts `XML` string into `JSON`. | Method | `parseDateTime(String)` | `Date` | `utils.parseDateTime('2020-02-24T08:00:00')` | Converts textual date/time representation into proper `Date` object. +| Method | `runExecutableGetOutput(String, String[])` | `String` | `utils.runExecutableGetOutput('cmd.exe', ['/c', 'dir'])` | Launches external executable with optional parameters, reads its standard output and returns the output when executable finishes. #### Examples Accept only messages/articles from "Bob", while also mark them "important": From d001ca63781072f9ec9d975da02588752f81b673 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Thu, 16 Dec 2021 09:44:42 +0100 Subject: [PATCH 09/19] use same UA for all network stuff in rss guard, also make UA to be more standard format --- pri/vars.pri | 2 +- resources/desktop/com.github.rssguard.appdata.xml | 2 +- src/librssguard/definitions/definitions.h | 6 ++++++ src/librssguard/network-web/basenetworkaccessmanager.cpp | 6 +++++- src/librssguard/network-web/cookiejar.h | 2 ++ src/librssguard/network-web/networkurlinterceptor.cpp | 5 +++++ 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/pri/vars.pri b/pri/vars.pri index d3c589c4d..005e0e4ba 100644 --- a/pri/vars.pri +++ b/pri/vars.pri @@ -11,7 +11,7 @@ APP_URL = "https://github.com/martinrotter/rssguard" APP_URL_ISSUES = "https://github.com/martinrotter/rssguard/issues" APP_URL_ISSUES_NEW = "https://github.com/martinrotter/rssguard/issues/new" APP_URL_DOCUMENTATION = "https://github.com/martinrotter/rssguard/blob/master/resources/docs/Documentation.md" -APP_USERAGENT = "RSS Guard/$$APP_VERSION (github.com/martinrotter/rssguard)" +APP_USERAGENT = "RSS Guard/$$APP_VERSION" APP_DONATE_URL = "https://martinrotter.github.io/donate" message($$MSG_PREFIX: Welcome RSS Guard qmake script.) diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index ae258f422..30c77e7f2 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/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index 566306565..f786f5cd1 100644 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -143,6 +143,12 @@ #define DEFAULT_ZOOM_FACTOR 1.0f #define ZOOM_FACTOR_STEP 0.1f +#if defined(USE_WEBENGINE) +#define HTTP_COMPLETE_USERAGENT (QWebEngineProfile::defaultProfile()->httpUserAgent().toLocal8Bit() + QByteArrayLiteral(" ") + QByteArrayLiteral(APP_USERAGENT)) +#else +#define HTTP_COMPLETE_USERAGENT (QByteArrayLiteral(APP_USERAGENT)) +#endif + #define INTERNAL_URL_MESSAGE "http://rssguard.message" #define INTERNAL_URL_BLANK "http://rssguard.blank" #define INTERNAL_URL_ADBLOCKED "http://rssguard.adblocked" diff --git a/src/librssguard/network-web/basenetworkaccessmanager.cpp b/src/librssguard/network-web/basenetworkaccessmanager.cpp index c2e33eb5c..7dc6759b6 100644 --- a/src/librssguard/network-web/basenetworkaccessmanager.cpp +++ b/src/librssguard/network-web/basenetworkaccessmanager.cpp @@ -9,6 +9,10 @@ #include #include +#if defined(USE_WEBENGINE) +#include +#endif + BaseNetworkAccessManager::BaseNetworkAccessManager(QObject* parent) : QNetworkAccessManager(parent) { connect(this, &BaseNetworkAccessManager::sslErrors, this, &BaseNetworkAccessManager::onSslErrors); @@ -61,7 +65,7 @@ QNetworkReply* BaseNetworkAccessManager::createRequest(QNetworkAccessManager::Op #endif new_request.setRawHeader(HTTP_HEADERS_COOKIE, QSL("JSESSIONID= ").toLocal8Bit()); - new_request.setRawHeader(HTTP_HEADERS_USER_AGENT, QSL(APP_USERAGENT).toLocal8Bit()); + new_request.setRawHeader(HTTP_HEADERS_USER_AGENT, HTTP_COMPLETE_USERAGENT); auto reply = QNetworkAccessManager::createRequest(op, new_request, outgoingData); return reply; diff --git a/src/librssguard/network-web/cookiejar.h b/src/librssguard/network-web/cookiejar.h index cfe9f57d8..8b6507e39 100644 --- a/src/librssguard/network-web/cookiejar.h +++ b/src/librssguard/network-web/cookiejar.h @@ -14,6 +14,8 @@ class CookieJar : public QNetworkCookieJar { virtual bool insertCookie(const QNetworkCookie& cookie); virtual bool updateCookie(const QNetworkCookie& cookie); virtual bool deleteCookie(const QNetworkCookie& cookie); + + public: static QList extractCookiesFromUrl(const QString& url); private: diff --git a/src/librssguard/network-web/networkurlinterceptor.cpp b/src/librssguard/network-web/networkurlinterceptor.cpp index 6d668cdc8..871d0153e 100644 --- a/src/librssguard/network-web/networkurlinterceptor.cpp +++ b/src/librssguard/network-web/networkurlinterceptor.cpp @@ -23,6 +23,8 @@ #include "miscellaneous/settings.h" #include "network-web/urlinterceptor.h" +#include + NetworkUrlInterceptor::NetworkUrlInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent), m_sendDnt(false) {} @@ -33,6 +35,9 @@ void NetworkUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { // NOTE: Here we can add custom headers for each webengine request, for example "User-Agent". + info.setHttpHeader(QByteArrayLiteral(HTTP_HEADERS_USER_AGENT), + HTTP_COMPLETE_USERAGENT); + for (UrlInterceptor* interceptor : qAsConst(m_interceptors)) { interceptor->interceptRequest(info); } From 465c0d81e1442322136383d7aad73233234e04b0 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Thu, 16 Dec 2021 10:02:49 +0100 Subject: [PATCH 10/19] use bit better place to use UA --- src/librssguard/miscellaneous/application.cpp | 2 ++ src/librssguard/network-web/networkurlinterceptor.cpp | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index f06c00943..68b5be343 100644 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -101,6 +101,8 @@ Application::Application(const QString& id, int& argc, char** argv) #if defined(USE_WEBENGINE) m_webFactory->urlIinterceptor()->load(); + QWebEngineProfile::defaultProfile()->setHttpUserAgent(QString(HTTP_COMPLETE_USERAGENT)); + connect(QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested, this, &Application::downloadRequested); connect(m_webFactory->adBlock(), &AdBlockManager::processTerminated, this, &Application::onAdBlockFailure); diff --git a/src/librssguard/network-web/networkurlinterceptor.cpp b/src/librssguard/network-web/networkurlinterceptor.cpp index 871d0153e..c7656e58f 100644 --- a/src/librssguard/network-web/networkurlinterceptor.cpp +++ b/src/librssguard/network-web/networkurlinterceptor.cpp @@ -35,9 +35,6 @@ void NetworkUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { // NOTE: Here we can add custom headers for each webengine request, for example "User-Agent". - info.setHttpHeader(QByteArrayLiteral(HTTP_HEADERS_USER_AGENT), - HTTP_COMPLETE_USERAGENT); - for (UrlInterceptor* interceptor : qAsConst(m_interceptors)) { interceptor->interceptRequest(info); } From d85683178b35819681aa8a81acbeb9042ec67482 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Thu, 16 Dec 2021 10:21:46 +0100 Subject: [PATCH 11/19] add more items to "web settings" context menu - now allows to disable internal PDF viewer etc. --- src/librssguard/network-web/webfactory.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/librssguard/network-web/webfactory.cpp b/src/librssguard/network-web/webfactory.cpp index a9d5c47f1..ee08d7f59 100644 --- a/src/librssguard/network-web/webfactory.cpp +++ b/src/librssguard/network-web/webfactory.cpp @@ -315,6 +315,25 @@ void WebFactory::createMenu(QMenu* menu) { actions << createEngineSettingsAction(tr("Allow geolocation on insecure origins"), QWebEngineSettings::WebAttribute::AllowGeolocationOnInsecureOrigins); #endif +#if QT_VERSION >= 0x050A00 // Qt >= 5.10.0 + actions << createEngineSettingsAction(tr("JS can activate windows"), QWebEngineSettings::WebAttribute::AllowWindowActivationFromJavaScript); + actions << createEngineSettingsAction(tr("Show scrollbars"), QWebEngineSettings::WebAttribute::ShowScrollBars); +#endif + +#if QT_VERSION >= 0x050B00 // Qt >= 5.11.0 + actions << createEngineSettingsAction(tr("Media playback with gestures"), QWebEngineSettings::WebAttribute::PlaybackRequiresUserGesture); + actions << createEngineSettingsAction(tr("WebRTC uses only public interfaces"), QWebEngineSettings::WebAttribute::WebRTCPublicInterfacesOnly); + actions << createEngineSettingsAction(tr("JS can paste from clipboard"), QWebEngineSettings::WebAttribute::JavascriptCanPaste); +#endif + +#if QT_VERSION >= 0x050C00 // Qt >= 5.12.0 + actions << createEngineSettingsAction(tr("DNS prefetch enabled"), QWebEngineSettings::WebAttribute::DnsPrefetchEnabled); +#endif + +#if QT_VERSION >= 0x050D00 // Qt >= 5.13.0 + actions << createEngineSettingsAction(tr("PDF viewer enabled"), QWebEngineSettings::WebAttribute::PdfViewerEnabled); +#endif + menu->addActions(actions); } From 38989fa8842342894753422785c1a3f54419eece Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Mon, 20 Dec 2021 10:26:26 +0100 Subject: [PATCH 12/19] determine root global node.js package location in runtime instead of hardcoding it --- .../desktop/com.github.rssguard.appdata.xml | 2 +- src/librssguard/core/filterutils.cpp | 17 ++----------- src/librssguard/miscellaneous/iofactory.cpp | 21 ++++++++++++++++ src/librssguard/miscellaneous/iofactory.h | 1 + .../network-web/adblock/adblockmanager.cpp | 25 ++++++++++--------- 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 30c77e7f2..76c5137a0 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/src/librssguard/core/filterutils.cpp b/src/librssguard/core/filterutils.cpp index d9854ed4e..e2e19fd9b 100644 --- a/src/librssguard/core/filterutils.cpp +++ b/src/librssguard/core/filterutils.cpp @@ -3,6 +3,7 @@ #include "core/filterutils.h" #include "definitions/definitions.h" +#include "miscellaneous/iofactory.h" #include "miscellaneous/textfactory.h" #include @@ -87,19 +88,5 @@ QDateTime FilterUtils::parseDateTime(const QString& dat) const { } QString FilterUtils::runExecutableGetOutput(const QString& executable, const QStringList& arguments) const { - QProcess proc; - - proc.setProgram(executable); - proc.setArguments(arguments); - - proc.start(); - - if (proc.waitForFinished() && - proc.exitStatus() == QProcess::ExitStatus::NormalExit && - proc.exitCode() == EXIT_SUCCESS) { - return proc.readAllStandardOutput(); - } - else { - return proc.readAllStandardError().simplified(); - } + return IOFactory::startProcessGetOutput(executable, arguments); } diff --git a/src/librssguard/miscellaneous/iofactory.cpp b/src/librssguard/miscellaneous/iofactory.cpp index 8dacb3b47..d8a144ac4 100644 --- a/src/librssguard/miscellaneous/iofactory.cpp +++ b/src/librssguard/miscellaneous/iofactory.cpp @@ -93,6 +93,27 @@ bool IOFactory::startProcessDetached(const QString& program, const QStringList& return process.startDetached(nullptr); } +QString IOFactory::startProcessGetOutput(const QString& executable, const QStringList& arguments) { + QProcess proc; + + proc.setProgram(executable); + proc.setArguments(arguments); + proc.setProcessEnvironment(QProcessEnvironment::systemEnvironment()); + + proc.start(); + + if (proc.waitForFinished() && + proc.exitStatus() == QProcess::ExitStatus::NormalExit && + proc.exitCode() == EXIT_SUCCESS) { + return proc.readAllStandardOutput(); + } + else { + QString err = proc.readAllStandardError().simplified(); + + return err; + } +} + QByteArray IOFactory::readFile(const QString& file_path) { QFile input_file(file_path); QByteArray input_data; diff --git a/src/librssguard/miscellaneous/iofactory.h b/src/librssguard/miscellaneous/iofactory.h index a3dd426e5..27ecb29b7 100644 --- a/src/librssguard/miscellaneous/iofactory.h +++ b/src/librssguard/miscellaneous/iofactory.h @@ -31,6 +31,7 @@ class IOFactory { const QStringList& arguments, const QString& native_arguments = {}, const QString& working_directory = {}); + static QString startProcessGetOutput(const QString& executable, const QStringList& arguments = {}); // Returns contents of a file. // Throws exception when no such file exists. diff --git a/src/librssguard/network-web/adblock/adblockmanager.cpp b/src/librssguard/network-web/adblock/adblockmanager.cpp index 34760c9e6..d542972f6 100644 --- a/src/librssguard/network-web/adblock/adblockmanager.cpp +++ b/src/librssguard/network-web/adblock/adblockmanager.cpp @@ -296,19 +296,20 @@ QProcess* AdBlockManager::startServer(int port) { proc->setProcessEnvironment(QProcessEnvironment::systemEnvironment()); auto pe = proc->processEnvironment(); - QString default_node_path = -#if defined(Q_OS_WIN) - pe.value(QSL("APPDATA")) + QDir::separator() + QSL("npm") + QDir::separator() + QSL("node_modules"); -#elif defined(Q_OS_LINUX) - QSL("/usr/lib/node_modules"); -#elif defined(Q_OS_MACOS) - QSL("/usr/local/lib/node_modules"); -#else - QSL(""); -#endif - if (!pe.contains(QSL("NODE_PATH")) && !default_node_path.isEmpty()) { - pe.insert(QSL("NODE_PATH"), default_node_path); + if (!pe.contains(QSL("NODE_PATH"))) { + const QString system_node_prefix = IOFactory::startProcessGetOutput( +#if defined(Q_OS_WIN) + QSL("npm.cmd") +#else + QSL("npm") +#endif + , { QSL("root"), QSL("--quiet"), QSL("-g") } + ); + + if (!system_node_prefix.isEmpty()) { + pe.insert(QSL("NODE_PATH"), system_node_prefix.simplified()); + } } proc->setProcessEnvironment(pe); From bd7cb87525b7b91409992bdfa46d2c4e77479bf3 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Mon, 20 Dec 2021 10:44:56 +0100 Subject: [PATCH 13/19] update docs --- resources/docs/Documentation.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/docs/Documentation.md b/resources/docs/Documentation.md index 18c6ba07a..dee1669b3 100644 --- a/resources/docs/Documentation.md +++ b/resources/docs/Documentation.md @@ -400,12 +400,15 @@ RSS Guard is distributed in two variants: If you're not sure which version to use, **use the WebEngine-based RSS Guard**. -#### AdBlock +#### AdBlock [Web-based variant](#webb) of RSS Guard offers ad-blocking functionality via [Adblocker](https://github.com/cliqz-oss/adblocker). Adblocker offers similar performance to [uBlock Origin](https://github.com/gorhill/uBlock). -You need to have have [Node.js](https://nodejs.org) with [NPM](https://www.npmjs.com) (which is usually included in Node.js installer) installed to have ad-blocking in RSS Guard working. Also, the implementation requires additional [npm](https://www.npmjs.com) modules to be installed. You see the list of needed modules near the top of [this](https://github.com/martinrotter/rssguard/blob/master/resources/scripts/adblock/adblock-server.js) file. +If you want to enable AdBlock in RSS Guard you need to do this: -I understand that the above installation of needed dependencies is not trivial, but it is necessary evil to have up-to-date and modern implementation of AdBlock in RSS Guard. Previous, "C++"-based, implementation was buggy, quite slow, and hard to maintain. +1. Have [Node.js](https://nodejs.org) with [NPM](https://www.npmjs.com) (which is usually included in Node.js installer) installed. Also you need to have paths `node.exe` and `npm` added to your system `PATH` environment available. +2. The implementation requires additional [npm](https://www.npmjs.com) modules to be installed. You see the list of needed modules near the top of [this](https://github.com/martinrotter/rssguard/blob/master/resources/scripts/adblock/adblock-server.js) file. + +I understand that the above installation is not trivial, but it is necessary evil to have up-to-date and modern implementation of AdBlock in RSS Guard. Previous, `C++`-based, implementation was buggy, slow, and hard to maintain. You can find elaborate lists of AdBlock rules [here](https://easylist.to). You can just copy direct hyperlinks to those lists and paste them into the "Filter lists" text-box as shown below. Remember to always separate individual links with newlines. Same applies to "Custom filters", where you can insert individual filters, for example [filter](https://adblockplus.org/filter-cheatsheet) "idnes" to block all URLs with "idnes" in them. From b1f45d75fdf0751a5c7b1912c7157defa9ed69ba Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 21 Dec 2021 08:08:45 +0100 Subject: [PATCH 14/19] improve app behavior when compiled statically --- pri/build_opts.pri | 4 ++++ resources/desktop/com.github.rssguard.appdata.xml | 2 +- src/rssguard/main.cpp | 7 +++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pri/build_opts.pri b/pri/build_opts.pri index 2a36951cc..6bf2b16ce 100644 --- a/pri/build_opts.pri +++ b/pri/build_opts.pri @@ -82,6 +82,10 @@ win32 { # Additionally link against Shell32. LIBS *= Shell32.lib + + static { + LIBS *= -lodbc32 + } } static { diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 76c5137a0..4ea14d24b 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/src/rssguard/main.cpp b/src/rssguard/main.cpp index 31be1e13c..4b8fd58a7 100644 --- a/src/rssguard/main.cpp +++ b/src/rssguard/main.cpp @@ -30,6 +30,13 @@ int main(int argc, char* argv[]) { QApplication::setDesktopFileName(APP_DESKTOP_ENTRY_FILE); #endif +#if defined(QT_STATIC) + // NOTE: Add all used resources here. + Q_INIT_RESOURCE(icons); + Q_INIT_RESOURCE(sql); + Q_INIT_RESOURCE(rssguard); +#endif + // Ensure that ini format is used as application settings storage on macOS. QSettings::setDefaultFormat(QSettings::IniFormat); From e9302cdbbada6316d7a5d2b01ef52c6e19bc02de Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 21 Dec 2021 09:18:10 +0100 Subject: [PATCH 15/19] fix some warnings --- src/librssguard/gui/settings/settingsgui.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/librssguard/gui/settings/settingsgui.cpp b/src/librssguard/gui/settings/settingsgui.cpp index 8703f8c74..3fa6fe63f 100644 --- a/src/librssguard/gui/settings/settingsgui.cpp +++ b/src/librssguard/gui/settings/settingsgui.cpp @@ -39,7 +39,6 @@ SettingsGui::SettingsGui(Settings* settings, QWidget* parent) : SettingsPanel(se // Setup skins. m_ui->m_treeSkins->header()->setSectionResizeMode(0, QHeaderView::ResizeMode::ResizeToContents); - m_ui->m_treeSkins->header()->setSectionResizeMode(1, QHeaderView::ResizeMode::ResizeToContents); m_ui->m_treeSkins->header()->setSectionResizeMode(2, QHeaderView::ResizeMode::ResizeToContents); @@ -238,7 +237,7 @@ void SettingsGui::loadSettings() { clr_btn->setObjectName(QString::number(enumer.value(i))); clr_btn->setColor(clr); - auto* lay = new QHBoxLayout(this); + auto* lay = new QHBoxLayout(); lay->addWidget(clr_btn); lay->addWidget(rst_btn); @@ -251,7 +250,6 @@ void SettingsGui::loadSettings() { m_ui->m_layoutCustomColors->setLayout(row, QFormLayout::ItemRole::FieldRole, lay); - } onEndLoadSettings(); From 7049e3ca6c5d3db69ea0bb01d987e823ebf6647b Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 21 Dec 2021 14:46:27 +0100 Subject: [PATCH 16/19] some work on feed list filtering, now features "show unread only" and regex filtering actually works together, still thinking about how to solve #546 --- src/librssguard/core/feedsproxymodel.cpp | 27 ++++++++++++++++-------- src/librssguard/gui/feedsview.cpp | 4 +++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/librssguard/core/feedsproxymodel.cpp b/src/librssguard/core/feedsproxymodel.cpp index e57ba1fd5..15ff0ecc2 100644 --- a/src/librssguard/core/feedsproxymodel.cpp +++ b/src/librssguard/core/feedsproxymodel.cpp @@ -198,7 +198,15 @@ bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right bool FeedsProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { bool should_show = filterAcceptsRowInternal(source_row, source_parent); + qDebugNN << LOGSEC_CORE + << "Filter accepts row" + << QUOTE_W_SPACE(m_sourceModel->itemForIndex(m_sourceModel->index(source_row, 0, source_parent))->title()) + << "and filter result is:" + << QUOTE_W_SPACE_DOT(should_show); + if (should_show && m_hiddenIndices.contains(QPair(source_row, source_parent))) { + qDebugNN << LOGSEC_CORE << "Item was previously hidden and now shows up, expand."; + const_cast(this)->m_hiddenIndices.removeAll(QPair(source_row, source_parent)); // Load status. @@ -213,10 +221,6 @@ bool FeedsProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source } bool FeedsProxyModel::filterAcceptsRowInternal(int source_row, const QModelIndex& source_parent) const { - if (!m_showUnreadOnly) { - return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); - } - const QModelIndex idx = m_sourceModel->index(source_row, 0, source_parent); if (!idx.isValid()) { @@ -225,18 +229,23 @@ bool FeedsProxyModel::filterAcceptsRowInternal(int source_row, const QModelIndex const RootItem* item = m_sourceModel->itemForIndex(idx); - if (item->kind() != RootItem::Kind::Category && item->kind() != RootItem::Kind::Feed) { + if (item->kind() != RootItem::Kind::Category && + item->kind() != RootItem::Kind::Feed && + item->kind() != RootItem::Kind::Label) { // Some items are always visible. return true; } - else if (item->isParentOf(m_selectedItem) /* || item->isChildOf(m_selectedItem)*/ || m_selectedItem == item) { - // Currently selected item and all its parents and children must be displayed. - return true; + + if (!m_showUnreadOnly) { + // Take only regexp filtering into account. + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } else { // NOTE: If item has < 0 of unread message it may mean, that the count // of unread messages is not (yet) known, display that item too. - return item->countOfUnreadMessages() != 0; + return + item->countOfUnreadMessages() != 0 && + QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } } diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp index 10aa4933c..8ec8ddf46 100644 --- a/src/librssguard/gui/feedsview.cpp +++ b/src/librssguard/gui/feedsview.cpp @@ -550,7 +550,9 @@ void FeedsView::focusInEvent(QFocusEvent* event) { void FeedsView::expandItemDelayed(const QModelIndex& idx) { QTimer::singleShot(100, this, [=] { - setExpanded(m_proxyModel->mapFromSource(idx), true); + QModelIndex pidx = m_proxyModel->mapFromSource(idx); + + setExpanded(pidx, true); }); } From c7fb0d221ca3a4909def9e94a2cec621b87e8aab Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 21 Dec 2021 15:04:41 +0100 Subject: [PATCH 17/19] think --- src/librssguard/gui/feedsview.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp index 8ec8ddf46..bdae89b83 100644 --- a/src/librssguard/gui/feedsview.cpp +++ b/src/librssguard/gui/feedsview.cpp @@ -106,6 +106,14 @@ void FeedsView::saveExpandStates(RootItem* item) { QModelIndex source_index = sourceModel()->indexForItem(it); QModelIndex visible_index = model()->mapFromSource(source_index); + // TODO: Think. + + /* + if (isRowHidden(visible_index.row(), visible_index.parent())) { + continue; + } + */ + settings->setValue(GROUP(CategoriesExpandStates), setting_name, isExpanded(visible_index)); From 0a42d50e5439a6231fbc10fdba05b97446f7cbc9 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Wed, 22 Dec 2021 09:48:08 +0100 Subject: [PATCH 18/19] fix #562 --- resources/desktop/com.github.rssguard.appdata.xml | 2 +- resources/scripts/adblock/adblock-server.js | 2 +- .../gui/dialogs/formmessagefiltersmanager.cpp | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 4ea14d24b..490b57a02 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/scripts/adblock/adblock-server.js b/resources/scripts/adblock/adblock-server.js index 5555402d7..6b27ff13d 100644 --- a/resources/scripts/adblock/adblock-server.js +++ b/resources/scripts/adblock/adblock-server.js @@ -19,7 +19,7 @@ const fs = require('fs'); const tldts = require('tldts-experimental'); -const adblock = require('@cliqz/adblocker') +const adblock = require('@cliqz/adblocker'); const http = require('http'); const cluster = require('cluster'); diff --git a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp index 8261e5754..37faa4c83 100644 --- a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp +++ b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp @@ -152,8 +152,14 @@ void FormMessageFiltersManager::removeSelectedFilter() { return; } - m_reader->removeMessageFilter(fltr); - delete m_ui.m_listFilters->currentItem(); + if (MessageBox::show(this, QMessageBox::Icon::Question, tr("Are you sure?"), + tr("Do you really want to remove selected filter?"), + {}, fltr->name(), + QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, + QMessageBox::StandardButton::No) == QMessageBox::StandardButton::Yes) { + m_reader->removeMessageFilter(fltr); + delete m_ui.m_listFilters->currentItem(); + } } void FormMessageFiltersManager::loadFilters() { From b743c72a040733a2ea2846af6b92c225d7c93246 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Wed, 22 Dec 2021 14:21:21 +0100 Subject: [PATCH 19/19] fix problem with copying of new adblock js --- src/librssguard/miscellaneous/iofactory.cpp | 17 +++++++++++++++-- src/librssguard/miscellaneous/iofactory.h | 5 ++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/librssguard/miscellaneous/iofactory.cpp b/src/librssguard/miscellaneous/iofactory.cpp index d8a144ac4..34fe0211e 100644 --- a/src/librssguard/miscellaneous/iofactory.cpp +++ b/src/librssguard/miscellaneous/iofactory.cpp @@ -93,13 +93,18 @@ bool IOFactory::startProcessDetached(const QString& program, const QStringList& return process.startDetached(nullptr); } -QString IOFactory::startProcessGetOutput(const QString& executable, const QStringList& arguments) { +QString IOFactory::startProcessGetOutput(const QString& executable, + const QStringList& arguments, + const QProcessEnvironment& pe) { QProcess proc; proc.setProgram(executable); proc.setArguments(arguments); - proc.setProcessEnvironment(QProcessEnvironment::systemEnvironment()); + QProcessEnvironment system_pe = QProcessEnvironment::systemEnvironment(); + + system_pe.insert(pe); + proc.setProcessEnvironment(system_pe); proc.start(); if (proc.waitForFinished() && @@ -142,6 +147,14 @@ void IOFactory::writeFile(const QString& file_path, const QByteArray& data) { bool IOFactory::copyFile(const QString& source, const QString& destination) { if (QFile::exists(destination)) { + QFile file(destination); + + file.setPermissions(file.permissions() | + QFileDevice::WriteOwner | + QFileDevice::WriteUser | + QFileDevice::WriteGroup | + QFileDevice::WriteOther); + if (!QFile::remove(destination)) { return false; } diff --git a/src/librssguard/miscellaneous/iofactory.h b/src/librssguard/miscellaneous/iofactory.h index 27ecb29b7..06f2cd020 100644 --- a/src/librssguard/miscellaneous/iofactory.h +++ b/src/librssguard/miscellaneous/iofactory.h @@ -7,6 +7,7 @@ #include "definitions/definitions.h" +#include #include class IOFactory { @@ -31,7 +32,9 @@ class IOFactory { const QStringList& arguments, const QString& native_arguments = {}, const QString& working_directory = {}); - static QString startProcessGetOutput(const QString& executable, const QStringList& arguments = {}); + static QString startProcessGetOutput(const QString& executable, + const QStringList& arguments = {}, + const QProcessEnvironment& pe = {}); // Returns contents of a file. // Throws exception when no such file exists.