Compare commits

..

35 commits

Author SHA1 Message Date
Jeena
710416015c version bump 2014-10-27 00:29:27 +01:00
Jeena
3e4be0fe29 hide publish button for nono TTRSS 2014-10-27 00:27:11 +01:00
Jeena Paradies
d1c7059751 Merge pull request #40 from Bonbadil/master
Two-states buttons are now using opacity instead of two icons.
2014-10-16 08:34:11 +02:00
Bonbadil
2f9452376d Merge branch 'master' of https://github.com/jeena/FeedMonkey 2014-10-16 00:08:42 +02:00
Bonbadil
de93b8059a Two-states buttons are now using opacity instead of two icons. 2014-10-16 00:06:00 +02:00
Jeena Paradies
7c63205d36 Merge pull request #39 from Bonbadil/master
Add a publish button on article view for TTRSS.
2014-10-15 23:45:54 +02:00
Bonbadil
8b0cc42075 Use cloud symbols for the publish button. 2014-10-15 22:58:33 +02:00
Bonbadil
2a2d35dfde Add a publish button on article view. 2014-10-15 22:00:32 +02:00
Jeena
2bf7e97c55 version bump 2014-08-29 20:19:55 +02:00
Jeena
2d16a03192 Merge branch 'master' of github.com:jeena/FeedMonkey 2014-08-27 22:56:31 +02:00
Jeena
d6a2ca9fd6 OwnCloud: sending right Content-Type, fixes #36 2014-08-27 22:56:12 +02:00
Jeena Paradies
2895e2749e Merge pull request #35 from mossroy/master
Issue #30 : Handle the NOT_LOGGED_IN errors that can come from tt-rss backends.
2014-08-09 17:39:57 +02:00
mossroy
cc88b341b2 Issue #30 : Avoid displaying "undefined" for username when it was not yet stored in the localStorage 2014-08-06 15:37:08 +02:00
mossroy
568ca8005d Issue #30 : Fix a typo in function name 2014-08-06 15:29:34 +02:00
mossroy
b2f319af71 Issue #30 : Keep the username in localStorage, so that the user does not have to type it again in case his session has expired
+ also fill the login form with the localStorage values when the user chooses to logout
2014-08-06 15:22:59 +02:00
mossroy
3f75c49e3e Issue #30 : Handle the NOT_LOGGED_IN errors that can come from tt-rss backends. It currently asks the user to type the login/password again, which is not ideal. But at least he can access his unread articles again. 2014-08-06 12:18:27 +02:00
Jeena
dfcc5ba27a version bump 2014-06-03 22:55:43 +02:00
Jeena
7161642224 easier swiping 2014-06-03 22:55:32 +02:00
Jeena
a33fbe3110 fixed images and figures 2014-06-03 22:54:59 +02:00
Jeena
54900c3346 better slide 2014-06-03 19:13:19 +02:00
Jeena
aec6cd079b fixed so the app is working offline again 2014-05-22 22:54:43 +02:00
Jeena
22d6761ba3 added pond to manifest and changed version number 2014-05-22 22:15:54 +02:00
Jeena
fd9b3d9580 added pond with link to readme 2014-05-22 22:15:30 +02:00
Jeena
f5f9e9761f fixed circle text color on white 2014-05-22 22:15:06 +02:00
Jeena
89142c54c0 better error message on login with wrong server address, fixes #15 2014-05-22 21:29:39 +02:00
Jeena
54c87e58d8 typo 2014-05-22 21:19:15 +02:00
Jeena
1f404a846c disabled word suggestions inf input fields, fixes #31 2014-05-22 21:14:41 +02:00
Jeena
b5931a8538 fixed long images not scrolling 2014-05-22 21:09:32 +02:00
Jeena
208fa70c52 removed alert on end of downloads 2014-05-22 20:48:41 +02:00
Jeena
e120f99e4e Merge branch 'master' of github.com:jeena/FeedMonkey 2014-05-22 20:20:11 +02:00
Jeena
28ce177e17 replaced jetser with hammertime which hopefully fixes #32 2014-05-22 20:11:54 +02:00
Jeena
d99928908e added method to logOut doOperation 2014-05-22 19:43:42 +02:00
Jeena Paradies
e9a8af5429 Merge pull request #33 from Alvarord/master20140429-issue30
Limiting the size of the download so localStorage doesn't get full, thanks @Alvarord
2014-05-17 14:17:19 +02:00
AlvaroRD
cfad4d4f4d Added 2 parameters to limit the download size, and how many articles are downloaded by request to the server.
Alerts to detect size download anytime you connect to the server.

This patch catch any exception in case data is not able to be stored in localStorage
2014-05-06 00:12:59 +02:00
Alvarord
bc1c99354f Adding identificator to show it in the interface, developing branch. 2014-04-29 04:56:43 +02:00
12 changed files with 2882 additions and 732 deletions

View file

@ -11,4 +11,4 @@ A RSS mobile client with which you can read your RSS feeds and mark them as read
- Works also offline. - Works also offline.
- You can chose between 4 fresh color schemes. - You can chose between 4 fresh color schemes.
To use this RSS client to read your feeds you need a backend server. As a backend you can use [TinyTinyRSS](http://tt-rss.org) or [ownCloud News](http://apps.owncloud.com/content/show.php/News?content=158434). This is not a stand alone application. To use this RSS client to read your feeds you need a backend server. As a backend you can use [TinyTinyRSS](http://tt-rss.org), [ownCloud News](http://apps.owncloud.com/content/show.php/News?content=158434) or [Pond](https://github.com/ArturoVM/pond#pond). This is not a stand alone application.

View file

@ -83,7 +83,7 @@ label {
} }
.hidden { .hidden {
display: none; display: none !important;
} }
section { section {
@ -120,11 +120,13 @@ section > header h1 {
overflow: auto; overflow: auto;
} }
.reload, .all-read, #setstarred, #setunread { .reload, .all-read, #setstarred, #setunread, #setpublished {
float: right; float: right;
margin-left: 10px; margin-left: 10px;
} }
.inactive { opacity: 0.4; }
.settings, .list { .settings, .list {
float: left; float: left;
} }
@ -138,22 +140,29 @@ section > footer {
box-sizing: border-box; box-sizing: border-box;
} }
figure {
margin: 0;
padding: 0;
}
img { img {
max-width: 100% !important; max-width: 100% !important;
height: auto; height: auto;
} }
@media screen and (width: 320px) { #full > article pre {
#full > article * , #full > article pre {
max-width: 300px !important;
overflow: auto; overflow: auto;
} }
@media screen and (width: 320px) {
#full > article * {
max-width: 300px !important;
}
} }
@media screen and (width: 480px) { @media screen and (width: 480px) {
#full > article * { #full > article * {
max-width: 460px !important; max-width: 460px !important;
overflow: auto;
} }
} }
@ -165,7 +174,6 @@ canvas {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
word-wrap: break-word;
} }
#list p { #list p {
@ -189,7 +197,6 @@ canvas {
#list li { #list li {
position: relative; position: relative;
min-height: 3em;
} }
.red #list li { border-bottom: 1px solid #c0392b; } .red #list li { border-bottom: 1px solid #c0392b; }
@ -201,7 +208,7 @@ canvas {
content: ""; content: "";
position: absolute; position: absolute;
right: 7px; right: 7px;
top: 0; top: 0.1em;
font-weight: 100; font-weight: 100;
font-size: 3em; font-size: 3em;
font-family: "Entypo"; font-family: "Entypo";
@ -250,7 +257,6 @@ canvas {
font-weight: normal; font-weight: normal;
margin: 0; margin: 0;
padding: 0; padding: 0;
display: none;
} }
#full .wrapper { #full .wrapper {
@ -281,28 +287,9 @@ canvas {
padding: 0; padding: 0;
} }
#full article header p:nth-child(1) {
float: left;
}
#full article header p:nth-child(2) {
float: right;
}
#full article header p:nth-child(3) {
clear: both;
}
#full .article {
clear: both;
padding-top: 1em;
padding-bottom: 3em;
font-size: 1.3em;
}
#full footer.bar { #full footer.bar {
margin: auto 0 0 0; margin: auto 0 0 0;
position: fixed; position: relative;
height: 3.8em; height: 3.8em;
} }

View file

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>FeedMonkey</title> <title>FeedMonkey</title>
@ -15,7 +15,7 @@
<script src="js/Pond.js"></script> <script src="js/Pond.js"></script>
<script src="js/md5.js"></script> <script src="js/md5.js"></script>
<script src="js/Login.js"></script> <script src="js/Login.js"></script>
<script src="js/jester.js"></script> <script src="js/hammer.js"></script>
</head> </head>
<body> <body>
@ -23,7 +23,7 @@
<header class="bar"> <header class="bar">
<a class="button icon settings" href="#settings">&#9881;</a> <a class="button icon settings" href="#settings">&#9881;</a>
<a class="button icon reload" href="#reload">&#128260;</a> <a class="button icon reload" href="#reload">&#128260;</a>
<a class="button icon all-read" href="#all-read" id="all-read"></a> <a class="button icon all-read inactive" href="#all-read" id="all-read"></a>
<canvas width="40" height="40"></canvas> <canvas width="40" height="40"></canvas>
</header> </header>
<article> <article>
@ -71,11 +71,11 @@
</p> </p>
<p> <p>
<label for="url">URL:</label> <label for="url">URL:</label>
<input class="button" type="text" name="url" id="url" value="" placeholder="http://example.com/tt-rss/" /> <input class="button" type="text" name="url" id="url" value="" placeholder="http://example.com/tt-rss/" x-inputmode="verbatim" autocorrect="off" />
<label for="un">Username:</label> <label for="un">Username:</label>
<input class="button" type="text" name="user" id="un" value="" placeholder="username" /> <input class="button" type="text" name="user" id="un" value="" placeholder="username" x-inputmode="verbatim" autocorrect="off" />
<label for="pw">Password:</label> <label for="pw">Password:</label>
<input class="button" type="password" name="pass" id="pw" value="" placeholder="password" /> <input class="button" type="password" name="pass" id="pw" value="" placeholder="password" x-inputmode="verbatim" autocorrect="off" />
</p> </p>
<p> <p>
<button class="button" type="submit">Sign in</button> <button class="button" type="submit">Sign in</button>
@ -87,15 +87,16 @@
<section id="full"> <section id="full">
<header class="bar"> <header class="bar">
<a class="button icon list" href="#list">&#57349;</a> <a class="button icon list" href="#list">&#57349;</a>
<a id="setstarred" class="button icon" href="#starred">&#9734;</a> <a id="setpublished" class="button icon inactive hidden" href="#published">&#59194;</a>
<a id="setunread" class="button icon" href="#unread"></a> <a id="setstarred" class="button icon inactive" href="#starred">&#9733;</a>
<a id="setunread" class="button icon inactive" href="#unread"></a>
<canvas width="40" height="40"></canvas> <canvas width="40" height="40"></canvas>
</header> </header>
<article> <article>
<header> <header>
<p><span class="feed_title"></span> <span class="author"></span></p> <p><span class="feed_title"></span> <span class="author"></span></p>
<h1><a class="title" href="" target="_blank"></a></h1>
<p><timedate class="date"></timedate></p> <p><timedate class="date"></timedate></p>
<p><a class="link" href="" target="_blank"></a></p>
</header> </header>
<div class="article"></div> <div class="article"></div>
<div class="info swipe"> <div class="info swipe">

178
js/App.js
View file

@ -8,19 +8,12 @@ function App() {
this.setColor(color); this.setColor(color);
this.fontChange(); this.fontChange();
var _this = this; var numArticles = localStorage.numArticles;
if(!numArticles) numArticles = 50;
window.onkeydown = function(e) { this.numArticles(numArticles);
if(e.keyCode == 39) { var maxDownload = localStorage.maxDownload;
_this.showNext(); if(!maxDownload) maxDownload = 500000;
} else if(e.keyCode == 37) { this.maxDownload(maxDownload);
_this.showPrevious();
} else if(e.keyCode == 13) {
_this.openInBrowser();
} else if(e.keyCode == 82) {
_this.reload();
}
}
}; };
App.prototype.authenticate = function() { App.prototype.authenticate = function() {
@ -28,11 +21,11 @@ App.prototype.authenticate = function() {
}; };
App.prototype.after_login = function(backend) { App.prototype.after_login = function(backend) {
/*
var request = window.navigator.mozApps.getSelf(); var request = window.navigator.mozApps.getSelf();
request.onsuccess = function() { request.onsuccess = function() {
$("#version").innerHTML = request.result.manifest.version; $("#version").innerHTML = request.result.manifest.version;
}*/ }
var _this = this; var _this = this;
@ -61,6 +54,8 @@ App.prototype.after_login = function(backend) {
_this.toggleCurrentUnread(); _this.toggleCurrentUnread();
} else if(url == "#starred") { } else if(url == "#starred") {
_this.toggleStarred(); _this.toggleStarred();
} else if(url == "#published") {
_this.togglePublished();
} else if(url == "#logout") { } else if(url == "#logout") {
_this.logout(); _this.logout();
} else if(url == "#reset-info") { } else if(url == "#reset-info") {
@ -98,11 +93,15 @@ App.prototype.after_login = function(backend) {
} }
// set up swiping // set up swiping
jester($("#full")).flick(function(touches, direction) { var options = {
if(direction == "left") _this.showNext(); dragLockToAxis: true,
else _this.showPrevious(); dragBlockHorizontal: true
}); };
var hammertime = new Hammer($("#full"), options);
hammertime.on("dragleft", function(ev){ ev.gesture.preventDefault(); });
hammertime.on("dragright", function(ev){ ev.gesture.preventDefault(); });
hammertime.on("swipeleft", function(ev){ _this.showNext(); ev.gesture.preventDefault(); });
hammertime.on("swiperight", function(ev){ _this.showPrevious(); ev.gesture.preventDefault(); });
this.changeToPage("#list"); this.changeToPage("#list");
@ -112,7 +111,16 @@ App.prototype.after_login = function(backend) {
this.backend = new Pond(this, localStorage.server_url, localStorage.session_id) this.backend = new Pond(this, localStorage.server_url, localStorage.session_id)
} else { } else {
this.backend = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id); this.backend = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id);
$("#setpublished").removeClass("hidden");
} }
var numArticles = localStorage.numArticles;
if(!numArticles) numArticles = 50;
this.numArticles(numArticles);
var maxDownload = localStorage.maxDownload;
if(!maxDownload) maxDownload = 500000;
this.maxDownload(maxDownload);
this.reload(); this.reload();
}; };
@ -150,28 +158,49 @@ App.prototype.setColor = function(color) {
App.prototype.reload = function() { App.prototype.reload = function() {
this.unread_articles = []; this.unread_articles = [];
$("#all-read").innerHTML = "❌"; $("#all-read").addClass('inactive');
this.backend.reload(this.gotUnreadFeeds.bind(this)); var number=parseInt(localStorage.numArticles);
this.backend.reload(this.gotUnreadFeeds.bind(this),number);
}; };
App.prototype.gotUnreadFeeds = function(new_articles) { App.prototype.gotUnreadFeeds = function(new_articles) {
if(new_articles == null || !this.validate(new_articles)) { // on error load the saved unread articles. if(new_articles == null || !this.validate(new_articles)) {
// Check if we did not get a NOT_LOGGED_IN error, and ask the
// user to login again if it is the case.
// This can happen with TT-RSS backend
if (new_articles.error && new_articles.error === "NOT_LOGGED_IN") {
alert("Your TinyTinyRSS session has expired. Please login again");
this.login.fillLoginFormFromLocalStorage();
this.login.log_in();
} else {
// On other errors, load the saved unread articles.
var old_articles = localStorage.unread_articles; var old_articles = localStorage.unread_articles;
if(old_articles) { if(old_articles) {
this.unread_articles = JSON.parse(old_articles); this.unread_articles = JSON.parse(old_articles);
} }
this.populateList(); this.populateList();
}
} else { } else {
this.unread_articles = this.unread_articles.concat(new_articles); this.unread_articles = this.unread_articles.concat(new_articles);
if(new_articles.length > 0) { if(new_articles.length > 0) {
this.backend.getUnreadFeeds(this.gotUnreadFeeds.bind(this), this.unread_articles); try {
} else { //To check if when it fails it is the same
localStorage.unread_articles = JSON.stringify(this.unread_articles); localStorage.unread_articles = JSON.stringify(this.unread_articles);
var size = parseInt(localStorage.maxDownload);
if(localStorage.unread_articles.length < size) {
var num = parseInt(localStorage.numArticles);
this.backend.getUnreadFeeds(this.gotUnreadFeeds.bind(this), this.unread_articles,num);
} else {
alert("Limit size reached: Downloaded: " + this.unread_articles.length + " articles. Reached: " + localStorage.unread_articles.length +" bytes");
}
} catch (e) {
alert("Reached maximum memory by app " + e.name + " " + e.message + ". We will keep working in anycase with: " + localStorage.unread_articles.length);
}
this.populateList(); this.populateList();
} }
} }
@ -196,10 +225,8 @@ App.prototype.populateList = function() {
html_str += "<li"+ (article.unread ? " class='unread'" : "") +">"; html_str += "<li"+ (article.unread ? " class='unread'" : "") +">";
html_str += "<a href='#full-"+i+"'>"; html_str += "<a href='#full-"+i+"'>";
html_str += "<p class='title'>" + article.feed_title + "</p>"; html_str += "<p class='title'>" + article.feed_title + "</p>";
var content = article.content.stripHTML(); html_str += "<h2>" + article.title + "</h2>";
if(content.replace(/^\s+|\s+$/g,'').length == 0) content = article.title; if(article.excerpt) html_str += "<p class='excerpt'>" + article.excerpt + "</p>";
html_str += "<h2>" + content + "</h2>";
//if(article.excerpt) html_str += "<p class='excerpt'>" + article.excerpt + "</p>";
html_str += "</a></li>"; html_str += "</a></li>";
} }
@ -222,9 +249,9 @@ App.prototype.updateList = function() {
}, this); }, this);
if(unread > 0) { if(unread > 0) {
$("#all-read").innerHTML = "❌"; $("#all-read").addClass('inactive');
} else { } else {
$("#all-read").innerHTML = "✓"; $("#all-read").removeClass('inactive');
} }
this.updatePieChart(); this.updatePieChart();
@ -246,6 +273,7 @@ App.prototype.updatePieChart = function() {
var bg = window.getComputedStyle($("body"), null).backgroundColor; var bg = window.getComputedStyle($("body"), null).backgroundColor;
var fg = window.getComputedStyle($(".bar"), null).backgroundColor; var fg = window.getComputedStyle($(".bar"), null).backgroundColor;
var tx = window.getComputedStyle($(".bar"), null).color;
var myColor = [bg, fg]; var myColor = [bg, fg];
@ -270,7 +298,7 @@ App.prototype.updatePieChart = function() {
if(all > 0) { if(all > 0) {
ctx.font = "12px FeuraSans, sans-serif"; ctx.font = "12px FeuraSans, sans-serif";
ctx.fillStyle = "#fff"; ctx.fillStyle = tx;
ctx.textAlign = "center"; ctx.textAlign = "center";
var text = unread + "/" + all; var text = unread + "/" + all;
var x = canvas.width / 2; var x = canvas.width / 2;
@ -290,9 +318,9 @@ App.prototype.showFull = function(article, slide_back) {
$(page_id + " .date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000)).toLocaleString(); $(page_id + " .date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000)).toLocaleString();
var link = $(page_id + " .link"); var title = $(page_id + " .title");
link.innerHTML = article.link; title.innerHTML = article.title;
link.href = article.link; title.href = article.link;
$(page_id + " .feed_title").innerHTML = article.feed_title; $(page_id + " .feed_title").innerHTML = article.feed_title;
@ -300,23 +328,29 @@ App.prototype.showFull = function(article, slide_back) {
if(article.author && article.author.length > 0) if(article.author && article.author.length > 0)
$(page_id + " .author").innerHTML = "&ndash; " + article.author; $(page_id + " .author").innerHTML = "&ndash; " + article.author;
var content = article.content; $(page_id + " .article").innerHTML = article.content;
if(content.replace(/^\s+|\s+$/g,'').length == 0) content = article.title;
$(page_id + " .article").innerHTML = content.urlify(); // Open all links in browser
$$(page_id + " .article a").forEach(function(o, i) { $$(page_id + " .article a").forEach(function(o, i) {
o.target = "_blank"; o.target = "_blank";
}); });
if(article.unread) { if(article.unread) {
$("#setunread").innerHTML = "❌"; $("#setunread").addClass('inactive');
} else { } else {
$("#setunread").innerHTML = "✓"; $("#setunread").removeClass('inactive');
} }
if(article.marked) { if(!article.marked) {
$("#setstarred").innerHTML = "&#9733;"; $("#setstarred").addClass('inactive');
} else { } else {
$("#setstarred").innerHTML = "&#9734;"; $("#setstarred").removeClass('inactive');
}
if(!article.published) {
$("#setpublished").addClass('inactive');
} else {
$("#setpublished").removeClass('inactive');
} }
}; };
@ -343,10 +377,6 @@ App.prototype.showPrevious = function() {
} }
}; };
App.prototype.openInBrowser = function() {
$("#full .link").click();
};
App.prototype.setCurrentRead = function() { App.prototype.setCurrentRead = function() {
var article = this.unread_articles[this.currentIndex]; var article = this.unread_articles[this.currentIndex];
if(!article) return; // happens if we're not on a full article site if(!article) return; // happens if we're not on a full article site
@ -358,7 +388,7 @@ App.prototype.setCurrentRead = function() {
article.set_unread = false; article.set_unread = false;
$("#setunread").innerHTML = "✓"; $("#setunread").removeClass('inactive');
this.updatePieChart(); this.updatePieChart();
}; };
@ -368,11 +398,11 @@ App.prototype.toggleCurrentUnread = function() {
if(article.unread) { if(article.unread) {
article.unread = false; article.unread = false;
article.set_unread = false; article.set_unread = false;
$("#setunread").innerHTML = "✓"; $("#setunread").removeClass('inactive');
} else { } else {
article.unread = true; article.unread = true;
article.set_unread = true; article.set_unread = true;
$("#setunread").innerHTML = "❌"; $("#setunread").addClass('inactive');
} }
this.updateList(); this.updateList();
@ -381,7 +411,7 @@ App.prototype.toggleCurrentUnread = function() {
App.prototype.toggleAllRead = function() { App.prototype.toggleAllRead = function() {
if($("#all-read").innerHTML == "❌") { // set all read if($("#all-read").hasClass('inactive')) { // set all read
var articles = []; var articles = [];
for (var i = 0; i < this.unread_articles.length; i++) { for (var i = 0; i < this.unread_articles.length; i++) {
@ -390,10 +420,9 @@ App.prototype.toggleAllRead = function() {
article.set_unread = false; article.set_unread = false;
articles.push(article); articles.push(article);
} }
$("#all-read").innerHTML = "&#10003;"; $("#all-read").removeClass('inactive');
this.updateList(); this.updateList();
this.backend.setArticlesRead(articles); this.backend.setArticlesRead(articles);
} else { } else {
@ -405,11 +434,10 @@ App.prototype.toggleAllRead = function() {
article.set_unread = false; article.set_unread = false;
articles.push(article); articles.push(article);
} }
$("#all-read").innerHTML = "&#10060;"; $("#all-read").addClass('inactive');
this.updateList(); this.updateList();
this.backend.setArticlesUnread(articles); this.backend.setArticlesUnread(articles);
} }
}; };
@ -421,13 +449,28 @@ App.prototype.toggleStarred = function() {
article.marked = true; article.marked = true;
this.updateList(); this.updateList();
this.backend.setArticleStarred(article); this.backend.setArticleStarred(article);
$("#setstarred").innerHTML = "&#9733;"; $("#setstarred").removeClass('inactive');
} } else {
else {
article.marked = false; article.marked = false;
this.updateList(); this.updateList();
this.backend.setArticleUnstarred(article); this.backend.setArticleUnstarred(article);
$("#setstarred").innerHTML = "&#9734;"; $("#setstarred").addClass('inactive');
}
};
App.prototype.togglePublished = function() {
var article = this.unread_articles[this.currentIndex];
if(!article) return; // happens if we're not on a full article site
if(!article.published) {
article.published = true;
this.backend.setArticlePublished(article);
$("#setpublished").removeClass('inactive');
} else {
article.published = false;
this.backend.setArticleUnpublished(article);
$("#setpublished").addClass('inactive');
} }
}; };
@ -467,5 +510,20 @@ App.prototype.fontChange = function(size) {
document.body.addClass("f" + i); document.body.addClass("f" + i);
} }
};
App.prototype.numArticles = function(askfor) {
if(askfor < 200 && askfor > 0) {
localStorage.numArticles=askfor;
} else {
localStorage.numArticles=100;
}
};
App.prototype.maxDownload = function(maxdata) {
if(maxdata < 5000000 && maxdata > 100000) {
localStorage.maxDownload=maxdata;
} else {
localStorage.maxDownload=500000;
}
}; };

View file

@ -17,6 +17,19 @@ Login.prototype.is_logged_in = function() {
Login.prototype.log_in = function() { Login.prototype.log_in = function() {
this.app.changeToPage("#login"); this.app.changeToPage("#login");
$("#login form").backend.forEach(function(o, i) {
o.addEventListener("change", function(e) {
if(e.target.checked) {
if(e.target.value == "OwnCloud") {
$("#url").placeholder = "http://example.com/owncloud/";
} else if(e.target.value == "Pond") {
$("#url").placeholder = "http://example.com/pond/";
} else {
$("#url").placeholder = "http://example.com/tt-rss/";
}
}
});
});
$("#login form").addEventListener('submit', this.authenticate.bind(this)); $("#login form").addEventListener('submit', this.authenticate.bind(this));
}; };
@ -25,10 +38,11 @@ Login.prototype.authenticate = function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
var backend = "Pond"; var backend = "TinyTinyRSS";
if($("#login form").backend[1].checked) backend = "OwnCloud";
else if($("#login form").backend[2].checked) backend = "Pond";
var server_url = window.location.href.split("#")[0].replace(/\/FeedMonkey\//, ''); var server_url = $("#url").value;
console.log(server_url)
var user = $("#un").value; var user = $("#un").value;
var password = $("#pw").value; var password = $("#pw").value;
@ -53,6 +67,7 @@ Login.prototype.authenticate = function(e) {
if(data.version) { if(data.version) {
var auth = btoa(user + ':' + password); var auth = btoa(user + ':' + password);
localStorage.server_url = server_url; localStorage.server_url = server_url;
localStorage.username = user;
localStorage.session_id = auth; localStorage.session_id = auth;
localStorage.backend = "OwnCloud"; localStorage.backend = "OwnCloud";
_this.app.after_login(localStorage.backend); _this.app.after_login(localStorage.backend);
@ -69,6 +84,7 @@ Login.prototype.authenticate = function(e) {
Pond.login(server_url, user, password, function(data) { Pond.login(server_url, user, password, function(data) {
if(data.session_token) { if(data.session_token) {
localStorage.server_url = server_url; localStorage.server_url = server_url;
localStorage.username = user;
localStorage.session_id = data.session_token; localStorage.session_id = data.session_token;
localStorage.backend = "Pond"; localStorage.backend = "Pond";
_this.app.after_login(localStorage.backend); _this.app.after_login(localStorage.backend);
@ -92,6 +108,7 @@ Login.prototype.authenticate = function(e) {
} else { } else {
localStorage.server_url = server_url; localStorage.server_url = server_url;
localStorage.username = user;
localStorage.session_id = data.session_id; localStorage.session_id = data.session_id;
localStorage.backend = "TinyTinyRSS"; localStorage.backend = "TinyTinyRSS";
_this.app.after_login(localStorage.backend); _this.app.after_login(localStorage.backend);
@ -106,7 +123,29 @@ Login.prototype.authenticate = function(e) {
return false; return false;
}; };
Login.prototype.fillLoginFormFromLocalStorage = function() {
var serverUrl = localStorage.server_url;
if (serverUrl) {
$("#url").value = serverUrl;
}
var userName = localStorage.username;
if (userName) {
$("#un").value = userName;
}
var backendName = localStorage.backend;
if (backendName === "TinyTinyRSS") {
$("#login form").backend[0].checked = true;
}
else if (backendName === "OwnCloud") {
$("#login form").backend[1].checked = true;
}
else if (backendName === "Pond") {
$("#login form").backend[2].checked = true;
}
}
Login.prototype.log_out = function() { Login.prototype.log_out = function() {
this.fillLoginFormFromLocalStorage();
localStorage.removeItem("server_url"); localStorage.removeItem("server_url");
localStorage.removeItem("session_id"); localStorage.removeItem("session_id");
localStorage.removeItem("unread_articles"); localStorage.removeItem("unread_articles");

259
js/OwnCloud.js Normal file
View file

@ -0,0 +1,259 @@
function OwnCloud(app, server_url, user_pass_btoa) {
this.app = app;
this.server_url = server_url;
this.session_id = user_pass_btoa;
this.feeds = {};
var feeds = localStorage.feeds;
if(feeds) this.feeds = JSON.parse(feeds);
window.addEventListener("offline", this.onoffline.bind(this));
window.addEventListener("online", this.ononline.bind(this));
}
OwnCloud.prototype.onoffline = function() {
// Do nothing
};
OwnCloud.prototype.ononline = function() {
["read", "unread", "starred", "unstarred"].forEach(function(type) {
var articles = localStorage[type + "_articles"];
if(articles) {
var callback = function(ok) { if(ok) localStorage[type + "_articles"] = null }
this.call("setArticles" + type.capitalize(), [JSON.parse(articles), callback]);
}
});
};
OwnCloud.prototype.doOperation = function(method, operation, new_options, callback) {
if(!navigator.onLine) {
callback(null);
return;
}
var url = this.server_url + "/index.php/apps/news/api/v1-2/" + operation;
var options = {};
for (var key in new_options) {
options[key] = new_options[key];
}
if(method == "GET" || method == "HEAD") {
var a = [];
for(var key in options) {
a.push(key + "=" + options[key]);
}
url += "?" + a.join("&");
}
var xhr = new XMLHttpRequest({mozSystem: true});
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
if(callback)
callback(JSON.parse(xhr.responseText));
} else {
if(xhr.status != 0) alert("error: " + xhr.status + " " + xhr.statusText);
if(callback) callback(null);
}
}
}
xhr.open(method, url, true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.withCredentials = true;
xhr.setRequestHeader('Authorization', 'Basic ' + this.session_id);
var body = JSON.stringify(options);
xhr.send(body);
}
OwnCloud.prototype.reload = function(callback,limit) {
var _this = this;
this.getFeeds(function() { _this.getUnreadFeeds(callback,0,limit); });
};
OwnCloud.prototype.getUnreadFeeds = function(callback, skip, limit) {
if(skip) {
skip = skip[skip.length - 1].id;
}
var options = {
batchSize: limit || 700,
offset: skip || 0,
type: 3,
id: 0,
getRead: false
};
var _this = this;
this.doOperation("GET", "items", options, function(data) {
var items = data.items;
function isFeedAvailable(o) {
return !!_this.feeds[o.feedId];
}
if(items.every(isFeedAvailable)) {
callback(items.map(_this.normalize_article, _this));
} else {
_this.getFeeds(function() {
callback(items.map(_this.normalize_article, _this));
});
}
});
};
OwnCloud.prototype.toString = function() {
return "OwnCloud"
};
OwnCloud.prototype.getFeeds = function(callback) {
var _this = this;
this.doOperation("GET", "feeds", {}, function(data) {
_this.feeds = {};
for (var i = 0; i < data.feeds.length; i++) {
var feed = data.feeds[i];
_this.feeds[feed.id] = feed;
}
localStorage.feeds = JSON.stringify(_this.feeds);
callback();
});
};
OwnCloud.prototype.setArticlesRead = function(articles, callback) {
var options = {
items: articles.map(function(o) { return o.id; }),
};
if (navigator.onLine) {
this.doOperation("PUT", "items/read/multiple", options, callback);
} else {
this.append("read_articles", articles);
}
}
OwnCloud.prototype.setArticleRead = function(article, callback) {
this.setArticlesRead([article], callback);
}
OwnCloud.prototype.setArticlesUnread = function(articles, callback) {
var options = {
items: articles.map(function(o) { return o.id; }),
};
if (navigator.onLine) this.doOperation("PUT", "items/unread/multiple", options, callback);
else {
this.append("unread_articles", articles);
}
};
OwnCloud.prototype.setArticleUnread = function(article, callback) {
this.setArticlesUnread([article], callback);
}
OwnCloud.prototype.setArticlesStarred = function(articles, callback) {
var options = {
items: articles.map(function(o) { return { feedId: o.feed_id, guidHash: o.guid_hash }; })
};
if (navigator.onLine) {
this.doOperation("PUT", "items/star/multiple", options, callback);
} else {
this.append("starred_articles", articles);
}
};
OwnCloud.prototype.setArticleStarred = function(article, callback) {
this.setArticlesStarred([article], callback);
}
OwnCloud.prototype.setArticlesUnstarred = function(articles, callback) {
var options = {
items: articles.map(function(o) { return { feedId: o.feed_id, guidHash: o.guid_hash }; })
};
if (navigator.onLine) {
this.doOperation("PUT", "items/unstar/multiple", options, callback);
} else {
this.append("unstarred_articles", articles);
}
};
OwnCloud.prototype.setArticleUnstarred = function(articles, callback) {
this.setArticlesUnstarred([articles], callback);
}
OwnCloud.prototype.normalize_article = function(article) {
var feed = this.feeds[article.feedId];
var feed_title = "";
if(feed) {
feed_title = feed.title;
}
return {
id: article.id,
guid_hash: article.guidHash,
title: article.title,
content: article.body,
feed_title: feed_title,
feed_id: article.feedId,
excerpt: article.body.stripHTML().substring(0, 100),
updated: article.pubDate,
link: article.link,
marked: article.starred,
unread: article.unread
}
};
OwnCloud.prototype.logOut = function() {
this.doOperation("logout");
localStorage.feeds = null;
};
OwnCloud.prototype.getFeedFor = function(o) {
return this.feeds[o.feedId];
};
OwnCloud.prototype.append = function(key, array) {
var tmp = localStorage[key];
if (typeof tmp !== "undefined") tmp = JSON.parse(tmp);
else tmp = [];
tmp.concat(array);
localStorage[key] = JSON.stringify(tmp);
};
OwnCloud.login = function(server_url, user, password, callback) {
var url = server_url + "/index.php/apps/news/api/v1-2/version";
var xhr = new XMLHttpRequest({mozSystem: true});
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
callback(JSON.parse(xhr.responseText))
} else {
if(xhr.status == 0) {
alert("Something went wrong, please check your credentials and the server address")
} else {
alert("error: " + xhr.status + " " + xhr.statusText);
}
}
}
}
xhr.open("GET", url, true);
xhr.withCredentials = true;
var auth = btoa(user + ':' + password);
xhr.setRequestHeader('Authorization', 'Basic ' + auth);
xhr.send();
}

View file

@ -15,7 +15,13 @@ Pond.prototype.onoffline = function() {
}; };
Pond.prototype.ononline = function() { Pond.prototype.ononline = function() {
// Send read ["read", "unread"].forEach(function(type) {
var articles = localStorage[type + "_articles"];
if(articles) {
var callback = function(ok) { if(ok) localStorage[type + "_articles"] = null }
this.call("setArticles" + type.capitalize(), [JSON.parse(articles), callback]);
}
});
}; };
Pond.prototype.toString = function() { Pond.prototype.toString = function() {
@ -69,15 +75,15 @@ Pond.prototype.doOperation = function(method, operation, new_options, callback)
}; };
Pond.prototype.reload = function(callback) { Pond.prototype.reload = function(callback,limit) {
var _this = this; var _this = this;
this.getFeeds(function() { _this.getUnreadFeeds(callback); }); this.getFeeds(function() { _this.getUnreadFeeds(callback,0,limit); });
}; };
Pond.prototype.getUnreadFeeds = function(callback, skip) { Pond.prototype.getUnreadFeeds = function(callback, skip, limit) {
var options = { var options = {
status: "unread", status: "unread",
limit: 100 limit: limit || 100
}; };
if(skip && skip.length > 0) { if(skip && skip.length > 0) {
@ -107,6 +113,8 @@ Pond.prototype.getFeeds = function(callback) {
var _this = this; var _this = this;
this.doOperation("GET", "subscriptions", {}, function(feeds) { this.doOperation("GET", "subscriptions", {}, function(feeds) {
if(!feeds) return;
_this.feeds = {}; _this.feeds = {};
for (var i = 0; i < feeds.length; i++) { for (var i = 0; i < feeds.length; i++) {
var feed = feeds[i]; var feed = feeds[i];
@ -157,11 +165,11 @@ Pond.prototype.setArticleStatus = function(article, callback, status) {
var url = "subscriptions/" + article.feed_id + "/articles/" + article.id var url = "subscriptions/" + article.feed_id + "/articles/" + article.id
if (navigator.onLine) this.doOperation("PUT", url, options, callback); if (navigator.onLine) {
else { this.doOperation("PUT", url, options, callback);
this.append("unread_articles", articles); } else {
this.append(status + "_articles", articles);
} }
} }
Pond.prototype.setArticleRead = function(article, callback) { Pond.prototype.setArticleRead = function(article, callback) {
@ -192,6 +200,16 @@ Pond.prototype.setArticleUnstarred = function(articles, callback) {
// not implemented yet in Pond // not implemented yet in Pond
} }
TinyTinyRSS.prototype.append = function(key, array) {
var tmp = localStorage[key];
if (typeof tmp !== "undefined") tmp = JSON.parse(tmp);
else tmp = [];
tmp.concat(array);
localStorage[key] = JSON.stringify(tmp);
};
Pond.prototype.logOut = function() { Pond.prototype.logOut = function() {
this.doOperation("DELETE", "auth/sessions/" + this.session_token ); this.doOperation("DELETE", "auth/sessions/" + this.session_token );
localStorage.feeds = null; localStorage.feeds = null;
@ -213,7 +231,11 @@ Pond.login = function(server_url, user, password, callback) {
if(xhr.status == 201) { if(xhr.status == 201) {
callback(JSON.parse(xhr.responseText)) callback(JSON.parse(xhr.responseText))
} else { } else {
alert("error: " + typeof(xhr.status) + " " + xhr.statusText + "\n\n" + xhr.responseText) if(xhr.status == 0) {
alert("Something went wrong, please check your credentials and the server address")
} else {
alert("error: " + typeof(xhr.status) + " " + xhr.statusText + "\n\n" + xhr.responseText);
}
} }
} }
} }

225
js/TinyTinyRSS.js Normal file
View file

@ -0,0 +1,225 @@
function TinyTinyRSS(app, server_url, session_id) {
this.app = app;
this.server_url = server_url;
this.session_id = session_id;
window.addEventListener("offline", this.onoffline.bind(this));
window.addEventListener("online", this.ononline.bind(this));
}
TinyTinyRSS.prototype.onoffline = function() {
// Do nothing
};
TinyTinyRSS.prototype.ononline = function() {
["read", "unread", "starred", "unstarred", "published", "unpublished"].forEach(function(type) {
var articles = localStorage[type + "_articles"];
if(articles) {
var callback = function(ok) { if(ok) localStorage[type + "_articles"] = null }
this.call("setArticles" + type.capitalize(), [JSON.parse(articles), callback]);
}
});
};
TinyTinyRSS.prototype.doOperation = function(operation, new_options, callback) {
if(!navigator.onLine) {
callback(null);
return;
}
var url = this.server_url + "/api/";
var options = {
sid: this.session_id,
op: operation
};
for (var key in new_options) {
options[key] = new_options[key];
}
var xhr = new XMLHttpRequest({mozSystem: true});
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
if(callback)
callback(JSON.parse(xhr.responseText).content);
} else {
if(xhr.status != 0) alert("error: " + xhr.status + " " + xhr.statusText);
if(callback) callback(null);
}
}
}
xhr.open("POST", url, true);
xhr.send(JSON.stringify(options));
}
TinyTinyRSS.prototype.reload = function(callback,limit) {
this.getUnreadFeeds(callback, 0, limit);
};
TinyTinyRSS.prototype.getUnreadFeeds = function(callback, skip, limit) {
skip = skip.length;
var options = {
show_excerpt: false,
view_mode: "unread",
show_content: true,
feed_id: -4,
limit: limit || 0,
skip: skip || 0
};
this.doOperation("getHeadlines", options, callback);
}
TinyTinyRSS.prototype.setArticlesRead = function(articles, callback) {
var options = {
article_ids: articles.map(function(o) { return o.id }).join(","),
mode: 0,
field: 2
};
if (navigator.onLine) {
this.doOperation("updateArticle", options, callback);
} else {
this.append("read_articles", articles);
}
};
TinyTinyRSS.prototype.setArticleRead = function(article, callback) {
this.setArticlesRead([article], callback);
};
TinyTinyRSS.prototype.setArticlesUnread = function(articles, callback) {
var options = {
article_ids: articles.map(function(o) { return o.id }).join(","),
mode: 1,
field: 2
};
if (navigator.onLine) {
this.doOperation("updateArticle", options, callback);
} else {
this.append("unread_articles", articles);
}
};
TinyTinyRSS.prototype.setArticleUnread = function(article, callback) {
this.setArticlesUnread([article], callback);
};
TinyTinyRSS.prototype.setArticlesStarred = function(articles, callback) {
var options = {
article_ids: articles.map(function(o) { return o.id }).join(","),
mode: 1,
field: 0
};
if (navigator.onLine) {
this.doOperation("updateArticle", options);
} else {
this.append("starred_articles", articles);
}
};
TinyTinyRSS.prototype.setArticleStarred = function(article, callback) {
this.setArticlesStarred([article], callback);
};
TinyTinyRSS.prototype.setArticlesUnstarred = function(articles, callback) {
var options = {
article_ids: articles.map(function(o) { return o.id}).join(","),
mode: 0,
field: 0
};
if (navigator.onLine) {
this.doOperation("updateArticle", options, callback);
} else {
this.append("unstarred_articles", articles);
}
};
TinyTinyRSS.prototype.setArticleUnstarred = function(article, callback) {
this.setArticlesUnstarred([article], callback);
};
TinyTinyRSS.prototype.setArticlesPublished = function(articles, callback) {
var options = {
article_ids: articles.map(function(o) { return o.id }).join(","),
mode: 1,
field: 1
};
if (navigator.onLine) {
this.doOperation("updateArticle", options);
} else {
this.append("published_articles", articles);
}
};
TinyTinyRSS.prototype.setArticlePublished = function(article, callback) {
this.setArticlesPublished([article], callback);
};
TinyTinyRSS.prototype.setArticlesUnpublished = function(articles, callback) {
var options = {
article_ids: articles.map(function(o) { return o.id}).join(","),
mode: 0,
field: 1
};
if (navigator.onLine) {
this.doOperation("updateArticle", options, callback);
} else {
this.append("unpublished_articles", articles);
}
};
TinyTinyRSS.prototype.setArticleUnpublished = function(article, callback) {
this.setArticlesUnpublished([article], callback);
};
TinyTinyRSS.prototype.append = function(key, array) {
var tmp = localStorage[key];
if (typeof tmp !== "undefined") tmp = JSON.parse(tmp);
else tmp = [];
tmp.concat(array);
localStorage[key] = JSON.stringify(tmp);
};
TinyTinyRSS.prototype.logOut = function() {
this.doOperation("logout");
};
TinyTinyRSS.login = function(server_url, user, password, callback) {
var url = server_url + "/api/";
var options = {op: "login", user: user, password: password};
var xhr = new XMLHttpRequest({mozSystem: true});
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
callback(JSON.parse(xhr.responseText).content)
} else {
if(xhr.status == 0) {
alert("Something went wrong, please check your credentials and the server address")
} else {
alert("error: " + xhr.status + " " + xhr.statusText)
}
}
}
}
xhr.open("POST", url, true);
xhr.send(JSON.stringify(options));
}

View file

@ -63,10 +63,5 @@ String.prototype.capitalize = function() {
return this.charAt(0).toUpperCase() + this.slice(1); return this.charAt(0).toUpperCase() + this.slice(1);
} }
String.prototype.urlify = function() {
var exp = /[^\>](https?:\/\/[^\s\<]*)/ig;
return this.replace(exp," <a href='$1'>$1</a>");
}
if(!window.app) window.app = new App(); if(!window.app) window.app = new App();

2163
js/hammer.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,599 +0,0 @@
/*
* Jester JavaScript Library v0.3
* http://github.com/plainview/Jester
*
* Easy JavaScript gesture recognition.
*
* Released under MIT License
*
* Copyright (C) 2011 by Scott Seaward
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function(container, undefined) {
var Jester = container.Jester = {
cache : {},
cacheId : "Jester" + (new Date()).getTime(),
guid : 0,
// The Jester constructor
Watcher : function(element, options) {
var that = this,
cacheId = Jester.cacheId,
cache = Jester.cache,
gestures = "swipe flick tap doubletap pinchnarrow pinchwiden pinchend";
if(!element || !element.nodeType) {
throw new TypeError("Jester: no element given.");
}
// if this element hasn't had Jester called on it before,
// set it up with a cache entry and give it the expando
if(typeof element[cacheId] !== "number") {
element[cacheId] = Jester.guid;
Jester.guid++;
}
var elementId = element[cacheId];
if(!(elementId in cache)) {
Jester.cache[elementId] = {};
}
var elementCache = Jester.cache[elementId];
if(!("options" in elementCache)) {
elementCache.options = {};
}
options = options || elementCache.options || {};
// cache the option values for reuse or, if options already
// exist for this element, replace those that have been
// specified
if(elementCache.options !== options) {
for(var prop in options) {
if(elementCache.options[prop]) {
if(elementCache.options[prop] !== options[prop]) {
elementCache.options[prop] = options[prop];
}
}
else {
elementCache.options[prop] = options[prop];
}
}
}
if(!("eventSet" in elementCache) || !(elementCache.eventSet instanceof Jester.EventSet)) {
elementCache.eventSet = new Jester.EventSet(element);
}
if(!elementCache.touchMonitor) {
elementCache.touchMonitor = new Jester.TouchMonitor(element);
}
var events = elementCache.eventSet;
var touches = elementCache.touchMonitor;
this.id = element[cacheId];
this.bind = function(evt, fn) {
if(evt && typeof evt === "string" && typeof fn === "function") {
events.register(evt, fn);
}
return this;
};
// create shortcut bind methods for all gestures
gestures.split(" ").forEach(function(gesture) {
this[gesture] = function(fn) {
return this.bind(gesture, fn);
};
}, that);
this.start = function(fn) {
return this.bind("start", fn);
};
this.during = function(fn) {
return this.bind("during", fn);
};
this.end = function(fn) {
return this.bind("end", fn);
};
// wrapper to cover all three pinch methods
this.pinch = function(fns) {
if(typeof fns !== "undefined") {
// if its just a function it gets assigned to pinchend
if(typeof fns === "function") {
that.pinchend(fns);
}
else if(typeof fns === "object") {
var method;
"narrow widen end".split(" ").forEach(function(eventExt) {
method = "pinch" + eventExt;
if(typeof fns[eventExt] === "function") {
that[method](fns[eventExt]);
}
});
}
}
};
this.halt = function() {
touches.stopListening();
events.clear();
delete elementCache.eventSet;
delete elementCache.touchMonitor;
};
},
EventSet : function(element) {
// all event names and their associated functions in an array i.e. "swipe" : [fn1, fn2, fn2]
var eventsTable = {};
this.eventsTable = eventsTable;
// register a handler with an event
this.register = function(eventName, fn) {
// if the event exists and has handlers attached to it, add this one to the array of them
if(eventsTable[eventName] && eventsTable[eventName].push) {
// make sure multiple copies of the same handler aren't inserted
if(!~eventsTable[eventName].indexOf(fn)) {
eventsTable[eventName].push(fn);
}
}
else {
// create a new array bound to the event containing only the handler passed in
eventsTable[eventName] = [fn];
}
};
this.release = function(eventName, fn) {
if(typeof eventName === "undefined") return;
// if a handler hasn't been specified, remove all handlers
if(typeof fn === "undefined") {
for(var handlers in eventsTable.eventName) {
delete eventsTable.eventName[handlers];
}
}
else {
// pull the given handler from the given event
if(eventsTable[eventName] && ~eventsTable[eventName].indexOf(fn))
{
eventsTable[eventName].splice(eventsTable[eventName].indexOf(fn), 1);
}
}
// if the event has no more handlers registered to it, get rid of the event completely
if(eventsTable[eventName] && eventsTable[eventName].length === 0) {
delete eventsTable[eventName];
}
};
// completely remove all events and their handlers
this.clear = function() {
var events;
for(events in eventsTable) {
delete eventsTable[events];
}
};
// get all the handlers associated with an event
// return an empty array if nothing is registered with the given event name
this.getHandlers = function(eventName) {
if(eventsTable[eventName] && eventsTable[eventName].length) {
return eventsTable[eventName];
}
else {
return [];
}
};
// inject an array of handlers into the event table for the given event
// this will klobber all current handlers associated with the event
this.setHandlers = function(eventName, handlers) {
eventsTable[eventName] = handlers;
};
// execute all handlers associated with an event, passing each handler the arguments provided after the event's name.
this.execute = function(eventName) {
if(typeof eventName === "undefined") return;
// if the event asked for exists in the events table
if(eventsTable[eventName] && eventsTable[eventName].length) {
// get the arguments sent to the function
var args = Array.prototype.slice.call(arguments, 1);
// iterate throuh all the handlers
for(var i = 0; i < eventsTable[eventName].length; i++) {
// check current handler is a function
if(typeof eventsTable[eventName][i] === "function") {
// execute handler with the provided arguments
eventsTable[eventName][i].apply(element, args);
}
}
}
};
},
TouchMonitor : function(element)
{
var cacheId = Jester.cacheId,
elementId = element[cacheId],
cache = Jester.cache,
elementCache = cache[elementId],
opts = elementCache.options;
opts.move = opts.move || {};
opts.scale = opts.scale || {};
opts.tapDistance = opts.tapDistance || 0;
opts.tapTime = opts.tapTime || 20;
opts.doubleTapTime = opts.doubleTapTime || 300;
opts.swipeDistance = opts.swipeDistance || 200;
opts.flickTime = opts.flickTime || 300;
opts.flickDistance = opts.flickDistance || 100;
opts.deadX = opts.deadX || 0;
opts.deadY = opts.deadY || 0;
if(opts.capture !== false) opts.capture = true;
if(typeof opts.preventDefault !== "undefined" && opts.preventDefault !== false) opts.preventDefault = true;
if(typeof opts.preventDefault !== "undefined" && opts.stopPropagation !== false) opts.stopPropagation = true;
var eventSet = elementCache.eventSet;
var touches;
var previousTapTime = 0;
var touchStart = function(evt) {
touches = new Jester.TouchGroup(evt);
eventSet.execute("start", touches, evt);
if(opts.preventDefault) evt.preventDefault();
if(opts.stopPropagation) evt.stopPropagation();
};
var touchMove = function(evt) {
touches.update(evt);
eventSet.execute("during", touches, evt);
if(opts.preventDefault) evt.preventDefault();
if(opts.stopPropagation) evt.stopPropagation();
if(touches.numTouches() == 2) {
// pinchnarrow
if(touches.delta.scale() < 0.0) {
eventSet.execute("pinchnarrow", touches);
}
// pinchwiden
else if(touches.delta.scale() > 0.0) {
eventSet.execute("pinchwiden", touches);
}
}
};
var touchEnd = function(evt) {
var swipeDirection;
eventSet.execute("end", touches, evt);
if(opts.preventDefault) evt.preventDefault();
if(opts.stopPropagation) evt.stopPropagation();
if(touches.numTouches() == 1) {
// tap
if(touches.touch(0).total.x() <= opts.tapDistance && touches.touch(0).total.y() <= opts.tapDistance && touches.touch(0).total.time() < opts.tapTime) {
eventSet.execute("tap", touches);
}
// doubletap
if(touches.touch(0).total.time() < opts.tapTime) {
var now = (new Date()).getTime();
if(now - previousTapTime <= opts.doubleTapTime) {
eventSet.execute("doubletap", touches);
}
previousTapTime = now;
}
// swipe left/right
if(Math.abs(touches.touch(0).total.x()) >= opts.swipeDistance) {
swipeDirection = touches.touch(0).total.x() < 0 ? "left" : "right";
eventSet.execute("swipe", touches, swipeDirection);
}
// swipe up/down
if(Math.abs(touches.touch(0).total.y()) >= opts.swipeDistance) {
swipeDirection = touches.touch(0).total.y() < 0 ? "up" : "down";
eventSet.execute("swipe", touches, swipeDirection);
}
// flick
if(Math.abs(touches.touch(0).total.x()) >= opts.flickDistance && touches.touch(0).total.time() <= opts.flickTime) {
var flickDirection = touches.touch(0).total.x() < 0 ? "left" : "right";
eventSet.execute("flick", touches, flickDirection);
}
}
else if(touches.numTouches() == 2) {
// pinchend
if(touches.current.scale() !== 1.0) {
var pinchDirection = touches.current.scale() < 1.0 ? "narrowed" : "widened";
eventSet.execute("pinchend", touches, pinchDirection);
}
}
};
var stopListening = function() {
element.removeEventListener("touchstart", touchStart, opts.capture);
element.removeEventListener("touchmove", touchMove, opts.capture);
element.removeEventListener("touchend", touchEnd, opts.capture);
};
element.addEventListener("touchstart", touchStart, opts.capture);
element.addEventListener("touchmove", touchMove, opts.capture);
element.addEventListener("touchend", touchEnd, opts.capture);
return {
stopListening: stopListening
};
},
TouchGroup : function(event) {
var that = this;
var numTouches = event.touches.length;
var midpointX = 0;
var midpointY = 0;
var scale = event.scale;
var prevScale = scale;
var deltaScale = scale;
for(var i = 0; i < numTouches; i++) {
this["touch" + i] = new Jester.Touch(event.touches[i].pageX, event.touches[i].pageY);
midpointX = event.touches[i].pageX;
midpointY = event.touches[i].pageY;
}
function getNumTouches() {
return numTouches;
}
function getTouch(num) {
return that["touch" + num];
}
function getMidPointX() {
return midpointX;
}
function getMidPointY() {
return midpointY;
}
function getScale() {
return scale;
}
function getDeltaScale() {
return deltaScale;
}
function updateTouches(event) {
var mpX = 0;
var mpY = 0;
for(var i = 0; i < event.touches.length; i++) {
if(i < numTouches) {
that["touch" + i].update(event.touches[i].pageX, event.touches[i].pageY);
mpX += event.touches[i].pageX;
mpY += event.touches[i].pageY;
}
}
midpointX = mpX / numTouches;
midpointY = mpY / numTouches;
prevScale = scale;
scale = event.scale;
deltaScale = scale - prevScale;
}
return {
numTouches: getNumTouches,
touch: getTouch,
current: {
scale: getScale,
midX: getMidPointX,
midY: getMidPointY
},
delta: {
scale: getDeltaScale
},
update: updateTouches
};
},
Touch : function(_startX, _startY) {
var startX = _startX,
startY = _startY,
startTime = now(),
currentX = startX,
currentY = startY,
currentTime = startTime,
currentSpeedX = 0,
currentSpeedY = 0,
prevX = startX,
prevY = startX,
prevTime = startTime,
prevSpeedX = 0,
prevSpeedY = 0,
deltaX = 0,
deltaY = 0,
deltaTime = 0,
deltaSpeedX = 0,
deltaSpeedY = 0,
totalX = 0,
totalY = 0,
totalTime = 0;
// position getters
function getStartX() {
return startX;
}
function getStartY() {
return startY;
}
function getCurrentX() {
return currentX;
}
function getCurrentY() {
return currentY;
}
function getPrevX() {
return prevX;
}
function getPrevY() {
return prevY;
}
function getDeltaX() {
return deltaX;
}
function getDeltaY() {
return deltaY;
}
function getTotalX() {
return totalX;
}
function getTotalY() {
return totalY;
}
// time getters
function now() {
return (new Date()).getTime();
}
function getStartTime() {
return startTime;
}
function getCurrentTime() {
return currentTime;
}
function getPrevTime() {
return prevTime;
}
function getDeltaTime() {
return deltaTime;
}
function getTotalTime() {
return totalTime;
}
// speed getters
function getCurrentSpeedX() {
return currentSpeedX;
}
function getCurrentSpeedY() {
return currentSpeedY;
}
function getPrevSpeedX() {
return prevSpeedX;
}
function getPrevSpeedY() {
return prevSpeedY;
}
function getDeltaSpeedX() {
return deltaSpeedX;
}
function getDeltaSpeedY() {
return deltaSpeedY;
}
return {
start: {
x: getStartX,
y: getStartY,
speedX: 0,
speedY: 0,
time: getStartTime
},
current: {
x: getCurrentX,
y: getCurrentY,
time: getCurrentTime,
speedX: getCurrentSpeedX,
speedY: getCurrentSpeedY
},
prev: {
x: getPrevX,
y: getPrevY,
time: getPrevTime,
speedX: getPrevSpeedX,
speedY: getPrevSpeedY
},
delta: {
x: getDeltaX,
y: getDeltaY,
speedX: getDeltaSpeedX,
speedY: getDeltaSpeedY,
time: getDeltaTime
},
total: {
x: getTotalX,
y: getTotalY,
time: getTotalTime
},
update: function(_x, _y) {
prevX = currentX;
prevY = currentY;
currentX = _x;
currentY = _y;
deltaX = currentX - prevX;
deltaY = currentY - prevY;
totalX = currentX - startX;
totalY = currentY - startY;
prevTime = currentTime;
currentTime = now();
deltaTime = currentTime - prevTime;
totalTime = currentTime - startTime;
prevSpeedX = currentSpeedX;
prevSpeedY = currentSpeedY;
currentSpeedX = deltaX / (deltaTime/1000);
currentSpeedY = deltaY / (deltaTime/1000);
deltaSpeedX = currentSpeedX - prevSpeedX;
deltaSpeedY = currentSpeedY - prevSpeedY;
}
};
}
};
container.jester = function(el, opts) {
return new Jester.Watcher(el, opts);
};
}(window));

View file

@ -1,6 +1,6 @@
{ {
"name": "FeedMonkey", "name": "FeedMonkey",
"description": "A TinyTinyRSS mobile client with which you can read your RSS feeds and mark them as read on your server. Works also offline.", "description": "A feed mobile client with which you can read your RSS feeds and mark them as read on your server. Works also offline.",
"launch_path": "/index.html", "launch_path": "/index.html",
"icons": { "icons": {
"58": "/img/icon-58.png", "58": "/img/icon-58.png",
@ -15,9 +15,9 @@
"type": "privileged", "type": "privileged",
"permissions": { "permissions": {
"systemXHR": { "systemXHR": {
"description": "Connection with your own TinyTinyRSS server." "description": "Connection with your own server."
} }
}, },
"installs_allowed_from": ["*"], "installs_allowed_from": ["*"],
"version": "0.3.0" "version": "0.4.3"
} }