none
diff --git a/resources/docs/Documentation.md b/resources/docs/Documentation.md
index 49ba1da3b..dee1669b3 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":
@@ -399,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.
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/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/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/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
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
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 @@
+
\ 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
+ //
+ 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 @@
- /
\ 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 @@
-
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 @@
-
+
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/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/core/filterutils.cpp b/src/librssguard/core/filterutils.cpp
index 76bfce161..e2e19fd9b 100644
--- a/src/librssguard/core/filterutils.cpp
+++ b/src/librssguard/core/filterutils.cpp
@@ -3,12 +3,14 @@
#include "core/filterutils.h"
#include "definitions/definitions.h"
+#include "miscellaneous/iofactory.h"
#include "miscellaneous/textfactory.h"
#include
#include
#include
#include
+#include
FilterUtils::FilterUtils(QObject* parent) : QObject(parent) {}
@@ -84,3 +86,7 @@ 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 {
+ return IOFactory::startProcessGetOutput(executable, arguments);
+}
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
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;"));
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/gui/dialogs/formmessagefiltersmanager.cpp b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp
index 21466e898..37faa4c83 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")));
@@ -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() {
diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp
index 10aa4933c..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));
@@ -550,7 +558,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);
});
}
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();
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/miscellaneous/iofactory.cpp b/src/librssguard/miscellaneous/iofactory.cpp
index 8dacb3b47..34fe0211e 100644
--- a/src/librssguard/miscellaneous/iofactory.cpp
+++ b/src/librssguard/miscellaneous/iofactory.cpp
@@ -93,6 +93,32 @@ bool IOFactory::startProcessDetached(const QString& program, const QStringList&
return process.startDetached(nullptr);
}
+QString IOFactory::startProcessGetOutput(const QString& executable,
+ const QStringList& arguments,
+ const QProcessEnvironment& pe) {
+ QProcess proc;
+
+ proc.setProgram(executable);
+ proc.setArguments(arguments);
+
+ QProcessEnvironment system_pe = QProcessEnvironment::systemEnvironment();
+
+ system_pe.insert(pe);
+ proc.setProcessEnvironment(system_pe);
+ 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;
@@ -121,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 a3dd426e5..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,6 +32,9 @@ class IOFactory {
const QStringList& arguments,
const QString& native_arguments = {},
const QString& working_directory = {});
+ static QString startProcessGetOutput(const QString& executable,
+ const QStringList& arguments = {},
+ const QProcessEnvironment& pe = {});
// Returns contents of a file.
// Throws exception when no such file exists.
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
}
}
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);
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);
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..c7656e58f 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) {}
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);
}
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);