Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
5862478593 |
18 changed files with 214 additions and 264 deletions
33
.travis.yml
33
.travis.yml
|
@ -1,33 +0,0 @@
|
||||||
language: cpp
|
|
||||||
compiler: gcc
|
|
||||||
sudo: require
|
|
||||||
dist: trusty
|
|
||||||
before_install:
|
|
||||||
- sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y
|
|
||||||
- sudo apt-get update -qq
|
|
||||||
install:
|
|
||||||
- sudo apt-get -y install qt58base qt58webengine qt58quickcontrols
|
|
||||||
- source /opt/qt58/bin/qt58-env.sh
|
|
||||||
script:
|
|
||||||
- qmake PREFIX=/usr
|
|
||||||
- make -j4
|
|
||||||
- sudo make INSTALL_ROOT=appdir install ; sudo chown -R $USER appdir ; find appdir/
|
|
||||||
- wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
|
|
||||||
- chmod a+x linuxdeployqt*.AppImage
|
|
||||||
- unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
|
||||||
- "./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -qmldir=./qml/
|
|
||||||
-bundle-non-qt-libs"
|
|
||||||
- "./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -qmldir=./qml/
|
|
||||||
-appimage"
|
|
||||||
- find ./appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " "
|
|
||||||
-f 2-3 | sort | uniq
|
|
||||||
- mv FeedTheMonkey*.AppImage FeedTheMonkey.AppImage
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key:
|
|
||||||
secure: d+hHwOnmeLPVvuue6VDCs2LwLS+BFzJF/BB5iObtkCYBwQ8ybnVzUcgnjJKOt37SHI0T9kLegI+Lq/843ECYiGiDjQg4PvCF69V8ODgHv3v1qiN5oG/eroBXd83a0+xhi4BuJt0SwcV9mcv4uD9bCPhj944rmMLH+3qD4ysgImBmbYSbbLecE9+QAs7bfrCwQRfdCePBORX3FHa/p12NEtln7xv6ZRyku9LdJSzAcdgm4zc95ggTAVC1+aQB6J0q2QzWPlQcOkLx+ZYmOqClhbSMFpIyPXP8UpXjYyvUlTAd0+wH8BGf0O3lpOqACc7IKIbj9d5oPmghVZo55SyW+RR77G+az+IbGJ7iXZsMfQZsMvtB7hNYhNvUUxQrAau7Y/ve+6sMQmvA7aMHV8kDUvnNW/c2r2jAWwk+N8QzGcP/rclDCKeOWZqZABmrzTViXZVAeXh4hJ8r6mbq8iwagBUPCsVYhVuerQt/KIoWxyn6/1GmMfKGi3dA/v3u1qU61vzrz3yLlJBmUAVPxZdVmqfRweh4BXjImxFMFmf5PYm5FnDg1gmw8rWsgii7+IPYw7DjTAHpjYbtXvDwDgG1nRXiRp2TGtPPgKW1/Uk8r/j5vfB5WcEZ7exLUgsPPjny5MGvzjqOxeLvwK1Pg9jFBFXIx7l1tNMJQxQU0r3DmBg=
|
|
||||||
file: FeedTheMonkey.AppImage
|
|
||||||
on:
|
|
||||||
repo: jeena/FeedTheMonkey
|
|
||||||
skip_cleanup: true
|
|
||||||
draft: true
|
|
12
README.md
12
README.md
|
@ -1,6 +1,6 @@
|
||||||
# FeedTheMonkey
|
# FeedTheMonkey
|
||||||
|
|
||||||
<img align=right src="http://jeena.net/feedthemonkey/feedthemonkey-icon.png" width='256' alt='Icon'>
|
<img align=right src="http://jabs.nu/feedthemonkey/feedthemonkey-icon.png" width='256' alt='Icon'>
|
||||||
|
|
||||||
FeedTheMonkey is a desktop client for [TinyTinyRSS](http://tt-rss.org). That means that
|
FeedTheMonkey is a desktop client for [TinyTinyRSS](http://tt-rss.org). That means that
|
||||||
it doesn't work as a standalone feed reader but only as a client for the TinyTinyRSS API
|
it doesn't work as a standalone feed reader but only as a client for the TinyTinyRSS API
|
||||||
|
@ -11,12 +11,10 @@ to have Qt 5.6 installed to be able to compile and have a account on a TinyTinyR
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
If you run Linux then there is an AppImage on the [Latest release](https://github.com/jeena/FeedTheMonkey/releases/latest) page. You download it, make executable and are able to run, it should work on most of the distributions out there.
|
Download the latest release code from: https://github.com/jeena/FeedTheMonkey/releases/latest
|
||||||
|
|
||||||
For ArchLinux I package it and it's available on https://aur.archlinux.org/packages/feedthemonkey/
|
|
||||||
|
|
||||||
You can compile and install it everywhere Qt is suported, this means on macOS, Windows
|
You can compile and install it everywhere Qt is suported, this means on macOS, Windows
|
||||||
and Linux.
|
and Linux. For ArchLinux I package it and it's available on https://aur.archlinux.org/packages/feedthemonkey/
|
||||||
|
|
||||||
## Keyboard shortcuts
|
## Keyboard shortcuts
|
||||||
|
|
||||||
|
@ -43,13 +41,13 @@ the use on a desktop computer but I'd like to see it on a mobile device too.
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This file is part of FeedTheMonkey.
|
This file is part of FeedTheMonkey.
|
||||||
|
|
||||||
Copyright 2015-2017 Jeena
|
Copyright 2015 Jeena
|
||||||
|
|
||||||
FeedTheMonkey is free software: you can redistribute it and/or modify
|
FeedTheMonkey is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -25,12 +25,13 @@ html, body {
|
||||||
body {
|
body {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
word-wrap: break-word;
|
padding: 2em;
|
||||||
|
font-weight: lighter;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nightmode {
|
.nightmode {
|
||||||
background: #353535;
|
background: #111;
|
||||||
color: #ddd;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nightmode::-webkit-scrollbar {
|
.nightmode::-webkit-scrollbar {
|
||||||
|
@ -49,28 +50,32 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
font-weight: lighter;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#date {
|
||||||
|
border-bottom: 1px solid #aaa;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nightmode #date {
|
||||||
|
border-bottom-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
.starred:after {
|
.starred:after {
|
||||||
content: "*";
|
content: "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
|
||||||
padding: 2em;
|
|
||||||
border-bottom: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nightmode header {
|
|
||||||
border-bottom-color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
header p {
|
header p {
|
||||||
color: #666;
|
color: #aaa;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nightmode header p {
|
.nightmode header p {
|
||||||
|
@ -84,7 +89,6 @@ a {
|
||||||
|
|
||||||
article {
|
article {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin: 2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
article a {
|
article a {
|
||||||
|
|
|
@ -4,96 +4,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>TTRSS</title>
|
<title>TTRSS</title>
|
||||||
<link href="content.css" media="all" rel="stylesheet">
|
<link href="content.css" media="all" rel="stylesheet">
|
||||||
<script type="text/javascript">
|
<script type="text/javascript" src="content.js"></script>
|
||||||
/*
|
|
||||||
* This file is part of FeedTheMonkey.
|
|
||||||
*
|
|
||||||
* Copyright 2015 Jeena
|
|
||||||
*
|
|
||||||
* FeedTheMonkey is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* FeedTheMonkey is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with FeedTheMonkey. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function $(id) {
|
|
||||||
return document.getElementById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setArticle(article) {
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
|
|
||||||
$("date").innerHTML = "";
|
|
||||||
$("title").innerHTML = "";
|
|
||||||
$("title").href = "";
|
|
||||||
$("title").title = "";
|
|
||||||
$("feed_title").innerHTML = "";
|
|
||||||
$("author").innerHTML = "";
|
|
||||||
$("article").innerHTML = "";
|
|
||||||
|
|
||||||
if(article === "empty") {
|
|
||||||
|
|
||||||
$("article").innerHTML = "No unread articles to display.";
|
|
||||||
|
|
||||||
} else if(article === "loading") {
|
|
||||||
|
|
||||||
$("article").innerHTML = "Loading <blink>…</blink>";
|
|
||||||
|
|
||||||
} else if (article === "logout") {
|
|
||||||
|
|
||||||
} else if(article) {
|
|
||||||
|
|
||||||
$("date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000));
|
|
||||||
$("title").innerHTML = article.title;
|
|
||||||
$("title").href = article.link;
|
|
||||||
$("title").title = article.link;
|
|
||||||
$("feed_title").innerHTML = article.feed_title;
|
|
||||||
$("title").className = article.marked ? "starred" : "";
|
|
||||||
$("author").innerHTML = "";
|
|
||||||
if(article.author && article.author.length > 0)
|
|
||||||
$("author").innerHTML = "– " + article.author
|
|
||||||
$("article").innerHTML = article.content;
|
|
||||||
|
|
||||||
var as = $("article").getElementsByTagName("a");
|
|
||||||
for(var i = 0; i < as.length; i++) {
|
|
||||||
as[i].target = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFont(font, size) {
|
|
||||||
document.body.style.fontFamily = font;
|
|
||||||
document.body.style.fontSize = size + "pt";
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNightmode(nightmode) {
|
|
||||||
if(nightmode) document.body.className = "nightmode";
|
|
||||||
else document.body.className = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkKey(e) {
|
|
||||||
e = e || window.event;
|
|
||||||
|
|
||||||
if (e.keyCode === 37) {
|
|
||||||
window.location.href = "feedthemonkey:previous";
|
|
||||||
} else if (e.keyCode === 39) {
|
|
||||||
window.location.href = "feedthemonkey:next";
|
|
||||||
} else if(e.keyCode == 13) {
|
|
||||||
window.location.href = "feedthemonkey:open";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("keydown", checkKey);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||||
</head>
|
</head>
|
||||||
<body class=''>
|
<body class=''>
|
||||||
|
|
73
html/content.js
Normal file
73
html/content.js
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* This file is part of FeedTheMonkey.
|
||||||
|
*
|
||||||
|
* Copyright 2015 Jeena
|
||||||
|
*
|
||||||
|
* FeedTheMonkey is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* FeedTheMonkey is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with FeedTheMonkey. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function $(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setArticle(article) {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
|
$("date").innerHTML = "";
|
||||||
|
$("title").innerHTML = "";
|
||||||
|
$("title").href = "";
|
||||||
|
$("title").title = "";
|
||||||
|
$("feed_title").innerHTML = "";
|
||||||
|
$("author").innerHTML = "";
|
||||||
|
$("article").innerHTML = "";
|
||||||
|
|
||||||
|
if(article === "empty") {
|
||||||
|
|
||||||
|
$("article").innerHTML = "No unread articles to display.";
|
||||||
|
|
||||||
|
} else if(article === "loading") {
|
||||||
|
|
||||||
|
$("article").innerHTML = "Loading <blink>…</blink>";
|
||||||
|
|
||||||
|
} else if (article === "logout") {
|
||||||
|
|
||||||
|
} else if(article) {
|
||||||
|
|
||||||
|
$("date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000));
|
||||||
|
$("title").innerHTML = article.title;
|
||||||
|
$("title").href = article.link;
|
||||||
|
$("title").title = article.link;
|
||||||
|
$("feed_title").innerHTML = article.feed_title;
|
||||||
|
$("title").className = article.marked ? "starred" : "";
|
||||||
|
$("author").innerHTML = "";
|
||||||
|
if(article.author && article.author.length > 0)
|
||||||
|
$("author").innerHTML = "– " + article.author
|
||||||
|
$("article").innerHTML = article.content;
|
||||||
|
|
||||||
|
var as = $("article").getElementsByTagName("a");
|
||||||
|
for(var i = 0; i < as.length; i++) {
|
||||||
|
as[i].target = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFont(font, size) {
|
||||||
|
document.body.style.fontFamily = font;
|
||||||
|
document.body.style.fontSize = size + "pt";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNightmode(nightmode) {
|
||||||
|
if(nightmode) document.body.className = "nightmode";
|
||||||
|
else document.body.className = "";
|
||||||
|
}
|
|
@ -2,5 +2,6 @@
|
||||||
<qresource prefix="/html">
|
<qresource prefix="/html">
|
||||||
<file>content.css</file>
|
<file>content.css</file>
|
||||||
<file>content.html</file>
|
<file>content.html</file>
|
||||||
|
<file>content.js</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
|
Version=2.0.0
|
||||||
Comment=A desktop client for the TinyTinyRSS feed reader.
|
Comment=A desktop client for the TinyTinyRSS feed reader.
|
||||||
Exec=feedthemonkey
|
Exec=feedthemonkey
|
||||||
GenericName=Feed Reader
|
GenericName=Feed Reader
|
||||||
|
@ -8,4 +9,4 @@ NoDisplay=false
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Network;Qt;
|
Categories=Network;Qt
|
||||||
|
|
|
@ -17,11 +17,12 @@
|
||||||
* along with FeedTheMonkey. If not, see <http://www.gnu.org/licenses/>.
|
* along with FeedTheMonkey. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import QtWebEngine 1.8
|
import QtWebEngine 1.0
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 1.3
|
import QtQuick.Controls 1.3
|
||||||
import QtQuick.Layouts 1.1
|
import QtQuick.Layouts 1.1
|
||||||
import QtQuick.Controls.Styles 1.3
|
import QtQuick.Controls.Styles 1.3
|
||||||
|
import QtQuick.Controls 1.3
|
||||||
import TTRSS 1.0
|
import TTRSS 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
@ -84,17 +85,9 @@ Item {
|
||||||
webView.runJavaScript("if(typeof setNightmode == \"function\") setNightmode(" + (content.nightmode ? "true" : "false") + ")")
|
webView.runJavaScript("if(typeof setNightmode == \"function\") setNightmode(" + (content.nightmode ? "true" : "false") + ")")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onNavigationRequested: {
|
onNavigationRequested: {
|
||||||
if (request.url == "feedthemonkey:previous") {
|
if (request.navigationType != WebEngineView.LinkClickedNavigation) {
|
||||||
request.action = WebEngineView.IgnoreRequest;
|
|
||||||
app.showPreviousPost();
|
|
||||||
} else if (request.url == "feedthemonkey:next") {
|
|
||||||
request.action = WebEngineView.IgnoreRequest;
|
|
||||||
app.showNextPost();
|
|
||||||
} else if (request.url == "feedthemonkey:open") {
|
|
||||||
request.action = WebEngineView.IgnoreRequest;
|
|
||||||
Qt.openUrlExternally(post.link)
|
|
||||||
} else if (request.navigationType !== WebEngineNavigationRequest.LinkClickedNavigation) {
|
|
||||||
request.action = WebEngineView.AcceptRequest;
|
request.action = WebEngineView.AcceptRequest;
|
||||||
} else {
|
} else {
|
||||||
request.action = WebEngineView.IgnoreRequest;
|
request.action = WebEngineView.IgnoreRequest;
|
||||||
|
|
|
@ -33,7 +33,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
id: item
|
id: item
|
||||||
height: d.height + t.height + e.height + 2
|
height: d.height + t.height + e.height + 20
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -81,7 +81,7 @@ Item {
|
||||||
Label {
|
Label {
|
||||||
id: t
|
id: t
|
||||||
text: title
|
text: title
|
||||||
color: nightmode ? (read ? "#888" : "#ddd") : (read ? "gray" : "black")
|
color: nightmode ? (read ? "#555" : "#aaa") : (read ? "gray" : "black")
|
||||||
font.pointSize: textFontSize
|
font.pointSize: textFontSize
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
wrapMode: Text.WrapAnywhere
|
wrapMode: Text.WrapAnywhere
|
||||||
|
|
|
@ -48,21 +48,6 @@ ScrollView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onWidthChanged: {
|
|
||||||
// Hide sidebar if smaller than 200px wide
|
|
||||||
if(width < 200) {
|
|
||||||
width = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 1
|
|
||||||
color: app.nightmode ? "#111" : "lightgray"
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
height: parent.height
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
|
@ -82,12 +67,12 @@ ScrollView {
|
||||||
highlightFollowsCurrentItem: false
|
highlightFollowsCurrentItem: false
|
||||||
highlight: Component {
|
highlight: Component {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: listView.currentItem.width -1
|
width: listView.currentItem.width
|
||||||
height: listView.currentItem.height
|
height: listView.currentItem.height
|
||||||
color: nightmode ? "#15539e" : "lightblue"
|
color: nightmode ? "#444" : "lightblue"
|
||||||
|
opacity: 0.5
|
||||||
y: listView.currentItem.y
|
y: listView.currentItem.y
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCurrentItemChanged: {
|
onCurrentItemChanged: {
|
||||||
|
@ -100,6 +85,8 @@ ScrollView {
|
||||||
}
|
}
|
||||||
|
|
||||||
item.content.post = server.posts[currentIndex]
|
item.content.post = server.posts[currentIndex]
|
||||||
|
//content.flickableItem.contentY = 0
|
||||||
|
|
||||||
previousPost = item.content.post
|
previousPost = item.content.post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,6 @@ MenuBar {
|
||||||
Menu {
|
Menu {
|
||||||
visible: menuBar.visible
|
visible: menuBar.visible
|
||||||
title: qsTr("File")
|
title: qsTr("File")
|
||||||
MenuItem {
|
|
||||||
text: qsTr("Close &Window")
|
|
||||||
shortcut: "Ctrl+W"
|
|
||||||
onTriggered: Qt.quit()
|
|
||||||
}
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: qsTr("Exit")
|
text: qsTr("Exit")
|
||||||
shortcut: "Ctrl+Q"
|
shortcut: "Ctrl+Q"
|
||||||
|
@ -137,7 +132,7 @@ MenuBar {
|
||||||
title: qsTr("Help")
|
title: qsTr("Help")
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: qsTr("About")
|
text: qsTr("About")
|
||||||
onTriggered: Qt.openUrlExternally("http://jeena.net/feedthemonkey/index.html");
|
onTriggered: Qt.openUrlExternally("http://jabs.nu/feedthemonkey");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
52
qml/main.qml
52
qml/main.qml
|
@ -21,7 +21,6 @@ import QtQuick 2.3
|
||||||
import QtQuick.Controls 1.3
|
import QtQuick.Controls 1.3
|
||||||
import QtQuick.Window 2.0
|
import QtQuick.Window 2.0
|
||||||
import QtQuick.Layouts 1.1
|
import QtQuick.Layouts 1.1
|
||||||
import QtQuick.Dialogs 1.1
|
|
||||||
import Qt.labs.settings 1.0
|
import Qt.labs.settings 1.0
|
||||||
import TTRSS 1.0
|
import TTRSS 1.0
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ ApplicationWindow {
|
||||||
id: app
|
id: app
|
||||||
title: "FeedTheMonkey"
|
title: "FeedTheMonkey"
|
||||||
visible: true
|
visible: true
|
||||||
color: nightmode ? "#2d2d2d" : "#eee"
|
color: nightmode ? "#111" : "#eee"
|
||||||
|
|
||||||
minimumWidth: 480
|
minimumWidth: 480
|
||||||
minimumHeight: 320
|
minimumHeight: 320
|
||||||
|
@ -48,17 +47,6 @@ ApplicationWindow {
|
||||||
property int textFontSizeIndex: defaultTextFontSizeIndex
|
property int textFontSizeIndex: defaultTextFontSizeIndex
|
||||||
property int textFontSize: fontSizes[textFontSizeIndex]
|
property int textFontSize: fontSizes[textFontSizeIndex]
|
||||||
property bool nightmode: false
|
property bool nightmode: false
|
||||||
property bool showMenuBar: false
|
|
||||||
|
|
||||||
menuBar: TheMenuBar {
|
|
||||||
id: menu
|
|
||||||
serverLogin: serverLogin
|
|
||||||
server: server
|
|
||||||
sidebar: sidebar
|
|
||||||
content: content
|
|
||||||
visible: app.showMenuBar
|
|
||||||
__contentItem.visible: visible
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
id: settings
|
id: settings
|
||||||
|
@ -72,6 +60,14 @@ ApplicationWindow {
|
||||||
property alias nightmode: app.nightmode
|
property alias nightmode: app.nightmode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property TheMenuBar menu: TheMenuBar {
|
||||||
|
id: menu
|
||||||
|
serverLogin: serverLogin
|
||||||
|
server: server
|
||||||
|
sidebar: sidebar
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
|
||||||
function loggedIn() {
|
function loggedIn() {
|
||||||
if(serverLogin.loggedIn()) {
|
if(serverLogin.loggedIn()) {
|
||||||
menu.loggedIn = true;
|
menu.loggedIn = true;
|
||||||
|
@ -113,14 +109,6 @@ ApplicationWindow {
|
||||||
return forEscapingHTML.getText(0, forEscapingHTML.length)
|
return forEscapingHTML.getText(0, forEscapingHTML.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
function showNextPost() {
|
|
||||||
sidebar.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPreviousPost() {
|
|
||||||
sidebar.previous()
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyPressed(event) {
|
function keyPressed(event) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_Right:
|
case Qt.Key_Right:
|
||||||
|
@ -159,6 +147,10 @@ ApplicationWindow {
|
||||||
case Qt.Key_Return:
|
case Qt.Key_Return:
|
||||||
Qt.openUrlExternally(content.post.link)
|
Qt.openUrlExternally(content.post.link)
|
||||||
break
|
break
|
||||||
|
case Qt.Key_S: {
|
||||||
|
console.log(Qt.openUrlExternally("speaker:"+ removeHTML(content.post.content)))
|
||||||
|
break
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -180,6 +172,7 @@ ApplicationWindow {
|
||||||
content: content
|
content: content
|
||||||
server: server
|
server: server
|
||||||
|
|
||||||
|
Layout.minimumWidth: 200
|
||||||
implicitWidth: 300
|
implicitWidth: 300
|
||||||
textFontSize: app.textFontSize
|
textFontSize: app.textFontSize
|
||||||
nightmode: app.nightmode
|
nightmode: app.nightmode
|
||||||
|
@ -199,7 +192,7 @@ ApplicationWindow {
|
||||||
Keys.onReleased: {
|
Keys.onReleased: {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_Alt:
|
case Qt.Key_Alt:
|
||||||
app.showMenuBar = !app.showMenuBar
|
app.menuBar = menu
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -213,27 +206,14 @@ ApplicationWindow {
|
||||||
visible: !serverLogin.loggedIn()
|
visible: !serverLogin.loggedIn()
|
||||||
|
|
||||||
function login() {
|
function login() {
|
||||||
|
console.log("FOO")
|
||||||
serverLogin.login(serverUrl, userName, password)
|
serverLogin.login(serverUrl, userName, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDialog {
|
|
||||||
id: loginErrorAlert
|
|
||||||
title: "A login error occured"
|
|
||||||
text: serverLogin.loginError
|
|
||||||
onAccepted: visible = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerLogin {
|
ServerLogin {
|
||||||
id: serverLogin
|
id: serverLogin
|
||||||
onSessionIdChanged: app.loggedIn()
|
onSessionIdChanged: app.loggedIn()
|
||||||
onLoginErrorChanged: {
|
|
||||||
console.log("loginError:", loginError)
|
|
||||||
if(loginError.length > 0) {
|
|
||||||
loginErrorAlert.visible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Server {
|
Server {
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
app.setOrganizationName("Jeena");
|
app.setOrganizationName("Jeena");
|
||||||
app.setOrganizationDomain("jeena.net");
|
app.setOrganizationDomain("jeena.net");
|
||||||
|
|
71
src/post.cpp
71
src/post.cpp
|
@ -28,6 +28,33 @@ Post::Post(QObject *parent) : QObject(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
Post::Post(QJsonObject post, QObject *parent) : QObject(parent)
|
Post::Post(QJsonObject post, QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
populateFromJson(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
Post::Post(QJsonObject post, QNetworkAccessManager *networkManager, QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
mNetworkManager = networkManager;
|
||||||
|
populateFromJson(post);
|
||||||
|
|
||||||
|
QObject::connect(this, &Post::contentChanged, [this]() {
|
||||||
|
QJsonObject obj = QJsonDocument::fromJson(mJsonString.toUtf8()).object();
|
||||||
|
obj["content"] = QJsonValue(mContent);
|
||||||
|
QJsonDocument doc(obj);
|
||||||
|
QString result(doc.toJson(QJsonDocument::Indented));
|
||||||
|
mJsonString = result;
|
||||||
|
emit jsonStringChanged(mJsonString);
|
||||||
|
});
|
||||||
|
|
||||||
|
cacheImgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
Post::~Post()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Post::populateFromJson(QJsonObject post)
|
||||||
{
|
{
|
||||||
mTitle = html2text(post.value("title").toString().trimmed());
|
mTitle = html2text(post.value("title").toString().trimmed());
|
||||||
mFeedTitle = html2text(post.value("feed_title").toString().trimmed());
|
mFeedTitle = html2text(post.value("feed_title").toString().trimmed());
|
||||||
|
@ -50,11 +77,6 @@ Post::Post(QJsonObject post, QObject *parent) : QObject(parent)
|
||||||
mJsonString = result;
|
mJsonString = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Post::~Post()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Post::setRead(bool r)
|
void Post::setRead(bool r)
|
||||||
{
|
{
|
||||||
if(mRead == r) return;
|
if(mRead == r) return;
|
||||||
|
@ -77,3 +99,42 @@ QString Post::html2text(const QString htmlString)
|
||||||
doc.setHtml(htmlString);
|
doc.setHtml(htmlString);
|
||||||
return doc.toPlainText();
|
return doc.toPlainText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Post::cacheImgs()
|
||||||
|
{
|
||||||
|
QRegExp imgTagRegex("\\<img[^\\>]*src\\s*=\\s*\"([^\"]*)\"[^\\>]*\\>", Qt::CaseInsensitive);
|
||||||
|
imgTagRegex.setMinimal(true);
|
||||||
|
QStringList urlmatches;
|
||||||
|
int offset = 0;
|
||||||
|
while( (offset = imgTagRegex.indexIn(mContent, offset)) != -1){
|
||||||
|
offset += imgTagRegex.matchedLength();
|
||||||
|
urlmatches.append(imgTagRegex.cap(1)); // Should hold only src property
|
||||||
|
}
|
||||||
|
|
||||||
|
for(QString url : urlmatches) {
|
||||||
|
|
||||||
|
if(url.startsWith("http")) {
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
QNetworkReply *reply = mNetworkManager->get(request);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, [url, this, reply] () {
|
||||||
|
if (reply) {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QVariant mimeType(reply->header(QNetworkRequest::ContentTypeHeader));
|
||||||
|
QString imgString = QString("data:") + mimeType.toString() + QString(";base64,") + QString(reply->readAll().toBase64());
|
||||||
|
if(mimeType == "image/jpeg" || mimeType == "image/gif" || mimeType == "image/png")
|
||||||
|
{
|
||||||
|
mContent = mContent.replace(url, imgString);
|
||||||
|
emit contentChanged(mContent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
//do some error management
|
||||||
|
qWarning() << "HTTP error: " << httpStatus;
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
12
src/post.h
12
src/post.h
|
@ -24,6 +24,7 @@
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
class Post : public QObject
|
class Post : public QObject
|
||||||
{
|
{
|
||||||
|
@ -35,16 +36,17 @@ class Post : public QObject
|
||||||
Q_PROPERTY(QString author READ author CONSTANT)
|
Q_PROPERTY(QString author READ author CONSTANT)
|
||||||
Q_PROPERTY(QUrl link READ link CONSTANT)
|
Q_PROPERTY(QUrl link READ link CONSTANT)
|
||||||
Q_PROPERTY(QDateTime date READ date CONSTANT)
|
Q_PROPERTY(QDateTime date READ date CONSTANT)
|
||||||
Q_PROPERTY(QString content READ content CONSTANT)
|
Q_PROPERTY(QString content READ content NOTIFY contentChanged)
|
||||||
Q_PROPERTY(QString excerpt READ excerpt CONSTANT)
|
Q_PROPERTY(QString excerpt READ excerpt CONSTANT)
|
||||||
Q_PROPERTY(bool starred READ starred NOTIFY starredChanged)
|
Q_PROPERTY(bool starred READ starred NOTIFY starredChanged)
|
||||||
Q_PROPERTY(bool read READ read WRITE setRead NOTIFY readChanged)
|
Q_PROPERTY(bool read READ read WRITE setRead NOTIFY readChanged)
|
||||||
Q_PROPERTY(bool dontChangeRead READ dontChangeRead WRITE setDontChangeRead NOTIFY dontChangeReadChanged)
|
Q_PROPERTY(bool dontChangeRead READ dontChangeRead WRITE setDontChangeRead NOTIFY dontChangeReadChanged)
|
||||||
Q_PROPERTY(QString jsonString READ jsonString CONSTANT)
|
Q_PROPERTY(QString jsonString READ jsonString NOTIFY jsonStringChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Post(QObject *parent = 0);
|
Post(QObject *parent = 0);
|
||||||
Post(QJsonObject post, QObject *parent = 0);
|
Post(QJsonObject post, QObject *parent = 0);
|
||||||
|
Post(QJsonObject post, QNetworkAccessManager *networkManager, QObject *parent = 0);
|
||||||
~Post();
|
~Post();
|
||||||
QString title() const { return mTitle; }
|
QString title() const { return mTitle; }
|
||||||
QString feedTitle() const { return mFeedTitle; }
|
QString feedTitle() const { return mFeedTitle; }
|
||||||
|
@ -63,9 +65,11 @@ public:
|
||||||
QString jsonString() const { return mJsonString; }
|
QString jsonString() const { return mJsonString; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void contentChanged(QString);
|
||||||
void starredChanged(bool);
|
void starredChanged(bool);
|
||||||
void readChanged(bool);
|
void readChanged(bool);
|
||||||
void dontChangeReadChanged(bool);
|
void dontChangeReadChanged(bool);
|
||||||
|
void jsonStringChanged(QString);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
|
@ -83,7 +87,11 @@ private:
|
||||||
bool mRead;
|
bool mRead;
|
||||||
bool mDontChangeRead;
|
bool mDontChangeRead;
|
||||||
QString mJsonString;
|
QString mJsonString;
|
||||||
|
|
||||||
QString html2text(const QString htmlString);
|
QString html2text(const QString htmlString);
|
||||||
|
void cacheImgs();
|
||||||
|
void populateFromJson(QJsonObject post);
|
||||||
|
QNetworkAccessManager *mNetworkManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // POST_H
|
#endif // POST_H
|
||||||
|
|
|
@ -62,7 +62,7 @@ void TinyTinyRSS::reload()
|
||||||
for(int i = 0; i < posts.count(); i++)
|
for(int i = 0; i < posts.count(); i++)
|
||||||
{
|
{
|
||||||
QJsonObject postJson = posts.at(i).toObject();
|
QJsonObject postJson = posts.at(i).toObject();
|
||||||
Post *post = new Post(postJson, this);
|
Post *post = new Post(postJson, mNetworkManager, this);
|
||||||
connect(post, SIGNAL(readChanged(bool)), this, SLOT(onPostReadChanged(bool)));
|
connect(post, SIGNAL(readChanged(bool)), this, SLOT(onPostReadChanged(bool)));
|
||||||
mPosts.append(post);
|
mPosts.append(post);
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,47 +88,23 @@ void TinyTinyRSSLogin::reply()
|
||||||
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
|
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
if (reply) {
|
if (reply) {
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
|
||||||
QString jsonString = QString(reply->readAll());
|
QString jsonString = QString(reply->readAll());
|
||||||
QJsonDocument json = QJsonDocument::fromJson(jsonString.toUtf8());
|
QJsonDocument json = QJsonDocument::fromJson(jsonString.toUtf8());
|
||||||
if(json.object().value("content").toObject().value("error").toString().length() > 0) {
|
mSessionId = json.object().value("content").toObject().value("session_id").toString();
|
||||||
|
|
||||||
mLoginError = json.object().value("content").toObject().value("error").toString();
|
emit sessionIdChanged(mSessionId);
|
||||||
qWarning() << mLoginError;
|
|
||||||
emit loginErrorChanged(mLoginError);
|
|
||||||
|
|
||||||
if(mLoginError == "NOT_LOGGED_IN") {
|
QSettings settings;
|
||||||
mSessionId = nullptr;
|
settings.setValue("sessionId", mSessionId);
|
||||||
mServerUrl = nullptr;
|
settings.setValue("serverUrl", mServerUrl);
|
||||||
|
settings.sync();
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
settings.remove("sessionId");
|
|
||||||
settings.remove("serverUrl");
|
|
||||||
settings.sync();
|
|
||||||
|
|
||||||
emit sessionIdChanged(mSessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
mSessionId = json.object().value("content").toObject().value("session_id").toString();
|
|
||||||
|
|
||||||
emit sessionIdChanged(mSessionId);
|
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
settings.setValue("sessionId", mSessionId);
|
|
||||||
settings.setValue("serverUrl", mServerUrl);
|
|
||||||
settings.sync();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
mLoginError = "HTTP error: "
|
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
+ reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString()
|
//do some error management
|
||||||
+ " :: "
|
qWarning() << "HTTP error: " << httpStatus << " :: " << reply->error();
|
||||||
+ reply->errorString();
|
|
||||||
qWarning() << mLoginError;
|
|
||||||
|
|
||||||
emit loginErrorChanged(mLoginError);
|
|
||||||
}
|
}
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,14 +30,12 @@ class TinyTinyRSSLogin : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QString sessionId READ sessionId NOTIFY sessionIdChanged)
|
Q_PROPERTY(QString sessionId READ sessionId NOTIFY sessionIdChanged)
|
||||||
Q_PROPERTY(QUrl serverUrl READ serverUrl)
|
Q_PROPERTY(QUrl serverUrl READ serverUrl)
|
||||||
Q_PROPERTY(QString loginError READ loginError NOTIFY loginErrorChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TinyTinyRSSLogin(QObject *parent = 0);
|
TinyTinyRSSLogin(QObject *parent = 0);
|
||||||
~TinyTinyRSSLogin();
|
~TinyTinyRSSLogin();
|
||||||
QString sessionId() const { return mSessionId; }
|
QString sessionId() const { return mSessionId; }
|
||||||
QUrl serverUrl() const { return mServerUrl; }
|
QUrl serverUrl() const { return mServerUrl; }
|
||||||
QString loginError() const { return mLoginError; }
|
|
||||||
|
|
||||||
Q_INVOKABLE bool loggedIn();
|
Q_INVOKABLE bool loggedIn();
|
||||||
Q_INVOKABLE void login(const QString serverUrl, const QString user, const QString password);
|
Q_INVOKABLE void login(const QString serverUrl, const QString user, const QString password);
|
||||||
|
@ -45,7 +43,6 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sessionIdChanged(QString);
|
void sessionIdChanged(QString);
|
||||||
void loginErrorChanged(QString);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void reply();
|
void reply();
|
||||||
|
@ -53,7 +50,6 @@ private slots:
|
||||||
private:
|
private:
|
||||||
QString mSessionId;
|
QString mSessionId;
|
||||||
QUrl mServerUrl;
|
QUrl mServerUrl;
|
||||||
QString mLoginError;
|
|
||||||
QNetworkAccessManager *mNetworkManager;
|
QNetworkAccessManager *mNetworkManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue